Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add new @ember/owner package (RFC 0821) #20271

Merged
merged 32 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a4de4f0
Introduce `@ember/owner` package with re-exports
chriskrycho Nov 16, 2022
cd27b29
Move `Resolver` to `@ember/-internals/owner`
chriskrycho Nov 16, 2022
bbacfdb
Move `TypeOptions` to `@ember/-internals/owner` as `RegisterOptions`
chriskrycho Nov 16, 2022
337118e
squash w/whatever adds KnownForTypeResult
chriskrycho Nov 16, 2022
3b01283
Fix some incorrect `@module` comments
chriskrycho Nov 16, 2022
edc1cf1
Rename `Owner` to `InternalOwner` in `-internals`
chriskrycho Nov 16, 2022
50e6aac
Debug-fail on type-unsafety with `assert`s
chriskrycho Nov 16, 2022
18be345
Distinguish `Factory` and `InternalFactory`
chriskrycho Nov 16, 2022
6ddeeec
Combine some imports for clarity
chriskrycho Nov 16, 2022
414aa6c
Propagate `FullName` type throughout the owner system
chriskrycho Nov 16, 2022
c29244a
@ember/engine/instance: fix a lint, add some comments
chriskrycho Nov 16, 2022
18313f9
Distinguish `FactoryManager` and `InternalFactoryManager`
chriskrycho Nov 16, 2022
f855303
Improve type safety for `.create()`
chriskrycho Nov 16, 2022
95ba2a5
Unify `Factory` and `DebugFactory` types in container
chriskrycho Nov 16, 2022
d68baae
Move `KnownForTypeResult` to `@ember/owner`
chriskrycho Nov 16, 2022
3a0bf77
Move `ContainerProxy` and `RegistryProxy` to `@ember/-internals/owner`
chriskrycho Nov 16, 2022
03bdc4c
Deprecate `getOwner` and `setOwner` from `@ember/application`
chriskrycho Nov 15, 2022
52b7330
Move docs for `getOwner` to `@ember/owner`
chriskrycho Nov 15, 2022
94276b5
Define a coherent `Owner` type hierarchy
chriskrycho Nov 15, 2022
d01a89c
Update `@ember/application` type tests for new `@ember/owner` types
chriskrycho Nov 16, 2022
ecd3db7
Introduce/update type tests for `Owner` internals
chriskrycho Nov 16, 2022
5658b13
Update preview types to match the implementation
chriskrycho Nov 16, 2022
63fb7cf
Fix white space in comments in `@ember/controller`
chriskrycho Nov 16, 2022
3af52bc
`EngineInstance` explicitly implements `Owner`
chriskrycho Nov 17, 2022
c339f5f
`Factory.create()` cannot be type safe
chriskrycho Nov 17, 2022
7e49f29
Fix lint errors in Owner packages
chriskrycho Nov 17, 2022
b2ce803
Fix a type error in CoreObject on TS@next
chriskrycho Nov 17, 2022
2340d2d
Fix another linting issue in `@ember/engine`
chriskrycho Nov 17, 2022
9cc6a8d
Disable a type safety/correctness assertion
chriskrycho Nov 17, 2022
7785b6f
Disable another type safety/correctness assertion
chriskrycho Nov 17, 2022
9763c71
Disable one more type safety/correctness assertion
chriskrycho Nov 17, 2022
b08757b
Fix explanatory comments in `@ember/-internals/owner`
chriskrycho Nov 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/@ember/-internals/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ The public API, specified on the application namespace should be considered the
@private
*/

export { default as Registry, Resolver, ResolverClass, privatize } from './lib/registry';
export { default as Registry, ResolverClass, privatize } from './lib/registry';
export { default as Container, getFactoryFor, setFactoryFor, INIT_FACTORY } from './lib/container';
127 changes: 78 additions & 49 deletions packages/@ember/-internals/container/lib/container.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { Factory, FactoryClass, Owner } from '@ember/-internals/owner';
import type {
InternalFactory,
FactoryClass,
InternalOwner,
RegisterOptions,
FactoryManager,
FullName,
} from '@ember/-internals/owner';
import { setOwner } from '@ember/-internals/owner';
import { dictionary } from '@ember/-internals/utils';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import type { DebugRegistry, TypeOptions } from './registry';
import type { DebugRegistry } from './registry';
import type Registry from './registry';

