From ce2913d9fe3bfad1d9ca1820ed5a1bc8cd5fcc80 Mon Sep 17 00:00:00 2001 From: Teun Mooij Date: Sun, 8 Dec 2024 11:45:15 +0100 Subject: [PATCH] type narrowing for mapping dependency --- README.md | 5 ++--- changelog.md | 2 +- src/types/dependencies.ts | 2 +- src/types/systemic.ts | 6 ++--- test/systemic.spec.ts | 10 ++++----- test/types/systemic.spec.ts | 44 +++---------------------------------- 6 files changed, 15 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 56e43a3..ec93a2a 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ You can rename dependencies passed to a components start function by specifying const system = systemic() .add('config', initConfig()) .add('mongo', initMongo()) - .dependsOn({ component: 'config', destination: 'options' } as const); + .dependsOn({ component: 'config', destination: 'options' }); ``` If you want to inject a property or subdocument of the dependency thing you can also express this with a dependency mapping @@ -279,11 +279,10 @@ If you want to inject a property or subdocument of the dependency thing you can const system = systemic() .add('config', initConfig()) .add('mongo', initMongo()) - .dependsOn({ component: 'config', source: 'mongo' } as const); + .dependsOn({ component: 'config', source: 'mongo' }); ``` Now `config.mongo` will be injected as `config` instead of the entire configuration object. -Because of the way typescript narrowing of object properties works, mappings need to be added as constants. Otherwise `@ilpt/systemic-ts` is not able to validate the dependency. #### Scoped Dependencies diff --git a/changelog.md b/changelog.md index 7c0179d..39f084a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ ### What's changed -- No changelog has been added to this release +- Remove the need for marking a mapping dependency `as const` --- diff --git a/src/types/dependencies.ts b/src/types/dependencies.ts index 0c02d5a..6976e68 100644 --- a/src/types/dependencies.ts +++ b/src/types/dependencies.ts @@ -68,7 +68,7 @@ type ValidateMappingDependency< TDependencies extends Record, TMapping extends { component: string; destination: string; source?: string }, > = IsDestinationOrSourceUnbound extends true - ? [DependencyValidationError] // Dependency not created as constant + ? [DependencyValidationError] // Dependency cannot be determined : [PropAt>] extends [never] ? [] // Unexpected dependency : [Injected] extends [ diff --git a/src/types/systemic.ts b/src/types/systemic.ts index 250d981..1c8daf3 100644 --- a/src/types/systemic.ts +++ b/src/types/systemic.ts @@ -144,7 +144,7 @@ export type DependsOn< * When name and type of the dependencies match those available in the system, the dependency can be added by name. * When a dependency is named differently in the system or only part of a component is required as a dependency, a MappingDependsOnOption can be used. */ - dependsOn: >[]>( + dependsOn: >[]>( ...names: TNames ) => ValidateDependencies extends [ infer First extends DependencyValidationError, @@ -171,7 +171,7 @@ export type SystemicWithInvalidDependency< > = { [X in keyof Systemic]: ( error: string extends TError[0] - ? `Destination of a dependency for component "${TCurrent}" is unknown. Did you neglect to mark it 'as const'?` + ? `Destination of a dependency for component "${TCurrent}" is unknown.` : `Dependency "${TError[0]}" on component "${TCurrent}" is not of the required type`, expected: TError[1], actual: TError[2], @@ -195,7 +195,7 @@ type SystemicBuildDefaultComponent< * When name and type of the dependencies match those available in the system, the dependency can be added by name. * When a dependency is named differently in the system or only part of a component is required as a dependency, a MappingDependsOnOption can be used. */ - dependsOn: >[]>( + dependsOn: >[]>( ...names: TNames ) => ValidateDependencies extends [ infer First extends DependencyValidationError, diff --git a/test/systemic.spec.ts b/test/systemic.spec.ts index 5f19d00..962d6aa 100644 --- a/test/systemic.spec.ts +++ b/test/systemic.spec.ts @@ -336,7 +336,7 @@ describe("systemic", () => { const system = systemic() .add("foo", foo) .add("bar", bar) - .dependsOn({ component: "foo", destination: "baz" } as const); + .dependsOn({ component: "foo", destination: "baz" }); await system.start(); @@ -349,7 +349,7 @@ describe("systemic", () => { const system = systemic() .add("foo", foo) .add("bar", bar) - .dependsOn({ component: "foo", destination: "baz" } as const); + .dependsOn({ component: "foo", destination: "baz" }); await system.start(); expect(bar.state.dependencies).toEqual({ baz: "foo" }); }); @@ -359,7 +359,7 @@ describe("systemic", () => { const system = systemic() .add("foo", foo) .add("bar") - .dependsOn({ component: "foo", destination: "baz" } as const); + .dependsOn({ component: "foo", destination: "baz" }); const components = await system.start(); expect(components).toEqual({ foo: "foo", bar: { baz: "foo" } }); }); @@ -370,7 +370,7 @@ describe("systemic", () => { const system = systemic() .add("foo", foo) .add("bar", bar) - .dependsOn({ component: "foo", source: "qux" } as const); + .dependsOn({ component: "foo", source: "qux" }); await system.start(); expect(bar.state.dependencies).toEqual({ foo: "baz" }); }); @@ -381,7 +381,7 @@ describe("systemic", () => { const system = systemic() .add("foo", foo, { scoped: true }) .add("bar", bar) - .dependsOn({ component: "foo", source: "" } as const); + .dependsOn({ component: "foo", source: "" }); await system.start(); expect(bar.state.dependencies).toEqual({ foo: { qux: "baz" } }); }); diff --git a/test/types/systemic.spec.ts b/test/types/systemic.spec.ts index b973de8..a0ad49c 100644 --- a/test/types/systemic.spec.ts +++ b/test/types/systemic.spec.ts @@ -292,7 +292,7 @@ describe("systemic types", () => { const system = mockSystemic() .add("foo.bar", { start: async (deps: EmptyObject) => ({ baz: 42 }) }) .add("qux", { start: async (deps: { baz: { baz: number } }) => 42 }) - .dependsOn({ component: "foo.bar", destination: "baz" } as const); + .dependsOn({ component: "foo.bar", destination: "baz" }); type Registrations = { "foo.bar": { component: { baz: number }; scoped: false }; @@ -307,7 +307,7 @@ describe("systemic types", () => { const system = mockSystemic() .add("foo", { start: async (deps: EmptyObject) => ({ baz: "qux" }) }, { scoped: true }) .add("bar", { start: async (deps: { foo: { baz: string } }) => 42 }) - .dependsOn({ component: "foo", source: "" } as const); + .dependsOn({ component: "foo", source: "" }); type Registrations = { foo: { component: { baz: string }; scoped: true }; @@ -322,7 +322,7 @@ describe("systemic types", () => { const system = mockSystemic() .add("foo", { start: async (deps: EmptyObject) => ({ baz: "qux" }) }, { scoped: true }) .add("bar", { start: async (deps: { foo: { baz: string } }) => 42 }) - .dependsOn({ component: "foo", source: "test123" } as const); + .dependsOn({ component: "foo", source: "test123" }); type Registrations = { foo: { component: { baz: string }; scoped: true }; @@ -333,44 +333,6 @@ describe("systemic types", () => { expectTypes().toBeEqual(); }); - it("is a systemic with a dependency not marked as const", () => { - const system = mockSystemic() - .add("foo", { start: async (deps: EmptyObject) => ({ bar: "bar" }) }) - .add("bar", { start: async (deps: { baz: { bar: string } }) => 42 }) - .dependsOn({ component: "foo", destination: "baz" }); - - type Expected = SystemicWithInvalidDependency<"bar", [string, unknown, unknown]>; - - expectTypes().toBeEqual(); - expectTypes< - (typeof system)["start"], - ( - error: `Destination of a dependency for component "bar" is unknown. Did you neglect to mark it 'as const'?`, - expected: unknown, - actual: unknown, - ) => void - >().toBeEqual(); - }); - - it("is a systemic with a dependency on a default component not marked as const", () => { - const system = mockSystemic() - .add("foo", { start: async (deps: EmptyObject) => ({ bar: "bar" }) }) - .add("bar") - .dependsOn({ component: "foo", destination: "baz" }); - - type Expected = SystemicWithInvalidDependency<"bar", [string, unknown, unknown]>; - - expectTypes().toBeEqual(); - expectTypes< - (typeof system)["start"], - ( - error: `Destination of a dependency for component "bar" is unknown. Did you neglect to mark it 'as const'?`, - expected: unknown, - actual: unknown, - ) => void - >().toBeEqual(); - }); - it("is a systemic with an injected dependency that extends the expected type", () => { const system = mockSystemic() .add("foo", { start: async (deps: EmptyObject) => ({ foo: "bar" }) })