From ae57be74553f3548d20a7b60b2410ce64516a2bf Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 4 Jun 2026 21:47:13 +0200 Subject: [PATCH 1/3] Add internal open-service visibility Co-authored-by: Cursor --- code/core/src/shared/open-service/README.md | 23 +++ code/core/src/shared/open-service/fixtures.ts | 170 ++++++++++++++++++ .../src/shared/open-service/index.test-d.ts | 61 +++++++ .../shared/open-service/service-definition.ts | 57 ++++-- .../open-service/service-registration.test.ts | 164 +++++++++-------- .../open-service/service-registration.ts | 42 +++-- code/core/src/shared/open-service/types.ts | 17 ++ 7 files changed, 424 insertions(+), 110 deletions(-) diff --git a/code/core/src/shared/open-service/README.md b/code/core/src/shared/open-service/README.md index 2043f6febb5d..670cf869eb00 100644 --- a/code/core/src/shared/open-service/README.md +++ b/code/core/src/shared/open-service/README.md @@ -104,6 +104,29 @@ A command is: Commands receive a `CommandCtx` whose `self` includes `state`, `queries`, `commands`, and `setState`. +### Discovery visibility + +Services and operations can be hidden from discovery APIs without disabling them at runtime: + +- Set `internal: true` on a **service** to omit it from `listServices()`. `describeService(id)` and + `getService(id)` still work when the id is known. +- Set `internal: true` on a **query or command** to omit it from `describeService()` output (and + therefore from `queryNames` / `commandNames` in `listServices()` summaries). Runtime callers can + still invoke the operation through a service handle, and TypeScript types remain available. + +`internal` defaults to `false` when omitted. It is part of the definition contract only — it cannot +be overridden at `registerService()` time. Static snapshot building is unaffected. + +### Internal operation naming + +Internal queries and commands must use a `_` prefix (for example `_debugState`). `defineService()` +enforces this bidirectionally at compile time: + +- `internal: true` requires a `_`-prefixed name +- a `_`-prefixed name requires `internal: true` + +Public operations must not use a `_` prefix unless they are internal. + ### Cross-service composition Handlers resolve other registered services through `ctx.getService(serviceId)`. Without a type diff --git a/code/core/src/shared/open-service/fixtures.ts b/code/core/src/shared/open-service/fixtures.ts index c9ed3a4ca78e..0c8a3292d376 100644 --- a/code/core/src/shared/open-service/fixtures.ts +++ b/code/core/src/shared/open-service/fixtures.ts @@ -272,3 +272,173 @@ export function createInvalidStaticInputServiceDef() { commands: {}, }); } + +/** Leaves handlers undefined so registration tests can supply them later. */ +export const unimplementedOperationsServiceDef = defineService({ + id: 'internal-fixture/unimplemented-operations', + description: 'Leaves handlers undefined so registration can supply them later.', + initialState: {} as Record, + queries: { + getValue: { + description: 'Reads a value that is not implemented in this environment.', + input: v.undefined(), + output: v.string(), + }, + }, + commands: { + run: { + description: 'Runs a command that is not implemented in this environment.', + input: v.undefined(), + output: voidOutputSchema, + }, + }, +}); + +export type RegisteredCommandOverrideState = { count: number }; + +/** Provides commands whose handlers are supplied at registration time. */ +export const registeredCommandOverrideServiceDef = defineService({ + id: 'internal-fixture/registered-command-override', + description: 'Provides a command handler at registration time.', + initialState: { count: 0 } satisfies RegisteredCommandOverrideState, + queries: { + getCount: { + description: 'Reads the current count.', + input: v.undefined(), + output: v.number(), + handler: (_input, ctx) => ctx.self.state.count, + }, + }, + commands: { + increment: { + description: 'Increments the current count.', + input: v.undefined(), + output: voidOutputSchema, + }, + assignFromLookup: { + description: 'Reads another service and mirrors whether a marker exists.', + input: assignEntryFieldInputSchema, + output: voidOutputSchema, + }, + }, +}); + +export type RegistrationOnlyStaticBuildState = { value: string | null }; + +/** Declares staticPath in the definition; load and handlers may be supplied at registration. */ +export const registrationOnlyStaticBuildServiceDef = defineService({ + id: 'internal-fixture/registration-only-static-build', + description: 'Declares staticPath in the definition and load at registration.', + initialState: { value: null } satisfies RegistrationOnlyStaticBuildState, + queries: { + getValue: { + description: 'Returns one statically built value.', + input: v.object({ build: v.literal('once') }), + output: v.nullable(v.string()), + handler: (_input, ctx) => ctx.self.state.value, + staticPath: () => 'state.json', + staticInputs: async () => [{ build: 'once' as const }], + }, + }, + commands: { + setValue: { + description: 'Stores one value during static load.', + input: v.undefined(), + output: voidOutputSchema, + }, + }, +}); + +export type MixedVisibilityState = { value: number }; + +/** Exposes one public and one internal operation per family for discovery tests. */ +export const mixedVisibilityServiceDef = defineService({ + id: 'internal-fixture/mixed-visibility', + description: 'Exposes one public and one internal operation per family.', + initialState: { value: 0 } satisfies MixedVisibilityState, + queries: { + getValue: { + description: 'Public query.', + input: v.undefined(), + output: v.number(), + handler: (_input, ctx) => ctx.self.state.value, + }, + _getInternalValue: { + internal: true, + description: 'Internal query.', + input: v.undefined(), + output: v.number(), + handler: (_input, ctx) => ctx.self.state.value, + }, + }, + commands: { + setValue: { + description: 'Public command.', + input: v.number(), + output: voidOutputSchema, + handler: (input, ctx) => { + ctx.self.setState((draft) => { + draft.value = input; + }); + }, + }, + _reset: { + internal: true, + description: 'Internal command.', + input: v.undefined(), + output: voidOutputSchema, + handler: (_input, ctx) => { + ctx.self.setState((draft) => { + draft.value = 0; + }); + }, + }, + }, +}); + +export type HiddenServiceState = { secret: boolean }; + +/** Hidden from listServices while remaining reachable through getService. */ +export const hiddenServiceDef = defineService({ + id: 'internal-fixture/hidden-service', + internal: true, + description: 'Hidden from listServices.', + initialState: { secret: true } satisfies HiddenServiceState, + queries: { + getSecret: { + description: 'Returns the secret flag.', + input: v.undefined(), + output: v.boolean(), + handler: (_input, ctx) => ctx.self.state.secret, + }, + }, + commands: {}, +}); + +export type InternalStaticBuildState = { value: string | null }; + +/** Internal query participates in static builds. */ +export const internalStaticBuildServiceDef = defineService({ + id: 'internal-fixture/internal-static-build', + description: 'Internal query participates in static builds.', + initialState: { value: null } satisfies InternalStaticBuildState, + queries: { + _getValue: { + internal: true, + description: 'Internal statically built query.', + input: v.object({ build: v.literal('once') }), + output: v.nullable(v.string()), + handler: (_input, ctx) => ctx.self.state.value, + staticPath: () => 'state.json', + staticInputs: async () => [{ build: 'once' as const }], + }, + }, + commands: { + _setValue: { + internal: true, + description: 'Internal command used during static load.', + input: v.undefined(), + output: voidOutputSchema, + }, + }, +}); diff --git a/code/core/src/shared/open-service/index.test-d.ts b/code/core/src/shared/open-service/index.test-d.ts index 023320d87026..7192ad7bc366 100644 --- a/code/core/src/shared/open-service/index.test-d.ts +++ b/code/core/src/shared/open-service/index.test-d.ts @@ -154,4 +154,65 @@ describe('open-service type inference', () => { commands: {}, }); }); + + it('accepts internal operations prefixed with _', () => { + defineService({ + id: 'internal-fixture/valid-internal-naming', + initialState: {} as Record, + queries: { + getValue: { + input: v.undefined(), + output: v.number(), + handler: () => 0, + }, + _getInternalValue: { + internal: true, + input: v.undefined(), + output: v.number(), + handler: () => 0, + }, + }, + commands: { + _reset: { + internal: true, + input: v.undefined(), + output: v.void(), + handler: async () => {}, + }, + }, + }); + }); + + it('rejects internal: true without a _ prefix', () => { + defineService({ + id: 'internal-fixture/invalid-internal-without-prefix', + initialState: {} as Record, + queries: { + // @ts-expect-error internal operations must be prefixed with "_" + debugQuery: { + internal: true, + input: v.undefined(), + output: v.number(), + handler: () => 0, + }, + }, + commands: {}, + }); + }); + + it('rejects _ prefix without internal: true', () => { + defineService({ + id: 'internal-fixture/invalid-prefix-without-internal', + initialState: {} as Record, + queries: { + // @ts-expect-error operations prefixed with "_" must set internal: true + _debugQuery: { + input: v.undefined(), + output: v.number(), + handler: () => 0, + }, + }, + commands: {}, + }); + }); }); diff --git a/code/core/src/shared/open-service/service-definition.ts b/code/core/src/shared/open-service/service-definition.ts index 2f9c389448e2..7df18e28ffcc 100644 --- a/code/core/src/shared/open-service/service-definition.ts +++ b/code/core/src/shared/open-service/service-definition.ts @@ -7,6 +7,29 @@ import type { ServiceId, } from './types.ts'; +type InvalidInternalOperationName = { + __internal_naming_error: `Operation "${TName}" has internal: true but must be prefixed with "_"`; +}; + +type InvalidUnderscoreWithoutInternal = { + __internal_naming_error: `Operation "${TName}" is prefixed with "_" and must set internal: true`; +}; + +/** + * Ensures internal operations use a `_` prefix and that `_`-prefixed operations set `internal: true`. + */ +type EnforceInternalOperationNaming> = { + [K in keyof T]: T[K] extends infer TOp + ? K extends `_${string}` + ? TOp extends { internal: true } + ? TOp + : InvalidUnderscoreWithoutInternal + : TOp extends { internal: true } + ? InvalidInternalOperationName + : TOp + : never; +}; + /** * Authoring-side query map derived from separate query input/output schema maps. * @@ -66,6 +89,9 @@ type DefinedCommands< * concrete query/command definition maps from those schemas. If we instead ask TypeScript to infer * the full runtime `ServiceDefinition` maps directly, it widens callback parameters to `unknown` * before it has correlated each inline object's `input` and `output` properties. + * + * Query and command maps are also inferred as separate const type parameters so internal naming + * validation can inspect literal `internal: true` flags without losing handler inference. */ export const defineService = < TState, @@ -73,26 +99,23 @@ export const defineService = < const TQueryOutputSchemas extends MatchingOutputSchemas, const TCommandInputSchemas extends OperationInputSchemas, const TCommandOutputSchemas extends MatchingOutputSchemas, ->(def: { - id: ServiceId; - description?: string; - initialState: TState; - queries: DefinedQueries< - TState, - TQueryInputSchemas, - TQueryOutputSchemas, - TCommandInputSchemas, - TCommandOutputSchemas - >; - commands: DefinedCommands; -}): ServiceDefinition< - TState, - DefinedQueries< + const TQueries extends DefinedQueries< TState, TQueryInputSchemas, TQueryOutputSchemas, TCommandInputSchemas, TCommandOutputSchemas >, - DefinedCommands -> => def; + const TCommands extends DefinedCommands, +>(def: { + id: ServiceId; + description?: string; + /** + * When true, hides this service from `listServices()` output. Defaults to false. Does not disable + * the service at runtime. + */ + internal?: boolean; + initialState: TState; + queries: TQueries & EnforceInternalOperationNaming; + commands: TCommands & EnforceInternalOperationNaming; +}): ServiceDefinition => def; diff --git a/code/core/src/shared/open-service/service-registration.test.ts b/code/core/src/shared/open-service/service-registration.test.ts index 0b4e8c0abcbe..4992f2277b31 100644 --- a/code/core/src/shared/open-service/service-registration.test.ts +++ b/code/core/src/shared/open-service/service-registration.test.ts @@ -1,14 +1,18 @@ -import * as v from 'valibot'; import { afterEach, describe, expect, it } from 'vitest'; -import { defineService } from './service-definition.ts'; import { - assignEntryFieldInputSchema, awaitedPreloadValueServiceDef, + assignEntryFieldInputSchema, createDerivedBooleanFromChildQueryServiceDef, entryIdInputSchema, + hiddenServiceDef, + internalStaticBuildServiceDef, + mixedVisibilityServiceDef, mutableRecordLookupServiceDef, recordFieldsOutputSchema, + registeredCommandOverrideServiceDef, + registrationOnlyStaticBuildServiceDef, + unimplementedOperationsServiceDef, voidOutputSchema, } from './fixtures.ts'; import { @@ -88,27 +92,7 @@ describe('service registration', () => { }); it('throws a Storybook error when a registered query or command is missing its handler', async () => { - const service = registerService( - defineService({ - id: 'internal-fixture/unimplemented-operations', - description: 'Leaves handlers undefined so registration can supply them later.', - initialState: {} as Record, - queries: { - getValue: { - description: 'Reads a value that is not implemented in this environment.', - input: v.undefined(), - output: v.string(), - }, - }, - commands: { - run: { - description: 'Runs a command that is not implemented in this environment.', - input: v.undefined(), - output: voidOutputSchema, - }, - }, - }) - ); + const service = registerService(unimplementedOperationsServiceDef); expect(() => service.queries.getValue(undefined)).toThrow( 'Query "internal-fixture/unimplemented-operations.getValue" is not implemented for this environment.' @@ -137,34 +121,8 @@ describe('service registration', () => { }); it('allows server registration to provide handlers that are omitted from the definition', async () => { - const incrementableServiceDef = defineService({ - id: 'internal-fixture/registered-command-override', - description: 'Provides a command handler at registration time.', - initialState: { count: 0 }, - queries: { - getCount: { - description: 'Reads the current count.', - input: v.undefined(), - output: v.number(), - handler: (_input, ctx) => ctx.self.state.count, - }, - }, - commands: { - increment: { - description: 'Increments the current count.', - input: v.undefined(), - output: voidOutputSchema, - }, - assignFromLookup: { - description: 'Reads another service and mirrors whether a marker exists.', - input: assignEntryFieldInputSchema, - output: voidOutputSchema, - }, - }, - }); - registerService(mutableRecordLookupServiceDef); - const service = registerService(incrementableServiceDef, { + const service = registerService(registeredCommandOverrideServiceDef, { commands: { increment: { handler: async (_input, ctx) => { @@ -218,30 +176,7 @@ describe('service registration', () => { }); it('allows load and staticInputs to be supplied only at registration time', async () => { - const serviceDef = defineService({ - id: 'internal-fixture/registration-only-static-build', - description: 'Declares staticPath in the definition and load at registration.', - initialState: { value: null as string | null }, - queries: { - getValue: { - description: 'Returns one statically built value.', - input: v.object({ build: v.literal('once') }), - output: v.nullable(v.string()), - handler: (_input, ctx) => ctx.self.state.value, - staticPath: () => 'state.json', - staticInputs: async () => [{ build: 'once' as const }], - }, - }, - commands: { - setValue: { - description: 'Stores one value during static load.', - input: v.undefined(), - output: voidOutputSchema, - }, - }, - }); - - registerService(serviceDef, { + registerService(registrationOnlyStaticBuildServiceDef, { queries: { getValue: { load: async (_input, ctx) => { @@ -272,4 +207,83 @@ describe('service registration', () => { }) ).toBe(null); }); + + it('omits internal operations from describeService and listServices summaries', async () => { + const service = registerService(mixedVisibilityServiceDef); + + await expect(listServices()).resolves.toEqual([ + { + id: 'internal-fixture/mixed-visibility', + description: 'Exposes one public and one internal operation per family.', + queryNames: ['getValue'], + commandNames: ['setValue'], + }, + ]); + + const descriptor = await describeService('internal-fixture/mixed-visibility'); + + expect(Object.keys(descriptor.queries)).toEqual(['getValue']); + expect(Object.keys(descriptor.commands)).toEqual(['setValue']); + + expect(service.queries._getInternalValue(undefined)).toBe(0); + await service.commands._reset(undefined); + expect(service.queries.getValue(undefined)).toBe(0); + }); + + it('omits internal services from listServices while keeping runtime access', async () => { + registerService(mutableRecordLookupServiceDef); + registerService(hiddenServiceDef); + + await expect(listServices()).resolves.toEqual([ + { + id: 'internal-fixture/mutable-record-lookup', + description: 'Provides a mutable record lookup keyed by entry id.', + queryNames: ['getRecordFields'], + commandNames: ['assignRecordField'], + }, + ]); + + const descriptor = await describeService('internal-fixture/hidden-service'); + + expect(descriptor).toMatchObject({ + id: 'internal-fixture/hidden-service', + description: 'Hidden from listServices.', + queries: { + getSecret: { + name: 'getSecret', + description: 'Returns the secret flag.', + }, + }, + commands: {}, + }); + + expect(getService('internal-fixture/hidden-service').queries.getSecret(undefined)).toBe(true); + }); + + it('still builds static snapshots for internal queries', async () => { + registerService(internalStaticBuildServiceDef, { + queries: { + _getValue: { + load: async (_input, ctx) => { + await ctx.self.commands._setValue(undefined); + }, + }, + }, + commands: { + _setValue: { + handler: async (_input, ctx) => { + ctx.self.setState((draft) => { + draft.value = 'built-internal'; + }); + }, + }, + }, + }); + + await expect(buildStaticFiles()).resolves.toEqual({ + 'internal-fixture/internal-static-build/state.json': { + value: 'built-internal', + }, + }); + }); }); diff --git a/code/core/src/shared/open-service/service-registration.ts b/code/core/src/shared/open-service/service-registration.ts index aad03d396d15..3717d52411fc 100644 --- a/code/core/src/shared/open-service/service-registration.ts +++ b/code/core/src/shared/open-service/service-registration.ts @@ -58,27 +58,31 @@ function describeDefinition(definition: AnyServiceDefinition): ServiceDescriptor id: definition.id, description: definition.description, queries: Object.fromEntries( - Object.entries(definition.queries).map(([name, query]) => [ - name, - { + Object.entries(definition.queries) + .filter(([, query]) => !query.internal) + .map(([name, query]) => [ name, - description: query.description, - input: query.input, - output: query.output, - ...(query.staticPath ? { staticPath: true as const } : {}), - }, - ]) + { + name, + description: query.description, + input: query.input, + output: query.output, + ...(query.staticPath ? { staticPath: true as const } : {}), + }, + ]) ), commands: Object.fromEntries( - Object.entries(definition.commands).map(([name, command]) => [ - name, - { + Object.entries(definition.commands) + .filter(([, command]) => !command.internal) + .map(([name, command]) => [ name, - description: command.description, - input: command.input, - output: command.output, - }, - ]) + { + name, + description: command.description, + input: command.input, + output: command.output, + }, + ]) ), }; } @@ -228,7 +232,9 @@ export function getRegisteredServices(): AnyServiceDefinition[] { * operation names. */ export async function listServices(): Promise { - return Array.from(getRegistry().values(), ({ summary }) => summary); + return Array.from(getRegistry().values()) + .filter(({ definition }) => !definition.internal) + .map(({ summary }) => summary); } /** diff --git a/code/core/src/shared/open-service/types.ts b/code/core/src/shared/open-service/types.ts index 3fe77efe6a6f..e4b20163c9ae 100644 --- a/code/core/src/shared/open-service/types.ts +++ b/code/core/src/shared/open-service/types.ts @@ -203,6 +203,11 @@ export type QueryDefinition< MatchingOutputSchemas, > = { description?: string; + /** + * When true, hides this query from `describeService()` output. Defaults to false. Does not disable + * the query at runtime — callers with a service handle can still invoke it. + */ + internal?: boolean; input: TInputSchema; output: TOutputSchema; /** Logical path for the serialized state snapshot, relative to this service's output folder. */ @@ -236,6 +241,11 @@ export type CommandDefinition< TOutputSchema extends AnySchema, > = { description?: string; + /** + * When true, hides this command from `describeService()` output. Defaults to false. Does not + * disable the command at runtime — callers with a service handle can still invoke it. + */ + internal?: boolean; input: TInputSchema; output: TOutputSchema; handler?: BivariantCallback< @@ -247,6 +257,7 @@ export type CommandDefinition< /** Internal structural constraint used to store any query definition in a record. */ export type AnyQueryDefinition = { description?: string; + internal?: boolean; input: AnySchema; output: AnySchema; staticPath?: BivariantCallback<[input: unknown], string>; @@ -258,6 +269,7 @@ export type AnyQueryDefinition = { /** Internal structural constraint used to store any command definition in a record. */ export type AnyCommandDefinition = { description?: string; + internal?: boolean; input: AnySchema; output: AnySchema; handler?: BivariantCallback< @@ -279,6 +291,11 @@ export type ServiceDefinition< > = { id: ServiceId; description?: string; + /** + * When true, hides this service from `listServices()` output. Defaults to false. Does not disable + * the service at runtime — callers can still resolve it through `getService()`. + */ + internal?: boolean; initialState: TState; queries: TQueries; commands: TCommands; From 121eacbfb53203a67c0f75bab7c51e90b29f8f81 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 4 Jun 2026 21:53:13 +0200 Subject: [PATCH 2/3] Fix internal operation type inference Co-authored-by: Cursor --- .../shared/open-service/service-definition.ts | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/code/core/src/shared/open-service/service-definition.ts b/code/core/src/shared/open-service/service-definition.ts index 7df18e28ffcc..d83daddc3b7d 100644 --- a/code/core/src/shared/open-service/service-definition.ts +++ b/code/core/src/shared/open-service/service-definition.ts @@ -15,20 +15,11 @@ type InvalidUnderscoreWithoutInternal = { __internal_naming_error: `Operation "${TName}" is prefixed with "_" and must set internal: true`; }; -/** - * Ensures internal operations use a `_` prefix and that `_`-prefixed operations set `internal: true`. - */ -type EnforceInternalOperationNaming> = { - [K in keyof T]: T[K] extends infer TOp - ? K extends `_${string}` - ? TOp extends { internal: true } - ? TOp - : InvalidUnderscoreWithoutInternal - : TOp extends { internal: true } - ? InvalidInternalOperationName - : TOp - : never; -}; +type InternalOperationNaming = TKey extends string + ? TKey extends `_${string}` + ? { internal: true } | InvalidUnderscoreWithoutInternal + : { internal?: false } | InvalidInternalOperationName + : {}; /** * Authoring-side query map derived from separate query input/output schema maps. @@ -52,7 +43,8 @@ type DefinedQueries< TQueryOutputSchemas[TKey], TCommandInputSchemas, TCommandOutputSchemas - >; + > & + InternalOperationNaming; } & { [TKey in keyof TQueryOutputSchemas]: { output: TQueryOutputSchemas[TKey]; @@ -75,7 +67,8 @@ type DefinedCommands< TState, TCommandInputSchemas[TKey], TCommandOutputSchemas[TKey] - >; + > & + InternalOperationNaming; } & { [TKey in keyof TCommandOutputSchemas]: { output: TCommandOutputSchemas[TKey]; @@ -89,9 +82,6 @@ type DefinedCommands< * concrete query/command definition maps from those schemas. If we instead ask TypeScript to infer * the full runtime `ServiceDefinition` maps directly, it widens callback parameters to `unknown` * before it has correlated each inline object's `input` and `output` properties. - * - * Query and command maps are also inferred as separate const type parameters so internal naming - * validation can inspect literal `internal: true` flags without losing handler inference. */ export const defineService = < TState, @@ -99,23 +89,27 @@ export const defineService = < const TQueryOutputSchemas extends MatchingOutputSchemas, const TCommandInputSchemas extends OperationInputSchemas, const TCommandOutputSchemas extends MatchingOutputSchemas, - const TQueries extends DefinedQueries< +>(def: { + id: ServiceId; + description?: string; + internal?: boolean; + initialState: TState; + queries: DefinedQueries< + TState, + TQueryInputSchemas, + TQueryOutputSchemas, + TCommandInputSchemas, + TCommandOutputSchemas + >; + commands: DefinedCommands; +}): ServiceDefinition< + TState, + DefinedQueries< TState, TQueryInputSchemas, TQueryOutputSchemas, TCommandInputSchemas, TCommandOutputSchemas >, - const TCommands extends DefinedCommands, ->(def: { - id: ServiceId; - description?: string; - /** - * When true, hides this service from `listServices()` output. Defaults to false. Does not disable - * the service at runtime. - */ - internal?: boolean; - initialState: TState; - queries: TQueries & EnforceInternalOperationNaming; - commands: TCommands & EnforceInternalOperationNaming; -}): ServiceDefinition => def; + DefinedCommands +> => def; From 1f19c674a033102ee5581efbc7eeb0d99b39fe3f Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 5 Jun 2026 14:48:50 +0200 Subject: [PATCH 3/3] Fix static build fixture state typing Use explicit state casts so registration handlers can assign string values to nullable state fields. Co-authored-by: Cursor --- code/core/src/shared/open-service/fixtures.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/shared/open-service/fixtures.ts b/code/core/src/shared/open-service/fixtures.ts index 0c8a3292d376..1937dc2efccd 100644 --- a/code/core/src/shared/open-service/fixtures.ts +++ b/code/core/src/shared/open-service/fixtures.ts @@ -329,7 +329,7 @@ export type RegistrationOnlyStaticBuildState = { value: string | null }; export const registrationOnlyStaticBuildServiceDef = defineService({ id: 'internal-fixture/registration-only-static-build', description: 'Declares staticPath in the definition and load at registration.', - initialState: { value: null } satisfies RegistrationOnlyStaticBuildState, + initialState: { value: null } as RegistrationOnlyStaticBuildState, queries: { getValue: { description: 'Returns one statically built value.', @@ -421,7 +421,7 @@ export type InternalStaticBuildState = { value: string | null }; export const internalStaticBuildServiceDef = defineService({ id: 'internal-fixture/internal-static-build', description: 'Internal query participates in static builds.', - initialState: { value: null } satisfies InternalStaticBuildState, + initialState: { value: null } as InternalStaticBuildState, queries: { _getValue: { internal: true,