@resonatehq/sdk 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/context.d.ts +10 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -7
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +10 -10
- package/dist/core.js.map +1 -1
- package/dist/coroutine.d.ts.map +1 -1
- package/dist/coroutine.js +1 -0
- package/dist/coroutine.js.map +1 -1
- package/dist/decorator.d.ts +1 -0
- package/dist/decorator.d.ts.map +1 -1
- package/dist/decorator.js +1 -0
- package/dist/decorator.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/network/http.d.ts +4 -0
- package/dist/network/http.d.ts.map +1 -1
- package/dist/network/http.js +12 -1
- package/dist/network/http.js.map +1 -1
- package/dist/network/local.d.ts +20 -11
- package/dist/network/local.d.ts.map +1 -1
- package/dist/network/local.js +256 -139
- package/dist/network/local.js.map +1 -1
- package/dist/network/network.d.ts +1 -0
- package/dist/network/network.d.ts.map +1 -1
- package/dist/network/types.d.ts +1 -0
- package/dist/network/types.d.ts.map +1 -1
- package/dist/network/types.js +2 -1
- package/dist/network/types.js.map +1 -1
- package/dist/options.d.ts +5 -2
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +5 -3
- package/dist/options.js.map +1 -1
- package/dist/promises.d.ts +14 -1
- package/dist/promises.d.ts.map +1 -1
- package/dist/promises.js +9 -0
- package/dist/promises.js.map +1 -1
- package/dist/resonate.d.ts.map +1 -1
- package/dist/resonate.js +1 -14
- package/dist/resonate.js.map +1 -1
- package/dist/util.d.ts +5 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +56 -11
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
package/dist/network/local.js
CHANGED
|
@@ -6,9 +6,6 @@ import { isResponse } from "./types.js";
|
|
|
6
6
|
// CONSTANTS
|
|
7
7
|
// =============================================================================
|
|
8
8
|
const PENDING_RETRY_TTL = 30000;
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// SERVER
|
|
11
|
-
// =============================================================================
|
|
12
9
|
export class Server {
|
|
13
10
|
promises = new Map();
|
|
14
11
|
tasks = new Map();
|
|
@@ -17,6 +14,15 @@ export class Server {
|
|
|
17
14
|
tTimeouts = [];
|
|
18
15
|
sTimeouts = [];
|
|
19
16
|
outgoing = [];
|
|
17
|
+
// When "eager", every action handler converges physical state to logical
|
|
18
|
+
// state by running tryEagerTimeout for the relevant id(s) before the
|
|
19
|
+
// handler body. When "none", convergence happens only via debug.tick (or
|
|
20
|
+
// an equivalent background mechanism) — read paths must rely on
|
|
21
|
+
// projection to return the logical state.
|
|
22
|
+
timeoutMode;
|
|
23
|
+
constructor(opts = {}) {
|
|
24
|
+
this.timeoutMode = opts.timeoutMode ?? "eager";
|
|
25
|
+
}
|
|
20
26
|
apply(now, req) {
|
|
21
27
|
const changes = [];
|
|
22
28
|
const error = this.validate(req);
|
|
@@ -26,28 +32,28 @@ export class Server {
|
|
|
26
32
|
let result;
|
|
27
33
|
switch (req.kind) {
|
|
28
34
|
case "promise.get": {
|
|
29
|
-
changes.push(...this.
|
|
35
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
30
36
|
result = this.promiseGet(now, req);
|
|
31
37
|
break;
|
|
32
38
|
}
|
|
33
39
|
case "promise.create": {
|
|
34
|
-
changes.push(...this.
|
|
40
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
35
41
|
result = this.promiseCreate(now, req);
|
|
36
42
|
break;
|
|
37
43
|
}
|
|
38
44
|
case "promise.settle": {
|
|
39
|
-
changes.push(...this.
|
|
45
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
40
46
|
result = this.promiseSettle(now, req);
|
|
41
47
|
break;
|
|
42
48
|
}
|
|
43
49
|
case "promise.register_callback": {
|
|
44
|
-
changes.push(...this.
|
|
45
|
-
changes.push(...this.
|
|
50
|
+
changes.push(...this.tryEagerTimeout(now, req.data.awaited));
|
|
51
|
+
changes.push(...this.tryEagerTimeout(now, req.data.awaiter));
|
|
46
52
|
result = this.promiseRegisterCallback(now, req);
|
|
47
53
|
break;
|
|
48
54
|
}
|
|
49
55
|
case "promise.register_listener": {
|
|
50
|
-
changes.push(...this.
|
|
56
|
+
changes.push(...this.tryEagerTimeout(now, req.data.awaited));
|
|
51
57
|
result = this.promiseRegisterListener(now, req);
|
|
52
58
|
break;
|
|
53
59
|
}
|
|
@@ -56,51 +62,51 @@ export class Server {
|
|
|
56
62
|
break;
|
|
57
63
|
}
|
|
58
64
|
case "task.get": {
|
|
59
|
-
changes.push(...this.
|
|
65
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
60
66
|
result = this.taskGet(now, req);
|
|
61
67
|
break;
|
|
62
68
|
}
|
|
63
69
|
case "task.create": {
|
|
64
|
-
changes.push(...this.
|
|
70
|
+
changes.push(...this.tryEagerTimeout(now, req.data.action.data.id));
|
|
65
71
|
result = this.taskCreate(now, req);
|
|
66
72
|
break;
|
|
67
73
|
}
|
|
68
74
|
case "task.acquire": {
|
|
69
|
-
changes.push(...this.
|
|
75
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
70
76
|
result = this.taskAcquire(now, req);
|
|
71
77
|
break;
|
|
72
78
|
}
|
|
73
79
|
case "task.release": {
|
|
74
|
-
changes.push(...this.
|
|
80
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
75
81
|
result = this.taskRelease(now, req);
|
|
76
82
|
break;
|
|
77
83
|
}
|
|
78
84
|
case "task.fulfill": {
|
|
79
|
-
changes.push(...this.
|
|
85
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
80
86
|
result = this.taskFulfill(now, req);
|
|
81
87
|
break;
|
|
82
88
|
}
|
|
83
89
|
case "task.suspend": {
|
|
84
|
-
changes.push(...this.
|
|
90
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
85
91
|
for (const action of req.data.actions) {
|
|
86
|
-
changes.push(...this.
|
|
92
|
+
changes.push(...this.tryEagerTimeout(now, action.data.awaited));
|
|
87
93
|
}
|
|
88
94
|
result = this.taskSuspend(now, req);
|
|
89
95
|
break;
|
|
90
96
|
}
|
|
91
97
|
case "task.halt": {
|
|
92
|
-
changes.push(...this.
|
|
98
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
93
99
|
result = this.taskHalt(now, req);
|
|
94
100
|
break;
|
|
95
101
|
}
|
|
96
102
|
case "task.continue": {
|
|
97
|
-
changes.push(...this.
|
|
103
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
98
104
|
result = this.taskContinue(now, req);
|
|
99
105
|
break;
|
|
100
106
|
}
|
|
101
107
|
case "task.fence": {
|
|
102
|
-
changes.push(...this.
|
|
103
|
-
changes.push(...this.
|
|
108
|
+
changes.push(...this.tryEagerTimeout(now, req.data.id));
|
|
109
|
+
changes.push(...this.tryEagerTimeout(now, req.data.action.data.id));
|
|
104
110
|
result = this.taskFence(now, req);
|
|
105
111
|
break;
|
|
106
112
|
}
|
|
@@ -192,13 +198,20 @@ export class Server {
|
|
|
192
198
|
if (!promise) {
|
|
193
199
|
return { response: this.response("promise.get", 404, "Promise not found"), changes: [] };
|
|
194
200
|
}
|
|
195
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
response: this.response("promise.get", 200, {
|
|
203
|
+
promise: this.toPromiseRecord(this.projectedPromise(now, promise)),
|
|
204
|
+
}),
|
|
205
|
+
changes: [],
|
|
206
|
+
};
|
|
196
207
|
}
|
|
197
208
|
promiseCreate(now, req) {
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
209
|
+
const existingPromise = this.promises.get(req.data.id);
|
|
210
|
+
if (existingPromise) {
|
|
200
211
|
return {
|
|
201
|
-
response: this.response("promise.create", 200, {
|
|
212
|
+
response: this.response("promise.create", 200, {
|
|
213
|
+
promise: this.toPromiseRecord(this.projectedPromise(now, existingPromise)),
|
|
214
|
+
}),
|
|
202
215
|
changes: [],
|
|
203
216
|
};
|
|
204
217
|
}
|
|
@@ -214,13 +227,23 @@ export class Server {
|
|
|
214
227
|
createdAt: req.data.timeoutAt,
|
|
215
228
|
settledAt: req.data.timeoutAt,
|
|
216
229
|
timeoutAt: req.data.timeoutAt,
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
callbacks: new Set(),
|
|
231
|
+
listeners: new Set(),
|
|
219
232
|
};
|
|
220
233
|
changes.push(this.setPromise(promise));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
234
|
+
// Create fulfilled task inline if promise has a target
|
|
235
|
+
const address = req.data.tags["resonate:target"];
|
|
236
|
+
if (address) {
|
|
237
|
+
changes.push(this.setTask({
|
|
238
|
+
id: req.data.id,
|
|
239
|
+
state: "fulfilled",
|
|
240
|
+
version: 0,
|
|
241
|
+
pid: undefined,
|
|
242
|
+
ttl: undefined,
|
|
243
|
+
resumes: new Set(),
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
changes.push(...this.triggerSettlement(req.data.id, now));
|
|
224
247
|
return {
|
|
225
248
|
response: this.response("promise.create", 200, { promise: this.toPromiseRecord(promise) }),
|
|
226
249
|
changes,
|
|
@@ -235,8 +258,8 @@ export class Server {
|
|
|
235
258
|
createdAt: now,
|
|
236
259
|
settledAt: null,
|
|
237
260
|
timeoutAt: req.data.timeoutAt,
|
|
238
|
-
|
|
239
|
-
|
|
261
|
+
callbacks: new Set(),
|
|
262
|
+
listeners: new Set(),
|
|
240
263
|
};
|
|
241
264
|
changes.push(this.setPromise(promise));
|
|
242
265
|
changes.push(this.setPTimeout({ id: req.data.id, timeout: req.data.timeoutAt }));
|
|
@@ -270,26 +293,28 @@ export class Server {
|
|
|
270
293
|
if (!promise) {
|
|
271
294
|
return { response: this.response("promise.settle", 404, "Promise not found"), changes: [] };
|
|
272
295
|
}
|
|
273
|
-
if
|
|
296
|
+
// Idempotent return: if the promise is already physically terminal OR
|
|
297
|
+
// pending past its deadline (logically settled), return the projected
|
|
298
|
+
// logical state without writing.
|
|
299
|
+
const projected = this.projectedPromise(now, promise);
|
|
300
|
+
if (projected.state !== "pending") {
|
|
274
301
|
return {
|
|
275
|
-
response: this.response("promise.settle", 200, { promise: this.toPromiseRecord(
|
|
302
|
+
response: this.response("promise.settle", 200, { promise: this.toPromiseRecord(projected) }),
|
|
276
303
|
changes: [],
|
|
277
304
|
};
|
|
278
305
|
}
|
|
279
306
|
const changes = [];
|
|
280
|
-
const
|
|
307
|
+
const settledPromise = {
|
|
281
308
|
...promise,
|
|
282
309
|
state: req.data.state,
|
|
283
310
|
value: req.data.value,
|
|
284
311
|
settledAt: now,
|
|
285
312
|
};
|
|
286
|
-
changes.push(this.setPromise(
|
|
313
|
+
changes.push(this.setPromise(settledPromise));
|
|
287
314
|
changes.push(this.delPTimeout(req.data.id));
|
|
288
|
-
changes.push(...this.
|
|
289
|
-
changes.push(...this.resumeAwaiters(req.data.id, now));
|
|
290
|
-
changes.push(...this.notifySubscribers(req.data.id));
|
|
315
|
+
changes.push(...this.triggerSettlement(req.data.id, now));
|
|
291
316
|
return {
|
|
292
|
-
response: this.response("promise.settle", 200, { promise: this.toPromiseRecord(
|
|
317
|
+
response: this.response("promise.settle", 200, { promise: this.toPromiseRecord(settledPromise) }),
|
|
293
318
|
changes,
|
|
294
319
|
};
|
|
295
320
|
}
|
|
@@ -307,13 +332,19 @@ export class Server {
|
|
|
307
332
|
return { response: this.response("promise.register_callback", 422, "Awaiter has no address"), changes: [] };
|
|
308
333
|
}
|
|
309
334
|
const changes = [];
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
335
|
+
// Project for the logically-pending check and for the response — the
|
|
336
|
+
// callback is written only when both sides are logically pending, and
|
|
337
|
+
// the response always reflects the projected awaited promise.
|
|
338
|
+
const awaitedProjected = this.projectedPromise(now, awaitedPromise);
|
|
339
|
+
const awaiterProjected = this.projectedPromise(now, awaiterPromise);
|
|
340
|
+
if (awaitedProjected.state === "pending" && awaiterProjected.state === "pending") {
|
|
341
|
+
awaitedPromise.callbacks.add(req.data.awaiter);
|
|
313
342
|
changes.push(this.setPromise(awaitedPromise));
|
|
314
343
|
}
|
|
315
344
|
return {
|
|
316
|
-
response: this.response("promise.register_callback", 200, {
|
|
345
|
+
response: this.response("promise.register_callback", 200, {
|
|
346
|
+
promise: this.toPromiseRecord(awaitedProjected),
|
|
347
|
+
}),
|
|
317
348
|
changes,
|
|
318
349
|
};
|
|
319
350
|
}
|
|
@@ -323,13 +354,18 @@ export class Server {
|
|
|
323
354
|
return { response: this.response("promise.register_listener", 404, "Promise not found"), changes: [] };
|
|
324
355
|
}
|
|
325
356
|
const changes = [];
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
357
|
+
// Project for the logically-pending check and for the response — the
|
|
358
|
+
// listener is registered only when the promise is logically pending,
|
|
359
|
+
// and the response always reflects the projected awaited promise.
|
|
360
|
+
const projected = this.projectedPromise(now, promise);
|
|
361
|
+
if (projected.state === "pending") {
|
|
362
|
+
promise.listeners.add(req.data.address);
|
|
329
363
|
changes.push(this.setPromise(promise));
|
|
330
364
|
}
|
|
331
365
|
return {
|
|
332
|
-
response: this.response("promise.register_listener", 200, {
|
|
366
|
+
response: this.response("promise.register_listener", 200, {
|
|
367
|
+
promise: this.toPromiseRecord(projected),
|
|
368
|
+
}),
|
|
333
369
|
changes,
|
|
334
370
|
};
|
|
335
371
|
}
|
|
@@ -341,7 +377,12 @@ export class Server {
|
|
|
341
377
|
if (!task) {
|
|
342
378
|
return { response: this.response("task.get", 404, "Task not found"), changes: [] };
|
|
343
379
|
}
|
|
344
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
response: this.response("task.get", 200, {
|
|
382
|
+
task: this.toTaskRecord(this.projectedTask(now, task)),
|
|
383
|
+
}),
|
|
384
|
+
changes: [],
|
|
385
|
+
};
|
|
345
386
|
}
|
|
346
387
|
taskCreate(now, req) {
|
|
347
388
|
const existingTask = this.tasks.get(req.data.action.data.id);
|
|
@@ -349,10 +390,12 @@ export class Server {
|
|
|
349
390
|
const promise = this.getPromiseOrThrow(existingTask.id);
|
|
350
391
|
// Pending → Acquired (acquire and return 200)
|
|
351
392
|
if (existingTask.state === "pending") {
|
|
393
|
+
const newVersion = existingTask.version + 1;
|
|
352
394
|
const changes = [];
|
|
353
395
|
changes.push(this.setTask({
|
|
354
396
|
...existingTask,
|
|
355
397
|
state: "acquired",
|
|
398
|
+
version: newVersion,
|
|
356
399
|
pid: req.data.pid,
|
|
357
400
|
ttl: req.data.ttl,
|
|
358
401
|
resumes: new Set(),
|
|
@@ -363,6 +406,7 @@ export class Server {
|
|
|
363
406
|
task: this.toTaskRecord({
|
|
364
407
|
...existingTask,
|
|
365
408
|
state: "acquired",
|
|
409
|
+
version: newVersion,
|
|
366
410
|
pid: req.data.pid,
|
|
367
411
|
ttl: req.data.ttl,
|
|
368
412
|
resumes: new Set(),
|
|
@@ -407,8 +451,8 @@ export class Server {
|
|
|
407
451
|
createdAt: actionData.timeoutAt,
|
|
408
452
|
settledAt: actionData.timeoutAt,
|
|
409
453
|
timeoutAt: actionData.timeoutAt,
|
|
410
|
-
|
|
411
|
-
|
|
454
|
+
callbacks: new Set(),
|
|
455
|
+
listeners: new Set(),
|
|
412
456
|
};
|
|
413
457
|
changes.push(this.setPromise(promise));
|
|
414
458
|
const task = {
|
|
@@ -439,8 +483,8 @@ export class Server {
|
|
|
439
483
|
createdAt: now,
|
|
440
484
|
settledAt: null,
|
|
441
485
|
timeoutAt: actionData.timeoutAt,
|
|
442
|
-
|
|
443
|
-
|
|
486
|
+
callbacks: new Set(),
|
|
487
|
+
listeners: new Set(),
|
|
444
488
|
};
|
|
445
489
|
changes.push(this.setPromise(promise));
|
|
446
490
|
changes.push(this.setPTimeout({ id: actionData.id, timeout: actionData.timeoutAt }));
|
|
@@ -448,7 +492,7 @@ export class Server {
|
|
|
448
492
|
const task = {
|
|
449
493
|
id: actionData.id,
|
|
450
494
|
state: "acquired",
|
|
451
|
-
version:
|
|
495
|
+
version: 1,
|
|
452
496
|
pid: req.data.pid,
|
|
453
497
|
ttl: req.data.ttl,
|
|
454
498
|
resumes: new Set(),
|
|
@@ -473,18 +517,34 @@ export class Server {
|
|
|
473
517
|
if (task.state !== "pending") {
|
|
474
518
|
return { response: this.response("task.acquire", 409, "Task not in pending state"), changes: [] };
|
|
475
519
|
}
|
|
520
|
+
// Logical-state check: if the task's promise is logically settled,
|
|
521
|
+
// the task is logically fulfilled and cannot be acquired. This catches
|
|
522
|
+
// the projection-mode case where the physical task state has not yet
|
|
523
|
+
// converged.
|
|
524
|
+
const promise = this.getPromiseOrThrow(req.data.id);
|
|
525
|
+
if (this.projectedPromise(now, promise).state !== "pending") {
|
|
526
|
+
return { response: this.response("task.acquire", 409, "Task logically fulfilled"), changes: [] };
|
|
527
|
+
}
|
|
476
528
|
if (task.version !== req.data.version) {
|
|
477
529
|
return { response: this.response("task.acquire", 409, "Version mismatch"), changes: [] };
|
|
478
530
|
}
|
|
531
|
+
const newVersion = task.version + 1;
|
|
479
532
|
const changes = [];
|
|
480
|
-
|
|
481
|
-
|
|
533
|
+
changes.push(this.setTask({
|
|
534
|
+
...task,
|
|
535
|
+
state: "acquired",
|
|
536
|
+
version: newVersion,
|
|
537
|
+
pid: req.data.pid,
|
|
538
|
+
ttl: req.data.ttl,
|
|
539
|
+
resumes: new Set(),
|
|
540
|
+
}));
|
|
482
541
|
changes.push(this.setTTimeout({ id: req.data.id, type: 1, timeout: now + req.data.ttl }));
|
|
483
542
|
return {
|
|
484
543
|
response: this.response("task.acquire", 200, {
|
|
485
544
|
task: this.toTaskRecord({
|
|
486
545
|
...task,
|
|
487
546
|
state: "acquired",
|
|
547
|
+
version: newVersion,
|
|
488
548
|
pid: req.data.pid,
|
|
489
549
|
ttl: req.data.ttl,
|
|
490
550
|
resumes: new Set(),
|
|
@@ -503,19 +563,24 @@ export class Server {
|
|
|
503
563
|
if (task.state !== "acquired") {
|
|
504
564
|
return { response: this.response("task.release", 409, "Task not acquired"), changes: [] };
|
|
505
565
|
}
|
|
566
|
+
// Logical-state check: if the task's promise is logically settled,
|
|
567
|
+
// the task is logically fulfilled and cannot be released.
|
|
568
|
+
const taskPromise = this.getPromiseOrThrow(req.data.id);
|
|
569
|
+
if (this.projectedPromise(now, taskPromise).state !== "pending") {
|
|
570
|
+
return { response: this.response("task.release", 409, "Task not acquired"), changes: [] };
|
|
571
|
+
}
|
|
506
572
|
if (task.version !== req.data.version) {
|
|
507
573
|
return { response: this.response("task.release", 409, "Version mismatch"), changes: [] };
|
|
508
574
|
}
|
|
509
575
|
const changes = [];
|
|
510
|
-
|
|
511
|
-
changes.push(this.setTask({ ...task, state: "pending", version: newVersion, pid: undefined, ttl: undefined }));
|
|
576
|
+
changes.push(this.setTask({ ...task, state: "pending", pid: undefined, ttl: undefined }));
|
|
512
577
|
changes.push(this.setTTimeout({ id: req.data.id, type: 0, timeout: now + PENDING_RETRY_TTL }));
|
|
513
578
|
const promise = this.getPromiseOrThrow(req.data.id);
|
|
514
579
|
const address = this.getAddressOrThrow(promise);
|
|
515
580
|
changes.push(this.sendMessage(address, {
|
|
516
581
|
kind: "execute",
|
|
517
582
|
head: {},
|
|
518
|
-
data: { task: { id: req.data.id, version:
|
|
583
|
+
data: { task: { id: req.data.id, version: task.version } },
|
|
519
584
|
}));
|
|
520
585
|
return { response: this.response("task.release", 200, {}), changes };
|
|
521
586
|
}
|
|
@@ -527,31 +592,34 @@ export class Server {
|
|
|
527
592
|
if (task.state !== "acquired") {
|
|
528
593
|
return { response: this.response("task.fulfill", 409, "Task not acquired"), changes: [] };
|
|
529
594
|
}
|
|
595
|
+
// Logical-state check: if the task's promise is logically settled, the
|
|
596
|
+
// task is logically fulfilled and cannot be fulfilled again.
|
|
597
|
+
const taskPromise = this.getPromiseOrThrow(req.data.id);
|
|
598
|
+
if (this.projectedPromise(now, taskPromise).state !== "pending") {
|
|
599
|
+
return { response: this.response("task.fulfill", 409, "Task not acquired"), changes: [] };
|
|
600
|
+
}
|
|
530
601
|
if (task.version !== req.data.version) {
|
|
531
602
|
return { response: this.response("task.fulfill", 409, "Version mismatch"), changes: [] };
|
|
532
603
|
}
|
|
533
|
-
const
|
|
534
|
-
const promise = this.getPromiseOrThrow(settle.id);
|
|
604
|
+
const promise = this.getPromiseOrThrow(req.data.action.data.id);
|
|
535
605
|
const changes = [];
|
|
536
606
|
// Check if promise is already settled (possibly by auto-timeout above)
|
|
537
607
|
if (promise.state !== "pending") {
|
|
538
608
|
// Still fulfill the task but indicate the promise was already settled
|
|
539
|
-
changes.push(...this.
|
|
609
|
+
changes.push(...this.triggerFulfilled(req.data.id));
|
|
540
610
|
return { response: this.response("task.fulfill", 200, { promise: this.toPromiseRecord(promise) }), changes };
|
|
541
611
|
}
|
|
542
612
|
// Settle the promise
|
|
543
|
-
const
|
|
613
|
+
const settledPromise = {
|
|
544
614
|
...promise,
|
|
545
|
-
state:
|
|
546
|
-
value:
|
|
615
|
+
state: req.data.action.data.state,
|
|
616
|
+
value: req.data.action.data.value ?? {},
|
|
547
617
|
settledAt: now,
|
|
548
618
|
};
|
|
549
|
-
changes.push(this.setPromise(
|
|
550
|
-
changes.push(this.delPTimeout(
|
|
551
|
-
changes.push(...this.
|
|
552
|
-
|
|
553
|
-
changes.push(...this.notifySubscribers(settle.id));
|
|
554
|
-
return { response: this.response("task.fulfill", 200, { promise: this.toPromiseRecord(settled) }), changes };
|
|
619
|
+
changes.push(this.setPromise(settledPromise));
|
|
620
|
+
changes.push(this.delPTimeout(req.data.action.data.id));
|
|
621
|
+
changes.push(...this.triggerSettlement(req.data.id, now));
|
|
622
|
+
return { response: this.response("task.fulfill", 200, { promise: this.toPromiseRecord(settledPromise) }), changes };
|
|
555
623
|
}
|
|
556
624
|
taskSuspend(now, req) {
|
|
557
625
|
const task = this.tasks.get(req.data.id);
|
|
@@ -561,36 +629,43 @@ export class Server {
|
|
|
561
629
|
if (task.state !== "acquired") {
|
|
562
630
|
return { response: this.response("task.suspend", 409, "Task not acquired"), changes: [] };
|
|
563
631
|
}
|
|
632
|
+
// Logical-state check: if the task's promise is logically settled, the
|
|
633
|
+
// task is logically fulfilled and cannot be suspended.
|
|
634
|
+
const taskPromise = this.getPromiseOrThrow(req.data.id);
|
|
635
|
+
if (this.projectedPromise(now, taskPromise).state !== "pending") {
|
|
636
|
+
return { response: this.response("task.suspend", 409, "Task not acquired"), changes: [] };
|
|
637
|
+
}
|
|
564
638
|
if (task.version !== req.data.version) {
|
|
565
639
|
return { response: this.response("task.suspend", 409, "Version mismatch"), changes: [] };
|
|
566
640
|
}
|
|
567
641
|
const changes = [];
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
// Register this task as an awaiter on each awaited promise.
|
|
574
|
-
// The awaiter is always req.data.id (validated above).
|
|
575
|
-
const triggers = [];
|
|
642
|
+
// First pass: validate all promises exist and classify as pending or settled.
|
|
643
|
+
// Callbacks are only registered when ALL promises are pending (spec transition #40).
|
|
644
|
+
// If any promise is already settled, return 300 immediately with no callbacks (spec transition #39).
|
|
645
|
+
const pendingPromises = [];
|
|
646
|
+
let hasSettled = false;
|
|
576
647
|
for (const action of req.data.actions) {
|
|
577
648
|
const awaitedPromise = this.promises.get(action.data.awaited);
|
|
578
649
|
if (!awaitedPromise) {
|
|
579
650
|
return { response: this.response("task.suspend", 422, {}), changes: [] };
|
|
580
651
|
}
|
|
581
652
|
if (awaitedPromise.state === "pending") {
|
|
582
|
-
|
|
583
|
-
changes.push(this.setPromise(awaitedPromise));
|
|
653
|
+
pendingPromises.push(awaitedPromise);
|
|
584
654
|
}
|
|
585
655
|
else {
|
|
586
|
-
|
|
587
|
-
triggers.push(action.data.awaited);
|
|
656
|
+
hasSettled = true;
|
|
588
657
|
}
|
|
589
658
|
}
|
|
590
|
-
|
|
659
|
+
// Immediate resume — stay acquired, clear all resumes, no callbacks registered
|
|
660
|
+
if (hasSettled) {
|
|
661
|
+
changes.push(this.setTask({ ...task, resumes: new Set() }));
|
|
591
662
|
return { response: this.response("task.suspend", 300, { preload: this.preload(req.data.id) }), changes };
|
|
592
663
|
}
|
|
593
|
-
//
|
|
664
|
+
// All pending: register callbacks then suspend
|
|
665
|
+
for (const awaitedPromise of pendingPromises) {
|
|
666
|
+
awaitedPromise.callbacks.add(req.data.id);
|
|
667
|
+
changes.push(this.setPromise(awaitedPromise));
|
|
668
|
+
}
|
|
594
669
|
changes.push(this.setTask({ ...task, state: "suspended", pid: undefined, ttl: undefined, resumes: new Set() }));
|
|
595
670
|
changes.push(this.delTTimeout(req.data.id));
|
|
596
671
|
return { response: this.response("task.suspend", 200, {}), changes };
|
|
@@ -619,15 +694,14 @@ export class Server {
|
|
|
619
694
|
return { response: this.response("task.continue", 409, "Task is not halted"), changes: [] };
|
|
620
695
|
}
|
|
621
696
|
const changes = [];
|
|
622
|
-
|
|
623
|
-
changes.push(this.setTask({ ...task, state: "pending", version: newVersion }));
|
|
697
|
+
changes.push(this.setTask({ ...task, state: "pending" }));
|
|
624
698
|
changes.push(this.setTTimeout({ id: req.data.id, type: 0, timeout: now + PENDING_RETRY_TTL }));
|
|
625
699
|
const promise = this.getPromiseOrThrow(req.data.id);
|
|
626
700
|
const address = this.getAddressOrThrow(promise);
|
|
627
701
|
changes.push(this.sendMessage(address, {
|
|
628
702
|
kind: "execute",
|
|
629
703
|
head: {},
|
|
630
|
-
data: { task: { id: req.data.id, version:
|
|
704
|
+
data: { task: { id: req.data.id, version: task.version } },
|
|
631
705
|
}));
|
|
632
706
|
return { response: this.response("task.continue", 200, {}), changes };
|
|
633
707
|
}
|
|
@@ -639,6 +713,12 @@ export class Server {
|
|
|
639
713
|
if (task.state !== "acquired") {
|
|
640
714
|
return { response: this.response("task.fence", 409, "Fence check failed"), changes: [] };
|
|
641
715
|
}
|
|
716
|
+
// Logical-state check: if the task's promise is logically settled,
|
|
717
|
+
// the task is logically fulfilled and the fence fails.
|
|
718
|
+
const taskPromise = this.getPromiseOrThrow(req.data.id);
|
|
719
|
+
if (this.projectedPromise(now, taskPromise).state !== "pending") {
|
|
720
|
+
return { response: this.response("task.fence", 409, "Fence check failed"), changes: [] };
|
|
721
|
+
}
|
|
642
722
|
if (task.version !== req.data.version) {
|
|
643
723
|
return { response: this.response("task.fence", 409, "Fence check failed"), changes: [] };
|
|
644
724
|
}
|
|
@@ -650,8 +730,12 @@ export class Server {
|
|
|
650
730
|
else {
|
|
651
731
|
inner = this.promiseSettle(now, action);
|
|
652
732
|
}
|
|
733
|
+
const actionResponse = {
|
|
734
|
+
...inner.response,
|
|
735
|
+
head: { corrId: action.head.corrId, status: inner.response.head.status, version: action.head.version },
|
|
736
|
+
};
|
|
653
737
|
return {
|
|
654
|
-
response: this.response("task.fence", 200, { action:
|
|
738
|
+
response: this.response("task.fence", 200, { action: actionResponse, preload: [] }),
|
|
655
739
|
changes: inner.changes,
|
|
656
740
|
};
|
|
657
741
|
}
|
|
@@ -662,6 +746,12 @@ export class Server {
|
|
|
662
746
|
if (!task || task.state !== "acquired" || task.version !== ref.version || task.pid !== req.data.pid) {
|
|
663
747
|
continue;
|
|
664
748
|
}
|
|
749
|
+
// Logical-state check: if the task's promise is logically settled,
|
|
750
|
+
// the task is logically fulfilled and its lease cannot be refreshed.
|
|
751
|
+
const promise = this.getPromiseOrThrow(ref.id);
|
|
752
|
+
if (this.projectedPromise(now, promise).state !== "pending") {
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
665
755
|
const ttl = task.ttl ?? 30000;
|
|
666
756
|
changes.push(this.setTTimeout({ id: ref.id, type: 1, timeout: now + ttl }));
|
|
667
757
|
}
|
|
@@ -678,10 +768,10 @@ export class Server {
|
|
|
678
768
|
return { response: this.response("schedule.get", 200, { schedule: this.toScheduleRecord(schedule) }), changes: [] };
|
|
679
769
|
}
|
|
680
770
|
scheduleCreate(now, req) {
|
|
681
|
-
const
|
|
682
|
-
if (
|
|
771
|
+
const existingSchedule = this.schedules.get(req.data.id);
|
|
772
|
+
if (existingSchedule) {
|
|
683
773
|
return {
|
|
684
|
-
response: this.response("schedule.create", 200, { schedule: this.toScheduleRecord(
|
|
774
|
+
response: this.response("schedule.create", 200, { schedule: this.toScheduleRecord(existingSchedule) }),
|
|
685
775
|
changes: [],
|
|
686
776
|
};
|
|
687
777
|
}
|
|
@@ -738,8 +828,8 @@ export class Server {
|
|
|
738
828
|
response: this.response("debug.snap", 200, {
|
|
739
829
|
promises: Array.from(this.promises.values()).map((p) => this.toPromiseRecord(p)),
|
|
740
830
|
promiseTimeouts: this.pTimeouts,
|
|
741
|
-
callbacks: Array.from(this.promises.values()).flatMap((p) => [...p.
|
|
742
|
-
listeners: Array.from(this.promises.values()).flatMap((p) => [...p.
|
|
831
|
+
callbacks: Array.from(this.promises.values()).flatMap((p) => [...p.callbacks].map((awaiter) => ({ awaiter, awaited: p.id }))),
|
|
832
|
+
listeners: Array.from(this.promises.values()).flatMap((p) => [...p.listeners].map((address) => ({ id: p.id, address }))),
|
|
743
833
|
tasks: Array.from(this.tasks.values()).map((t) => this.toTaskRecord(t)),
|
|
744
834
|
taskTimeouts: this.tTimeouts,
|
|
745
835
|
messages: this.outgoing,
|
|
@@ -792,11 +882,11 @@ export class Server {
|
|
|
792
882
|
// order promises appear in pTimeouts.
|
|
793
883
|
//
|
|
794
884
|
// Phase 1: Settle all expired promises (state change only).
|
|
795
|
-
// Phase 2: Fulfill tasks whose own promise settled (
|
|
796
|
-
// Phase 3: Resume suspended
|
|
885
|
+
// Phase 2: Fulfill tasks whose own promise settled (triggerFulfilled).
|
|
886
|
+
// Phase 3: Resume suspended callbacks of settled promises (triggerCallbacks).
|
|
797
887
|
//
|
|
798
888
|
// Phase 2 before phase 3 ensures that a task whose own promise settled
|
|
799
|
-
// is fulfilled before
|
|
889
|
+
// is fulfilled before triggerCallbacks runs. This prevents a spurious
|
|
800
890
|
// suspended → pending (version++) → fulfilled path for tasks that
|
|
801
891
|
// should go directly suspended → fulfilled.
|
|
802
892
|
const settledIds = [];
|
|
@@ -818,26 +908,25 @@ export class Server {
|
|
|
818
908
|
}
|
|
819
909
|
// Phase 2: Fulfill tasks whose own promise settled
|
|
820
910
|
for (const id of settledIds) {
|
|
821
|
-
changes.push(...this.
|
|
911
|
+
changes.push(...this.triggerFulfilled(id));
|
|
822
912
|
}
|
|
823
|
-
// Phase 3: Resume suspended
|
|
913
|
+
// Phase 3: Resume suspended callbacks and notify listeners
|
|
824
914
|
for (const id of settledIds) {
|
|
825
|
-
changes.push(...this.
|
|
826
|
-
changes.push(...this.
|
|
915
|
+
changes.push(...this.triggerCallbacks(id, now));
|
|
916
|
+
changes.push(...this.triggerListeners(id));
|
|
827
917
|
}
|
|
828
918
|
for (const action of actions) {
|
|
829
919
|
if (action.kind === "task.release") {
|
|
830
920
|
const task = this.getTaskOrThrow(action.data.id);
|
|
831
921
|
if (task.state === "acquired" && task.version === action.data.version) {
|
|
832
|
-
|
|
833
|
-
changes.push(this.setTask({ ...task, state: "pending", version: newVersion, pid: undefined, ttl: undefined }));
|
|
922
|
+
changes.push(this.setTask({ ...task, state: "pending", pid: undefined, ttl: undefined }));
|
|
834
923
|
changes.push(this.setTTimeout({ id: action.data.id, type: 0, timeout: now + PENDING_RETRY_TTL }));
|
|
835
924
|
const promise = this.getPromiseOrThrow(action.data.id);
|
|
836
925
|
const address = this.getAddressOrThrow(promise);
|
|
837
926
|
changes.push(this.sendMessage(address, {
|
|
838
927
|
kind: "execute",
|
|
839
928
|
head: {},
|
|
840
|
-
data: { task: { id: action.data.id, version:
|
|
929
|
+
data: { task: { id: action.data.id, version: task.version } },
|
|
841
930
|
}));
|
|
842
931
|
}
|
|
843
932
|
}
|
|
@@ -865,12 +954,12 @@ export class Server {
|
|
|
865
954
|
.replaceAll("{{.timestamp}}", String(st.timeout));
|
|
866
955
|
const { changes: createChanges } = this.promiseCreate(now, {
|
|
867
956
|
kind: "promise.create",
|
|
868
|
-
head: { corrId:
|
|
957
|
+
head: { corrId: "", version: "" },
|
|
869
958
|
data: {
|
|
870
959
|
id: promiseId,
|
|
871
960
|
timeoutAt: st.timeout + schedule.promiseTimeout,
|
|
872
961
|
param: schedule.promiseParam,
|
|
873
|
-
tags: schedule.promiseTags,
|
|
962
|
+
tags: { ...schedule.promiseTags, "resonate:schedule": schedule.id },
|
|
874
963
|
},
|
|
875
964
|
});
|
|
876
965
|
changes.push(...createChanges);
|
|
@@ -892,7 +981,7 @@ export class Server {
|
|
|
892
981
|
// CONVERTERS
|
|
893
982
|
// ===========================================================================
|
|
894
983
|
toPromiseRecord(p) {
|
|
895
|
-
const {
|
|
984
|
+
const { callbacks, listeners, settledAt, ...rest } = p;
|
|
896
985
|
return settledAt != null ? { ...rest, settledAt } : rest;
|
|
897
986
|
}
|
|
898
987
|
toTaskRecord(t) {
|
|
@@ -923,7 +1012,9 @@ export class Server {
|
|
|
923
1012
|
// ===========================================================================
|
|
924
1013
|
// HELPERS
|
|
925
1014
|
// ===========================================================================
|
|
926
|
-
|
|
1015
|
+
tryEagerTimeout(now, id) {
|
|
1016
|
+
if (this.timeoutMode !== "eager")
|
|
1017
|
+
return [];
|
|
927
1018
|
const promise = this.promises.get(id);
|
|
928
1019
|
if (!promise || promise.state !== "pending" || now < promise.timeoutAt) {
|
|
929
1020
|
return [];
|
|
@@ -932,50 +1023,37 @@ export class Server {
|
|
|
932
1023
|
const state = this.timeoutState(promise.tags);
|
|
933
1024
|
changes.push(this.setPromise({ ...promise, state, settledAt: promise.timeoutAt }));
|
|
934
1025
|
changes.push(this.delPTimeout(id));
|
|
935
|
-
changes.push(...this.
|
|
936
|
-
changes.push(...this.resumeAwaiters(id, now));
|
|
937
|
-
changes.push(...this.notifySubscribers(id));
|
|
1026
|
+
changes.push(...this.triggerSettlement(id, now));
|
|
938
1027
|
return changes;
|
|
939
1028
|
}
|
|
940
|
-
|
|
1029
|
+
triggerSettlement(id, now) {
|
|
1030
|
+
return [...this.triggerFulfilled(id), ...this.triggerCallbacks(id, now), ...this.triggerListeners(id)];
|
|
1031
|
+
}
|
|
1032
|
+
triggerFulfilled(promiseId) {
|
|
941
1033
|
const task = this.tasks.get(promiseId);
|
|
942
|
-
if (!task)
|
|
943
|
-
|
|
944
|
-
if (!promise?.tags["resonate:target"])
|
|
945
|
-
return [];
|
|
946
|
-
return [
|
|
947
|
-
this.setTask({
|
|
948
|
-
id: promiseId,
|
|
949
|
-
state: "fulfilled",
|
|
950
|
-
version: 0,
|
|
951
|
-
pid: undefined,
|
|
952
|
-
ttl: undefined,
|
|
953
|
-
resumes: new Set(),
|
|
954
|
-
}),
|
|
955
|
-
];
|
|
956
|
-
}
|
|
1034
|
+
if (!task)
|
|
1035
|
+
return [];
|
|
957
1036
|
if (task.state === "fulfilled")
|
|
958
1037
|
return [];
|
|
959
1038
|
const changes = [];
|
|
960
1039
|
changes.push(this.setTask({ ...task, state: "fulfilled", pid: undefined, ttl: undefined, resumes: new Set() }));
|
|
961
1040
|
changes.push(this.delTTimeout(promiseId));
|
|
962
|
-
// Remove this task from all promise
|
|
1041
|
+
// Remove this task from all promise callbacks (delete callbacks where awaiter_id = task_id)
|
|
963
1042
|
for (const [, promise] of this.promises) {
|
|
964
|
-
if (promise.
|
|
1043
|
+
if (promise.callbacks.delete(promiseId)) {
|
|
965
1044
|
changes.push(this.setPromise(promise));
|
|
966
1045
|
}
|
|
967
1046
|
}
|
|
968
1047
|
return changes;
|
|
969
1048
|
}
|
|
970
|
-
|
|
1049
|
+
triggerCallbacks(promiseId, now) {
|
|
971
1050
|
const settledPromise = this.getPromiseOrThrow(promiseId);
|
|
972
1051
|
const changes = [];
|
|
973
1052
|
// Resume or buffer for all tasks that were awaiting this promise
|
|
974
|
-
for (const awaiterId of settledPromise.
|
|
1053
|
+
for (const awaiterId of settledPromise.callbacks) {
|
|
975
1054
|
const task = this.getTaskOrThrow(awaiterId);
|
|
976
1055
|
if (task.state === "suspended") {
|
|
977
|
-
|
|
978
|
-
changes.push(this.setTask({ ...task, state: "pending", version: newVersion, resumes: new Set([promiseId]) }));
|
|
1056
|
+
changes.push(this.setTask({ ...task, state: "pending", resumes: new Set([promiseId]) }));
|
|
979
1057
|
// Add task timeout entry back (type=0 for pending retry)
|
|
980
1058
|
changes.push(this.setTTimeout({
|
|
981
1059
|
id: awaiterId,
|
|
@@ -987,7 +1065,7 @@ export class Server {
|
|
|
987
1065
|
changes.push(this.sendMessage(address, {
|
|
988
1066
|
kind: "execute",
|
|
989
1067
|
head: {},
|
|
990
|
-
data: { task: { id: awaiterId, version:
|
|
1068
|
+
data: { task: { id: awaiterId, version: task.version } },
|
|
991
1069
|
}));
|
|
992
1070
|
}
|
|
993
1071
|
else if (task.state === "pending" || task.state === "acquired" || task.state === "halted") {
|
|
@@ -996,24 +1074,24 @@ export class Server {
|
|
|
996
1074
|
changes.push(this.setTask(task));
|
|
997
1075
|
}
|
|
998
1076
|
}
|
|
999
|
-
// Clear
|
|
1000
|
-
settledPromise.
|
|
1077
|
+
// Clear callbacks after processing
|
|
1078
|
+
settledPromise.callbacks.clear();
|
|
1001
1079
|
changes.push(this.setPromise(settledPromise));
|
|
1002
1080
|
return changes;
|
|
1003
1081
|
}
|
|
1004
|
-
|
|
1082
|
+
triggerListeners(promiseId) {
|
|
1005
1083
|
const promise = this.getPromiseOrThrow(promiseId);
|
|
1006
|
-
if (promise.
|
|
1084
|
+
if (promise.listeners.size === 0)
|
|
1007
1085
|
return [];
|
|
1008
1086
|
const changes = [];
|
|
1009
|
-
for (const address of promise.
|
|
1087
|
+
for (const address of promise.listeners) {
|
|
1010
1088
|
changes.push(this.sendMessage(address, {
|
|
1011
1089
|
kind: "unblock",
|
|
1012
1090
|
head: {},
|
|
1013
1091
|
data: { promise: this.toPromiseRecord(promise) },
|
|
1014
1092
|
}));
|
|
1015
1093
|
}
|
|
1016
|
-
promise.
|
|
1094
|
+
promise.listeners.clear();
|
|
1017
1095
|
changes.push(this.setPromise(promise));
|
|
1018
1096
|
return changes;
|
|
1019
1097
|
}
|
|
@@ -1035,6 +1113,42 @@ export class Server {
|
|
|
1035
1113
|
timeoutState(tags) {
|
|
1036
1114
|
return tags["resonate:timer"] === "true" ? "resolved" : "rejected_timedout";
|
|
1037
1115
|
}
|
|
1116
|
+
// Projection: return the LOGICAL view of a promise without mutating
|
|
1117
|
+
// storage. When physical state is `pending` and `now >= timeoutAt`, the
|
|
1118
|
+
// logical state is the terminal state implied by the timer tag. Used at
|
|
1119
|
+
// response-construction sites in read paths so callers always observe
|
|
1120
|
+
// logical state regardless of whether convergence has happened yet.
|
|
1121
|
+
//
|
|
1122
|
+
// Unguarded by timeoutMode: when timeoutMode === "eager", the physical
|
|
1123
|
+
// state already matches the logical state by the time this runs (because
|
|
1124
|
+
// tryEagerTimeout fired at the start of the action), so the projection
|
|
1125
|
+
// condition is never met and this is a no-op.
|
|
1126
|
+
projectedPromise(now, promise) {
|
|
1127
|
+
if (promise.state !== "pending" || now < promise.timeoutAt) {
|
|
1128
|
+
return promise;
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
...promise,
|
|
1132
|
+
state: this.timeoutState(promise.tags),
|
|
1133
|
+
settledAt: promise.timeoutAt,
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
// Projection: return the LOGICAL view of a task without mutating storage.
|
|
1137
|
+
// A task's logical state is bound to its promise's logical state — when
|
|
1138
|
+
// the promise is logically settled, the task is logically fulfilled.
|
|
1139
|
+
//
|
|
1140
|
+
// Unguarded for the same reason as projectedPromise.
|
|
1141
|
+
projectedTask(now, task) {
|
|
1142
|
+
const promise = this.promises.get(task.id);
|
|
1143
|
+
if (!promise)
|
|
1144
|
+
return task;
|
|
1145
|
+
const projected = this.projectedPromise(now, promise);
|
|
1146
|
+
if (projected.state === "pending")
|
|
1147
|
+
return task;
|
|
1148
|
+
if (task.state === "fulfilled")
|
|
1149
|
+
return task;
|
|
1150
|
+
return { ...task, state: "fulfilled", pid: undefined, ttl: undefined };
|
|
1151
|
+
}
|
|
1038
1152
|
getPromiseOrThrow(id) {
|
|
1039
1153
|
const promise = this.promises.get(id);
|
|
1040
1154
|
if (!promise) {
|
|
@@ -1180,6 +1294,9 @@ export class LocalNetwork {
|
|
|
1180
1294
|
this.anycast = `local://any@${group}/${pid}`;
|
|
1181
1295
|
}
|
|
1182
1296
|
// -- Network ---------------------------------------------------------------
|
|
1297
|
+
match(target) {
|
|
1298
|
+
return `local://any@${target}`;
|
|
1299
|
+
}
|
|
1183
1300
|
async init() {
|
|
1184
1301
|
if (this.started)
|
|
1185
1302
|
return;
|