@nimbus-sh/fabric 0.7.1 → 0.9.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 +26 -11
- package/dist/bindings.d.ts +1 -0
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +2 -0
- package/dist/budgets.d.ts +50 -29
- package/dist/budgets.d.ts.map +1 -1
- package/dist/budgets.js +88 -40
- package/dist/connections.d.ts +1 -1
- package/dist/connections.js +1 -1
- package/dist/do-calls.d.ts +91 -9
- package/dist/do-calls.d.ts.map +1 -1
- package/dist/do-calls.js +161 -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 +40 -32
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +50 -53
- package/dist/fenced-work.d.ts +3 -3
- package/dist/host-wasm.d.ts +29 -0
- package/dist/host-wasm.d.ts.map +1 -0
- package/dist/host-wasm.js +31 -0
- package/dist/image-store.d.ts +1 -1
- package/dist/image-store.d.ts.map +1 -1
- package/dist/image-store.js +34 -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 +32 -24
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +78 -54
- package/dist/process-fabric.d.ts +42 -23
- package/dist/process-fabric.d.ts.map +1 -1
- package/dist/process-fabric.js +62 -6
- package/dist/process-host.d.ts +3 -1
- package/dist/process-host.d.ts.map +1 -1
- package/dist/process-host.js +13 -14
- 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/timers.d.ts +12 -0
- package/dist/timers.d.ts.map +1 -1
- package/dist/timers.js +44 -9
- package/dist/vendor/types.d.ts +11 -5
- package/dist/vendor/types.d.ts.map +1 -1
- 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 +120 -54
- package/package.json +6 -6
- package/src/bindings.ts +2 -0
- package/src/budgets.ts +96 -49
- package/src/connections.ts +1 -1
- package/src/do-calls.ts +216 -25
- package/src/facet-pool.ts +5 -0
- package/src/fanout.ts +64 -56
- package/src/fenced-work.ts +3 -3
- package/src/host-wasm.ts +41 -0
- package/src/image-store.ts +30 -3
- package/src/inner-do-registry.ts +31 -0
- package/src/isolate-pool.ts +108 -74
- package/src/process-fabric.ts +88 -30
- package/src/process-host.ts +16 -14
- package/src/supervisor-props.ts +56 -0
- package/src/timers.ts +45 -9
- package/src/vendor/types.ts +11 -5
- package/src/workerd-facet-host.ts +123 -58
package/README.md
CHANGED
|
@@ -22,9 +22,19 @@ npm install @nimbus-sh/fabric
|
|
|
22
22
|
|
|
23
23
|
## Requirements
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
dispatcher needs `AsyncLocalStorage`, which
|
|
27
|
-
|
|
25
|
+
Use a compatibility date of 2026-08-04 or later, or list `nodejs_compat` in
|
|
26
|
+
`compatibility_flags`. The timer dispatcher needs `AsyncLocalStorage`, which
|
|
27
|
+
workerd ships only under `nodejs_compat`, on by date from 2026-08-04. Without
|
|
28
|
+
it the module fails to load at deploy time.
|
|
29
|
+
|
|
30
|
+
`composeFabric` also needs `enhanced_error_serialization`: a compatibility
|
|
31
|
+
date of 2026-04-21 or later, or the flag in `compatibility_flags` on an older
|
|
32
|
+
date. A program's filesystem errors reach it across workerd RPC, and only
|
|
33
|
+
that flag carries their `code`. On workerd without it `composeFabric` throws,
|
|
34
|
+
naming both fixes, rather than every program seeing `EIO`. It throws where
|
|
35
|
+
it is called. At module scope the Worker fails at startup. A library host
|
|
36
|
+
that composes through `NimbusWorkspace.create({ fabric })` deploys and
|
|
37
|
+
starts, and its first create throws.
|
|
28
38
|
|
|
29
39
|
Import the root inside a Worker. Outside workerd, import subpaths such as
|
|
30
40
|
`@nimbus-sh/fabric/timers.js`, which are typed against plain objects and run
|
|
@@ -224,13 +234,18 @@ Warm isolates are scoped to one session. A pool may opt into
|
|
|
224
234
|
`cacheScope: 'global'` only if it takes no supervisor binding and keeps no
|
|
225
235
|
user state.
|
|
226
236
|
|
|
227
|
-
`Fanout` handles wider batches.
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
`Fanout` handles wider batches. A Durable Object may have 10 distinct Dynamic
|
|
238
|
+
Workers with in-flight requests at once (`DO_DYNAMIC_WORKER_LIMIT`), shared
|
|
239
|
+
across every concurrent request to it; repeated requests to one Dynamic
|
|
240
|
+
Worker count once. A batch that fits the coordinator's remaining headroom
|
|
241
|
+
runs there, one Dynamic Worker per task; a wider one shards across up to 32
|
|
242
|
+
sibling objects, 4 at a time, each spending its own headroom.
|
|
230
243
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
244
|
+
The loader ledger counts what is in flight per DO: pool and one-shot calls,
|
|
245
|
+
esbuild facet calls, git network ops, and every resident process for as long
|
|
246
|
+
as it lives. `dynamicWorkerHeadroom(ctx)` is what is left,
|
|
247
|
+
`claimDynamicWorkers(ctx, n)` reserves a width, `loaderLedgerStats(ctx)`
|
|
248
|
+
reports it all, and a limit refusal names the workers in flight.
|
|
234
249
|
|
|
235
250
|
## Process fabric
|
|
236
251
|
|
|
@@ -370,8 +385,8 @@ or left to you.
|
|
|
370
385
|
| Request-time `WebAssembly.compile`/`instantiate` CSP-blocked; wasm rides the loader modules map as `{ wasm: ArrayBuffer }`, compiled at module load | RPC of a compiled `Module` refused by structured clone; inlined bytes OOMed the supervisor |
|
|
371
386
|
| Module scope bans I/O; `new Function` succeeds at module scope and throws at request time | code reaches a facet through the module map or not at all |
|
|
372
387
|
| The facet start callback fires at most once | re-running it would re-execute the user's program |
|
|
373
|
-
|
|
|
374
|
-
| `ctx.facets.clone` is same-object only
|
|
388
|
+
| 10 distinct Dynamic Workers with in-flight requests per DO, shared across its concurrent requests; repeated requests to one Dynamic Worker count once ([changelog, 2026-08-28](https://developers.cloudflare.com/changelog/post/2026-08-28-durable-objects-dynamic-workers-limit/)) | `DO_DYNAMIC_WORKER_LIMIT`; `Fanout` sizes in-DO batches to the live headroom, and a refusal names the workers in flight |
|
|
389
|
+
| `ctx.facets.clone` is same-object only; declared by `@cloudflare/workers-types` 5 and present in workerd ≥ 1.20260926.1 and in production | 18–31 ms / 45.7 MB, 34–54 ms / 1 GB; an unresolvable `src` silently EMPTIES the destination and reports success — `cloneStorage` enforces the both-ends validation |
|
|
375
390
|
| A DO dies at ~200 MiB of live wasm linear memory; reserved and written pages die at the same ceiling | lazy growth buys nothing; bound guest memory by rewriting the memory section |
|
|
376
391
|
| A wasm stack suspended (JSPI) in one request cannot resume in another | 3 in-context resumes took 6 ms; the first cross-context one hit a 30 s timeout |
|
|
377
392
|
|
package/dist/bindings.d.ts
CHANGED
|
@@ -113,6 +113,7 @@ declare const NimbusLoadedEntrypointPropsSchema: z.ZodObject<{
|
|
|
113
113
|
hostNamespace: z.ZodString;
|
|
114
114
|
hostDispatchMethod: z.ZodString;
|
|
115
115
|
}, z.core.$strip>>;
|
|
116
|
+
hostIncarnation: z.ZodOptional<z.ZodString>;
|
|
116
117
|
}, z.core.$strip>>;
|
|
117
118
|
stage: z.ZodOptional<z.ZodUnknown>;
|
|
118
119
|
}, z.core.$loose>;
|
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;AAG3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAcpD;;;;GAIG;AACH,UAAU,gBAAgB;IACxB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3C,iBAAiB,CAAC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzD;AAED,4EAA4E;AAC5E,UAAU,YAAY;IACpB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC/C,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAAC;CACzD;AAED;;;;;;GAMG;AACH,UAAU,iBAAiB;IACzB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;CAC/D;AAED;;;;GAIG;AACH,UAAU,mBAAmB;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAyBD,4CAA4C;AAC5C,UAAU,iBAAiB;IACzB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAC9E;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;CAyEjD;AAiGD,QAAA,MAAM,iCAAiC
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
|
package/dist/bindings.js
CHANGED
|
@@ -255,6 +255,8 @@ const NimbusLoadedEntrypointPropsSchema = z.object({
|
|
|
255
255
|
pid: z.number().int().nonnegative(),
|
|
256
256
|
writerId: z.string().uuid(),
|
|
257
257
|
route: HostRouteSchema.optional(),
|
|
258
|
+
/** The host instance's delivery incarnation (ResidentSupervisorProps). */
|
|
259
|
+
hostIncarnation: z.string().uuid().optional(),
|
|
258
260
|
}).optional(),
|
|
259
261
|
/**
|
|
260
262
|
* Staged-artifact spec, for a ONE-SHOT run. The module map — ~23 MB for
|
package/dist/budgets.d.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* budgets.ts — per-DO accounting for the platform budgets the fabric spends:
|
|
3
|
-
* the
|
|
4
|
-
* dynamic-worker module-map ceiling.
|
|
3
|
+
* the Durable Object's Dynamic Worker concurrency limit, the facet-ID
|
|
4
|
+
* lifetime budget, and the dynamic-worker module-map ceiling.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (`IN_DO_THRESHOLD` = 5 in the fanout pool), which until now meant the slots
|
|
13
|
-
* were counted in prose. This ledger counts them at the fabric's loader call
|
|
14
|
-
* sites instead — the loader pool's slots, a resident process's keyed worker,
|
|
15
|
-
* a one-shot's load — so proximity is measurable and a cap failure can name
|
|
16
|
-
* the ids actually holding slots.
|
|
6
|
+
* The Dynamic Worker model is Cloudflare's documented one
|
|
7
|
+
* ({@link DO_DYNAMIC_WORKER_LIMIT}): a Durable Object may have a fixed number
|
|
8
|
+
* of DISTINCT Dynamic Workers with in-flight requests at once, shared across
|
|
9
|
+
* every concurrent request to that object (one I/O context), and any number
|
|
10
|
+
* of in-flight requests to the same Dynamic Worker count as one. Only
|
|
11
|
+
* in-flight requests count: a loader id with nothing in flight holds nothing.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
13
|
+
* The ledger counts, per hosting actor, the distinct workers that are in
|
|
14
|
+
* flight right now, keyed by loader id (a fresh key per unkeyed `load`), plus
|
|
15
|
+
* the width fan-outs have claimed and not yet released. A fan-out spends only
|
|
16
|
+
* the {@link dynamicWorkerHeadroom} that leaves, so work a Durable Object
|
|
17
|
+
* already has in flight — a resident process, the esbuild facet, a git
|
|
18
|
+
* network op, another fan-out — keeps its slots.
|
|
21
19
|
*
|
|
22
20
|
* Keyed weakly off the hosting actor's `ctx`, like the facet slot books: the
|
|
23
|
-
*
|
|
21
|
+
* limit is per Durable Object, and dynamic workers die with the isolate that
|
|
24
22
|
* loaded them, so a ledger that goes away with its host describes nothing
|
|
25
23
|
* that still exists.
|
|
26
24
|
*/
|
|
27
|
-
/** Record a keyed `loader.get(id)` — a permanent slot if the id is new. */
|
|
28
|
-
export declare function recordLoaderId(ctx: object, id: string): void;
|
|
29
25
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* Distinct Dynamic Workers one Durable Object may have with in-flight
|
|
27
|
+
* requests at once, shared across all concurrent requests to that object;
|
|
28
|
+
* multiple in-flight requests to one Dynamic Worker count once.
|
|
29
|
+
* https://developers.cloudflare.com/changelog/post/2026-08-28-durable-objects-dynamic-workers-limit/
|
|
30
|
+
*/
|
|
31
|
+
export declare const DO_DYNAMIC_WORKER_LIMIT = 10;
|
|
32
|
+
/**
|
|
33
|
+
* Hold the Dynamic Worker `workerKey` in flight on this actor's ledger; the
|
|
34
|
+
* returned function ends the hold (idempotently), from the caller's own
|
|
35
|
+
* `finally`. Holds on one key nest: the worker counts once until the last
|
|
36
|
+
* one ends, as the platform counts it.
|
|
32
37
|
*
|
|
33
38
|
* A begin/end pair rather than a wrapper on purpose, and the shape is
|
|
34
39
|
* load-bearing: wrapping the stub call in a ledger-owned async frame
|
|
@@ -41,19 +46,35 @@ export declare function recordLoaderId(ctx: object, id: string): void;
|
|
|
41
46
|
* workers: an RPC stub call must stay a direct property call awaited by the
|
|
42
47
|
* frame that made it, so the ledger only brackets it.
|
|
43
48
|
*/
|
|
44
|
-
export declare function beginLoaderFetch(ctx: object): () => void;
|
|
49
|
+
export declare function beginLoaderFetch(ctx: object, workerKey: string): () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Distinct Dynamic Workers this actor may still put in flight: the limit
|
|
52
|
+
* less what is held and claimed right now. Never negative.
|
|
53
|
+
*/
|
|
54
|
+
export declare function dynamicWorkerHeadroom(ctx: object): number;
|
|
55
|
+
/**
|
|
56
|
+
* Claim `width` distinct Dynamic Workers for one fan-out, or null when the
|
|
57
|
+
* headroom cannot hold it. The claim counts until `release` (idempotent), so
|
|
58
|
+
* a second fan-out sizing itself meanwhile sees it; the claimant's own
|
|
59
|
+
* dispatches are held as well while they run, which only ever over-counts
|
|
60
|
+
* toward sending that second fan-out elsewhere.
|
|
61
|
+
*/
|
|
62
|
+
export declare function claimDynamicWorkers(ctx: object, width: number): {
|
|
63
|
+
release(): void;
|
|
64
|
+
} | null;
|
|
45
65
|
/** Snapshot for the diag surface. Pure read; no I/O. */
|
|
46
66
|
export declare function loaderLedgerStats(ctx: object): {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
limit: number;
|
|
68
|
+
inFlightWorkers: string[];
|
|
69
|
+
claimed: number;
|
|
70
|
+
headroom: number;
|
|
71
|
+
peak: number;
|
|
50
72
|
};
|
|
51
73
|
/**
|
|
52
|
-
* Name the per-DO accounting on a "
|
|
74
|
+
* Name the per-DO accounting on a "Dynamic worker concurrency limit exceeded"
|
|
53
75
|
* failure; hand every other error back untouched. The platform's message
|
|
54
|
-
* says only that the
|
|
55
|
-
*
|
|
56
|
-
* shrink anything.
|
|
76
|
+
* says only that the limit was hit — which workers were in flight, and what
|
|
77
|
+
* fan-outs had claimed, is what the operator needs to know to shrink anything.
|
|
57
78
|
*/
|
|
58
79
|
export declare function withDynamicWorkerCapNamed<E>(ctx: object, error: E): E | Error;
|
|
59
80
|
/**
|
package/dist/budgets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../src/budgets.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../src/budgets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AA0B1C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,IAAI,CAY3E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,GAAG,IAAI,CAa1F;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CASA;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAU7E;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,WAAa,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAoBrF;AA4BD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,sEAAsE;AACtE,eAAO,MAAM,yBAAyB,iCAAiC,CAAC;AAExE,mEAAmE;AACnE,UAAU,sBAAsB;IAC9B,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;KACjD,CAAC;CACH;AAqCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAatF;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,EAAE,sBAAsB,GAAG,MAAM,CAGlE;AAED,4EAA4E;AAC5E,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxF;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,sBAAsB,GAC1B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAK/C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAU9E"}
|
package/dist/budgets.js
CHANGED
|
@@ -1,46 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* budgets.ts — per-DO accounting for the platform budgets the fabric spends:
|
|
3
|
-
* the
|
|
4
|
-
* dynamic-worker module-map ceiling.
|
|
3
|
+
* the Durable Object's Dynamic Worker concurrency limit, the facet-ID
|
|
4
|
+
* lifetime budget, and the dynamic-worker module-map ceiling.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (`IN_DO_THRESHOLD` = 5 in the fanout pool), which until now meant the slots
|
|
13
|
-
* were counted in prose. This ledger counts them at the fabric's loader call
|
|
14
|
-
* sites instead — the loader pool's slots, a resident process's keyed worker,
|
|
15
|
-
* a one-shot's load — so proximity is measurable and a cap failure can name
|
|
16
|
-
* the ids actually holding slots.
|
|
6
|
+
* The Dynamic Worker model is Cloudflare's documented one
|
|
7
|
+
* ({@link DO_DYNAMIC_WORKER_LIMIT}): a Durable Object may have a fixed number
|
|
8
|
+
* of DISTINCT Dynamic Workers with in-flight requests at once, shared across
|
|
9
|
+
* every concurrent request to that object (one I/O context), and any number
|
|
10
|
+
* of in-flight requests to the same Dynamic Worker count as one. Only
|
|
11
|
+
* in-flight requests count: a loader id with nothing in flight holds nothing.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
13
|
+
* The ledger counts, per hosting actor, the distinct workers that are in
|
|
14
|
+
* flight right now, keyed by loader id (a fresh key per unkeyed `load`), plus
|
|
15
|
+
* the width fan-outs have claimed and not yet released. A fan-out spends only
|
|
16
|
+
* the {@link dynamicWorkerHeadroom} that leaves, so work a Durable Object
|
|
17
|
+
* already has in flight — a resident process, the esbuild facet, a git
|
|
18
|
+
* network op, another fan-out — keeps its slots.
|
|
21
19
|
*
|
|
22
20
|
* Keyed weakly off the hosting actor's `ctx`, like the facet slot books: the
|
|
23
|
-
*
|
|
21
|
+
* limit is per Durable Object, and dynamic workers die with the isolate that
|
|
24
22
|
* loaded them, so a ledger that goes away with its host describes nothing
|
|
25
23
|
* that still exists.
|
|
26
24
|
*/
|
|
27
25
|
import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
|
|
26
|
+
import { hostWasmIdentity } from './host-wasm.js';
|
|
27
|
+
/**
|
|
28
|
+
* Distinct Dynamic Workers one Durable Object may have with in-flight
|
|
29
|
+
* requests at once, shared across all concurrent requests to that object;
|
|
30
|
+
* multiple in-flight requests to one Dynamic Worker count once.
|
|
31
|
+
* https://developers.cloudflare.com/changelog/post/2026-08-28-durable-objects-dynamic-workers-limit/
|
|
32
|
+
*/
|
|
33
|
+
export const DO_DYNAMIC_WORKER_LIMIT = 10;
|
|
28
34
|
const ledgers = new WeakMap();
|
|
29
35
|
function ledger(ctx) {
|
|
30
36
|
let entry = ledgers.get(ctx);
|
|
31
37
|
if (!entry) {
|
|
32
|
-
entry = {
|
|
38
|
+
entry = { inFlight: new Map(), claimed: 0, peak: 0 };
|
|
33
39
|
ledgers.set(ctx, entry);
|
|
34
40
|
}
|
|
35
41
|
return entry;
|
|
36
42
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ledger(ctx).ids.add(id);
|
|
43
|
+
function inUse(entry) {
|
|
44
|
+
return entry.inFlight.size + entry.claimed;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
|
-
*
|
|
43
|
-
* function ends
|
|
47
|
+
* Hold the Dynamic Worker `workerKey` in flight on this actor's ledger; the
|
|
48
|
+
* returned function ends the hold (idempotently), from the caller's own
|
|
49
|
+
* `finally`. Holds on one key nest: the worker counts once until the last
|
|
50
|
+
* one ends, as the platform counts it.
|
|
44
51
|
*
|
|
45
52
|
* A begin/end pair rather than a wrapper on purpose, and the shape is
|
|
46
53
|
* load-bearing: wrapping the stub call in a ledger-owned async frame
|
|
@@ -53,43 +60,77 @@ export function recordLoaderId(ctx, id) {
|
|
|
53
60
|
* workers: an RPC stub call must stay a direct property call awaited by the
|
|
54
61
|
* frame that made it, so the ledger only brackets it.
|
|
55
62
|
*/
|
|
56
|
-
export function beginLoaderFetch(ctx) {
|
|
63
|
+
export function beginLoaderFetch(ctx, workerKey) {
|
|
57
64
|
const entry = ledger(ctx);
|
|
58
|
-
entry.
|
|
59
|
-
entry.
|
|
65
|
+
entry.inFlight.set(workerKey, (entry.inFlight.get(workerKey) ?? 0) + 1);
|
|
66
|
+
entry.peak = Math.max(entry.peak, inUse(entry));
|
|
60
67
|
let ended = false;
|
|
61
68
|
return () => {
|
|
62
69
|
if (ended)
|
|
63
70
|
return;
|
|
64
71
|
ended = true;
|
|
65
|
-
entry.
|
|
72
|
+
const open = (entry.inFlight.get(workerKey) ?? 1) - 1;
|
|
73
|
+
if (open > 0)
|
|
74
|
+
entry.inFlight.set(workerKey, open);
|
|
75
|
+
else
|
|
76
|
+
entry.inFlight.delete(workerKey);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Distinct Dynamic Workers this actor may still put in flight: the limit
|
|
81
|
+
* less what is held and claimed right now. Never negative.
|
|
82
|
+
*/
|
|
83
|
+
export function dynamicWorkerHeadroom(ctx) {
|
|
84
|
+
return Math.max(0, DO_DYNAMIC_WORKER_LIMIT - inUse(ledger(ctx)));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Claim `width` distinct Dynamic Workers for one fan-out, or null when the
|
|
88
|
+
* headroom cannot hold it. The claim counts until `release` (idempotent), so
|
|
89
|
+
* a second fan-out sizing itself meanwhile sees it; the claimant's own
|
|
90
|
+
* dispatches are held as well while they run, which only ever over-counts
|
|
91
|
+
* toward sending that second fan-out elsewhere.
|
|
92
|
+
*/
|
|
93
|
+
export function claimDynamicWorkers(ctx, width) {
|
|
94
|
+
const entry = ledger(ctx);
|
|
95
|
+
if (width < 1 || width > DO_DYNAMIC_WORKER_LIMIT - inUse(entry))
|
|
96
|
+
return null;
|
|
97
|
+
entry.claimed += width;
|
|
98
|
+
entry.peak = Math.max(entry.peak, inUse(entry));
|
|
99
|
+
let released = false;
|
|
100
|
+
return {
|
|
101
|
+
release() {
|
|
102
|
+
if (released)
|
|
103
|
+
return;
|
|
104
|
+
released = true;
|
|
105
|
+
entry.claimed -= width;
|
|
106
|
+
},
|
|
66
107
|
};
|
|
67
108
|
}
|
|
68
109
|
/** Snapshot for the diag surface. Pure read; no I/O. */
|
|
69
110
|
export function loaderLedgerStats(ctx) {
|
|
70
111
|
const entry = ledger(ctx);
|
|
71
112
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
113
|
+
limit: DO_DYNAMIC_WORKER_LIMIT,
|
|
114
|
+
inFlightWorkers: [...entry.inFlight.keys()],
|
|
115
|
+
claimed: entry.claimed,
|
|
116
|
+
headroom: dynamicWorkerHeadroom(ctx),
|
|
117
|
+
peak: entry.peak,
|
|
75
118
|
};
|
|
76
119
|
}
|
|
77
120
|
/**
|
|
78
|
-
* Name the per-DO accounting on a "
|
|
121
|
+
* Name the per-DO accounting on a "Dynamic worker concurrency limit exceeded"
|
|
79
122
|
* failure; hand every other error back untouched. The platform's message
|
|
80
|
-
* says only that the
|
|
81
|
-
*
|
|
82
|
-
* shrink anything.
|
|
123
|
+
* says only that the limit was hit — which workers were in flight, and what
|
|
124
|
+
* fan-outs had claimed, is what the operator needs to know to shrink anything.
|
|
83
125
|
*/
|
|
84
126
|
export function withDynamicWorkerCapNamed(ctx, error) {
|
|
85
127
|
if (classifyError(error) !== 'dynamic_worker_cap')
|
|
86
128
|
return error;
|
|
87
129
|
const entry = ledger(ctx);
|
|
88
130
|
const platform = error instanceof Error ? error.message : String(error);
|
|
89
|
-
return new Error(`${platform} — this Durable Object
|
|
90
|
-
+ `
|
|
91
|
-
+
|
|
92
|
-
+ `peak ${entry.peakLiveFetches}`, { cause: error });
|
|
131
|
+
return new Error(`${platform} — this Durable Object had ${entry.inFlight.size} distinct dynamic worker(s) in flight `
|
|
132
|
+
+ `(${[...entry.inFlight.keys()].join(', ') || 'none recorded'}) and ${entry.claimed} claimed by fan-outs, `
|
|
133
|
+
+ `against a limit of ${DO_DYNAMIC_WORKER_LIMIT}; peak ${entry.peak}`, { cause: error });
|
|
93
134
|
}
|
|
94
135
|
// ── Dynamic-worker module-map ceiling ───────────────────────────────────────
|
|
95
136
|
/**
|
|
@@ -136,13 +177,18 @@ export function assertModuleMapWithinCodeLimit(modules) {
|
|
|
136
177
|
}
|
|
137
178
|
/**
|
|
138
179
|
* Bytes one module-map member carries, across the loader's content kinds
|
|
139
|
-
* (plain string, `{ js | cjs | py | text }`, `{ wasm | data }
|
|
140
|
-
* encoder, text is measured exactly; without
|
|
180
|
+
* (plain string, `{ js | cjs | py | text }`, `{ wasm | data }`, a bare
|
|
181
|
+
* WebAssembly.Module). With an encoder, text is measured exactly; without
|
|
182
|
+
* one, by code-unit length. A compiled module counts the wire size its host
|
|
183
|
+
* described (host-wasm.ts); one nobody described counts nothing here and is
|
|
184
|
+
* left to the platform's own refusal, as the text undercount is.
|
|
141
185
|
*/
|
|
142
186
|
function memberBytes(content, encoder) {
|
|
143
187
|
const textBytes = (text) => encoder ? encoder.encode(text).byteLength : text.length;
|
|
144
188
|
if (typeof content === 'string')
|
|
145
189
|
return textBytes(content);
|
|
190
|
+
if (content instanceof WebAssembly.Module)
|
|
191
|
+
return hostWasmIdentity(content)?.bytes ?? 0;
|
|
146
192
|
if (content !== null && typeof content === 'object') {
|
|
147
193
|
for (const value of Object.values(content)) {
|
|
148
194
|
if (typeof value === 'string')
|
|
@@ -151,6 +197,8 @@ function memberBytes(content, encoder) {
|
|
|
151
197
|
return value.byteLength;
|
|
152
198
|
if (ArrayBuffer.isView(value))
|
|
153
199
|
return value.byteLength;
|
|
200
|
+
if (value instanceof WebAssembly.Module)
|
|
201
|
+
return hostWasmIdentity(value)?.bytes ?? 0;
|
|
154
202
|
}
|
|
155
203
|
}
|
|
156
204
|
return 0;
|
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.
|
|
@@ -33,23 +40,74 @@
|
|
|
33
40
|
* namespace stubs, where the thunk shape is production-proven in Proteus.
|
|
34
41
|
*/
|
|
35
42
|
import { type DoCallClass } from '@nimbus-sh/platform/oom-classify.js';
|
|
43
|
+
import { type SpanRecorder } from '@nimbus-sh/platform/tracing.js';
|
|
36
44
|
export interface DoCallRetryPolicy {
|
|
37
45
|
maxAttempts?: number;
|
|
38
46
|
baseDelayMs?: number;
|
|
39
47
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
48
|
+
* No repeat — retry or hedge — starts once this long has passed since the
|
|
49
|
+
* first attempt did; the failure in hand surfaces instead. A mutation made
|
|
50
|
+
* repeatable by an identity its callee dedupes needs it: the callee keeps
|
|
51
|
+
* what answers a repeat for a bounded time, so the caller's repeats must
|
|
52
|
+
* stop well inside it. Unbounded when absent.
|
|
53
|
+
*/
|
|
54
|
+
retryWindowMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Hedge an attempt that has not answered after this long: send the same
|
|
57
|
+
* call again on a fresh stub while the first stays in flight. The caller
|
|
58
|
+
* gets the first success; an answer after it is disposed and dropped. That
|
|
59
|
+
* is only harmless when a second delivery of the call changes nothing — a
|
|
60
|
+
* read — and cheap only when the callee joins a repeat to the call it is
|
|
61
|
+
* already serving. A hedge is an attempt: it counts against `maxAttempts`,
|
|
62
|
+
* and has its own deadline. Never hedged when absent.
|
|
63
|
+
*
|
|
64
|
+
* With attempts overlapping, a failure decides less. A retryable one is
|
|
65
|
+
* retried after its backoff while attempts remain, and `overloaded` stops
|
|
66
|
+
* every further repeat; either way the call keeps waiting on the attempts
|
|
67
|
+
* still in flight, and fails with the last failure only once none is. Any
|
|
68
|
+
* other failure is the callee's answer — the call ran — and is the call's
|
|
69
|
+
* at once.
|
|
70
|
+
*/
|
|
71
|
+
hedgeAfterMs?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Called once per retry, as it starts — after its backoff delay — with the
|
|
74
|
+
* failure the retry is answering. A retry a hedge made unnecessary, or
|
|
75
|
+
* that no attempt is left for, is never announced. The consumer's logging
|
|
76
|
+
* seam: Proteus's hand-rolled predecessor logged every retry so a flaky
|
|
77
|
+
* object is visible in Workers Logs rather than silently absorbed, and
|
|
78
|
+
* `operation` names it there. A callback that throws fails the call with
|
|
79
|
+
* its error.
|
|
44
80
|
*/
|
|
45
81
|
onRetry?(info: DoCallRetryInfo): void;
|
|
82
|
+
/**
|
|
83
|
+
* Where the call's telemetry goes: its span's recorder. Each attempt lost
|
|
84
|
+
* to a transient or overloaded failure is recorded as an exception whose
|
|
85
|
+
* `code` is the failure's class, and once the call has settled it gets
|
|
86
|
+
* `do_call.attempts` (started), `do_call.hedges` (started by a hedge),
|
|
87
|
+
* `do_call.answered_by` (the attempt whose answer the call took, absent
|
|
88
|
+
* when none answered) and `do_call.outcome` ({@link DoCallOutcome}).
|
|
89
|
+
* Nothing recorded can change the call's answer. Records nothing when
|
|
90
|
+
* absent.
|
|
91
|
+
*/
|
|
92
|
+
span?: SpanRecorder;
|
|
46
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* How an `idempotent` call ended: `answered` (an attempt succeeded),
|
|
96
|
+
* `callee_error` (the callee's own failure, which is an answer),
|
|
97
|
+
* `exhausted` (the last transient failure, no repeat left), `overloaded`
|
|
98
|
+
* (shed, nothing repeated after it), or `caller_error` (the resolver or
|
|
99
|
+
* `onRetry` threw).
|
|
100
|
+
*/
|
|
101
|
+
export type DoCallOutcome = 'answered' | 'callee_error' | 'exhausted' | 'overloaded' | 'caller_error';
|
|
47
102
|
/** What one retry is answering: which call, which platform class, which
|
|
48
103
|
* attempt just failed out of how many. */
|
|
49
104
|
export interface DoCallRetryInfo {
|
|
50
105
|
operation: string;
|
|
51
106
|
classification: DoCallClass;
|
|
52
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* The 1-based number of the attempt that failed. Without hedging the retry
|
|
109
|
+
* is attempt+1; with it, other attempts may have started in between.
|
|
110
|
+
*/
|
|
53
111
|
attempt: number;
|
|
54
112
|
maxAttempts: number;
|
|
55
113
|
error: unknown;
|
|
@@ -79,9 +137,18 @@ export declare class DoCallError extends Error {
|
|
|
79
137
|
}
|
|
80
138
|
/**
|
|
81
139
|
* Call another Durable Object with an operation that is safe to repeat: a
|
|
82
|
-
* read,
|
|
83
|
-
*
|
|
84
|
-
*
|
|
140
|
+
* read, a converge-to-a-value write, or a mutation carrying an identity its
|
|
141
|
+
* callee applies at most once (see {@link mutating}). Transient failures
|
|
142
|
+
* retry on a fresh stub with full-jitter backoff; overloaded and permanent
|
|
143
|
+
* failures surface unchanged, as does the last error once no attempt may be
|
|
144
|
+
* repeated — attempts spent, or the policy's retry window closed. With
|
|
145
|
+
* `hedgeAfterMs`, an attempt still unanswered by then is joined by another
|
|
146
|
+
* on a fresh stub, and the first answer is taken: a success, or the
|
|
147
|
+
* callee's own error. A transient or overloaded failure then ends the call
|
|
148
|
+
* only once no attempt is left in flight.
|
|
149
|
+
*
|
|
150
|
+
* A failure of the resolver or of `onRetry` is the caller's own, and fails
|
|
151
|
+
* the call with it at once.
|
|
85
152
|
*/
|
|
86
153
|
export declare function idempotent<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>, policy?: DoCallRetryPolicy): Promise<T>;
|
|
87
154
|
/**
|
|
@@ -89,6 +156,21 @@ export declare function idempotent<S, T>(operation: string, stub: DoStubResolver
|
|
|
89
156
|
* or mints. NEVER retried — a dropped call may already have run. Failure
|
|
90
157
|
* surfaces as a {@link DoCallError} carrying the classification, so the
|
|
91
158
|
* caller can tell a refusal from an indeterminate drop.
|
|
159
|
+
*
|
|
160
|
+
* The rule is about the call as sent, not the operation's kind. A mutation
|
|
161
|
+
* the callee applies at most once per identity the call carries is
|
|
162
|
+
* repeatable by construction: a repeat of one that already ran is answered
|
|
163
|
+
* from the callee's record and applies nothing. Nimbus has two:
|
|
164
|
+
* - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
|
|
165
|
+
* a delivery id plus the callee INSTANCE's incarnation. The record lives
|
|
166
|
+
* in that instance's memory, and any other instance — or a callee that
|
|
167
|
+
* predates delivery — refuses the call permanently rather than apply it
|
|
168
|
+
* without one;
|
|
169
|
+
* - appends: writer, module incarnation and operation sequence, recorded
|
|
170
|
+
* durably until acknowledged.
|
|
171
|
+
* Such a call goes through {@link idempotent}, re-sending the same identity
|
|
172
|
+
* on every attempt, with a `retryWindowMs` inside the callee's retention of
|
|
173
|
+
* that record. Without such an identity, a mutation stays here.
|
|
92
174
|
*/
|
|
93
175
|
export declare function mutating<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>): Promise<T>;
|
|
94
176
|
//# 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;AAE1G,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAS7E,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;IACtC;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,cAAc,GAAG,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;AAEtG;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,CAgIZ;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"}
|