@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.
Files changed (66) hide show
  1. package/README.md +26 -11
  2. package/dist/bindings.d.ts +1 -0
  3. package/dist/bindings.d.ts.map +1 -1
  4. package/dist/bindings.js +2 -0
  5. package/dist/budgets.d.ts +50 -29
  6. package/dist/budgets.d.ts.map +1 -1
  7. package/dist/budgets.js +88 -40
  8. package/dist/connections.d.ts +1 -1
  9. package/dist/connections.js +1 -1
  10. package/dist/do-calls.d.ts +91 -9
  11. package/dist/do-calls.d.ts.map +1 -1
  12. package/dist/do-calls.js +161 -22
  13. package/dist/facet-pool.d.ts +3 -0
  14. package/dist/facet-pool.d.ts.map +1 -1
  15. package/dist/facet-pool.js +3 -0
  16. package/dist/fanout.d.ts +40 -32
  17. package/dist/fanout.d.ts.map +1 -1
  18. package/dist/fanout.js +50 -53
  19. package/dist/fenced-work.d.ts +3 -3
  20. package/dist/host-wasm.d.ts +29 -0
  21. package/dist/host-wasm.d.ts.map +1 -0
  22. package/dist/host-wasm.js +31 -0
  23. package/dist/image-store.d.ts +1 -1
  24. package/dist/image-store.d.ts.map +1 -1
  25. package/dist/image-store.js +34 -2
  26. package/dist/inner-do-registry.d.ts +9 -0
  27. package/dist/inner-do-registry.d.ts.map +1 -1
  28. package/dist/inner-do-registry.js +35 -0
  29. package/dist/isolate-pool.d.ts +32 -24
  30. package/dist/isolate-pool.d.ts.map +1 -1
  31. package/dist/isolate-pool.js +78 -54
  32. package/dist/process-fabric.d.ts +42 -23
  33. package/dist/process-fabric.d.ts.map +1 -1
  34. package/dist/process-fabric.js +62 -6
  35. package/dist/process-host.d.ts +3 -1
  36. package/dist/process-host.d.ts.map +1 -1
  37. package/dist/process-host.js +13 -14
  38. package/dist/supervisor-props.d.ts +47 -0
  39. package/dist/supervisor-props.d.ts.map +1 -0
  40. package/dist/supervisor-props.js +37 -0
  41. package/dist/timers.d.ts +12 -0
  42. package/dist/timers.d.ts.map +1 -1
  43. package/dist/timers.js +44 -9
  44. package/dist/vendor/types.d.ts +11 -5
  45. package/dist/vendor/types.d.ts.map +1 -1
  46. package/dist/workerd-facet-host.d.ts +4 -17
  47. package/dist/workerd-facet-host.d.ts.map +1 -1
  48. package/dist/workerd-facet-host.js +120 -54
  49. package/package.json +6 -6
  50. package/src/bindings.ts +2 -0
  51. package/src/budgets.ts +96 -49
  52. package/src/connections.ts +1 -1
  53. package/src/do-calls.ts +216 -25
  54. package/src/facet-pool.ts +5 -0
  55. package/src/fanout.ts +64 -56
  56. package/src/fenced-work.ts +3 -3
  57. package/src/host-wasm.ts +41 -0
  58. package/src/image-store.ts +30 -3
  59. package/src/inner-do-registry.ts +31 -0
  60. package/src/isolate-pool.ts +108 -74
  61. package/src/process-fabric.ts +88 -30
  62. package/src/process-host.ts +16 -14
  63. package/src/supervisor-props.ts +56 -0
  64. package/src/timers.ts +45 -9
  65. package/src/vendor/types.ts +11 -5
  66. 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
- Set `compatibility_flags: ["nodejs_compat"]` in your Worker. The timer
26
- dispatcher needs `AsyncLocalStorage`, which workerd ships only under that
27
- flag. Without it the module fails to load at deploy time.
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. One DO method can drive at most 4 concurrent
228
- loader fetches, so batches under 5 run in the coordinator and larger ones
229
- shard across up to 32 sibling objects, 4 at a time.
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
- Each keyed `loader.get(id)` permanently holds one of roughly 5–6
232
- dynamic-worker slots. `loaderLedgerStats(ctx)` reports what you have
233
- consumed, and a cap refusal names the IDs holding slots.
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
- | ~5–6 concurrent dynamic workers per DO; at most 4 concurrent Loader fetches per DO method; loader-cache entries are never released | `IN_DO_THRESHOLD` = 5 sits under the fetch cap; every `loader.get(id)` permanently consumes a slot — counted per DO by the loader ledger, and a cap refusal names the ids holding them |
374
- | `ctx.facets.clone` is same-object only, absent from `@cloudflare/workers-types` and the pinned workerd, present 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 |
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
 
