Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleaning up code #9

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 33 additions & 21 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Dependencies<FactoryLike> = Defined<NamedArguments<FactoryLike>>;
export type Injectable<FactoryLike> = ReturnType<FactoryFn<FactoryLike>>;

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
k: infer I,
) => void
? I
: never;
Expand All @@ -40,21 +40,23 @@ export type InjectableMap<Registry extends ObjectLike> = {
[key in keyof Registry]: Injectable<Registry[key]>;
};

type MaybeMet<Registry extends ObjectLike> =
keyof FlatDependencyTree<Registry> & keyof InjectableMap<Registry>;

/**
* Dependencies already met by the injectables themselves (union of keys)
* Dependencies already met by the injectables themselves
*/
export type FulfilledDependencies<Registry extends ObjectLike> = {
[Dep in MaybeMet<Registry>]: InjectableMap<Registry>[Dep] extends FlatDependencyTree<Registry>[Dep]
export type FulfilledDependencies<
Registry extends ObjectLike,
Dependencies = FlatDependencyTree<Registry>,
Injectables = InjectableMap<Registry>,
> = {
[Dep in keyof Dependencies &
keyof Injectables as Injectables[Dep] extends Dependencies[Dep]
? Dep
: never;
}[MaybeMet<Registry>];
: never]: Dependencies[Dep];
};

export type ExternalDeps<Registry extends ObjectLike> = Omit<
FlatDependencyTree<Registry>,
FulfilledDependencies<Registry>
keyof FulfilledDependencies<Registry>
> &
Partial<InjectableMap<Registry>>;

Expand All @@ -70,41 +72,51 @@ type ProviderFnArgs<Registry extends ObjectLike> = {

export type ProviderFn<
Registry extends ObjectLike,
PublicAPI extends Array<keyof Registry> = []
> = Partial<InjectableMap<Registry>> extends ExternalDeps<Registry>
? (externalDeps?: ProviderFnArgs<Registry>) => ModuleAPI<Registry, PublicAPI>
: (externalDeps: ProviderFnArgs<Registry>) => ModuleAPI<Registry, PublicAPI>;
PublicAPI extends Array<keyof Registry> = [],
> =
FulfilledDependencies<Registry> extends FlatDependencyTree<Registry>
? (
externalDeps?: ProviderFnArgs<Registry>,
) => ModuleAPI<Registry, PublicAPI>
: (
externalDeps: ProviderFnArgs<Registry>,
) => ModuleAPI<Registry, PublicAPI>;

/**
* If the injectable is a function, we have to wrap it in a function to avoid treating it as a factory
*/
type WrapFunctionInjectable<T> = [T] extends [(...args: any[]) => any]
? (deps?: any) => T
? (deps?: any) => T
: ((deps?: any) => T) | T;

/**
* Checks if each injectable match the required dependencies of the entire registry
*/
type ValidateRegistry<Registry extends ObjectLike, Deps = FlatDependencyTree<Registry>> = {
[key in keyof Registry]: key extends keyof Deps ? WrapFunctionInjectable<Deps[key]> : Registry[key];
type ValidateRegistry<
Registry extends ObjectLike,
Deps = FlatDependencyTree<Registry>,
> = {
[key in keyof Registry]: key extends keyof Deps
? WrapFunctionInjectable<Deps[key]>
: Registry[key];
};

declare function valueFn<T>(value: T): () => T;

declare const provideSymbol: unique symbol;

declare function singleton<Factory extends (...args: any[]) => any>(
factory: Factory
factory: Factory,
): (...args: Parameters<Factory>) => ReturnType<Factory>;

declare function createProvider<
Registry extends ObjectLike,
PublicAPI extends Array<keyof Registry> = []
PublicAPI extends Array<keyof Registry> = [],
>(args: {
injectables: ValidateRegistry<Registry>;
api?: PublicAPI;
}): ProviderFn<Registry, PublicAPI>;

declare function fromClass<T extends abstract new (deps: any) => any>(
Klass: T
Klass: T,
): (deps: Defined<ConstructorParameters<T>[0]>) => InstanceType<T>;
11 changes: 6 additions & 5 deletions src/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const mapValues = (mapFn) => (source) =>
[
...Object.getOwnPropertyNames(source),
...Object.getOwnPropertySymbols(source),
].map((key) => [key, mapFn(source[key], key)])
].map((key) => [key, mapFn(source[key], key)]),
);

