dsh-edge 0.9.0 → 0.10.0
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/THIRD_PARTY_NOTICES.md +1318 -1075
- package/dist/assets/index-Df-65__b.js +110 -0
- package/dist/assets/index-b24khbeK.css +1 -0
- package/dist/assets/langs/{php-DDkx9sim.js → php-DJl5gRdb.js} +2 -2
- package/dist/assets/langs/{ruby-DCxyoPen.js → ruby-5eNB0pDK.js} +2 -2
- package/dist/assets/vendor-BNsW4eBh.css +1 -0
- package/dist/assets/vendor-CCJJTK99.js +411 -0
- package/dist/index.html +8 -8
- package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +1513 -77
- package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +3608 -757
- package/dist/plugins/@deepseek-ai/dsh-api-session-controller/client.js +2777 -0
- package/dist/plugins/@deepseek-ai/dsh-api-workspace-controller/client.js +415 -0
- package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +1975 -7466
- package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +225 -76
- package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +67 -15
- package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +270 -460
- package/dist/plugins/@deepseek-ai/dsh-client-ui-approval/client.js +292 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-attachment/client.js +42 -17
- package/dist/plugins/@deepseek-ai/dsh-client-ui-brand-official/client.js +7 -9
- package/dist/plugins/@deepseek-ai/dsh-client-ui-chat/client.js +8177 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +22 -10
- package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +14950 -8910
- package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +161 -137
- package/dist/plugins/@deepseek-ai/dsh-client-ui-goal/client.js +6 -7
- package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +268 -40
- package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +1 -1
- package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +45 -11
- package/dist/plugins/@deepseek-ai/dsh-client-ui-message-feedback/client.js +30 -58
- package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +188 -89
- package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +105 -75
- package/dist/plugins/@deepseek-ai/dsh-client-ui-renderer/client.js +650 -188
- package/dist/plugins/@deepseek-ai/dsh-client-ui-schedule/client.js +307 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-session/client.js +340 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +57 -50
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +134 -69
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-models/client.js +575 -442
- package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +24 -11
- package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +13 -9
- package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +44 -20
- package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +265 -94
- package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +899 -433
- package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +1472 -613
- package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +249 -95
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +5 -5
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +425 -146
- package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +29 -14
- package/dist/plugins/dsh-edge-client-ui/client.js +28 -26
- package/package.json +62 -58
- package/scripts/verify-packed.mjs +1 -1
- package/worker/direct/index.js +2866 -1466
- package/worker/isolated/index.js +2438 -1038
- package/dist/assets/index-C6eRlFa6.css +0 -1
- package/dist/assets/index-ClqxG24t.js +0 -96
- package/dist/assets/vendor-CjyC-hUb.css +0 -1
- package/dist/assets/vendor-D22_Mp1f.js +0 -411
- package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +0 -10574
|
@@ -5,6 +5,1378 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
8
|
+
//#region ../../typert/protocol/lib/index.js
|
|
9
|
+
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
10
|
+
/**
|
|
11
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
12
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
13
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
14
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
15
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
16
|
+
*/
|
|
17
|
+
var RemoteError = class extends Error {
|
|
18
|
+
code;
|
|
19
|
+
details;
|
|
20
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
21
|
+
isDSHRemoteError = true;
|
|
22
|
+
/**
|
|
23
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
24
|
+
* @param message - human diagnostic carried across the wire.
|
|
25
|
+
* @param details - structured payload typed by the code.
|
|
26
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
27
|
+
*/
|
|
28
|
+
constructor(code, message, details, options) {
|
|
29
|
+
super(message, options);
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.details = details;
|
|
32
|
+
this.name = "RemoteError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Structurally identify a RemoteError thrown across module or realm copies of
|
|
37
|
+
* this class. Mechanism-internal: the Gateway and test assertions use it;
|
|
38
|
+
* business code receives typed failures and never needs it.
|
|
39
|
+
* @param value - a caught value.
|
|
40
|
+
* @returns the failure when the marker matches, otherwise undefined.
|
|
41
|
+
*/
|
|
42
|
+
function remoteErrorOf(value) {
|
|
43
|
+
if (typeof value === "object" && value !== null && value.isDSHRemoteError === true && typeof value.code === "string") return value;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region lib/types/stream-protocol.js
|
|
47
|
+
/** Wire messages for Gateway-owned Remote streams and event-result RPCs. */
|
|
48
|
+
/** Exact WebSocket route carrying every Typert Remote stream. */
|
|
49
|
+
const REMOTE_STREAM_MUX_PATH = "/api/remote.mux";
|
|
50
|
+
/** Gateway-internal logical stream carrying application-selected Cordis events. */
|
|
51
|
+
const REMOTE_EVENT_STREAM_ENDPOINT = "$events";
|
|
52
|
+
/** Gateway-internal unary endpoint returning one Client Remote Event outcome. */
|
|
53
|
+
const REMOTE_EVENT_RESULT_ENDPOINT = "$events/result";
|
|
54
|
+
/** Empty standard Remote payload used to open the forwarded-event stream. */
|
|
55
|
+
const REMOTE_EVENT_STREAM_PAYLOAD = { args: {} };
|
|
56
|
+
/**
|
|
57
|
+
* Project an arbitrary rejection to stable, JSON-safe error fields.
|
|
58
|
+
* @param reason - value thrown or rejected by a Client listener.
|
|
59
|
+
* @returns wire-safe rejection fields.
|
|
60
|
+
*/
|
|
61
|
+
function projectRemoteEventRejection(reason) {
|
|
62
|
+
const record = typeof reason === "object" && reason !== null ? reason : void 0;
|
|
63
|
+
const name = stringProperty(record, "name") ?? "Error";
|
|
64
|
+
const message = stringProperty(record, "message") ?? String(reason);
|
|
65
|
+
const code = stringProperty(record, "code");
|
|
66
|
+
const details = record === void 0 ? void 0 : Reflect.get(record, "details");
|
|
67
|
+
return {
|
|
68
|
+
name,
|
|
69
|
+
message,
|
|
70
|
+
...code === void 0 ? {} : { code },
|
|
71
|
+
...details === void 0 || !isRemoteJsonValue(details) ? {} : { details }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Test whether a value crosses JSON transport without coercion or omission.
|
|
76
|
+
* @param value - candidate boundary value.
|
|
77
|
+
* @returns whether the value is losslessly JSON-compatible.
|
|
78
|
+
*/
|
|
79
|
+
function isRemoteJsonValue(value) {
|
|
80
|
+
return visitJsonValue(value, /* @__PURE__ */ new Set());
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Recognize a non-empty Remote Event correlation id at a wire boundary.
|
|
84
|
+
* @param value - untrusted wire value.
|
|
85
|
+
* @returns whether the value is a valid Remote Event id.
|
|
86
|
+
*/
|
|
87
|
+
function isRemoteEventId(value) {
|
|
88
|
+
return typeof value === "string" && value.length > 0;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recognize a non-empty Remote Event Client id at a wire boundary.
|
|
92
|
+
* @param value - untrusted wire value.
|
|
93
|
+
* @returns whether the value identifies one event-stream generation.
|
|
94
|
+
*/
|
|
95
|
+
function isRemoteEventClientId(value) {
|
|
96
|
+
return typeof value === "string" && value.length > 0;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Recognize the direct Agent identity used by a scoped Remote Event.
|
|
100
|
+
* @param value - untrusted wire value.
|
|
101
|
+
* @returns whether the value is a non-empty Agent identity.
|
|
102
|
+
*/
|
|
103
|
+
function isRemoteEventAgentId(value) {
|
|
104
|
+
return typeof value === "string" && value.length > 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse and validate one Host-to-browser text message.
|
|
108
|
+
* @param text - complete WebSocket text message.
|
|
109
|
+
* @returns the validated logical-stream frame.
|
|
110
|
+
*/
|
|
111
|
+
function parseRemoteStreamServerMessage(text) {
|
|
112
|
+
return parseMessage(text, (value) => {
|
|
113
|
+
if (value.type === "item" && (exactKeys(value, ["type", "streamId"]) || exactKeys(value, [
|
|
114
|
+
"type",
|
|
115
|
+
"streamId",
|
|
116
|
+
"value"
|
|
117
|
+
])) && validId(value.streamId)) return value;
|
|
118
|
+
if (value.type === "end" && exactKeys(value, ["type", "streamId"]) && validId(value.streamId)) return value;
|
|
119
|
+
if (value.type === "error" && exactKeys(value, [
|
|
120
|
+
"type",
|
|
121
|
+
"streamId",
|
|
122
|
+
"error"
|
|
123
|
+
]) && validId(value.streamId) && isRecord(value.error) && exactKeys(value.error, [
|
|
124
|
+
"code",
|
|
125
|
+
"message",
|
|
126
|
+
"details"
|
|
127
|
+
]) && typeof value.error.code === "string" && typeof value.error.message === "string" && isRecord(value.error.details)) return value;
|
|
128
|
+
throw new Error("api gateway: invalid Remote stream server message");
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function parseMessage(text, validate) {
|
|
132
|
+
let decoded;
|
|
133
|
+
try {
|
|
134
|
+
decoded = JSON.parse(text);
|
|
135
|
+
} catch (cause) {
|
|
136
|
+
throw new Error("api gateway: Remote stream message is not JSON", { cause });
|
|
137
|
+
}
|
|
138
|
+
if (!isRecord(decoded)) throw new Error("api gateway: Remote stream message must be an object");
|
|
139
|
+
return validate(decoded);
|
|
140
|
+
}
|
|
141
|
+
function isRecord(value) {
|
|
142
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
143
|
+
}
|
|
144
|
+
function exactKeys(value, expected) {
|
|
145
|
+
return Reflect.ownKeys(value).length === expected.length && expected.every((key) => Object.hasOwn(value, key));
|
|
146
|
+
}
|
|
147
|
+
function validId(value) {
|
|
148
|
+
return typeof value === "string" && value.length > 0;
|
|
149
|
+
}
|
|
150
|
+
function stringProperty(value, key) {
|
|
151
|
+
if (value === void 0) return void 0;
|
|
152
|
+
const candidate = Reflect.get(value, key);
|
|
153
|
+
return typeof candidate === "string" ? candidate : void 0;
|
|
154
|
+
}
|
|
155
|
+
function visitJsonValue(value, ancestors) {
|
|
156
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
157
|
+
if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0);
|
|
158
|
+
if (typeof value !== "object") return false;
|
|
159
|
+
if (ancestors.has(value)) return false;
|
|
160
|
+
ancestors.add(value);
|
|
161
|
+
try {
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || Reflect.ownKeys(value).length !== value.length + 1) return false;
|
|
164
|
+
for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index) || !visitJsonValue(value[index], ancestors)) return false;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
const prototype = Object.getPrototypeOf(value);
|
|
168
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
169
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
170
|
+
if (typeof key !== "string") return false;
|
|
171
|
+
if (Object.getOwnPropertyDescriptor(value, key)?.enumerable !== true || !visitJsonValue(Reflect.get(value, key), ancestors)) return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
} finally {
|
|
175
|
+
ancestors.delete(value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region ../../util/deque/lib/index.js
|
|
180
|
+
/**
|
|
181
|
+
* Zero-dependency circular deque for queues that retain entries across asynchronous work.
|
|
182
|
+
* @module @deepseek-ai/dsh-deque
|
|
183
|
+
*/
|
|
184
|
+
const MIN_CAPACITY = 16;
|
|
185
|
+
/**
|
|
186
|
+
* A circular deque with amortized constant-time insertion and removal.
|
|
187
|
+
* Removed entries are cleared immediately, and sparse storage shrinks after
|
|
188
|
+
* the live entry count reaches one quarter of its capacity.
|
|
189
|
+
*/
|
|
190
|
+
var Deque = class {
|
|
191
|
+
buffer = new Array(MIN_CAPACITY);
|
|
192
|
+
head = 0;
|
|
193
|
+
count = 0;
|
|
194
|
+
/** Number of entries available to remove. */
|
|
195
|
+
get size() {
|
|
196
|
+
return this.count;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Append one entry after the current tail.
|
|
200
|
+
* @param value - entry to append.
|
|
201
|
+
*/
|
|
202
|
+
pushBack(value) {
|
|
203
|
+
this.ensureCapacity();
|
|
204
|
+
const tail = this.head + this.count;
|
|
205
|
+
this.buffer[tail < this.buffer.length ? tail : tail - this.buffer.length] = value;
|
|
206
|
+
this.count += 1;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Insert one entry before the current head.
|
|
210
|
+
* @param value - entry to prepend.
|
|
211
|
+
*/
|
|
212
|
+
pushFront(value) {
|
|
213
|
+
this.ensureCapacity();
|
|
214
|
+
this.head = this.head === 0 ? this.buffer.length - 1 : this.head - 1;
|
|
215
|
+
this.buffer[this.head] = value;
|
|
216
|
+
this.count += 1;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Remove the current head entry and clear its retained reference.
|
|
220
|
+
* Callers whose element type includes `undefined` use {@link size} to
|
|
221
|
+
* distinguish an empty deque from an `undefined` entry.
|
|
222
|
+
* @returns the removed entry, or `undefined` when the deque is empty.
|
|
223
|
+
*/
|
|
224
|
+
popFront() {
|
|
225
|
+
if (this.count === 0) return void 0;
|
|
226
|
+
const value = this.buffer[this.head];
|
|
227
|
+
this.buffer[this.head] = void 0;
|
|
228
|
+
this.head += 1;
|
|
229
|
+
if (this.head === this.buffer.length) this.head = 0;
|
|
230
|
+
this.count -= 1;
|
|
231
|
+
this.compact();
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
/** Drop every entry and release the current backing storage. */
|
|
235
|
+
clear() {
|
|
236
|
+
this.buffer = new Array(MIN_CAPACITY);
|
|
237
|
+
this.head = 0;
|
|
238
|
+
this.count = 0;
|
|
239
|
+
}
|
|
240
|
+
ensureCapacity() {
|
|
241
|
+
if (this.count < this.buffer.length) return;
|
|
242
|
+
this.resize(this.buffer.length * 2);
|
|
243
|
+
}
|
|
244
|
+
compact() {
|
|
245
|
+
if (this.count === 0) {
|
|
246
|
+
this.head = 0;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (this.buffer.length > MIN_CAPACITY && this.count <= this.buffer.length / 4) this.resize(Math.max(MIN_CAPACITY, this.buffer.length / 2));
|
|
250
|
+
}
|
|
251
|
+
resize(capacity) {
|
|
252
|
+
const next = new Array(capacity);
|
|
253
|
+
let source = this.head;
|
|
254
|
+
for (let index = 0; index < this.count; index += 1) {
|
|
255
|
+
next[index] = this.buffer[source];
|
|
256
|
+
source += 1;
|
|
257
|
+
if (source === this.buffer.length) source = 0;
|
|
258
|
+
}
|
|
259
|
+
this.buffer = next;
|
|
260
|
+
this.head = 0;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region ../../util/crypto/lib/index.js
|
|
265
|
+
/**
|
|
266
|
+
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
267
|
+
* @returns the UUID string.
|
|
268
|
+
*/
|
|
269
|
+
function randomUUID() {
|
|
270
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
271
|
+
const hex = Array.from(bytes, (byte, index) => {
|
|
272
|
+
return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
|
|
273
|
+
}).join("");
|
|
274
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region lib/types/client/stream-client.js
|
|
278
|
+
/** Browser owner for the Gateway multiplexed Remote stream socket. */
|
|
279
|
+
const INTERNAL_BASE = "http://dsh.internal";
|
|
280
|
+
/** Physical Remote stream socket failure that may be retried by a domain transport. */
|
|
281
|
+
var RemoteStreamCarrierError = class extends Error {
|
|
282
|
+
/**
|
|
283
|
+
* @param message - physical carrier failure description.
|
|
284
|
+
* @param options - optional causal error.
|
|
285
|
+
*/
|
|
286
|
+
constructor(message, options) {
|
|
287
|
+
super(message, options);
|
|
288
|
+
this.name = "RemoteStreamCarrierError";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
/** Keep one physical WebSocket and share it among independently cancellable Remote streams. */
|
|
292
|
+
var RemoteStreamMuxClient = class {
|
|
293
|
+
socket;
|
|
294
|
+
cancelCandidate;
|
|
295
|
+
keepAlive;
|
|
296
|
+
revision = 0;
|
|
297
|
+
streams = /* @__PURE__ */ new Map();
|
|
298
|
+
waiters = /* @__PURE__ */ new Set();
|
|
299
|
+
running = false;
|
|
300
|
+
disposed = false;
|
|
301
|
+
/** Ensure a physical attempt exists, following the current attempt once if needed. */
|
|
302
|
+
start() {
|
|
303
|
+
if (this.disposed) return;
|
|
304
|
+
this.running = true;
|
|
305
|
+
if (this.socket?.readyState === WebSocket.OPEN) return;
|
|
306
|
+
const pending = this.keepAlive;
|
|
307
|
+
if (pending === void 0) this.maintain();
|
|
308
|
+
else pending.then(() => {
|
|
309
|
+
this.maintain();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/** Cancel the current socket or retry wait and start a fresh attempt immediately. */
|
|
313
|
+
reconnect() {
|
|
314
|
+
if (!this.running || this.disposed) return;
|
|
315
|
+
const failure = new RemoteStreamCarrierError("api gateway: Remote stream reconnect requested");
|
|
316
|
+
const pending = this.keepAlive;
|
|
317
|
+
this.revision++;
|
|
318
|
+
this.cancelCandidate?.(failure);
|
|
319
|
+
const socket = this.socket;
|
|
320
|
+
if (socket !== void 0) {
|
|
321
|
+
this.socket = void 0;
|
|
322
|
+
this.failAll(failure);
|
|
323
|
+
socket.close(4e3, "reconnect requested");
|
|
324
|
+
}
|
|
325
|
+
if (pending === void 0) this.maintain();
|
|
326
|
+
else pending.then(() => {
|
|
327
|
+
this.maintain();
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Open one logical stream on the persistent physical connection.
|
|
332
|
+
* If no physical attempt is active, opening waits for Connection to request
|
|
333
|
+
* one or for the signal to abort.
|
|
334
|
+
* @param endpoint - Typert Remote stream endpoint.
|
|
335
|
+
* @param payload - endpoint request encoded on the wire.
|
|
336
|
+
* @param signal - cancellation for this logical stream.
|
|
337
|
+
* @returns Host items until completion, cancellation, or failure.
|
|
338
|
+
*/
|
|
339
|
+
async *open(endpoint, payload, signal) {
|
|
340
|
+
signal.throwIfAborted();
|
|
341
|
+
const streamId = randomUUID();
|
|
342
|
+
const inbox = new StreamInbox();
|
|
343
|
+
let carrier;
|
|
344
|
+
let opened = false;
|
|
345
|
+
let terminal = false;
|
|
346
|
+
const abort = () => {
|
|
347
|
+
inbox.fail(signal.reason);
|
|
348
|
+
};
|
|
349
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
350
|
+
try {
|
|
351
|
+
const socket = await this.waitForSocket(signal);
|
|
352
|
+
signal.throwIfAborted();
|
|
353
|
+
carrier = socket;
|
|
354
|
+
this.streams.set(streamId, inbox);
|
|
355
|
+
this.send(socket, {
|
|
356
|
+
type: "open",
|
|
357
|
+
streamId,
|
|
358
|
+
endpoint,
|
|
359
|
+
payload
|
|
360
|
+
});
|
|
361
|
+
opened = true;
|
|
362
|
+
while (true) {
|
|
363
|
+
const frame = await inbox.next();
|
|
364
|
+
signal.throwIfAborted();
|
|
365
|
+
if (frame.type === "item") {
|
|
366
|
+
yield frame.value;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
terminal = true;
|
|
370
|
+
if (frame.type === "error") throw new RemoteError(frame.error.code, frame.error.message, frame.error.details);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
} finally {
|
|
374
|
+
signal.removeEventListener("abort", abort);
|
|
375
|
+
this.streams.delete(streamId);
|
|
376
|
+
if (opened && !terminal && carrier?.readyState === WebSocket.OPEN) this.send(carrier, {
|
|
377
|
+
type: "cancel",
|
|
378
|
+
streamId
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Permanently stop the carrier, close the physical socket, and fail every
|
|
384
|
+
* active logical stream.
|
|
385
|
+
* @returns once the active connection attempt has stopped.
|
|
386
|
+
*/
|
|
387
|
+
async close() {
|
|
388
|
+
if (!this.disposed) {
|
|
389
|
+
this.disposed = true;
|
|
390
|
+
this.running = false;
|
|
391
|
+
const error = /* @__PURE__ */ new Error("api gateway: Remote stream client disposed");
|
|
392
|
+
this.failAll(error);
|
|
393
|
+
for (const waiter of [...this.waiters]) waiter.reject(error);
|
|
394
|
+
this.cancelCandidate?.(error);
|
|
395
|
+
const socket = this.socket;
|
|
396
|
+
this.socket = void 0;
|
|
397
|
+
socket?.close(1e3, "disposed");
|
|
398
|
+
}
|
|
399
|
+
await this.keepAlive;
|
|
400
|
+
}
|
|
401
|
+
connect() {
|
|
402
|
+
const socket = new WebSocket(remoteStreamUrl());
|
|
403
|
+
return new Promise((resolve, reject) => {
|
|
404
|
+
let settled = false;
|
|
405
|
+
const rejectCandidate = (error) => {
|
|
406
|
+
settled = true;
|
|
407
|
+
socket.removeEventListener("open", opened);
|
|
408
|
+
socket.removeEventListener("error", failed);
|
|
409
|
+
socket.removeEventListener("message", received);
|
|
410
|
+
socket.removeEventListener("close", closed);
|
|
411
|
+
this.cancelCandidate = void 0;
|
|
412
|
+
socket.close();
|
|
413
|
+
reject(error);
|
|
414
|
+
};
|
|
415
|
+
const opened = () => {
|
|
416
|
+
settled = true;
|
|
417
|
+
this.cancelCandidate = void 0;
|
|
418
|
+
this.socket = socket;
|
|
419
|
+
for (const waiter of [...this.waiters]) waiter.resolve(socket);
|
|
420
|
+
resolve(socket);
|
|
421
|
+
};
|
|
422
|
+
const failed = () => {
|
|
423
|
+
if (!settled) {
|
|
424
|
+
rejectCandidate(new RemoteStreamCarrierError("api gateway: Remote stream WebSocket failed to open"));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const error = new RemoteStreamCarrierError("api gateway: Remote stream WebSocket failed");
|
|
428
|
+
this.lost(socket, error);
|
|
429
|
+
socket.close();
|
|
430
|
+
};
|
|
431
|
+
const closed = () => {
|
|
432
|
+
if (!settled) {
|
|
433
|
+
rejectCandidate(new RemoteStreamCarrierError("api gateway: Remote stream WebSocket closed before opening"));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
this.lost(socket);
|
|
437
|
+
};
|
|
438
|
+
const received = (event) => {
|
|
439
|
+
this.receive(socket, event.data);
|
|
440
|
+
};
|
|
441
|
+
this.cancelCandidate = rejectCandidate;
|
|
442
|
+
socket.addEventListener("open", opened, { once: true });
|
|
443
|
+
socket.addEventListener("error", failed, { once: true });
|
|
444
|
+
socket.addEventListener("message", received);
|
|
445
|
+
socket.addEventListener("close", closed, { once: true });
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
waitForSocket(signal) {
|
|
449
|
+
signal.throwIfAborted();
|
|
450
|
+
if (this.socket?.readyState === WebSocket.OPEN) return Promise.resolve(this.socket);
|
|
451
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("api gateway: Remote stream client disposed"));
|
|
452
|
+
if (!this.running) return Promise.reject(/* @__PURE__ */ new Error("api gateway: Remote stream client not started"));
|
|
453
|
+
return new Promise((resolve, reject) => {
|
|
454
|
+
const aborted = () => {
|
|
455
|
+
waiter.reject(signal.reason);
|
|
456
|
+
};
|
|
457
|
+
const cleanup = () => {
|
|
458
|
+
this.waiters.delete(waiter);
|
|
459
|
+
signal.removeEventListener("abort", aborted);
|
|
460
|
+
};
|
|
461
|
+
const waiter = {
|
|
462
|
+
revision: this.revision,
|
|
463
|
+
resolve: (socket) => {
|
|
464
|
+
cleanup();
|
|
465
|
+
resolve(socket);
|
|
466
|
+
},
|
|
467
|
+
reject: (error) => {
|
|
468
|
+
cleanup();
|
|
469
|
+
reject(error);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
this.waiters.add(waiter);
|
|
473
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
receive(socket, data) {
|
|
477
|
+
if (socket !== this.socket) return;
|
|
478
|
+
try {
|
|
479
|
+
if (typeof data !== "string") throw new Error("api gateway: Remote stream WebSocket requires text messages");
|
|
480
|
+
const frame = parseRemoteStreamServerMessage(data);
|
|
481
|
+
this.streams.get(frame.streamId)?.push(frame);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
const failure = new RemoteStreamCarrierError("api gateway: invalid Remote stream frame", { cause: error });
|
|
484
|
+
this.failAll(failure);
|
|
485
|
+
this.lost(socket, failure);
|
|
486
|
+
socket.close(4002, "invalid Remote stream frame");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
lost(socket, error = new RemoteStreamCarrierError("api gateway: Remote stream WebSocket closed")) {
|
|
490
|
+
if (this.socket !== socket) return;
|
|
491
|
+
this.socket = void 0;
|
|
492
|
+
this.failAll(error);
|
|
493
|
+
}
|
|
494
|
+
maintain() {
|
|
495
|
+
if (!this.running || this.disposed) return;
|
|
496
|
+
if (this.socket?.readyState === WebSocket.OPEN || this.keepAlive !== void 0) return;
|
|
497
|
+
const revision = this.revision;
|
|
498
|
+
const task = this.connect().then(() => void 0, (error) => {
|
|
499
|
+
if (!this.running) return;
|
|
500
|
+
for (const waiter of [...this.waiters]) if (waiter.revision <= revision) waiter.reject(error);
|
|
501
|
+
});
|
|
502
|
+
this.keepAlive = task;
|
|
503
|
+
task.then(() => {
|
|
504
|
+
this.keepAlive = void 0;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
failAll(error) {
|
|
508
|
+
for (const stream of this.streams.values()) stream.fail(error);
|
|
509
|
+
}
|
|
510
|
+
send(socket, message) {
|
|
511
|
+
socket.send(JSON.stringify(message));
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
var StreamInbox = class {
|
|
515
|
+
frames = new Deque();
|
|
516
|
+
wake;
|
|
517
|
+
failure;
|
|
518
|
+
push(frame) {
|
|
519
|
+
if (this.failure !== void 0) return;
|
|
520
|
+
this.frames.pushBack(frame);
|
|
521
|
+
this.wake?.();
|
|
522
|
+
this.wake = void 0;
|
|
523
|
+
}
|
|
524
|
+
fail(error) {
|
|
525
|
+
if (this.failure !== void 0) return;
|
|
526
|
+
this.failure = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
527
|
+
this.frames.clear();
|
|
528
|
+
this.wake?.();
|
|
529
|
+
this.wake = void 0;
|
|
530
|
+
}
|
|
531
|
+
async next() {
|
|
532
|
+
while (this.frames.size === 0) {
|
|
533
|
+
if (this.failure !== void 0) throw this.failure;
|
|
534
|
+
await new Promise((resolve) => {
|
|
535
|
+
this.wake = resolve;
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return this.frames.popFront();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
function remoteStreamUrl() {
|
|
542
|
+
const location = globalThis.location;
|
|
543
|
+
const base = location?.origin !== void 0 && location.origin !== "null" ? location.origin : INTERNAL_BASE;
|
|
544
|
+
const url = new URL(REMOTE_STREAM_MUX_PATH, base);
|
|
545
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
546
|
+
return url.href;
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region lib/types/client/remote-events.js
|
|
550
|
+
/** Client owner for forwarded Remote Event subscriptions and deliveries. */
|
|
551
|
+
/** Private end-of-chain marker that cannot collide with a JSON listener result. */
|
|
552
|
+
const REMOTE_EVENT_NEXT = Symbol("api-gateway.remote-event.next");
|
|
553
|
+
/** Own Cordis registrations, generation pumping, waterfall dispatch, and HTTP replies. */
|
|
554
|
+
var ClientRemoteEvents = class {
|
|
555
|
+
ownerCtx;
|
|
556
|
+
connection;
|
|
557
|
+
openStream;
|
|
558
|
+
eventPrefix = `internal/api-gateway/remote-event/${randomUUID()}/`;
|
|
559
|
+
unregisterGeneration;
|
|
560
|
+
activeGeneration;
|
|
561
|
+
/**
|
|
562
|
+
* @param ownerCtx - Client Gateway root used for Agent Context resolution.
|
|
563
|
+
* @param connection - Connection carrier used for HTTP result calls.
|
|
564
|
+
* @param openStream - selected in-process or WebSocket stream opener.
|
|
565
|
+
*/
|
|
566
|
+
constructor(ownerCtx, connection, openStream) {
|
|
567
|
+
this.ownerCtx = ownerCtx;
|
|
568
|
+
this.connection = connection;
|
|
569
|
+
this.openStream = openStream;
|
|
570
|
+
this.unregisterGeneration = connection.registerGenerationSource(this.runGeneration);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Register one typed Remote Event listener in its calling fiber.
|
|
574
|
+
* @param callerCtx - fiber Context owning the registration.
|
|
575
|
+
* @param event - selected forwarded event.
|
|
576
|
+
* @param listener - listener derived from that event's declaration.
|
|
577
|
+
* @returns disposer for this exact registration.
|
|
578
|
+
*/
|
|
579
|
+
subscribe(callerCtx, event, listener) {
|
|
580
|
+
const dispose = privateEvents(callerCtx).on(this.eventKey(event), listener);
|
|
581
|
+
return () => {
|
|
582
|
+
dispose();
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/** Withdraw the generation source and wait for active listener work to quiesce. */
|
|
586
|
+
async dispose() {
|
|
587
|
+
this.unregisterGeneration();
|
|
588
|
+
await Promise.allSettled([this.activeGeneration]);
|
|
589
|
+
}
|
|
590
|
+
/** Track the current generation so plugin disposal waits for listener work to stop. */
|
|
591
|
+
runGeneration = (signal, ready) => {
|
|
592
|
+
const tracked = this.pumpEvents(signal, ready).finally(() => {
|
|
593
|
+
if (this.activeGeneration === tracked) this.activeGeneration = void 0;
|
|
594
|
+
});
|
|
595
|
+
this.activeGeneration = tracked;
|
|
596
|
+
return tracked;
|
|
597
|
+
};
|
|
598
|
+
/** Deliver one notification through Cordis while containing listener failures. */
|
|
599
|
+
deliver(frame) {
|
|
600
|
+
privateEvents(this.ownerCtx).parallel(this.eventKey(frame.event), ...frame.args).catch((error) => {
|
|
601
|
+
this.reportError(frame.event, error);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
/** Run one Connection generation over the forwarded-event logical stream. */
|
|
605
|
+
async pumpEvents(signal, ready) {
|
|
606
|
+
let clientId;
|
|
607
|
+
const failed = new AbortController();
|
|
608
|
+
const generationSignal = AbortSignal.any([signal, failed.signal]);
|
|
609
|
+
const active = /* @__PURE__ */ new Map();
|
|
610
|
+
const tasks = /* @__PURE__ */ new Set();
|
|
611
|
+
const source = this.openStream(REMOTE_EVENT_STREAM_ENDPOINT, REMOTE_EVENT_STREAM_PAYLOAD, generationSignal);
|
|
612
|
+
let streamFailed = false;
|
|
613
|
+
let streamError;
|
|
614
|
+
try {
|
|
615
|
+
for await (const value of source) {
|
|
616
|
+
if (clientId === void 0) {
|
|
617
|
+
const opening = parseRemoteEventReady(value);
|
|
618
|
+
clientId = opening.clientId;
|
|
619
|
+
ready(opening.host);
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const frame = parseRemoteEventFrame(value);
|
|
623
|
+
if (frame.type === "cancel") {
|
|
624
|
+
active.get(frame.eventId)?.abort(/* @__PURE__ */ new Error("client api: Remote event was cancelled by the Host"));
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (frame.type === "emit") {
|
|
628
|
+
this.deliver(frame);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const controller = new AbortController();
|
|
632
|
+
active.set(frame.eventId, controller);
|
|
633
|
+
const deliverySignal = AbortSignal.any([generationSignal, controller.signal]);
|
|
634
|
+
const task = this.answer(frame, clientId, deliverySignal).catch((error) => {
|
|
635
|
+
if (!deliverySignal.aborted) failed.abort(error);
|
|
636
|
+
}).finally(() => {
|
|
637
|
+
active.delete(frame.eventId);
|
|
638
|
+
tasks.delete(task);
|
|
639
|
+
});
|
|
640
|
+
tasks.add(task);
|
|
641
|
+
}
|
|
642
|
+
} catch (error) {
|
|
643
|
+
streamFailed = true;
|
|
644
|
+
streamError = error;
|
|
645
|
+
} finally {
|
|
646
|
+
for (const controller of active.values()) controller.abort(/* @__PURE__ */ new Error("client api: Remote event generation ended"));
|
|
647
|
+
await Promise.allSettled(tasks);
|
|
648
|
+
}
|
|
649
|
+
if (failed.signal.aborted) throw toError(failed.signal.reason, "client api: Remote event result delivery failed");
|
|
650
|
+
if (signal.aborted) return;
|
|
651
|
+
if (streamFailed) throw streamError;
|
|
652
|
+
throw new Error("client api: forwarded Remote event stream ended unexpectedly");
|
|
653
|
+
}
|
|
654
|
+
async answer(frame, clientId, signal) {
|
|
655
|
+
const adapter = this.ownerCtx.typert.contexts.getClient("agent");
|
|
656
|
+
let target;
|
|
657
|
+
try {
|
|
658
|
+
target = adapter?.resolve(frame.agentId);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
this.reportError(frame.event, error);
|
|
661
|
+
}
|
|
662
|
+
let outcome = { kind: "next" };
|
|
663
|
+
if (target !== void 0) try {
|
|
664
|
+
outcome = await this.dispatchWaterfall(target, frame, signal);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
if (signal.aborted) return;
|
|
667
|
+
outcome = {
|
|
668
|
+
kind: "rejected",
|
|
669
|
+
error: projectRemoteEventRejection(error)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (signal.aborted) return;
|
|
673
|
+
const result = {
|
|
674
|
+
clientId,
|
|
675
|
+
eventId: frame.eventId,
|
|
676
|
+
outcome: outcome.kind === "result" && outcome.value === void 0 ? { kind: "result" } : outcome
|
|
677
|
+
};
|
|
678
|
+
const response = await this.connection.rpc.call("/api", REMOTE_EVENT_RESULT_ENDPOINT, { args: result }, signal);
|
|
679
|
+
if (!response.ok) throw new Error(response.error.message);
|
|
680
|
+
}
|
|
681
|
+
async dispatchWaterfall(target, frame, signal) {
|
|
682
|
+
const request = {
|
|
683
|
+
...frame.request,
|
|
684
|
+
agent: target,
|
|
685
|
+
signal
|
|
686
|
+
};
|
|
687
|
+
const value = await abortable(Promise.resolve(privateEvents(target).waterfall(target, this.eventKey(frame.event), request, () => Promise.resolve(REMOTE_EVENT_NEXT))), signal);
|
|
688
|
+
if (value !== REMOTE_EVENT_NEXT && value !== void 0 && !isRemoteJsonValue(value)) throw new TypeError("Remote event listener result is not lossless JSON data");
|
|
689
|
+
return value === REMOTE_EVENT_NEXT ? { kind: "next" } : {
|
|
690
|
+
kind: "result",
|
|
691
|
+
value
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
eventKey(event) {
|
|
695
|
+
return `${this.eventPrefix}${event}`;
|
|
696
|
+
}
|
|
697
|
+
reportError(event, error) {
|
|
698
|
+
console.error(`client api: Remote event ${JSON.stringify(event)} listener threw:`, error);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
/** Validate and return one generation's Client identity and Host facts. */
|
|
702
|
+
function parseRemoteEventReady(value) {
|
|
703
|
+
if (!isRemoteEventRecord(value) || !hasExactRemoteEventKeys(value, [
|
|
704
|
+
"type",
|
|
705
|
+
"clientId",
|
|
706
|
+
"host"
|
|
707
|
+
]) || value.type !== "ready" || !isRemoteEventClientId(value.clientId) || !isRemoteEventRecord(value.host) || !hasExactRemoteEventKeys(value.host, ["home"]) || typeof value.host.home !== "string") throw new TypeError("client api: forwarded Remote event stream did not begin with ready");
|
|
708
|
+
return {
|
|
709
|
+
clientId: value.clientId,
|
|
710
|
+
host: { home: value.host.home }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
/** Validate one untrusted value from the Gateway-internal forwarded-event stream. */
|
|
714
|
+
function parseRemoteEventFrame(value) {
|
|
715
|
+
if (!isRemoteEventRecord(value)) invalidRemoteEventFrame();
|
|
716
|
+
if (value.type === "cancel" && hasExactRemoteEventKeys(value, ["type", "eventId"]) && isRemoteEventId(value.eventId)) return {
|
|
717
|
+
type: "cancel",
|
|
718
|
+
eventId: value.eventId
|
|
719
|
+
};
|
|
720
|
+
if (value.type === "emit" && hasExactRemoteEventKeys(value, [
|
|
721
|
+
"type",
|
|
722
|
+
"event",
|
|
723
|
+
"args"
|
|
724
|
+
]) && validRemoteEventName(value.event) && Array.isArray(value.args) && isRemoteJsonValue(value.args)) return {
|
|
725
|
+
type: "emit",
|
|
726
|
+
event: value.event,
|
|
727
|
+
args: value.args
|
|
728
|
+
};
|
|
729
|
+
if (value.type === "waterfall" && hasExactRemoteEventKeys(value, [
|
|
730
|
+
"type",
|
|
731
|
+
"event",
|
|
732
|
+
"eventId",
|
|
733
|
+
"agentId",
|
|
734
|
+
"request"
|
|
735
|
+
]) && validRemoteEventName(value.event) && isRemoteEventId(value.eventId) && isRemoteEventAgentId(value.agentId) && isRemoteEventRecord(value.request) && !Object.hasOwn(value.request, "agent") && !Object.hasOwn(value.request, "signal") && isRemoteJsonValue(value.request)) return {
|
|
736
|
+
type: "waterfall",
|
|
737
|
+
event: value.event,
|
|
738
|
+
eventId: value.eventId,
|
|
739
|
+
agentId: value.agentId,
|
|
740
|
+
request: value.request
|
|
741
|
+
};
|
|
742
|
+
invalidRemoteEventFrame();
|
|
743
|
+
}
|
|
744
|
+
function isRemoteEventRecord(value) {
|
|
745
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
746
|
+
const prototype = Object.getPrototypeOf(value);
|
|
747
|
+
return prototype === Object.prototype || prototype === null;
|
|
748
|
+
}
|
|
749
|
+
function hasExactRemoteEventKeys(value, keys) {
|
|
750
|
+
return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
751
|
+
}
|
|
752
|
+
function validRemoteEventName(value) {
|
|
753
|
+
return typeof value === "string" && value.length > 0;
|
|
754
|
+
}
|
|
755
|
+
function invalidRemoteEventFrame() {
|
|
756
|
+
throw new TypeError("client api: invalid forwarded Remote event frame");
|
|
757
|
+
}
|
|
758
|
+
/** Race listener completion against its delivery lifetime. */
|
|
759
|
+
async function abortable(value, signal) {
|
|
760
|
+
signal.throwIfAborted();
|
|
761
|
+
let rejectAbort;
|
|
762
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
763
|
+
rejectAbort = reject;
|
|
764
|
+
});
|
|
765
|
+
const onAbort = () => {
|
|
766
|
+
rejectAbort?.(signal.reason);
|
|
767
|
+
};
|
|
768
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
769
|
+
try {
|
|
770
|
+
return await Promise.race([Promise.resolve(value), aborted]);
|
|
771
|
+
} finally {
|
|
772
|
+
signal.removeEventListener("abort", onAbort);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function privateEvents(ctx) {
|
|
776
|
+
return ctx;
|
|
777
|
+
}
|
|
778
|
+
function toError(reason, message) {
|
|
779
|
+
return reason instanceof Error ? reason : new Error(message, { cause: reason });
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region lib/types/client/remote-stream.js
|
|
783
|
+
/** Reconnecting lifecycle for one single-consumer Remote stream. */
|
|
784
|
+
/**
|
|
785
|
+
* Reopens one logical Remote stream across carrier generations.
|
|
786
|
+
*
|
|
787
|
+
* Connection owns physical retry timing; Gateway performs each requested
|
|
788
|
+
* replacement. The domain consumer owns its opening item and every later
|
|
789
|
+
* item, and calls {@link RemoteStreamItem.accept} only after validating the
|
|
790
|
+
* opening baseline or cursor.
|
|
791
|
+
*/
|
|
792
|
+
var RemoteStream = class {
|
|
793
|
+
connection;
|
|
794
|
+
options;
|
|
795
|
+
lifetime = new AbortController();
|
|
796
|
+
generationAbort;
|
|
797
|
+
iterator;
|
|
798
|
+
closing;
|
|
799
|
+
revision = 0;
|
|
800
|
+
taken = false;
|
|
801
|
+
/**
|
|
802
|
+
* @param connection - observable Host generation source used to pace retries.
|
|
803
|
+
* @param options - domain stream opener, end classification, and diagnostics.
|
|
804
|
+
*/
|
|
805
|
+
constructor(connection, options) {
|
|
806
|
+
this.connection = connection;
|
|
807
|
+
this.options = options;
|
|
808
|
+
}
|
|
809
|
+
/** Cancellation lifetime shared by the stream and sibling page requests. */
|
|
810
|
+
get signal() {
|
|
811
|
+
return this.lifetime.signal;
|
|
812
|
+
}
|
|
813
|
+
/** Interrupt the current generation and immediately request a replacement. */
|
|
814
|
+
restart() {
|
|
815
|
+
if (this.lifetime.signal.aborted) return;
|
|
816
|
+
this.revision++;
|
|
817
|
+
this.generationAbort?.abort(/* @__PURE__ */ new Error(`${this.options.name} generation restarted`));
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Permanently stop this stream and wait for its iterator to close.
|
|
821
|
+
* @returns when the active generation and consumer iterator are quiescent.
|
|
822
|
+
*/
|
|
823
|
+
dispose() {
|
|
824
|
+
if (this.closing !== void 0) return this.closing;
|
|
825
|
+
if (!this.lifetime.signal.aborted) {
|
|
826
|
+
const reason = /* @__PURE__ */ new Error(`${this.options.name} disposed`);
|
|
827
|
+
this.lifetime.abort(reason);
|
|
828
|
+
this.generationAbort?.abort(reason);
|
|
829
|
+
}
|
|
830
|
+
const iterator = this.iterator;
|
|
831
|
+
if (iterator === void 0) return Promise.resolve();
|
|
832
|
+
const closing = closeRemoteStreamIterator(iterator);
|
|
833
|
+
this.closing = closing;
|
|
834
|
+
return closing;
|
|
835
|
+
}
|
|
836
|
+
/** @inheritdoc */
|
|
837
|
+
[Symbol.asyncIterator]() {
|
|
838
|
+
if (this.taken) throw new Error(`${this.options.name} already has a consumer`);
|
|
839
|
+
this.taken = true;
|
|
840
|
+
const iterator = this.read();
|
|
841
|
+
this.iterator = iterator;
|
|
842
|
+
return iterator;
|
|
843
|
+
}
|
|
844
|
+
async *read() {
|
|
845
|
+
let attempt = 0;
|
|
846
|
+
let generation = 0;
|
|
847
|
+
let observedRevision = this.revision;
|
|
848
|
+
try {
|
|
849
|
+
while (!isAborted(this.lifetime.signal)) {
|
|
850
|
+
if (observedRevision !== this.revision) {
|
|
851
|
+
observedRevision = this.revision;
|
|
852
|
+
attempt = 0;
|
|
853
|
+
}
|
|
854
|
+
const revision = this.revision;
|
|
855
|
+
const generationAbort = new AbortController();
|
|
856
|
+
this.generationAbort = generationAbort;
|
|
857
|
+
const signal = AbortSignal.any([this.lifetime.signal, generationAbort.signal]);
|
|
858
|
+
const generationId = ++generation;
|
|
859
|
+
let accepted = false;
|
|
860
|
+
try {
|
|
861
|
+
for await (const value of this.options.open(signal)) {
|
|
862
|
+
if (isAborted(this.lifetime.signal)) return;
|
|
863
|
+
if (revision !== this.revision) break;
|
|
864
|
+
yield {
|
|
865
|
+
generation: generationId,
|
|
866
|
+
value,
|
|
867
|
+
signal,
|
|
868
|
+
accept: () => {
|
|
869
|
+
if (this.generationAbort !== generationAbort || revision !== this.revision) return;
|
|
870
|
+
accepted = true;
|
|
871
|
+
attempt = 0;
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
if (isAborted(this.lifetime.signal)) return;
|
|
876
|
+
if (revision !== this.revision) continue;
|
|
877
|
+
throw this.options.ended(accepted);
|
|
878
|
+
} catch (error) {
|
|
879
|
+
if (isAborted(this.lifetime.signal)) return;
|
|
880
|
+
if (revision !== this.revision) continue;
|
|
881
|
+
if (!(error instanceof RemoteStreamCarrierError)) throw terminalStreamFailure(error);
|
|
882
|
+
this.options.carrierFailed?.(error);
|
|
883
|
+
if (revision !== this.revision) continue;
|
|
884
|
+
attempt++;
|
|
885
|
+
try {
|
|
886
|
+
await waitForRemoteStreamRetry(this.connection, error, attempt, signal);
|
|
887
|
+
} catch (retryError) {
|
|
888
|
+
if (isAborted(this.lifetime.signal)) return;
|
|
889
|
+
if (revision !== this.revision) continue;
|
|
890
|
+
throw terminalStreamFailure(retryError);
|
|
891
|
+
}
|
|
892
|
+
} finally {
|
|
893
|
+
this.generationAbort = void 0;
|
|
894
|
+
if (!generationAbort.signal.aborted) generationAbort.abort(/* @__PURE__ */ new Error(`${this.options.name} generation ended`));
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
} finally {
|
|
898
|
+
if (!this.lifetime.signal.aborted) this.lifetime.abort(/* @__PURE__ */ new Error(`${this.options.name} consumer closed`));
|
|
899
|
+
this.generationAbort?.abort(this.lifetime.signal.reason);
|
|
900
|
+
this.generationAbort = void 0;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
async function waitForRemoteStreamRetry(connection, error, attempt, signal) {
|
|
905
|
+
signal.throwIfAborted();
|
|
906
|
+
if (connection.generation.getSnapshot() !== void 0) {
|
|
907
|
+
if (attempt === 1) return;
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
await new Promise((resolve, reject) => {
|
|
911
|
+
const subscription = { finished: false };
|
|
912
|
+
const finish = (failure) => {
|
|
913
|
+
if (subscription.finished) return;
|
|
914
|
+
subscription.finished = true;
|
|
915
|
+
subscription.dispose?.();
|
|
916
|
+
signal.removeEventListener("abort", aborted);
|
|
917
|
+
if (failure === void 0) resolve();
|
|
918
|
+
else reject(failure);
|
|
919
|
+
};
|
|
920
|
+
const inspect = () => {
|
|
921
|
+
if (connection.generation.getSnapshot() !== void 0) finish();
|
|
922
|
+
};
|
|
923
|
+
const aborted = () => {
|
|
924
|
+
finish(new Error("Remote stream retry aborted", { cause: signal.reason }));
|
|
925
|
+
};
|
|
926
|
+
const dispose = connection.generation.subscribe(inspect);
|
|
927
|
+
subscription.dispose = dispose;
|
|
928
|
+
if (subscription.finished) dispose();
|
|
929
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
930
|
+
if (signal.aborted) aborted();
|
|
931
|
+
else inspect();
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Mark a terminal escape before it crosses the stream boundary: consumers
|
|
936
|
+
* discriminate failures by code, so an unmarked throw reads as a local bug.
|
|
937
|
+
* Marked failures pass through verbatim. The carrier class never escapes as a
|
|
938
|
+
* terminal outcome — it stays the retry-internal signal fed to `carrierFailed`
|
|
939
|
+
* and the `ended(true)` retry trigger.
|
|
940
|
+
*/
|
|
941
|
+
function terminalStreamFailure(error) {
|
|
942
|
+
return remoteErrorOf(error) ?? new RemoteError("gateway/internal", error instanceof Error ? error.message : String(error), {}, { cause: error });
|
|
943
|
+
}
|
|
944
|
+
function isAborted(signal) {
|
|
945
|
+
return signal.aborted;
|
|
946
|
+
}
|
|
947
|
+
async function closeRemoteStreamIterator(iterator) {
|
|
948
|
+
try {
|
|
949
|
+
await iterator.return?.();
|
|
950
|
+
} catch {}
|
|
951
|
+
}
|
|
952
|
+
//#endregion
|
|
953
|
+
//#region lib/types/client/journal-stream.js
|
|
954
|
+
/** Cursor, page, and live-tail coordination over a reconnecting Remote stream. */
|
|
955
|
+
/** Host-side stream protocol violation, marked so consumers surface it as an error state. */
|
|
956
|
+
function protocolViolation$1(message) {
|
|
957
|
+
return new RemoteError("gateway/internal", message, {});
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Owns snapshot-first opening, ordered live delivery, pagination, and repair.
|
|
961
|
+
*
|
|
962
|
+
* The domain retains its published window during reconnection. A replacement is
|
|
963
|
+
* published only after the opening page reaches the generation's cursor.
|
|
964
|
+
*/
|
|
965
|
+
var RemoteJournalStream = class {
|
|
966
|
+
options;
|
|
967
|
+
stream;
|
|
968
|
+
initialRequest;
|
|
969
|
+
resumeCursor;
|
|
970
|
+
hasResumeCursor = false;
|
|
971
|
+
generation = 0;
|
|
972
|
+
firstCursor;
|
|
973
|
+
lastCursor;
|
|
974
|
+
started = false;
|
|
975
|
+
opened = false;
|
|
976
|
+
disposed = false;
|
|
977
|
+
done;
|
|
978
|
+
closing;
|
|
979
|
+
pendingNext;
|
|
980
|
+
/**
|
|
981
|
+
* @param remote - Gateway factory for the reconnecting physical-generation stream.
|
|
982
|
+
* @param options - cursor algebra and domain publication sinks.
|
|
983
|
+
*/
|
|
984
|
+
constructor(remote, options) {
|
|
985
|
+
this.options = options;
|
|
986
|
+
this.stream = remote.$stream({
|
|
987
|
+
name: options.name,
|
|
988
|
+
open: (signal) => this.follow(this.initialRequest, signal),
|
|
989
|
+
ended: (accepted) => accepted ? new RemoteStreamCarrierError(`${options.name} ended without a terminal result`) : protocolViolation$1(`${this.hasResumeCursor ? "resumed " : ""}${options.name} ended before its opening cursor`),
|
|
990
|
+
...options.carrierFailed === void 0 ? {} : { carrierFailed: options.carrierFailed }
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
/** Cancellation lifetime shared by follow and page calls. */
|
|
994
|
+
get signal() {
|
|
995
|
+
return this.stream.signal;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Establish follow and publish the opening snapshot carried by its first frame.
|
|
999
|
+
* @param request - initial tail-page request.
|
|
1000
|
+
* @returns after the first complete window is published.
|
|
1001
|
+
*/
|
|
1002
|
+
async open(request) {
|
|
1003
|
+
if (this.started) throw new Error(`${this.options.name} already opened`);
|
|
1004
|
+
this.started = true;
|
|
1005
|
+
this.initialRequest = request;
|
|
1006
|
+
const iterator = this.stream[Symbol.asyncIterator]();
|
|
1007
|
+
try {
|
|
1008
|
+
const first = await this.takeNext(iterator);
|
|
1009
|
+
if (first.done) throw protocolViolation$1(`${this.options.name} ended before its opening cursor`);
|
|
1010
|
+
this.replaceGeneration(first.value, false);
|
|
1011
|
+
this.opened = true;
|
|
1012
|
+
this.done = this.consume(iterator);
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
await this.stream.dispose();
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Read and prepend one older page after a successful open.
|
|
1020
|
+
* @param request - domain page request bound to this stream's address.
|
|
1021
|
+
* @returns after the page is applied or rejected as discontinuous.
|
|
1022
|
+
*/
|
|
1023
|
+
async prepend(request) {
|
|
1024
|
+
if (!this.opened || this.disposed) throw new Error(`${this.options.name} is not open`);
|
|
1025
|
+
const page = await this.readPage(request, this.currentCursor(), this.stream.signal);
|
|
1026
|
+
this.stream.signal.throwIfAborted();
|
|
1027
|
+
const entries = this.options.entries(page);
|
|
1028
|
+
this.assertPage(entries);
|
|
1029
|
+
const before = this.firstCursor;
|
|
1030
|
+
const accepted = before === void 0 ? [...entries] : entries.filter((entry) => this.options.compare(this.options.first(entry), before) < 0);
|
|
1031
|
+
const tail = accepted.at(-1);
|
|
1032
|
+
if (tail !== void 0 && before !== void 0 && !this.options.follows(this.options.last(tail), before)) {
|
|
1033
|
+
this.options.publish({
|
|
1034
|
+
type: "prepend",
|
|
1035
|
+
page,
|
|
1036
|
+
entries: [],
|
|
1037
|
+
hasMore: false
|
|
1038
|
+
});
|
|
1039
|
+
throw protocolViolation$1(`${this.options.name} history page is discontinuous`);
|
|
1040
|
+
}
|
|
1041
|
+
const first = accepted[0];
|
|
1042
|
+
if (first !== void 0) this.firstCursor = this.options.first(first);
|
|
1043
|
+
this.options.publish({
|
|
1044
|
+
type: "prepend",
|
|
1045
|
+
page,
|
|
1046
|
+
entries: accepted,
|
|
1047
|
+
hasMore: this.options.hasMore(page)
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
/** Replace the active physical generation while retaining the published window. */
|
|
1051
|
+
restart() {
|
|
1052
|
+
this.stream.restart();
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Permanently stop follow, page requests, and the background consumer.
|
|
1056
|
+
* @returns when no stream work or publication callback can still run.
|
|
1057
|
+
*/
|
|
1058
|
+
dispose() {
|
|
1059
|
+
if (this.closing !== void 0) return this.closing;
|
|
1060
|
+
this.disposed = true;
|
|
1061
|
+
const done = this.done;
|
|
1062
|
+
const closing = (async () => {
|
|
1063
|
+
await this.stream.dispose();
|
|
1064
|
+
await done;
|
|
1065
|
+
})();
|
|
1066
|
+
this.closing = closing;
|
|
1067
|
+
return closing;
|
|
1068
|
+
}
|
|
1069
|
+
async consume(iterator) {
|
|
1070
|
+
try {
|
|
1071
|
+
while (true) {
|
|
1072
|
+
const next = await this.takeNext(iterator);
|
|
1073
|
+
if (next.done) return;
|
|
1074
|
+
const item = next.value;
|
|
1075
|
+
if (item.generation !== this.generation) {
|
|
1076
|
+
this.replaceGeneration(item, true);
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (item.value.type === "opened") throw protocolViolation$1(`${this.options.name} emitted more than one opening cursor`);
|
|
1080
|
+
await this.acceptEntry(item.value.entry, item, iterator);
|
|
1081
|
+
}
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
if (!this.disposed) this.options.failed(error);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
replaceGeneration(initial, resumed) {
|
|
1087
|
+
const opening = this.opening(initial, resumed);
|
|
1088
|
+
this.replaceFromOpening(opening.page, opening.cursor);
|
|
1089
|
+
}
|
|
1090
|
+
opening(item, resumed) {
|
|
1091
|
+
if (item.value.type !== "opened") throw protocolViolation$1(`${resumed ? "resumed " : ""}${this.options.name} emitted an entry before its opening cursor`);
|
|
1092
|
+
const cursor = item.value.cursor;
|
|
1093
|
+
if (resumed && this.lastCursor !== void 0 && this.options.compare(cursor, this.lastCursor) < 0) throw protocolViolation$1(`${this.options.name} resumed at a cursor behind the last applied entry`);
|
|
1094
|
+
this.generation = item.generation;
|
|
1095
|
+
item.accept();
|
|
1096
|
+
return {
|
|
1097
|
+
cursor,
|
|
1098
|
+
page: item.value.page
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
/** Publish a generation's opening page without issuing a second Remote call. */
|
|
1102
|
+
replaceFromOpening(page, cursor) {
|
|
1103
|
+
this.assertPageThrough(page, cursor);
|
|
1104
|
+
const entries = [...this.options.entries(page)];
|
|
1105
|
+
this.assertPage(entries);
|
|
1106
|
+
const first = entries[0];
|
|
1107
|
+
this.firstCursor = first === void 0 ? void 0 : this.options.first(first);
|
|
1108
|
+
this.lastCursor = cursor;
|
|
1109
|
+
this.setResumeCursor(cursor);
|
|
1110
|
+
this.options.publish({
|
|
1111
|
+
type: "replace",
|
|
1112
|
+
page,
|
|
1113
|
+
entries,
|
|
1114
|
+
hasMore: this.options.hasMore(page)
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
async acceptEntry(entry, item, iterator) {
|
|
1118
|
+
const { first, last: cursor } = this.entryRange(entry);
|
|
1119
|
+
const last = this.lastCursor;
|
|
1120
|
+
if (this.options.compare(cursor, last) <= 0) return;
|
|
1121
|
+
if (this.options.compare(first, last) <= 0) throw protocolViolation$1(`${this.options.name} emitted a partially overlapping entry`);
|
|
1122
|
+
if (!this.options.follows(last, first)) {
|
|
1123
|
+
const request = this.repairPageRequest();
|
|
1124
|
+
const superseded = await this.replaceThrough(request, cursor, item.generation, item.signal, iterator, [entry]);
|
|
1125
|
+
if (superseded !== void 0) this.replaceGeneration(superseded, true);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (this.firstCursor === void 0) this.firstCursor = first;
|
|
1129
|
+
this.lastCursor = cursor;
|
|
1130
|
+
this.setResumeCursor(cursor);
|
|
1131
|
+
this.options.publish({
|
|
1132
|
+
type: "append",
|
|
1133
|
+
entry
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
async replaceThrough(request, requiredCursor, generation, signal, iterator, queued) {
|
|
1137
|
+
let read = await this.readPageWhileFollowing(request, requiredCursor, generation, signal, iterator, queued);
|
|
1138
|
+
if (read.type === "superseded") return read.item;
|
|
1139
|
+
let page = read.page;
|
|
1140
|
+
this.assertPageThrough(page, requiredCursor);
|
|
1141
|
+
let entries = this.mergeReplacement(page, queued);
|
|
1142
|
+
let target = this.maxCursor(requiredCursor, queued);
|
|
1143
|
+
if (entries === void 0 || this.options.compare(this.tailCursor(entries), target) < 0) {
|
|
1144
|
+
read = await this.readPageWhileFollowing(this.repairPageRequest(), target, generation, signal, iterator, queued);
|
|
1145
|
+
if (read.type === "superseded") return read.item;
|
|
1146
|
+
page = read.page;
|
|
1147
|
+
this.assertPageThrough(page, target);
|
|
1148
|
+
entries = this.mergeReplacement(page, queued);
|
|
1149
|
+
target = this.maxCursor(requiredCursor, queued);
|
|
1150
|
+
}
|
|
1151
|
+
if (entries === void 0 || this.options.compare(this.tailCursor(entries), target) < 0) throw protocolViolation$1(`${this.options.name} page did not reach its opening cursor`);
|
|
1152
|
+
const first = entries[0];
|
|
1153
|
+
/* v8 ignore next -- a successful positive-cursor replacement page cannot be empty. */
|
|
1154
|
+
this.firstCursor = first === void 0 ? void 0 : this.options.first(first);
|
|
1155
|
+
this.lastCursor = this.tailCursor(entries);
|
|
1156
|
+
this.setResumeCursor(this.lastCursor);
|
|
1157
|
+
this.options.publish({
|
|
1158
|
+
type: "replace",
|
|
1159
|
+
page,
|
|
1160
|
+
entries,
|
|
1161
|
+
hasMore: this.options.hasMore(page)
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
async readPageWhileFollowing(request, through, generation, signal, iterator, queued) {
|
|
1165
|
+
const page = this.readPage(request, through, signal).then((value) => ({
|
|
1166
|
+
type: "page",
|
|
1167
|
+
value
|
|
1168
|
+
}), (error) => ({
|
|
1169
|
+
type: "page-error",
|
|
1170
|
+
error
|
|
1171
|
+
}));
|
|
1172
|
+
while (true) {
|
|
1173
|
+
const pending = this.nextResult(iterator);
|
|
1174
|
+
const next = pending.then((value) => ({
|
|
1175
|
+
type: "next",
|
|
1176
|
+
value
|
|
1177
|
+
}), (error) => ({
|
|
1178
|
+
type: "next-error",
|
|
1179
|
+
error
|
|
1180
|
+
}));
|
|
1181
|
+
const result = await Promise.race([page, next]);
|
|
1182
|
+
if (result.type === "page") {
|
|
1183
|
+
signal.throwIfAborted();
|
|
1184
|
+
return {
|
|
1185
|
+
type: "page",
|
|
1186
|
+
page: result.value
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
if (result.type === "page-error") {
|
|
1190
|
+
if (!signal.aborted || this.stream.signal.aborted) throw result.error;
|
|
1191
|
+
return this.awaitReplacementGeneration(generation, iterator, pending);
|
|
1192
|
+
}
|
|
1193
|
+
this.releaseNext();
|
|
1194
|
+
if (result.type === "next-error") throw result.error;
|
|
1195
|
+
if (result.value.done) {
|
|
1196
|
+
signal.throwIfAborted();
|
|
1197
|
+
throw protocolViolation$1(`${this.options.name} ended while reading its replacement page`);
|
|
1198
|
+
}
|
|
1199
|
+
const item = result.value.value;
|
|
1200
|
+
if (item.generation !== generation) return {
|
|
1201
|
+
type: "superseded",
|
|
1202
|
+
item
|
|
1203
|
+
};
|
|
1204
|
+
if (item.value.type === "opened") throw protocolViolation$1(`${this.options.name} emitted more than one opening cursor`);
|
|
1205
|
+
queued.push(item.value.entry);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
async awaitReplacementGeneration(generation, iterator, initial) {
|
|
1209
|
+
let pending = initial;
|
|
1210
|
+
while (true) {
|
|
1211
|
+
let next;
|
|
1212
|
+
try {
|
|
1213
|
+
next = await pending;
|
|
1214
|
+
} finally {
|
|
1215
|
+
this.releaseNext();
|
|
1216
|
+
}
|
|
1217
|
+
if (next.done) {
|
|
1218
|
+
this.stream.signal.throwIfAborted();
|
|
1219
|
+
throw protocolViolation$1(`${this.options.name} ended while replacing an aborted page generation`);
|
|
1220
|
+
}
|
|
1221
|
+
const item = next.value;
|
|
1222
|
+
if (item.generation !== generation) return {
|
|
1223
|
+
type: "superseded",
|
|
1224
|
+
item
|
|
1225
|
+
};
|
|
1226
|
+
if (item.value.type === "opened") throw protocolViolation$1(`${this.options.name} emitted more than one opening cursor`);
|
|
1227
|
+
pending = this.nextResult(iterator);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
mergeReplacement(page, queued) {
|
|
1231
|
+
const entries = [...this.options.entries(page)];
|
|
1232
|
+
this.assertPage(entries);
|
|
1233
|
+
for (const entry of queued) this.entryRange(entry);
|
|
1234
|
+
const sorted = [...queued].sort((left, right) => this.options.compare(this.options.first(left), this.options.first(right)));
|
|
1235
|
+
let tail = this.tailCursor(entries);
|
|
1236
|
+
for (const entry of sorted) {
|
|
1237
|
+
const first = this.options.first(entry);
|
|
1238
|
+
const last = this.options.last(entry);
|
|
1239
|
+
if (this.options.compare(last, tail) <= 0) continue;
|
|
1240
|
+
if (this.options.compare(first, tail) <= 0) throw protocolViolation$1(`${this.options.name} replacement contains a partially overlapping entry`);
|
|
1241
|
+
if (!this.options.follows(tail, first)) return void 0;
|
|
1242
|
+
entries.push(entry);
|
|
1243
|
+
tail = last;
|
|
1244
|
+
}
|
|
1245
|
+
return entries;
|
|
1246
|
+
}
|
|
1247
|
+
maxCursor(cursor, entries) {
|
|
1248
|
+
let result = cursor;
|
|
1249
|
+
for (const entry of entries) {
|
|
1250
|
+
const candidate = this.options.last(entry);
|
|
1251
|
+
if (this.options.compare(candidate, result) > 0) result = candidate;
|
|
1252
|
+
}
|
|
1253
|
+
return result;
|
|
1254
|
+
}
|
|
1255
|
+
nextResult(iterator) {
|
|
1256
|
+
this.pendingNext ??= iterator.next();
|
|
1257
|
+
return this.pendingNext;
|
|
1258
|
+
}
|
|
1259
|
+
async takeNext(iterator) {
|
|
1260
|
+
const pending = this.nextResult(iterator);
|
|
1261
|
+
try {
|
|
1262
|
+
return await pending;
|
|
1263
|
+
} finally {
|
|
1264
|
+
this.releaseNext();
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
releaseNext() {
|
|
1268
|
+
this.pendingNext = void 0;
|
|
1269
|
+
}
|
|
1270
|
+
repairPageRequest() {
|
|
1271
|
+
return this.repairRequest(this.initialRequest);
|
|
1272
|
+
}
|
|
1273
|
+
setResumeCursor(cursor) {
|
|
1274
|
+
this.resumeCursor = cursor;
|
|
1275
|
+
this.hasResumeCursor = true;
|
|
1276
|
+
}
|
|
1277
|
+
currentCursor() {
|
|
1278
|
+
return this.resumeCursor;
|
|
1279
|
+
}
|
|
1280
|
+
tailCursor(entries) {
|
|
1281
|
+
const tail = entries.at(-1);
|
|
1282
|
+
return tail === void 0 ? this.options.emptyCursor : this.options.last(tail);
|
|
1283
|
+
}
|
|
1284
|
+
assertPage(entries) {
|
|
1285
|
+
const iterator = entries[Symbol.iterator]();
|
|
1286
|
+
const first = iterator.next();
|
|
1287
|
+
if (first.done) return;
|
|
1288
|
+
let previousRange = this.entryRange(first.value);
|
|
1289
|
+
for (const entry of iterator) {
|
|
1290
|
+
const range = this.entryRange(entry);
|
|
1291
|
+
if (!this.options.follows(previousRange.last, range.first)) throw protocolViolation$1(`${this.options.name} page contains discontinuous entries`);
|
|
1292
|
+
previousRange = range;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
entryRange(entry) {
|
|
1296
|
+
const first = this.options.first(entry);
|
|
1297
|
+
const last = this.options.last(entry);
|
|
1298
|
+
if (this.options.compare(first, last) > 0) throw protocolViolation$1(`${this.options.name} entry has an inverted cursor range`);
|
|
1299
|
+
return {
|
|
1300
|
+
first,
|
|
1301
|
+
last
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
assertPageThrough(page, through) {
|
|
1305
|
+
const tail = this.tailCursor(this.options.entries(page));
|
|
1306
|
+
if (this.options.compare(tail, through) !== 0) throw protocolViolation$1(`${this.options.name} page did not end at its requested cursor`);
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region lib/types/client/snapshot-stream.js
|
|
1311
|
+
/** Baseline-and-delta protocol layered over a reconnecting Remote stream. */
|
|
1312
|
+
/** Host-side stream protocol violation, marked so consumers surface it as an error state. */
|
|
1313
|
+
function protocolViolation(message) {
|
|
1314
|
+
return new RemoteError("gateway/internal", message, {});
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Consumes generations that each contain exactly one opening snapshot followed by deltas.
|
|
1318
|
+
*
|
|
1319
|
+
* The previous domain snapshot remains published while the underlying stream retries. A
|
|
1320
|
+
* replacement becomes accepted only after the domain owner applies it successfully.
|
|
1321
|
+
*/
|
|
1322
|
+
var RemoteSnapshotStream = class {
|
|
1323
|
+
stream;
|
|
1324
|
+
options;
|
|
1325
|
+
started = false;
|
|
1326
|
+
disposed = false;
|
|
1327
|
+
done;
|
|
1328
|
+
/**
|
|
1329
|
+
* @param stream - reconnecting physical-generation stream.
|
|
1330
|
+
* @param options - frame discriminator and domain state destinations.
|
|
1331
|
+
*/
|
|
1332
|
+
constructor(stream, options) {
|
|
1333
|
+
this.stream = stream;
|
|
1334
|
+
this.options = options;
|
|
1335
|
+
}
|
|
1336
|
+
/** Start the single consumer; repeated calls are inert. */
|
|
1337
|
+
start() {
|
|
1338
|
+
if (this.started) return;
|
|
1339
|
+
this.started = true;
|
|
1340
|
+
this.done = this.consume();
|
|
1341
|
+
}
|
|
1342
|
+
/** Replace the active physical generation without discarding the published snapshot. */
|
|
1343
|
+
restart() {
|
|
1344
|
+
this.stream.restart();
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Permanently stop the stream and wait for its consumer to become quiescent.
|
|
1348
|
+
* @returns when no generation or callback can still run.
|
|
1349
|
+
*/
|
|
1350
|
+
async dispose() {
|
|
1351
|
+
this.disposed = true;
|
|
1352
|
+
await this.stream.dispose();
|
|
1353
|
+
await this.done;
|
|
1354
|
+
}
|
|
1355
|
+
async consume() {
|
|
1356
|
+
let generation = 0;
|
|
1357
|
+
let snapshotSeen = false;
|
|
1358
|
+
try {
|
|
1359
|
+
for await (const item of this.stream) {
|
|
1360
|
+
if (item.generation !== generation) {
|
|
1361
|
+
generation = item.generation;
|
|
1362
|
+
snapshotSeen = false;
|
|
1363
|
+
}
|
|
1364
|
+
if (this.options.isSnapshot(item.value)) {
|
|
1365
|
+
if (snapshotSeen) throw protocolViolation(`${this.options.name} emitted more than one opening snapshot`);
|
|
1366
|
+
this.options.replace(item.value);
|
|
1367
|
+
snapshotSeen = true;
|
|
1368
|
+
item.accept();
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
if (!snapshotSeen) throw protocolViolation(`${this.options.name} emitted an update before its opening snapshot`);
|
|
1372
|
+
this.options.update(item.value);
|
|
1373
|
+
}
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
if (!this.disposed) this.options.failed(error);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
//#endregion
|
|
8
1380
|
//#region lib/types/client/index.js
|
|
9
1381
|
/**
|
|
10
1382
|
* Client projection of generated Typert Remote descriptors. Contributions
|
|
@@ -22,15 +1394,53 @@ window.__ModuleLoader__.load({
|
|
|
22
1394
|
}
|
|
23
1395
|
var ClientRemoteService = class extends _deepseek_ai_cordis.Service {
|
|
24
1396
|
ownerCtx;
|
|
1397
|
+
connection;
|
|
25
1398
|
namespaces = /* @__PURE__ */ new Map();
|
|
26
|
-
|
|
1399
|
+
hostFacts;
|
|
1400
|
+
streams = new RemoteStreamMuxClient();
|
|
1401
|
+
events;
|
|
27
1402
|
mutations = Promise.resolve();
|
|
28
1403
|
constructor(ctx) {
|
|
29
1404
|
super(ctx, "remote");
|
|
30
1405
|
this.ownerCtx = ctx;
|
|
31
|
-
ctx.
|
|
32
|
-
|
|
33
|
-
|
|
1406
|
+
const connection = ctx.get("connection");
|
|
1407
|
+
this.connection = connection;
|
|
1408
|
+
this.events = new ClientRemoteEvents(ctx, connection, (endpoint, payload, signal) => this.openRemoteStream(endpoint, payload, signal));
|
|
1409
|
+
if (connection.rpc.open === void 0) this.streams.start();
|
|
1410
|
+
let disposed = false;
|
|
1411
|
+
let loop;
|
|
1412
|
+
const start = () => {
|
|
1413
|
+
if (disposed) return;
|
|
1414
|
+
if (connection.rpc.open === void 0) this.streams.start();
|
|
1415
|
+
loop = connection.start({
|
|
1416
|
+
onConnected: () => {
|
|
1417
|
+
this.ownerCtx.emit("connection/reset");
|
|
1418
|
+
},
|
|
1419
|
+
onReconnectRequested: () => {
|
|
1420
|
+
if (connection.rpc.open === void 0) this.streams.reconnect();
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
};
|
|
1424
|
+
const loader = ctx.get("loader");
|
|
1425
|
+
if (loader === void 0) start();
|
|
1426
|
+
else loader.await().then(start, () => {});
|
|
1427
|
+
ctx.effect(() => async () => {
|
|
1428
|
+
disposed = true;
|
|
1429
|
+
loop?.stop();
|
|
1430
|
+
await this.events.dispose();
|
|
1431
|
+
await this.streams.close();
|
|
1432
|
+
}, "api-gateway.client.transport");
|
|
1433
|
+
}
|
|
1434
|
+
$stream(options) {
|
|
1435
|
+
return new RemoteStream(this.connection, options);
|
|
1436
|
+
}
|
|
1437
|
+
get $host() {
|
|
1438
|
+
const home = this.connection.generation.getSnapshot()?.host.home;
|
|
1439
|
+
if (this.hostFacts === void 0 || this.hostFacts.home !== home) this.hostFacts = {
|
|
1440
|
+
home,
|
|
1441
|
+
isLoopback: this.connection.isLoopback
|
|
1442
|
+
};
|
|
1443
|
+
return this.hostFacts;
|
|
34
1444
|
}
|
|
35
1445
|
async $mount(contribution) {
|
|
36
1446
|
const callerCtx = this.ctx;
|
|
@@ -44,48 +1454,14 @@ window.__ModuleLoader__.load({
|
|
|
44
1454
|
};
|
|
45
1455
|
}
|
|
46
1456
|
$on(event, listener) {
|
|
47
|
-
|
|
48
|
-
const owned = this.ctx.effect(() => {
|
|
49
|
-
const listeners = this.listeners(event);
|
|
50
|
-
listeners.push(subscription);
|
|
51
|
-
return () => {
|
|
52
|
-
const at = listeners.indexOf(subscription);
|
|
53
|
-
/* v8 ignore next -- listener */
|
|
54
|
-
if (at >= 0) listeners.splice(at, 1);
|
|
55
|
-
};
|
|
56
|
-
}, `api-gateway.client.$on(${JSON.stringify(event)})`);
|
|
57
|
-
return () => {
|
|
58
|
-
owned();
|
|
59
|
-
};
|
|
1457
|
+
return this.events.subscribe(this.ctx, event, listener);
|
|
60
1458
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const listeners = this.subscriptions.get(event);
|
|
68
|
-
if (listeners === void 0) return;
|
|
69
|
-
for (const { listener } of [...listeners]) {
|
|
70
|
-
const report = (error) => {
|
|
71
|
-
console.error(`client api: Remote event ${JSON.stringify(event)} listener threw:`, error);
|
|
72
|
-
};
|
|
73
|
-
try {
|
|
74
|
-
const settled = listener(...args);
|
|
75
|
-
if (settled instanceof Promise) settled.catch(report);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
report(error);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Subscriptions for one event name; empty arrays are retained, bounded by the Host's selection. */
|
|
82
|
-
listeners(event) {
|
|
83
|
-
let listeners = this.subscriptions.get(event);
|
|
84
|
-
if (listeners === void 0) {
|
|
85
|
-
listeners = [];
|
|
86
|
-
this.subscriptions.set(event, listeners);
|
|
87
|
-
}
|
|
88
|
-
return listeners;
|
|
1459
|
+
/** Open one Remote stream and normalize a worker-local carrier's structural failures. */
|
|
1460
|
+
openRemoteStream(endpoint, payload, signal, noConnection = `client api: ${endpoint} has no active Connection`) {
|
|
1461
|
+
const connection = this.ownerCtx.get("connection");
|
|
1462
|
+
if (connection === void 0) throw new Error(noConnection);
|
|
1463
|
+
const local = connection.rpc.open?.("/api", endpoint, payload, signal);
|
|
1464
|
+
return local === void 0 ? this.streams.open(endpoint, payload, signal) : normalizeConnectionStream(local);
|
|
89
1465
|
}
|
|
90
1466
|
enqueue(operation) {
|
|
91
1467
|
const result = this.mutations.then(operation, operation);
|
|
@@ -205,15 +1581,50 @@ window.__ModuleLoader__.load({
|
|
|
205
1581
|
invokeMethod(direct, scoped, callerCtx, values) {
|
|
206
1582
|
if (scoped !== void 0) {
|
|
207
1583
|
const identity = this.ownerCtx.typert.contexts.getClient(scoped.projection.context)?.identity(callerCtx);
|
|
208
|
-
if (identity !== void 0) return this.
|
|
1584
|
+
if (identity !== void 0) return this.invokeSelected(scoped.descriptor, scoped.projection, scoped.token, callerCtx, values, { value: identity });
|
|
209
1585
|
}
|
|
210
|
-
if (direct !== void 0) return this.
|
|
211
|
-
if (scoped !== void 0) return this.
|
|
1586
|
+
if (direct !== void 0) return this.invokeSelected(direct.descriptor, void 0, direct.token, callerCtx, values);
|
|
1587
|
+
if (scoped !== void 0) return this.invokeSelected(scoped.descriptor, scoped.projection, scoped.token, callerCtx, values);
|
|
212
1588
|
throw new Error("client api: Remote method is no longer mounted");
|
|
213
1589
|
}
|
|
1590
|
+
invokeSelected(descriptor, projection, token, callerCtx, values, boundIdentity) {
|
|
1591
|
+
if (descriptor.mode === "stream") return this.invokeStream(descriptor, projection, token, callerCtx, values, boundIdentity);
|
|
1592
|
+
return this.invoke(descriptor, projection, token, callerCtx, values, boundIdentity);
|
|
1593
|
+
}
|
|
214
1594
|
async invoke(descriptor, projection, token, callerCtx, values, boundIdentity) {
|
|
215
1595
|
const endpoint = endpointOf(descriptor);
|
|
216
1596
|
if (!token.active) return withdrawn(endpoint);
|
|
1597
|
+
const prepared = this.prepareInvocation(descriptor, projection, token, callerCtx, values, boundIdentity);
|
|
1598
|
+
const connection = this.ownerCtx.get("connection");
|
|
1599
|
+
if (connection === void 0) throw new Error(`client api: ${endpoint} has no active Connection`);
|
|
1600
|
+
try {
|
|
1601
|
+
const result = await connection.rpc.call("/api", endpoint, { args: prepared.args }, prepared.signal);
|
|
1602
|
+
if (!mountActive(token)) return withdrawn(endpoint);
|
|
1603
|
+
if (!result.ok) return {
|
|
1604
|
+
ok: false,
|
|
1605
|
+
error: rebuiltFailure(result.error)
|
|
1606
|
+
};
|
|
1607
|
+
return {
|
|
1608
|
+
ok: true,
|
|
1609
|
+
value: result.value
|
|
1610
|
+
};
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
if (prepared.signal.aborted) return cancelledFailure(endpoint, error);
|
|
1613
|
+
return carrierFailure(endpoint, error);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async *invokeStream(descriptor, projection, token, callerCtx, values, boundIdentity) {
|
|
1617
|
+
const endpoint = endpointOf(descriptor);
|
|
1618
|
+
if (!token.active) throw new Error(withdrawn(endpoint).error.message);
|
|
1619
|
+
const prepared = this.prepareInvocation(descriptor, projection, token, callerCtx, values, boundIdentity);
|
|
1620
|
+
const stream = this.openRemoteStream(endpoint, { args: prepared.args }, prepared.signal);
|
|
1621
|
+
for await (const value of stream) {
|
|
1622
|
+
if (!mountActive(token)) throw new Error(withdrawn(endpoint).error.message);
|
|
1623
|
+
yield value;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
prepareInvocation(descriptor, projection, token, callerCtx, values, boundIdentity) {
|
|
1627
|
+
const endpoint = endpointOf(descriptor);
|
|
217
1628
|
const expected = descriptor.parameters.length - (projection?.parameterIndex === void 0 ? 0 : 1);
|
|
218
1629
|
const hasCallerSignal = descriptor.cancellation !== void 0 && values.length === expected + 1;
|
|
219
1630
|
if (values.length !== expected && !hasCallerSignal) {
|
|
@@ -222,37 +1633,25 @@ window.__ModuleLoader__.load({
|
|
|
222
1633
|
}
|
|
223
1634
|
const args = Object.create(null);
|
|
224
1635
|
if (projection !== void 0) {
|
|
225
|
-
const
|
|
226
|
-
if (boundIdentity === void 0 &&
|
|
227
|
-
const identity = boundIdentity === void 0 ?
|
|
1636
|
+
const adapter = boundIdentity === void 0 ? this.ownerCtx.typert.contexts.getClient(projection.context) : void 0;
|
|
1637
|
+
if (boundIdentity === void 0 && adapter === void 0) throw new Error(`client api: ${endpoint} has no Client Context adapter for ${JSON.stringify(projection.context)}`);
|
|
1638
|
+
const identity = boundIdentity === void 0 ? adapter?.identity(callerCtx) : boundIdentity.value;
|
|
228
1639
|
if (identity === void 0) throw new Error(`client api: ${endpoint} requires a ${JSON.stringify(projection.context)} Context`);
|
|
229
|
-
args[projection.wire] =
|
|
1640
|
+
args[projection.wire] = parseInput(projection.codec, identity, endpoint, projection.wire);
|
|
230
1641
|
}
|
|
231
1642
|
let valueIndex = 0;
|
|
232
1643
|
descriptor.parameters.forEach((parameter, parameterIndex) => {
|
|
233
1644
|
if (parameterIndex === projection?.parameterIndex) return;
|
|
234
|
-
const value =
|
|
1645
|
+
const value = parseInput(parameter.codec, values[valueIndex], endpoint, parameter.wire);
|
|
235
1646
|
if (value !== void 0) args[parameter.wire] = value;
|
|
236
1647
|
valueIndex += 1;
|
|
237
1648
|
});
|
|
238
|
-
const connection = this.ownerCtx.get("connection");
|
|
239
|
-
if (connection === void 0) throw new Error(`client api: ${endpoint} has no active Connection`);
|
|
240
1649
|
const callerSignal = hasCallerSignal ? values[expected] : void 0;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
ok: false,
|
|
247
|
-
error: result.error
|
|
248
|
-
};
|
|
249
|
-
return {
|
|
250
|
-
ok: true,
|
|
251
|
-
value: parse(descriptor.result, result.value, endpoint, "result")
|
|
252
|
-
};
|
|
253
|
-
} catch (error) {
|
|
254
|
-
return carrierFailure(endpoint, error);
|
|
255
|
-
}
|
|
1650
|
+
return {
|
|
1651
|
+
endpoint,
|
|
1652
|
+
args,
|
|
1653
|
+
signal: callerSignal === void 0 ? token.abort.signal : AbortSignal.any([token.abort.signal, callerSignal])
|
|
1654
|
+
};
|
|
256
1655
|
}
|
|
257
1656
|
};
|
|
258
1657
|
var RemoteNamespaceService = class RemoteNamespaceService extends _deepseek_ai_cordis.Service {
|
|
@@ -407,14 +1806,13 @@ window.__ModuleLoader__.load({
|
|
|
407
1806
|
}
|
|
408
1807
|
function requireStrictDescriptor(descriptor) {
|
|
409
1808
|
const endpoint = endpointOf(descriptor);
|
|
410
|
-
requireStrictCodec(descriptor.result, endpoint, "result");
|
|
411
1809
|
for (const parameter of descriptor.parameters) requireStrictCodec(parameter.codec, endpoint, parameter.wire);
|
|
412
1810
|
if (descriptor.invocation.kind === "context") requireStrictCodec(descriptor.invocation.codec, endpoint, descriptor.invocation.wire);
|
|
413
1811
|
}
|
|
414
1812
|
function requireStrictCodec(codec, endpoint, field) {
|
|
415
1813
|
if (codec.mode !== "strict") throw new Error(`client api: generated Remote ${endpoint} field ${JSON.stringify(field)} has no strict codec`);
|
|
416
1814
|
}
|
|
417
|
-
function
|
|
1815
|
+
function parseInput(codec, value, endpoint, field) {
|
|
418
1816
|
if (codec.mode !== "strict") throw new Error(`client api: generated Remote ${endpoint} field ${JSON.stringify(field)} has no strict codec`);
|
|
419
1817
|
try {
|
|
420
1818
|
return codec.schema.parse(value);
|
|
@@ -429,19 +1827,57 @@ window.__ModuleLoader__.load({
|
|
|
429
1827
|
function carrierFailure(endpoint, error) {
|
|
430
1828
|
return internalFailure(`client api: ${endpoint} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
431
1829
|
}
|
|
1830
|
+
function cancelledFailure(endpoint, cause) {
|
|
1831
|
+
return {
|
|
1832
|
+
ok: false,
|
|
1833
|
+
error: new RemoteError("gateway/cancelled", `client api: Remote invocation "${endpoint}" was aborted`, {}, { cause })
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
432
1836
|
function internalFailure(message) {
|
|
433
1837
|
return {
|
|
434
1838
|
ok: false,
|
|
435
|
-
error: {
|
|
436
|
-
code: "internal",
|
|
437
|
-
message,
|
|
438
|
-
details: {}
|
|
439
|
-
}
|
|
1839
|
+
error: new RemoteError("gateway/internal", message, {})
|
|
440
1840
|
};
|
|
441
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Whether a caught value is a Remote failure this face delivered or threw.
|
|
1844
|
+
* The one consumer-facing discrimination point: marked instances carry their
|
|
1845
|
+
* Host code; anything else is a local fault the caller should let crash.
|
|
1846
|
+
* @param error - a caught value.
|
|
1847
|
+
* @returns true when the value narrows to RemoteFailure.
|
|
1848
|
+
*/
|
|
1849
|
+
function isRemoteFailure(error) {
|
|
1850
|
+
return remoteErrorOf(error) !== void 0;
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Rebuild the wire failure as a local RemoteError instance so the error branch
|
|
1854
|
+
* carries a real Error and `throw result.error` keeps throw semantics. The code
|
|
1855
|
+
* is passed through verbatim without runtime validation: a code outside this
|
|
1856
|
+
* Client's merged map still surfaces as-is, so a newer Host stays readable.
|
|
1857
|
+
*/
|
|
1858
|
+
function rebuiltFailure(error) {
|
|
1859
|
+
return new RemoteError(error.code, error.message, error.details);
|
|
1860
|
+
}
|
|
1861
|
+
/** Preserve Gateway error classes across a worker transport's separately bundled page half. */
|
|
1862
|
+
async function* normalizeConnectionStream(source) {
|
|
1863
|
+
try {
|
|
1864
|
+
yield* source;
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
if (!(error instanceof Error)) throw error;
|
|
1867
|
+
const marker = error.dshRemoteStreamFailure;
|
|
1868
|
+
if (marker?.kind === "remote") throw new RemoteError(marker.code, error.message, marker.details);
|
|
1869
|
+
if (marker?.kind === "carrier") throw new RemoteStreamCarrierError(error.message, { cause: error });
|
|
1870
|
+
throw error;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
442
1873
|
//#endregion
|
|
1874
|
+
exports.RemoteJournalStream = RemoteJournalStream;
|
|
1875
|
+
exports.RemoteSnapshotStream = RemoteSnapshotStream;
|
|
1876
|
+
exports.RemoteStream = RemoteStream;
|
|
1877
|
+
exports.RemoteStreamCarrierError = RemoteStreamCarrierError;
|
|
443
1878
|
exports.apply = apply;
|
|
444
1879
|
exports.inject = inject;
|
|
1880
|
+
exports.isRemoteFailure = isRemoteFailure;
|
|
445
1881
|
return module.exports;
|
|
446
1882
|
}
|
|
447
1883
|
});
|