import { injectable, InjectKey, makeInjector, override } from './injector'; interface A { foo: string; } // The following 3 injectables all provide an implementation of the "A" interface const A = injectable(() => { return { foo: 'a', }; }); const A2 = injectable(() => { return { foo: 'a2', }; }); const A3 = injectable(() => { return { foo: 'a3', }; }); // Using a named interface for an injectable is nice, but not necessary - here's an example where TS // just infers the InjectKey's type from the factory function's return value: const B = injectable((inject) => { const a = inject(A); function getA(): A { return a; } return { getA, bar: 'b' + a.foo, }; }); // This demonstrates how to express an optional dependency - just use a key that defaults to undefined, and // then override it in the injector if needed const OptionalA = injectable(() => undefined); const C = injectable((inject) => { const a = inject(A); const b = inject(B); const maybeA = inject(OptionalA); return { bagel: 'c' + a.foo + b.bar, hasOptionalA: maybeA !== undefined, }; }); // Type tests: // @ts-expect-error Should only be able to override a key with a key/value assignable to it override(A, injectable(() => ({ foo2: 'A' }))); class C1 { private foo: undefined; } class C2 extends C1 { private bar: undefined; } const IC1 = injectable(() => new C1()); const IC2 = injectable(() => new C2()); // @ts-expect-error Should not allow a subtype to be overridden with a parent type override(IC2, IC1); // @ts-expect-error Should properly type the InjectKey based on the return value of the factory fn const BadReturnValue: InjectKey = injectable(() => 1337); // Behavior tests: describe('injector v2', () => { it('should work', () => { const inject = makeInjector(); const c = inject(C); const b = inject(B) const a = inject(A); expect(b.bar).toEqual('ba'); expect(c.bagel).toEqual('caba'); expect(b.getA()).toBe(a); expect(c.hasOptionalA).toBe(false); }) it('should allow overriding with key', () => { const inject = makeInjector([ override(A, A2), ]); const c = inject(C); const b = inject(B) const a = inject(A); expect(a.foo).toEqual('a2'); expect(b.bar).toEqual('ba2'); expect(c.bagel).toEqual('ca2ba2'); expect(b.getA()).toBe(a); }) it('should allow overriding with key (optional dep, for demonstration)', () => { const inject = makeInjector([ override(OptionalA, A), ]); const c = inject(C); expect(c.hasOptionalA).toEqual(true); }) it('should allow overriding with value', () => { const inject = makeInjector([ override(A, injectable(() => ({ foo: 'A' }))), ]); const c = inject(C); const b = inject(B) const a = inject(A); expect(a.foo).toEqual('A'); expect(b.bar).toEqual('bA'); expect(c.bagel).toEqual('cAbA'); expect(b.getA()).toBe(a); }) })