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

feat(core): lightweight keys #10

Merged
merged 1 commit into from
Dec 15, 2022
Merged
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
6 changes: 6 additions & 0 deletions packages/core/src/injectable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,10 @@ describe('injectable', () => {
expect(result).toBe('override')
expect(project).not.toHaveBeenCalled()
})
it('stores key', () => {
const foo = injectable('foo', () => 123)
// $ExpectType "foo"
foo.key
expect(foo.key).toEqual('foo')
})
})
30 changes: 24 additions & 6 deletions packages/core/src/injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,22 @@ export type Flatten<Tree> = Tree extends UnknownDependencyTree
>
: never

export interface Injectable<Tree extends UnknownDependencyTree, Value> {
export interface InjectableWithoutName<
Tree extends UnknownDependencyTree,
Value
> {
(tree: Flatten<NoInfer<Tree>>): Value
}

export interface InjectableWithName<Tree extends UnknownDependencyTree, Value> {
(tree: Flatten<NoInfer<Tree>>): Value
readonly key: Tree['name']
}

export type Injectable<Tree extends UnknownDependencyTree, Value> =
| InjectableWithoutName<Tree, Value>
| InjectableWithName<Tree, Value>

export type InjectableValue<Target> = Target extends Injectable<
UnknownDependencyTree,
infer Value
Expand All @@ -59,7 +71,7 @@ type MapInjectablesToValues<Targets> = {
}

type MergeDependencies<
Inputs extends readonly Injectable<never, unknown>[],
Inputs extends readonly Injectable<UnknownDependencyTree, unknown>[],
Name extends PropertyKey | never,
Type
> = {
Expand All @@ -73,12 +85,15 @@ type MergeDependencies<

export function injectable<
Name extends PropertyKey,
Inputs extends readonly Injectable<UnknownDependencyTree, unknown>[],
Inputs extends readonly (
| Injectable<UnknownDependencyTree, unknown>
| InjectableWithName<UnknownDependencyTree, unknown>
)[],
Value
>(
name: Name,
...args: [...Inputs, (...values: MapInjectablesToValues<Inputs>) => Value]
): Injectable<
): InjectableWithName<
{
readonly [Key in keyof MergeDependencies<
Inputs,
Expand All @@ -93,7 +108,7 @@ export function injectable<
Value
>(
...args: [...Inputs, (...values: MapInjectablesToValues<Inputs>) => Value]
): Injectable<
): InjectableWithoutName<
{
readonly [Key in keyof MergeDependencies<
Inputs,
Expand All @@ -116,7 +131,7 @@ export function injectable(
] as never

const memoizedProject = memoMany(project)
return (dependencies: Record<PropertyKey, unknown>) => {
const f = (dependencies: Record<PropertyKey, unknown>): unknown => {
if (name !== undefined) {
const override = dependencies[name]
if (override !== undefined) {
Expand All @@ -126,6 +141,9 @@ export function injectable(
const values = injectables.map((injectable) => injectable(dependencies))
return memoizedProject(...values)
}
f.key = name

return f
}

const isPropertyKey = (input: unknown): input is PropertyKey =>
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ describe('token', () => {
const result = foo({ [TOKEN_ACCESSOR_KEY]: accessor, foo: 'bar' })
expect(cb).toHaveBeenCalledWith('bar')
})
it('stores key', () => {
const foo = token('foo')<string>()
// $ExpectType "foo"
foo.key
expect(foo.key).toEqual('foo')
})
})
12 changes: 9 additions & 3 deletions packages/core/src/token.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from './injectable'
import { InjectableWithName } from './injectable'

export const TOKEN_ACCESSOR_KEY = '@injectable-ts/core//TOKEN_ACCESSOR'

Expand All @@ -10,7 +10,7 @@ export interface TokenAccessor {
}

export function token<Name extends PropertyKey>(name: Name) {
return <Type = never>(): Injectable<
return <Type = never>(): InjectableWithName<
{
readonly name: Name
readonly type: Type
Expand All @@ -26,9 +26,15 @@ export function token<Name extends PropertyKey>(name: Name) {
},
Type
> => {
return (dependencies) => {
const f = (
dependencies: {
readonly [TOKEN_ACCESSOR_KEY]?: TokenAccessor
} & Record<Name, Type>
): Type => {
const accessor = dependencies[TOKEN_ACCESSOR_KEY]
return accessor ? accessor(dependencies, name) : dependencies[name]
}
f.key = name
return f
}
}