export const valueFn = (val) => () => val;
Expand Down Expand Up @@ -31,7 +31,7 @@ export const createProvider = ({ injectables, api = [] }) => {
provide({
...externalDeps,
...subArgs,
})
}),
),
...externalDeps,
},
Expand All @@ -40,13 +40,13 @@ export const createProvider = ({ injectables, api = [] }) => {
if (!(prop in target)) {
throw new Error(
`could not resolve injectable with injection token "${String(
prop
)}"`
prop,
)}"`,
);
}
return Reflect.get(target, prop, receiver);
},
}
},
);

const mapWithPropertyDescriptor = mapValues((factory, key) => {
Expand All @@ -61,6 +61,7 @@ export const createProvider = ({ injectables, api = [] }) => {
});

const properties = mapWithPropertyDescriptor(_injectables);

return Object.defineProperties(_injectables, properties);
};
};
78 changes: 54 additions & 24 deletions src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,31 +112,62 @@ fulfilledDependencies: {
foo: (arg: { x: number; blah: string; woot: { prop: number } }) => any;
x: ({ otherThing }: { otherThing: string; y: string }) => number;
woot: () => { prop: number };
}> = 'x';
fulfilled = 'woot';
}> = {
woot: { prop: 42 },
x: 42,
};

// @ts-expect-error
// blah is not met
fulfilled = 'blah';
fulfilled = {
woot: { prop: 42 },
x: 42,
// @ts-expect-error
// blah is not met
blah: 'hello',
};

// @ts-expect-error
// otherThing is not met
fulfilled = 'otherThing';
fulfilled = {
woot: { prop: 42 },
x: 42,
// @ts-expect-error
// otherThing is not met
otherThing: 'hello',
};

fulfilled = {
// @ts-expect-error
// prop should be a number
woot: { prop: 'hello' },
x: 42,
};

let incompatibleInterfaces: FulfilledDependencies<{
x: (deps: { y: number; woot: { prop: { nested: number } } }) => any;
y: () => string;
woot: (deps: { met: string }) => { prop: { nested: string } };
met: () => string;
}> = 'met';
}> = {
met: 'hello',
};

// @ts-expect-error
// y should return a number
incompatibleInterfaces = 'y';
incompatibleInterfaces = {
met: 'hello',
// @ts-expect-error
// y should return a number
y: 'hello',

// @ts-expect-error
// nested type should be number
incompatibleInterfaces = 'woot';
woot: {
prop: { nested: 'woot' },
},
};

incompatibleInterfaces = {
met: 'hello',
// @ts-expect-error
// nested should be a number
woot: {
prop: { nested: 'woot' },
},
};
}

lateBoundDependencies: {
Expand Down Expand Up @@ -215,8 +246,8 @@ createProvider: {
bar: ({ b }: { b: string }) => b,
baz: ({ c }: { c: () => boolean }) => c(),
// @ts-expect-error a is not a number
a: "42",
// @ts-expect-error b is not a string
a: '42',
// @ts-expect-error b is not a string
b: () => 42 as number,
// @ts-expect-error c has to be wrapped in a function
c: () => true,
Expand Down Expand Up @@ -271,7 +302,7 @@ createProvider: {
provideDeepMissing({ typedDep: 'toto', nonTypedDep: 42 });
// @ts-expect-error typedDep & nonTypedDep is missing here
provideDeepMissing();
// @ts-expect-error typedDep & nonTypedDep is missing here
// @ts-expect-error typedDep & nonTypedDep is missing here
provideDeepMissing({});

const provideWrongType = createProvider({
Expand All @@ -281,12 +312,12 @@ createProvider: {
api: ['foo'],
});
// @ts-expect-error wrong dependency type
provideWrongType({ val: "42" })
provideWrongType({ val: '42' });
}

fromClass: {
class Foo {
constructor({ b }: { b: string }) { }
constructor({ b }: { b: string }) {}
}
let factory = fromClass(Foo);
factory({ b: 'woot' });
Expand All @@ -309,11 +340,10 @@ issue4: {
};
const provideMissingWithIntermediate = createProvider({
injectables: injectables,
api: ['a']
api: ['a'],
});


provideMissingWithIntermediate({
value: ({ intermediate }: { intermediate: string }) => Number(intermediate)
})
value: ({ intermediate }: { intermediate: string }) => Number(intermediate),
});
}
Loading