di-craft 0.0.19 → 0.0.21

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/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  <p align="center">
8
8
  <b>A tiny, type-safe dependency injection container for TypeScript</b>
9
+ <br />
10
+ <span>Framework-agnostic core with an optional Next.js App Router / RSC adapter</span>
9
11
  </p>
10
12
 
11
13
  <p align="center">
@@ -90,6 +92,7 @@ they still use explicit tokens.
90
92
  - Zero runtime dependencies
91
93
  - Type-safe tokens and factories
92
94
  - Optional `@Injectable` annotation for class providers
95
+ - Optional Next.js App Router / React Server Components adapter
93
96
  - Optional dependencies via `optional()`
94
97
  - Singleton, transient, and scoped lifetimes
95
98
  - Hierarchical child containers
@@ -0,0 +1,323 @@
1
+ //#region src/core/error/error.ts
2
+ var DiError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "DiError";
6
+ const errorConstructor = Error;
7
+ const classConstructor = this.constructor;
8
+ if (errorConstructor.captureStackTrace) errorConstructor.captureStackTrace(this, classConstructor);
9
+ }
10
+ };
11
+
12
+ //#endregion
13
+ //#region src/core/provider/errors.ts
14
+ /**
15
+ * Error thrown when a provider configuration cannot be used safely.
16
+ */
17
+ var InvalidProviderError = class extends DiError {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "InvalidProviderError";
21
+ }
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/core/scope/scope.ts
26
+ /**
27
+ * Built-in provider lifetimes.
28
+ */
29
+ const Scopes = {
30
+ Singleton: "singleton",
31
+ Transient: "transient",
32
+ Scoped: "scoped"
33
+ };
34
+
35
+ //#endregion
36
+ //#region src/core/provider/provider.ts
37
+ /**
38
+ * Creates a provider for an already constructed value.
39
+ *
40
+ * The value must match the token type.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * provideValue(PORT, 3000);
45
+ * ```
46
+ */
47
+ const provideValue = (token, useValue) => ({
48
+ provide: token,
49
+ useValue
50
+ });
51
+ /**
52
+ * Creates a provider that lazily builds a value.
53
+ *
54
+ * Dependencies declared in `deps` become the object passed to `useFactory`.
55
+ * The factory return type must match the token type.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * provideFactory(HTTP, {
60
+ * deps: { config: CONFIG },
61
+ * useFactory: ({ config }) => new HttpClient(config.apiUrl),
62
+ * });
63
+ * ```
64
+ */
65
+ const provideFactory = (token, options) => {
66
+ if (options.scope === Scopes.Transient && options.onDispose) throw new InvalidProviderError(`onDispose is not supported for transient providers (token "${token.name}"): transient instances are not tracked, so the hook would never run.`);
67
+ return {
68
+ provide: token,
69
+ useFactory: options.useFactory,
70
+ scope: options.scope ?? Scopes.Singleton,
71
+ ...options.deps ? { deps: options.deps } : {},
72
+ ...options.onDispose ? { onDispose: options.onDispose } : {}
73
+ };
74
+ };
75
+ /**
76
+ * Marks a token as optional.
77
+ *
78
+ * Optional dependencies resolve to `undefined` instead of throwing when no
79
+ * provider exists in the container chain. They can be used in factory `deps`
80
+ * or passed directly to `container.get`.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const logger = container.get(optional(LOGGER));
85
+ * ```
86
+ */
87
+ const optional = (token) => ({
88
+ token,
89
+ optional: true
90
+ });
91
+ const isOptionalDependency = (dependency) => {
92
+ return dependency.optional === true;
93
+ };
94
+ const isValueProvider = (provider) => {
95
+ return "useValue" in provider;
96
+ };
97
+
98
+ //#endregion
99
+ //#region src/core/registry/errors.ts
100
+ /**
101
+ * Error thrown when registering a provider for an already registered token.
102
+ */
103
+ var DuplicateProviderError = class extends DiError {
104
+ constructor(tokenName) {
105
+ super(`Provider for token "${tokenName}" is already registered`);
106
+ this.name = "DuplicateProviderError";
107
+ }
108
+ };
109
+
110
+ //#endregion
111
+ //#region src/core/registry/registry.ts
112
+ var RegistryClass = class {
113
+ providers = /* @__PURE__ */ new Map();
114
+ register(provider, options) {
115
+ const existed = this.providers.has(provider.provide.id);
116
+ if (existed && !options?.allowOverride) throw new DuplicateProviderError(provider.provide.name);
117
+ this.providers.set(provider.provide.id, provider);
118
+ return existed;
119
+ }
120
+ get(token) {
121
+ return this.providers.get(token.id);
122
+ }
123
+ has(token) {
124
+ return this.providers.has(token.id);
125
+ }
126
+ };
127
+ const createRegistry = () => new RegistryClass();
128
+
129
+ //#endregion
130
+ //#region src/core/resolver/errors.ts
131
+ /**
132
+ * Error thrown when resolving a token without a registered provider.
133
+ */
134
+ var MissingProviderError = class extends DiError {
135
+ constructor(tokenName) {
136
+ super(`Provider for token "${tokenName}" is not registered`);
137
+ this.name = "MissingProviderError";
138
+ }
139
+ };
140
+ /**
141
+ * Error thrown when a dependency descriptor is invalid.
142
+ */
143
+ var InvalidDependencyError = class extends DiError {
144
+ constructor(dependencyKey) {
145
+ super(`Invalid dependency "${dependencyKey}"`);
146
+ this.name = "InvalidDependencyError";
147
+ }
148
+ };
149
+ /**
150
+ * Error thrown when provider dependencies form a cycle.
151
+ */
152
+ var CircularDependencyError = class extends DiError {
153
+ constructor(tokenNames) {
154
+ super(`Circular dependency detected: ${tokenNames.join(" -> ")}`);
155
+ this.name = "CircularDependencyError";
156
+ }
157
+ };
158
+
159
+ //#endregion
160
+ //#region src/core/store/store.ts
161
+ var StoreClass = class {
162
+ instances = /* @__PURE__ */ new Map();
163
+ get(token) {
164
+ return this.instances.get(token.id);
165
+ }
166
+ set(token, record) {
167
+ this.instances.set(token.id, record);
168
+ }
169
+ delete(token) {
170
+ this.instances.delete(token.id);
171
+ }
172
+ async dispose() {
173
+ const records = [...this.instances.values()].reverse();
174
+ this.instances.clear();
175
+ for (const record of records) if (record.onDispose) await record.onDispose(record.value);
176
+ }
177
+ };
178
+ const createStore = () => new StoreClass();
179
+
180
+ //#endregion
181
+ //#region src/core/resolver/context.ts
182
+ var ResolutionContext = class {
183
+ resolving = /* @__PURE__ */ new Set();
184
+ path = [];
185
+ enter(token) {
186
+ if (this.resolving.has(token.id)) {
187
+ const cycleStartIndex = this.path.findIndex((pathToken) => pathToken.id === token.id);
188
+ throw new CircularDependencyError([...this.path.slice(cycleStartIndex), token].map((cycleToken) => cycleToken.name));
189
+ }
190
+ this.resolving.add(token.id);
191
+ this.path.push(token);
192
+ }
193
+ exit(token) {
194
+ this.path.pop();
195
+ this.resolving.delete(token.id);
196
+ }
197
+ };
198
+
199
+ //#endregion
200
+ //#region src/core/resolver/resolver.ts
201
+ const lifetimeRank = (scope) => scope === Scopes.Scoped ? 1 : scope === Scopes.Transient ? 2 : 0;
202
+ var ResolverClass = class {
203
+ registry;
204
+ store = createStore();
205
+ parent;
206
+ constructor(registry, parent) {
207
+ this.registry = registry;
208
+ this.parent = parent;
209
+ }
210
+ resolve(token) {
211
+ return this.resolveToken(token, void 0);
212
+ }
213
+ resolveOptional(token) {
214
+ return this.lookupOptional(token, void 0);
215
+ }
216
+ invalidate(token) {
217
+ this.store.delete(token);
218
+ }
219
+ hasDisposableInstance(token) {
220
+ return this.store.get(token)?.onDispose !== void 0;
221
+ }
222
+ dispose() {
223
+ return this.store.dispose();
224
+ }
225
+ resolveToken(token, context, consumerScope, consumerName) {
226
+ let owner = this;
227
+ let provider;
228
+ while (owner) {
229
+ provider = owner.registry.get(token);
230
+ if (provider) break;
231
+ owner = owner.parent;
232
+ }
233
+ if (!provider || !owner) throw new MissingProviderError(token.name);
234
+ if (isValueProvider(provider)) return provider.useValue;
235
+ if (consumerScope !== void 0 && lifetimeRank(provider.scope) > lifetimeRank(consumerScope)) throw new InvalidProviderError(`"${consumerName}" (${consumerScope}) cannot depend on "${token.name}" (${provider.scope ?? Scopes.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`);
236
+ const host = this.selectHost(provider.scope, owner);
237
+ if (host) {
238
+ const cached = host.store.get(token);
239
+ if (cached) return cached.value;
240
+ }
241
+ const ctx = context ?? new ResolutionContext();
242
+ ctx.enter(token);
243
+ try {
244
+ const deps = (host ?? this).resolveDeps(provider.deps, ctx, provider.scope, token.name);
245
+ const value = provider.useFactory(deps);
246
+ if (host) host.store.set(token, {
247
+ value,
248
+ ...provider.onDispose ? { onDispose: provider.onDispose } : {}
249
+ });
250
+ return value;
251
+ } finally {
252
+ ctx.exit(token);
253
+ }
254
+ }
255
+ selectHost(scope, owner) {
256
+ if (scope === Scopes.Transient) return;
257
+ if (scope === Scopes.Scoped) return this;
258
+ return owner;
259
+ }
260
+ resolveDeps(deps, context, consumerScope, consumerName) {
261
+ if (!deps) return {};
262
+ const resolvedDeps = {};
263
+ for (const key of Object.keys(deps)) {
264
+ const dependency = deps[key];
265
+ if (dependency === void 0) throw new InvalidDependencyError(String(key));
266
+ resolvedDeps[key] = isOptionalDependency(dependency) ? this.lookupOptional(dependency.token, context, consumerScope, consumerName) : this.resolveToken(dependency, context, consumerScope, consumerName);
267
+ }
268
+ return resolvedDeps;
269
+ }
270
+ lookupOptional(token, context, consumerScope, consumerName) {
271
+ let owner = this;
272
+ while (owner) {
273
+ if (owner.registry.has(token)) return this.resolveToken(token, context, consumerScope, consumerName);
274
+ owner = owner.parent;
275
+ }
276
+ }
277
+ };
278
+ const createResolver = (registry, parent) => new ResolverClass(registry, parent);
279
+
280
+ //#endregion
281
+ //#region src/core/container/container.ts
282
+ var ContainerClass = class {
283
+ registry;
284
+ resolver;
285
+ parent;
286
+ constructor(providers = [], parent) {
287
+ this.parent = parent;
288
+ this.registry = createRegistry();
289
+ for (const provider of providers) this.registry.register(provider);
290
+ this.resolver = createResolver(this.registry, parent?.resolver);
291
+ }
292
+ register(provider, options) {
293
+ if (options?.allowOverride && this.resolver.hasDisposableInstance(provider.provide)) throw new InvalidProviderError(`Cannot override token "${provider.provide.name}": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`);
294
+ if (this.registry.register(provider, options)) this.resolver.invalidate(provider.provide);
295
+ }
296
+ get(dependency) {
297
+ if (isOptionalDependency(dependency)) return this.resolver.resolveOptional(dependency.token);
298
+ return this.resolver.resolve(dependency);
299
+ }
300
+ has(token) {
301
+ return this.registry.has(token) || (this.parent?.has(token) ?? false);
302
+ }
303
+ dispose() {
304
+ return this.resolver.dispose();
305
+ }
306
+ };
307
+ /**
308
+ * Creates a root container with optional initial providers.
309
+ *
310
+ * Root containers own singleton instances for providers registered in them.
311
+ */
312
+ const createContainer = (providers = []) => new ContainerClass(providers);
313
+ /**
314
+ * Creates a child container that can resolve providers from its parent.
315
+ *
316
+ * Child containers may register their own providers while still reusing parent
317
+ * providers. Scoped providers create one cached instance per resolving child.
318
+ */
319
+ const createChildContainer = (parent, providers = []) => new ContainerClass(providers, parent);
320
+
321
+ //#endregion
322
+ export { MissingProviderError as a, provideFactory as c, InvalidProviderError as d, DiError as f, InvalidDependencyError as i, provideValue as l, createContainer as n, DuplicateProviderError as o, CircularDependencyError as r, optional as s, createChildContainer as t, Scopes as u };
323
+ //# sourceMappingURL=container-l5DkUBUj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-l5DkUBUj.mjs","names":[],"sources":["../src/core/error/error.ts","../src/core/provider/errors.ts","../src/core/scope/scope.ts","../src/core/provider/provider.ts","../src/core/registry/errors.ts","../src/core/registry/registry.ts","../src/core/resolver/errors.ts","../src/core/store/store.ts","../src/core/resolver/context.ts","../src/core/resolver/resolver.ts","../src/core/container/container.ts"],"sourcesContent":["/**\n * Base class for all di-craft runtime errors.\n */\ntype ErrorConstructorWithStackTrace = ErrorConstructor & {\n\treadonly captureStackTrace?: (\n\t\terror: Error,\n\t\tconstructorFunction: (...args: never[]) => unknown,\n\t) => void;\n};\n\nexport class DiError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"DiError\";\n\n\t\tconst errorConstructor = Error as ErrorConstructorWithStackTrace;\n\t\tconst classConstructor = this.constructor as (...args: never[]) => unknown;\n\n\t\tif (errorConstructor.captureStackTrace) {\n\t\t\terrorConstructor.captureStackTrace(this, classConstructor);\n\t\t}\n\t}\n}\n","import { DiError } from \"../error\";\n\n/**\n * Error thrown when a provider configuration cannot be used safely.\n */\nexport class InvalidProviderError extends DiError {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"InvalidProviderError\";\n\t}\n}\n","/**\n * Built-in provider lifetimes.\n */\nexport const Scopes = {\n\tSingleton: \"singleton\",\n\tTransient: \"transient\",\n\tScoped: \"scoped\",\n} as const;\n\n/**\n * Provider lifetime.\n *\n * - `singleton`: one cached instance in the container that owns the provider.\n * - `scoped`: one cached instance in the container that resolves the provider.\n * - `transient`: a new instance for every resolution.\n */\nexport type Scope = (typeof Scopes)[keyof typeof Scopes];\n","import { type Scope, Scopes } from \"../scope\";\nimport type { Token } from \"../token\";\nimport { InvalidProviderError } from \"./errors\";\nimport type {\n\tAnyFactoryProvider,\n\tDependency,\n\tDepsMap,\n\tDisposeHook,\n\tFactory,\n\tFactoryProvider,\n\tOptionalDependency,\n\tProvider,\n\tValueProvider,\n} from \"./types\";\n\n/**\n * Creates a provider for an already constructed value.\n *\n * The value must match the token type.\n *\n * @example\n * ```ts\n * provideValue(PORT, 3000);\n * ```\n */\nexport const provideValue = <T>(\n\ttoken: Token<T>,\n\tuseValue: T,\n): ValueProvider<T> => ({\n\tprovide: token,\n\tuseValue,\n});\n\n/**\n * Creates a provider that lazily builds a value.\n *\n * Dependencies declared in `deps` become the object passed to `useFactory`.\n * The factory return type must match the token type.\n *\n * @example\n * ```ts\n * provideFactory(HTTP, {\n * deps: { config: CONFIG },\n * useFactory: ({ config }) => new HttpClient(config.apiUrl),\n * });\n * ```\n */\nexport const provideFactory = <T, TDeps extends DepsMap = Record<never, never>>(\n\ttoken: Token<T>,\n\toptions: {\n\t\treadonly deps?: TDeps;\n\t\treadonly scope?: Scope;\n\t\treadonly useFactory: Factory<T, TDeps>;\n\t\treadonly onDispose?: DisposeHook<T>;\n\t},\n): FactoryProvider<T, TDeps> => {\n\t// Transient instances aren't cached, so onDispose could never run.\n\tif (options.scope === Scopes.Transient && options.onDispose) {\n\t\tthrow new InvalidProviderError(\n\t\t\t`onDispose is not supported for transient providers (token \"${token.name}\"): transient instances are not tracked, so the hook would never run.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tprovide: token,\n\t\tuseFactory: options.useFactory,\n\t\tscope: options.scope ?? Scopes.Singleton,\n\t\t...(options.deps ? { deps: options.deps } : {}),\n\t\t...(options.onDispose ? { onDispose: options.onDispose } : {}),\n\t};\n};\n\n/**\n * Marks a token as optional.\n *\n * Optional dependencies resolve to `undefined` instead of throwing when no\n * provider exists in the container chain. They can be used in factory `deps`\n * or passed directly to `container.get`.\n *\n * @example\n * ```ts\n * const logger = container.get(optional(LOGGER));\n * ```\n */\nexport const optional = <T>(token: Token<T>): OptionalDependency<T> => ({\n\ttoken,\n\toptional: true,\n});\n\nexport const isOptionalDependency = <T>(\n\tdependency: Dependency<T>,\n): dependency is OptionalDependency<T> => {\n\treturn (dependency as OptionalDependency<T>).optional === true;\n};\n\nexport const isValueProvider = (\n\tprovider: Provider,\n): provider is ValueProvider<unknown> => {\n\treturn \"useValue\" in provider;\n};\n\nexport const isFactoryProvider = (\n\tprovider: Provider,\n): provider is AnyFactoryProvider => {\n\treturn \"useFactory\" in provider;\n};\n","import { DiError } from \"../error\";\n\n/**\n * Error thrown when registering a provider for an already registered token.\n */\nexport class DuplicateProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is already registered`);\n\n\t\tthis.name = \"DuplicateProviderError\";\n\t}\n}\n","import type { Provider } from \"../provider\";\nimport type { Token } from \"../token\";\nimport { DuplicateProviderError } from \"./errors\";\nimport type { RegisterOptions, Registry } from \"./types\";\n\nclass RegistryClass implements Registry {\n\tprivate readonly providers = new Map<symbol, Provider>();\n\n\tregister(provider: Provider, options?: RegisterOptions): boolean {\n\t\tconst existed = this.providers.has(provider.provide.id);\n\n\t\tif (existed && !options?.allowOverride) {\n\t\t\tthrow new DuplicateProviderError(provider.provide.name);\n\t\t}\n\n\t\tthis.providers.set(provider.provide.id, provider);\n\n\t\t// true if an existing provider was replaced (override).\n\t\treturn existed;\n\t}\n\n\tget(token: Token<unknown>): Provider | undefined {\n\t\treturn this.providers.get(token.id);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.providers.has(token.id);\n\t}\n}\n\nexport const createRegistry = (): Registry => new RegistryClass();\n","import { DiError } from \"../error\";\n\n/**\n * Error thrown when resolving a token without a registered provider.\n */\nexport class MissingProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is not registered`);\n\n\t\tthis.name = \"MissingProviderError\";\n\t}\n}\n\n/**\n * Error thrown when a dependency descriptor is invalid.\n */\nexport class InvalidDependencyError extends DiError {\n\tconstructor(dependencyKey: string) {\n\t\tsuper(`Invalid dependency \"${dependencyKey}\"`);\n\n\t\tthis.name = \"InvalidDependencyError\";\n\t}\n}\n\n/**\n * Error thrown when provider dependencies form a cycle.\n */\nexport class CircularDependencyError extends DiError {\n\tconstructor(tokenNames: string[]) {\n\t\tsuper(`Circular dependency detected: ${tokenNames.join(\" -> \")}`);\n\n\t\tthis.name = \"CircularDependencyError\";\n\t}\n}\n","import type { Token } from \"../token\";\nimport type { InstanceRecord, Store } from \"./types\";\n\nclass StoreClass implements Store {\n\tprivate readonly instances = new Map<symbol, InstanceRecord>();\n\n\tget(token: Token<unknown>): InstanceRecord | undefined {\n\t\treturn this.instances.get(token.id);\n\t}\n\n\tset(token: Token<unknown>, record: InstanceRecord): void {\n\t\tthis.instances.set(token.id, record);\n\t}\n\n\tdelete(token: Token<unknown>): void {\n\t\tthis.instances.delete(token.id);\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\t// Snapshot and clear first so dispose() is idempotent and re-entrancy safe.\n\t\tconst records = [...this.instances.values()].reverse();\n\n\t\tthis.instances.clear();\n\n\t\tfor (const record of records) {\n\t\t\tif (record.onDispose) {\n\t\t\t\tawait record.onDispose(record.value);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport const createStore = (): Store => new StoreClass();\n","import type { Token } from \"../token\";\nimport { CircularDependencyError } from \"./errors\";\n\nexport class ResolutionContext {\n\tprivate readonly resolving = new Set<symbol>();\n\tprivate readonly path: Token<unknown>[] = [];\n\n\tenter(token: Token<unknown>): void {\n\t\tif (this.resolving.has(token.id)) {\n\t\t\tconst cycleStartIndex = this.path.findIndex(\n\t\t\t\t(pathToken) => pathToken.id === token.id,\n\t\t\t);\n\n\t\t\tconst cycle = [...this.path.slice(cycleStartIndex), token];\n\n\t\t\tthrow new CircularDependencyError(\n\t\t\t\tcycle.map((cycleToken) => cycleToken.name),\n\t\t\t);\n\t\t}\n\n\t\tthis.resolving.add(token.id);\n\t\tthis.path.push(token);\n\t}\n\n\texit(token: Token<unknown>): void {\n\t\tthis.path.pop();\n\t\tthis.resolving.delete(token.id);\n\t}\n}\n","import type { DepsMap, Provider, ResolveDeps } from \"../provider\";\nimport {\n\tInvalidProviderError,\n\tisOptionalDependency,\n\tisValueProvider,\n} from \"../provider\";\nimport type { Registry } from \"../registry\";\nimport { type Scope, Scopes } from \"../scope\";\nimport { createStore, type Store } from \"../store\";\nimport type { Token } from \"../token\";\nimport { ResolutionContext } from \"./context\";\nimport { InvalidDependencyError, MissingProviderError } from \"./errors\";\nimport type { Resolver } from \"./types\";\n\n// Lifetime ordering: longer-lived scopes rank lower. A provider may only depend\n// on dependencies that live at least as long as itself.\nconst lifetimeRank = (scope: Scope | undefined): number =>\n\tscope === Scopes.Scoped ? 1 : scope === Scopes.Transient ? 2 : 0;\n\nclass ResolverClass implements Resolver {\n\tprivate readonly registry: Registry;\n\tprivate readonly store: Store = createStore();\n\tprivate readonly parent: ResolverClass | undefined;\n\n\tconstructor(registry: Registry, parent?: ResolverClass) {\n\t\tthis.registry = registry;\n\t\tthis.parent = parent;\n\t}\n\n\tresolve<T>(token: Token<T>): T {\n\t\treturn this.resolveToken(token, undefined);\n\t}\n\n\tresolveOptional<T>(token: Token<T>): T | undefined {\n\t\treturn this.lookupOptional(token, undefined);\n\t}\n\n\tinvalidate(token: Token<unknown>): void {\n\t\tthis.store.delete(token);\n\t}\n\n\t// True if a locally-cached instance has an onDispose hook.\n\thasDisposableInstance(token: Token<unknown>): boolean {\n\t\treturn this.store.get(token)?.onDispose !== undefined;\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.store.dispose();\n\t}\n\n\t// context is allocated lazily by the first building factory (zero-alloc cache hits).\n\t// consumerScope/consumerName describe the provider depending on this token, if any.\n\tprivate resolveToken<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T {\n\t\t// Single walk up the parent chain, fetching the provider directly.\n\t\tlet owner: ResolverClass | undefined = this;\n\t\tlet provider: Provider | undefined;\n\n\t\twhile (owner) {\n\t\t\tprovider = owner.registry.get(token);\n\n\t\t\tif (provider) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\tif (!provider || !owner) {\n\t\t\tthrow new MissingProviderError(token.name);\n\t\t}\n\n\t\t// Values outlive every scope, so they are always a safe dependency.\n\t\tif (isValueProvider(provider)) {\n\t\t\treturn provider.useValue as T;\n\t\t}\n\n\t\t// Otherwise it is a factory provider (the only remaining variant).\n\t\t// Reject capturing a shorter-lived dependency into a longer-lived consumer.\n\t\tif (\n\t\t\tconsumerScope !== undefined &&\n\t\t\tlifetimeRank(provider.scope) > lifetimeRank(consumerScope)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`\"${consumerName}\" (${consumerScope}) cannot depend on \"${token.name}\" (${provider.scope ?? Scopes.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`,\n\t\t\t);\n\t\t}\n\n\t\t// Scope decides the host: singleton on owner, scoped on this, transient nowhere.\n\t\tconst host = this.selectHost(provider.scope, owner);\n\n\t\tif (host) {\n\t\t\tconst cached = host.store.get(token);\n\n\t\t\tif (cached) {\n\t\t\t\treturn cached.value as T;\n\t\t\t}\n\t\t}\n\n\t\t// Allocate cycle detection only now, threading it through the build.\n\t\tconst ctx = context ?? new ResolutionContext();\n\t\tctx.enter(token);\n\n\t\ttry {\n\t\t\t// Resolve deps from the host: scoped sees this container, singleton sees owner.\n\t\t\tconst deps = (host ?? this).resolveDeps(\n\t\t\t\tprovider.deps,\n\t\t\t\tctx,\n\t\t\t\tprovider.scope,\n\t\t\t\ttoken.name,\n\t\t\t);\n\t\t\tconst value = provider.useFactory(deps) as T;\n\n\t\t\tif (host) {\n\t\t\t\thost.store.set(token, {\n\t\t\t\t\tvalue,\n\t\t\t\t\t...(provider.onDispose ? { onDispose: provider.onDispose } : {}),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tctx.exit(token);\n\t\t}\n\t}\n\n\tprivate selectHost(\n\t\tscope: Scope | undefined,\n\t\towner: ResolverClass,\n\t): ResolverClass | undefined {\n\t\tif (scope === Scopes.Transient) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (scope === Scopes.Scoped) {\n\t\t\treturn this;\n\t\t}\n\n\t\treturn owner;\n\t}\n\n\tprivate resolveDeps<TDeps extends DepsMap>(\n\t\tdeps: TDeps | undefined,\n\t\tcontext: ResolutionContext,\n\t\tconsumerScope: Scope | undefined,\n\t\tconsumerName: string,\n\t): ResolveDeps<TDeps> {\n\t\tif (!deps) {\n\t\t\treturn {} as ResolveDeps<TDeps>;\n\t\t}\n\n\t\tconst resolvedDeps: Partial<ResolveDeps<TDeps>> = {};\n\n\t\tfor (const key of Object.keys(deps) as Array<keyof TDeps>) {\n\t\t\tconst dependency = deps[key];\n\n\t\t\tif (dependency === undefined) {\n\t\t\t\tthrow new InvalidDependencyError(String(key));\n\t\t\t}\n\n\t\t\tresolvedDeps[key] = (\n\t\t\t\tisOptionalDependency(dependency)\n\t\t\t\t\t? this.lookupOptional(\n\t\t\t\t\t\t\tdependency.token,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tconsumerScope,\n\t\t\t\t\t\t\tconsumerName,\n\t\t\t\t\t\t)\n\t\t\t\t\t: this.resolveToken(dependency, context, consumerScope, consumerName)\n\t\t\t) as ResolveDeps<TDeps>[typeof key];\n\t\t}\n\n\t\treturn resolvedDeps as ResolveDeps<TDeps>;\n\t}\n\n\t// Absent provider -> undefined; a present one resolves normally (its errors surface).\n\tprivate lookupOptional<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T | undefined {\n\t\tlet owner: ResolverClass | undefined = this;\n\n\t\twhile (owner) {\n\t\t\tif (owner.registry.has(token)) {\n\t\t\t\treturn this.resolveToken(token, context, consumerScope, consumerName);\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n\nexport const createResolver = (\n\tregistry: Registry,\n\tparent?: Resolver,\n): Resolver => new ResolverClass(registry, parent as ResolverClass | undefined);\n","import {\n\ttype Dependency,\n\tInvalidProviderError,\n\tisOptionalDependency,\n\ttype OptionalDependency,\n\ttype Provider,\n} from \"../provider\";\nimport {\n\tcreateRegistry,\n\ttype RegisterOptions,\n\ttype Registry,\n} from \"../registry\";\nimport { createResolver, type Resolver } from \"../resolver\";\nimport type { Token } from \"../token\";\nimport type { Container } from \"./types\";\n\nclass ContainerClass implements Container {\n\tprivate readonly registry: Registry;\n\tprivate readonly resolver: Resolver;\n\tprivate readonly parent: ContainerClass | undefined;\n\n\tconstructor(providers: readonly Provider[] = [], parent?: ContainerClass) {\n\t\tthis.parent = parent;\n\t\tthis.registry = createRegistry();\n\n\t\tfor (const provider of providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\n\t\tthis.resolver = createResolver(this.registry, parent?.resolver);\n\t}\n\n\tregister(provider: Provider, options?: RegisterOptions): void {\n\t\t// Don't silently drop a live disposable instance; require explicit dispose.\n\t\tif (\n\t\t\toptions?.allowOverride &&\n\t\t\tthis.resolver.hasDisposableInstance(provider.provide)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`Cannot override token \"${provider.provide.name}\": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`,\n\t\t\t);\n\t\t}\n\n\t\tconst overridden = this.registry.register(provider, options);\n\n\t\tif (overridden) {\n\t\t\tthis.resolver.invalidate(provider.provide);\n\t\t}\n\t}\n\n\tget<T>(token: Token<T>): T;\n\tget<T>(dependency: OptionalDependency<T>): T | undefined;\n\tget<T>(dependency: Dependency<T>): T | undefined {\n\t\tif (isOptionalDependency(dependency)) {\n\t\t\treturn this.resolver.resolveOptional(dependency.token);\n\t\t}\n\n\t\treturn this.resolver.resolve(dependency);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.registry.has(token) || (this.parent?.has(token) ?? false);\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.resolver.dispose();\n\t}\n}\n\n/**\n * Creates a root container with optional initial providers.\n *\n * Root containers own singleton instances for providers registered in them.\n */\nexport const createContainer = (\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers);\n\n/**\n * Creates a child container that can resolve providers from its parent.\n *\n * Child containers may register their own providers while still reusing parent\n * providers. Scoped providers create one cached instance per resolving child.\n */\nexport const createChildContainer = (\n\tparent: Container,\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers, parent as ContainerClass);\n"],"mappings":"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"}
@@ -171,4 +171,5 @@ declare const dehydrate: <const TSchema extends HydrationSchema>(options: Dehydr
171
171
  */
172
172
  declare const hydrate: <const TSchema extends HydrationSchema>(options: HydrateOptions<TSchema>) => void;
173
173
  //#endregion
174
- export { Hydratable as a, HydrationSnapshot as c, RunWithRequestContainerOptions as d, Serializable as f, DehydrateOptions as i, NextDiAdapter as l, hydrate as n, HydrateOptions as o, SerializablePrimitive as p, CreateNextDiOptions as r, HydrationSchema as s, dehydrate as t, RequestCache as u };
174
+ export { Hydratable as a, HydrationSnapshot as c, RunWithRequestContainerOptions as d, Serializable as f, DehydrateOptions as i, NextDiAdapter as l, hydrate as n, HydrateOptions as o, SerializablePrimitive as p, CreateNextDiOptions as r, HydrationSchema as s, dehydrate as t, RequestCache as u };
175
+ //# sourceMappingURL=hydration-BJ-gWd38.d.mts.map
@@ -0,0 +1,60 @@
1
+ //#region src/adapters/next/hydration.ts
2
+ /**
3
+ * Reads serializable snapshots from hydratable entities declared in a schema.
4
+ *
5
+ * Use this on the server side before crossing into a Client Component. Only the
6
+ * returned snapshot should cross the boundary, not the container or service
7
+ * instances themselves.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const hydration = { user: USER_STATE };
12
+ * const snapshot = dehydrate({
13
+ * container: getRequestContainer(),
14
+ * schema: hydration,
15
+ * });
16
+ * ```
17
+ */
18
+ const dehydrate = (options) => {
19
+ const snapshot = {};
20
+ for (const key of Object.keys(options.schema)) {
21
+ const token = options.schema[key];
22
+ const isMissingToken = token === void 0;
23
+ const snapshotKey = String(key);
24
+ if (isMissingToken) throw new Error(`Missing hydration token for key "${snapshotKey}".`);
25
+ snapshot[snapshotKey] = options.container.get(token).dehydrate();
26
+ }
27
+ return snapshot;
28
+ };
29
+ /**
30
+ * Restores serializable snapshots into hydratable entities declared in a schema.
31
+ *
32
+ * Use this with a client-safe container and the snapshot received through a
33
+ * Client Component prop.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * hydrate({
38
+ * container: clientContainer,
39
+ * schema: hydration,
40
+ * snapshot,
41
+ * });
42
+ * ```
43
+ */
44
+ const hydrate = (options) => {
45
+ const snapshotRecord = options.snapshot;
46
+ for (const key of Object.keys(options.schema)) {
47
+ const token = options.schema[key];
48
+ const snapshotKey = String(key);
49
+ const value = snapshotRecord[snapshotKey];
50
+ const isMissingToken = token === void 0;
51
+ const isMissingSnapshot = value === void 0;
52
+ if (isMissingToken) throw new Error(`Missing hydration token for key "${snapshotKey}".`);
53
+ if (isMissingSnapshot) throw new Error(`Missing hydration snapshot for key "${snapshotKey}".`);
54
+ options.container.get(token).hydrate(value);
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ export { hydrate as n, dehydrate as t };
60
+ //# sourceMappingURL=hydration-CLZgCNpJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration-CLZgCNpJ.mjs","names":[],"sources":["../src/adapters/next/hydration.ts"],"sourcesContent":["import type {\n\tDehydrateOptions,\n\tHydrateOptions,\n\tHydrationSchema,\n\tHydrationSnapshot,\n\tSerializable,\n} from \"./types\";\n\n/**\n * Reads serializable snapshots from hydratable entities declared in a schema.\n *\n * Use this on the server side before crossing into a Client Component. Only the\n * returned snapshot should cross the boundary, not the container or service\n * instances themselves.\n *\n * @example\n * ```ts\n * const hydration = { user: USER_STATE };\n * const snapshot = dehydrate({\n * container: getRequestContainer(),\n * schema: hydration,\n * });\n * ```\n */\nexport const dehydrate = <const TSchema extends HydrationSchema>(\n\toptions: DehydrateOptions<TSchema>,\n): HydrationSnapshot<TSchema> => {\n\tconst snapshot: Record<string, Serializable> = {};\n\n\tfor (const key of Object.keys(options.schema) as Array<keyof TSchema>) {\n\t\tconst token = options.schema[key];\n\t\tconst isMissingToken = token === undefined;\n\t\tconst snapshotKey = String(key);\n\n\t\tif (isMissingToken) {\n\t\t\tthrow new Error(`Missing hydration token for key \"${snapshotKey}\".`);\n\t\t}\n\n\t\tsnapshot[snapshotKey] = options.container.get(token).dehydrate();\n\t}\n\n\treturn snapshot as HydrationSnapshot<TSchema>;\n};\n\n/**\n * Restores serializable snapshots into hydratable entities declared in a schema.\n *\n * Use this with a client-safe container and the snapshot received through a\n * Client Component prop.\n *\n * @example\n * ```ts\n * hydrate({\n * container: clientContainer,\n * schema: hydration,\n * snapshot,\n * });\n * ```\n */\nexport const hydrate = <const TSchema extends HydrationSchema>(\n\toptions: HydrateOptions<TSchema>,\n): void => {\n\tconst snapshotRecord = options.snapshot as Record<\n\t\tstring,\n\t\tSerializable | undefined\n\t>;\n\n\tfor (const key of Object.keys(options.schema) as Array<keyof TSchema>) {\n\t\tconst token = options.schema[key];\n\t\tconst snapshotKey = String(key);\n\t\tconst value = snapshotRecord[snapshotKey];\n\n\t\tconst isMissingToken = token === undefined;\n\t\tconst isMissingSnapshot = value === undefined;\n\n\t\tif (isMissingToken) {\n\t\t\tthrow new Error(`Missing hydration token for key \"${snapshotKey}\".`);\n\t\t}\n\n\t\tif (isMissingSnapshot) {\n\t\t\tthrow new Error(`Missing hydration snapshot for key \"${snapshotKey}\".`);\n\t\t}\n\n\t\toptions.container.get(token).hydrate(value);\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAa,aACZ,YACgC;CAChC,MAAM,WAAyC,CAAC;CAEhD,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,MAAM,GAA2B;EACtE,MAAM,QAAQ,QAAQ,OAAO;EAC7B,MAAM,iBAAiB,UAAU;EACjC,MAAM,cAAc,OAAO,GAAG;EAE9B,IAAI,gBACH,MAAM,IAAI,MAAM,oCAAoC,YAAY,GAAG;EAGpE,SAAS,eAAe,QAAQ,UAAU,IAAI,KAAK,CAAC,CAAC,UAAU;CAChE;CAEA,OAAO;AACR;;;;;;;;;;;;;;;;AAiBA,MAAa,WACZ,YACU;CACV,MAAM,iBAAiB,QAAQ;CAK/B,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,MAAM,GAA2B;EACtE,MAAM,QAAQ,QAAQ,OAAO;EAC7B,MAAM,cAAc,OAAO,GAAG;EAC9B,MAAM,QAAQ,eAAe;EAE7B,MAAM,iBAAiB,UAAU;EACjC,MAAM,oBAAoB,UAAU;EAEpC,IAAI,gBACH,MAAM,IAAI,MAAM,oCAAoC,YAAY,GAAG;EAGpE,IAAI,mBACH,MAAM,IAAI,MAAM,uCAAuC,YAAY,GAAG;EAGvE,QAAQ,UAAU,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK;CAC3C;AACD"}
package/dist/index.d.mts CHANGED
@@ -1,9 +1,6 @@
1
1
  import { a as DisposeHook, c as OptionalDependency, d as Token, f as Scope, i as DepsMap, l as Provider, n as RegisterOptions, o as Factory, p as Scopes, r as Dependency, s as FactoryProvider, t as Container, u as ValueProvider } from "./types-BzLfq9VA.mjs";
2
2
 
3
3
  //#region src/core/error/error.d.ts
4
- /**
5
- * Base class for all di-craft runtime errors.
6
- */
7
4
  declare class DiError extends Error {
8
5
  constructor(message: string);
9
6
  }
@@ -178,4 +175,5 @@ declare class CircularDependencyError extends DiError {
178
175
  constructor(tokenNames: string[]);
179
176
  }
180
177
  //#endregion
181
- export { CircularDependencyError, type Container, type Dependency, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, Injectable, InvalidDependencyError, InvalidProviderError, MissingProviderError, type OptionalDependency, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, optional, provideFactory, provideInjectable, provideValue };
178
+ export { CircularDependencyError, type Container, type Dependency, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, Injectable, InvalidDependencyError, InvalidProviderError, MissingProviderError, type OptionalDependency, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, optional, provideFactory, provideInjectable, provideValue };
179
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1 +1,103 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,r as c,s as l,t as u,u as d}from"./container-C6GcrdNn.mjs";const f=e=>{if(e.length!==0)return Object.fromEntries(e.entries())},p=new WeakMap,m=e=>p.get(e),h=e=>{p.set(e.target,e.metadata)},g=e=>{let{context:t,target:r}=e;if(!(t.kind===`class`&&typeof r==`function`))throw new n(`@Injectable can only decorate classes`)};function _(e){return(t,n)=>{g({target:t,context:n}),h({target:t,metadata:e})}}function v(e){let r=m(e);if(!r)throw new n(`Class "${e.name||`<anonymous>`}" is not marked as injectable. Add @Injectable({ token: TOKEN }) before calling provideInjectable().`);let{deps:i=[],onDispose:a,scope:o,token:s}=r,c=f(i),l=e;return t(s,{useFactory:e=>new l(...i.map((t,n)=>e[String(n)])),...c?{deps:c}:{},...o?{scope:o}:{},...a?{onDispose:a}:{}})}var y=class{name;id;constructor(e){this.name=e,this.id=Symbol(e)}};const b=e=>new y(e);export{c as CircularDependencyError,r as DiError,s as DuplicateProviderError,_ as Injectable,i as InvalidDependencyError,n as InvalidProviderError,e as MissingProviderError,d as Scopes,u as createChildContainer,o as createContainer,b as createToken,l as optional,t as provideFactory,v as provideInjectable,a as provideValue};
1
+ import { a as MissingProviderError, c as provideFactory, d as InvalidProviderError, f as DiError, i as InvalidDependencyError, l as provideValue, n as createContainer, o as DuplicateProviderError, r as CircularDependencyError, s as optional, t as createChildContainer, u as Scopes } from "./container-l5DkUBUj.mjs";
2
+
3
+ //#region src/core/annotation/deps.ts
4
+ const tupleDepsToMap = (deps) => {
5
+ if (deps.length === 0) return;
6
+ return Object.fromEntries(deps.entries());
7
+ };
8
+
9
+ //#endregion
10
+ //#region src/core/annotation/metadata.ts
11
+ const injectableMetadata = /* @__PURE__ */ new WeakMap();
12
+ const getInjectableMetadata = (target) => injectableMetadata.get(target);
13
+ const setInjectableMetadata = (options) => {
14
+ injectableMetadata.set(options.target, options.metadata);
15
+ };
16
+
17
+ //#endregion
18
+ //#region src/core/annotation/annotation.ts
19
+ const ANONYMOUS_CLASS_NAME = "<anonymous>";
20
+ const assertClassDecorator = (options) => {
21
+ const { context, target } = options;
22
+ if (!(context.kind === "class" && typeof target === "function")) throw new InvalidProviderError("@Injectable can only decorate classes");
23
+ };
24
+ /**
25
+ * Marks a class as an injectable provider for a token.
26
+ *
27
+ * Dependencies are explicit and ordered by constructor parameter position. No
28
+ * `reflect-metadata`, parameter decorators, or global container are used.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * @Injectable({ token: USER_SERVICE, deps: [LOGGER] })
33
+ * class UserService {
34
+ * private readonly logger: Logger;
35
+ *
36
+ * constructor(logger: Logger) {
37
+ * this.logger = logger;
38
+ * }
39
+ * }
40
+ * ```
41
+ */
42
+ function Injectable(options) {
43
+ return (target, context) => {
44
+ assertClassDecorator({
45
+ target,
46
+ context
47
+ });
48
+ setInjectableMetadata({
49
+ target,
50
+ metadata: options
51
+ });
52
+ };
53
+ }
54
+ /**
55
+ * Creates a factory provider from a class marked with `@Injectable`.
56
+ *
57
+ * The returned provider is a normal di-craft factory provider, so scopes,
58
+ * optional dependencies, disposal hooks, overrides, and cycle detection behave
59
+ * the same as with `provideFactory`.
60
+ */
61
+ function provideInjectable(target) {
62
+ const metadata = getInjectableMetadata(target);
63
+ if (!metadata) throw new InvalidProviderError(`Class "${target.name || ANONYMOUS_CLASS_NAME}" is not marked as injectable. Add @Injectable({ token: TOKEN }) before calling provideInjectable().`);
64
+ const { deps = [], onDispose, scope, token } = metadata;
65
+ const depsMap = tupleDepsToMap(deps);
66
+ const Constructor = target;
67
+ const useFactory = (resolvedDeps) => {
68
+ return new Constructor(...deps.map((_, index) => resolvedDeps[String(index)]));
69
+ };
70
+ return provideFactory(token, {
71
+ useFactory,
72
+ ...depsMap ? { deps: depsMap } : {},
73
+ ...scope ? { scope } : {},
74
+ ...onDispose ? { onDispose } : {}
75
+ });
76
+ }
77
+
78
+ //#endregion
79
+ //#region src/core/token/token.ts
80
+ var TokenClass = class {
81
+ name;
82
+ id;
83
+ constructor(name) {
84
+ this.name = name;
85
+ this.id = Symbol(name);
86
+ }
87
+ };
88
+ /**
89
+ * Creates a unique typed token.
90
+ *
91
+ * The `name` is used only for diagnostics. Token identity is unique per call,
92
+ * so two tokens with the same name do not collide.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const CONFIG = createToken<Config>("config");
97
+ * ```
98
+ */
99
+ const createToken = (name) => new TokenClass(name);
100
+
101
+ //#endregion
102
+ export { CircularDependencyError, DiError, DuplicateProviderError, Injectable, InvalidDependencyError, InvalidProviderError, MissingProviderError, Scopes, createChildContainer, createContainer, createToken, optional, provideFactory, provideInjectable, provideValue };
103
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/core/annotation/deps.ts","../src/core/annotation/metadata.ts","../src/core/annotation/annotation.ts","../src/core/token/token.ts"],"sourcesContent":["import type { Dependency, DepsMap } from \"../provider\";\n\nexport const tupleDepsToMap = (\n\tdeps: readonly Dependency<unknown>[],\n): DepsMap | undefined => {\n\tif (deps.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(deps.entries());\n};\n","import type { InjectableClass, InjectableOptions } from \"./types\";\n\ntype InjectableMetadata = InjectableOptions<unknown>;\n\nconst injectableMetadata = new WeakMap<InjectableClass, InjectableMetadata>();\n\nexport const getInjectableMetadata = (\n\ttarget: InjectableClass,\n): InjectableMetadata | undefined => injectableMetadata.get(target);\n\nexport const setInjectableMetadata = <T>(options: {\n\treadonly target: InjectableClass<T>;\n\treadonly metadata: InjectableOptions<T>;\n}): void => {\n\tinjectableMetadata.set(\n\t\toptions.target,\n\t\toptions.metadata as InjectableOptions<unknown>,\n\t);\n};\n","import {\n\ttype Dependency,\n\ttype DepsMap,\n\ttype DisposeHook,\n\ttype FactoryProvider,\n\tInvalidProviderError,\n\tprovideFactory,\n\ttype ResolveDeps,\n} from \"../provider\";\nimport type { Token } from \"../token\";\nimport { tupleDepsToMap } from \"./deps\";\nimport { getInjectableMetadata, setInjectableMetadata } from \"./metadata\";\nimport type {\n\tInjectableClass,\n\tInjectableConstructor,\n\tInjectableOptions,\n} from \"./types\";\n\ntype Constructable<T> = new (...args: unknown[]) => T;\ntype InjectableDecorator<TTarget> = (\n\ttarget: TTarget,\n\tcontext: ClassDecoratorContext,\n) => void;\n\ntype InjectableOptionsWithoutDeps<T> = Omit<\n\tInjectableOptions<T, readonly []>,\n\t\"deps\"\n>;\n\nconst ANONYMOUS_CLASS_NAME = \"<anonymous>\";\n\nconst assertClassDecorator = (options: {\n\treadonly target: InjectableClass;\n\treadonly context: ClassDecoratorContext;\n}): void => {\n\tconst { context, target } = options;\n\tconst isClassDecorator =\n\t\tcontext.kind === \"class\" && typeof target === \"function\";\n\n\tif (!isClassDecorator) {\n\t\tthrow new InvalidProviderError(\"@Injectable can only decorate classes\");\n\t}\n};\n\n// Overloads keep constructor parameters aligned with explicit deps. The single\n// runtime implementation is below.\nexport function Injectable<\n\tT,\n\tconst TDeps extends readonly Dependency<unknown>[],\n>(\n\toptions: InjectableOptions<T, TDeps> & { readonly deps: TDeps },\n): InjectableDecorator<InjectableConstructor<T, TDeps>>;\n\nexport function Injectable<T>(\n\toptions: InjectableOptionsWithoutDeps<T>,\n): InjectableDecorator<InjectableConstructor<T, readonly []>>;\n/**\n * Marks a class as an injectable provider for a token.\n *\n * Dependencies are explicit and ordered by constructor parameter position. No\n * `reflect-metadata`, parameter decorators, or global container are used.\n *\n * @example\n * ```ts\n * @Injectable({ token: USER_SERVICE, deps: [LOGGER] })\n * class UserService {\n * private readonly logger: Logger;\n *\n * constructor(logger: Logger) {\n * this.logger = logger;\n * }\n * }\n * ```\n */\nexport function Injectable<T>(\n\toptions: InjectableOptions<T>,\n): InjectableDecorator<InjectableClass<T>> {\n\treturn (target: InjectableClass<T>, context: ClassDecoratorContext): void => {\n\t\tassertClassDecorator({ target, context });\n\t\tsetInjectableMetadata({ target, metadata: options });\n\t};\n}\n\n/**\n * Creates a factory provider from a class marked with `@Injectable`.\n *\n * The returned provider is a normal di-craft factory provider, so scopes,\n * optional dependencies, disposal hooks, overrides, and cycle detection behave\n * the same as with `provideFactory`.\n */\nexport function provideInjectable<T>(\n\ttarget: InjectableClass<T>,\n): FactoryProvider<T, DepsMap> {\n\tconst metadata = getInjectableMetadata(target);\n\n\tif (!metadata) {\n\t\tconst className = target.name || ANONYMOUS_CLASS_NAME;\n\n\t\tthrow new InvalidProviderError(\n\t\t\t`Class \"${className}\" is not marked as injectable. Add @Injectable({ token: TOKEN }) before calling provideInjectable().`,\n\t\t);\n\t}\n\n\tconst { deps = [], onDispose, scope, token } = metadata;\n\tconst depsMap = tupleDepsToMap(deps);\n\tconst Constructor = target as unknown as Constructable<T>;\n\n\tconst useFactory = (resolvedDeps: ResolveDeps<DepsMap>): T => {\n\t\tconst args = deps.map((_, index): unknown => resolvedDeps[String(index)]);\n\n\t\treturn new Constructor(...args);\n\t};\n\n\treturn provideFactory(token as Token<T>, {\n\t\tuseFactory,\n\t\t...(depsMap ? { deps: depsMap } : {}),\n\t\t...(scope ? { scope } : {}),\n\t\t...(onDispose ? { onDispose: onDispose as DisposeHook<T> } : {}),\n\t});\n}\n","import type { Token } from \"./types\";\n\nclass TokenClass<T> implements Token<T> {\n\treadonly name: string;\n\treadonly id: symbol;\n\n\tdeclare readonly __type?: T;\n\n\tconstructor(name: string) {\n\t\tthis.name = name;\n\t\tthis.id = Symbol(name);\n\t}\n}\n\n/**\n * Creates a unique typed token.\n *\n * The `name` is used only for diagnostics. Token identity is unique per call,\n * so two tokens with the same name do not collide.\n *\n * @example\n * ```ts\n * const CONFIG = createToken<Config>(\"config\");\n * ```\n */\nexport const createToken = <T>(name: string): Token<T> =>\n\tnew TokenClass<T>(name);\n"],"mappings":";;;AAEA,MAAa,kBACZ,SACyB;CACzB,IAAI,KAAK,WAAW,GACnB;CAGD,OAAO,OAAO,YAAY,KAAK,QAAQ,CAAC;AACzC;;;;ACNA,MAAM,qCAAqB,IAAI,QAA6C;AAE5E,MAAa,yBACZ,WACoC,mBAAmB,IAAI,MAAM;AAElE,MAAa,yBAA4B,YAG7B;CACX,mBAAmB,IAClB,QAAQ,QACR,QAAQ,QACT;AACD;;;;ACWA,MAAM,uBAAuB;AAE7B,MAAM,wBAAwB,YAGlB;CACX,MAAM,EAAE,SAAS,WAAW;CAI5B,IAAI,EAFH,QAAQ,SAAS,WAAW,OAAO,WAAW,aAG9C,MAAM,IAAI,qBAAqB,uCAAuC;AAExE;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,WACf,SAC0C;CAC1C,QAAQ,QAA4B,YAAyC;EAC5E,qBAAqB;GAAE;GAAQ;EAAQ,CAAC;EACxC,sBAAsB;GAAE;GAAQ,UAAU;EAAQ,CAAC;CACpD;AACD;;;;;;;;AASA,SAAgB,kBACf,QAC8B;CAC9B,MAAM,WAAW,sBAAsB,MAAM;CAE7C,IAAI,CAAC,UAGJ,MAAM,IAAI,qBACT,UAHiB,OAAO,QAAQ,qBAGZ,qGACrB;CAGD,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,OAAO,UAAU;CAC/C,MAAM,UAAU,eAAe,IAAI;CACnC,MAAM,cAAc;CAEpB,MAAM,cAAc,iBAA0C;EAG7D,OAAO,IAAI,YAAY,GAFV,KAAK,KAAK,GAAG,UAAmB,aAAa,OAAO,KAAK,EAEzC,CAAC;CAC/B;CAEA,OAAO,eAAe,OAAmB;EACxC;EACA,GAAI,UAAU,EAAE,MAAM,QAAQ,IAAI,CAAC;EACnC,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,YAAY,EAAa,UAA4B,IAAI,CAAC;CAC/D,CAAC;AACF;;;;ACrHA,IAAM,aAAN,MAAwC;CACvC,AAAS;CACT,AAAS;CAIT,YAAY,MAAc;EACzB,KAAK,OAAO;EACZ,KAAK,KAAK,OAAO,IAAI;CACtB;AACD;;;;;;;;;;;;AAaA,MAAa,eAAkB,SAC9B,IAAI,WAAc,IAAI"}
@@ -1 +1,3 @@
1
- import{n as e}from"../hydration-DsCFyhuJ.mjs";export{e as hydrate};
1
+ import { n as hydrate } from "../hydration-CLZgCNpJ.mjs";
2
+
3
+ export { hydrate };
@@ -30,4 +30,5 @@ declare const createNextDi: ({
30
30
  requestProviders
31
31
  }: CreateNextDiOptions) => NextDiAdapter;
