@nimbus-sh/fabric 0.7.0 → 0.8.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 +19 -1
- package/dist/bindings.d.ts +11 -0
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +19 -8
- package/dist/connections.d.ts +1 -1
- package/dist/connections.js +1 -1
- package/dist/do-calls.d.ts +71 -9
- package/dist/do-calls.d.ts.map +1 -1
- package/dist/do-calls.js +148 -22
- package/dist/facet-pool.d.ts +3 -0
- package/dist/facet-pool.d.ts.map +1 -1
- package/dist/facet-pool.js +3 -0
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +6 -3
- package/dist/fenced-work.d.ts +3 -3
- package/dist/host-dispatch.d.ts +12 -4
- package/dist/host-dispatch.d.ts.map +1 -1
- package/dist/host-dispatch.js +11 -4
- package/dist/image-store.js +2 -2
- package/dist/inner-do-registry.d.ts +9 -0
- package/dist/inner-do-registry.d.ts.map +1 -1
- package/dist/inner-do-registry.js +35 -0
- package/dist/isolate-pool.d.ts +8 -1
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +9 -7
- package/dist/process-fabric.d.ts +16 -5
- package/dist/process-fabric.d.ts.map +1 -1
- package/dist/process-fabric.js +18 -6
- package/dist/process-host.d.ts +5 -0
- package/dist/process-host.d.ts.map +1 -1
- package/dist/process-host.js +13 -11
- package/dist/supervisor-props.d.ts +47 -0
- package/dist/supervisor-props.d.ts.map +1 -0
- package/dist/supervisor-props.js +37 -0
- package/dist/workerd-facet-host.d.ts +4 -17
- package/dist/workerd-facet-host.d.ts.map +1 -1
- package/dist/workerd-facet-host.js +98 -32
- package/package.json +6 -6
- package/src/bindings.ts +25 -10
- package/src/connections.ts +1 -1
- package/src/do-calls.ts +182 -25
- package/src/facet-pool.ts +5 -0
- package/src/fanout.ts +6 -3
- package/src/fenced-work.ts +3 -3
- package/src/host-dispatch.ts +22 -7
- package/src/image-store.ts +2 -2
- package/src/inner-do-registry.ts +31 -0
- package/src/isolate-pool.ts +16 -8
- package/src/process-fabric.ts +33 -10
- package/src/process-host.ts +18 -11
- package/src/supervisor-props.ts +56 -0
- package/src/workerd-facet-host.ts +99 -37
package/README.md
CHANGED
|
@@ -51,7 +51,25 @@ composeFabric({
|
|
|
51
51
|
adoptCtxExports(ctx.exports);
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
`composeFabric` runs once per isolate. Calling it again with the same values
|
|
55
|
+
is a no-op; calling it again with different values throws, naming both. A
|
|
56
|
+
Worker that imports Nimbus's own entry (`@nimbus-sh/sdk/worker`) inherits
|
|
57
|
+
that entry's composition, so a host that names its own namespace composes in
|
|
58
|
+
a Worker that does not import it.
|
|
59
|
+
|
|
60
|
+
The route back to the host (namespace, dispatch method, supervisor
|
|
61
|
+
entrypoint) is minted into every binding the fabric hands a program, in the
|
|
62
|
+
host's isolate, and the entrypoints that answer those bindings read it from
|
|
63
|
+
their props. The platform serves a Worker's entrypoints from whichever
|
|
64
|
+
isolate it likes; an isolate that composed nothing still answers to the
|
|
65
|
+
right host.
|
|
66
|
+
|
|
67
|
+
Every instance of the host namespace forwards `supervisorOp(envelope)` to a
|
|
68
|
+
hosted runtime, the siblings Nimbus opens by name included: a wide `npm
|
|
69
|
+
install` fans out to `nbf:*` peers of the same namespace, and
|
|
70
|
+
`NIMBUS_PROCESS_HOST=peer` places processes on siblings. A sibling that
|
|
71
|
+
forwards to a bare workspace answers "is a host op, and this handler is a
|
|
72
|
+
bare workspace's".
|
|
55
73
|
|
|
56
74
|
## Timers
|
|
57
75
|
|
package/dist/bindings.d.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
26
26
|
import { z } from 'zod/v4';
|
|
27
|
+
import type { HostRoute } from './composition.js';
|
|
27
28
|
import type { WorkerCode } from './vendor/types.js';
|
|
28
29
|
/**
|
|
29
30
|
* A dynamic worker's entrypoint, as hop 3 relays to it. `fetch` is the
|
|
@@ -67,6 +68,8 @@ interface NimbusAssetsProps {
|
|
|
67
68
|
assetsDir?: string;
|
|
68
69
|
/** Supervisor DO id whose VFS holds the assets. */
|
|
69
70
|
doId?: string;
|
|
71
|
+
/** The way back to that DO, minted with the binding. */
|
|
72
|
+
route?: HostRoute;
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
72
75
|
* Assets binding shim. The inner Worker calls `env.ASSETS.fetch(request)`
|
|
@@ -105,6 +108,12 @@ declare const NimbusLoadedEntrypointPropsSchema: z.ZodObject<{
|
|
|
105
108
|
doId: z.ZodString;
|
|
106
109
|
pid: z.ZodNumber;
|
|
107
110
|
writerId: z.ZodString;
|
|
111
|
+
route: z.ZodOptional<z.ZodObject<{
|
|
112
|
+
supervisorEntrypoint: z.ZodString;
|
|
113
|
+
hostNamespace: z.ZodString;
|
|
114
|
+
hostDispatchMethod: z.ZodString;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
hostIncarnation: z.ZodOptional<z.ZodString>;
|
|
108
117
|
}, z.core.$strip>>;
|
|
109
118
|
stage: z.ZodOptional<z.ZodUnknown>;
|
|
110
119
|
}, z.core.$loose>;
|
|
@@ -213,6 +222,8 @@ export declare class NimbusLoadedEntrypoint extends WorkerEntrypoint<NimbusLoade
|
|
|
213
222
|
interface NimbusDoNamespaceProps {
|
|
214
223
|
bindingName?: string;
|
|
215
224
|
supervisorDoId?: string;
|
|
225
|
+
/** The way back to the supervisor DO, minted with the binding. */
|
|
226
|
+
route?: HostRoute;
|
|
216
227
|
}
|
|
217
228
|
/**
|
|
218
229
|
* `env.MY_DO` shim — a DurableObjectNamespace-like WorkerEntrypoint.
|
package/dist/bindings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
|
package/dist/bindings.js
CHANGED
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
26
26
|
import { z } from 'zod/v4';
|
|
27
27
|
import { disposeRpcResource, useRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
28
|
-
import { supervisorEntrypoint, supervisorEntrypointName } from './composition.js';
|
|
29
|
-
import { stagedBootAssembler } from './composition.js';
|
|
28
|
+
import { supervisorEntrypoint, supervisorEntrypointName, stagedBootAssembler } from './composition.js';
|
|
30
29
|
import { hostNamespaceBinding, hostOpDispatch } from './host-dispatch.js';
|
|
31
30
|
import { assertModuleMapWithinCodeLimit } from './budgets.js';
|
|
32
31
|
/**
|
|
@@ -98,9 +97,9 @@ export class NimbusAssetsRPC extends WorkerEntrypoint {
|
|
|
98
97
|
let stub = null;
|
|
99
98
|
let dispatch;
|
|
100
99
|
try {
|
|
101
|
-
const ns = hostNamespaceBinding(this.env, 'NimbusAssetsRPC');
|
|
100
|
+
const ns = hostNamespaceBinding(this.env, 'NimbusAssetsRPC', props.route);
|
|
102
101
|
stub = ns.get(ns.idFromString(doId));
|
|
103
|
-
dispatch = hostOpDispatch(stub, 'NimbusAssetsRPC');
|
|
102
|
+
dispatch = hostOpDispatch(stub, 'NimbusAssetsRPC', props.route);
|
|
104
103
|
}
|
|
105
104
|
catch (e) {
|
|
106
105
|
disposeRpcResource(stub);
|
|
@@ -242,6 +241,11 @@ function mimeTypeForPath(path) {
|
|
|
242
241
|
const _NIMBUS_LOADED_CODES = new Map();
|
|
243
242
|
const _LOADED_CODES_MAX = 32;
|
|
244
243
|
let _loadedCodesEvictions = 0;
|
|
244
|
+
const HostRouteSchema = z.object({
|
|
245
|
+
supervisorEntrypoint: z.string().min(1),
|
|
246
|
+
hostNamespace: z.string().min(1),
|
|
247
|
+
hostDispatchMethod: z.string().min(1),
|
|
248
|
+
});
|
|
245
249
|
const NimbusLoadedEntrypointPropsSchema = z.object({
|
|
246
250
|
key: z.string().min(1),
|
|
247
251
|
name: z.string().nullable().optional(),
|
|
@@ -250,6 +254,9 @@ const NimbusLoadedEntrypointPropsSchema = z.object({
|
|
|
250
254
|
doId: z.string().min(1),
|
|
251
255
|
pid: z.number().int().nonnegative(),
|
|
252
256
|
writerId: z.string().uuid(),
|
|
257
|
+
route: HostRouteSchema.optional(),
|
|
258
|
+
/** The host instance's delivery incarnation (ResidentSupervisorProps). */
|
|
259
|
+
hostIncarnation: z.string().uuid().optional(),
|
|
253
260
|
}).optional(),
|
|
254
261
|
/**
|
|
255
262
|
* Staged-artifact spec, for a ONE-SHOT run. The module map — ~23 MB for
|
|
@@ -445,9 +452,12 @@ export class NimbusLoadedEntrypoint extends WorkerEntrypoint {
|
|
|
445
452
|
async _supervisorBinding(props) {
|
|
446
453
|
if (!props.supervisor)
|
|
447
454
|
return undefined;
|
|
448
|
-
|
|
455
|
+
// The route names the entrypoint too: this stateless hop may run in an
|
|
456
|
+
// isolate whose composition is not the host's.
|
|
457
|
+
const name = props.supervisor.route?.supervisorEntrypoint ?? supervisorEntrypointName();
|
|
458
|
+
const factory = supervisorEntrypoint(ctxExportsOf(this.ctx), name ?? undefined);
|
|
449
459
|
if (!factory) {
|
|
450
|
-
throw new Error(`Nimbus: ctx.exports.${
|
|
460
|
+
throw new Error(`Nimbus: ctx.exports.${name ?? '<supervisor entrypoint>'} unavailable`);
|
|
451
461
|
}
|
|
452
462
|
return await factory({ props: props.supervisor });
|
|
453
463
|
}
|
|
@@ -635,6 +645,7 @@ export class NimbusDurableObjectNamespace extends WorkerEntrypoint {
|
|
|
635
645
|
props: {
|
|
636
646
|
bindingName: props.bindingName,
|
|
637
647
|
supervisorDoId: props.supervisorDoId,
|
|
648
|
+
route: props.route,
|
|
638
649
|
id: String(id),
|
|
639
650
|
},
|
|
640
651
|
});
|
|
@@ -664,9 +675,9 @@ export class NimbusDOStub extends WorkerEntrypoint {
|
|
|
664
675
|
let stub = null;
|
|
665
676
|
let dispatch;
|
|
666
677
|
try {
|
|
667
|
-
const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub');
|
|
678
|
+
const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub', props.route);
|
|
668
679
|
stub = ns.get(ns.idFromString(supervisorDoId));
|
|
669
|
-
dispatch = hostOpDispatch(stub, 'NimbusDOStub');
|
|
680
|
+
dispatch = hostOpDispatch(stub, 'NimbusDOStub', props.route);
|
|
670
681
|
}
|
|
671
682
|
catch (e) {
|
|
672
683
|
disposeRpcResource(stub);
|
package/dist/connections.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* state over the WebSocket attachment.
|
|
4
4
|
*
|
|
5
5
|
* Specified from Proteus's DeviceSocketHub (`cf-backend/src/user/device-hub.ts`)
|
|
6
|
-
* and CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
|
|
6
|
+
* and its original CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
|
|
7
7
|
* pattern into its two halves:
|
|
8
8
|
* - a TAG is the immutable-at-accept lookup key and authorization — it
|
|
9
9
|
* rides the hibernation state, which is why the rpc gate persists auth
|
package/dist/connections.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* state over the WebSocket attachment.
|
|
4
4
|
*
|
|
5
5
|
* Specified from Proteus's DeviceSocketHub (`cf-backend/src/user/device-hub.ts`)
|
|
6
|
-
* and CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
|
|
6
|
+
* and its original CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
|
|
7
7
|
* pattern into its two halves:
|
|
8
8
|
* - a TAG is the immutable-at-accept lookup key and authorization — it
|
|
9
9
|
* rides the hibernation state, which is why the rpc gate persists auth
|
package/dist/do-calls.d.ts
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* the one property that decides whether a retry is safe.
|
|
4
4
|
*
|
|
5
5
|
* Both consumers asked for this. Proteus hand-wrote the retry
|
|
6
|
-
* (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
6
|
+
* (originally `cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
7
7
|
* "An operation that appends, sends, charges or mints is never wrapped: a
|
|
8
8
|
* dropped call there may already have run, so a retry is a correctness bug
|
|
9
9
|
* wearing resilience as a costume." agent-core has no retry machinery at all
|
|
10
10
|
* and its backlog calls the gap "the most production-proven gap in the
|
|
11
11
|
* corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
|
|
12
|
+
* A mutation earns a retry only by carrying an identity its callee applies
|
|
13
|
+
* at most once; it is then `idempotent` by construction (see `mutating`).
|
|
12
14
|
*
|
|
13
15
|
* What the platform contract requires, and this keeps:
|
|
14
16
|
* - a FRESH stub per attempt. Cloudflare documents that many exceptions
|
|
@@ -20,6 +22,11 @@
|
|
|
20
22
|
* object is what overloaded it.
|
|
21
23
|
* - attempts and backoff are the consumer-proven bounds: 3 attempts total,
|
|
22
24
|
* full-jitter delays in [0, 2**attempt * 60ms).
|
|
25
|
+
* - an `idempotent` call may also be HEDGED (`hedgeAfterMs`): an attempt
|
|
26
|
+
* that has not answered by then is joined by the same call on a fresh
|
|
27
|
+
* stub, both left running, the first success taken. Hedges count
|
|
28
|
+
* against the attempts, and a callee that joins a repeat to the call it
|
|
29
|
+
* is already serving makes one that did arrive cost nothing.
|
|
23
30
|
*
|
|
24
31
|
* The resolver MINTS a stub per call and the verb disposes each one it
|
|
25
32
|
* minted — that ownership is what makes the fresh-stub retry real.
|
|
@@ -37,10 +44,38 @@ export interface DoCallRetryPolicy {
|
|
|
37
44
|
maxAttempts?: number;
|
|
38
45
|
baseDelayMs?: number;
|
|
39
46
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
47
|
+
* No repeat — retry or hedge — starts once this long has passed since the
|
|
48
|
+
* first attempt did; the failure in hand surfaces instead. A mutation made
|
|
49
|
+
* repeatable by an identity its callee dedupes needs it: the callee keeps
|
|
50
|
+
* what answers a repeat for a bounded time, so the caller's repeats must
|
|
51
|
+
* stop well inside it. Unbounded when absent.
|
|
52
|
+
*/
|
|
53
|
+
retryWindowMs?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Hedge an attempt that has not answered after this long: send the same
|
|
56
|
+
* call again on a fresh stub while the first stays in flight. The caller
|
|
57
|
+
* gets the first success; an answer after it is disposed and dropped. That
|
|
58
|
+
* is only harmless when a second delivery of the call changes nothing — a
|
|
59
|
+
* read — and cheap only when the callee joins a repeat to the call it is
|
|
60
|
+
* already serving. A hedge is an attempt: it counts against `maxAttempts`,
|
|
61
|
+
* and has its own deadline. Never hedged when absent.
|
|
62
|
+
*
|
|
63
|
+
* With attempts overlapping, a failure decides less. A retryable one is
|
|
64
|
+
* retried after its backoff while attempts remain, and `overloaded` stops
|
|
65
|
+
* every further repeat; either way the call keeps waiting on the attempts
|
|
66
|
+
* still in flight, and fails with the last failure only once none is. Any
|
|
67
|
+
* other failure is the callee's answer — the call ran — and is the call's
|
|
68
|
+
* at once.
|
|
69
|
+
*/
|
|
70
|
+
hedgeAfterMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Called once per retry, as it starts — after its backoff delay — with the
|
|
73
|
+
* failure the retry is answering. A retry a hedge made unnecessary, or
|
|
74
|
+
* that no attempt is left for, is never announced. The consumer's logging
|
|
75
|
+
* seam: Proteus's hand-rolled predecessor logged every retry so a flaky
|
|
76
|
+
* object is visible in Workers Logs rather than silently absorbed, and
|
|
77
|
+
* `operation` names it there. A callback that throws fails the call with
|
|
78
|
+
* its error.
|
|
44
79
|
*/
|
|
45
80
|
onRetry?(info: DoCallRetryInfo): void;
|
|
46
81
|
}
|
|
@@ -49,7 +84,10 @@ export interface DoCallRetryPolicy {
|
|
|
49
84
|
export interface DoCallRetryInfo {
|
|
50
85
|
operation: string;
|
|
51
86
|
classification: DoCallClass;
|
|
52
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* The 1-based number of the attempt that failed. Without hedging the retry
|
|
89
|
+
* is attempt+1; with it, other attempts may have started in between.
|
|
90
|
+
*/
|
|
53
91
|
attempt: number;
|
|
54
92
|
maxAttempts: number;
|
|
55
93
|
error: unknown;
|
|
@@ -79,9 +117,18 @@ export declare class DoCallError extends Error {
|
|
|
79
117
|
}
|
|
80
118
|
/**
|
|
81
119
|
* Call another Durable Object with an operation that is safe to repeat: a
|
|
82
|
-
* read,
|
|
83
|
-
*
|
|
84
|
-
*
|
|
120
|
+
* read, a converge-to-a-value write, or a mutation carrying an identity its
|
|
121
|
+
* callee applies at most once (see {@link mutating}). Transient failures
|
|
122
|
+
* retry on a fresh stub with full-jitter backoff; overloaded and permanent
|
|
123
|
+
* failures surface unchanged, as does the last error once no attempt may be
|
|
124
|
+
* repeated — attempts spent, or the policy's retry window closed. With
|
|
125
|
+
* `hedgeAfterMs`, an attempt still unanswered by then is joined by another
|
|
126
|
+
* on a fresh stub, and the first answer is taken: a success, or the
|
|
127
|
+
* callee's own error. A transient or overloaded failure then ends the call
|
|
128
|
+
* only once no attempt is left in flight.
|
|
129
|
+
*
|
|
130
|
+
* A failure of the resolver or of `onRetry` is the caller's own, and fails
|
|
131
|
+
* the call with it at once.
|
|
85
132
|
*/
|
|
86
133
|
export declare function idempotent<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>, policy?: DoCallRetryPolicy): Promise<T>;
|
|
87
134
|
/**
|
|
@@ -89,6 +136,21 @@ export declare function idempotent<S, T>(operation: string, stub: DoStubResolver
|
|
|
89
136
|
* or mints. NEVER retried — a dropped call may already have run. Failure
|
|
90
137
|
* surfaces as a {@link DoCallError} carrying the classification, so the
|
|
91
138
|
* caller can tell a refusal from an indeterminate drop.
|
|
139
|
+
*
|
|
140
|
+
* The rule is about the call as sent, not the operation's kind. A mutation
|
|
141
|
+
* the callee applies at most once per identity the call carries is
|
|
142
|
+
* repeatable by construction: a repeat of one that already ran is answered
|
|
143
|
+
* from the callee's record and applies nothing. Nimbus has two:
|
|
144
|
+
* - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
|
|
145
|
+
* a delivery id plus the callee INSTANCE's incarnation. The record lives
|
|
146
|
+
* in that instance's memory, and any other instance — or a callee that
|
|
147
|
+
* predates delivery — refuses the call permanently rather than apply it
|
|
148
|
+
* without one;
|
|
149
|
+
* - appends: writer, module incarnation and operation sequence, recorded
|
|
150
|
+
* durably until acknowledged.
|
|
151
|
+
* Such a call goes through {@link idempotent}, re-sending the same identity
|
|
152
|
+
* on every attempt, with a `retryWindowMs` inside the callee's retention of
|
|
153
|
+
* that record. Without such an identity, a mutation stays here.
|
|
92
154
|
*/
|
|
93
155
|
export declare function mutating<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>): Promise<T>;
|
|
94
156
|
//# sourceMappingURL=do-calls.d.ts.map
|
package/dist/do-calls.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"do-calls.d.ts","sourceRoot":"","sources":["../src/do-calls.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"do-calls.d.ts","sourceRoot":"","sources":["../src/do-calls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAqC,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAU1G,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CACvC;AAED;2CAC2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,WAAW,CAAC;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAErD;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAElC,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU;IACxC,QAAQ,CAAC,cAAc,EAAE,WAAW;gBAF3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,GAAG,UAAU,EAC/B,cAAc,EAAE,WAAW,EACpC,KAAK,EAAE,OAAO;CASjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,MAAM,GAAE,iBAAsB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAmHZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
|
package/dist/do-calls.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* the one property that decides whether a retry is safe.
|
|
4
4
|
*
|
|
5
5
|
* Both consumers asked for this. Proteus hand-wrote the retry
|
|
6
|
-
* (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
6
|
+
* (originally `cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
7
7
|
* "An operation that appends, sends, charges or mints is never wrapped: a
|
|
8
8
|
* dropped call there may already have run, so a retry is a correctness bug
|
|
9
9
|
* wearing resilience as a costume." agent-core has no retry machinery at all
|
|
10
10
|
* and its backlog calls the gap "the most production-proven gap in the
|
|
11
11
|
* corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
|
|
12
|
+
* A mutation earns a retry only by carrying an identity its callee applies
|
|
13
|
+
* at most once; it is then `idempotent` by construction (see `mutating`).
|
|
12
14
|
*
|
|
13
15
|
* What the platform contract requires, and this keeps:
|
|
14
16
|
* - a FRESH stub per attempt. Cloudflare documents that many exceptions
|
|
@@ -20,6 +22,11 @@
|
|
|
20
22
|
* object is what overloaded it.
|
|
21
23
|
* - attempts and backoff are the consumer-proven bounds: 3 attempts total,
|
|
22
24
|
* full-jitter delays in [0, 2**attempt * 60ms).
|
|
25
|
+
* - an `idempotent` call may also be HEDGED (`hedgeAfterMs`): an attempt
|
|
26
|
+
* that has not answered by then is joined by the same call on a fresh
|
|
27
|
+
* stub, both left running, the first success taken. Hedges count
|
|
28
|
+
* against the attempts, and a callee that joins a repeat to the call it
|
|
29
|
+
* is already serving makes one that did arrive cost nothing.
|
|
23
30
|
*
|
|
24
31
|
* The resolver MINTS a stub per call and the verb disposes each one it
|
|
25
32
|
* minted — that ownership is what makes the fresh-stub retry real.
|
|
@@ -64,38 +71,157 @@ export class DoCallError extends Error {
|
|
|
64
71
|
}
|
|
65
72
|
/**
|
|
66
73
|
* Call another Durable Object with an operation that is safe to repeat: a
|
|
67
|
-
* read,
|
|
68
|
-
*
|
|
69
|
-
*
|
|
74
|
+
* read, a converge-to-a-value write, or a mutation carrying an identity its
|
|
75
|
+
* callee applies at most once (see {@link mutating}). Transient failures
|
|
76
|
+
* retry on a fresh stub with full-jitter backoff; overloaded and permanent
|
|
77
|
+
* failures surface unchanged, as does the last error once no attempt may be
|
|
78
|
+
* repeated — attempts spent, or the policy's retry window closed. With
|
|
79
|
+
* `hedgeAfterMs`, an attempt still unanswered by then is joined by another
|
|
80
|
+
* on a fresh stub, and the first answer is taken: a success, or the
|
|
81
|
+
* callee's own error. A transient or overloaded failure then ends the call
|
|
82
|
+
* only once no attempt is left in flight.
|
|
83
|
+
*
|
|
84
|
+
* A failure of the resolver or of `onRetry` is the caller's own, and fails
|
|
85
|
+
* the call with it at once.
|
|
70
86
|
*/
|
|
71
|
-
export
|
|
87
|
+
export function idempotent(operation, stub, call, policy = {}) {
|
|
72
88
|
const maxAttempts = policy.maxAttempts ?? MAX_ATTEMPTS;
|
|
73
89
|
const baseDelayMs = policy.baseDelayMs ?? BASE_DELAY_MS;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
const { hedgeAfterMs, retryWindowMs } = policy;
|
|
91
|
+
const startedAt = Date.now();
|
|
92
|
+
// The executor form: fabric's library target predates Promise.withResolvers.
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const hedges = new Set();
|
|
95
|
+
let started = 0;
|
|
96
|
+
// Attempts in flight, or backing off before their retry: while one is,
|
|
97
|
+
// a failure is not the call's answer.
|
|
98
|
+
let live = 0;
|
|
99
|
+
// An attempt was shed as overloaded: nothing is repeated after it.
|
|
100
|
+
let refused = false;
|
|
101
|
+
let settled = false;
|
|
102
|
+
const settle = (answer) => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
settled = true;
|
|
106
|
+
for (const timer of hedges)
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
hedges.clear();
|
|
109
|
+
answer();
|
|
110
|
+
};
|
|
111
|
+
/** May another attempt start at `at`? */
|
|
112
|
+
const canRepeat = (at) => !settled && !refused && started < maxAttempts
|
|
113
|
+
&& (retryWindowMs === undefined || at - startedAt <= retryWindowMs);
|
|
114
|
+
/** `error` ended an attempt that will not be repeated: the call's answer, once nothing else is live. */
|
|
115
|
+
const exhausted = (error) => {
|
|
116
|
+
if (live === 0)
|
|
117
|
+
settle(() => reject(error));
|
|
118
|
+
};
|
|
119
|
+
/** A failed attempt, numbered: repeat it after its backoff, or let it stand. */
|
|
120
|
+
const failed = async (number, error) => {
|
|
121
|
+
if (settled)
|
|
122
|
+
return;
|
|
84
123
|
const classification = classifyDoCall(error);
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
124
|
+
if (classification === 'overloaded') {
|
|
125
|
+
// A shed call is no answer: nothing more is sent, and an attempt
|
|
126
|
+
// still in flight may yet answer.
|
|
127
|
+
refused = true;
|
|
128
|
+
exhausted(error);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!isRetryableDoCall(classification)) {
|
|
132
|
+
// The call ran and its answer is this error — ENOENT is a read's answer as much as bytes are.
|
|
133
|
+
settle(() => reject(error));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const delayMs = Math.floor(Math.random() * 2 ** number * baseDelayMs);
|
|
137
|
+
if (!canRepeat(Date.now() + delayMs)) {
|
|
138
|
+
exhausted(error);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
live++;
|
|
142
|
+
await new Promise((wake) => {
|
|
143
|
+
setTimeout(wake, delayMs);
|
|
90
144
|
});
|
|
91
|
-
|
|
92
|
-
|
|
145
|
+
live--;
|
|
146
|
+
// A hedge may have taken the last attempt, or answered, meanwhile.
|
|
147
|
+
if (!canRepeat(Date.now())) {
|
|
148
|
+
exhausted(error);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
policy.onRetry?.({ operation, classification, attempt: number, maxAttempts, error });
|
|
152
|
+
attempt();
|
|
153
|
+
};
|
|
154
|
+
const run = async () => {
|
|
155
|
+
const number = ++started;
|
|
156
|
+
live++;
|
|
157
|
+
const minted = await stub();
|
|
158
|
+
if (settled) {
|
|
159
|
+
live--;
|
|
160
|
+
disposeRpcResource(minted);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const hedge = hedgeAfterMs === undefined ? undefined : setTimeout(() => {
|
|
164
|
+
if (hedge !== undefined)
|
|
165
|
+
hedges.delete(hedge);
|
|
166
|
+
if (canRepeat(Date.now()))
|
|
167
|
+
attempt();
|
|
168
|
+
}, hedgeAfterMs);
|
|
169
|
+
if (hedge !== undefined)
|
|
170
|
+
hedges.add(hedge);
|
|
171
|
+
/** This attempt has its answer: it hedges no more, and its stub goes. */
|
|
172
|
+
const answered = () => {
|
|
173
|
+
if (hedge !== undefined) {
|
|
174
|
+
clearTimeout(hedge);
|
|
175
|
+
hedges.delete(hedge);
|
|
176
|
+
}
|
|
177
|
+
live--;
|
|
178
|
+
// A stub that threw may be permanently broken; none is ever reused.
|
|
179
|
+
disposeRpcResource(minted);
|
|
180
|
+
};
|
|
181
|
+
let result;
|
|
182
|
+
try {
|
|
183
|
+
result = await call(minted);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
answered();
|
|
187
|
+
await failed(number, error);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
answered();
|
|
191
|
+
if (settled) {
|
|
192
|
+
// Another attempt answered first: this answer is dropped, so nothing of it is kept.
|
|
193
|
+
disposeRpcResource(result);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
settle(() => resolve(result));
|
|
197
|
+
};
|
|
198
|
+
/** Start an attempt. Whatever it throws outside the call itself fails the call. */
|
|
199
|
+
const attempt = () => {
|
|
200
|
+
run().catch((error) => settle(() => reject(error)));
|
|
201
|
+
};
|
|
202
|
+
attempt();
|
|
203
|
+
});
|
|
93
204
|
}
|
|
94
205
|
/**
|
|
95
206
|
* Call another Durable Object with an operation that appends, sends, charges
|
|
96
207
|
* or mints. NEVER retried — a dropped call may already have run. Failure
|
|
97
208
|
* surfaces as a {@link DoCallError} carrying the classification, so the
|
|
98
209
|
* caller can tell a refusal from an indeterminate drop.
|
|
210
|
+
*
|
|
211
|
+
* The rule is about the call as sent, not the operation's kind. A mutation
|
|
212
|
+
* the callee applies at most once per identity the call carries is
|
|
213
|
+
* repeatable by construction: a repeat of one that already ran is answered
|
|
214
|
+
* from the callee's record and applies nothing. Nimbus has two:
|
|
215
|
+
* - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
|
|
216
|
+
* a delivery id plus the callee INSTANCE's incarnation. The record lives
|
|
217
|
+
* in that instance's memory, and any other instance — or a callee that
|
|
218
|
+
* predates delivery — refuses the call permanently rather than apply it
|
|
219
|
+
* without one;
|
|
220
|
+
* - appends: writer, module incarnation and operation sequence, recorded
|
|
221
|
+
* durably until acknowledged.
|
|
222
|
+
* Such a call goes through {@link idempotent}, re-sending the same identity
|
|
223
|
+
* on every attempt, with a `retryWindowMs` inside the callee's retention of
|
|
224
|
+
* that record. Without such an identity, a mutation stays here.
|
|
99
225
|
*/
|
|
100
226
|
export async function mutating(operation, stub, call) {
|
|
101
227
|
const minted = await stub();
|
package/dist/facet-pool.d.ts
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* whichever acts second aborts or retires facets the other still tracks,
|
|
42
42
|
* and the facet-id ledger here counts only the names this pool minted.
|
|
43
43
|
*/
|
|
44
|
+
import type { SqlDatabase } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
44
45
|
/** `ctx.facets`, as the pool drives it — same surface the facet host uses. */
|
|
45
46
|
export interface FacetPoolContainer {
|
|
46
47
|
get(name: string, start: () => Promise<{
|
|
@@ -56,6 +57,8 @@ export interface FacetPoolContext {
|
|
|
56
57
|
storage: {
|
|
57
58
|
get(key: string): Promise<unknown> | unknown;
|
|
58
59
|
put(key: string, value: unknown): Promise<void>;
|
|
60
|
+
/** The session's SQL, where the storage ledger (N18) records facet databases. */
|
|
61
|
+
sql?: SqlDatabase;
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
/**
|
package/dist/facet-pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;
|
|
1
|
+
{"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAQ3E,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC;IACrE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;wCACwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE;QACP,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,iFAAiF;QACjF,GAAG,CAAC,EAAE,WAAW,CAAC;KACnB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,gEAAgE;IAChE,MAAM,IAAI,IAAI,CAAC;IACf,sEAAsE;IACtE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAE1D;AAED,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,gBAAgB;IAElD;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GACvC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAkD1B"}
|
package/dist/facet-pool.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* whichever acts second aborts or retires facets the other still tracks,
|
|
42
42
|
* and the facet-id ledger here counts only the names this pool minted.
|
|
43
43
|
*/
|
|
44
|
+
import { forgetFacetStorage } from '@nimbus-sh/core/runtime/storage-ledger.js';
|
|
44
45
|
import { FACET_ID_LIFETIME_BUDGET, facetNameCount, recordFacetNameMinted, withFacetBudgetNamed, } from './budgets.js';
|
|
45
46
|
/** Names this ctx's pool has already charged to the lifetime ledger. */
|
|
46
47
|
const chargedNames = new WeakMap();
|
|
@@ -93,6 +94,8 @@ export class FacetPool {
|
|
|
93
94
|
return;
|
|
94
95
|
try {
|
|
95
96
|
facets.delete(name);
|
|
97
|
+
if (this.ctx.storage.sql)
|
|
98
|
+
forgetFacetStorage(this.ctx.storage.sql, name);
|
|
96
99
|
}
|
|
97
100
|
catch (e) {
|
|
98
101
|
throw new Error(`fabric: facet '${name}' was evicted but its storage was not reclaimed — `
|
package/dist/fanout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD;;+EAE+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,qBAAqB,UAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,uCAAuC;AACvC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa;IAoBzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,UAAU,CAAC,CAAC,EAAE,CAAC,EACnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC;IASf,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO;IAK7D;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;YAOvC,aAAa;YAoCb,eAAe;CA6J9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAUrE"}
|
package/dist/fanout.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
import { serializeFunction } from './vendor/serialize.js';
|
|
17
17
|
import { BindingError } from './vendor/errors.js';
|
|
18
18
|
import { IsolatePool } from './isolate-pool.js';
|
|
19
|
+
import { hostRoute } from './composition.js';
|
|
19
20
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
20
|
-
import {
|
|
21
|
+
import { classifyDoCall, describeError, isRetryableDoCall } from '@nimbus-sh/platform/oom-classify.js';
|
|
21
22
|
import { hostNamespaceBinding, hostOpDispatch, } from './host-dispatch.js';
|
|
22
23
|
/**
|
|
23
24
|
* Threshold at which routing switches from coordinator-local loaders to
|
|
@@ -265,6 +266,7 @@ export class Fanout {
|
|
|
265
266
|
// ... write into the peer's own VFS — invisible to the
|
|
266
267
|
// user. See INSTALL-HONESTY-retro.md.
|
|
267
268
|
coordinatorDoId: this.coordDoId,
|
|
269
|
+
coordinatorRoute: hostRoute() ?? undefined,
|
|
268
270
|
// Credential source for peer-side writeBatchStream — the
|
|
269
271
|
// invoking process pid, so package writes are authorized
|
|
270
272
|
// as the user (not rejected as pid:0).
|
|
@@ -295,8 +297,9 @@ export class Fanout {
|
|
|
295
297
|
return;
|
|
296
298
|
}
|
|
297
299
|
catch (err) {
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
+
const cls = classifyDoCall(err);
|
|
301
|
+
const schedule = cls === 'overloaded' ? PEER_OVERLOAD_BACKOFF_MS
|
|
302
|
+
: isRetryableDoCall(cls) ? PEER_RETRY_BACKOFF_MS
|
|
300
303
|
: null;
|
|
301
304
|
if (schedule && attempt < PEER_TRANSIENT_RESET_RETRIES) {
|
|
302
305
|
const backoff = schedule[Math.min(attempt, schedule.length - 1)];
|
package/dist/fenced-work.d.ts
CHANGED
|
@@ -53,9 +53,9 @@ export declare const FENCED_WORK_MAX_ATTEMPT = 1;
|
|
|
53
53
|
* (staging, 2026-08-13): every observed reset struck seconds AFTER the launch
|
|
54
54
|
* settled — the platform kills the object while the resident runs, which is
|
|
55
55
|
* when a launch-scoped row had already been deleted and recovery had nothing
|
|
56
|
-
* to find. A resident's facet
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* to find. A resident's facet can outlive its session instance (a pending
|
|
57
|
+
* timer keeps it running), but nothing routes to it from the next one, and a
|
|
58
|
+
* spawn that takes its facet name ends it first.
|
|
59
59
|
*/
|
|
60
60
|
export interface FencedWorkRecord {
|
|
61
61
|
pid: number;
|