Skip to content

Commit 81f542a

Browse files
committed
refactor(yok)!: the facade extends Injector instead of wrapping one
Yok is now an Injector - class Yok extends Injector - so the new API works on the facade directly (get, register with Providers, createChild, runInInjectionContext($injector, ...)) and inject(Injector) inside legacy-constructed classes returns the facade itself instead of a second, inner container identity. The di bridge is gone. register() dispatches by argument shape: a string first argument is the legacy name-based form, anything else is a Provider. IInjector now extends the Injector class type, which constrains implementers to the real class hierarchy (Yok and its subclasses) - intentional, since the interface only ever described Yok.
1 parent ec5de90 commit 81f542a

5 files changed

Lines changed: 83 additions & 66 deletions

File tree

dependency-injection.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,13 @@ and resolve late (see above).
195195
Working alongside the legacy `$injector`
196196
----------------------------------------
197197

198-
The `Yok` facade (`global.$injector`) delegates to the token-based container,
199-
exposed as `$injector.di`. Everything is one registry:
198+
The `Yok` facade (`global.$injector`) IS an `Injector`the class extends the
199+
token-based container — so the new API works on it directly:
200200

201201
```ts
202-
$injector.resolve("doctorService") === $injector.di.get(DoctorService); // true
202+
$injector.resolve("doctorService") === $injector.get(DoctorService); // true
203+
$injector.register(provide(DoctorService, DoctorServiceImpl));
204+
runInInjectionContext($injector, () => inject(DoctorService));
203205
```
204206

205207
- Legacy string names are permanent: a contract's token name is its interop
@@ -244,8 +246,8 @@ The first tranche, growing as services migrate:
244246
Legacy → new quick reference
245247
----------------------------
246248

247-
`di` below is the token-based container backing the facade (`$injector.di`),
248-
or any `Injector` you hold directly.
249+
`di` below is any `Injector` you hold — including `$injector` itself, which
250+
extends `Injector`.
249251

250252
| Legacy (`$injector`) | New |
251253
|---|---|

lib/common/definitions/yok.d.ts

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
1-
import { IDisposable, IDictionary } from "../declarations";
1+
import { IDictionary } from "../declarations";
22
import { ICommand } from "./commands";
33
import { IKeyCommand, IValidKeyName } from "./key-commands";
44
import { Injector } from "../di/injector";
5+
import { Provider } from "../di/providers";
56

