atomic-di 1.2.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +262 -224
- package/dist/index.d.mts +101 -78
- package/dist/index.d.ts +101 -78
- package/dist/index.js +51 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
@@ -29,33 +29,40 @@ __export(src_exports, {
|
|
29
29
|
});
|
30
30
|
module.exports = __toCommonJS(src_exports);
|
31
31
|
|
32
|
-
// src/
|
33
|
-
var
|
32
|
+
// src/resolver.ts
|
33
|
+
var mockable = (fn) => {
|
34
34
|
const instance = (context) => {
|
35
35
|
var _a;
|
36
|
-
const
|
37
|
-
if (
|
38
|
-
return resolver(context);
|
36
|
+
const mock = (_a = context == null ? void 0 : context.mocks) == null ? void 0 : _a.get(instance);
|
37
|
+
if (!mock) return fn(context);
|
38
|
+
if (!mock.isPartial) return mock.resolver(context);
|
39
|
+
const resolution = fn(context);
|
40
|
+
const mockResolution = mock.resolver(context);
|
41
|
+
if (resolution instanceof Promise && mockResolution instanceof Promise)
|
42
|
+
return Promise.all([resolution, mockResolution]).then(
|
43
|
+
([a, b]) => Object.assign(a, b)
|
44
|
+
);
|
45
|
+
return Object.assign(resolution, mockResolution);
|
39
46
|
};
|
40
|
-
return
|
47
|
+
return instance;
|
41
48
|
};
|
42
|
-
var transient =
|
43
|
-
var singleton = (
|
49
|
+
var transient = (fn) => mockable(fn);
|
50
|
+
var singleton = (fn) => {
|
44
51
|
let resolved = false;
|
45
52
|
let resolution;
|
46
|
-
const instance =
|
53
|
+
const instance = mockable((context) => {
|
47
54
|
if (resolved) return resolution;
|
48
|
-
resolution =
|
55
|
+
resolution = fn(context);
|
49
56
|
resolved = true;
|
50
57
|
return resolution;
|
51
58
|
});
|
52
59
|
return instance;
|
53
60
|
};
|
54
|
-
var scoped = (
|
55
|
-
const singletonFallback = singleton(
|
56
|
-
const instance =
|
61
|
+
var scoped = (fn) => {
|
62
|
+
const singletonFallback = singleton(fn);
|
63
|
+
const instance = mockable((context) => {
|
57
64
|
if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
|
58
|
-
const resolution = context.scope.has(instance) ? context.scope.get(instance) :
|
65
|
+
const resolution = context.scope.has(instance) ? context.scope.get(instance) : fn(context);
|
59
66
|
context.scope.set(instance, resolution);
|
60
67
|
return resolution;
|
61
68
|
});
|
@@ -66,19 +73,43 @@ var scoped = (resolver) => {
|
|
66
73
|
var createScope = () => /* @__PURE__ */ new Map();
|
67
74
|
|
68
75
|
// src/mock-map.ts
|
69
|
-
var
|
76
|
+
var createMockMapWithEntries = (entries = []) => {
|
77
|
+
const set = (key, value) => createMockMapWithEntries([
|
78
|
+
...entries.filter((entry) => entry[0] !== key),
|
79
|
+
[key, value]
|
80
|
+
]);
|
81
|
+
return {
|
82
|
+
mock(original, mock) {
|
83
|
+
return set(original, {
|
84
|
+
isPartial: false,
|
85
|
+
resolver: mock
|
86
|
+
});
|
87
|
+
},
|
88
|
+
mockPartially(original, mock) {
|
89
|
+
return set(original, {
|
90
|
+
isPartial: true,
|
91
|
+
resolver: mock
|
92
|
+
});
|
93
|
+
},
|
94
|
+
get(original) {
|
95
|
+
var _a;
|
96
|
+
return (_a = entries.find((entry) => entry[0] === original)) == null ? void 0 : _a[1];
|
97
|
+
}
|
98
|
+
};
|
99
|
+
};
|
100
|
+
var createMockMap = () => createMockMapWithEntries();
|
70
101
|
|
71
102
|
// src/collection-resolution.ts
|
72
|
-
var resolveList = (
|
73
|
-
const resolutions =
|
103
|
+
var resolveList = (resolvers, context) => {
|
104
|
+
const resolutions = resolvers.map((resolver) => resolver(context));
|
74
105
|
const hasPromises = resolutions.some(
|
75
106
|
(resolution) => resolution instanceof Promise
|
76
107
|
);
|
77
108
|
return hasPromises ? Promise.all(resolutions) : resolutions;
|
78
109
|
};
|
79
|
-
var resolveMap = (
|
80
|
-
const resolutionMapEntries = Object.entries(
|
81
|
-
([key,
|
110
|
+
var resolveMap = (resolvers, context) => {
|
111
|
+
const resolutionMapEntries = Object.entries(resolvers).map(
|
112
|
+
([key, resolver]) => [key, resolver(context)]
|
82
113
|
);
|
83
114
|
const hasPromises = resolutionMapEntries.some(
|
84
115
|
([, resolution]) => resolution instanceof Promise
|
package/dist/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["export * from \"./provider\";\nexport * from \"./scope\";\nexport * from \"./mock-map\";\nexport * from \"./collection-resolution\";\n","import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T> & { __brand: \"provider\" };\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Creating a nominal type value and introducing common functionality.\n */\nconst createProvider = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Resolver<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return Object.assign(instance, { __brand: \"provider\" as const });\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = createProvider;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = createProvider((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance = createProvider((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : resolver(context);\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2BA,IAAM,iBAAiB,CAAI,aAAuC;AAC9D,QAAM,WAAwB,CAAC,YAAY;AA5B/C;AA6BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO,OAAO,OAAO,UAAU,EAAE,SAAS,WAAoB,CAAC;AACnE;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACpGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/resolver.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["export * from \"./resolver\";\nexport * from \"./scope\";\nexport * from \"./mock-map\";\nexport * from \"./collection-resolution\";\n","import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that takes a resolution context\n * and returns a value of some type.\n */\nexport type ResolverFn<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that returns a value of some type\n * based on a resolution context.\n */\nexport type Resolver<T> = ResolverFn<T>;\n\n/**\n * A context used by resolvers that defines the behaviour of the resolver\n * with the passed mocks and scope.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Makes the resolver function capable of replacing itself\n * with a mock if one is defined in the resolution context.\n */\nconst mockable = <T>(fn: ResolverFn<T>): ResolverFn<T> => {\n const instance = (context?: ResolutionContext) => {\n const mock = context?.mocks?.get(instance);\n if (!mock) return fn(context);\n\n if (!mock.isPartial) return mock.resolver(context);\n\n const resolution = fn(context);\n const mockResolution = mock.resolver(context);\n\n if (resolution instanceof Promise && mockResolution instanceof Promise)\n return Promise.all([resolution, mockResolution]).then(([a, b]) =>\n Object.assign(a as object, b),\n ) as T;\n\n return Object.assign(resolution as object, mockResolution);\n };\n\n return instance;\n};\n\n/**\n * Creates a resolver that creates a new resolution on each call.\n *\n * @example\n * ```ts\n * const getEntity = transient(() => createEntity())\n * getEntity() !== getEntity()\n * ```\n *\n * @param resolver\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The transient resolver.\n */\nexport const transient = <T>(fn: ResolverFn<T>): Resolver<T> => mockable(fn);\n\n/**\n * Creates a resolver that creates\n * a resolution once and return it on each call.\n *\n * @example\n * ```ts\n * const getEntity = singleton(() => createEntity())\n * getEntity() === getEntity()\n * ```\n *\n * @param fn\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The singleton resolver.\n */\nexport const singleton = <T>(fn: ResolverFn<T>): Resolver<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = mockable((context) => {\n if (resolved) return resolution!;\n\n resolution = fn(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a resolver that takes its resolution\n * from a scope or create a new one and save it if there is none.\n * If no scope was passed in a resolution context,\n * it will act as a singleton.\n *\n * @example\n * ```ts\n * const getEntity = scoped(() => createEntity())\n * getEntity() === getEntity()\n * ```\n *\n * @example\n * ```ts\n * const getEntity = scoped(() => createEntity())\n * const scope = createScope()\n * getEntity({ scope }) === getEntity({ scope }) !== getEntity()\n * ```\n *\n * @param fn\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The scoped resolver.\n */\nexport const scoped = <T>(fn: ResolverFn<T>): Resolver<T> => {\n const singletonFallback = singleton(fn);\n\n const instance = mockable((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : fn(context);\n\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./resolver\";\n\n/**\n * A `Map` of resolvers to their resolutions.\n * Is passed in a resolution context and used by scoped resolvers\n * to retrieve or save resolution within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances.\n * Is passed in a resolution context and used by scoped resolvers\n * to retrieve or save resolution within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * })\n * ```\n *\n * @returns The map.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./resolver\";\n\ntype PromiseAwarePartial<T> =\n T extends Promise<infer U> ? Promise<Partial<U>> : Partial<T>;\n\ntype Mock<T> =\n | {\n isPartial: false;\n resolver: Resolver<T>;\n }\n | {\n isPartial: true;\n resolver: Resolver<PromiseAwarePartial<T>>;\n };\n\ntype MocksEntries = [Resolver<any>, Mock<any>][];\n\n/**\n * Immutable map that registers and provides mocks.\n * Is passed in a resolution context and used by resolvers\n * to replace or partially replace themselves with a mock if one is defined.\n */\nexport type MockMap = {\n /**\n * Registers a mock for a resolver,\n * creating a new `MockMap` with this registration.\n *\n * @param original - The original resolver.\n * @param mock - The mock resolver.\n */\n mock<T>(original: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Registers a partial mock for a resolver,\n * creating a new `MockMap` with this registration.\n * In this case, the mock resolver's resolution object will be\n * merged with the original resolver's resolution object,\n * overwriting certain fields.\n *\n * @param original - The original resolver.\n * @param mock - The mock resolver.\n */\n mockPartially<T extends object>(\n original: Resolver<T>,\n mock: Resolver<PromiseAwarePartial<T>>,\n ): MockMap;\n /**\n * Returns a mock of a resolver\n * or `undefined` if one is not registered.\n *\n * @param original - The original resolver.\n */\n get<T>(original: Resolver<T>): Mock<T> | undefined;\n};\n\n/**\n * Internal implementation that accepts entries.\n */\nconst createMockMapWithEntries = (entries: MocksEntries = []): MockMap => {\n const set = (key: Resolver<any>, value: Mock<any>) =>\n createMockMapWithEntries([\n ...entries.filter((entry) => entry[0] !== key),\n [key, value],\n ]);\n\n return {\n mock(original, mock) {\n return set(original, {\n isPartial: false,\n resolver: mock,\n });\n },\n mockPartially(original, mock) {\n return set(original, {\n isPartial: true,\n resolver: mock,\n });\n },\n get(original) {\n return entries.find((entry) => entry[0] === original)?.[1] as any;\n },\n };\n};\n\n/**\n * Creates a mock map, an immutable map that registers and provides mocks.\n * Is passed in a resolution context and used by resolvers\n * to replace or partially replace themselves with a mock if one is defined.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .mock(getDependency, getDependencyMock)\n * .mockPartially(\n * getOtherDepedency,\n * transient(() => ({ someField: \"mock\" }))\n * )\n *\n * const entityWithMocks = getEntity({ mocks })\n * ```\n *\n * @returns The mock map.\n */\nexport const createMockMap = (): MockMap => createMockMapWithEntries();\n","import { ResolutionContext, Resolver } from \"./resolver\";\n\ntype ResolverList = Resolver<any>[];\ntype ResolverRecord = Record<string, Resolver<any>>;\n\ntype InferResolverCollectionResolutions<\n Resolvers extends ResolverList | ResolverRecord,\n> = {\n [K in keyof Resolvers]: Resolvers[K] extends Resolver<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every resolver in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync resolvers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some resolver is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param resolvers - The list of resolvers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Resolvers extends ResolverList>(\n resolvers: Resolvers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<InferResolverCollectionResolutions<Resolvers>> => {\n const resolutions = resolvers.map((resolver) => resolver(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every resolver in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync resolvers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some resolver is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param resolvers - The map of resolvers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Resolvers extends ResolverRecord>(\n resolvers: Resolvers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<InferResolverCollectionResolutions<Resolvers>> => {\n const resolutionMapEntries = Object.entries(resolvers).map(\n ([key, resolver]) => [key, resolver(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
@@ -1,30 +1,37 @@
|
|
1
|
-
// src/
|
2
|
-
var
|
1
|
+
// src/resolver.ts
|
2
|
+
var mockable = (fn) => {
|
3
3
|
const instance = (context) => {
|
4
4
|
var _a;
|
5
|
-
const
|
6
|
-
if (
|
7
|
-
return resolver(context);
|
5
|
+
const mock = (_a = context == null ? void 0 : context.mocks) == null ? void 0 : _a.get(instance);
|
6
|
+
if (!mock) return fn(context);
|
7
|
+
if (!mock.isPartial) return mock.resolver(context);
|
8
|
+
const resolution = fn(context);
|
9
|
+
const mockResolution = mock.resolver(context);
|
10
|
+
if (resolution instanceof Promise && mockResolution instanceof Promise)
|
11
|
+
return Promise.all([resolution, mockResolution]).then(
|
12
|
+
([a, b]) => Object.assign(a, b)
|
13
|
+
);
|
14
|
+
return Object.assign(resolution, mockResolution);
|
8
15
|
};
|
9
|
-
return
|
16
|
+
return instance;
|
10
17
|
};
|
11
|
-
var transient =
|
12
|
-
var singleton = (
|
18
|
+
var transient = (fn) => mockable(fn);
|
19
|
+
var singleton = (fn) => {
|
13
20
|
let resolved = false;
|
14
21
|
let resolution;
|
15
|
-
const instance =
|
22
|
+
const instance = mockable((context) => {
|
16
23
|
if (resolved) return resolution;
|
17
|
-
resolution =
|
24
|
+
resolution = fn(context);
|
18
25
|
resolved = true;
|
19
26
|
return resolution;
|
20
27
|
});
|
21
28
|
return instance;
|
22
29
|
};
|
23
|
-
var scoped = (
|
24
|
-
const singletonFallback = singleton(
|
25
|
-
const instance =
|
30
|
+
var scoped = (fn) => {
|
31
|
+
const singletonFallback = singleton(fn);
|
32
|
+
const instance = mockable((context) => {
|
26
33
|
if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
|
27
|
-
const resolution = context.scope.has(instance) ? context.scope.get(instance) :
|
34
|
+
const resolution = context.scope.has(instance) ? context.scope.get(instance) : fn(context);
|
28
35
|
context.scope.set(instance, resolution);
|
29
36
|
return resolution;
|
30
37
|
});
|
@@ -35,19 +42,43 @@ var scoped = (resolver) => {
|
|
35
42
|
var createScope = () => /* @__PURE__ */ new Map();
|
36
43
|
|
37
44
|
// src/mock-map.ts
|
38
|
-
var
|
45
|
+
var createMockMapWithEntries = (entries = []) => {
|
46
|
+
const set = (key, value) => createMockMapWithEntries([
|
47
|
+
...entries.filter((entry) => entry[0] !== key),
|
48
|
+
[key, value]
|
49
|
+
]);
|
50
|
+
return {
|
51
|
+
mock(original, mock) {
|
52
|
+
return set(original, {
|
53
|
+
isPartial: false,
|
54
|
+
resolver: mock
|
55
|
+
});
|
56
|
+
},
|
57
|
+
mockPartially(original, mock) {
|
58
|
+
return set(original, {
|
59
|
+
isPartial: true,
|
60
|
+
resolver: mock
|
61
|
+
});
|
62
|
+
},
|
63
|
+
get(original) {
|
64
|
+
var _a;
|
65
|
+
return (_a = entries.find((entry) => entry[0] === original)) == null ? void 0 : _a[1];
|
66
|
+
}
|
67
|
+
};
|
68
|
+
};
|
69
|
+
var createMockMap = () => createMockMapWithEntries();
|
39
70
|
|
40
71
|
// src/collection-resolution.ts
|
41
|
-
var resolveList = (
|
42
|
-
const resolutions =
|
72
|
+
var resolveList = (resolvers, context) => {
|
73
|
+
const resolutions = resolvers.map((resolver) => resolver(context));
|
43
74
|
const hasPromises = resolutions.some(
|
44
75
|
(resolution) => resolution instanceof Promise
|
45
76
|
);
|
46
77
|
return hasPromises ? Promise.all(resolutions) : resolutions;
|
47
78
|
};
|
48
|
-
var resolveMap = (
|
49
|
-
const resolutionMapEntries = Object.entries(
|
50
|
-
([key,
|
79
|
+
var resolveMap = (resolvers, context) => {
|
80
|
+
const resolutionMapEntries = Object.entries(resolvers).map(
|
81
|
+
([key, resolver]) => [key, resolver(context)]
|
51
82
|
);
|
52
83
|
const hasPromises = resolutionMapEntries.some(
|
53
84
|
([, resolution]) => resolution instanceof Promise
|
package/dist/index.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T> & { __brand: \"provider\" };\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Creating a nominal type value and introducing common functionality.\n */\nconst createProvider = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Resolver<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return Object.assign(instance, { __brand: \"provider\" as const });\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = createProvider;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = createProvider((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance = createProvider((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : resolver(context);\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";AA2BA,IAAM,iBAAiB,CAAI,aAAuC;AAC9D,QAAM,WAAwB,CAAC,YAAY;AA5B/C;AA6BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO,OAAO,OAAO,UAAU,EAAE,SAAS,WAAoB,CAAC;AACnE;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACpGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
|
1
|
+
{"version":3,"sources":["../src/resolver.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that takes a resolution context\n * and returns a value of some type.\n */\nexport type ResolverFn<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that returns a value of some type\n * based on a resolution context.\n */\nexport type Resolver<T> = ResolverFn<T>;\n\n/**\n * A context used by resolvers that defines the behaviour of the resolver\n * with the passed mocks and scope.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Makes the resolver function capable of replacing itself\n * with a mock if one is defined in the resolution context.\n */\nconst mockable = <T>(fn: ResolverFn<T>): ResolverFn<T> => {\n const instance = (context?: ResolutionContext) => {\n const mock = context?.mocks?.get(instance);\n if (!mock) return fn(context);\n\n if (!mock.isPartial) return mock.resolver(context);\n\n const resolution = fn(context);\n const mockResolution = mock.resolver(context);\n\n if (resolution instanceof Promise && mockResolution instanceof Promise)\n return Promise.all([resolution, mockResolution]).then(([a, b]) =>\n Object.assign(a as object, b),\n ) as T;\n\n return Object.assign(resolution as object, mockResolution);\n };\n\n return instance;\n};\n\n/**\n * Creates a resolver that creates a new resolution on each call.\n *\n * @example\n * ```ts\n * const getEntity = transient(() => createEntity())\n * getEntity() !== getEntity()\n * ```\n *\n * @param resolver\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The transient resolver.\n */\nexport const transient = <T>(fn: ResolverFn<T>): Resolver<T> => mockable(fn);\n\n/**\n * Creates a resolver that creates\n * a resolution once and return it on each call.\n *\n * @example\n * ```ts\n * const getEntity = singleton(() => createEntity())\n * getEntity() === getEntity()\n * ```\n *\n * @param fn\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The singleton resolver.\n */\nexport const singleton = <T>(fn: ResolverFn<T>): Resolver<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = mockable((context) => {\n if (resolved) return resolution!;\n\n resolution = fn(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a resolver that takes its resolution\n * from a scope or create a new one and save it if there is none.\n * If no scope was passed in a resolution context,\n * it will act as a singleton.\n *\n * @example\n * ```ts\n * const getEntity = scoped(() => createEntity())\n * getEntity() === getEntity()\n * ```\n *\n * @example\n * ```ts\n * const getEntity = scoped(() => createEntity())\n * const scope = createScope()\n * getEntity({ scope }) === getEntity({ scope }) !== getEntity()\n * ```\n *\n * @param fn\n * A function that takes a resolution context\n * and returns a value of some type.\n *\n * @returns The scoped resolver.\n */\nexport const scoped = <T>(fn: ResolverFn<T>): Resolver<T> => {\n const singletonFallback = singleton(fn);\n\n const instance = mockable((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : fn(context);\n\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./resolver\";\n\n/**\n * A `Map` of resolvers to their resolutions.\n * Is passed in a resolution context and used by scoped resolvers\n * to retrieve or save resolution within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances.\n * Is passed in a resolution context and used by scoped resolvers\n * to retrieve or save resolution within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * })\n * ```\n *\n * @returns The map.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./resolver\";\n\ntype PromiseAwarePartial<T> =\n T extends Promise<infer U> ? Promise<Partial<U>> : Partial<T>;\n\ntype Mock<T> =\n | {\n isPartial: false;\n resolver: Resolver<T>;\n }\n | {\n isPartial: true;\n resolver: Resolver<PromiseAwarePartial<T>>;\n };\n\ntype MocksEntries = [Resolver<any>, Mock<any>][];\n\n/**\n * Immutable map that registers and provides mocks.\n * Is passed in a resolution context and used by resolvers\n * to replace or partially replace themselves with a mock if one is defined.\n */\nexport type MockMap = {\n /**\n * Registers a mock for a resolver,\n * creating a new `MockMap` with this registration.\n *\n * @param original - The original resolver.\n * @param mock - The mock resolver.\n */\n mock<T>(original: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Registers a partial mock for a resolver,\n * creating a new `MockMap` with this registration.\n * In this case, the mock resolver's resolution object will be\n * merged with the original resolver's resolution object,\n * overwriting certain fields.\n *\n * @param original - The original resolver.\n * @param mock - The mock resolver.\n */\n mockPartially<T extends object>(\n original: Resolver<T>,\n mock: Resolver<PromiseAwarePartial<T>>,\n ): MockMap;\n /**\n * Returns a mock of a resolver\n * or `undefined` if one is not registered.\n *\n * @param original - The original resolver.\n */\n get<T>(original: Resolver<T>): Mock<T> | undefined;\n};\n\n/**\n * Internal implementation that accepts entries.\n */\nconst createMockMapWithEntries = (entries: MocksEntries = []): MockMap => {\n const set = (key: Resolver<any>, value: Mock<any>) =>\n createMockMapWithEntries([\n ...entries.filter((entry) => entry[0] !== key),\n [key, value],\n ]);\n\n return {\n mock(original, mock) {\n return set(original, {\n isPartial: false,\n resolver: mock,\n });\n },\n mockPartially(original, mock) {\n return set(original, {\n isPartial: true,\n resolver: mock,\n });\n },\n get(original) {\n return entries.find((entry) => entry[0] === original)?.[1] as any;\n },\n };\n};\n\n/**\n * Creates a mock map, an immutable map that registers and provides mocks.\n * Is passed in a resolution context and used by resolvers\n * to replace or partially replace themselves with a mock if one is defined.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .mock(getDependency, getDependencyMock)\n * .mockPartially(\n * getOtherDepedency,\n * transient(() => ({ someField: \"mock\" }))\n * )\n *\n * const entityWithMocks = getEntity({ mocks })\n * ```\n *\n * @returns The mock map.\n */\nexport const createMockMap = (): MockMap => createMockMapWithEntries();\n","import { ResolutionContext, Resolver } from \"./resolver\";\n\ntype ResolverList = Resolver<any>[];\ntype ResolverRecord = Record<string, Resolver<any>>;\n\ntype InferResolverCollectionResolutions<\n Resolvers extends ResolverList | ResolverRecord,\n> = {\n [K in keyof Resolvers]: Resolvers[K] extends Resolver<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every resolver in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync resolvers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some resolver is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param resolvers - The list of resolvers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Resolvers extends ResolverList>(\n resolvers: Resolvers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<InferResolverCollectionResolutions<Resolvers>> => {\n const resolutions = resolvers.map((resolver) => resolver(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every resolver in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync resolvers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some resolver is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param resolvers - The map of resolvers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Resolvers extends ResolverRecord>(\n resolvers: Resolvers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<InferResolverCollectionResolutions<Resolvers>> => {\n const resolutionMapEntries = Object.entries(resolvers).map(\n ([key, resolver]) => [key, resolver(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";AA4BA,IAAM,WAAW,CAAI,OAAqC;AACtD,QAAM,WAAW,CAAC,YAAgC;AA7BtD;AA8BQ,UAAM,QAAO,wCAAS,UAAT,mBAAgB,IAAI;AACjC,QAAI,CAAC,KAAM,QAAO,GAAG,OAAO;AAE5B,QAAI,CAAC,KAAK,UAAW,QAAO,KAAK,SAAS,OAAO;AAEjD,UAAM,aAAa,GAAG,OAAO;AAC7B,UAAM,iBAAiB,KAAK,SAAS,OAAO;AAE5C,QAAI,sBAAsB,WAAW,0BAA0B;AAC3D,aAAO,QAAQ,IAAI,CAAC,YAAY,cAAc,CAAC,EAAE;AAAA,QAAK,CAAC,CAAC,GAAG,CAAC,MACxD,OAAO,OAAO,GAAa,CAAC;AAAA,MAChC;AAEJ,WAAO,OAAO,OAAO,YAAsB,cAAc;AAAA,EAC7D;AAEA,SAAO;AACX;AAiBO,IAAM,YAAY,CAAI,OAAmC,SAAS,EAAE;AAkBpE,IAAM,YAAY,CAAI,OAAmC;AAC5D,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAW,SAAS,CAAC,YAAY;AACnC,QAAI,SAAU,QAAO;AAErB,iBAAa,GAAG,OAAO;AACvB,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA2BO,IAAM,SAAS,CAAI,OAAmC;AACzD,QAAM,oBAAoB,UAAU,EAAE;AAEtC,QAAM,WAAW,SAAS,CAAC,YAAY;AACnC,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,GAAG,OAAO;AAEhB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;AClHO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACgChD,IAAM,2BAA2B,CAAC,UAAwB,CAAC,MAAe;AACtE,QAAM,MAAM,CAAC,KAAoB,UAC7B,yBAAyB;AAAA,IACrB,GAAG,QAAQ,OAAO,CAAC,UAAU,MAAM,CAAC,MAAM,GAAG;AAAA,IAC7C,CAAC,KAAK,KAAK;AAAA,EACf,CAAC;AAEL,SAAO;AAAA,IACH,KAAK,UAAU,MAAM;AACjB,aAAO,IAAI,UAAU;AAAA,QACjB,WAAW;AAAA,QACX,UAAU;AAAA,MACd,CAAC;AAAA,IACL;AAAA,IACA,cAAc,UAAU,MAAM;AAC1B,aAAO,IAAI,UAAU;AAAA,QACjB,WAAW;AAAA,QACX,UAAU;AAAA,MACd,CAAC;AAAA,IACL;AAAA,IACA,IAAI,UAAU;AA7EtB;AA8EY,cAAO,aAAQ,KAAK,CAAC,UAAU,MAAM,CAAC,MAAM,QAAQ,MAA7C,mBAAiD;AAAA,IAC5D;AAAA,EACJ;AACJ;AAqBO,IAAM,gBAAgB,MAAe,yBAAyB;;;AC7B9D,IAAM,cAAc,CACvB,WACA,YACyE;AACzE,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YACyE;AACzE,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "atomic-di",
|
3
|
-
"version": "
|
4
|
-
"description": "Lifetimes, scopes and mocking for pure dependency injection
|
3
|
+
"version": "2.0.1",
|
4
|
+
"description": "Lifetimes, scopes and mocking for pure dependency injection",
|
5
5
|
"repository": "https://github.com/ncor/atomic-di",
|
6
6
|
"bugs": "https://github.com/ncor/atomic-di/issues",
|
7
7
|
"homepage": "https://github.com/ncor/atomic-di#readme",
|