refactor(api): expose traced remote namespaces
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Context } from 'cordis'
|
||||
import { Context, Service } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
||||
@@ -38,7 +38,7 @@ declare module '@deepseek-ai/dsh-type-meta' {
|
||||
|
||||
}
|
||||
|
||||
type FixtureContext = Context & TypeRTRemoteContextApi<'fixture'>
|
||||
type FixtureContext = Omit<Context, 'remote'> & { readonly remote: TypeRTRemoteContextApi<'fixture'> }
|
||||
|
||||
const idSchema = z.string().min(1)
|
||||
const requestSchema = z.object({ objective: z.string().min(1) })
|
||||
@@ -105,17 +105,16 @@ describe('Client TypeRT API', () => {
|
||||
const call = vi.fn<ConnectionHandle['rpc']['call']>()
|
||||
.mockResolvedValue({ ok: true, value: { ref: 'goal-1' } })
|
||||
const ctx = await bench(call)
|
||||
let retained: typeof ctx.api.goals.create | undefined
|
||||
const businessGoals = { owner: 'host business service' }
|
||||
const disposeBusinessGoals = ctx.provide('goals', businessGoals)
|
||||
const assembly = ctx.plugin(Object.assign(
|
||||
(scope: Context) => {
|
||||
scope.api.mount({ package: '@fixture/goals', descriptors: [directDescriptor()] })
|
||||
retained = scope.api.goals.create
|
||||
},
|
||||
{ inject: ['api'] },
|
||||
(scope: Context) => scope.remote.$mount({ package: '@fixture/goals', descriptors: [directDescriptor()] }),
|
||||
{ inject: ['remote'] },
|
||||
))
|
||||
await assembly
|
||||
const retained = ctx.remote.goals.create
|
||||
|
||||
await expect(ctx.api.goals.create('agent-1', { objective: 'ship' })).resolves.toEqual({ ref: 'goal-1' })
|
||||
await expect(ctx.remote.goals.create('agent-1', { objective: 'ship' })).resolves.toEqual({ ref: 'goal-1' })
|
||||
expect(call).toHaveBeenCalledWith(
|
||||
'/api',
|
||||
'goals/create',
|
||||
@@ -123,7 +122,7 @@ describe('Client TypeRT API', () => {
|
||||
expect.any(AbortSignal),
|
||||
)
|
||||
const callerAbort = new AbortController()
|
||||
await expect(ctx.api.goals.create(
|
||||
await expect(ctx.remote.goals.create(
|
||||
'agent-1',
|
||||
{ objective: 'cancel me' },
|
||||
callerAbort.signal,
|
||||
@@ -135,16 +134,18 @@ describe('Client TypeRT API', () => {
|
||||
callerAbort.abort(cancellation)
|
||||
expect(combinedSignal?.aborted).toBe(true)
|
||||
expect(combinedSignal?.reason).toBe(cancellation)
|
||||
await expect(ctx.api.goals.create('', { objective: 'ship' })).rejects.toThrow('rejected "agentId"')
|
||||
await expect(ctx.remote.goals.create('', { objective: 'ship' })).rejects.toThrow('rejected "agentId"')
|
||||
|
||||
call.mockResolvedValueOnce({ ok: true, value: { ref: 1 } })
|
||||
await expect(ctx.api.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('rejected "result"')
|
||||
await expect(ctx.remote.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('rejected "result"')
|
||||
|
||||
await assembly.dispose()
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
expect((ctx.remote as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect(ctx.get('remote.goals')).toBeUndefined()
|
||||
expect(ctx.get('goals')).toBe(businessGoals)
|
||||
expect(ctx.typert.remotes.list()).toEqual([])
|
||||
await expect(retained?.('agent-1', { objective: 'ship' })).rejects.toThrow('no longer mounted')
|
||||
disposeBusinessGoals()
|
||||
})
|
||||
|
||||
it('projects one direct lookup descriptor onto an Agent-scoped alias', async () => {
|
||||
@@ -156,26 +157,24 @@ describe('Client TypeRT API', () => {
|
||||
identity: candidate => (candidate as Context & { fixtureId?: string }).fixtureId,
|
||||
})
|
||||
const assembly = ctx.plugin(Object.assign(
|
||||
(scope: Context) => {
|
||||
scope.api.mount({ package: '@fixture/goals', descriptors: [directDescriptor()] })
|
||||
},
|
||||
{ inject: ['api'] },
|
||||
(scope: Context) => scope.remote.$mount({ package: '@fixture/goals', descriptors: [directDescriptor()] }),
|
||||
{ inject: ['remote'] },
|
||||
))
|
||||
await assembly
|
||||
|
||||
await expect(agentCtx.goals.create({ objective: 'ship scoped' })).resolves.toEqual({ ref: 'goal-2' })
|
||||
await expect(agentCtx.remote.goals.create({ objective: 'ship scoped' })).resolves.toEqual({ ref: 'goal-2' })
|
||||
expect(call).toHaveBeenCalledWith(
|
||||
'/api',
|
||||
'goals/create',
|
||||
{ args: { agentId: 'agent-2', request: { objective: 'ship scoped' } } },
|
||||
expect.any(AbortSignal),
|
||||
)
|
||||
await expect((ctx as FixtureContext).goals.create({ objective: 'wrong scope' }))
|
||||
.rejects.toThrow('requires a "fixture" Context')
|
||||
await expect((ctx as FixtureContext).remote.goals.create({ objective: 'wrong scope' }))
|
||||
.rejects.toThrow('expected 2 business argument(s)')
|
||||
|
||||
await assembly.dispose()
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
expect((ctx.remote as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect(ctx.get('remote.goals')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('uses the caller Context identity for scoped namespace methods', async () => {
|
||||
@@ -187,25 +186,23 @@ describe('Client TypeRT API', () => {
|
||||
identity: candidate => (candidate as Context & { fixtureId?: string }).fixtureId,
|
||||
})
|
||||
const assembly = ctx.plugin(Object.assign(
|
||||
(scope: Context) => {
|
||||
scope.api.mount({ package: '@fixture/goals', descriptors: [contextDescriptor()] })
|
||||
},
|
||||
{ inject: ['api'] },
|
||||
(scope: Context) => scope.remote.$mount({ package: '@fixture/goals', descriptors: [contextDescriptor()] }),
|
||||
{ inject: ['remote'] },
|
||||
))
|
||||
await assembly
|
||||
|
||||
await expect(agentCtx.goals.rename({ objective: 'land' })).resolves.toEqual({ renamed: true })
|
||||
await expect(agentCtx.remote.goals.rename({ objective: 'land' })).resolves.toEqual({ renamed: true })
|
||||
expect(call).toHaveBeenCalledWith(
|
||||
'/api',
|
||||
'goals/rename',
|
||||
{ args: { agentId: 'agent-2', request: { objective: 'land' } } },
|
||||
expect.any(AbortSignal),
|
||||
)
|
||||
await expect((ctx as FixtureContext).goals.rename({ objective: 'land' }))
|
||||
await expect((ctx as FixtureContext).remote.goals.rename({ objective: 'land' }))
|
||||
.rejects.toThrow('requires a "fixture" Context')
|
||||
|
||||
await assembly.dispose()
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
expect(ctx.get('remote.goals')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects weak descriptors and namespace collisions before registration', async () => {
|
||||
@@ -215,12 +212,12 @@ describe('Client TypeRT API', () => {
|
||||
result: { mode: 'src-json' },
|
||||
}
|
||||
|
||||
expect(() => ctx.api.mount({ package: '@fixture/weak', descriptors: [weak] }))
|
||||
.toThrow('has no strict codec')
|
||||
expect(() => ctx.api.mount({
|
||||
await expect(ctx.remote.$mount({ package: '@fixture/weak', descriptors: [weak] }))
|
||||
.rejects.toThrow('has no strict codec')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/conflict',
|
||||
descriptors: [{ ...directDescriptor(), namespace: 'mount' }],
|
||||
})).toThrow('conflicts with the API service')
|
||||
descriptors: [{ ...directDescriptor(), namespace: '$mount' }],
|
||||
})).rejects.toThrow('conflicts with the Remote service')
|
||||
expect(ctx.typert.remotes.list()).toEqual([])
|
||||
})
|
||||
|
||||
@@ -235,48 +232,50 @@ describe('Client TypeRT API', () => {
|
||||
const direct = directDescriptor()
|
||||
const context = contextDescriptor()
|
||||
|
||||
expect(() => ctx.api.mount({
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/direct-duplicates',
|
||||
descriptors: [direct, { ...direct, id: '@fixture/goals#goals/create-again' }],
|
||||
})).toThrow('repeats direct method')
|
||||
expect(() => ctx.api.mount({
|
||||
})).rejects.toThrow('repeats direct method')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/scoped-duplicates',
|
||||
descriptors: [context, { ...context, id: '@fixture/goals#goals/rename-again' }],
|
||||
})).toThrow('repeats scoped method')
|
||||
})).rejects.toThrow('repeats scoped method')
|
||||
|
||||
const disposeDirect = ctx.api.mount({ package: '@fixture/direct-live', descriptors: [direct] })
|
||||
expect(() => ctx.api.mount({
|
||||
const disposeDirect = await ctx.remote.$mount({ package: '@fixture/direct-live', descriptors: [direct] })
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/direct-conflict', descriptors: [{ ...direct, id: '@fixture/other#goals/create' }],
|
||||
})).toThrow('direct method goals/create is already mounted')
|
||||
})).rejects.toThrow('direct method goals/create is already mounted')
|
||||
await disposeDirect()
|
||||
|
||||
const disposeScoped = ctx.api.mount({ package: '@fixture/scoped-live', descriptors: [context] })
|
||||
expect(() => ctx.api.mount({
|
||||
const disposeScoped = await ctx.remote.$mount({ package: '@fixture/scoped-live', descriptors: [context] })
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/scoped-conflict', descriptors: [{ ...context, id: '@fixture/other#goals/rename' }],
|
||||
})).toThrow('scoped method goals/rename is already mounted')
|
||||
expect(() => ctx.api.mount({
|
||||
})).rejects.toThrow('scoped method goals/rename is already mounted')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/service-method-conflict',
|
||||
descriptors: [{ ...context, id: '@fixture/goals#goals/remove', method: 'remove' }],
|
||||
})).toThrow('conflicts with its namespace service')
|
||||
const scopedService = ctx.get('goals') as unknown as object
|
||||
})).rejects.toThrow('conflicts with its namespace service')
|
||||
const scopedService = ctx.get('remote.goals') as unknown as object
|
||||
Object.defineProperty(scopedService, 'custom', { configurable: true, value: () => undefined })
|
||||
expect(() => ctx.api.mount({
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/service-own-property-conflict',
|
||||
descriptors: [{ ...direct, id: '@fixture/goals#goals/custom', method: 'custom' }],
|
||||
})).toThrow('conflicts with its namespace service')
|
||||
})).rejects.toThrow('conflicts with its namespace service')
|
||||
Reflect.deleteProperty(scopedService, 'custom')
|
||||
await disposeScoped()
|
||||
|
||||
expect(() => ctx.api.mount({
|
||||
const disposeRemoteTypert = ctx.reflect.provide('remote.typert', { owner: 'fixture' })
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/context-property-conflict',
|
||||
descriptors: [{ ...context, namespace: 'typert' }],
|
||||
})).toThrow('conflicts with an existing Context property')
|
||||
})).rejects.toThrow('conflicts with an existing Remote namespace')
|
||||
await disposeRemoteTypert()
|
||||
|
||||
const disposeMultipleScoped = ctx.api.mount({
|
||||
const disposeMultipleScoped = await ctx.remote.$mount({
|
||||
package: '@fixture/multiple-scoped',
|
||||
descriptors: [directDescriptor(), contextDescriptor()],
|
||||
})
|
||||
await expect(agentCtx.goals.rename({ objective: 'remounted' })).resolves.toEqual({ renamed: true })
|
||||
await expect(agentCtx.remote.goals.rename({ objective: 'remounted' })).resolves.toEqual({ renamed: true })
|
||||
expect(call).toHaveBeenLastCalledWith(
|
||||
'/api',
|
||||
'goals/rename',
|
||||
@@ -286,41 +285,6 @@ describe('Client TypeRT API', () => {
|
||||
await disposeMultipleScoped()
|
||||
})
|
||||
|
||||
it('rolls back direct projection when scoped installation fails', async () => {
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
|
||||
const disposeScoped = ctx.api.mount({
|
||||
package: '@fixture/scoped-base',
|
||||
descriptors: [contextDescriptor()],
|
||||
})
|
||||
const defineProperty = Object.defineProperty
|
||||
let createDefinitions = 0
|
||||
const definePropertySpy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => {
|
||||
// The direct projection defines `create` first; fail the following scoped projection.
|
||||
if (key === 'create' && ++createDefinitions === 2) throw new Error('simulated scoped installation failure')
|
||||
return defineProperty(target, key, attributes)
|
||||
})
|
||||
|
||||
try {
|
||||
expect(() => ctx.api.mount({
|
||||
package: '@fixture/failing-install',
|
||||
descriptors: [directDescriptor()],
|
||||
})).toThrow('simulated scoped installation failure')
|
||||
} finally {
|
||||
definePropertySpy.mockRestore()
|
||||
}
|
||||
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect(ctx.get('goals') !== undefined).toBe(true)
|
||||
expect(ctx.typert.remotes.list()).toHaveLength(1)
|
||||
|
||||
const disposeRetry = ctx.api.mount({
|
||||
package: '@fixture/retry',
|
||||
descriptors: [directDescriptor()],
|
||||
})
|
||||
await disposeRetry()
|
||||
await disposeScoped()
|
||||
})
|
||||
|
||||
it('rolls back earlier descriptors when a later descriptor fails to install', async () => {
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
|
||||
const { scope: _scope, ...first } = directDescriptor()
|
||||
@@ -335,17 +299,17 @@ describe('Client TypeRT API', () => {
|
||||
return defineProperty(target, key, attributes)
|
||||
})
|
||||
try {
|
||||
expect(() => ctx.api.mount({ package: '@fixture/failing-batch', descriptors: [first, second] }))
|
||||
.toThrow('fixture later-descriptor failure')
|
||||
await expect(ctx.remote.$mount({ package: '@fixture/failing-batch', descriptors: [first, second] }))
|
||||
.rejects.toThrow('fixture later-descriptor failure')
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect((ctx.remote as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
|
||||
const retry = ctx.api.mount({ package: '@fixture/retry-batch', descriptors: [first, second] })
|
||||
expect(ctx.api.goals.create).toBeTypeOf('function')
|
||||
expect((ctx.api.goals as unknown as Record<string, unknown>).archive).toBeTypeOf('function')
|
||||
const retry = await ctx.remote.$mount({ package: '@fixture/retry-batch', descriptors: [first, second] })
|
||||
expect(ctx.remote.goals.create).toBeTypeOf('function')
|
||||
expect((ctx.remote.goals as unknown as Record<string, unknown>).archive).toBeTypeOf('function')
|
||||
await retry()
|
||||
})
|
||||
|
||||
@@ -353,7 +317,7 @@ describe('Client TypeRT API', () => {
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
|
||||
const direct = directDescriptor()
|
||||
const context = contextDescriptor()
|
||||
expect(() => ctx.api.mount({
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/weak-parameter',
|
||||
descriptors: [{
|
||||
...direct,
|
||||
@@ -361,19 +325,19 @@ describe('Client TypeRT API', () => {
|
||||
? { ...parameter, codec: { mode: 'src-json' } }
|
||||
: parameter),
|
||||
}],
|
||||
})).toThrow('has no strict codec')
|
||||
expect(() => ctx.api.mount({
|
||||
})).rejects.toThrow('has no strict codec')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/weak-context',
|
||||
descriptors: [{
|
||||
...context,
|
||||
invocation: { ...context.invocation, codec: { mode: 'src-json' } },
|
||||
} as InvocationDescriptor],
|
||||
})).toThrow('has no strict codec')
|
||||
expect(() => ctx.api.mount({
|
||||
})).rejects.toThrow('has no strict codec')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/malformed-scope',
|
||||
descriptors: [{ ...direct, scope: { context: 'fixture', wire: 'missingId' } }],
|
||||
})).toThrow('scope must select its only lookup parameter')
|
||||
expect(() => ctx.api.mount({
|
||||
})).rejects.toThrow('scope must select its only lookup parameter')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/ambiguous-scope',
|
||||
descriptors: [{
|
||||
...direct,
|
||||
@@ -382,7 +346,7 @@ describe('Client TypeRT API', () => {
|
||||
codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', schema: idSchema },
|
||||
}],
|
||||
}],
|
||||
})).toThrow('scope must select its only lookup parameter')
|
||||
})).rejects.toThrow('scope must select its only lookup parameter')
|
||||
})
|
||||
|
||||
it('validates invocation arity, required binders, live Connection, and mutable descriptor codecs', async () => {
|
||||
@@ -390,27 +354,29 @@ describe('Client TypeRT API', () => {
|
||||
.mockResolvedValue({ ok: true, value: { ref: 'goal-1' } })
|
||||
const ctx = await bench(call)
|
||||
const descriptor = directDescriptor()
|
||||
const dispose = ctx.api.mount({
|
||||
const dispose = await ctx.remote.$mount({
|
||||
package: '@fixture/goals',
|
||||
descriptors: [descriptor, contextDescriptor()],
|
||||
})
|
||||
const create = ctx.api.goals.create as unknown as (...args: unknown[]) => Promise<unknown>
|
||||
const goals = (ctx as FixtureContext).goals
|
||||
const create = ctx.remote.goals.create as unknown as (...args: unknown[]) => Promise<unknown>
|
||||
const goals = (ctx as FixtureContext).remote.goals
|
||||
const rename = goals.rename as unknown as (...args: unknown[]) => Promise<unknown>
|
||||
|
||||
await expect(create('agent-1')).rejects.toThrow('expected 2 business argument(s) plus an optional AbortSignal, got 1')
|
||||
await expect(create('agent-1', { objective: 'ship' }, undefined, 'extra'))
|
||||
.rejects.toThrow('got 4')
|
||||
await expect(rename.call(goals)).rejects.toThrow('expected 1 argument(s), got 0')
|
||||
await expect((ctx as FixtureContext).goals.create({ objective: 'ship' }))
|
||||
await expect((ctx as FixtureContext).remote.goals.create({ objective: 'ship' }))
|
||||
.rejects.toThrow('expected 2 business argument(s)')
|
||||
await expect((ctx as FixtureContext).remote.goals.rename({ objective: 'ship' }))
|
||||
.rejects.toThrow('no Client Context binder')
|
||||
|
||||
;(descriptor.parameters[0] as { codec: { mode: string } }).codec.mode = 'src-json'
|
||||
await expect(ctx.api.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('has no strict codec')
|
||||
await expect(ctx.remote.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('has no strict codec')
|
||||
;(descriptor.parameters[0] as { codec: { mode: string } }).codec.mode = 'strict'
|
||||
|
||||
ctx.set('connection', undefined)
|
||||
await expect(ctx.api.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('no active Connection')
|
||||
await expect(ctx.remote.goals.create('agent-1', { objective: 'ship' })).rejects.toThrow('no active Connection')
|
||||
await dispose()
|
||||
})
|
||||
|
||||
@@ -427,14 +393,14 @@ describe('Client TypeRT API', () => {
|
||||
id: '@fixture/goals#goals/archive',
|
||||
method: 'archive',
|
||||
}
|
||||
const dispose = ctx.api.mount({ package: '@fixture/goals', descriptors: [first, second] })
|
||||
const invocation = ctx.api.goals.create('agent-1', { objective: 'ship' })
|
||||
const dispose = await ctx.remote.$mount({ package: '@fixture/goals', descriptors: [first, second] })
|
||||
const invocation = ctx.remote.goals.create('agent-1', { objective: 'ship' })
|
||||
await vi.waitFor(() => { expect(call).toHaveBeenCalledTimes(1) })
|
||||
await dispose()
|
||||
resolveCall({ ok: true, value: { ref: 'goal-1' } })
|
||||
|
||||
await expect(invocation).rejects.toThrow('withdrawn during invocation')
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect((ctx.remote as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
})
|
||||
|
||||
it('preserves a __proto__ wire parameter as an own named argument', async () => {
|
||||
@@ -453,9 +419,9 @@ describe('Client TypeRT API', () => {
|
||||
codec: { mode: 'strict', typeSymbol: '@fixture#PrototypeValue', schema: z.string() },
|
||||
}],
|
||||
}
|
||||
const dispose = ctx.api.mount({ package: '@fixture/prototype', descriptors: [descriptor] })
|
||||
const dispose = await ctx.remote.$mount({ package: '@fixture/prototype', descriptors: [descriptor] })
|
||||
|
||||
const method = (ctx.api.goals as unknown as Record<string, (...args: unknown[]) => Promise<unknown>>).prototype
|
||||
const method = (ctx.remote.goals as unknown as Record<string, (...args: unknown[]) => Promise<unknown>>).prototype
|
||||
await expect(method?.('wire-value')).resolves.toEqual({ ref: 'goal-1' })
|
||||
const payload = call.mock.calls[0]?.[2] as { readonly args: Record<string, unknown> }
|
||||
expect(Object.getPrototypeOf(payload.args)).toBeNull()
|
||||
@@ -464,23 +430,23 @@ describe('Client TypeRT API', () => {
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('rolls back Remote registration when concrete method installation fails', async () => {
|
||||
it('rolls back Remote registration when namespace Service startup fails', async () => {
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
|
||||
const defineProperty = Object.defineProperty
|
||||
const spy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => {
|
||||
if (key === 'goals') throw new Error('fixture installation failure')
|
||||
if (key === Service.tracker) throw new Error('fixture namespace startup failure')
|
||||
return defineProperty(target, key, attributes)
|
||||
})
|
||||
try {
|
||||
expect(() => ctx.api.mount({ package: '@fixture/goals', descriptors: [directDescriptor()] }))
|
||||
.toThrow('fixture installation failure')
|
||||
await expect(ctx.remote.$mount({ package: '@fixture/goals', descriptors: [directDescriptor()] }))
|
||||
.rejects.toThrow('fixture namespace startup failure')
|
||||
await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
|
||||
const retry = ctx.api.mount({ package: '@fixture/goals-retry', descriptors: [directDescriptor()] })
|
||||
expect(ctx.api.goals.create).toBeTypeOf('function')
|
||||
const retry = await ctx.remote.$mount({ package: '@fixture/goals-retry', descriptors: [directDescriptor()] })
|
||||
expect(ctx.remote.goals.create).toBeTypeOf('function')
|
||||
await retry()
|
||||
})
|
||||
|
||||
@@ -492,16 +458,21 @@ describe('Client TypeRT API', () => {
|
||||
return defineProperty(target, key, attributes)
|
||||
})
|
||||
try {
|
||||
expect(() => ctx.api.mount({ package: '@fixture/direct-method-failure', descriptors: [directDescriptor()] }))
|
||||
.toThrow('fixture direct method installation failure')
|
||||
await expect(ctx.remote.$mount({
|
||||
package: '@fixture/direct-method-failure',
|
||||
descriptors: [directDescriptor()],
|
||||
})).rejects.toThrow('fixture direct method installation failure')
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
|
||||
expect((ctx.api as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
expect((ctx.remote as unknown as Record<string, unknown>).goals).toBeUndefined()
|
||||
await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
|
||||
const retry = ctx.api.mount({ package: '@fixture/direct-method-retry', descriptors: [directDescriptor()] })
|
||||
expect(ctx.api.goals.create).toBeTypeOf('function')
|
||||
const retry = await ctx.remote.$mount({
|
||||
package: '@fixture/direct-method-retry',
|
||||
descriptors: [directDescriptor()],
|
||||
})
|
||||
expect(ctx.remote.goals.create).toBeTypeOf('function')
|
||||
await retry()
|
||||
})
|
||||
|
||||
@@ -513,41 +484,41 @@ describe('Client TypeRT API', () => {
|
||||
return defineProperty(target, key, attributes)
|
||||
})
|
||||
try {
|
||||
expect(() => ctx.api.mount({ package: '@fixture/scoped-failure', descriptors: [contextDescriptor()] }))
|
||||
.toThrow('fixture scoped installation failure')
|
||||
await expect(ctx.remote.$mount({ package: '@fixture/scoped-failure', descriptors: [contextDescriptor()] }))
|
||||
.rejects.toThrow('fixture scoped installation failure')
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
expect(ctx.get('remote.goals')).toBeUndefined()
|
||||
await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
|
||||
const retry = ctx.api.mount({ package: '@fixture/scoped-retry', descriptors: [contextDescriptor()] })
|
||||
expect((ctx.get('goals') as unknown as Record<string, unknown>).rename).toBeTypeOf('function')
|
||||
const retry = await ctx.remote.$mount({ package: '@fixture/scoped-retry', descriptors: [contextDescriptor()] })
|
||||
expect((ctx.get('remote.goals') as unknown as Record<string, unknown>).rename).toBeTypeOf('function')
|
||||
await retry()
|
||||
})
|
||||
|
||||
it('unregisters an empty scoped namespace so another provider can claim its name', async () => {
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
|
||||
const dispose = ctx.api.mount({ package: '@fixture/scoped', descriptors: [contextDescriptor()] })
|
||||
expect(ctx.get('goals')).toBeDefined()
|
||||
const dispose = await ctx.remote.$mount({ package: '@fixture/scoped', descriptors: [contextDescriptor()] })
|
||||
expect(ctx.get('remote.goals')).toBeDefined()
|
||||
|
||||
await dispose()
|
||||
|
||||
expect(ctx.get('goals')).toBeUndefined()
|
||||
expect(ctx.get('remote.goals')).toBeUndefined()
|
||||
const replacement = { owner: 'replacement' }
|
||||
const disposeReplacement = ctx.reflect.provide('goals', replacement)
|
||||
expect(ctx.get('goals')).toBe(replacement)
|
||||
const disposeReplacement = ctx.reflect.provide('remote.goals', replacement)
|
||||
expect(ctx.get('remote.goals')).toBe(replacement)
|
||||
await disposeReplacement()
|
||||
})
|
||||
|
||||
it('throws RPC failures with the structured error as its cause', async () => {
|
||||
const rpcError = { code: 'internal' as const, message: 'host failed', details: {} }
|
||||
const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>().mockResolvedValue({ ok: false, error: rpcError }))
|
||||
ctx.api.mount({ package: '@fixture/goals', descriptors: [directDescriptor()] })
|
||||
await ctx.remote.$mount({ package: '@fixture/goals', descriptors: [directDescriptor()] })
|
||||
|
||||
let failure: unknown
|
||||
try {
|
||||
await ctx.api.goals.create('agent-1', { objective: 'ship' })
|
||||
await ctx.remote.goals.create('agent-1', { objective: 'ship' })
|
||||
} catch (error) {
|
||||
failure = error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user