32
32
  //#endregion
33
- export { type CreateNextDiOptions, type DehydrateOptions, type Hydratable, type HydrationSchema, type HydrationSnapshot, type NextDiAdapter, type RequestCache, type RunWithRequestContainerOptions, type Serializable, type SerializablePrimitive, createNextDi, dehydrate };
33
+ export { type CreateNextDiOptions, type DehydrateOptions, type Hydratable, type HydrationSchema, type HydrationSnapshot, type NextDiAdapter, type RequestCache, type RunWithRequestContainerOptions, type Serializable, type SerializablePrimitive, createNextDi, dehydrate };
34
+ //# sourceMappingURL=server.d.mts.map
@@ -1 +1,53 @@
1
- import{n as e,t}from"../container-C6GcrdNn.mjs";import{t as n}from"../hydration-DsCFyhuJ.mjs";const r=()=>{if(`window`in globalThis)throw Error(`di-craft/next/server can only be used in a server runtime.`)},i=({cache:n,providers:i=[],requestProviders:a})=>{r();let o=e(i),s=(e=[])=>t(o,[...a?.()??[],...e]);return{getRootContainer:()=>o,getRequestContainer:n(()=>s()),createRequestContainer:s,runWithRequestContainer:async({providers:e=[],run:t})=>{let n=s(e);try{return await t(n)}finally{await n.dispose()}},disposeRootContainer:()=>o.dispose()}};export{i as createNextDi,n as dehydrate};
1
+ import { n as createContainer, t as createChildContainer } from "../container-l5DkUBUj.mjs";
2
+ import { t as dehydrate } from "../hydration-CLZgCNpJ.mjs";
3
+
4
+ //#region src/adapters/next/server.ts
5
+ const assertServerRuntime = () => {
6
+ if ("window" in globalThis) throw new Error("di-craft/next/server can only be used in a server runtime.");
7
+ };
8
+ /**
9
+ * Creates a server adapter for Next.js App Router and React Server Components.
10
+ *
11
+ * The root container is created once. `getRequestContainer` creates a child
12
+ * container through the provided React request cache, so repeated calls inside
13
+ * one RSC request resolve through the same scoped dependency graph.
14
+ *
15
+ * Import this helper from a server-only composition file. The adapter does not
16
+ * import React itself; pass `cache` from `react` so React/Next owns request
17
+ * memoization.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import "server-only";
22
+ * import { cache } from "react";
23
+ * import { createNextDi } from "di-craft/next/server";
24
+ *
25
+ * export const { getRequestContainer } = createNextDi({
26
+ * cache,
27
+ * providers,
28
+ * });
29
+ * ```
30
+ */
31
+ const createNextDi = ({ cache, providers = [], requestProviders }) => {
32
+ assertServerRuntime();
33
+ const rootContainer = createContainer(providers);
34
+ const createRequestContainer = (providers = []) => createChildContainer(rootContainer, [...requestProviders?.() ?? [], ...providers]);
35
+ return {
36
+ getRootContainer: () => rootContainer,
37
+ getRequestContainer: cache(() => createRequestContainer()),
38
+ createRequestContainer,
39
+ runWithRequestContainer: async ({ providers = [], run }) => {
40
+ const container = createRequestContainer(providers);
41
+ try {
42
+ return await run(container);
43
+ } finally {
44
+ await container.dispose();
45
+ }
46
+ },
47
+ disposeRootContainer: () => rootContainer.dispose()
48
+ };
49
+ };
50
+
51
+ //#endregion
52
+ export { createNextDi, dehydrate };
53
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../src/adapters/next/server.ts"],"sourcesContent":["/**\n * Server-side Next.js App Router adapter for request-scoped containers.\n *\n * @module\n */\n\nimport type { Container } from \"../../core/container\";\nimport { createChildContainer, createContainer } from \"../../core/container\";\nimport type { Provider } from \"../../core/provider\";\nimport type {\n\tCreateNextDiOptions,\n\tNextDiAdapter,\n\tRunWithRequestContainerOptions,\n} from \"./types\";\n\nexport { dehydrate } from \"./hydration\";\nexport type {\n\tCreateNextDiOptions,\n\tDehydrateOptions,\n\tHydratable,\n\tHydrationSchema,\n\tHydrationSnapshot,\n\tNextDiAdapter,\n\tRequestCache,\n\tRunWithRequestContainerOptions,\n\tSerializable,\n\tSerializablePrimitive,\n} from \"./types\";\n\nconst assertServerRuntime = (): void => {\n\tif (\"window\" in globalThis) {\n\t\tthrow new Error(\n\t\t\t\"di-craft/next/server can only be used in a server runtime.\",\n\t\t);\n\t}\n};\n\n/**\n * Creates a server adapter for Next.js App Router and React Server Components.\n *\n * The root container is created once. `getRequestContainer` creates a child\n * container through the provided React request cache, so repeated calls inside\n * one RSC request resolve through the same scoped dependency graph.\n *\n * Import this helper from a server-only composition file. The adapter does not\n * import React itself; pass `cache` from `react` so React/Next owns request\n * memoization.\n *\n * @example\n * ```ts\n * import \"server-only\";\n * import { cache } from \"react\";\n * import { createNextDi } from \"di-craft/next/server\";\n *\n * export const { getRequestContainer } = createNextDi({\n * cache,\n * providers,\n * });\n * ```\n */\nexport const createNextDi = ({\n\tcache,\n\tproviders = [],\n\trequestProviders,\n}: CreateNextDiOptions): NextDiAdapter => {\n\tassertServerRuntime();\n\n\tconst rootContainer = createContainer(providers);\n\tconst createRequestContainer = (\n\t\tproviders: readonly Provider[] = [],\n\t): Container =>\n\t\tcreateChildContainer(rootContainer, [\n\t\t\t...(requestProviders?.() ?? []),\n\t\t\t...providers,\n\t\t]);\n\tconst getRequestContainer = cache(() => createRequestContainer());\n\n\treturn {\n\t\tgetRootContainer: () => rootContainer,\n\t\tgetRequestContainer,\n\t\tcreateRequestContainer,\n\t\trunWithRequestContainer: async <TResult>({\n\t\t\tproviders = [],\n\t\t\trun,\n\t\t}: RunWithRequestContainerOptions<TResult>): Promise<Awaited<TResult>> => {\n\t\t\tconst container = createRequestContainer(providers);\n\n\t\t\ttry {\n\t\t\t\treturn await run(container);\n\t\t\t} finally {\n\t\t\t\tawait container.dispose();\n\t\t\t}\n\t\t},\n\t\tdisposeRootContainer: () => rootContainer.dispose(),\n\t};\n};\n"],"mappings":";;;;AA6BA,MAAM,4BAAkC;CACvC,IAAI,YAAY,YACf,MAAM,IAAI,MACT,4DACD;AAEF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,gBAAgB,EAC5B,OACA,YAAY,CAAC,GACb,uBACyC;CACzC,oBAAoB;CAEpB,MAAM,gBAAgB,gBAAgB,SAAS;CAC/C,MAAM,0BACL,YAAiC,CAAC,MAElC,qBAAqB,eAAe,CACnC,GAAI,mBAAmB,KAAK,CAAC,GAC7B,GAAG,SACJ,CAAC;CAGF,OAAO;EACN,wBAAwB;EACxB,qBAJ2B,YAAY,uBAAuB,CAI5C;EAClB;EACA,yBAAyB,OAAgB,EACxC,YAAY,CAAC,GACb,UACyE;GACzE,MAAM,YAAY,uBAAuB,SAAS;GAElD,IAAI;IACH,OAAO,MAAM,IAAI,SAAS;GAC3B,UAAU;IACT,MAAM,UAAU,QAAQ;GACzB;EACD;EACA,4BAA4B,cAAc,QAAQ;CACnD;AACD"}
@@ -137,4 +137,5 @@ type Container = {
137
137
  dispose(): Promise<void>;
138
138
  };