interface LeakTracking {
Expand Down Expand Up @@ -44,9 +51,9 @@ if (DEBUG) {
}

export interface ContainerOptions {
owner?: Owner;
owner?: InternalOwner;
cache?: { [key: string]: object };
factoryManagerCache?: { [key: string]: FactoryManager<any, any> };
factoryManagerCache?: { [key: string]: InternalFactoryManager<any, any> };
validationCache?: { [key: string]: boolean };
}

Expand All @@ -66,10 +73,10 @@ export interface ContainerOptions {
export default class Container {
static _leakTracking: LeakTracking;

readonly owner: Owner | null;
readonly owner: InternalOwner | null;
readonly registry: Registry & DebugRegistry;
cache: { [key: string]: object };
factoryManagerCache!: { [key: string]: FactoryManager<object> };
factoryManagerCache!: { [key: string]: InternalFactoryManager<object> };
readonly validationCache!: { [key: string]: boolean };
isDestroyed: boolean;
isDestroying: boolean;
Expand Down Expand Up @@ -137,10 +144,13 @@ export default class Container {
@private
@method lookup
@param {String} fullName
@param {TypeOptions} [options]
@param {RegisterOptions} [options]
@return {any}
*/
lookup(fullName: string, options?: TypeOptions): Factory<object> | object | undefined {
lookup(
fullName: string,
options?: RegisterOptions
): InternalFactory<object> | object | undefined {
if (this.isDestroyed) {
throw new Error(`Cannot call \`.lookup\` after the owner has been destroyed`);
}
Expand Down Expand Up @@ -172,7 +182,7 @@ export default class Container {
@method reset
@param {String} fullName optional key to reset; if missing, resets everything
*/
reset(fullName: string) {
reset(fullName: FullName) {
if (this.isDestroyed) return;
if (fullName === undefined) {
destroyDestroyables(this);
Expand Down Expand Up @@ -205,7 +215,7 @@ export default class Container {
@param {String} fullName
@return {any}
*/
factoryFor(fullName: string): FactoryManager<object> | undefined {
factoryFor(fullName: FullName): InternalFactoryManager<object> | undefined {
if (this.isDestroyed) {
throw new Error(`Cannot call \`.factoryFor\` after the owner has been destroyed`);
}
Expand All @@ -226,8 +236,8 @@ if (DEBUG) {
* set on the manager.
*/
function wrapManagerInDeprecationProxy<T extends object, C extends object | FactoryClass>(
manager: FactoryManager<T, C>
): FactoryManager<T, C> {
manager: InternalFactoryManager<T, C>
): InternalFactoryManager<T, C> {
let validator = {
set(_obj: T, prop: keyof T) {
throw new Error(
Expand All @@ -244,27 +254,27 @@ function wrapManagerInDeprecationProxy<T extends object, C extends object | Fact
let m = manager;
let proxiedManager = {
class: m.class,
create(props?: { [prop: string]: any }) {
create(props?: Partial<T>) {
return m.create(props);
},
};

return new Proxy(proxiedManager, validator as any) as any;
}

function isSingleton(container: Container, fullName: string) {
function isSingleton(container: Container, fullName: FullName) {
return container.registry.getOption(fullName, 'singleton') !== false;
}

function isInstantiatable(container: Container, fullName: string) {
function isInstantiatable(container: Container, fullName: FullName) {
return container.registry.getOption(fullName, 'instantiate') !== false;
}

function lookup(
container: Container,
fullName: string,
options: TypeOptions = {}
): Factory<object> | object | undefined {
fullName: FullName,
options: RegisterOptions = {}
): InternalFactory<object> | object | undefined {
let normalizedName = fullName;

if (
Expand All @@ -282,9 +292,9 @@ function lookup(

function factoryFor(
container: Container,
normalizedName: string,
fullName: string
): FactoryManager<object> | undefined {
normalizedName: FullName,
fullName: FullName
): InternalFactoryManager<object> | undefined {
let cached = container.factoryManagerCache[normalizedName];

if (cached !== undefined) {
Expand All @@ -301,7 +311,7 @@ function factoryFor(
factory._onLookup(fullName);
}

let manager = new FactoryManager(container, factory, fullName, normalizedName);
let manager = new InternalFactoryManager(container, factory, fullName, normalizedName);

if (DEBUG) {
manager = wrapManagerInDeprecationProxy(manager);
Expand All @@ -313,8 +323,8 @@ function factoryFor(

function isSingletonClass(
container: Container,
fullName: string,
{ instantiate, singleton }: TypeOptions
fullName: FullName,
{ instantiate, singleton }: RegisterOptions
) {
return (
singleton !== false &&
Expand All @@ -326,8 +336,8 @@ function isSingletonClass(

function isSingletonInstance(
container: Container,
fullName: string,
{ instantiate, singleton }: TypeOptions
fullName: FullName,
{ instantiate, singleton }: RegisterOptions
) {
return (
singleton !== false &&
Expand All @@ -339,8 +349,8 @@ function isSingletonInstance(

function isFactoryClass(
container: Container,
fullname: string,
{ instantiate, singleton }: TypeOptions
fullname: FullName,
{ instantiate, singleton }: RegisterOptions
) {
return (
instantiate === false &&
Expand All @@ -351,8 +361,8 @@ function isFactoryClass(

function isFactoryInstance(
container: Container,
fullName: string,
{ instantiate, singleton }: TypeOptions
fullName: FullName,
{ instantiate, singleton }: RegisterOptions
) {
return (
instantiate !== false &&
Expand All @@ -363,10 +373,10 @@ function isFactoryInstance(

function instantiateFactory(
container: Container,
normalizedName: string,
fullName: string,
options: TypeOptions
): Factory<object> | object | undefined {
normalizedName: FullName,
fullName: FullName,
options: RegisterOptions
): InternalFactory<object> | object | undefined {
let factoryManager = factoryFor(container, normalizedName, fullName);

if (factoryManager === undefined) {
Expand Down Expand Up @@ -445,40 +455,59 @@ export interface LazyInjection {
}

declare interface DebugFactory<T extends object, C extends FactoryClass | object = FactoryClass>
extends Factory<T, C> {
extends InternalFactory<T, C> {
_onLookup?: (fullName: string) => void;
_initFactory?: (factoryManager: FactoryManager<T, C>) => void;
_lazyInjections(): { [key: string]: LazyInjection };
_lazyInjections?: () => { [key: string]: LazyInjection };
_initFactory?: (factoryManager: InternalFactoryManager<T, C>) => void;
}

export const INIT_FACTORY = Symbol('INIT_FACTORY');

export function getFactoryFor(obj: any): FactoryManager<any, any> {
return obj[INIT_FACTORY];
interface MaybeHasInitFactory {
[INIT_FACTORY]?: InternalFactoryManager<object, FactoryClass | object>;
}

export function getFactoryFor(
obj: object
): InternalFactoryManager<object, FactoryClass | object> | undefined {
// SAFETY: since we know `obj` is an `object`, we also know we can safely ask
// whether a key is set on it.
return (obj as MaybeHasInitFactory)[INIT_FACTORY];
}

export function setFactoryFor(obj: any, factory: FactoryManager<any, any>): void {
obj[INIT_FACTORY] = factory;
export function setFactoryFor<T extends object, C extends FactoryClass | object>(
obj: object,
factory: InternalFactoryManager<T, C>
): void {
// SAFETY: since we know `obj` is an `object`, we also know we can safely set
// a key it safely at this location. (The only way this could be blocked is if
// someone has gone out of their way to use `Object.defineProperty()` with our
// internal-only symbol and made it `writable: false`.)
(obj as MaybeHasInitFactory)[INIT_FACTORY] = factory;
}

export class FactoryManager<T extends object, C extends FactoryClass | object = FactoryClass> {
export class InternalFactoryManager<
T extends object,
C extends FactoryClass | object = FactoryClass
> implements FactoryManager<T>
{
readonly container: Container;
readonly owner: Owner | null;
readonly class: Factory<T, C> & DebugFactory<T, C>;
readonly fullName: string;
readonly owner: InternalOwner | null;
readonly class: DebugFactory<T, C>;
readonly fullName: FullName;
readonly normalizedName: string;
private madeToString: string | undefined;
injections: { [key: string]: unknown } | undefined;

constructor(
container: Container,
factory: Factory<T, C>,
fullName: string,
factory: InternalFactory<T, C>,
fullName: FullName,
normalizedName: string
) {
this.container = container;
this.owner = container.owner;
this.class = factory as Factory<T, C> & DebugFactory<T, C>;
this.class = factory;
this.fullName = fullName;
this.normalizedName = normalizedName;
this.madeToString = undefined;
Expand All @@ -493,7 +522,7 @@ export class FactoryManager<T extends object, C extends FactoryClass | object =
return this.madeToString;
}

create(options?: { [prop: string]: any }) {
create(options?: Partial<T>) {
let { container } = this;

if (container.isDestroyed) {
Expand Down
Loading