test: finish the Remote-result and picker-split test migrations
Every generated Remote method resolves to `RemoteResult<T>`, so the Gateway client spec asserts the ok and error branches instead of the unwrapped value and a throw, and the generator fixtures declare the wrapper in the consumer face they typecheck. The RPC-failure test splits into the Host error carried verbatim in the error branch plus a transport throw folded into it. The runtime client, ui-command and ui-plan benches answer the generated commands Remote through its result branches and provide the `remote.commands` namespace their plugins now inject; the ui-command bench also serves the `$on` the service subscribes on construction. The directory-picker chooser mounts a backend and its surface as a pair, so the real-Loader composition serves both surface packages and asserts each entry arrives and leaves with its backend.
This commit is contained in:
@@ -13,6 +13,16 @@ declare module '@deepseek-ai/dsh-type-meta' {
|
||||
export interface TypeRTRemoteMap {}
|
||||
export interface TypeRTRemoteScopeMap {}
|
||||
|
||||
export interface RemoteFailure {
|
||||
readonly code: string
|
||||
readonly message: string
|
||||
readonly details: object
|
||||
}
|
||||
|
||||
export type RemoteResult<T> =
|
||||
| { readonly ok: true; readonly value: T }
|
||||
| { readonly ok: false; readonly error: RemoteFailure }
|
||||
|
||||
export type TypeRTRemoteNamespace<Namespace extends string> = {
|
||||
[Endpoint in keyof TypeRTRemoteMap as Endpoint extends `${Namespace}/${infer Method}`
|
||||
? Method
|
||||
|
||||
@@ -114,15 +114,15 @@ describe('Remote model generation', { timeout: 60_000 }, () => {
|
||||
|
||||
expect(artifact?.js).toContain('invocations: [')
|
||||
expect(artifact?.remote?.dts).toContain(
|
||||
"'goals/create': (agentId: AgentId, request: CreateGoalRequest, signal?: AbortSignal) => Promise<CreateGoalResult>",
|
||||
"'goals/create': (agentId: AgentId, request: CreateGoalRequest, signal?: AbortSignal) => Promise<RemoteResult<CreateGoalResult>>",
|
||||
)
|
||||
expect(artifact?.remote?.dts).toContain('interface TypeRTRemoteNamespace$676f616c73 {\n create:')
|
||||
expect(artifact?.remote?.dts).toContain("'goals': TypeRTRemoteNamespace$676f616c73")
|
||||
expect(artifact?.remote?.dts).toContain(
|
||||
"'agent:goals/create': (request: CreateGoalRequest, signal?: AbortSignal) => Promise<CreateGoalResult>",
|
||||
"'agent:goals/create': (request: CreateGoalRequest, signal?: AbortSignal) => Promise<RemoteResult<CreateGoalResult>>",
|
||||
)
|
||||
expect(artifact?.remote?.dts).toContain(
|
||||
"'agent:goals/rename': (request: RenameGoalRequest) => Promise<RenameGoalResult>",
|
||||
"'agent:goals/rename': (request: RenameGoalRequest) => Promise<RemoteResult<RenameGoalResult>>",
|
||||
)
|
||||
|
||||
const remoteJs = artifact?.remote?.js
|
||||
@@ -172,13 +172,13 @@ export type {`,
|
||||
|
||||
const [artifact] = new WorkspaceTypertGenerator(root).generate()
|
||||
expect(artifact?.remote?.dts).toContain(
|
||||
"'goals/maybe': (value: string | undefined) => Promise<string | undefined>",
|
||||
"'goals/maybe': (value: string | undefined) => Promise<RemoteResult<string | undefined>>",
|
||||
)
|
||||
expect(artifact?.remote?.dts).toContain("'goals/clear': () => Promise<void>")
|
||||
expect(artifact?.remote?.dts).toContain("'goals/clear': () => Promise<RemoteResult<void>>")
|
||||
// An explicit `T | undefined` stays a required argument; only authored
|
||||
// optionality lets a consumer omit the field.
|
||||
expect(artifact?.remote?.dts).not.toContain('value?: string')
|
||||
expect(artifact?.remote?.dts).toContain("'goals/labelled': (id: string, label?: string) => Promise<string>")
|
||||
expect(artifact?.remote?.dts).toContain("'goals/labelled': (id: string, label?: string) => Promise<RemoteResult<string>>")
|
||||
|
||||
const remoteJs = artifact?.remote?.js
|
||||
if (remoteJs === undefined) throw new Error('undefined Remote fixture emitted no Host-for-Client JavaScript')
|
||||
@@ -256,7 +256,7 @@ export type GenericResult = {
|
||||
|
||||
const [artifact] = new WorkspaceTypertGenerator(root).generate()
|
||||
expect(artifact?.remote?.dts).toContain(
|
||||
"'goals/dispatch': (request: GenericRequest) => Promise<GenericResult>",
|
||||
"'goals/dispatch': (request: GenericRequest) => Promise<RemoteResult<GenericResult>>",
|
||||
)
|
||||
const remoteJs = artifact?.remote?.js
|
||||
if (remoteJs === undefined) throw new Error('generic Remote fixture emitted no Host-for-Client JavaScript')
|
||||
@@ -306,7 +306,7 @@ export interface BoxPayload {
|
||||
|
||||
const [artifact] = new WorkspaceTypertGenerator(root).generate()
|
||||
expect(artifact?.remote?.dts).toMatch(/import type \{ [^}]*Box[^}]*BoxPayload[^}]* \} from '@fixture\/remote\/types'/)
|
||||
expect(artifact?.remote?.dts).toContain('box: (request: Box<BoxPayload>) => Promise<Box<BoxPayload>>')
|
||||
expect(artifact?.remote?.dts).toContain('box: (request: Box<BoxPayload>) => Promise<RemoteResult<Box<BoxPayload>>>')
|
||||
assertRemoteConsumerTypechecks(artifact?.remote?.dts, artifact?.remote?.dtsMap, root)
|
||||
})
|
||||
|
||||
@@ -326,7 +326,7 @@ export interface BoxPayload {
|
||||
))
|
||||
|
||||
const [artifact] = new WorkspaceTypertGenerator(root).generate()
|
||||
expect(artifact?.remote?.dts).toContain("'create-goal': (request: CreateGoalRequest) => Promise<CreateGoalResult>")
|
||||
expect(artifact?.remote?.dts).toContain("'create-goal': (request: CreateGoalRequest) => Promise<RemoteResult<CreateGoalResult>>")
|
||||
assertRemoteConsumerTypechecks(artifact?.remote?.dts, artifact?.remote?.dtsMap, root)
|
||||
})
|
||||
|
||||
@@ -636,6 +636,7 @@ function assertRemoteConsumerTypechecks(
|
||||
const consumerSource = `
|
||||
import remote from '@fixture/remote/remote'
|
||||
import type {
|
||||
RemoteResult,
|
||||
TypeRTRemoteContribution,
|
||||
TypeRTRemoteScopeMap,
|
||||
TypeRTRemoteMap,
|
||||
@@ -647,12 +648,12 @@ const contribution: TypeRTRemoteContribution = remote
|
||||
declare const create: TypeRTRemoteMap['goals/create']
|
||||
declare const createScoped: TypeRTRemoteScopeMap['agent:goals/create']
|
||||
declare const rename: TypeRTRemoteScopeMap['agent:goals/rename']
|
||||
const created: Promise<CreateGoalResult> = create('agent-1', { title: 'ship' })
|
||||
const cancellable: Promise<CreateGoalResult> = create('agent-1', { title: 'ship' }, new AbortController().signal)
|
||||
const createdScoped: Promise<CreateGoalResult> = createScoped({ title: 'ship' })
|
||||
const renamed: Promise<RenameGoalResult> = rename({ ref: 'goal-1', title: 'land' })
|
||||
const created: Promise<RemoteResult<CreateGoalResult>> = create('agent-1', { title: 'ship' })
|
||||
const cancellable: Promise<RemoteResult<CreateGoalResult>> = create('agent-1', { title: 'ship' }, new AbortController().signal)
|
||||
const createdScoped: Promise<RemoteResult<CreateGoalResult>> = createScoped({ title: 'ship' })
|
||||
const renamed: Promise<RemoteResult<RenameGoalResult>> = rename({ ref: 'goal-1', title: 'land' })
|
||||
declare const ctx: { remote: TypeRTRemoteNamespaceMap }
|
||||
const navigated: Promise<CreateGoalResult> = ctx.remote.goals.create('agent-1', { title: 'navigate' })
|
||||
const navigated: Promise<RemoteResult<CreateGoalResult>> = ctx.remote.goals.create('agent-1', { title: 'navigate' })
|
||||
void contribution
|
||||
void created
|
||||
void cancellable
|
||||
|
||||
Reference in New Issue
Block a user