@fnioc/di 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,26 +1,54 @@
1
1
  // ../core/src/store.ts
2
- var hole = null;
3
2
  var GLOBAL_KEY = Symbol.for("fnioc:deps");
4
3
  var globals = globalThis;
5
4
  var store = globals[GLOBAL_KEY] ??= new Map;
5
+
6
6
  // ../core/src/defineDeps.ts
7
7
  function isFactoryRef(slot) {
8
- return typeof slot === "object" && slot !== null && typeof slot.factory === "string";
8
+ return typeof slot === "object" && slot !== null && typeof slot.type === "string";
9
9
  }
10
10
  function isScopeRef(slot) {
11
11
  return typeof slot === "object" && slot !== null && slot.scope === true;
12
12
  }
13
+ function isUnionSlot(slot) {
14
+ return typeof slot === "object" && slot !== null && Array.isArray(slot.union);
15
+ }
13
16
  function slotsEqual(a, b) {
14
17
  const aIsRef = isFactoryRef(a);
15
18
  const bIsRef = isFactoryRef(b);
16
19
  if (aIsRef || bIsRef) {
17
- return aIsRef && bIsRef && a.factory === b.factory;
20
+ if (!aIsRef || !bIsRef)
21
+ return false;
22
+ if (a.type !== b.type)
23
+ return false;
24
+ const aParams = a.params ?? [];
25
+ const bParams = b.params ?? [];
26
+ if (aParams.length !== bParams.length)
27
+ return false;
28
+ for (let i = 0;i < aParams.length; i++) {
29
+ if (aParams[i] !== bParams[i])
30
+ return false;
31
+ }
32
+ return true;
18
33
  }
19
34
  const aIsScope = isScopeRef(a);
20
35
  const bIsScope = isScopeRef(b);
21
36
  if (aIsScope || bIsScope) {
22
37
  return aIsScope && bIsScope;
23
38
  }
39
+ const aIsUnion = isUnionSlot(a);
40
+ const bIsUnion = isUnionSlot(b);
41
+ if (aIsUnion || bIsUnion) {
42
+ if (!aIsUnion || !bIsUnion)
43
+ return false;
44
+ if (a.union.length !== b.union.length)
45
+ return false;
46
+ for (let i = 0;i < a.union.length; i++) {
47
+ if (!slotsEqual(a.union[i], b.union[i]))
48
+ return false;
49
+ }
50
+ return true;
51
+ }
24
52
  return a === b;
25
53
  }
26
54
  function signaturesEqual(a, b) {
@@ -65,6 +93,12 @@ function forCtor(ctor) {
65
93
  };
66
94
  return builder;
67
95
  }
96
+
97
+ // ../core/src/index.ts
98
+ function union(...slots) {
99
+ return { union: slots };
100
+ }
101
+
68
102
  // src/errors.ts