139
139
  //#endregion
140
- export { DisposeHook as a, OptionalDependency as c, Token as d, Scope as f, DepsMap as i, Provider as l, RegisterOptions as n, Factory as o, Scopes as p, Dependency as r, FactoryProvider as s, Container as t, ValueProvider as u };
140
+ export { DisposeHook as a, OptionalDependency as c, Token as d, Scope as f, DepsMap as i, Provider as l, RegisterOptions as n, Factory as o, Scopes as p, Dependency as r, FactoryProvider as s, Container as t, ValueProvider as u };
141
+ //# sourceMappingURL=types-BzLfq9VA.d.mts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "di-craft",
3
- "version": "0.0.19",
4
- "description": "A tiny, type-safe dependency injection container for TypeScript",
3
+ "version": "0.0.21",
4
+ "description": "A tiny, type-safe dependency injection container for TypeScript with an optional Next.js App Router adapter",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Egor Bezmen",
@@ -38,7 +38,7 @@
38
38
  "scripts": {
39
39
  "build": "tsdown",
40
40
  "test": "bun test",
41
- "typecheck": "tsc --noEmit",
41
+ "typecheck": "tsc --noEmit -p tsconfig.src.json && tsc --noEmit -p tsconfig.test.json",
42
42
  "lint": "biome check .",
43
43
  "format": "biome check --write .",
44
44
  "publint": "publint",
@@ -61,7 +61,17 @@
61
61
  "zero dependencies",
62
62
  "lightweight",
63
63
  "tiny",
64
- "tree-shakable"
64
+ "tree-shakable",
65
+ "nextjs",
66
+ "next.js",
67
+ "app router",
68
+ "react server components",
69
+ "rsc",
70
+ "server components",
71
+ "request scope",
72
+ "ssr",
73
+ "state hydration",
74
+ "next adapter"
65
75
  ],