@@ -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>;
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
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 Worker Loader's two caps, the facet-ID lifetime budget, and 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
- * Measured on production workerd: a Durable Object admits ~5–6 concurrent
7
- * dynamic workers before the platform refuses with "Too many concurrent
8
- * dynamic workers", one DO method can drive at most 4 concurrent Loader
9
- * fetches, and loader-cache entries are never released — every DISTINCT
10
- * `loader.get(id)` permanently consumes one of the dynamic-worker slots for
11
- * the object's lifetime. Nimbus stays under the caps by construction
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
- * Measurement only: no admission control. The caps are the platform's, they
19
- * are approximate ("~5–6"), and a gate on an approximate number would refuse
20
- * work the platform would have run.
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
- * caps are per Durable Object, and dynamic workers die with the isolate that
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
- * Count one call into a dynamic worker as a live Loader fetch; the returned
31
- * function ends it (idempotently), from the caller's own `finally`.
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
- idsEverGotten: string[];
48
- liveFetches: number;
49
- peakLiveFetches: number;
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 "Too many concurrent dynamic workers"
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 cap was hit — which ids hold the slots, and that a
55
- * keyed id can never give one back, is what the operator needs to know to
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
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../src/budgets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAwBH,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,IAAI,CAUxD;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG;IAC9C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAOA;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAW7E;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;AAuBD;;;;;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"}
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 Worker Loader's two caps, the facet-ID lifetime budget, and 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
- * Measured on production workerd: a Durable Object admits ~5–6 concurrent
7
- * dynamic workers before the platform refuses with "Too many concurrent
8
- * dynamic workers", one DO method can drive at most 4 concurrent Loader
9
- * fetches, and loader-cache entries are never released — every DISTINCT
10
- * `loader.get(id)` permanently consumes one of the dynamic-worker slots for
11
- * the object's lifetime. Nimbus stays under the caps by construction
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
- * Measurement only: no admission control. The caps are the platform's, they
19
- * are approximate ("~5–6"), and a gate on an approximate number would refuse
20
- * work the platform would have run.
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
- * caps are per Durable Object, and dynamic workers die with the isolate that
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 = { ids: new Set(), liveFetches: 0, peakLiveFetches: 0 };
38
+ entry = { inFlight: new Map(), claimed: 0, peak: 0 };
33
39
  ledgers.set(ctx, entry);
34
40
  }
35
41
  return entry;
36
42
  }
37
- /** Record a keyed `loader.get(id)` — a permanent slot if the id is new. */
38
- export function recordLoaderId(ctx, id) {
39
- ledger(ctx).ids.add(id);
43
+ function inUse(entry) {
44
+ return entry.inFlight.size + entry.claimed;
40
45
  }
41
46
  /**
42
- * Count one call into a dynamic worker as a live Loader fetch; the returned
43
- * function ends it (idempotently), from the caller's own `finally`.
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.liveFetches++;
59
- entry.peakLiveFetches = Math.max(entry.peakLiveFetches, entry.liveFetches);
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.liveFetches--;
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
- idsEverGotten: [...entry.ids],
73
- liveFetches: entry.liveFetches,
74
- peakLiveFetches: entry.peakLiveFetches,
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 "Too many concurrent dynamic workers"
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 cap was hit — which ids hold the slots, and that a
81
- * keyed id can never give one back, is what the operator needs to know to
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 has ${entry.ids.size} loader id(s) permanently `
90
- + `holding dynamic-worker slots (a loader.get id is never released): `
91
- + `${[...entry.ids].join(', ') || '(none recorded)'}; live Loader fetches ${entry.liveFetches}, `
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 }`). With an
140
- * encoder, text is measured exactly; without one, by code-unit length.
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;
@@ -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
@@ -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
@@ -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
- * Called once per retry, before its backoff delay, with the failure the
41
- * retry is answering. The consumer's logging seam: Proteus's hand-rolled
42
- * predecessor logged every retry so a flaky object is visible in Workers
43
- * Logs rather than silently absorbed, and `operation` names it there.
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
- /** The 1-based attempt that failed; the retry about to run is attempt+1. */
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, or a converge-to-a-value write. Transient failures retry on a fresh
83
- * stub with full-jitter backoff; overloaded and permanent failures surface
84
- * unchanged, as does the last error at exhaustion.
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
@@ -1 +1 @@
1
- {"version":3,"file":"do-calls.d.ts","sourceRoot":"","sources":["../src/do-calls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;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;;;;;OAKG;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,4EAA4E;IAC5E,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;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,EACnC,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,CAoBZ;AAED;;;;;GAKG;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"}
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"}