69
103
  class DiError extends Error {
70
104
  constructor(message) {
@@ -134,6 +168,15 @@ class FactoryTargetError extends DiError {
134
168
  }
135
169
  }
136
170
 
171
+ class NoSatisfiableUnionError extends DiError {
172
+ members;
173
+ constructor(members) {
174
+ const memberList = members.map((m) => typeof m === "string" ? `"${m}"` : JSON.stringify(m)).join(", ");
175
+ super(`No satisfiable union member found. Tried: [${memberList}]. ` + `Register at least one of the union members before resolving.`);
176
+ this.members = members;
177
+ }
178
+ }
179
+
137
180
  class AsyncDisposalRequiredError extends DiError {
138
181
  constructor() {
139
182
  super(`Cannot dispose synchronously: this scope owns a Promise-valued ` + `instance (an async useFactory result). Awaiting it is required ` + `before disposal — call disposeAsync() instead of dispose().`);
@@ -150,108 +193,72 @@ function isAsyncDisposable(value) {
150
193
  function isThenable(value) {
151
194
  return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
152
195
  }
153
- function isFactoryRef2(slot) {
154
- return slot !== null && typeof slot === "object" && typeof slot.factory === "string";
155
- }
156
- function isScopeRef2(slot) {
157
- return slot !== null && typeof slot === "object" && slot.scope === true;
158
- }
196
+ var isFactoryRef2 = isFactoryRef;
197
+ var isScopeRef2 = isScopeRef;
198
+ var isUnion = isUnionSlot;
159
199
 
160
200
  class Scope {
161
201
  name;
162
202
  parent;
163
- baseRegistrations;
164
- localRegistrations = new Map;
165
- instances = new Map;
166
- ownedOrder = [];
167
- disposed = false;
168
- constructor(name, parent, baseRegistrations) {
203
+ cache = new Map;
204
+ owned = [];
205
+ constructor(name, parent) {
169
206
  this.name = name;
170
207
  this.parent = parent;
171
- this.baseRegistrations = baseRegistrations;
172
208
  }
173
- static appendTo(map, token, registration) {
174
- const existing = map.get(token);
175
- if (existing === undefined) {
176
- map.set(token, [registration]);
177
- } else {
178
- existing.push(registration);
209
+ }
210
+
211
+ class ServiceProvider {
212
+ registrations;
213
+ disposed = false;
214
+ frame;
215
+ constructor(registrations, frame) {
216
+ this.registrations = registrations;
217
+ this.frame = frame;
218
+ }
219
+ get name() {
220
+ if (this.frame === undefined) {
221
+ throw new TypeError("This ServiceProvider has no scope frame (unscoped root).");
179
222
  }
223
+ return this.frame.name;
180
224
  }
181
- createScope(childName) {
182
- return new Scope(childName, this, this.baseRegistrations);
183
- }
184
- appendScopedLocal(token, base) {
185
- Scope.appendTo(this.localRegistrations, token, base);
186
- const map = this.localRegistrations;
187
- return {
188
- as(scope) {
189
- if (scope === undefined)
190
- return;
191
- Scope.appendTo(map, token, { ...base, scope });
192
- }
193
- };
194
- }
195
- add(token, ctor) {
196
- return this.appendScopedLocal(token, {
197
- kind: "class",
198
- ctor,
199
- scope: undefined
200
- });
201
- }
202
- addFactory(token, factory) {
203
- return this.appendScopedLocal(token, {
204
- kind: "factory",
205
- factory,
206
- scope: undefined
207
- });
208
- }
209
- addValue(token, value) {
210
- Scope.appendTo(this.localRegistrations, token, {
211
- kind: "value",
212
- useValue: value
213
- });
214
- return this;
225
+ createScope(...args) {
226
+ const name = args[0] ?? "scoped";
227
+ const childFrame = new Scope(name, this.frame);
228
+ return new ServiceProvider(this.registrations, childFrame);
215
229
  }
216
230
  resolve(token) {
217
231
  if (token === undefined) {
218
232
  throw new TypeError("resolve<T>() requires the @fnioc/transformer plugin (no token at " + "runtime). Without it, resolve with an explicit token: " + 'resolve<T>("my:token").');
219
233
  }
220
- return this.resolveWith(token, []);
234
+ return this.resolveWith(token, this.frame, []);
221
235
  }
222
- resolveFactory(token) {
223
- return this.makeFactory({ factory: token });
236
+ resolveFactory(type, params) {
237
+ return this.makeFactory({ type, params }, this.frame);
224
238
  }
225
239
  lookup(token) {
226
- let node = this;
227
- while (node !== undefined) {
228
- const local = node.localRegistrations.get(token);
229
- if (local !== undefined && local.length > 0)
230
- return local[local.length - 1];
231
- node = node.parent;
232
- }
233
- const base = this.baseRegistrations.get(token);
234
- return base !== undefined && base.length > 0 ? base[base.length - 1] : undefined;
240
+ const list = this.registrations.get(token);
241
+ return list !== undefined && list.length > 0 ? list[list.length - 1] : undefined;
235
242
  }
236
- findOwner(scope) {
237
- let node = this;
243
+ static findOwner(vantage, scopeName) {
244
+ let node = vantage;
238
245
  while (node !== undefined) {
239
- if (node.name === scope)
246
+ if (node.name === scopeName)
240
247
  return node;
241
248
  node = node.parent;
242
249
  }
243
250
  return;
244
251
  }
245
- chainNames() {
252
+ static chainNames(vantage) {
246
253
  const names = [];
247
- let node = this;
254
+ let node = vantage;
248
255
  while (node !== undefined) {
249
256
  names.push(node.name);
250
257
  node = node.parent;
251
258
  }
252
259
  return names;
253
260
  }
254
- resolveWith(token, stack) {
261
+ resolveWith(token, vantage, stack) {
255
262
  if (stack.includes(token)) {
256
263
  throw new CircularDependencyError([...stack, token]);
257
264
  }
@@ -265,65 +272,70 @@ class Scope {
265
272
  if (registration.scope === undefined) {
266
273
  stack.push(token);
267
274
  try {
268
- return this.instantiate(token, registration, this, stack);
275
+ return this.instantiate(token, registration, vantage, stack);
269
276
  } finally {
270
277
  stack.pop();
271
278
  }
272
279
  }
273
- const owner = this.findOwner(registration.scope);
280
+ const owner = ServiceProvider.findOwner(vantage, registration.scope);
274
281
  if (owner === undefined) {
275
- throw new MissingScopeError(token, registration.scope, this.chainNames());
282
+ throw new MissingScopeError(token, registration.scope, ServiceProvider.chainNames(vantage));
276
283
  }
277
- if (owner.instances.has(token)) {
278
- return owner.instances.get(token);
284
+ if (owner.cache.has(token)) {
285
+ return owner.cache.get(token);
279
286
  }
280
287
  stack.push(token);
281
288
  try {
282
- const instance = owner.instantiate(token, registration, owner, stack);
283
- owner.instances.set(token, instance);
284
- owner.ownedOrder.push(instance);
289
+ const instance = this.instantiate(token, registration, owner, stack);
290
+ owner.cache.set(token, instance);
291
+ owner.owned.push(instance);
285
292
  return instance;
286
293
  } finally {
287
294
  stack.pop();
288
295
  }
289
296
  }
290
- instantiate(token, registration, owningScope, stack) {
297
+ instantiate(token, registration, owningFrame, stack) {
291
298
  if (registration.kind === "factory") {
292
- return owningScope.invokeFactory(token, registration.factory, stack);
299
+ return this.invokeFactory(token, registration.factory, owningFrame, stack);
293
300
  }
294
- return owningScope.construct(token, registration.ctor, stack);
301
+ return this.construct(token, registration.ctor, owningFrame, stack);
295
302
  }
296
- makeScopeView(stack) {
297
- const owner = this;
298
- const view = {
303
+ makeProviderView(owningFrame, stack) {
304
+ const sp = this;
305
+ return {
299
306
  resolve: (depToken) => {
300
307
  if (depToken === undefined) {
301
308
  throw new TypeError("resolve<T>() requires the @fnioc/transformer plugin (no token at " + "runtime).");
302
309
  }
303
- return owner.resolveWith(depToken, stack);
310
+ return sp.resolveWith(depToken, owningFrame, stack);
304
311
  },
305
- resolveFactory: (depToken) => owner.makeFactory({ factory: depToken }),
306
- createScope: (name) => owner.createScope(name)
312
+ resolveFactory: (depToken, depParams) => sp.makeFactory({ type: depToken, params: depParams }, owningFrame),
313
+ createScope: (...args) => {
314
+ const name = args[0] ?? "scoped";
315
+ const childFrame = new Scope(name, owningFrame);
316
+ return new ServiceProvider(sp.registrations, childFrame);
317
+ }
307
318
  };
308
- return view;
309
319
  }
310
- invokeFactory(token, factory, stack) {
311
- const scopeView = this.makeScopeView(stack);
320
+ invokeFactory(token, factory, owningFrame, stack) {
321
+ const providerView = this.makeProviderView(owningFrame, stack);
312
322
  const record = getDeps(factory);
313
323
  if (record === undefined || record.signatures.length === 0) {
314
- return factory(scopeView);
324
+ return factory(providerView);
315
325
  }
316
- const signature2 = this.selectSignature(token, factory.name, record.signatures);
326
+ const signature2 = this.selectSignature(token, factory.name, record.signatures, owningFrame);
317
327
  const args = signature2.map((slot) => {
318
328
  if (isScopeRef2(slot))
319
- return scopeView;
329
+ return providerView;
320
330
  if (isFactoryRef2(slot))
321
- return this.makeFactory(slot);
322
- return this.resolveWith(slot, stack);
331
+ return this.makeFactory(slot, owningFrame);
332
+ if (isUnion(slot))
333
+ return this.resolveUnion(slot, owningFrame, stack);
334
+ return this.resolveWith(slot, owningFrame, stack);
323
335
  });
324
336
  return factory(...args);
325
337
  }
326
- construct(token, ctor, stack) {
338
+ construct(token, ctor, owningFrame, stack) {
327
339
  const record = getDeps(ctor);
328
340
  if (record === undefined || record.signatures.length === 0) {
329
341
  if (ctor.length > 0) {
@@ -331,52 +343,69 @@ class Scope {
331
343
  }
332
344
  return new ctor;
333
345
  }
334
- const signature2 = this.selectSignature(token, ctor.name, record.signatures);
346
+ const signature2 = this.selectSignature(token, ctor.name, record.signatures, owningFrame);
347
+ const providerView = this.makeProviderView(owningFrame, stack);
335
348
  const args = signature2.map((slot) => {
336
349
  if (isScopeRef2(slot)) {
337
- return this.makeScopeView(stack);
350
+ return providerView;
338
351
  }
339
352
  if (isFactoryRef2(slot)) {
340
- return this.makeFactory(slot);
353
+ return this.makeFactory(slot, owningFrame);
354
+ }
355
+ if (isUnion(slot)) {
356
+ return this.resolveUnion(slot, owningFrame, stack);
341
357
  }
342
- return this.resolveWith(slot, stack);
358
+ return this.resolveWith(slot, owningFrame, stack);
343
359
  });
344
360
  return new ctor(...args);
345
361
  }
346
- makeFactory(ref) {
347
- const owningScope = this;
348
- const target = this.lookup(ref.factory);
362
+ makeFactory(ref, owningFrame) {
363
+ const sp = this;
364
+ const target = this.lookup(ref.type);
349
365
  if (target === undefined) {
350
- throw new FactoryTargetError(ref.factory, "unregistered");
366
+ throw new FactoryTargetError(ref.type, "unregistered");
351
367
  }
352
368
  if (target.kind === "value") {
353
- return () => owningScope.resolveWith(ref.factory, []);
369
+ return () => sp.resolveWith(ref.type, owningFrame, []);
370
+ }
371
+ const callerParams = ref.params !== undefined && ref.params.length > 0 ? ref.params : undefined;
372
+ if (callerParams === undefined) {
373
+ return () => sp.resolveWith(ref.type, owningFrame, []);
354
374
  }
355
375
  const depTarget = target.kind === "class" ? target.ctor : target.factory;
356
376
  const record = getDeps(depTarget);
357
- const targetSignature = record === undefined || record.signatures.length === 0 ? undefined : owningScope.selectTargetSignature(record.signatures);
358
- const parameterized = targetSignature !== undefined && targetSignature.some((slot) => !isFactoryRef2(slot) && !isScopeRef2(slot) && !owningScope.isResolvable(slot));
359
- if (!parameterized) {
360
- return () => owningScope.resolveWith(ref.factory, []);
361
- }
362
- return (...callArgs) => owningScope.buildPartitioned(target, targetSignature, callArgs);
377
+ const targetSignature = record === undefined || record.signatures.length === 0 ? undefined : sp.selectTargetSignature(record.signatures);
378
+ return (...callArgs) => sp.buildPartitioned(target, targetSignature, callerParams, callArgs, owningFrame);
363
379
  }
364
- buildPartitioned(target, signature2, callerArgs) {
380
+ buildPartitioned(target, signature2, callerParams, callArgs, owningFrame) {
365
381
  const stack = [];
366
- let nextCallerArg = 0;
382
+ const providerView = this.makeProviderView(owningFrame, stack);
383
+ if (signature2 === undefined || signature2.length === 0) {
384
+ return target.kind === "class" ? new target.ctor : target.factory(providerView);
385
+ }
386
+ const remainingParamIndices = callerParams.map((_, i) => i);
367
387
  const args = signature2.map((slot) => {
368
388
  if (isScopeRef2(slot))
369
- return this.makeScopeView(stack);
389
+ return providerView;
370
390
  if (isFactoryRef2(slot))
371
- return this.makeFactory(slot);
372
- if (!this.isResolvable(slot)) {
373
- return callerArgs[nextCallerArg++];
391
+ return this.makeFactory(slot, owningFrame);
392
+ if (isUnion(slot))
393
+ return this.resolveUnion(slot, owningFrame, stack);
394
+ const token = slot;
395
+ const matchIdx = remainingParamIndices.findIndex((pi) => callerParams[pi] === token);
396
+ if (matchIdx !== -1) {
397
+ const paramIdx = remainingParamIndices[matchIdx];
398
+ remainingParamIndices.splice(matchIdx, 1);
399
+ return callArgs[paramIdx];
400
+ }
401
+ if (!this.isResolvable(token)) {
402
+ throw new NoSatisfiableSignatureError(token, token, [token]);
374
403
  }
375
- return this.resolveWith(slot, stack);
404
+ return this.resolveWith(token, owningFrame, stack);
376
405
  });
377
406
  return target.kind === "class" ? new target.ctor(...args) : target.factory(...args);
378
407
  }
379
- selectSignature(token, targetName, signatures) {
408
+ selectSignature(token, targetName, signatures, _owningFrame) {
380
409
  const ordered = signatures.map((sig, index) => ({ sig, index })).sort((a, b) => b.sig.length !== a.sig.length ? b.sig.length - a.sig.length : a.index - b.index);
381
410
  const unsatisfiable = new Set;
382
411
  for (const { sig } of ordered) {
@@ -384,6 +413,12 @@ class Scope {
384
413
  for (const slot of sig) {
385
414
  if (isFactoryRef2(slot) || isScopeRef2(slot))
386
415
  continue;
416
+ if (isUnion(slot)) {
417
+ if (!this.isResolvableSlot(slot)) {
418
+ satisfiable = false;
419
+ }
420
+ continue;
421
+ }
387
422
  if (!this.isResolvable(slot)) {
388
423
  satisfiable = false;
389
424
  if (typeof slot === "string")
@@ -401,17 +436,43 @@ class Scope {
401
436
  isResolvable(slot) {
402
437
  return typeof slot === "string" && this.lookup(slot) !== undefined;
403
438
  }
439
+ isResolvableSlot(slot) {
440
+ if (isFactoryRef2(slot) || isScopeRef2(slot))
441
+ return true;
442
+ if (isUnion(slot)) {
443
+ return slot.union.some((member) => this.isResolvableSlot(member));
444
+ }
445
+ return this.isResolvable(slot);
446
+ }
447
+ resolveUnion(slot, owningFrame, stack) {
448
+ for (const member of slot.union) {
449
+ if (this.isResolvableSlot(member)) {
450
+ return this.resolveSlot(member, owningFrame, stack);
451
+ }
452
+ }
453
+ throw new NoSatisfiableUnionError(slot.union);
454
+ }
455
+ resolveSlot(slot, owningFrame, stack) {
456
+ if (isScopeRef2(slot))
457
+ return this.makeProviderView(owningFrame, stack);
458
+ if (isFactoryRef2(slot))
459
+ return this.makeFactory(slot, owningFrame);
460
+ if (isUnion(slot))
461
+ return this.resolveUnion(slot, owningFrame, stack);
462
+ return this.resolveWith(slot, owningFrame, stack);
463
+ }
404
464
  dispose() {
405
465
  if (this.disposed)
406
466
  return;
407
- for (const instance of this.ownedOrder) {
467
+ const owned = this.frame?.owned ?? [];
468
+ for (const instance of owned) {
408
469
  if (isThenable(instance)) {
409
470
  throw new AsyncDisposalRequiredError;
410
471
  }
411
472
  }
412
473
  this.disposed = true;
413
- for (let i = this.ownedOrder.length - 1;i >= 0; i--) {
414
- const instance = this.ownedOrder[i];
474
+ for (let i = owned.length - 1;i >= 0; i--) {
475
+ const instance = owned[i];
415
476
  if (isDisposable(instance)) {
416
477
  instance[Symbol.dispose]();
417
478
  }
@@ -422,8 +483,9 @@ class Scope {
422
483
  if (this.disposed)
423
484
  return;
424
485
  this.disposed = true;
486
+ const owned = this.frame?.owned ?? [];
425
487
  const settled = [];
426
- for (const instance of this.ownedOrder) {
488
+ for (const instance of owned) {
427
489
  settled.push(isThenable(instance) ? await instance : instance);
428
490
  }
429
491
  for (let i = settled.length - 1;i >= 0; i--) {
@@ -437,8 +499,10 @@ class Scope {
437
499
  this.clear();
438
500
  }
439
501
  clear() {
440
- this.instances.clear();
441
- this.ownedOrder.length = 0;
502
+ if (this.frame) {
503
+ this.frame.cache.clear();
504
+ this.frame.owned.length = 0;
505
+ }
442
506
  }
443
507
  [Symbol.dispose]() {
444
508
  this.dispose();
@@ -500,16 +564,24 @@ class DiBuilder {
500
564
  this.append(token, { kind: "value", useValue: value });
501
565
  }
502
566
  build() {
503
- return new Scope(this.rootName, undefined, this.registrations);
567
+ const sealed = new Map;
568
+ for (const [token, list] of this.registrations) {
569
+ sealed.set(token, Object.freeze([...list]));
570
+ }
571
+ Object.freeze(sealed);
572
+ const rootFrame = new Scope(this.rootName);
573
+ return new ServiceProvider(sealed, rootFrame);
504
574
  }
505
575
  }
506
576
  export {
577
+ union,
507
578
  signature,
508
- hole,
509
579
  forCtor,
510
580
  defineDeps,
511
581
  UnregisteredTokenError,
582
+ ServiceProvider,
512
583
  Scope,
584
+ NoSatisfiableUnionError,
513
585
  NoSatisfiableSignatureError,
514
586
  MissingScopeError,
515
587
  MissingMetadataError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnioc/di",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "The ioc runtime engine: DiBuilder, scopes, resolution, captive-dependency protection, factories, and native disposal.",
5
5
  "keywords": [
6
6
  "dependency-injection",