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

Add compatibility layer for typestack/typedi #189

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[submodule "src/contrib/upstream/tree"]
path = src/contrib/upstream/tree
url = https://github.com/typestack/typedi

# The version of typestack/typedi available on NPM actually publishes the code
# in the master branch, as opposed to the develop branch (which has significant
# changes.) For simplicity's sake, we're only going to support master, as the
# module seems to be more or less abandoned, so develop will most likely remain
# in development for eternity.
branch = master
67 changes: 4 additions & 63 deletions src/container-instance.class.mts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ export class ContainerInstance implements Disposable {
*
* @throws Error
* This exception is thrown if the container has been disposed.
*
* @remarks
* Internally, identifiers with the `multiple` bit set to `true` are stored in the
* {@link ContainerInstance.multiServiceIds} array
*/
public getMany<T = unknown>(identifier: ServiceIdentifier<T>, recursive?: boolean): T[] {
/**
Expand Down Expand Up @@ -554,69 +558,6 @@ export class ContainerInstance implements Disposable {
): T[] | U {
this.throwIfDisposed();

// TODO: redo this, very clunky wording.
/**
* # An explanation of `multiple: true` semantics
*
* Internally, we store IDs set with `{ multiple: true }` as objects containing anonymous tokens
* which are then stored in the usual metadata map. For every many-to-one value stored with the
* flag gets its own token, and that token is then added to an object which is stored in the
* "multiServiceIds" Map, with the public identifier used as a key."
*
* To demonstrate this, let's provide an example:
*
* ```ts
* const NAME = new Token<string>();
*
* Container.set({ id: NAME, multiple: true, value: 1 });
* Container.set({ id: NAME, multiple: true, value: 2 });
* ```
*
* *Internally, this code will result in the following:*
*
* The container checks if the identifier (which, in this case, is `NAME`) is present
* in the {@link ContainerInstance.multiServiceIds} Map. If it is not, an object is
* created with the following properties:
*
* ```ts
* interface ManyServicesMetadata {
* scope: ContainerScope;
* tokens: Token<unknown>[];
* }
* ```
*
* This object is then stored in the {@link Container.multiServiceIds} Map, with the key
* being the identifier passed to {@link Container.set} by the user. In the above example,
* the identifier would be the `NAME` token.
*
* 1. The `scope` property is a {@link ContainerScope}.
* It tells {@link ContainerInstance.getManyOrDefault} how to resolve the identifier.
* 2. The `tokens` property is explained below.
*
* For each call to {@link ContainerInstance.set} with `multiple: true`, a new {@link Token} is created.
* This would then be passed to {@link ContainerInstance.set}, much like an ordinary call to set a new value.
*
* The new {@link Token} is then bound to a value.
* In the case of the above example, the value is the number 1.
*
* Once the token has been bound, it is then added to the array of tokens
* inside the {@link ManyServicesMetadata} object referenced above.
*
* ---
*
* In the presence of new features, this deceptively simplistic design has required some
* specialized support to accomplish correctly.
*
* For example, in the case of the {@link ContainerTreeVisitor} API,
* a flag ({@link ContainerInstance.isRetrievingPrivateToken} was required to prevent callers
* being notified of the retrieval of anonymous tokens to retrieve values required by a call
* to {@link ContainerInstance.getMany} (or its counterparts).
*
* Furthermore, this design has one implicit side-effect: the scope of all values associated
* with a single identifier must be equal. For example, you would not be able to set one
* value as a singleton, and have another as a transient service -- in the current API,
* this would not be possible.
*/
const [location, idMap] = this.resolveMultiID(identifier, recursive);

/** Notify listeners we have retrieved a service. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ContainerInstance as OurContainerInstance } from "../../../index.mjs";
import { ContainerIdentifier as UpstreamContainerIdentifier } from "../tree/src/types/container-identifier.type";

Check failure on line 2 in src/contrib/upstream/compat/upstream-container-instance-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module '../tree/src/types/container-identifier.type' or its corresponding type declarations.
import { UpstreamConstructable, UpstreamContainerInstance, UpstreamServiceIdentifier, UpstreamServiceMetadata, UpstreamToken } from "../upstream.types";

/**
* An internal map of {@link ContainerInstanceProxy} instances to their
* respective host {@link OurContainerInstance} objects.
*
* It should be noted that the {@link OurContainerInstance} instances are not direct
* references to the host container; instead, they are intermediary containers that
* are then children of the respective host containers.
*
* If a container is created via `new`, the first method call on that container
* shall set the respective host in this map to `null` -- in this mode, the stub
* shall operate independently as documented.
*/
const STUB_TO_HOST_CONTAINER_MAP: WeakMap<UpstreamContainerInstance, OurContainerInstance> = new WeakMap();

/**
* Find the host of the {@link ContainerInstanceProxy}, if it exists.
*
* @returns The host, or `null` if one doesn't exist.
*/
function getHostIfExists (stub: UpstreamContainerInstance) {
return STUB_TO_HOST_CONTAINER_MAP.get(stub) ?? null;
}

// for typestack container proxy, make a custom ContainerInstance (++) class which acts as a proxy (child) for the host container
// lets you use the metadataMap without custom Map class, usual ++ inheritance as codebases are not that different

/**
* An implementation of a proxy which acts identically to the `ContainerInstance` class
* found in the upstream `typestack/typedi` project.
*
* The construction of this class *is* allowed for 1:1 compatibility, though doing this
* means that the container shall operate independently, and therefore not as a proxy.
*
* @remarks
* The name of this class remains identical to upstream, as changing it may cause
* unintented compatibility issues.
*/
const ContainerInstanceProxy = class ContainerInstance implements UpstreamContainerInstance {

Check failure on line 42 in src/contrib/upstream/compat/upstream-container-instance-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Property 'services' of exported class expression may not be private or protected.
constructor (public readonly id: string) { }

// For the purpose of maximal backwards compatibility, we're also going to implement private Container methods.
private services: UpstreamServiceMetadata<unknown>[] = [ ];

has<T>(type: UpstreamConstructable<T>): boolean;
has<T>(id: string): boolean;
has<T>(id: UpstreamToken<T>): boolean;
has<T>(identifier: UpstreamServiceIdentifier): boolean {
return !!this.findService(identifier);

Check failure on line 52 in src/contrib/upstream/compat/upstream-container-instance-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Property 'findService' does not exist on type 'ContainerInstance'.
}


}

export function createContainerInstanceProxy (host: OurContainerInstance, id?: UpstreamContainerIdentifier) {

Check failure on line 58 in src/contrib/upstream/compat/upstream-container-instance-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Property 'services' of exported class expression may not be private or protected.
const stub = new ContainerInstanceProxy(id ?? host.id);
STUB_TO_HOST_CONTAINER_MAP.set(stub, host);

return stub;
}

export { ContainerInstanceProxy as ContainerInstanceStub };
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
UpstreamContainerIdentifier,
UpstreamContainerInstance,
UpstreamContainerRegistry
} from "../upstream.types";
import { ContainerInstanceStub } from "./upstream-container-instance-proxy.class.mjs";

const ContainerRegistryStub = class ContainerRegistry implements UpstreamContainerRegistry {

Check failure on line 8 in src/contrib/upstream/compat/upstream-container-registry-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Property 'containerMap' of exported class expression may not be private or protected.

Check failure on line 8 in src/contrib/upstream/compat/upstream-container-registry-proxy.class.mts

View workflow job for this annotation

GitHub Actions / Build

Property 'services' of exported class expression may not be private or protected.
private static readonly containerMap: Map<UpstreamContainerIdentifier, UpstreamContainerInstance> = new Map();
public static readonly defaultContainer = new ContainerInstanceStub('default');

public static registerContainer(container: UpstreamContainerInstance): void {
if (container instanceof ContainerInstanceStub === false) {
throw new Error('Only ContainerInstance instances can be registered.');
}

if (!ContainerRegistryStub.defaultContainer && container.id === 'default') {
throw new Error('You cannot register a container with the "default" ID.');
}

if (ContainerRegistryStub.containerMap.has(container.id)) {
throw new Error('Cannot register container with same ID.');
}

ContainerRegistryStub.containerMap.set(container.id, container);
}
}

export { ContainerRegistryStub };
1 change: 1 addition & 0 deletions src/contrib/upstream/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './upstream.types';
1 change: 1 addition & 0 deletions src/contrib/upstream/tree
Submodule tree added at 9bab80
27 changes: 27 additions & 0 deletions src/contrib/upstream/upstream.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// .
export { ContainerRegistry as UpstreamContainerRegistry } from './tree/src/container-registry.class';

Check failure on line 2 in src/contrib/upstream/upstream.types.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module './tree/src/container-registry.class' or its corresponding type declarations.
export { EMPTY_VALUE as UPSTREAM_EMPTY_VALUE } from './tree/src/empty.const';

Check failure on line 3 in src/contrib/upstream/upstream.types.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module './tree/src/empty.const' or its corresponding type declarations.

// ./index
export {
CannotInjectValueError as UpstreamCannotInjectValueError,
CannotInstantiateValueError as UpstreamCannotInstantiateValueError,
Constructable as UpstreamConstructable,
Container as UpstreamContainer,
ContainerInstance as UpstreamContainerInstance,
Handler as UpstreamHandler,
Inject as UpstreamInject,
InjectMany as UpstreamInjectMany,
Service as UpstreamService,
ServiceIdentifier as UpstreamServiceIdentifier,
ServiceMetadata as UpstreamServiceMetadata,
ServiceNotFoundError as UpstreamServiceNotFoundError,
ServiceOptions as UpstreamServiceOptions,
Token as UpstreamToken,
} from './tree/src/index';

Check failure on line 21 in src/contrib/upstream/upstream.types.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module './tree/src/index' or its corresponding type declarations.

// ./interfaces/
export { ContainerOptions as UpstreamContainerOptions } from './tree/src/interfaces/container-options.interface';

Check failure on line 24 in src/contrib/upstream/upstream.types.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module './tree/src/interfaces/container-options.interface' or its corresponding type declarations.
export { AbstractConstructable as UpstreamAbstractConstructable } from './tree/src/types/abstract-constructable.type';
export { ContainerIdentifier as UpstreamContainerIdentifier } from './tree/src/types/container-identifier.type';
export { ContainerScope as UpstreamContainerScope } from './tree/src/types/container-scope.type';
Loading