66
76
  "homepage": "https://github.com/bezmen-e/di-craft",
67
77
  "repository": {
@@ -72,11 +82,11 @@
72
82
  "url": "https://github.com/bezmen-e/di-craft/issues"
73
83
  },
74
84
  "devDependencies": {
75
- "@biomejs/biome": "2.4.16",
85
+ "@biomejs/biome": "2.5.1",
76
86
  "@types/bun": "1.3.14",
77
87
  "publint": "0.3.21",
78
88
  "simple-git-hooks": "2.13.1",
79
- "tsdown": "0.22.0",
89
+ "tsdown": "0.22.3",
80
90
  "typescript": "6.0.3"
81
91
  }
82
92
  }
@@ -1 +0,0 @@
1
- var e=class extends Error{constructor(e){super(e),this.name=`DiError`,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},t=class extends e{constructor(e){super(e),this.name=`InvalidProviderError`}};const n={Singleton:`singleton`,Transient:`transient`,Scoped:`scoped`},r=(e,t)=>({provide:e,useValue:t}),i=(e,r)=>{if(r.scope===n.Transient&&r.onDispose)throw new t(`onDispose is not supported for transient providers (token "${e.name}"): transient instances are not tracked, so the hook would never run.`);return{provide:e,useFactory:r.useFactory,scope:r.scope??n.Singleton,...r.deps?{deps:r.deps}:{},...r.onDispose?{onDispose:r.onDispose}:{}}},a=e=>({token:e,optional:!0}),o=e=>e.optional===!0,s=e=>`useValue`in e;var c=class extends e{constructor(e){super(`Provider for token "${e}" is already registered`),this.name=`DuplicateProviderError`}},l=class{providers=new Map;register(e,t){let n=this.providers.has(e.provide.id);if(n&&!t?.allowOverride)throw new c(e.provide.name);return this.providers.set(e.provide.id,e),n}get(e){return this.providers.get(e.id)}has(e){return this.providers.has(e.id)}};const u=()=>new l;var d=class extends e{constructor(e){super(`Provider for token "${e}" is not registered`),this.name=`MissingProviderError`}},f=class extends e{constructor(e){super(`Invalid dependency "${e}"`),this.name=`InvalidDependencyError`}},p=class extends e{constructor(e){super(`Circular dependency detected: ${e.join(` -> `)}`),this.name=`CircularDependencyError`}},m=class{instances=new Map;get(e){return this.instances.get(e.id)}set(e,t){this.instances.set(e.id,t)}delete(e){this.instances.delete(e.id)}async dispose(){let e=[...this.instances.values()].reverse();this.instances.clear();for(let t of e)t.onDispose&&await t.onDispose(t.value)}};const h=()=>new m;var g=class{resolving=new Set;path=[];enter(e){if(this.resolving.has(e.id)){let t=this.path.findIndex(t=>t.id===e.id);throw new p([...this.path.slice(t),e].map(e=>e.name))}this.resolving.add(e.id),this.path.push(e)}exit(e){this.path.pop(),this.resolving.delete(e.id)}};const _=e=>e===n.Scoped?1:e===n.Transient?2:0;var v=class{registry;store=h();parent;constructor(e,t){this.registry=e,this.parent=t}resolve(e){return this.resolveToken(e,void 0)}resolveOptional(e){return this.lookupOptional(e,void 0)}invalidate(e){this.store.delete(e)}hasDisposableInstance(e){return this.store.get(e)?.onDispose!==void 0}dispose(){return this.store.dispose()}resolveToken(e,r,i,a){let o=this,c;for(;o&&(c=o.registry.get(e),!c);)o=o.parent;if(!c||!o)throw new d(e.name);if(s(c))return c.useValue;if(i!==void 0&&_(c.scope)>_(i))throw new t(`"${a}" (${i}) cannot depend on "${e.name}" (${c.scope??n.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`);let l=this.selectHost(c.scope,o);if(l){let t=l.store.get(e);if(t)return t.value}let u=r??new g;u.enter(e);try{let t=(l??this).resolveDeps(c.deps,u,c.scope,e.name),n=c.useFactory(t);return l&&l.store.set(e,{value:n,...c.onDispose?{onDispose:c.onDispose}:{}}),n}finally{u.exit(e)}}selectHost(e,t){if(e!==n.Transient)return e===n.Scoped?this:t}resolveDeps(e,t,n,r){if(!e)return{};let i={};for(let a of Object.keys(e)){let s=e[a];if(s===void 0)throw new f(String(a));i[a]=o(s)?this.lookupOptional(s.token,t,n,r):this.resolveToken(s,t,n,r)}return i}lookupOptional(e,t,n,r){let i=this;for(;i;){if(i.registry.has(e))return this.resolveToken(e,t,n,r);i=i.parent}}};const y=(e,t)=>new v(e,t);var b=class{registry;resolver;parent;constructor(e=[],t){this.parent=t,this.registry=u();for(let t of e)this.registry.register(t);this.resolver=y(this.registry,t?.resolver)}register(e,n){if(n?.allowOverride&&this.resolver.hasDisposableInstance(e.provide))throw new t(`Cannot override token "${e.provide.name}": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`);this.registry.register(e,n)&&this.resolver.invalidate(e.provide)}get(e){return o(e)?this.resolver.resolveOptional(e.token):this.resolver.resolve(e)}has(e){return this.registry.has(e)||(this.parent?.has(e)??!1)}dispose(){return this.resolver.dispose()}};const x=(e=[])=>new b(e),S=(e,t=[])=>new b(t,e);export{d as a,i as c,t as d,e as f,f as i,r as l,x as n,c as o,p as r,a as s,S as t,n as u};
@@ -1 +0,0 @@
1
- const e=e=>{let t={};for(let n of Object.keys(e.schema)){let r=e.schema[n],i=r===void 0,a=String(n);if(i)throw Error(`Missing hydration token for key "${a}".`);t[a]=e.container.get(r).dehydrate()}return t},t=e=>{let t=e.snapshot;for(let n of Object.keys(e.schema)){let r=e.schema[n],i=String(n),a=t[i],o=r===void 0,s=a===void 0;if(o)throw Error(`Missing hydration token for key "${i}".`);if(s)throw Error(`Missing hydration snapshot for key "${i}".`);e.container.get(r).hydrate(a)}};export{t as n,e as t};