@amazon/vinyl-di 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +129 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +113 -0
- package/dist/index.js.map +7 -0
- package/dist/types/current/di/CyclicDependencyError.d.ts +12 -0
- package/dist/types/current/di/CyclicDependencyError.d.ts.map +1 -0
- package/dist/types/current/di/CyclicDependencyError.js +20 -0
- package/dist/types/current/di/di.d.ts +110 -0
- package/dist/types/current/di/di.d.ts.map +1 -0
- package/dist/types/current/di/di.js +148 -0
- package/dist/types/current/di/validation.d.ts +106 -0
- package/dist/types/current/di/validation.d.ts.map +1 -0
- package/dist/types/current/di/validation.js +10 -0
- package/dist/types/current/index.d.ts +5 -0
- package/dist/types/current/index.d.ts.map +1 -0
- package/dist/types/current/index.js +4 -0
- package/dist/types/current/tsconfig.tsbuildinfo +1 -0
- package/dist/types/ts3.7/di/CyclicDependencyError.d.ts +12 -0
- package/dist/types/ts3.7/di/di.d.ts +110 -0
- package/dist/types/ts3.7/di/validation.d.ts +131 -0
- package/dist/types/ts3.7/index.d.ts +5 -0
- package/dist/types/ts4.1/di/CyclicDependencyError.d.ts +12 -0
- package/dist/types/ts4.1/di/di.d.ts +110 -0
- package/dist/types/ts4.1/di/validation.d.ts +131 -0
- package/dist/types/ts4.1/index.d.ts +5 -0
- package/dist/types/ts4.7/di/CyclicDependencyError.d.ts +12 -0
- package/dist/types/ts4.7/di/di.d.ts +110 -0
- package/dist/types/ts4.7/di/validation.d.ts +131 -0
- package/dist/types/ts4.7/index.d.ts +5 -0
- package/docs/DEPENDENCY_INJECTION.md +129 -0
- package/package.json +81 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CyclicDependencyError: () => CyclicDependencyError,
|
|
24
|
+
ExternalDependency: () => ExternalDependency,
|
|
25
|
+
createContainer: () => createContainer,
|
|
26
|
+
externalDependencies: () => externalDependencies,
|
|
27
|
+
validateFactories: () => validateFactories
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/di/validation.ts
|
|
32
|
+
function validateFactories(dependencyMap) {
|
|
33
|
+
return dependencyMap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/di/di.ts
|
|
37
|
+
var import_vinyl_util2 = require("@amazon/vinyl-util");
|
|
38
|
+
var import_vinyl_util3 = require("@amazon/vinyl-util");
|
|
39
|
+
|
|
40
|
+
// src/di/CyclicDependencyError.ts
|
|
41
|
+
var import_vinyl_util = require("@amazon/vinyl-util");
|
|
42
|
+
var CyclicDependencyError = class _CyclicDependencyError extends import_vinyl_util.IllegalArgumentError {
|
|
43
|
+
get [Symbol.toStringTag]() {
|
|
44
|
+
return "CyclicDependencyError";
|
|
45
|
+
}
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
Object.setPrototypeOf(this, _CyclicDependencyError.prototype);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/di/di.ts
|
|
53
|
+
var ExternalDependency = /* @__PURE__ */ Symbol("externalDependency");
|
|
54
|
+
function externalDependencies(deps) {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const key of Object.keys(deps)) {
|
|
57
|
+
const provider = () => deps[key];
|
|
58
|
+
provider[ExternalDependency] = true;
|
|
59
|
+
out[key] = provider;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function createContainer(factories) {
|
|
64
|
+
let disposed = false;
|
|
65
|
+
const disposables = [];
|
|
66
|
+
const o = {};
|
|
67
|
+
let creationCount = 0;
|
|
68
|
+
Object.defineProperty(o, "__enumerableGuard", {
|
|
69
|
+
get() {
|
|
70
|
+
if (creationCount !== 0) {
|
|
71
|
+
throw new import_vinyl_util2.IllegalStateError(
|
|
72
|
+
"Enumeration is not supported within factories."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
configurable: false,
|
|
77
|
+
enumerable: true
|
|
78
|
+
});
|
|
79
|
+
for (const key in factories) {
|
|
80
|
+
if (key === "__enumerableGuard") continue;
|
|
81
|
+
let creating = false;
|
|
82
|
+
let created = false;
|
|
83
|
+
let value = null;
|
|
84
|
+
Object.defineProperty(o, key, {
|
|
85
|
+
get() {
|
|
86
|
+
if (disposed) throw new import_vinyl_util3.DisposedError();
|
|
87
|
+
if (!created) {
|
|
88
|
+
if (creating)
|
|
89
|
+
throw new CyclicDependencyError(
|
|
90
|
+
`The dependency with key '${key}' is cyclical. Check to ensure dependencies are not used in a property iterator.`
|
|
91
|
+
);
|
|
92
|
+
creationCount++;
|
|
93
|
+
creating = true;
|
|
94
|
+
const provider = factories[key];
|
|
95
|
+
const index = disposables.length;
|
|
96
|
+
const add = (disposableOrUnsub) => {
|
|
97
|
+
if (disposableOrUnsub) {
|
|
98
|
+
const disposable = (0, import_vinyl_util3.isDisposable)(disposableOrUnsub) ? disposableOrUnsub : (0, import_vinyl_util3.toDisposable)(disposableOrUnsub);
|
|
99
|
+
disposables.splice(index, 0, disposable);
|
|
100
|
+
}
|
|
101
|
+
return disposableOrUnsub;
|
|
102
|
+
};
|
|
103
|
+
value = provider(o, add);
|
|
104
|
+
if ((0, import_vinyl_util3.isPromiseLike)(value)) {
|
|
105
|
+
value = (0, import_vinyl_util3.toDisposablePromise)(value);
|
|
106
|
+
}
|
|
107
|
+
if ((0, import_vinyl_util3.isDisposable)(value) && !(ExternalDependency in provider))
|
|
108
|
+
disposables.splice(index, 0, value);
|
|
109
|
+
creating = false;
|
|
110
|
+
created = true;
|
|
111
|
+
creationCount--;
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
},
|
|
115
|
+
configurable: false,
|
|
116
|
+
enumerable: true
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
dependencies: o,
|
|
121
|
+
dispose() {
|
|
122
|
+
if (disposed) throw new import_vinyl_util3.DisposedError();
|
|
123
|
+
(0, import_vinyl_util3.disposeAll)(disposables);
|
|
124
|
+
disposed = true;
|
|
125
|
+
disposables.length = 0;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/di/validation.ts", "../src/di/di.ts", "../src/di/CyclicDependencyError.ts"],
|
|
4
|
+
"sourcesContent": ["/** Auto-generated from build scripts */\nexport * from './di/validation'\nexport * from './di/di'\nexport * from './di/CyclicDependencyError'\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/*\n * Provides validation utilities for type-safe dependency factories.\n */\n\nimport type { Factories, Factory } from './di'\nimport type { AddDisposable, IsNever, Merge } from '@amazon/vinyl-util'\n\n/**\n * A compile-time validation error that a dependency may not cycle back upon itself.\n */\nexport type NeverCyclicDependency<\n T extends PropertyKey,\n U extends PropertyKey[],\n> = [never, T, 'cyclic dependency', U]\n\n/**\n * A compile-time validation error that a provided dependency must be assignable\n * to factories for which it is an input.\n */\nexport type NeverWrongReturnType<Expected, Actual> = [\n never,\n 'expected provided dependency is not a subtype of what is used. Expected subtype of:',\n Expected,\n 'Actual: ',\n Actual,\n]\n\n/**\n * A compile-time validation error that a dependency must be a supertype of the actual type\n * provided.\n */\nexport type NeverWrongDependencyType<Expected, Actual> = [\n never,\n 'expected dependency is not a supertype of what is provided. Expected supertype of:',\n Expected,\n 'Actual: ',\n Actual,\n]\n\n/**\n * A factory expects a dependency that isn't defined.\n */\nexport type NeverMissingDependency<K> = [\n never,\n 'provider is missing a dependency for key:',\n K,\n]\n\n/**\n * A factory expects a non-void return type.\n */\nexport type NeverNoVoidReturnType = [never, 'void return type not allowed']\n\n/**\n * A factory must be a function.\n */\nexport type NeverInvalidFactoryType = [never, 'not a valid factory']\n\n/**\n * Dependencies must be an object.\n */\nexport type NeverInvalidDependenciesType = [never, 'not valid dependency type']\n\n/**\n * A compile-time validation error that dependency factories take an input of type object.\n */\nexport type NeverWrongDependenciesInput = [\n never,\n 'expected dependency factory to have an object argument',\n]\n\n/**\n * Provides a compile-time assertion that the given dependency map provides valid factories.\n */\nexport function validateFactories<Dm extends Factories>(\n dependencyMap: ValidFactories<Dm>\n): NoInfer<Dm> {\n return dependencyMap as Dm\n}\n\n/**\n * Validates that the type provided is a valid dependency map.\n *\n * This will guarantee that:\n * - All dependency factories exist with the correct signature.\n * - There are no cyclic dependencies.\n * - The factory's dependencies are provided.\n *\n * @see ValidFactory\n */\nexport type ValidFactories<Dm> = {\n readonly [K in keyof Dm]: ValidFactory<Dm, K>\n}\n\n/**\n * Validates that the dependency factory is valid.\n */\nexport type ValidFactory<Dm, K extends keyof Dm> = Dm[K] extends (\n deps: infer Deps,\n add: AddDisposable\n) => infer R\n ? [R] extends [void]\n ? [never, Dm[K]]\n : [R] extends [ExpectedInputType<Dm, K>]\n ? Dm[K] extends () => any\n ? Dm[K] // No dependencies\n : Deps extends ValidProviderDependencies<Deps, Dm>\n ? Dm[K]\n : (deps: ValidProviderDependencies<Deps, Dm>) => R\n : NeverWrongReturnType<ExpectedInputType<Dm, K>, R>\n : NeverInvalidFactoryType\n\ntype _ExpectedInputUnion<Dm, K extends keyof Dm> = Dm[keyof Dm] extends (\n deps: infer Deps,\n add: AddDisposable\n) => any\n ? K extends keyof Deps\n ? Deps[K]\n : never\n : never\n\nexport type ExpectedInputType<Dm, K extends keyof Dm> =\n // If the union provides `never`, it isn't a required dependency for any other factory, allow `any`\n IsNever<_ExpectedInputUnion<Dm, K>> extends true\n ? any\n : _ExpectedInputUnion<Dm, K>\n\n/**\n * A type validating dependency overrides when merged with existing dependency factories.\n */\nexport type ValidFactoryOverrides<Deps, Base, Ext> = Pick<\n ValidFactories<Merge<Base, Ext>>,\n keyof Ext\n> &\n Partial<Factories<Deps>>\n\n/**\n * Validate that the dependencies of a factory are not cyclical and that the return type of the\n * dependency is a subtype of what's needed.\n */\nexport type ValidProviderDependencies<Deps, Dm> = Deps extends object\n ? {\n readonly [K in keyof Deps]: K extends keyof Dm\n ? Dm[K] extends Factory<infer R>\n ? R extends Deps[K]\n ? K extends FlattenDependencies<Dm, K>\n ? NeverCyclicDependency<K, FlattenDependencies<Dm, K>>\n : Deps[K]\n : NeverWrongDependencyType<ReturnType<Dm[K]>, Deps[K]>\n : NeverMissingDependency<K>\n : NeverMissingDependency<K>\n }\n : NeverInvalidDependenciesType\n\n/**\n * Flattens the dependency graph for the given dependency factory.\n * E.g. given a map:\n * ```\n * type Map = {\n * a: () => 1,\n * b: (deps: { a: number }) => 2,\n * c: () => 3,\n * d: (deps: { b: number, c: number }) => 4,\n * }\n * ```\n *\n * `FlattenDependencies<'d', Map> // 'c' | 'b' | 'a'`\n */\nexport type FlattenDependencies<\n Dm,\n K extends keyof Dm,\n Current extends keyof Dm = never,\n> = Dm[K] extends () => any\n ? never\n : Dm[K] extends (deps: infer Expected) => any\n ? keyof Expected extends Current\n ? never\n :\n | keyof Expected\n | FlattenDependencies<\n Dm,\n keyof Expected & keyof Dm,\n Current | (keyof Expected & keyof Dm)\n >\n : never\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { type AnyRecord, IllegalStateError } from '@amazon/vinyl-util'\nimport {\n type AddDisposable,\n type Disposable,\n disposeAll,\n DisposedError,\n isDisposable,\n isPromiseLike,\n toDisposable,\n toDisposablePromise,\n} from '@amazon/vinyl-util'\nimport { CyclicDependencyError } from './CyclicDependencyError'\n\n/**\n * A factory function that produces a dependency.\n * Called at most once per container lifecycle.\n *\n * @param deps - A statically-typed record of other dependencies required by this factory,\n * provided by other factories.\n * @param add - A function used to register disposables or cleanup callbacks.\n * Use this to add teardown logic that should run when the container is disposed.\n *\n * @returns The constructed dependency. If it:\n * - Implements `Disposable`, it will automatically be disposed when the container is disposed.\n * Do not manually pass it to `add`.\n * - Is `PromiseLike`, the container will wait for the promise to resolve.\n * If the resolved value implements `Disposable`, it will also be disposed automatically.\n * Additionally, the promise will reject if the container is disposed before resolution.\n */\nexport type Factory<T> = (deps: any, add: AddDisposable) => T\n\n/**\n * A symbol used to mark dependency factories as externally owned.\n *\n * If a `Factory` has this symbol as a property,\n * it will be considered to be owned by a parent container and will not be\n * disposed regardless of whether it implements {@link Disposable}.\n *\n * This prevents the dependency injection container from taking ownership\n * of dependencies that should be managed elsewhere.\n *\n * @example\n * ```typescript\n * const provider = () => someExternalService\n * provider[ExternalDependency] = true\n * ```\n */\nexport const ExternalDependency = /* @__PURE__ */ Symbol('externalDependency')\n\n/**\n * Creates a Factories record which provides the values of `deps`.\n * These dependencies will be considered to be owned externally and will not\n * be disposed by containers using these factories.\n *\n * This is useful when you want to inject existing instances into a dependency\n * container without transferring ownership. The container will use these\n * dependencies but won't dispose them when the container is disposed.\n *\n * @param deps - A record of dependency instances to be wrapped as factories.\n * Each value will be returned as-is by the corresponding factory.\n * @returns An object with dependency factories for each member of deps.\n * Each provider is marked with the {@link ExternalDependency} symbol.\n *\n * @example\n * ```typescript\n * const logger = new Logger()\n * const config = { port: 8080 }\n *\n * const factories = validateFactories({\n * ...externalDependencies({ logger, config }),\n * controller: (deps: { readonly logger: Logger }) => ({\n * start: () => deps.logger.log('starting...'),\n * dispose: () => deps.logger.log('disposed')\n * })\n * })\n * const container = createContainer(factories)\n *\n * // logger and config won't be disposed when container is disposed\n * // controller will be disposed\n * container.dispose()\n * ```\n */\nexport function externalDependencies<T extends AnyRecord>(\n deps: T\n): Factories<T> {\n const out = {} as any\n for (const key of Object.keys(deps)) {\n const provider = () => deps[key as keyof T]\n provider[ExternalDependency] = true\n out[key] = provider\n }\n return out\n}\n\n/**\n * A map from dependency keys to factory functions.\n *\n * For each property `K` in `T`, if `T[K]` is a `Dependency`, the value must be\n * a `Factory` that produces `T[K]`. If `T[K]` is not a `Dependency`, the\n * property type resolves to `never`, causing a compile-time error.\n *\n * This ensures factories are defined only for valid dependencies and that\n * each factory is correctly typed.\n *\n * @typeParam T - A record whose values are expected to be `Dependency` types.\n */\nexport type Factories<T = any> = {\n readonly [K in keyof T]: Factory<T[K]>\n}\n\n/**\n * Given a dependency map of keys to dependency factories, provides the mapped dependency\n * instances.\n */\nexport type Dependencies<T extends Factories> = {\n readonly [K in keyof T]: ReturnType<T[K]>\n}\n\n/**\n * A dependencies container provides final dependencies and a handle for disposal.\n */\nexport interface Container<T> extends Disposable {\n readonly dependencies: T\n}\n\n/**\n * Creates a {@link Container} from a dependency factory map.\n *\n * The dependencies will be a map of getters where the key matches the key within\n * `factories` and the value is a lazily constructed dependency obtained from\n * the respective provider.\n *\n * @param factories An object of key value pairs where the values are dependency\n * providers.\n */\nexport function createContainer<Dm extends Factories>(\n factories: Dm\n): Container<Dependencies<Dm>> {\n let disposed = false\n const disposables: Disposable[] = []\n const o = {}\n let creationCount = 0\n Object.defineProperty(o, '__enumerableGuard', {\n get() {\n if (creationCount !== 0) {\n throw new IllegalStateError(\n 'Enumeration is not supported within factories.'\n )\n }\n },\n configurable: false,\n enumerable: true,\n })\n for (const key in factories) {\n if (key === '__enumerableGuard') continue\n let creating = false\n let created = false\n let value: any = null\n Object.defineProperty(o, key, {\n get(): any {\n if (disposed) throw new DisposedError()\n if (!created) {\n if (creating)\n throw new CyclicDependencyError(\n `The dependency with key '${key}' is cyclical. Check to ensure dependencies are not used in a property iterator.`\n )\n creationCount++\n creating = true\n const provider = factories[key]\n const index = disposables.length\n const add: AddDisposable = (disposableOrUnsub) => {\n if (disposableOrUnsub) {\n const disposable = isDisposable(disposableOrUnsub)\n ? disposableOrUnsub\n : toDisposable(disposableOrUnsub)\n disposables.splice(index, 0, disposable)\n }\n return disposableOrUnsub\n }\n value = provider(o, add)\n\n if (isPromiseLike(value)) {\n value = toDisposablePromise(value)\n }\n // Insert the disposable into the disposables list at the end of the list\n // as it was before construction. This ensures reentrancy due to\n // dependencies referenced during construction will be added after the\n // independent.\n if (\n isDisposable(value) &&\n !(ExternalDependency in provider)\n )\n disposables.splice(index, 0, value)\n creating = false\n created = true\n creationCount--\n }\n return value\n },\n configurable: false,\n enumerable: true,\n })\n }\n return {\n dependencies: o as Dependencies<Dm>,\n dispose() {\n if (disposed) throw new DisposedError()\n disposeAll(disposables)\n disposed = true\n disposables.length = 0\n },\n }\n}\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IllegalArgumentError } from '@amazon/vinyl-util'\n\n/**\n * Thrown at runtime if a dependency is determined to depend upon itself.\n *\n * When using TypeScript this should result in a {@link NeverCyclicDependency} compile-time\n * validation error when using {@link validateFactories} unless there was an unsafe cast.\n */\nexport class CyclicDependencyError extends IllegalArgumentError {\n get [Symbol.toStringTag](): string {\n return 'CyclicDependencyError'\n }\n\n constructor(message: string) {\n super(message)\n Object.setPrototypeOf(this, CyclicDependencyError.prototype)\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+EO,SAAS,kBACZ,eACW;AACX,SAAO;AACX;;;AC9EA,IAAAA,qBAAkD;AAClD,IAAAA,qBASO;;;ACVP,wBAAqC;AAQ9B,IAAM,wBAAN,MAAM,+BAA8B,uCAAqB;AAAA,EAC5D,KAAK,OAAO,WAAW,IAAY;AAC/B,WAAO;AAAA,EACX;AAAA,EAEA,YAAY,SAAiB;AACzB,UAAM,OAAO;AACb,WAAO,eAAe,MAAM,uBAAsB,SAAS;AAAA,EAC/D;AACJ;;;AD8BO,IAAM,qBAAqC,uBAAO,oBAAoB;AAmCtE,SAAS,qBACZ,MACY;AACZ,QAAM,MAAM,CAAC;AACb,aAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACjC,UAAM,WAAW,MAAM,KAAK,GAAc;AAC1C,aAAS,kBAAkB,IAAI;AAC/B,QAAI,GAAG,IAAI;AAAA,EACf;AACA,SAAO;AACX;AA2CO,SAAS,gBACZ,WAC2B;AAC3B,MAAI,WAAW;AACf,QAAM,cAA4B,CAAC;AACnC,QAAM,IAAI,CAAC;AACX,MAAI,gBAAgB;AACpB,SAAO,eAAe,GAAG,qBAAqB;AAAA,IAC1C,MAAM;AACF,UAAI,kBAAkB,GAAG;AACrB,cAAM,IAAI;AAAA,UACN;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,EAChB,CAAC;AACD,aAAW,OAAO,WAAW;AACzB,QAAI,QAAQ,oBAAqB;AACjC,QAAI,WAAW;AACf,QAAI,UAAU;AACd,QAAI,QAAa;AACjB,WAAO,eAAe,GAAG,KAAK;AAAA,MAC1B,MAAW;AACP,YAAI,SAAU,OAAM,IAAI,iCAAc;AACtC,YAAI,CAAC,SAAS;AACV,cAAI;AACA,kBAAM,IAAI;AAAA,cACN,4BAA4B,GAAG;AAAA,YACnC;AACJ;AACA,qBAAW;AACX,gBAAM,WAAW,UAAU,GAAG;AAC9B,gBAAM,QAAQ,YAAY;AAC1B,gBAAM,MAAqB,CAAC,sBAAsB;AAC9C,gBAAI,mBAAmB;AACnB,oBAAM,iBAAa,iCAAa,iBAAiB,IAC3C,wBACA,iCAAa,iBAAiB;AACpC,0BAAY,OAAO,OAAO,GAAG,UAAU;AAAA,YAC3C;AACA,mBAAO;AAAA,UACX;AACA,kBAAQ,SAAS,GAAG,GAAG;AAEvB,kBAAI,kCAAc,KAAK,GAAG;AACtB,wBAAQ,wCAAoB,KAAK;AAAA,UACrC;AAKA,kBACI,iCAAa,KAAK,KAClB,EAAE,sBAAsB;AAExB,wBAAY,OAAO,OAAO,GAAG,KAAK;AACtC,qBAAW;AACX,oBAAU;AACV;AAAA,QACJ;AACA,eAAO;AAAA,MACX;AAAA,MACA,cAAc;AAAA,MACd,YAAY;AAAA,IAChB,CAAC;AAAA,EACL;AACA,SAAO;AAAA,IACH,cAAc;AAAA,IACd,UAAU;AACN,UAAI,SAAU,OAAM,IAAI,iCAAc;AACtC,yCAAW,WAAW;AACtB,iBAAW;AACX,kBAAY,SAAS;AAAA,IACzB;AAAA,EACJ;AACJ;",
|
|
6
|
+
"names": ["import_vinyl_util"]
|
|
7
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/di/validation.ts
|
|
2
|
+
function validateFactories(dependencyMap) {
|
|
3
|
+
return dependencyMap;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/di/di.ts
|
|
7
|
+
import { IllegalStateError } from "@amazon/vinyl-util";
|
|
8
|
+
import {
|
|
9
|
+
disposeAll,
|
|
10
|
+
DisposedError,
|
|
11
|
+
isDisposable,
|
|
12
|
+
isPromiseLike,
|
|
13
|
+
toDisposable,
|
|
14
|
+
toDisposablePromise
|
|
15
|
+
} from "@amazon/vinyl-util";
|
|
16
|
+
|
|
17
|
+
// src/di/CyclicDependencyError.ts
|
|
18
|
+
import { IllegalArgumentError } from "@amazon/vinyl-util";
|
|
19
|
+
var CyclicDependencyError = class _CyclicDependencyError extends IllegalArgumentError {
|
|
20
|
+
get [Symbol.toStringTag]() {
|
|
21
|
+
return "CyclicDependencyError";
|
|
22
|
+
}
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
Object.setPrototypeOf(this, _CyclicDependencyError.prototype);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/di/di.ts
|
|
30
|
+
var ExternalDependency = /* @__PURE__ */ Symbol("externalDependency");
|
|
31
|
+
function externalDependencies(deps) {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const key of Object.keys(deps)) {
|
|
34
|
+
const provider = () => deps[key];
|
|
35
|
+
provider[ExternalDependency] = true;
|
|
36
|
+
out[key] = provider;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function createContainer(factories) {
|
|
41
|
+
let disposed = false;
|
|
42
|
+
const disposables = [];
|
|
43
|
+
const o = {};
|
|
44
|
+
let creationCount = 0;
|
|
45
|
+
Object.defineProperty(o, "__enumerableGuard", {
|
|
46
|
+
get() {
|
|
47
|
+
if (creationCount !== 0) {
|
|
48
|
+
throw new IllegalStateError(
|
|
49
|
+
"Enumeration is not supported within factories."
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
configurable: false,
|
|
54
|
+
enumerable: true
|
|
55
|
+
});
|
|
56
|
+
for (const key in factories) {
|
|
57
|
+
if (key === "__enumerableGuard") continue;
|
|
58
|
+
let creating = false;
|
|
59
|
+
let created = false;
|
|
60
|
+
let value = null;
|
|
61
|
+
Object.defineProperty(o, key, {
|
|
62
|
+
get() {
|
|
63
|
+
if (disposed) throw new DisposedError();
|
|
64
|
+
if (!created) {
|
|
65
|
+
if (creating)
|
|
66
|
+
throw new CyclicDependencyError(
|
|
67
|
+
`The dependency with key '${key}' is cyclical. Check to ensure dependencies are not used in a property iterator.`
|
|
68
|
+
);
|
|
69
|
+
creationCount++;
|
|
70
|
+
creating = true;
|
|
71
|
+
const provider = factories[key];
|
|
72
|
+
const index = disposables.length;
|
|
73
|
+
const add = (disposableOrUnsub) => {
|
|
74
|
+
if (disposableOrUnsub) {
|
|
75
|
+
const disposable = isDisposable(disposableOrUnsub) ? disposableOrUnsub : toDisposable(disposableOrUnsub);
|
|
76
|
+
disposables.splice(index, 0, disposable);
|
|
77
|
+
}
|
|
78
|
+
return disposableOrUnsub;
|
|
79
|
+
};
|
|
80
|
+
value = provider(o, add);
|
|
81
|
+
if (isPromiseLike(value)) {
|
|
82
|
+
value = toDisposablePromise(value);
|
|
83
|
+
}
|
|
84
|
+
if (isDisposable(value) && !(ExternalDependency in provider))
|
|
85
|
+
disposables.splice(index, 0, value);
|
|
86
|
+
creating = false;
|
|
87
|
+
created = true;
|
|
88
|
+
creationCount--;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
},
|
|
92
|
+
configurable: false,
|
|
93
|
+
enumerable: true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
dependencies: o,
|
|
98
|
+
dispose() {
|
|
99
|
+
if (disposed) throw new DisposedError();
|
|
100
|
+
disposeAll(disposables);
|
|
101
|
+
disposed = true;
|
|
102
|
+
disposables.length = 0;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
CyclicDependencyError,
|
|
108
|
+
ExternalDependency,
|
|
109
|
+
createContainer,
|
|
110
|
+
externalDependencies,
|
|
111
|
+
validateFactories
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/di/validation.ts", "../src/di/di.ts", "../src/di/CyclicDependencyError.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/*\n * Provides validation utilities for type-safe dependency factories.\n */\n\nimport type { Factories, Factory } from './di'\nimport type { AddDisposable, IsNever, Merge } from '@amazon/vinyl-util'\n\n/**\n * A compile-time validation error that a dependency may not cycle back upon itself.\n */\nexport type NeverCyclicDependency<\n T extends PropertyKey,\n U extends PropertyKey[],\n> = [never, T, 'cyclic dependency', U]\n\n/**\n * A compile-time validation error that a provided dependency must be assignable\n * to factories for which it is an input.\n */\nexport type NeverWrongReturnType<Expected, Actual> = [\n never,\n 'expected provided dependency is not a subtype of what is used. Expected subtype of:',\n Expected,\n 'Actual: ',\n Actual,\n]\n\n/**\n * A compile-time validation error that a dependency must be a supertype of the actual type\n * provided.\n */\nexport type NeverWrongDependencyType<Expected, Actual> = [\n never,\n 'expected dependency is not a supertype of what is provided. Expected supertype of:',\n Expected,\n 'Actual: ',\n Actual,\n]\n\n/**\n * A factory expects a dependency that isn't defined.\n */\nexport type NeverMissingDependency<K> = [\n never,\n 'provider is missing a dependency for key:',\n K,\n]\n\n/**\n * A factory expects a non-void return type.\n */\nexport type NeverNoVoidReturnType = [never, 'void return type not allowed']\n\n/**\n * A factory must be a function.\n */\nexport type NeverInvalidFactoryType = [never, 'not a valid factory']\n\n/**\n * Dependencies must be an object.\n */\nexport type NeverInvalidDependenciesType = [never, 'not valid dependency type']\n\n/**\n * A compile-time validation error that dependency factories take an input of type object.\n */\nexport type NeverWrongDependenciesInput = [\n never,\n 'expected dependency factory to have an object argument',\n]\n\n/**\n * Provides a compile-time assertion that the given dependency map provides valid factories.\n */\nexport function validateFactories<Dm extends Factories>(\n dependencyMap: ValidFactories<Dm>\n): NoInfer<Dm> {\n return dependencyMap as Dm\n}\n\n/**\n * Validates that the type provided is a valid dependency map.\n *\n * This will guarantee that:\n * - All dependency factories exist with the correct signature.\n * - There are no cyclic dependencies.\n * - The factory's dependencies are provided.\n *\n * @see ValidFactory\n */\nexport type ValidFactories<Dm> = {\n readonly [K in keyof Dm]: ValidFactory<Dm, K>\n}\n\n/**\n * Validates that the dependency factory is valid.\n */\nexport type ValidFactory<Dm, K extends keyof Dm> = Dm[K] extends (\n deps: infer Deps,\n add: AddDisposable\n) => infer R\n ? [R] extends [void]\n ? [never, Dm[K]]\n : [R] extends [ExpectedInputType<Dm, K>]\n ? Dm[K] extends () => any\n ? Dm[K] // No dependencies\n : Deps extends ValidProviderDependencies<Deps, Dm>\n ? Dm[K]\n : (deps: ValidProviderDependencies<Deps, Dm>) => R\n : NeverWrongReturnType<ExpectedInputType<Dm, K>, R>\n : NeverInvalidFactoryType\n\ntype _ExpectedInputUnion<Dm, K extends keyof Dm> = Dm[keyof Dm] extends (\n deps: infer Deps,\n add: AddDisposable\n) => any\n ? K extends keyof Deps\n ? Deps[K]\n : never\n : never\n\nexport type ExpectedInputType<Dm, K extends keyof Dm> =\n // If the union provides `never`, it isn't a required dependency for any other factory, allow `any`\n IsNever<_ExpectedInputUnion<Dm, K>> extends true\n ? any\n : _ExpectedInputUnion<Dm, K>\n\n/**\n * A type validating dependency overrides when merged with existing dependency factories.\n */\nexport type ValidFactoryOverrides<Deps, Base, Ext> = Pick<\n ValidFactories<Merge<Base, Ext>>,\n keyof Ext\n> &\n Partial<Factories<Deps>>\n\n/**\n * Validate that the dependencies of a factory are not cyclical and that the return type of the\n * dependency is a subtype of what's needed.\n */\nexport type ValidProviderDependencies<Deps, Dm> = Deps extends object\n ? {\n readonly [K in keyof Deps]: K extends keyof Dm\n ? Dm[K] extends Factory<infer R>\n ? R extends Deps[K]\n ? K extends FlattenDependencies<Dm, K>\n ? NeverCyclicDependency<K, FlattenDependencies<Dm, K>>\n : Deps[K]\n : NeverWrongDependencyType<ReturnType<Dm[K]>, Deps[K]>\n : NeverMissingDependency<K>\n : NeverMissingDependency<K>\n }\n : NeverInvalidDependenciesType\n\n/**\n * Flattens the dependency graph for the given dependency factory.\n * E.g. given a map:\n * ```\n * type Map = {\n * a: () => 1,\n * b: (deps: { a: number }) => 2,\n * c: () => 3,\n * d: (deps: { b: number, c: number }) => 4,\n * }\n * ```\n *\n * `FlattenDependencies<'d', Map> // 'c' | 'b' | 'a'`\n */\nexport type FlattenDependencies<\n Dm,\n K extends keyof Dm,\n Current extends keyof Dm = never,\n> = Dm[K] extends () => any\n ? never\n : Dm[K] extends (deps: infer Expected) => any\n ? keyof Expected extends Current\n ? never\n :\n | keyof Expected\n | FlattenDependencies<\n Dm,\n keyof Expected & keyof Dm,\n Current | (keyof Expected & keyof Dm)\n >\n : never\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { type AnyRecord, IllegalStateError } from '@amazon/vinyl-util'\nimport {\n type AddDisposable,\n type Disposable,\n disposeAll,\n DisposedError,\n isDisposable,\n isPromiseLike,\n toDisposable,\n toDisposablePromise,\n} from '@amazon/vinyl-util'\nimport { CyclicDependencyError } from './CyclicDependencyError'\n\n/**\n * A factory function that produces a dependency.\n * Called at most once per container lifecycle.\n *\n * @param deps - A statically-typed record of other dependencies required by this factory,\n * provided by other factories.\n * @param add - A function used to register disposables or cleanup callbacks.\n * Use this to add teardown logic that should run when the container is disposed.\n *\n * @returns The constructed dependency. If it:\n * - Implements `Disposable`, it will automatically be disposed when the container is disposed.\n * Do not manually pass it to `add`.\n * - Is `PromiseLike`, the container will wait for the promise to resolve.\n * If the resolved value implements `Disposable`, it will also be disposed automatically.\n * Additionally, the promise will reject if the container is disposed before resolution.\n */\nexport type Factory<T> = (deps: any, add: AddDisposable) => T\n\n/**\n * A symbol used to mark dependency factories as externally owned.\n *\n * If a `Factory` has this symbol as a property,\n * it will be considered to be owned by a parent container and will not be\n * disposed regardless of whether it implements {@link Disposable}.\n *\n * This prevents the dependency injection container from taking ownership\n * of dependencies that should be managed elsewhere.\n *\n * @example\n * ```typescript\n * const provider = () => someExternalService\n * provider[ExternalDependency] = true\n * ```\n */\nexport const ExternalDependency = /* @__PURE__ */ Symbol('externalDependency')\n\n/**\n * Creates a Factories record which provides the values of `deps`.\n * These dependencies will be considered to be owned externally and will not\n * be disposed by containers using these factories.\n *\n * This is useful when you want to inject existing instances into a dependency\n * container without transferring ownership. The container will use these\n * dependencies but won't dispose them when the container is disposed.\n *\n * @param deps - A record of dependency instances to be wrapped as factories.\n * Each value will be returned as-is by the corresponding factory.\n * @returns An object with dependency factories for each member of deps.\n * Each provider is marked with the {@link ExternalDependency} symbol.\n *\n * @example\n * ```typescript\n * const logger = new Logger()\n * const config = { port: 8080 }\n *\n * const factories = validateFactories({\n * ...externalDependencies({ logger, config }),\n * controller: (deps: { readonly logger: Logger }) => ({\n * start: () => deps.logger.log('starting...'),\n * dispose: () => deps.logger.log('disposed')\n * })\n * })\n * const container = createContainer(factories)\n *\n * // logger and config won't be disposed when container is disposed\n * // controller will be disposed\n * container.dispose()\n * ```\n */\nexport function externalDependencies<T extends AnyRecord>(\n deps: T\n): Factories<T> {\n const out = {} as any\n for (const key of Object.keys(deps)) {\n const provider = () => deps[key as keyof T]\n provider[ExternalDependency] = true\n out[key] = provider\n }\n return out\n}\n\n/**\n * A map from dependency keys to factory functions.\n *\n * For each property `K` in `T`, if `T[K]` is a `Dependency`, the value must be\n * a `Factory` that produces `T[K]`. If `T[K]` is not a `Dependency`, the\n * property type resolves to `never`, causing a compile-time error.\n *\n * This ensures factories are defined only for valid dependencies and that\n * each factory is correctly typed.\n *\n * @typeParam T - A record whose values are expected to be `Dependency` types.\n */\nexport type Factories<T = any> = {\n readonly [K in keyof T]: Factory<T[K]>\n}\n\n/**\n * Given a dependency map of keys to dependency factories, provides the mapped dependency\n * instances.\n */\nexport type Dependencies<T extends Factories> = {\n readonly [K in keyof T]: ReturnType<T[K]>\n}\n\n/**\n * A dependencies container provides final dependencies and a handle for disposal.\n */\nexport interface Container<T> extends Disposable {\n readonly dependencies: T\n}\n\n/**\n * Creates a {@link Container} from a dependency factory map.\n *\n * The dependencies will be a map of getters where the key matches the key within\n * `factories` and the value is a lazily constructed dependency obtained from\n * the respective provider.\n *\n * @param factories An object of key value pairs where the values are dependency\n * providers.\n */\nexport function createContainer<Dm extends Factories>(\n factories: Dm\n): Container<Dependencies<Dm>> {\n let disposed = false\n const disposables: Disposable[] = []\n const o = {}\n let creationCount = 0\n Object.defineProperty(o, '__enumerableGuard', {\n get() {\n if (creationCount !== 0) {\n throw new IllegalStateError(\n 'Enumeration is not supported within factories.'\n )\n }\n },\n configurable: false,\n enumerable: true,\n })\n for (const key in factories) {\n if (key === '__enumerableGuard') continue\n let creating = false\n let created = false\n let value: any = null\n Object.defineProperty(o, key, {\n get(): any {\n if (disposed) throw new DisposedError()\n if (!created) {\n if (creating)\n throw new CyclicDependencyError(\n `The dependency with key '${key}' is cyclical. Check to ensure dependencies are not used in a property iterator.`\n )\n creationCount++\n creating = true\n const provider = factories[key]\n const index = disposables.length\n const add: AddDisposable = (disposableOrUnsub) => {\n if (disposableOrUnsub) {\n const disposable = isDisposable(disposableOrUnsub)\n ? disposableOrUnsub\n : toDisposable(disposableOrUnsub)\n disposables.splice(index, 0, disposable)\n }\n return disposableOrUnsub\n }\n value = provider(o, add)\n\n if (isPromiseLike(value)) {\n value = toDisposablePromise(value)\n }\n // Insert the disposable into the disposables list at the end of the list\n // as it was before construction. This ensures reentrancy due to\n // dependencies referenced during construction will be added after the\n // independent.\n if (\n isDisposable(value) &&\n !(ExternalDependency in provider)\n )\n disposables.splice(index, 0, value)\n creating = false\n created = true\n creationCount--\n }\n return value\n },\n configurable: false,\n enumerable: true,\n })\n }\n return {\n dependencies: o as Dependencies<Dm>,\n dispose() {\n if (disposed) throw new DisposedError()\n disposeAll(disposables)\n disposed = true\n disposables.length = 0\n },\n }\n}\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IllegalArgumentError } from '@amazon/vinyl-util'\n\n/**\n * Thrown at runtime if a dependency is determined to depend upon itself.\n *\n * When using TypeScript this should result in a {@link NeverCyclicDependency} compile-time\n * validation error when using {@link validateFactories} unless there was an unsafe cast.\n */\nexport class CyclicDependencyError extends IllegalArgumentError {\n get [Symbol.toStringTag](): string {\n return 'CyclicDependencyError'\n }\n\n constructor(message: string) {\n super(message)\n Object.setPrototypeOf(this, CyclicDependencyError.prototype)\n }\n}\n"],
|
|
5
|
+
"mappings": ";AA+EO,SAAS,kBACZ,eACW;AACX,SAAO;AACX;;;AC9EA,SAAyB,yBAAyB;AAClD;AAAA,EAGI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;;;ACVP,SAAS,4BAA4B;AAQ9B,IAAM,wBAAN,MAAM,+BAA8B,qBAAqB;AAAA,EAC5D,KAAK,OAAO,WAAW,IAAY;AAC/B,WAAO;AAAA,EACX;AAAA,EAEA,YAAY,SAAiB;AACzB,UAAM,OAAO;AACb,WAAO,eAAe,MAAM,uBAAsB,SAAS;AAAA,EAC/D;AACJ;;;AD8BO,IAAM,qBAAqC,uBAAO,oBAAoB;AAmCtE,SAAS,qBACZ,MACY;AACZ,QAAM,MAAM,CAAC;AACb,aAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACjC,UAAM,WAAW,MAAM,KAAK,GAAc;AAC1C,aAAS,kBAAkB,IAAI;AAC/B,QAAI,GAAG,IAAI;AAAA,EACf;AACA,SAAO;AACX;AA2CO,SAAS,gBACZ,WAC2B;AAC3B,MAAI,WAAW;AACf,QAAM,cAA4B,CAAC;AACnC,QAAM,IAAI,CAAC;AACX,MAAI,gBAAgB;AACpB,SAAO,eAAe,GAAG,qBAAqB;AAAA,IAC1C,MAAM;AACF,UAAI,kBAAkB,GAAG;AACrB,cAAM,IAAI;AAAA,UACN;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,EAChB,CAAC;AACD,aAAW,OAAO,WAAW;AACzB,QAAI,QAAQ,oBAAqB;AACjC,QAAI,WAAW;AACf,QAAI,UAAU;AACd,QAAI,QAAa;AACjB,WAAO,eAAe,GAAG,KAAK;AAAA,MAC1B,MAAW;AACP,YAAI,SAAU,OAAM,IAAI,cAAc;AACtC,YAAI,CAAC,SAAS;AACV,cAAI;AACA,kBAAM,IAAI;AAAA,cACN,4BAA4B,GAAG;AAAA,YACnC;AACJ;AACA,qBAAW;AACX,gBAAM,WAAW,UAAU,GAAG;AAC9B,gBAAM,QAAQ,YAAY;AAC1B,gBAAM,MAAqB,CAAC,sBAAsB;AAC9C,gBAAI,mBAAmB;AACnB,oBAAM,aAAa,aAAa,iBAAiB,IAC3C,oBACA,aAAa,iBAAiB;AACpC,0BAAY,OAAO,OAAO,GAAG,UAAU;AAAA,YAC3C;AACA,mBAAO;AAAA,UACX;AACA,kBAAQ,SAAS,GAAG,GAAG;AAEvB,cAAI,cAAc,KAAK,GAAG;AACtB,oBAAQ,oBAAoB,KAAK;AAAA,UACrC;AAKA,cACI,aAAa,KAAK,KAClB,EAAE,sBAAsB;AAExB,wBAAY,OAAO,OAAO,GAAG,KAAK;AACtC,qBAAW;AACX,oBAAU;AACV;AAAA,QACJ;AACA,eAAO;AAAA,MACX;AAAA,MACA,cAAc;AAAA,MACd,YAAY;AAAA,IAChB,CAAC;AAAA,EACL;AACA,SAAO;AAAA,IACH,cAAc;AAAA,IACd,UAAU;AACN,UAAI,SAAU,OAAM,IAAI,cAAc;AACtC,iBAAW,WAAW;AACtB,iBAAW;AACX,kBAAY,SAAS;AAAA,IACzB;AAAA,EACJ;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IllegalArgumentError } from '@amazon/vinyl-util';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown at runtime if a dependency is determined to depend upon itself.
|
|
4
|
+
*
|
|
5
|
+
* When using TypeScript this should result in a {@link NeverCyclicDependency} compile-time
|
|
6
|
+
* validation error when using {@link validateFactories} unless there was an unsafe cast.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CyclicDependencyError extends IllegalArgumentError {
|
|
9
|
+
get [Symbol.toStringTag](): string;
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=CyclicDependencyError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CyclicDependencyError.d.ts","sourceRoot":"","sources":["../../../../src/di/CyclicDependencyError.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAEzD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,oBAAoB;IAC3D,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAEjC;gBAEW,OAAO,EAAE,MAAM;CAI9B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { IllegalArgumentError } from '@amazon/vinyl-util';
|
|
6
|
+
/**
|
|
7
|
+
* Thrown at runtime if a dependency is determined to depend upon itself.
|
|
8
|
+
*
|
|
9
|
+
* When using TypeScript this should result in a {@link NeverCyclicDependency} compile-time
|
|
10
|
+
* validation error when using {@link validateFactories} unless there was an unsafe cast.
|
|
11
|
+
*/
|
|
12
|
+
export class CyclicDependencyError extends IllegalArgumentError {
|
|
13
|
+
get [Symbol.toStringTag]() {
|
|
14
|
+
return 'CyclicDependencyError';
|
|
15
|
+
}
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
Object.setPrototypeOf(this, CyclicDependencyError.prototype);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type AnyRecord } from '@amazon/vinyl-util';
|
|
2
|
+
import { type AddDisposable, type Disposable } from '@amazon/vinyl-util';
|
|
3
|
+
/**
|
|
4
|
+
* A factory function that produces a dependency.
|
|
5
|
+
* Called at most once per container lifecycle.
|
|
6
|
+
*
|
|
7
|
+
* @param deps - A statically-typed record of other dependencies required by this factory,
|
|
8
|
+
* provided by other factories.
|
|
9
|
+
* @param add - A function used to register disposables or cleanup callbacks.
|
|
10
|
+
* Use this to add teardown logic that should run when the container is disposed.
|
|
11
|
+
*
|
|
12
|
+
* @returns The constructed dependency. If it:
|
|
13
|
+
* - Implements `Disposable`, it will automatically be disposed when the container is disposed.
|
|
14
|
+
* Do not manually pass it to `add`.
|
|
15
|
+
* - Is `PromiseLike`, the container will wait for the promise to resolve.
|
|
16
|
+
* If the resolved value implements `Disposable`, it will also be disposed automatically.
|
|
17
|
+
* Additionally, the promise will reject if the container is disposed before resolution.
|
|
18
|
+
*/
|
|
19
|
+
export type Factory<T> = (deps: any, add: AddDisposable) => T;
|
|
20
|
+
/**
|
|
21
|
+
* A symbol used to mark dependency factories as externally owned.
|
|
22
|
+
*
|
|
23
|
+
* If a `Factory` has this symbol as a property,
|
|
24
|
+
* it will be considered to be owned by a parent container and will not be
|
|
25
|
+
* disposed regardless of whether it implements {@link Disposable}.
|
|
26
|
+
*
|
|
27
|
+
* This prevents the dependency injection container from taking ownership
|
|
28
|
+
* of dependencies that should be managed elsewhere.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const provider = () => someExternalService
|
|
33
|
+
* provider[ExternalDependency] = true
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const ExternalDependency: unique symbol;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Factories record which provides the values of `deps`.
|
|
39
|
+
* These dependencies will be considered to be owned externally and will not
|
|
40
|
+
* be disposed by containers using these factories.
|
|
41
|
+
*
|
|
42
|
+
* This is useful when you want to inject existing instances into a dependency
|
|
43
|
+
* container without transferring ownership. The container will use these
|
|
44
|
+
* dependencies but won't dispose them when the container is disposed.
|
|
45
|
+
*
|
|
46
|
+
* @param deps - A record of dependency instances to be wrapped as factories.
|
|
47
|
+
* Each value will be returned as-is by the corresponding factory.
|
|
48
|
+
* @returns An object with dependency factories for each member of deps.
|
|
49
|
+
* Each provider is marked with the {@link ExternalDependency} symbol.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const logger = new Logger()
|
|
54
|
+
* const config = { port: 8080 }
|
|
55
|
+
*
|
|
56
|
+
* const factories = validateFactories({
|
|
57
|
+
* ...externalDependencies({ logger, config }),
|
|
58
|
+
* controller: (deps: { readonly logger: Logger }) => ({
|
|
59
|
+
* start: () => deps.logger.log('starting...'),
|
|
60
|
+
* dispose: () => deps.logger.log('disposed')
|
|
61
|
+
* })
|
|
62
|
+
* })
|
|
63
|
+
* const container = createContainer(factories)
|
|
64
|
+
*
|
|
65
|
+
* // logger and config won't be disposed when container is disposed
|
|
66
|
+
* // controller will be disposed
|
|
67
|
+
* container.dispose()
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function externalDependencies<T extends AnyRecord>(deps: T): Factories<T>;
|
|
71
|
+
/**
|
|
72
|
+
* A map from dependency keys to factory functions.
|
|
73
|
+
*
|
|
74
|
+
* For each property `K` in `T`, if `T[K]` is a `Dependency`, the value must be
|
|
75
|
+
* a `Factory` that produces `T[K]`. If `T[K]` is not a `Dependency`, the
|
|
76
|
+
* property type resolves to `never`, causing a compile-time error.
|
|
77
|
+
*
|
|
78
|
+
* This ensures factories are defined only for valid dependencies and that
|
|
79
|
+
* each factory is correctly typed.
|
|
80
|
+
*
|
|
81
|
+
* @typeParam T - A record whose values are expected to be `Dependency` types.
|
|
82
|
+
*/
|
|
83
|
+
export type Factories<T = any> = {
|
|
84
|
+
readonly [K in keyof T]: Factory<T[K]>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Given a dependency map of keys to dependency factories, provides the mapped dependency
|
|
88
|
+
* instances.
|
|
89
|
+
*/
|
|
90
|
+
export type Dependencies<T extends Factories> = {
|
|
91
|
+
readonly [K in keyof T]: ReturnType<T[K]>;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* A dependencies container provides final dependencies and a handle for disposal.
|
|
95
|
+
*/
|
|
96
|
+
export interface Container<T> extends Disposable {
|
|
97
|
+
readonly dependencies: T;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Creates a {@link Container} from a dependency factory map.
|
|
101
|
+
*
|
|
102
|
+
* The dependencies will be a map of getters where the key matches the key within
|
|
103
|
+
* `factories` and the value is a lazily constructed dependency obtained from
|
|
104
|
+
* the respective provider.
|
|
105
|
+
*
|
|
106
|
+
* @param factories An object of key value pairs where the values are dependency
|
|
107
|
+
* providers.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createContainer<Dm extends Factories>(factories: Dm): Container<Dependencies<Dm>>;
|
|
110
|
+
//# sourceMappingURL=di.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di.d.ts","sourceRoot":"","sources":["../../../../src/di/di.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,oBAAoB,CAAA;AACtE,OAAO,EACH,KAAK,aAAa,EAClB,KAAK,UAAU,EAOlB,MAAM,oBAAoB,CAAA;AAG3B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,KAAK,CAAC,CAAA;AAE7D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,eAA+B,CAAA;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,SAAS,EACpD,IAAI,EAAE,CAAC,GACR,SAAS,CAAC,CAAC,CAAC,CAQd;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,GAAG,IAAI;IAC7B,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI;IAC5C,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,UAAU;IAC5C,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAA;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,SAAS,EAChD,SAAS,EAAE,EAAE,GACd,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CA2E7B"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { IllegalStateError } from '@amazon/vinyl-util';
|
|
6
|
+
import { disposeAll, DisposedError, isDisposable, isPromiseLike, toDisposable, toDisposablePromise, } from '@amazon/vinyl-util';
|
|
7
|
+
import { CyclicDependencyError } from './CyclicDependencyError';
|
|
8
|
+
/**
|
|
9
|
+
* A symbol used to mark dependency factories as externally owned.
|
|
10
|
+
*
|
|
11
|
+
* If a `Factory` has this symbol as a property,
|
|
12
|
+
* it will be considered to be owned by a parent container and will not be
|
|
13
|
+
* disposed regardless of whether it implements {@link Disposable}.
|
|
14
|
+
*
|
|
15
|
+
* This prevents the dependency injection container from taking ownership
|
|
16
|
+
* of dependencies that should be managed elsewhere.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const provider = () => someExternalService
|
|
21
|
+
* provider[ExternalDependency] = true
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const ExternalDependency = Symbol('externalDependency');
|
|
25
|
+
/**
|
|
26
|
+
* Creates a Factories record which provides the values of `deps`.
|
|
27
|
+
* These dependencies will be considered to be owned externally and will not
|
|
28
|
+
* be disposed by containers using these factories.
|
|
29
|
+
*
|
|
30
|
+
* This is useful when you want to inject existing instances into a dependency
|
|
31
|
+
* container without transferring ownership. The container will use these
|
|
32
|
+
* dependencies but won't dispose them when the container is disposed.
|
|
33
|
+
*
|
|
34
|
+
* @param deps - A record of dependency instances to be wrapped as factories.
|
|
35
|
+
* Each value will be returned as-is by the corresponding factory.
|
|
36
|
+
* @returns An object with dependency factories for each member of deps.
|
|
37
|
+
* Each provider is marked with the {@link ExternalDependency} symbol.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const logger = new Logger()
|
|
42
|
+
* const config = { port: 8080 }
|
|
43
|
+
*
|
|
44
|
+
* const factories = validateFactories({
|
|
45
|
+
* ...externalDependencies({ logger, config }),
|
|
46
|
+
* controller: (deps: { readonly logger: Logger }) => ({
|
|
47
|
+
* start: () => deps.logger.log('starting...'),
|
|
48
|
+
* dispose: () => deps.logger.log('disposed')
|
|
49
|
+
* })
|
|
50
|
+
* })
|
|
51
|
+
* const container = createContainer(factories)
|
|
52
|
+
*
|
|
53
|
+
* // logger and config won't be disposed when container is disposed
|
|
54
|
+
* // controller will be disposed
|
|
55
|
+
* container.dispose()
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function externalDependencies(deps) {
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const key of Object.keys(deps)) {
|
|
61
|
+
const provider = () => deps[key];
|
|
62
|
+
provider[ExternalDependency] = true;
|
|
63
|
+
out[key] = provider;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates a {@link Container} from a dependency factory map.
|
|
69
|
+
*
|
|
70
|
+
* The dependencies will be a map of getters where the key matches the key within
|
|
71
|
+
* `factories` and the value is a lazily constructed dependency obtained from
|
|
72
|
+
* the respective provider.
|
|
73
|
+
*
|
|
74
|
+
* @param factories An object of key value pairs where the values are dependency
|
|
75
|
+
* providers.
|
|
76
|
+
*/
|
|
77
|
+
export function createContainer(factories) {
|
|
78
|
+
let disposed = false;
|
|
79
|
+
const disposables = [];
|
|
80
|
+
const o = {};
|
|
81
|
+
let creationCount = 0;
|
|
82
|
+
Object.defineProperty(o, '__enumerableGuard', {
|
|
83
|
+
get() {
|
|
84
|
+
if (creationCount !== 0) {
|
|
85
|
+
throw new IllegalStateError('Enumeration is not supported within factories.');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
configurable: false,
|
|
89
|
+
enumerable: true,
|
|
90
|
+
});
|
|
91
|
+
for (const key in factories) {
|
|
92
|
+
if (key === '__enumerableGuard')
|
|
93
|
+
continue;
|
|
94
|
+
let creating = false;
|
|
95
|
+
let created = false;
|
|
96
|
+
let value = null;
|
|
97
|
+
Object.defineProperty(o, key, {
|
|
98
|
+
get() {
|
|
99
|
+
if (disposed)
|
|
100
|
+
throw new DisposedError();
|
|
101
|
+
if (!created) {
|
|
102
|
+
if (creating)
|
|
103
|
+
throw new CyclicDependencyError(`The dependency with key '${key}' is cyclical. Check to ensure dependencies are not used in a property iterator.`);
|
|
104
|
+
creationCount++;
|
|
105
|
+
creating = true;
|
|
106
|
+
const provider = factories[key];
|
|
107
|
+
const index = disposables.length;
|
|
108
|
+
const add = (disposableOrUnsub) => {
|
|
109
|
+
if (disposableOrUnsub) {
|
|
110
|
+
const disposable = isDisposable(disposableOrUnsub)
|
|
111
|
+
? disposableOrUnsub
|
|
112
|
+
: toDisposable(disposableOrUnsub);
|
|
113
|
+
disposables.splice(index, 0, disposable);
|
|
114
|
+
}
|
|
115
|
+
return disposableOrUnsub;
|
|
116
|
+
};
|
|
117
|
+
value = provider(o, add);
|
|
118
|
+
if (isPromiseLike(value)) {
|
|
119
|
+
value = toDisposablePromise(value);
|
|
120
|
+
}
|
|
121
|
+
// Insert the disposable into the disposables list at the end of the list
|
|
122
|
+
// as it was before construction. This ensures reentrancy due to
|
|
123
|
+
// dependencies referenced during construction will be added after the
|
|
124
|
+
// independent.
|
|
125
|
+
if (isDisposable(value) &&
|
|
126
|
+
!(ExternalDependency in provider))
|
|
127
|
+
disposables.splice(index, 0, value);
|
|
128
|
+
creating = false;
|
|
129
|
+
created = true;
|
|
130
|
+
creationCount--;
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
},
|
|
134
|
+
configurable: false,
|
|
135
|
+
enumerable: true,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
dependencies: o,
|
|
140
|
+
dispose() {
|
|
141
|
+
if (disposed)
|
|
142
|
+
throw new DisposedError();
|
|
143
|
+
disposeAll(disposables);
|
|
144
|
+
disposed = true;
|
|
145
|
+
disposables.length = 0;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|