67
/**
7-
* The legacy injector facade surface. Every member is individually
8-
* @deprecated in favor of the token-based container in lib/common/di;
9-
* the interface itself survives until the hook/extension deprecation
10-
* completes.
8+
* The legacy injector facade surface. It extends the token-based `Injector` —
9+
* the facade IS an injector — and adds the legacy subsystems, whose members
10+
* are individually @deprecated. Only the `Yok` class hierarchy implements
11+
* this; the interface survives until the hook/extension deprecation completes.
1112
*/
12-
interface IInjector extends IDisposable {
13+
interface IInjector extends Injector {
1314
/**
14-
* The token-based container backing this facade — the bridge to the
15-
* new-style API. Registrations and lookups by token go here; code already
16-
* running in an injection context should prefer inject(Injector).
17-
*/
18-
readonly di: Injector;
19-
20-
/**
21-
* @deprecated Use provideLazy() from lib/common/di — the same deferred
22-
* loading, token-based.
15+
* @deprecated Use provideLazy() — the same deferred loading, token-based.
2316
*/
2417
require(name: string, file: string): void;
2518
/**
26-
* @deprecated Use provideLazy() from lib/common/di — the same deferred
27-
* loading, token-based.
19+
* @deprecated Use provideLazy() — the same deferred loading, token-based.
2820
*/
2921
require(names: string[], file: string): void;
3022
/**
@@ -52,23 +44,21 @@ interface IInjector extends IDisposable {
5244
/**
5345
* Resolves an implementation by constructor function.
5446
* The injector will create new instances for every call.
55-
* @deprecated Use Injector.createInstance from lib/common/di.
47+
* @deprecated Use Injector.createInstance.
5648
*/
5749
resolve(ctor: Function, ctorArguments?: { [key: string]: any }): any;
5850
/**
59-
* @deprecated Use Injector.createInstance from lib/common/di.
51+
* @deprecated Use Injector.createInstance.
6052
*/
6153
resolve<T>(ctor: Function, ctorArguments?: { [key: string]: any }): T;
6254
/**
6355
* Resolves an implementation by name.
6456
* The injector will create only one instance per name and return the same instance on subsequent calls.
65-
* @deprecated Use inject(Token) in an injection context, or Injector.get
66-
* from lib/common/di.
57+
* @deprecated Use inject(Token) in an injection context, or Injector.get.
6758
*/
6859
resolve(name: string, ctorArguments?: IDictionary<any>): any;
6960
/**
70-
* @deprecated Use inject(Token) in an injection context, or Injector.get
71-
* from lib/common/di.
61+
* @deprecated Use inject(Token) in an injection context, or Injector.get.
7262
*/
7363
resolve<T>(name: string, ctorArguments?: IDictionary<any>): T;
7464

@@ -81,10 +71,11 @@ interface IInjector extends IDisposable {
8171
*/
8272
resolveKeyCommand(key: string): IKeyCommand;
8373
/**
84-
* @deprecated Use provide() / Injector.register from lib/common/di; a
85-
* contract's token name keeps string spellings resolvable.
74+
* @deprecated Legacy name-based registration. Use the Provider overload or
75+
* provide(); a contract's token name keeps string spellings resolvable.
8676
*/
8777
register(name: string, resolver: any, shared?: boolean): void;
78+
register(providers: Provider | Provider[]): void;
8879
/**
8980
* @deprecated Slated for replacement by defineCommand and manifest-declared
9081
* commands.

lib/common/yok.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IInjector } from "./definitions/yok";
99
import { ICommandArgument, ICommand } from "./definitions/commands";
1010
import { IKeyCommand, IValidKeyName } from "./definitions/key-commands";
1111
import { Injector } from "./di/injector";
12+
import type { Provider } from "./di/providers";
1213

1314
/**
1415
* The legacy global facade binding. New code should obtain the container via
@@ -47,22 +48,20 @@ export interface IDependency {
4748
}
4849

4950
/**
50-
* The Yok facade: the externally reachable injector surface (every IInjector
51-
* member, the global $injector, subclassability) kept intact while storage and
52-
* resolution live in the token-based `Injector` (lib/common/di). Command
53-
* routing, the key-command namespace, and the public-API builder still live
54-
* here — they are separate subsystems that only share the container.
51+
* The Yok facade IS the token-based `Injector` — it extends it — plus the
52+
* legacy surface: command routing, the key-command namespace, the module
53+
* loader, and the public-API builder. Those subsystems historically shared
54+
* the container object and migrate out separately; until then they live here,
55+
* individually marked @deprecated.
5556
*/
56-
export class Yok implements IInjector {
57+
export class Yok extends Injector implements IInjector {
5758
/**
5859
* @deprecated Escape hatch of the legacy require-time module map.
5960
*/
6061
public overrideAlreadyRequiredModule: boolean = false;
6162

62-
/** The token-based container backing this facade. */
63-
private container = new Injector();
64-
6563
constructor() {
64+
super();
6665
this.register("injector", this);
6766
}
6867

@@ -76,11 +75,6 @@ export class Yok implements IInjector {
7675
private KEY_COMMANDS_NAMESPACE: string = "keyCommands";
7776
private hierarchicalCommands: IDictionary<string[]> = {};
7877

79-
/** New-style access to the backing container, for token-based registration. */
80-
public get di(): Injector {
81-
return this.container;
82-
}
83-
8478
/**
8579
* @deprecated Path-based command registration; slated for replacement by
8680
* manifest-declared commands.
@@ -94,7 +88,7 @@ export class Yok implements IInjector {
9488
if (commands.length > 1) {
9589
if (
9690
_.startsWith(commands[1], "*") &&
97-
this.container.has(this.createCommandName(commands[0])) &&
91+
this.has(this.createCommandName(commands[0])) &&
9892
!this.synthesizedParents.has(commands[0])
9993
) {
10094
throw new Error(
@@ -115,7 +109,7 @@ export class Yok implements IInjector {
115109

116110
if (
117111
commands.length > 1 &&
118-
!this.container.has(this.createCommandName(commands[0]))
112+
!this.has(this.createCommandName(commands[0]))
119113
) {
120114
this.require(this.createCommandName(commands[0]), file);
121115
if (commands[1] && !commandName.match(/\|\*/)) {
@@ -191,7 +185,7 @@ export class Yok implements IInjector {
191185
}
192186

193187
private resolveInstance(name: string): any {
194-
let classInstance = this.container.peek(name);
188+
let classInstance = this.peek(name);
195189
if (!classInstance) {
196190
classInstance = this.resolve(name);
197191
}
@@ -207,11 +201,11 @@ export class Yok implements IInjector {
207201
? relativePath
208202
: file;
209203

210-
if (!this.container.has(name) || this.overrideAlreadyRequiredModule) {
204+
if (!this.has(name) || this.overrideAlreadyRequiredModule) {
211205
// Yok replaced the whole record on an allowed re-require, dropping any
212206
// resolver and cached instances with it — preserved via remove().
213-
this.container.remove(name);
214-
this.container.register({
207+
this.remove(name);
208+
this.register({
215209
provide: name,
216210
useLazyRequire: () => require(dependencyPath),
217211
});
@@ -427,22 +421,33 @@ export class Yok implements IInjector {
427421
}
428422

429423
/**
430-
* @deprecated Use provide() / Injector.register from lib/common/di (via
431-
* `Yok.di`); a contract's token name keeps string spellings resolvable.
424+
* @deprecated Legacy name-based registration. Use a Provider (the overload
425+
* below) or provide(); a contract's token name keeps string spellings
426+
* resolvable.
432427
*/
433-
public register(name: string, resolver: any, shared?: boolean): void {
434-
shared = shared === undefined ? true : shared;
428+
public register(name: string, resolver: any, shared?: boolean): void;
429+
public register(providers: Provider | Provider[]): void;
430+
public register(
431+
nameOrProviders: string | Provider | Provider[],
432+
resolver?: any,
433+
shared?: boolean,
434+
): void {
435+
if (typeof nameOrProviders !== "string") {
436+
super.register(nameOrProviders);
437+
return;
438+
}
435439

440+
shared = shared === undefined ? true : shared;
436441
if (_.isFunction(resolver)) {
437442
// Classes and factory functions alike: the legacy provider kind
438443
// annotate()s the resolver and calls or news it by casing.
439-
this.container.register({
440-
provide: name,
444+
super.register({
445+
provide: nameOrProviders,
441446
useLegacyClass: resolver,
442447
shared,
443448
});
444449
} else {
445-
this.container.register({ provide: name, useValue: resolver, shared });
450+
super.register({ provide: nameOrProviders, useValue: resolver, shared });
446451
}
447452
}
448453

@@ -452,7 +457,7 @@ export class Yok implements IInjector {
452457
public resolveCommand(name: string): ICommand {
453458
let command: ICommand;
454459
const commandModuleName = this.createCommandName(name);
455-
if (!this.container.has(commandModuleName)) {
460+
if (!this.has(commandModuleName)) {
456461
return null;
457462
}
458463
command = this.resolve(commandModuleName);
@@ -466,7 +471,7 @@ export class Yok implements IInjector {
466471
public resolveKeyCommand(name: string): IKeyCommand {
467472
let command: IKeyCommand;
468473
const commandModuleName = this.createKeyCommandName(name);
469-
if (!this.container.has(commandModuleName)) {
474+
if (!this.has(commandModuleName)) {
470475
return null;
471476
}
472477

@@ -483,9 +488,9 @@ export class Yok implements IInjector {
483488
if (_.isFunction(param)) {
484489
// By-class resolution is transient and never retained — Yok did not
485490
// track these instances for disposal either.
486-
return this.container.createInstance(<Function>param, [], ctorArguments);
491+
return this.createInstance(<Function>param, [], ctorArguments);
487492
}
488-
return this.container.get(<string>param, ctorArguments);
493+
return this.get(<string>param, ctorArguments);
489494
}
490495

491496
/* Regex to match dynamic calls in the following format:
@@ -535,7 +540,7 @@ export class Yok implements IInjector {
535540
* and help, so the `|` encoding is user-visible.
536541
*/
537542
public getRegisteredCommandsNames(includeDev: boolean): string[] {
538-
const commandsNames = this.container.getRegisteredNames(
543+
const commandsNames = this.getRegisteredNames(
539544
`${this.COMMANDS_NAMESPACE}.`,
540545
);
541546
let commands = _.map(commandsNames, (commandName: string) =>
@@ -551,7 +556,7 @@ export class Yok implements IInjector {
551556
* @deprecated Legacy command-registry enumeration.
552557
*/
553558
public getRegisteredKeyCommandsNames(): string[] {
554-
const commandsNames = this.container.getRegisteredNames(
559+
const commandsNames = this.getRegisteredNames(
555560
`${this.KEY_COMMANDS_NAMESPACE}.`,
556561
);
557562
const commands = _.map(commandsNames, (commandName: string) =>
@@ -579,8 +584,8 @@ export class Yok implements IInjector {
579584
* @deprecated Delegates to Injector.dispose (reverse instantiation order);
580585
* new code disposes the di container directly.
581586
*/
582-
public dispose(): void {
583-
this.container.dispose([this]);
587+
public dispose(exclude: any[] = []): void {
588+
super.dispose([this, ...exclude]);
584589
}
585590
}
586591

test/compat/injector-facade-surface.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assert } from "chai";
22
import { Yok } from "../../lib/common/yok";
3+
import { Injector, inject, runInInjectionContext } from "../../lib/common/di";
34

45
// Pins the externally reachable injector surface: every IInjector member
56
// (lib/common/definitions/yok.d.ts) plus dispose, subclassability, the
@@ -65,6 +66,24 @@ describe("injector facade surface", () => {
6566
}
6667
});
6768

69+
it("IS an Injector: instanceof holds and the new API works on the facade directly", () => {
70+
const inj = new Yok();
71+
assert.instanceOf(inj, Injector);
72+
73+
// Provider-form registration dispatches to the container...
74+
inj.register({ provide: "viaProvider", useValue: { tag: 1 } });
75+
assert.equal(inj.get<any>("viaProvider").tag, 1);
76+
// ...while string-form registration keeps legacy semantics.
77+
inj.register("viaLegacy", { tag: 2 });
78+
assert.equal(inj.resolve("viaLegacy").tag, 2);
79+
assert.strictEqual(inj.get("viaLegacy"), inj.resolve("viaLegacy"));
80+
81+
// One identity: the injection context IS the facade.
82+
runInInjectionContext(inj, () => {
83+
assert.strictEqual(inject(Injector), inj);
84+
});
85+
});
86+
6887
it("calls lowercase/anonymous resolvers as factories instead of new-ing them", () => {
6988
const inj = new Yok();
7089
inj.register("factoryMade", function () {

test/compat/legacy-hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ describe("legacy hook contract", () => {
283283
});
284284

285285
assert.strictEqual(capture.logger, testInjector.resolve("logger"));
286-
assert.strictEqual(capture.container, testInjector.di);
286+
assert.strictEqual(capture.container, testInjector);
287287
assert.strictEqual(capture.hookArgs, payload);
288288
});
289289

0 commit comments

Comments
 (0)