@deepseek-ai/dsh-remote-mock 0.1.6-alpha.2 → 0.1.7-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +5 -3
- package/README.zh.md +5 -3
- package/lib/index.js +165 -11
- package/lib/types/index.d.ts +2 -1
- package/lib/types/remote-mock.d.ts +9 -3
- package/lib/types/streams.d.ts +73 -1
- package/package.json +7 -6
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/test-support/remote-mock/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 7f76ce6217d7cc6f86923aa1ee4f7257ad30926e
|
|
6
|
+
README.zh.md: b79849fe3b23b5135feff7095468f65809b0d506
|
package/README.md
CHANGED
|
@@ -73,7 +73,9 @@ mock.streams.fail('session/follow', new Error('gone'))
|
|
|
73
73
|
await mock.streams.drained('session/follow')
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
A failed stream rejects the consumer's next read with the given `Error`. Consumer cancellation (the opening signal or an early iterator `return()`) aborts `StreamHandle.signal`, ends the iteration without throwing, and logs the stream as `cancelled`.
|
|
76
|
+
A failed stream rejects the consumer's next read with the given `Error`. Consumer cancellation (the opening signal or an early iterator `return()`) aborts `StreamHandle.signal`, ends the iteration without throwing, and logs the stream as `cancelled`. `StreamHandle.uplink` is what the script reads as the Client's uplink: through `rpc.open` it is the carrier's iterable; for a direct `mock.remote.<namespace>.<method>(...)` call it is fed by the returned handle, which is the `RemoteStreamHandle` a generated method returns (iterate it for the downlink, `send()` and `end()` feed the uplink, `dispose()` cancels the stream); `send()` rejects a non-lossless JSON item as the real handle does, and the uplink it owns closes once the stream settles, is cancelled, or the consumer leaves, so a later `send()` throws. The mock function is called with the method's own arguments only, so the uplink never appears in call assertions or logged args.
|
|
77
|
+
|
|
78
|
+
A fake that stands in for a generated stream method returns the `RemoteStreamHandle` the generated method does. `streamHandle(source)` types an `AsyncIterable` as that handle with inert `send`, `end`, and `dispose`; `streamMethod<M>(generator)` lifts an async generator function written for the method's arguments into the method's own signature, for `vi.fn<M>()` and `mockImplementation`.
|
|
77
79
|
|
|
78
80
|
### Connect a client
|
|
79
81
|
|
|
@@ -108,7 +110,7 @@ A failed stream rejects the consumer's next read with the given `Error`. Consume
|
|
|
108
110
|
| [`src/index.ts`](src/index.ts) | Public face re-exports |
|
|
109
111
|
| [`src/remote-mock.ts`](src/remote-mock.ts) | `RemoteMock`: default responses, native mocks, Connection dispatch, controlled streams and missing-response checks; `ok` |
|
|
110
112
|
| [`src/remote-proxy.ts`](src/remote-proxy.ts) | Namespace/method lookup and generated-map mock types |
|
|
111
|
-
| [`src/streams.ts`](src/streams.ts) | `frames` / `openStream` scripts and `MockStream` (handle + `AsyncIterable`) |
|
|
113
|
+
| [`src/streams.ts`](src/streams.ts) | `frames` / `openStream` scripts, `streamHandle` / `streamMethod` fake typing, and `MockStream` (handle + `AsyncIterable`) |
|
|
112
114
|
| [`src/log.ts`](src/log.ts) | Log store with the shared `seq` counter |
|
|
113
115
|
| — | No runtime invariant companion is published; this test-support library owns no production event stream or mutable process state, and its behavior is exercised by its package tests. |
|
|
114
116
|
|
|
@@ -130,7 +132,7 @@ None; this package neither assembles nor sends a provider request.
|
|
|
130
132
|
<a id="known-limitations-and-deferred-work"></a>
|
|
131
133
|
|
|
132
134
|
- **In-process carrier only** — `rpc` serves a Connection instance in the same realm; no HTTP or WebSocket carrier for browser-lane specs is provided.
|
|
133
|
-
- **Values cross by reference** — answers and
|
|
135
|
+
- **Values cross by reference** — answers and downlink items reach the client unserialized, so a non-JSON downlink value that the real wire would reject passes through unchanged; only the handle's `send()` applies the real uplink check.
|
|
134
136
|
- **Values are not checked** — a unary answer must be the result the caller reads (`{ ok, value }` or `{ ok: false, error }`); the mock passes it through unchanged and does not check those fields.
|
|
135
137
|
- **No payload matching** — rules match on endpoint only; discriminate on business arguments inside a handler.
|
|
136
138
|
- **Native stream overrides own their iterable** — an override returning its own iterable bypasses scripted-stream logs, `requests`, `opened`, `drained`, and `push` / `end` / `fail`; the caller also owns cancellation. Native call assertions still work. Use a registered stream script when a scenario needs those controls.
|
package/README.zh.md
CHANGED
|
@@ -73,7 +73,9 @@ mock.streams.fail('session/follow', new Error('gone'))
|
|
|
73
73
|
await mock.streams.drained('session/follow')
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
失败的流让消费方的下一次读取以给定的 `Error` reject。消费方取消(打开时的 signal 或 iterator 提前 `return()`)会中止 `StreamHandle.signal`、结束迭代而不抛错,并把该流记为 `cancelled
|
|
76
|
+
失败的流让消费方的下一次读取以给定的 `Error` reject。消费方取消(打开时的 signal 或 iterator 提前 `return()`)会中止 `StreamHandle.signal`、结束迭代而不抛错,并把该流记为 `cancelled`。`StreamHandle.uplink` 是脚本读到的 Client 上行:经 `rpc.open` 打开时它是载体传入的 iterable;直接调用 `mock.remote.<namespace>.<method>(...)` 时它由返回的句柄喂入——该句柄就是生成方法返回的 `RemoteStreamHandle`(迭代它读下行,`send()` 与 `end()` 喂上行,`dispose()` 取消流);`send()` 像真实句柄一样拒绝非无损 JSON 项,它拥有的上行在流结束、取消或消费方离开时关闭,之后的 `send()` 抛错。mock 函数只以方法自己的参数被调用,上行不会出现在调用断言或记录的 args 里。
|
|
77
|
+
|
|
78
|
+
替代生成流方法的假实现返回生成方法所返回的 `RemoteStreamHandle`。`streamHandle(source)` 把一个 `AsyncIterable` 标注为该句柄,其 `send`、`end`、`dispose` 为空操作;`streamMethod<M>(generator)` 把按方法参数编写的 async generator 函数提升为方法自己的签名,供 `vi.fn<M>()` 与 `mockImplementation` 使用。
|
|
77
79
|
|
|
78
80
|
### 接上客户端
|
|
79
81
|
|
|
@@ -108,7 +110,7 @@ await mock.streams.drained('session/follow')
|
|
|
108
110
|
| [`src/index.ts`](src/index.ts) | 公开面转出 |
|
|
109
111
|
| [`src/remote-mock.ts`](src/remote-mock.ts) | `RemoteMock`:默认响应、原生 mock、Connection 分发、受控流与缺失响应检查;`ok` |
|
|
110
112
|
| [`src/remote-proxy.ts`](src/remote-proxy.ts) | 命名空间/方法查找与生成映射的 mock 类型 |
|
|
111
|
-
| [`src/streams.ts`](src/streams.ts) | `frames` / `openStream`
|
|
113
|
+
| [`src/streams.ts`](src/streams.ts) | `frames` / `openStream` 脚本、`streamHandle` / `streamMethod` 假实现标注,以及 `MockStream`(句柄 + `AsyncIterable`) |
|
|
112
114
|
| [`src/log.ts`](src/log.ts) | 带共享 `seq` 计数器的日志 |
|
|
113
115
|
| — | 不发布运行时不变量伴生件;本测试支持库不拥有任何生产事件流或可变进程状态,其行为由本包测试覆盖。 |
|
|
114
116
|
|
|
@@ -130,7 +132,7 @@ await mock.streams.drained('session/follow')
|
|
|
130
132
|
<a id="known-limitations-and-deferred-work"></a>
|
|
131
133
|
|
|
132
134
|
- **仅进程内载体**——`rpc` 服务同一 realm 中的 Connection 实例;不提供给浏览器车道测试用的 HTTP 或 WebSocket 载体。
|
|
133
|
-
-
|
|
135
|
+
- **值按引用传递**——应答与下行项都未经序列化就到达客户端,真实线路会拒绝的非 JSON 下行值在这里原样通过;只有句柄的 `send()` 施加真实的上行校验。
|
|
134
136
|
- **不校验值**——一元应答必须是调用方读取的结果(`{ ok, value }` 或 `{ ok: false, error }`);mock 原样传递它,不检查这些字段。
|
|
135
137
|
- **不做 payload 匹配**——规则只按端点匹配;在 handler 内按业务参数判别。
|
|
136
138
|
- **原生流覆盖自行管理 iterable**——覆盖返回自有 iterable 时,不参与脚本流日志、`requests`、`opened`、`drained` 以及 `push` / `end` / `fail`;调用方也负责取消。原生调用断言仍然有效。需要这些控制能力的场景应使用已登记的流脚本。
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fn } from "@vitest/spy";
|
|
2
|
+
import { isRemoteUplinkItem } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
3
|
//#region lib/types/log.js
|
|
3
4
|
/** What a `RemoteMock` saw: answered calls, opened streams, and requests without a rule. */
|
|
4
5
|
/** The mock's log store. */
|
|
@@ -69,6 +70,30 @@ var MockLogStore = class {
|
|
|
69
70
|
//#region lib/types/streams.js
|
|
70
71
|
/** Stream scripts and the pushable, abort-aware stream a script drives. */
|
|
71
72
|
/**
|
|
73
|
+
* Type a fake stream method's result as the handle a generated Client method
|
|
74
|
+
* returns. The uplink members are inert: a fake built on it drives the downlink only.
|
|
75
|
+
* @param source - downlink items the fake yields.
|
|
76
|
+
* @returns a handle that iterates `source`.
|
|
77
|
+
*/
|
|
78
|
+
function streamHandle(source) {
|
|
79
|
+
return {
|
|
80
|
+
[Symbol.asyncIterator]: () => source[Symbol.asyncIterator](),
|
|
81
|
+
send: () => void 0,
|
|
82
|
+
end: () => void 0,
|
|
83
|
+
dispose: () => void 0
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Lift a fake stream method written as an async generator function into the
|
|
88
|
+
* signature of the generated method it stands in for.
|
|
89
|
+
* @param method - fake taking the method's arguments and yielding its downlink items.
|
|
90
|
+
* @returns the fake returning {@link streamHandle} of each call.
|
|
91
|
+
*/
|
|
92
|
+
function streamMethod(method) {
|
|
93
|
+
const lifted = (...args) => streamHandle(method(...args));
|
|
94
|
+
return lifted;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
72
97
|
* Script yielding `items`, then ending.
|
|
73
98
|
* @param items - items in order.
|
|
74
99
|
* @returns the script.
|
|
@@ -93,6 +118,8 @@ function openStream(initial = []) {
|
|
|
93
118
|
var MockStream = class {
|
|
94
119
|
record;
|
|
95
120
|
sourceSignal;
|
|
121
|
+
uplink;
|
|
122
|
+
release;
|
|
96
123
|
queue = [];
|
|
97
124
|
settled;
|
|
98
125
|
waiting;
|
|
@@ -110,10 +137,14 @@ var MockStream = class {
|
|
|
110
137
|
/**
|
|
111
138
|
* @param record - log entry this stream updates.
|
|
112
139
|
* @param sourceSignal - cancellation from the caller that opened the stream.
|
|
140
|
+
* @param uplink - the uplink the script reads.
|
|
141
|
+
* @param release - closes the uplink this stream owns; called once, when the stream settles, is cancelled, or the consumer leaves.
|
|
113
142
|
*/
|
|
114
|
-
constructor(record, sourceSignal) {
|
|
143
|
+
constructor(record, sourceSignal, uplink, release) {
|
|
115
144
|
this.record = record;
|
|
116
145
|
this.sourceSignal = sourceSignal;
|
|
146
|
+
this.uplink = uplink;
|
|
147
|
+
this.release = release;
|
|
117
148
|
if (sourceSignal.aborted) this.cancel(sourceSignal.reason);
|
|
118
149
|
else sourceSignal.addEventListener("abort", this.onAbort, { once: true });
|
|
119
150
|
}
|
|
@@ -140,6 +171,10 @@ var MockStream = class {
|
|
|
140
171
|
error
|
|
141
172
|
}, "failed");
|
|
142
173
|
}
|
|
174
|
+
/** The consumer left: cancel the stream and drop what it left. */
|
|
175
|
+
dispose() {
|
|
176
|
+
this.cancel(/* @__PURE__ */ new Error(`remote-mock: ${this.record.endpoint} stream disposed`));
|
|
177
|
+
}
|
|
143
178
|
/**
|
|
144
179
|
* Start `script` on this stream.
|
|
145
180
|
* @param script - the registered script.
|
|
@@ -214,6 +249,7 @@ var MockStream = class {
|
|
|
214
249
|
this.record.state = state;
|
|
215
250
|
this.settled = settled;
|
|
216
251
|
this.sourceSignal.removeEventListener("abort", this.onAbort);
|
|
252
|
+
this.release();
|
|
217
253
|
this.wakeDrained();
|
|
218
254
|
const waiting = this.waiting;
|
|
219
255
|
if (waiting === void 0) return;
|
|
@@ -228,6 +264,7 @@ var MockStream = class {
|
|
|
228
264
|
this.waiting = void 0;
|
|
229
265
|
this.queue.length = 0;
|
|
230
266
|
this.cancellation.abort(reason);
|
|
267
|
+
this.release();
|
|
231
268
|
waiting?.resolve({
|
|
232
269
|
value: void 0,
|
|
233
270
|
done: true
|
|
@@ -247,6 +284,104 @@ var MockStream = class {
|
|
|
247
284
|
}
|
|
248
285
|
};
|
|
249
286
|
/**
|
|
287
|
+
* The uplink a direct handle owns: `send()` pushes, `end()` half-closes, and
|
|
288
|
+
* `close()` ends it and drops unread items once the stream settles, is
|
|
289
|
+
* cancelled, or the consumer leaves. The script reads it as `StreamHandle.uplink`.
|
|
290
|
+
*/
|
|
291
|
+
var HandleUplink = class {
|
|
292
|
+
items = [];
|
|
293
|
+
done = false;
|
|
294
|
+
waiter;
|
|
295
|
+
/**
|
|
296
|
+
* Queues one uplink item for the script, or completes the read waiting for it.
|
|
297
|
+
* @param item - the item the handle's `send()` carried.
|
|
298
|
+
* @throws {Error} after `end()`.
|
|
299
|
+
*/
|
|
300
|
+
push(item) {
|
|
301
|
+
if (this.done) throw new Error("remote-mock: uplink was ended");
|
|
302
|
+
const waiter = this.waiter;
|
|
303
|
+
this.waiter = void 0;
|
|
304
|
+
if (waiter === void 0) this.items.push(item);
|
|
305
|
+
else waiter({
|
|
306
|
+
value: item,
|
|
307
|
+
done: false
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/** Half-closes the uplink: pending and later reads complete as done; repeated calls are no-ops. */
|
|
311
|
+
end() {
|
|
312
|
+
if (this.done) return;
|
|
313
|
+
this.done = true;
|
|
314
|
+
this.waiter?.({
|
|
315
|
+
value: void 0,
|
|
316
|
+
done: true
|
|
317
|
+
});
|
|
318
|
+
this.waiter = void 0;
|
|
319
|
+
}
|
|
320
|
+
/** Ends the uplink and drops unread items; the owning stream calls it on settle or cancel, the script's iterator on `return()`. */
|
|
321
|
+
close() {
|
|
322
|
+
this.items.length = 0;
|
|
323
|
+
this.end();
|
|
324
|
+
}
|
|
325
|
+
[Symbol.asyncIterator]() {
|
|
326
|
+
return {
|
|
327
|
+
next: () => {
|
|
328
|
+
if (this.items.length > 0) return Promise.resolve({
|
|
329
|
+
value: this.items.shift(),
|
|
330
|
+
done: false
|
|
331
|
+
});
|
|
332
|
+
if (this.done) return Promise.resolve({
|
|
333
|
+
value: void 0,
|
|
334
|
+
done: true
|
|
335
|
+
});
|
|
336
|
+
return new Promise((resolve) => {
|
|
337
|
+
this.waiter = resolve;
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
return: () => {
|
|
341
|
+
this.close();
|
|
342
|
+
return Promise.resolve({
|
|
343
|
+
value: void 0,
|
|
344
|
+
done: true
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* The handle a direct `mock.remote` stream call returns, as a generated method
|
|
352
|
+
* would: iterate it for the script's downlink, `send()` and `end()` feed
|
|
353
|
+
* `StreamHandle.uplink`, `dispose()` cancels the stream. `send()` applies the
|
|
354
|
+
* real handle's check, so a non-lossless JSON item throws and a top-level
|
|
355
|
+
* `undefined` passes. A stream opened through `rpc.open` reads the carrier's
|
|
356
|
+
* uplink instead, and this handle owns none.
|
|
357
|
+
*/
|
|
358
|
+
var MockClientStream = class {
|
|
359
|
+
stream;
|
|
360
|
+
uplink;
|
|
361
|
+
/**
|
|
362
|
+
* @param stream - the script-side stream.
|
|
363
|
+
* @param uplink - the uplink this handle feeds; absent when the carrier supplied one.
|
|
364
|
+
*/
|
|
365
|
+
constructor(stream, uplink) {
|
|
366
|
+
this.stream = stream;
|
|
367
|
+
this.uplink = uplink;
|
|
368
|
+
}
|
|
369
|
+
send(item) {
|
|
370
|
+
if (this.uplink === void 0) throw new Error(`remote-mock: ${this.stream.record.endpoint} uplink belongs to the carrier that opened the stream`);
|
|
371
|
+
if (!isRemoteUplinkItem(item)) throw new Error(`remote-mock: ${this.stream.record.endpoint} uplink item is not a lossless JSON value`);
|
|
372
|
+
this.uplink.push(item);
|
|
373
|
+
}
|
|
374
|
+
end() {
|
|
375
|
+
this.uplink?.end();
|
|
376
|
+
}
|
|
377
|
+
dispose() {
|
|
378
|
+
this.stream.dispose();
|
|
379
|
+
}
|
|
380
|
+
[Symbol.asyncIterator]() {
|
|
381
|
+
return this.stream[Symbol.asyncIterator]();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
250
385
|
* The `Error` a thrown value stands for: itself, or a new Error carrying its string form.
|
|
251
386
|
* @param reason - thrown value.
|
|
252
387
|
* @returns the error.
|
|
@@ -318,6 +453,8 @@ var RemoteMock = class RemoteMock {
|
|
|
318
453
|
logStore = new MockLogStore();
|
|
319
454
|
unaryMocks = /* @__PURE__ */ new Map();
|
|
320
455
|
streamMocks = /* @__PURE__ */ new Map();
|
|
456
|
+
/** The carrier's uplink for the open in progress through {@link open}; a direct call has none. */
|
|
457
|
+
carrierUplink;
|
|
321
458
|
proxy = createRemoteProxy((endpoint) => this.modeOf(endpoint) === "stream" ? this.streamMock(endpoint) : this.unaryMock(endpoint));
|
|
322
459
|
/**
|
|
323
460
|
* Native mock functions for every namespace: configure answers and inspect calls without declaring method names.
|
|
@@ -361,7 +498,7 @@ var RemoteMock = class RemoteMock {
|
|
|
361
498
|
const pending = this.dispatch(endpoint, argsOf(endpoint, payload));
|
|
362
499
|
return await (signal === void 0 ? pending : settleOrAbort(pending, signal));
|
|
363
500
|
},
|
|
364
|
-
open: (_channel, endpoint, payload, signal) => this.open(endpoint, argsOf(endpoint, payload), signal)
|
|
501
|
+
open: (_channel, endpoint, payload, signal, uplink) => this.open(endpoint, argsOf(endpoint, payload), signal, uplink)
|
|
365
502
|
};
|
|
366
503
|
constructor() {}
|
|
367
504
|
unary(endpoint, rule) {
|
|
@@ -455,16 +592,25 @@ var RemoteMock = class RemoteMock {
|
|
|
455
592
|
return isRuleFn(rule) ? answerOf(rule, args) : rule;
|
|
456
593
|
}
|
|
457
594
|
/**
|
|
458
|
-
* Open through the endpoint's native mock
|
|
459
|
-
*
|
|
595
|
+
* Open through the endpoint's native mock, which is called with the generated
|
|
596
|
+
* signature's arguments only; its default runs the registered script as a
|
|
597
|
+
* controlled stream. A native override returns its own iterable: the caller
|
|
598
|
+
* owns consumption and cancellation, outside `OpenStreams`.
|
|
460
599
|
* @param endpoint - endpoint.
|
|
461
600
|
* @param args - positional args.
|
|
462
601
|
* @param signal - consumer cancellation.
|
|
602
|
+
* @param uplink - the carrier's uplink for this open, which the script reads as `StreamHandle.uplink`;
|
|
603
|
+
* absent for a direct call, whose returned handle owns the uplink.
|
|
463
604
|
* @returns the controlled script stream or the native override's caller-owned iterable.
|
|
464
605
|
* @throws {Error} when the default runs without a registered script (logged as unmatched).
|
|
465
606
|
*/
|
|
466
|
-
open(endpoint, args, signal) {
|
|
467
|
-
|
|
607
|
+
open(endpoint, args, signal, uplink) {
|
|
608
|
+
this.carrierUplink = uplink;
|
|
609
|
+
try {
|
|
610
|
+
return this.streamMock(endpoint)(...args, signal);
|
|
611
|
+
} finally {
|
|
612
|
+
this.carrierUplink = void 0;
|
|
613
|
+
}
|
|
468
614
|
}
|
|
469
615
|
streamMock(endpoint) {
|
|
470
616
|
let mock = this.streamMocks.get(endpoint);
|
|
@@ -472,23 +618,31 @@ var RemoteMock = class RemoteMock {
|
|
|
472
618
|
mock = fn((...values) => {
|
|
473
619
|
const args = [...values];
|
|
474
620
|
const signal = args.at(-1) instanceof AbortSignal ? args.pop() : new AbortController().signal;
|
|
475
|
-
return this.openScript(endpoint, args, signal);
|
|
621
|
+
return this.openScript(endpoint, args, signal, this.carrierUplink);
|
|
476
622
|
});
|
|
477
623
|
this.streamMocks.set(endpoint, mock);
|
|
478
624
|
}
|
|
479
625
|
return mock;
|
|
480
626
|
}
|
|
481
|
-
openScript(endpoint, args, signal) {
|
|
627
|
+
openScript(endpoint, args, signal, carrierUplink) {
|
|
482
628
|
const script = this.scripts.get(endpoint);
|
|
483
629
|
if (script === void 0) {
|
|
484
630
|
this.logStore.miss(endpoint, "stream");
|
|
485
631
|
throw new Error(this.noRuleMessage(endpoint));
|
|
486
632
|
}
|
|
487
|
-
|
|
633
|
+
let uplink;
|
|
634
|
+
let owned;
|
|
635
|
+
if (carrierUplink === void 0) {
|
|
636
|
+
owned = new HandleUplink();
|
|
637
|
+
uplink = owned;
|
|
638
|
+
} else uplink = carrierUplink;
|
|
639
|
+
const stream = new MockStream(this.logStore.stream(endpoint, args), signal, uplink, () => {
|
|
640
|
+
owned?.close();
|
|
641
|
+
});
|
|
488
642
|
this.live.push(stream);
|
|
489
643
|
this.wakeOpened(endpoint);
|
|
490
644
|
stream.run(script, args);
|
|
491
|
-
return stream;
|
|
645
|
+
return new MockClientStream(stream, owned);
|
|
492
646
|
}
|
|
493
647
|
/** Throw when any request found no rule, naming the endpoints and the registered ones. */
|
|
494
648
|
assertNoUnmatched() {
|
|
@@ -554,4 +708,4 @@ function settleOrAbort(pending, signal) {
|
|
|
554
708
|
});
|
|
555
709
|
}
|
|
556
710
|
//#endregion
|
|
557
|
-
export { RemoteMock, frames, ok, openStream };
|
|
711
|
+
export { MockClientStream, RemoteMock, frames, ok, openStream, streamHandle, streamMethod };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { RemoteMock, ok } from './remote-mock.ts';
|
|
12
12
|
export type { OpenStreams, RemoteMockOptions, RemoteTable, StreamFilter, UnaryRuleFn } from './remote-mock.ts';
|
|
13
|
-
export { frames, openStream } from './streams.ts';
|
|
13
|
+
export { frames, openStream, streamHandle, streamMethod } from './streams.ts';
|
|
14
14
|
export type { StreamHandle, StreamScript } from './streams.ts';
|
|
15
|
+
export { MockClientStream } from './streams.ts';
|
|
15
16
|
export type { LoggedCall, LoggedMiss, LoggedStream, MockLog } from './log.ts';
|
|
16
17
|
export type { MockedRemote } from './remote-proxy.ts';
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -90,6 +90,8 @@ export declare class RemoteMock {
|
|
|
90
90
|
private readonly logStore;
|
|
91
91
|
private readonly unaryMocks;
|
|
92
92
|
private readonly streamMocks;
|
|
93
|
+
/** The carrier's uplink for the open in progress through {@link open}; a direct call has none. */
|
|
94
|
+
private carrierUplink;
|
|
93
95
|
private readonly proxy;
|
|
94
96
|
/**
|
|
95
97
|
* Native mock functions for every namespace: configure answers and inspect calls without declaring method names.
|
|
@@ -162,15 +164,19 @@ export declare class RemoteMock {
|
|
|
162
164
|
private unaryMock;
|
|
163
165
|
private unaryAnswer;
|
|
164
166
|
/**
|
|
165
|
-
* Open through the endpoint's native mock
|
|
166
|
-
*
|
|
167
|
+
* Open through the endpoint's native mock, which is called with the generated
|
|
168
|
+
* signature's arguments only; its default runs the registered script as a
|
|
169
|
+
* controlled stream. A native override returns its own iterable: the caller
|
|
170
|
+
* owns consumption and cancellation, outside `OpenStreams`.
|
|
167
171
|
* @param endpoint - endpoint.
|
|
168
172
|
* @param args - positional args.
|
|
169
173
|
* @param signal - consumer cancellation.
|
|
174
|
+
* @param uplink - the carrier's uplink for this open, which the script reads as `StreamHandle.uplink`;
|
|
175
|
+
* absent for a direct call, whose returned handle owns the uplink.
|
|
170
176
|
* @returns the controlled script stream or the native override's caller-owned iterable.
|
|
171
177
|
* @throws {Error} when the default runs without a registered script (logged as unmatched).
|
|
172
178
|
*/
|
|
173
|
-
open(endpoint: string, args: readonly unknown[], signal: AbortSignal): AsyncIterable<unknown>;
|
|
179
|
+
open(endpoint: string, args: readonly unknown[], signal: AbortSignal, uplink?: AsyncIterable<unknown>): AsyncIterable<unknown>;
|
|
174
180
|
private streamMock;
|
|
175
181
|
private openScript;
|
|
176
182
|
/** Throw when any request found no rule, naming the endpoints and the registered ones. */
|
package/lib/types/streams.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
/** Stream scripts and the pushable, abort-aware stream a script drives. */
|
|
2
|
+
import { type RemoteStreamHandle } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
3
|
import type { StreamRecord } from './log.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Type a fake stream method's result as the handle a generated Client method
|
|
6
|
+
* returns. The uplink members are inert: a fake built on it drives the downlink only.
|
|
7
|
+
* @param source - downlink items the fake yields.
|
|
8
|
+
* @returns a handle that iterates `source`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function streamHandle<Out, In = never>(source: AsyncIterable<Out>): RemoteStreamHandle<Out, In>;
|
|
11
|
+
/** Downlink item type of one generated stream method. */
|
|
12
|
+
type StreamItem<Method> = Method extends (...args: never[]) => RemoteStreamHandle<infer Out, unknown> ? Out : never;
|
|
13
|
+
/**
|
|
14
|
+
* Lift a fake stream method written as an async generator function into the
|
|
15
|
+
* signature of the generated method it stands in for.
|
|
16
|
+
* @param method - fake taking the method's arguments and yielding its downlink items.
|
|
17
|
+
* @returns the fake returning {@link streamHandle} of each call.
|
|
18
|
+
*/
|
|
19
|
+
export declare function streamMethod<Method extends (...args: never[]) => RemoteStreamHandle<unknown, unknown>>(method: (...args: Parameters<Method>) => AsyncIterable<StreamItem<Method>>): Method;
|
|
3
20
|
/** Test-side controls over one open stream. */
|
|
4
21
|
export interface StreamHandle {
|
|
5
22
|
/** Queue one item for the consumer. */
|
|
@@ -10,6 +27,12 @@ export interface StreamHandle {
|
|
|
10
27
|
fail(error: Error): void;
|
|
11
28
|
/** Aborts when the opening signal aborts or the consumer returns early. */
|
|
12
29
|
readonly signal: AbortSignal;
|
|
30
|
+
/**
|
|
31
|
+
* Uplink items for the script: the carrier's iterable when the open came
|
|
32
|
+
* through `rpc.open`, otherwise what the returned handle's `send()` and
|
|
33
|
+
* `end()` feed.
|
|
34
|
+
*/
|
|
35
|
+
readonly uplink: AsyncIterable<unknown>;
|
|
13
36
|
}
|
|
14
37
|
/**
|
|
15
38
|
* How a stream endpoint answers an open: receives the open args and the
|
|
@@ -33,6 +56,8 @@ export declare function openStream(initial?: readonly unknown[]): StreamScript;
|
|
|
33
56
|
export declare class MockStream implements StreamHandle, AsyncIterable<unknown> {
|
|
34
57
|
readonly record: StreamRecord;
|
|
35
58
|
private readonly sourceSignal;
|
|
59
|
+
readonly uplink: AsyncIterable<unknown>;
|
|
60
|
+
private readonly release;
|
|
36
61
|
private readonly queue;
|
|
37
62
|
private settled;
|
|
38
63
|
private waiting;
|
|
@@ -46,11 +71,15 @@ export declare class MockStream implements StreamHandle, AsyncIterable<unknown>
|
|
|
46
71
|
/**
|
|
47
72
|
* @param record - log entry this stream updates.
|
|
48
73
|
* @param sourceSignal - cancellation from the caller that opened the stream.
|
|
74
|
+
* @param uplink - the uplink the script reads.
|
|
75
|
+
* @param release - closes the uplink this stream owns; called once, when the stream settles, is cancelled, or the consumer leaves.
|
|
49
76
|
*/
|
|
50
|
-
constructor(record: StreamRecord, sourceSignal: AbortSignal);
|
|
77
|
+
constructor(record: StreamRecord, sourceSignal: AbortSignal, uplink: AsyncIterable<unknown>, release: () => void);
|
|
51
78
|
push(item: unknown): void;
|
|
52
79
|
end(): void;
|
|
53
80
|
fail(error: Error): void;
|
|
81
|
+
/** The consumer left: cancel the stream and drop what it left. */
|
|
82
|
+
dispose(): void;
|
|
54
83
|
/**
|
|
55
84
|
* Start `script` on this stream.
|
|
56
85
|
* @param script - the registered script.
|
|
@@ -72,10 +101,53 @@ export declare class MockStream implements StreamHandle, AsyncIterable<unknown>
|
|
|
72
101
|
private isDrained;
|
|
73
102
|
private wakeDrained;
|
|
74
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The uplink a direct handle owns: `send()` pushes, `end()` half-closes, and
|
|
106
|
+
* `close()` ends it and drops unread items once the stream settles, is
|
|
107
|
+
* cancelled, or the consumer leaves. The script reads it as `StreamHandle.uplink`.
|
|
108
|
+
*/
|
|
109
|
+
export declare class HandleUplink implements AsyncIterable<unknown> {
|
|
110
|
+
private readonly items;
|
|
111
|
+
private done;
|
|
112
|
+
private waiter;
|
|
113
|
+
/**
|
|
114
|
+
* Queues one uplink item for the script, or completes the read waiting for it.
|
|
115
|
+
* @param item - the item the handle's `send()` carried.
|
|
116
|
+
* @throws {Error} after `end()`.
|
|
117
|
+
*/
|
|
118
|
+
push(item: unknown): void;
|
|
119
|
+
/** Half-closes the uplink: pending and later reads complete as done; repeated calls are no-ops. */
|
|
120
|
+
end(): void;
|
|
121
|
+
/** Ends the uplink and drops unread items; the owning stream calls it on settle or cancel, the script's iterator on `return()`. */
|
|
122
|
+
close(): void;
|
|
123
|
+
[Symbol.asyncIterator](): AsyncIterator<unknown>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The handle a direct `mock.remote` stream call returns, as a generated method
|
|
127
|
+
* would: iterate it for the script's downlink, `send()` and `end()` feed
|
|
128
|
+
* `StreamHandle.uplink`, `dispose()` cancels the stream. `send()` applies the
|
|
129
|
+
* real handle's check, so a non-lossless JSON item throws and a top-level
|
|
130
|
+
* `undefined` passes. A stream opened through `rpc.open` reads the carrier's
|
|
131
|
+
* uplink instead, and this handle owns none.
|
|
132
|
+
*/
|
|
133
|
+
export declare class MockClientStream implements RemoteStreamHandle<unknown, unknown> {
|
|
134
|
+
private readonly stream;
|
|
135
|
+
private readonly uplink;
|
|
136
|
+
/**
|
|
137
|
+
* @param stream - the script-side stream.
|
|
138
|
+
* @param uplink - the uplink this handle feeds; absent when the carrier supplied one.
|
|
139
|
+
*/
|
|
140
|
+
constructor(stream: MockStream, uplink: HandleUplink | undefined);
|
|
141
|
+
send(item: unknown): void;
|
|
142
|
+
end(): void;
|
|
143
|
+
dispose(): void;
|
|
144
|
+
[Symbol.asyncIterator](): AsyncIterator<unknown>;
|
|
145
|
+
}
|
|
75
146
|
/**
|
|
76
147
|
* The `Error` a thrown value stands for: itself, or a new Error carrying its string form.
|
|
77
148
|
* @param reason - thrown value.
|
|
78
149
|
* @returns the error.
|
|
79
150
|
*/
|
|
80
151
|
export declare function toError(reason: unknown): Error;
|
|
152
|
+
export {};
|
|
81
153
|
//# sourceMappingURL=streams.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-remote-mock",
|
|
3
3
|
"description": "Endpoint-named mock for Typert Remote traffic: unary answers and stream scripts per <namespace>/<method>, live stream control, a log, and the Connection carrier face whole-client specs install",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,14 +26,15 @@
|
|
|
26
26
|
"@vitest/spy": "^4.1.8"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@deepseek-ai/
|
|
29
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-alpha.2",
|
|
30
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"typescript": "^6.0.3",
|
|
33
|
-
"@deepseek-ai/dsh-api-settings-controller": "
|
|
34
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
35
|
-
"@deepseek-ai/
|
|
36
|
-
"@deepseek-ai/
|
|
34
|
+
"@deepseek-ai/dsh-api-settings-controller": "0.1.7-alpha.2",
|
|
35
|
+
"@deepseek-ai/dsh-client-connection": "0.1.7-alpha.2",
|
|
36
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
37
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-alpha.2"
|
|
37
38
|
},
|
|
38
39
|
"files": [
|
|
39
40
|
"lib/index.js",
|