-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathremote.ts
More file actions
86 lines (79 loc) · 3.79 KB
/
Copy pathremote.ts
File metadata and controls
86 lines (79 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/** Test-owned Remote face: `$on` subscriptions with an explicit test event driver. */
import type { Context } from '@deepseek-ai/cordis'
// Value re-export for spec-side failure construction: the api-remotes facade
// cannot carry it — its src top-level imports owner /remote lib artifacts, so a
// value import from a spec would load the unbuilt assembly chain.
export { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
/**
* Remote service test double for the forwarded-event path. Feature specs need
* `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
* Host events to reach those subscribers, but not the wire — so this double
* implements subscription plus an explicit `emit` driver available only on the
* concrete test object. A spec that also calls one namespace scripts it through
* the constructor rather than reaching the real Client Remote service.
*
* `$mount` rejects: a spec that needs a real generated contribution installed —
* codecs, descriptors, and the wire — has outgrown this double and needs the
* real Client Remote service.
*
* One deliberate asymmetry with production: a throwing listener propagates out
* of the emit instead of being contained and logged, so a spec cannot lean on
* this double for the containment guarantee `$on` documents — assert that
* against the real service.
*/
export class TestRemote {
private readonly subscriptions = new Map<string, Set<(...args: never[]) => void>>()
/**
* Fixed Host facts mirrored from the production `ctx.remote.$host`. Plain
* mutable field: a spec assigns it to script a non-loopback or homed Host.
*/
$host: { home: string | undefined; isLoopback: boolean } = { home: undefined, isLoopback: true }
/**
* Register the double as `ctx.remote`, plus one service per scripted
* namespace so a plugin injecting `remote.<name>` also unparks.
* @param ctx - the spec's root Context.
* @param namespaces - scripted namespace faces reached as `ctx.remote.<name>`.
*/
constructor(ctx: Context, namespaces: Readonly<Record<string, object>> = {}) {
for (const name of Object.keys(namespaces)) {
// A namespace named after one of the double's own members would replace
// it, and `$mount`'s rejection is the contract a spec relies on.
if (name in TestRemote.prototype || name === 'subscriptions' || name === '$host') {
throw new TypeError(`TestRemote: scripted namespace "${name}" would shadow the double's own member`)
}
}
Object.assign(this, namespaces)
ctx.provide('remote', this)
for (const [name, face] of Object.entries(namespaces)) ctx.provide(`remote.${name}`, face)
}
/**
* Deliver one forwarded host event to its subscribers, standing in for the
* carrier that owns the frame sink.
* @param event - forwarded host event name.
* @param args - the Host argument list, verbatim.
*/
emit(event: string, args: readonly unknown[]): void {
const listeners = this.subscriptions.get(event)
if (listeners === undefined) return
for (const listener of [...listeners]) listener(...args as never[])
}
/**
* Subscribe to one forwarded host event.
* @param event - forwarded host event name.
* @param listener - receives the Host argument list verbatim.
* @returns disposer removing this subscription.
*/
$on(event: string, listener: (...args: never[]) => void): () => void {
const listeners = this.subscriptions.get(event) ?? new Set()
this.subscriptions.set(event, listeners)
listeners.add(listener)
return () => { listeners.delete(listener) }
}
/**
* Generated-namespace mount, unsupported by this double.
* @returns never; always rejects.
*/
$mount(): Promise<() => Promise<void>> {
return Promise.reject(new Error('TestRemote: $mount needs the real Client Remote service'))
}
}