@naeemo/capnp 0.2.0 → 0.4.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/README.md +6 -1
- package/README.zh.md +5 -1
- package/dist/codegen/cli-v3.js +206 -25
- package/dist/codegen/cli-v3.js.map +1 -1
- package/dist/index.cjs +4138 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4084 -1
- package/dist/index.js.map +1 -1
- package/dist/rpc-connection-BKWQQ7f9.js +960 -0
- package/dist/rpc-connection-BKWQQ7f9.js.map +1 -0
- package/dist/rpc-connection-C2C1wyga.js +3 -0
- package/dist/rpc-connection-Dz3rYT1P.js +870 -0
- package/dist/rpc-connection-Dz3rYT1P.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
//#region src/rpc/four-tables.ts
|
|
2
|
+
/** Manages the question table for outbound calls */
|
|
3
|
+
var QuestionTable = class {
|
|
4
|
+
questions = /* @__PURE__ */ new Map();
|
|
5
|
+
nextId = 1;
|
|
6
|
+
/** Create a new question entry */
|
|
7
|
+
create() {
|
|
8
|
+
const id = this.allocateId();
|
|
9
|
+
let resolveCompletion;
|
|
10
|
+
let rejectCompletion;
|
|
11
|
+
const question = {
|
|
12
|
+
id,
|
|
13
|
+
isComplete: false,
|
|
14
|
+
finishSent: false,
|
|
15
|
+
completionPromise: new Promise((resolve, reject) => {
|
|
16
|
+
resolveCompletion = resolve;
|
|
17
|
+
rejectCompletion = reject;
|
|
18
|
+
}),
|
|
19
|
+
resolveCompletion,
|
|
20
|
+
rejectCompletion
|
|
21
|
+
};
|
|
22
|
+
this.questions.set(id, question);
|
|
23
|
+
return question;
|
|
24
|
+
}
|
|
25
|
+
/** Get a question by ID */
|
|
26
|
+
get(id) {
|
|
27
|
+
return this.questions.get(id);
|
|
28
|
+
}
|
|
29
|
+
/** Mark a question as complete */
|
|
30
|
+
complete(id, result) {
|
|
31
|
+
const question = this.questions.get(id);
|
|
32
|
+
if (question && !question.isComplete) {
|
|
33
|
+
question.isComplete = true;
|
|
34
|
+
question.resolveCompletion(result);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Mark a question as canceled */
|
|
38
|
+
cancel(id, error) {
|
|
39
|
+
const question = this.questions.get(id);
|
|
40
|
+
if (question && !question.isComplete) {
|
|
41
|
+
question.isComplete = true;
|
|
42
|
+
question.rejectCompletion(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Mark that Finish has been sent for a question */
|
|
46
|
+
markFinishSent(id) {
|
|
47
|
+
const question = this.questions.get(id);
|
|
48
|
+
if (question) question.finishSent = true;
|
|
49
|
+
}
|
|
50
|
+
/** Remove a question from the table (when both sides are done) */
|
|
51
|
+
remove(id) {
|
|
52
|
+
const question = this.questions.get(id);
|
|
53
|
+
if (question?.isComplete && question.finishSent) this.questions.delete(id);
|
|
54
|
+
}
|
|
55
|
+
/** Clean up all questions (e.g., on disconnect) */
|
|
56
|
+
clear() {
|
|
57
|
+
for (const question of this.questions.values()) if (!question.isComplete) question.rejectCompletion(/* @__PURE__ */ new Error("Connection closed"));
|
|
58
|
+
this.questions.clear();
|
|
59
|
+
this.nextId = 1;
|
|
60
|
+
}
|
|
61
|
+
allocateId() {
|
|
62
|
+
return this.nextId++;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Manages the answer table for inbound calls */
|
|
66
|
+
var AnswerTable = class {
|
|
67
|
+
answers = /* @__PURE__ */ new Map();
|
|
68
|
+
/** Create a new answer entry */
|
|
69
|
+
create(id) {
|
|
70
|
+
const answer = {
|
|
71
|
+
id,
|
|
72
|
+
isComplete: false,
|
|
73
|
+
returnSent: false,
|
|
74
|
+
finishReceived: false
|
|
75
|
+
};
|
|
76
|
+
this.answers.set(id, answer);
|
|
77
|
+
return answer;
|
|
78
|
+
}
|
|
79
|
+
/** Get an answer by ID */
|
|
80
|
+
get(id) {
|
|
81
|
+
return this.answers.get(id);
|
|
82
|
+
}
|
|
83
|
+
/** Mark that Return has been sent */
|
|
84
|
+
markReturnSent(id) {
|
|
85
|
+
const answer = this.answers.get(id);
|
|
86
|
+
if (answer) answer.returnSent = true;
|
|
87
|
+
}
|
|
88
|
+
/** Mark that Finish has been received */
|
|
89
|
+
markFinishReceived(id) {
|
|
90
|
+
const answer = this.answers.get(id);
|
|
91
|
+
if (answer) answer.finishReceived = true;
|
|
92
|
+
}
|
|
93
|
+
/** Remove an answer from the table (when both sides are done) */
|
|
94
|
+
remove(id) {
|
|
95
|
+
const answer = this.answers.get(id);
|
|
96
|
+
if (answer?.returnSent && answer.finishReceived) this.answers.delete(id);
|
|
97
|
+
}
|
|
98
|
+
/** Clean up all answers (e.g., on disconnect) */
|
|
99
|
+
clear() {
|
|
100
|
+
this.answers.clear();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
/** Manages the import table for capabilities received from remote */
|
|
104
|
+
var ImportTable = class {
|
|
105
|
+
imports = /* @__PURE__ */ new Map();
|
|
106
|
+
/** Add a new import */
|
|
107
|
+
add(id, isPromise) {
|
|
108
|
+
const importEntry = {
|
|
109
|
+
id,
|
|
110
|
+
refCount: 1,
|
|
111
|
+
isPromise
|
|
112
|
+
};
|
|
113
|
+
this.imports.set(id, importEntry);
|
|
114
|
+
return importEntry;
|
|
115
|
+
}
|
|
116
|
+
/** Get an import by ID */
|
|
117
|
+
get(id) {
|
|
118
|
+
return this.imports.get(id);
|
|
119
|
+
}
|
|
120
|
+
/** Increment reference count */
|
|
121
|
+
addRef(id) {
|
|
122
|
+
const importEntry = this.imports.get(id);
|
|
123
|
+
if (importEntry) importEntry.refCount++;
|
|
124
|
+
}
|
|
125
|
+
/** Decrement reference count, returns true if refCount reached 0 */
|
|
126
|
+
release(id, count) {
|
|
127
|
+
const importEntry = this.imports.get(id);
|
|
128
|
+
if (importEntry) {
|
|
129
|
+
importEntry.refCount -= count;
|
|
130
|
+
if (importEntry.refCount <= 0) {
|
|
131
|
+
this.imports.delete(id);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/** Mark a promise as resolved */
|
|
138
|
+
markResolved(id) {
|
|
139
|
+
const importEntry = this.imports.get(id);
|
|
140
|
+
if (importEntry) importEntry.isPromise = false;
|
|
141
|
+
}
|
|
142
|
+
/** Clean up all imports (e.g., on disconnect) */
|
|
143
|
+
clear() {
|
|
144
|
+
this.imports.clear();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/** Manages the export table for capabilities sent to remote */
|
|
148
|
+
var ExportTable = class {
|
|
149
|
+
exports = /* @__PURE__ */ new Map();
|
|
150
|
+
nextId = 1;
|
|
151
|
+
/** Add a new export */
|
|
152
|
+
add(capability, isPromise) {
|
|
153
|
+
const id = this.allocateId();
|
|
154
|
+
const exportEntry = {
|
|
155
|
+
id,
|
|
156
|
+
refCount: 1,
|
|
157
|
+
isPromise,
|
|
158
|
+
capability
|
|
159
|
+
};
|
|
160
|
+
this.exports.set(id, exportEntry);
|
|
161
|
+
return exportEntry;
|
|
162
|
+
}
|
|
163
|
+
/** Get an export by ID */
|
|
164
|
+
get(id) {
|
|
165
|
+
return this.exports.get(id);
|
|
166
|
+
}
|
|
167
|
+
/** Increment reference count */
|
|
168
|
+
addRef(id) {
|
|
169
|
+
const exportEntry = this.exports.get(id);
|
|
170
|
+
if (exportEntry) exportEntry.refCount++;
|
|
171
|
+
}
|
|
172
|
+
/** Decrement reference count, returns true if refCount reached 0 */
|
|
173
|
+
release(id, count) {
|
|
174
|
+
const exportEntry = this.exports.get(id);
|
|
175
|
+
if (exportEntry) {
|
|
176
|
+
exportEntry.refCount -= count;
|
|
177
|
+
if (exportEntry.refCount <= 0) {
|
|
178
|
+
this.exports.delete(id);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
/** Mark a promise as resolved */
|
|
185
|
+
markResolved(id) {
|
|
186
|
+
const exportEntry = this.exports.get(id);
|
|
187
|
+
if (exportEntry) exportEntry.isPromise = false;
|
|
188
|
+
}
|
|
189
|
+
/** Clean up all exports (e.g., on disconnect) */
|
|
190
|
+
clear() {
|
|
191
|
+
this.exports.clear();
|
|
192
|
+
this.nextId = 1;
|
|
193
|
+
}
|
|
194
|
+
allocateId() {
|
|
195
|
+
return this.nextId++;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/rpc/pipeline.ts
|
|
201
|
+
/**
|
|
202
|
+
* Tracks a chain of operations to apply to a promised answer.
|
|
203
|
+
* This forms the "transform" field in PromisedAnswer.
|
|
204
|
+
*/
|
|
205
|
+
var PipelineOpTracker = class PipelineOpTracker {
|
|
206
|
+
ops = [];
|
|
207
|
+
/**
|
|
208
|
+
* Add a no-op (use the result as-is)
|
|
209
|
+
*/
|
|
210
|
+
addNoop() {
|
|
211
|
+
this.ops.push({ type: "noop" });
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Add a pointer field access operation
|
|
215
|
+
*/
|
|
216
|
+
addGetPointerField(fieldIndex) {
|
|
217
|
+
this.ops.push({
|
|
218
|
+
type: "getPointerField",
|
|
219
|
+
fieldIndex
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get the current transform chain
|
|
224
|
+
*/
|
|
225
|
+
getTransform() {
|
|
226
|
+
return [...this.ops];
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Clone this tracker (for creating derived pipelines)
|
|
230
|
+
*/
|
|
231
|
+
clone() {
|
|
232
|
+
const cloned = new PipelineOpTracker();
|
|
233
|
+
cloned.ops = [...this.ops];
|
|
234
|
+
return cloned;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Symbol used to identify pipeline clients internally
|
|
239
|
+
*/
|
|
240
|
+
const PIPELINE_CLIENT_SYMBOL = Symbol("PipelineClient");
|
|
241
|
+
/**
|
|
242
|
+
* Creates a PipelineClient using JavaScript Proxy.
|
|
243
|
+
* The proxy intercepts property accesses to build up the transform chain.
|
|
244
|
+
*/
|
|
245
|
+
function createPipelineClient(options) {
|
|
246
|
+
const { connection, questionId, opTracker = new PipelineOpTracker() } = options;
|
|
247
|
+
return {
|
|
248
|
+
[PIPELINE_CLIENT_SYMBOL]: true,
|
|
249
|
+
connection,
|
|
250
|
+
questionId,
|
|
251
|
+
opTracker,
|
|
252
|
+
call(interfaceId, methodId, params) {
|
|
253
|
+
return makePipelinedCall(connection, questionId, opTracker.getTransform(), interfaceId, methodId, params);
|
|
254
|
+
},
|
|
255
|
+
getPointerField(fieldIndex) {
|
|
256
|
+
const newTracker = opTracker.clone();
|
|
257
|
+
newTracker.addGetPointerField(fieldIndex);
|
|
258
|
+
return createPipelineClient({
|
|
259
|
+
connection,
|
|
260
|
+
questionId,
|
|
261
|
+
opTracker: newTracker
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if a value is a PipelineClient
|
|
268
|
+
*/
|
|
269
|
+
function isPipelineClient(value) {
|
|
270
|
+
return typeof value === "object" && value !== null && PIPELINE_CLIENT_SYMBOL in value;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Makes a call on a promised answer (pipeline call).
|
|
274
|
+
* This sends a Call message with target.type = 'promisedAnswer'.
|
|
275
|
+
*/
|
|
276
|
+
async function makePipelinedCall(connection, questionId, transform, interfaceId, methodId, params) {
|
|
277
|
+
const newQuestionId = connection.createQuestion();
|
|
278
|
+
const call = {
|
|
279
|
+
questionId: newQuestionId,
|
|
280
|
+
target: {
|
|
281
|
+
type: "promisedAnswer",
|
|
282
|
+
promisedAnswer: {
|
|
283
|
+
questionId,
|
|
284
|
+
transform
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
interfaceId,
|
|
288
|
+
methodId,
|
|
289
|
+
allowThirdPartyTailCall: false,
|
|
290
|
+
noPromisePipelining: false,
|
|
291
|
+
onlyPromisePipeline: false,
|
|
292
|
+
params,
|
|
293
|
+
sendResultsTo: { type: "caller" }
|
|
294
|
+
};
|
|
295
|
+
await connection.sendCall(call);
|
|
296
|
+
return connection.waitForAnswer(newQuestionId);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Manages calls that were made on a pipeline client before the answer arrived.
|
|
300
|
+
* When the answer arrives, these calls are dispatched to the actual capability.
|
|
301
|
+
*/
|
|
302
|
+
var QueuedCallManager = class {
|
|
303
|
+
queuedCalls = /* @__PURE__ */ new Map();
|
|
304
|
+
/**
|
|
305
|
+
* Queue a call for when the promise resolves
|
|
306
|
+
*/
|
|
307
|
+
queueCall(questionId, call) {
|
|
308
|
+
const calls = this.queuedCalls.get(questionId) ?? [];
|
|
309
|
+
calls.push(call);
|
|
310
|
+
this.queuedCalls.set(questionId, calls);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get and clear all queued calls for a question
|
|
314
|
+
*/
|
|
315
|
+
dequeueCalls(questionId) {
|
|
316
|
+
const calls = this.queuedCalls.get(questionId) ?? [];
|
|
317
|
+
this.queuedCalls.delete(questionId);
|
|
318
|
+
return calls;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if there are queued calls for a question
|
|
322
|
+
*/
|
|
323
|
+
hasQueuedCalls(questionId) {
|
|
324
|
+
return (this.queuedCalls.get(questionId)?.length ?? 0) > 0;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Clear all queued calls (e.g., on disconnect)
|
|
328
|
+
*/
|
|
329
|
+
clear() {
|
|
330
|
+
for (const calls of this.queuedCalls.values()) for (const call of calls) call.reject(/* @__PURE__ */ new Error("Connection closed"));
|
|
331
|
+
this.queuedCalls.clear();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Tracks pending pipeline resolutions
|
|
336
|
+
*/
|
|
337
|
+
var PipelineResolutionTracker = class {
|
|
338
|
+
pendingResolutions = /* @__PURE__ */ new Map();
|
|
339
|
+
/**
|
|
340
|
+
* Mark a question as resolved to a capability
|
|
341
|
+
*/
|
|
342
|
+
resolveToCapability(questionId, importId) {
|
|
343
|
+
this.pendingResolutions.set(questionId, {
|
|
344
|
+
type: "capability",
|
|
345
|
+
importId
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Mark a question as resolved to an exception
|
|
350
|
+
*/
|
|
351
|
+
resolveToException(questionId, reason) {
|
|
352
|
+
this.pendingResolutions.set(questionId, {
|
|
353
|
+
type: "exception",
|
|
354
|
+
reason
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get the resolution for a question (if available)
|
|
359
|
+
*/
|
|
360
|
+
getResolution(questionId) {
|
|
361
|
+
return this.pendingResolutions.get(questionId);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Check if a question has been resolved
|
|
365
|
+
*/
|
|
366
|
+
isResolved(questionId) {
|
|
367
|
+
return this.pendingResolutions.has(questionId);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Remove a resolution entry
|
|
371
|
+
*/
|
|
372
|
+
remove(questionId) {
|
|
373
|
+
this.pendingResolutions.delete(questionId);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Clear all resolutions
|
|
377
|
+
*/
|
|
378
|
+
clear() {
|
|
379
|
+
this.pendingResolutions.clear();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/rpc/rpc-connection.ts
|
|
385
|
+
var RpcConnection = class {
|
|
386
|
+
transport;
|
|
387
|
+
options;
|
|
388
|
+
questions = new QuestionTable();
|
|
389
|
+
answers = new AnswerTable();
|
|
390
|
+
imports = new ImportTable();
|
|
391
|
+
exports = new ExportTable();
|
|
392
|
+
queuedCalls = new QueuedCallManager();
|
|
393
|
+
pipelineResolutions = new PipelineResolutionTracker();
|
|
394
|
+
running = false;
|
|
395
|
+
messageHandler;
|
|
396
|
+
level3Handlers;
|
|
397
|
+
level4Handlers;
|
|
398
|
+
constructor(transport, options = {}) {
|
|
399
|
+
this.transport = transport;
|
|
400
|
+
this.options = options;
|
|
401
|
+
this.level3Handlers = options.level3Handlers;
|
|
402
|
+
this.transport.onClose = (reason) => {
|
|
403
|
+
this.handleDisconnect(reason);
|
|
404
|
+
};
|
|
405
|
+
this.transport.onError = (error) => {
|
|
406
|
+
this.handleError(error);
|
|
407
|
+
};
|
|
408
|
+
this.level3Handlers = options.level3Handlers;
|
|
409
|
+
this.level4Handlers = options.level4Handlers;
|
|
410
|
+
}
|
|
411
|
+
/** Start processing messages */
|
|
412
|
+
async start() {
|
|
413
|
+
if (this.running) return;
|
|
414
|
+
this.running = true;
|
|
415
|
+
this.messageHandler = this.messageLoop();
|
|
416
|
+
}
|
|
417
|
+
/** Stop the connection */
|
|
418
|
+
async stop() {
|
|
419
|
+
this.running = false;
|
|
420
|
+
this.transport.close();
|
|
421
|
+
if (this.messageHandler) try {
|
|
422
|
+
await this.messageHandler;
|
|
423
|
+
} catch {}
|
|
424
|
+
}
|
|
425
|
+
/** Send a bootstrap request and return the bootstrap capability */
|
|
426
|
+
async bootstrap() {
|
|
427
|
+
const question = this.questions.create();
|
|
428
|
+
const bootstrapMsg = {
|
|
429
|
+
type: "bootstrap",
|
|
430
|
+
bootstrap: { questionId: question.id }
|
|
431
|
+
};
|
|
432
|
+
await this.transport.send(bootstrapMsg);
|
|
433
|
+
await question.completionPromise;
|
|
434
|
+
return {};
|
|
435
|
+
}
|
|
436
|
+
/** Make a call to a remote capability */
|
|
437
|
+
async call(target, interfaceId, methodId, params) {
|
|
438
|
+
if (isPipelineClient(target)) return target.call(interfaceId, methodId, params);
|
|
439
|
+
const question = this.questions.create();
|
|
440
|
+
const callMsg = {
|
|
441
|
+
type: "call",
|
|
442
|
+
call: {
|
|
443
|
+
questionId: question.id,
|
|
444
|
+
target: {
|
|
445
|
+
type: "importedCap",
|
|
446
|
+
importId: target
|
|
447
|
+
},
|
|
448
|
+
interfaceId,
|
|
449
|
+
methodId,
|
|
450
|
+
allowThirdPartyTailCall: false,
|
|
451
|
+
noPromisePipelining: false,
|
|
452
|
+
onlyPromisePipeline: false,
|
|
453
|
+
params,
|
|
454
|
+
sendResultsTo: { type: "caller" }
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
await this.transport.send(callMsg);
|
|
458
|
+
return question.completionPromise;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Make a call that returns a PipelineClient for promise pipelining.
|
|
462
|
+
* This allows making calls on the result before it arrives.
|
|
463
|
+
*/
|
|
464
|
+
async callPipelined(target, interfaceId, methodId, params) {
|
|
465
|
+
const question = this.questions.create();
|
|
466
|
+
const callMsg = {
|
|
467
|
+
type: "call",
|
|
468
|
+
call: {
|
|
469
|
+
questionId: question.id,
|
|
470
|
+
target: {
|
|
471
|
+
type: "importedCap",
|
|
472
|
+
importId: target
|
|
473
|
+
},
|
|
474
|
+
interfaceId,
|
|
475
|
+
methodId,
|
|
476
|
+
allowThirdPartyTailCall: false,
|
|
477
|
+
noPromisePipelining: false,
|
|
478
|
+
onlyPromisePipeline: false,
|
|
479
|
+
params,
|
|
480
|
+
sendResultsTo: { type: "caller" }
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
await this.transport.send(callMsg);
|
|
484
|
+
return createPipelineClient({
|
|
485
|
+
connection: this,
|
|
486
|
+
questionId: question.id
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/** Send a finish message to release a question */
|
|
490
|
+
async finish(questionId, releaseResultCaps = true) {
|
|
491
|
+
if (!this.questions.get(questionId)) return;
|
|
492
|
+
const finishMsg = {
|
|
493
|
+
type: "finish",
|
|
494
|
+
finish: {
|
|
495
|
+
questionId,
|
|
496
|
+
releaseResultCaps,
|
|
497
|
+
requireEarlyCancellationWorkaround: false
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
await this.transport.send(finishMsg);
|
|
501
|
+
this.questions.markFinishSent(questionId);
|
|
502
|
+
this.questions.remove(questionId);
|
|
503
|
+
}
|
|
504
|
+
/** Send a release message for an imported capability */
|
|
505
|
+
async release(importId, referenceCount = 1) {
|
|
506
|
+
const releaseMsg = {
|
|
507
|
+
type: "release",
|
|
508
|
+
release: {
|
|
509
|
+
id: importId,
|
|
510
|
+
referenceCount
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
await this.transport.send(releaseMsg);
|
|
514
|
+
}
|
|
515
|
+
/** Send a resolve message to indicate a promise has resolved */
|
|
516
|
+
async resolve(promiseId, cap) {
|
|
517
|
+
const resolveMsg = {
|
|
518
|
+
type: "resolve",
|
|
519
|
+
resolve: {
|
|
520
|
+
promiseId,
|
|
521
|
+
resolution: {
|
|
522
|
+
type: "cap",
|
|
523
|
+
cap
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
await this.transport.send(resolveMsg);
|
|
528
|
+
}
|
|
529
|
+
/** Send a resolve message indicating a promise was broken */
|
|
530
|
+
async resolveException(promiseId, reason) {
|
|
531
|
+
const resolveMsg = {
|
|
532
|
+
type: "resolve",
|
|
533
|
+
resolve: {
|
|
534
|
+
promiseId,
|
|
535
|
+
resolution: {
|
|
536
|
+
type: "exception",
|
|
537
|
+
exception: {
|
|
538
|
+
reason,
|
|
539
|
+
type: "failed"
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
await this.transport.send(resolveMsg);
|
|
545
|
+
}
|
|
546
|
+
/** Send a return message (internal use) */
|
|
547
|
+
async sendReturn(ret) {
|
|
548
|
+
const returnMsg = {
|
|
549
|
+
type: "return",
|
|
550
|
+
return: ret
|
|
551
|
+
};
|
|
552
|
+
await this.transport.send(returnMsg);
|
|
553
|
+
}
|
|
554
|
+
/** Send a disembargo message (internal use) */
|
|
555
|
+
async sendDisembargo(disembargo) {
|
|
556
|
+
const disembargoMsg = {
|
|
557
|
+
type: "disembargo",
|
|
558
|
+
disembargo
|
|
559
|
+
};
|
|
560
|
+
await this.transport.send(disembargoMsg);
|
|
561
|
+
}
|
|
562
|
+
/** Internal method: Create a new question (used by pipeline) */
|
|
563
|
+
createQuestion() {
|
|
564
|
+
return this.questions.create().id;
|
|
565
|
+
}
|
|
566
|
+
/** Internal method: Send a call message (used by pipeline) */
|
|
567
|
+
async sendCall(call) {
|
|
568
|
+
const callMsg = {
|
|
569
|
+
type: "call",
|
|
570
|
+
call
|
|
571
|
+
};
|
|
572
|
+
await this.transport.send(callMsg);
|
|
573
|
+
}
|
|
574
|
+
/** Internal method: Wait for an answer (used by pipeline) */
|
|
575
|
+
async waitForAnswer(questionId) {
|
|
576
|
+
const question = this.questions.get(questionId);
|
|
577
|
+
if (!question) throw new Error(`Question ${questionId} not found`);
|
|
578
|
+
return question.completionPromise;
|
|
579
|
+
}
|
|
580
|
+
/** Main message processing loop */
|
|
581
|
+
async messageLoop() {
|
|
582
|
+
while (this.running) try {
|
|
583
|
+
const message = await this.transport.receive();
|
|
584
|
+
if (message === null) break;
|
|
585
|
+
await this.handleMessage(message);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (this.running) this.handleError(error);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/** Handle incoming messages */
|
|
591
|
+
async handleMessage(message) {
|
|
592
|
+
switch (message.type) {
|
|
593
|
+
case "bootstrap":
|
|
594
|
+
await this.handleBootstrap(message.bootstrap);
|
|
595
|
+
break;
|
|
596
|
+
case "call":
|
|
597
|
+
await this.handleCall(message.call);
|
|
598
|
+
break;
|
|
599
|
+
case "return":
|
|
600
|
+
await this.handleReturn(message.return);
|
|
601
|
+
break;
|
|
602
|
+
case "finish":
|
|
603
|
+
await this.handleFinish(message.finish);
|
|
604
|
+
break;
|
|
605
|
+
case "resolve":
|
|
606
|
+
await this.handleResolve(message.resolve);
|
|
607
|
+
break;
|
|
608
|
+
case "release":
|
|
609
|
+
await this.handleRelease(message.release);
|
|
610
|
+
break;
|
|
611
|
+
case "disembargo":
|
|
612
|
+
await this.handleDisembargo(message.disembargo);
|
|
613
|
+
break;
|
|
614
|
+
case "provide":
|
|
615
|
+
await this.handleProvide(message.provide);
|
|
616
|
+
break;
|
|
617
|
+
case "accept":
|
|
618
|
+
await this.handleAccept(message.accept);
|
|
619
|
+
break;
|
|
620
|
+
case "join":
|
|
621
|
+
await this.handleJoin(message.join);
|
|
622
|
+
break;
|
|
623
|
+
case "abort":
|
|
624
|
+
this.handleAbort(message.exception.reason);
|
|
625
|
+
break;
|
|
626
|
+
case "unimplemented": break;
|
|
627
|
+
default: await this.sendUnimplemented(message);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/** Handle bootstrap request */
|
|
631
|
+
async handleBootstrap(bootstrap) {
|
|
632
|
+
this.answers.create(bootstrap.questionId);
|
|
633
|
+
const returnMsg = {
|
|
634
|
+
type: "return",
|
|
635
|
+
return: {
|
|
636
|
+
answerId: bootstrap.questionId,
|
|
637
|
+
releaseParamCaps: true,
|
|
638
|
+
noFinishNeeded: false,
|
|
639
|
+
result: {
|
|
640
|
+
type: "results",
|
|
641
|
+
payload: {
|
|
642
|
+
content: new Uint8Array(0),
|
|
643
|
+
capTable: []
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
await this.transport.send(returnMsg);
|
|
649
|
+
this.answers.markReturnSent(bootstrap.questionId);
|
|
650
|
+
}
|
|
651
|
+
/** Handle incoming call */
|
|
652
|
+
async handleCall(call) {
|
|
653
|
+
this.answers.create(call.questionId);
|
|
654
|
+
const returnMsg = {
|
|
655
|
+
type: "return",
|
|
656
|
+
return: {
|
|
657
|
+
answerId: call.questionId,
|
|
658
|
+
releaseParamCaps: true,
|
|
659
|
+
noFinishNeeded: false,
|
|
660
|
+
result: {
|
|
661
|
+
type: "exception",
|
|
662
|
+
exception: {
|
|
663
|
+
reason: "Method not implemented",
|
|
664
|
+
type: "unimplemented"
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
await this.transport.send(returnMsg);
|
|
670
|
+
this.answers.markReturnSent(call.questionId);
|
|
671
|
+
}
|
|
672
|
+
/** Handle return message */
|
|
673
|
+
async handleReturn(ret) {
|
|
674
|
+
if (!this.questions.get(ret.answerId)) return;
|
|
675
|
+
if (ret.result.type === "results") {
|
|
676
|
+
const capTable = ret.result.payload.capTable;
|
|
677
|
+
if (capTable.length > 0) {
|
|
678
|
+
const cap = capTable[0];
|
|
679
|
+
if (cap.type === "receiverHosted") this.pipelineResolutions.resolveToCapability(ret.answerId, cap.importId);
|
|
680
|
+
else if (cap.type === "thirdPartyHosted") await this.handleThirdPartyCapability(ret.answerId, cap.thirdPartyCapId);
|
|
681
|
+
}
|
|
682
|
+
} else if (ret.result.type === "exception") this.pipelineResolutions.resolveToException(ret.answerId, ret.result.exception.reason);
|
|
683
|
+
switch (ret.result.type) {
|
|
684
|
+
case "results":
|
|
685
|
+
this.questions.complete(ret.answerId, ret.result.payload);
|
|
686
|
+
break;
|
|
687
|
+
case "exception":
|
|
688
|
+
this.questions.cancel(ret.answerId, new Error(ret.result.exception.reason));
|
|
689
|
+
break;
|
|
690
|
+
case "canceled":
|
|
691
|
+
this.questions.cancel(ret.answerId, /* @__PURE__ */ new Error("Call canceled"));
|
|
692
|
+
break;
|
|
693
|
+
case "acceptFromThirdParty":
|
|
694
|
+
await this.handleAcceptFromThirdParty(ret.answerId, ret.result.thirdPartyCapId);
|
|
695
|
+
break;
|
|
696
|
+
default: this.questions.cancel(ret.answerId, /* @__PURE__ */ new Error("Unknown return type"));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/** Handle finish message */
|
|
700
|
+
async handleFinish(finish) {
|
|
701
|
+
this.answers.markFinishReceived(finish.questionId);
|
|
702
|
+
this.answers.remove(finish.questionId);
|
|
703
|
+
}
|
|
704
|
+
/** Handle resolve message (Level 1) */
|
|
705
|
+
async handleResolve(resolve) {
|
|
706
|
+
const { promiseId, resolution } = resolve;
|
|
707
|
+
switch (resolution.type) {
|
|
708
|
+
case "cap":
|
|
709
|
+
this.imports.markResolved(promiseId);
|
|
710
|
+
break;
|
|
711
|
+
case "exception":
|
|
712
|
+
console.warn(`Promise ${promiseId} broken: ${resolution.exception.reason}`);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/** Handle release message (Level 1) */
|
|
717
|
+
async handleRelease(release) {
|
|
718
|
+
const { id, referenceCount } = release;
|
|
719
|
+
if (this.exports.release(id, referenceCount)) console.log(`Export ${id} fully released`);
|
|
720
|
+
}
|
|
721
|
+
/** Handle disembargo message (Level 1) */
|
|
722
|
+
async handleDisembargo(disembargo) {
|
|
723
|
+
const { target, context } = disembargo;
|
|
724
|
+
if (this.level3Handlers) {
|
|
725
|
+
await this.level3Handlers.handleDisembargo(disembargo);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (context.type === "senderLoopback") {
|
|
729
|
+
const echoMsg = {
|
|
730
|
+
type: "disembargo",
|
|
731
|
+
disembargo: {
|
|
732
|
+
target,
|
|
733
|
+
context: {
|
|
734
|
+
type: "receiverLoopback",
|
|
735
|
+
embargoId: context.embargoId
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
await this.transport.send(echoMsg);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/** Handle provide message (Level 3) */
|
|
743
|
+
async handleProvide(provide) {
|
|
744
|
+
if (this.level3Handlers) await this.level3Handlers.handleProvide(provide);
|
|
745
|
+
else await this.sendReturnException(provide.questionId, "Level 3 RPC (Provide) not implemented");
|
|
746
|
+
}
|
|
747
|
+
/** Handle accept message (Level 3) */
|
|
748
|
+
async handleAccept(accept) {
|
|
749
|
+
if (this.level3Handlers) await this.level3Handlers.handleAccept(accept);
|
|
750
|
+
else await this.sendReturnException(accept.questionId, "Level 3 RPC (Accept) not implemented");
|
|
751
|
+
}
|
|
752
|
+
/** Handle third-party capability in return results (Level 3) */
|
|
753
|
+
async handleThirdPartyCapability(_questionId, thirdPartyCapId) {
|
|
754
|
+
if (this.level3Handlers) {
|
|
755
|
+
if (await this.level3Handlers.handleThirdPartyCapability(thirdPartyCapId) !== void 0) {}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/** Handle acceptFromThirdParty return type (Level 3) */
|
|
759
|
+
async handleAcceptFromThirdParty(questionId, thirdPartyCapId) {
|
|
760
|
+
if (this.level3Handlers) {
|
|
761
|
+
const importId = await this.level3Handlers.handleThirdPartyCapability(thirdPartyCapId);
|
|
762
|
+
if (importId !== void 0) this.questions.complete(questionId, { importId });
|
|
763
|
+
else this.questions.cancel(questionId, /* @__PURE__ */ new Error("Failed to resolve third-party capability"));
|
|
764
|
+
} else this.questions.cancel(questionId, /* @__PURE__ */ new Error("Level 3 RPC not enabled"));
|
|
765
|
+
}
|
|
766
|
+
/** Handle abort message */
|
|
767
|
+
handleAbort(_reason) {
|
|
768
|
+
this.running = false;
|
|
769
|
+
this.questions.clear();
|
|
770
|
+
this.answers.clear();
|
|
771
|
+
this.imports.clear();
|
|
772
|
+
this.exports.clear();
|
|
773
|
+
this.queuedCalls.clear();
|
|
774
|
+
this.pipelineResolutions.clear();
|
|
775
|
+
}
|
|
776
|
+
/** Handle disconnect */
|
|
777
|
+
handleDisconnect(_reason) {
|
|
778
|
+
this.running = false;
|
|
779
|
+
this.questions.clear();
|
|
780
|
+
this.answers.clear();
|
|
781
|
+
this.imports.clear();
|
|
782
|
+
this.exports.clear();
|
|
783
|
+
this.queuedCalls.clear();
|
|
784
|
+
this.pipelineResolutions.clear();
|
|
785
|
+
}
|
|
786
|
+
/** Handle error */
|
|
787
|
+
handleError(error) {
|
|
788
|
+
console.error("RPC error:", error);
|
|
789
|
+
}
|
|
790
|
+
/** Send unimplemented response */
|
|
791
|
+
async sendUnimplemented(originalMessage) {
|
|
792
|
+
const msg = {
|
|
793
|
+
type: "unimplemented",
|
|
794
|
+
message: originalMessage
|
|
795
|
+
};
|
|
796
|
+
await this.transport.send(msg);
|
|
797
|
+
}
|
|
798
|
+
/** Send return exception (helper) */
|
|
799
|
+
async sendReturnException(questionId, reason) {
|
|
800
|
+
const returnMsg = {
|
|
801
|
+
type: "return",
|
|
802
|
+
return: {
|
|
803
|
+
answerId: questionId,
|
|
804
|
+
releaseParamCaps: true,
|
|
805
|
+
noFinishNeeded: false,
|
|
806
|
+
result: {
|
|
807
|
+
type: "exception",
|
|
808
|
+
exception: {
|
|
809
|
+
reason,
|
|
810
|
+
type: "unimplemented"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
await this.transport.send(returnMsg);
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Set the Level 4 handlers for this connection.
|
|
819
|
+
* This enables reference equality verification support.
|
|
820
|
+
*/
|
|
821
|
+
setLevel4Handlers(handlers) {
|
|
822
|
+
this.level4Handlers = handlers;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Send a Join message to verify that two capabilities point to the same object.
|
|
826
|
+
* Requires Level 4 handlers to be set.
|
|
827
|
+
*/
|
|
828
|
+
async join(_target1, _target2) {
|
|
829
|
+
if (!this.level4Handlers) throw new Error("Level 4 handlers not set");
|
|
830
|
+
}
|
|
831
|
+
/** Handle join message (Level 4) */
|
|
832
|
+
async handleJoin(join) {
|
|
833
|
+
if (this.level4Handlers) await this.level4Handlers.handleJoin(join);
|
|
834
|
+
else await this.sendReturnException(join.questionId, "Level 4 RPC (Join) not implemented");
|
|
835
|
+
}
|
|
836
|
+
/** Import a capability from the remote peer */
|
|
837
|
+
importCapability(importId, isPromise = false) {
|
|
838
|
+
this.imports.add(importId, isPromise);
|
|
839
|
+
}
|
|
840
|
+
/** Export a capability to the remote peer */
|
|
841
|
+
exportCapability(capability, isPromise = false) {
|
|
842
|
+
return this.exports.add(capability, isPromise).id;
|
|
843
|
+
}
|
|
844
|
+
/** Get an imported capability */
|
|
845
|
+
getImport(importId) {
|
|
846
|
+
return this.imports.get(importId);
|
|
847
|
+
}
|
|
848
|
+
/** Get an exported capability */
|
|
849
|
+
getExport(exportId) {
|
|
850
|
+
return this.exports.get(exportId);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Set the Level 3 handlers for this connection.
|
|
854
|
+
* This enables three-way introduction support.
|
|
855
|
+
*/
|
|
856
|
+
setLevel3Handlers(handlers) {
|
|
857
|
+
this.level3Handlers = handlers;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Send a Provide message to offer a capability to a third party.
|
|
861
|
+
* Requires Level 3 handlers to be set.
|
|
862
|
+
*/
|
|
863
|
+
async provideToThirdParty(_target, _recipient) {
|
|
864
|
+
if (!this.level3Handlers) throw new Error("Level 3 handlers not set");
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
//#endregion
|
|
869
|
+
export { QueuedCallManager as a, AnswerTable as c, QuestionTable as d, PipelineResolutionTracker as i, ExportTable as l, PIPELINE_CLIENT_SYMBOL as n, createPipelineClient as o, PipelineOpTracker as r, isPipelineClient as s, RpcConnection as t, ImportTable as u };
|
|
870
|
+
//# sourceMappingURL=rpc-connection-Dz3rYT1P.js.map
|