@nimbus-sh/fabric 0.1.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/LICENSE +21 -0
- package/README.md +487 -0
- package/dist/alarms.d.ts +134 -0
- package/dist/alarms.d.ts.map +1 -0
- package/dist/alarms.js +214 -0
- package/dist/bindings.d.ts +316 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +678 -0
- package/dist/ctx-exports.d.ts +47 -0
- package/dist/ctx-exports.d.ts.map +1 -0
- package/dist/ctx-exports.js +54 -0
- package/dist/facet-image-store.d.ts +112 -0
- package/dist/facet-image-store.d.ts.map +1 -0
- package/dist/facet-image-store.js +181 -0
- package/dist/fanout-pool.d.ts +223 -0
- package/dist/fanout-pool.d.ts.map +1 -0
- package/dist/fanout-pool.js +368 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/inner-do-registry.d.ts +41 -0
- package/dist/inner-do-registry.d.ts.map +1 -0
- package/dist/inner-do-registry.js +51 -0
- package/dist/launch-journal.d.ts +170 -0
- package/dist/launch-journal.d.ts.map +1 -0
- package/dist/launch-journal.js +154 -0
- package/dist/launch-pacer.d.ts +173 -0
- package/dist/launch-pacer.d.ts.map +1 -0
- package/dist/launch-pacer.js +193 -0
- package/dist/loader-ledger.d.ts +57 -0
- package/dist/loader-ledger.d.ts.map +1 -0
- package/dist/loader-ledger.js +91 -0
- package/dist/loader-pool.d.ts +315 -0
- package/dist/loader-pool.d.ts.map +1 -0
- package/dist/loader-pool.js +666 -0
- package/dist/process-fabric.d.ts +524 -0
- package/dist/process-fabric.d.ts.map +1 -0
- package/dist/process-fabric.js +388 -0
- package/dist/process-host.d.ts +132 -0
- package/dist/process-host.d.ts.map +1 -0
- package/dist/process-host.js +444 -0
- package/dist/vendor/errors.d.ts +24 -0
- package/dist/vendor/errors.d.ts.map +1 -0
- package/dist/vendor/errors.js +46 -0
- package/dist/vendor/serialize.d.ts +3 -0
- package/dist/vendor/serialize.d.ts.map +1 -0
- package/dist/vendor/serialize.js +25 -0
- package/dist/vendor/types.d.ts +69 -0
- package/dist/vendor/types.d.ts.map +1 -0
- package/dist/vendor/types.js +4 -0
- package/dist/workerd-facet-host.d.ts +207 -0
- package/dist/workerd-facet-host.d.ts.map +1 -0
- package/dist/workerd-facet-host.js +508 -0
- package/dist/ws-hibernation-config.d.ts +73 -0
- package/dist/ws-hibernation-config.d.ts.map +1 -0
- package/dist/ws-hibernation-config.js +93 -0
- package/package.json +62 -0
- package/src/alarms.ts +275 -0
- package/src/bindings.ts +871 -0
- package/src/ctx-exports.ts +77 -0
- package/src/facet-image-store.ts +196 -0
- package/src/fanout-pool.ts +503 -0
- package/src/index.ts +26 -0
- package/src/inner-do-registry.ts +58 -0
- package/src/launch-journal.ts +229 -0
- package/src/launch-pacer.ts +231 -0
- package/src/loader-ledger.ts +112 -0
- package/src/loader-pool.ts +984 -0
- package/src/process-fabric.ts +729 -0
- package/src/process-host.ts +566 -0
- package/src/vendor/errors.ts +56 -0
- package/src/vendor/serialize.ts +37 -0
- package/src/vendor/types.ts +75 -0
- package/src/workerd-facet-host.ts +694 -0
- package/src/ws-hibernation-config.ts +123 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-pacer.ts — spreading a resident launch across Durable Object turns.
|
|
3
|
+
*
|
|
4
|
+
* Building a resident process is the largest single span of computation this
|
|
5
|
+
* session performs: for pi it walks a 17 MB source tree through eight
|
|
6
|
+
* enrichment passes, serializes a 22.9 MB module map, and writes that map into
|
|
7
|
+
* the image store. Done in one turn it occupied the session DO's only thread
|
|
8
|
+
* for 15-35 s, and a session that cannot reach its thread cannot service the
|
|
9
|
+
* terminal WebSocket — the launch turn finished `outcome=ok` and the terminal
|
|
10
|
+
* died anyway, painting "[process terminal closed]" over a process that was
|
|
11
|
+
* still running.
|
|
12
|
+
*
|
|
13
|
+
* A faster launch does not fix that. A launch half the length still blocks the
|
|
14
|
+
* thread for as long as it runs, and the socket is dropped inside that window
|
|
15
|
+
* whether or not the work succeeds. What fixes it is never holding the thread
|
|
16
|
+
* for long in the first place, which means suspending the launch at bounded
|
|
17
|
+
* intervals and resuming it on a fresh turn. Responsiveness stops depending on
|
|
18
|
+
* how long the total work takes.
|
|
19
|
+
*
|
|
20
|
+
* A fresh turn is also a fresh CPU budget. The same launches that dropped the
|
|
21
|
+
* socket were also being killed with `exceededCpu` at 31.8 s and 32.5 s
|
|
22
|
+
* against a 30 s ceiling, and no amount of yielding *within* one invocation
|
|
23
|
+
* moves that: CPU accrues to the invocation, not to the pause. Only genuinely
|
|
24
|
+
* re-entering the object resets it.
|
|
25
|
+
*
|
|
26
|
+
* Progress is measured in bytes rather than milliseconds because workerd's
|
|
27
|
+
* clock does not advance without I/O — a wall-clock guard inside a span of
|
|
28
|
+
* pure computation reads zero however many seconds it burns, which is why the
|
|
29
|
+
* phase costs behind this module had to be recovered from per-turn `cpuTime`
|
|
30
|
+
* rather than measured in place. Bytes are what the work is actually
|
|
31
|
+
* proportional to, and they are exact. The same reasoning is why
|
|
32
|
+
* `git/network-facet.ts` bounds its checkout chunks by entries and decoded
|
|
33
|
+
* bytes and treats its wall guard as coarse.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Bytes of launch work one turn may perform before it must yield.
|
|
37
|
+
*
|
|
38
|
+
* Sized so a chunk stays far below both the CPU ceiling and the span in which
|
|
39
|
+
* a terminal socket is at risk, while keeping the number of turn handoffs —
|
|
40
|
+
* each an alarm round trip — small enough not to dominate a launch. pi's
|
|
41
|
+
* 22.9 MB map crosses this about a dozen times per phase that handles it.
|
|
42
|
+
*/
|
|
43
|
+
export const LAUNCH_CHUNK_MAX_BYTES = 2_000_000;
|
|
44
|
+
/**
|
|
45
|
+
* Accounts launch progress and ends the turn when a chunk's worth has been
|
|
46
|
+
* spent.
|
|
47
|
+
*
|
|
48
|
+
* Callers report the work they are about to do or have just done and await
|
|
49
|
+
* the result; a pacer that is not yielding returns without suspending, so the
|
|
50
|
+
* one-shot exec path — which passes no pacer at all — keeps its exact
|
|
51
|
+
* behaviour and cost. Nothing here decides WHAT the launch does, only where it
|
|
52
|
+
* is allowed to stop.
|
|
53
|
+
*/
|
|
54
|
+
export class LaunchPacer {
|
|
55
|
+
scheduler;
|
|
56
|
+
maxChunkBytes;
|
|
57
|
+
stillWanted;
|
|
58
|
+
/** Turn handoffs this launch has taken. Reported with the launch. */
|
|
59
|
+
chunks = 0;
|
|
60
|
+
/** Total work accounted, for the same report. */
|
|
61
|
+
bytes = 0;
|
|
62
|
+
spent = 0;
|
|
63
|
+
chunkEnded;
|
|
64
|
+
/**
|
|
65
|
+
* @param stillWanted Checked every time the launch resumes. A launch spans
|
|
66
|
+
* many turns, so anything may have happened to what it is building for
|
|
67
|
+
* while it was suspended; throwing from here is how a launch stops instead
|
|
68
|
+
* of spending turn after turn on work nothing will use. Checked at the one
|
|
69
|
+
* place a launch can be interrupted, rather than at whichever phases
|
|
70
|
+
* remembered to ask.
|
|
71
|
+
*/
|
|
72
|
+
constructor(scheduler, maxChunkBytes = LAUNCH_CHUNK_MAX_BYTES, stillWanted) {
|
|
73
|
+
this.scheduler = scheduler;
|
|
74
|
+
this.maxChunkBytes = maxChunkBytes;
|
|
75
|
+
this.stillWanted = stillWanted;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Account `bytes` of completed work, ending the turn if a chunk is full.
|
|
79
|
+
*
|
|
80
|
+
* Safe to call anywhere the launch holds no state that a concurrent turn
|
|
81
|
+
* could invalidate — which is why the image store registers its whole root
|
|
82
|
+
* set before the first call rather than one entry at a time.
|
|
83
|
+
*/
|
|
84
|
+
async spend(bytes) {
|
|
85
|
+
this.bytes += bytes;
|
|
86
|
+
this.spent += bytes;
|
|
87
|
+
if (this.spent < this.maxChunkBytes)
|
|
88
|
+
return;
|
|
89
|
+
this.spent = 0;
|
|
90
|
+
this.chunks++;
|
|
91
|
+
// Release the turn that resumed us before asking for the next one.
|
|
92
|
+
this.chunkEnded?.resolve();
|
|
93
|
+
const ended = withResolvers();
|
|
94
|
+
this.chunkEnded = ended;
|
|
95
|
+
await this.scheduler.nextTurn(ended.promise);
|
|
96
|
+
this.stillWanted?.();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The launch has finished (or failed). Releases the turn still waiting on
|
|
100
|
+
* the chunk it resumed, so a launch that ends mid-chunk does not strand the
|
|
101
|
+
* handler that granted it.
|
|
102
|
+
*/
|
|
103
|
+
settle() {
|
|
104
|
+
this.chunkEnded?.resolve();
|
|
105
|
+
this.chunkEnded = undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function withResolvers() {
|
|
109
|
+
let resolve;
|
|
110
|
+
const promise = new Promise((r) => { resolve = r; });
|
|
111
|
+
return { promise, resolve };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The granting side of {@link LaunchTurnScheduler}: parks suspended launches
|
|
115
|
+
* and resumes every one of them when the host grants a fresh turn.
|
|
116
|
+
*/
|
|
117
|
+
export class LaunchTurnPump {
|
|
118
|
+
host;
|
|
119
|
+
/**
|
|
120
|
+
* Launches suspended between chunks, waiting for a turn of their own.
|
|
121
|
+
*
|
|
122
|
+
* In-memory on purpose: a launch is only meaningful while the process table
|
|
123
|
+
* entry it is building for exists, and both are lost together if the isolate
|
|
124
|
+
* resets. What survives a reset is the journal, which names the launch's
|
|
125
|
+
* INPUTS rather than its position — a resumed queue would be resurrecting
|
|
126
|
+
* half-built work for pids that no longer exist, where re-driving a launch
|
|
127
|
+
* from its inputs is the same idempotent work again.
|
|
128
|
+
*/
|
|
129
|
+
waiters = [];
|
|
130
|
+
constructor(host) {
|
|
131
|
+
this.host = host;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* How a paced launch asks for a fresh turn.
|
|
135
|
+
*
|
|
136
|
+
* The host grants one by calling {@link pump} from a context that is
|
|
137
|
+
* genuinely a new invocation — the session's alarm. Without such a host
|
|
138
|
+
* there is no fresh turn to be had, and the launch continues on this one
|
|
139
|
+
* rather than hanging: that is exactly the single-turn launch this path has
|
|
140
|
+
* always performed, so a harness or a runtime without alarms loses the
|
|
141
|
+
* responsiveness but keeps the behaviour.
|
|
142
|
+
*/
|
|
143
|
+
nextTurn(chunkEnded) {
|
|
144
|
+
return new Promise((resume) => {
|
|
145
|
+
this.waiters.push({ resume, chunkEnded });
|
|
146
|
+
if (this.host.requestTurn) {
|
|
147
|
+
this.host.requestTurn();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setTimeout(() => { void this.pump(); }, 0);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Run one chunk of every launch waiting for a turn.
|
|
155
|
+
*
|
|
156
|
+
* Awaits the chunk each resumed launch then performs, so the invocation that
|
|
157
|
+
* granted the turn is the invocation that pays for the work — rather than
|
|
158
|
+
* releasing it into a handler's microtask drain, where nothing owns it and
|
|
159
|
+
* the runtime may tear the context down mid-chunk.
|
|
160
|
+
*/
|
|
161
|
+
async pump() {
|
|
162
|
+
await this.host.recover?.();
|
|
163
|
+
const waiting = this.waiters;
|
|
164
|
+
if (waiting.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
this.waiters = [];
|
|
167
|
+
for (const waiter of waiting)
|
|
168
|
+
waiter.resume();
|
|
169
|
+
await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
|
|
170
|
+
}
|
|
171
|
+
/** Whether any launch is suspended waiting for a turn. */
|
|
172
|
+
get hasPending() {
|
|
173
|
+
return this.waiters.length > 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Chunk bound for this session, honouring the verification knob.
|
|
178
|
+
*
|
|
179
|
+
* `NIMBUS_LAUNCH_CHUNK_BYTES` forces a small bound so an ordinary launch —
|
|
180
|
+
* not just a pathological one — crosses several turns and exercises every
|
|
181
|
+
* suspension point. Without it the multi-turn path would only ever be
|
|
182
|
+
* reached by the largest programs, which is the same reason
|
|
183
|
+
* `git/commands.ts` carries `NIMBUS_GIT_CHECKOUT_CHUNK_ENTRIES`. Unset in
|
|
184
|
+
* production, where the default applies.
|
|
185
|
+
*/
|
|
186
|
+
export function launchChunkMaxBytes(env) {
|
|
187
|
+
const raw = env
|
|
188
|
+
?.NIMBUS_LAUNCH_CHUNK_BYTES;
|
|
189
|
+
if (!raw)
|
|
190
|
+
return LAUNCH_CHUNK_MAX_BYTES;
|
|
191
|
+
const parsed = Number(raw);
|
|
192
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : LAUNCH_CHUNK_MAX_BYTES;
|
|
193
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loader-ledger.ts — per-DO accounting for the Worker Loader's two caps.
|
|
3
|
+
*
|
|
4
|
+
* Measured on production workerd: a Durable Object admits ~5–6 concurrent
|
|
5
|
+
* dynamic workers before the platform refuses with "Too many concurrent
|
|
6
|
+
* dynamic workers", one DO method can drive at most 4 concurrent Loader
|
|
7
|
+
* fetches, and loader-cache entries are never released — every DISTINCT
|
|
8
|
+
* `loader.get(id)` permanently consumes one of the dynamic-worker slots for
|
|
9
|
+
* the object's lifetime. Nimbus stays under the caps by construction
|
|
10
|
+
* (`IN_DO_THRESHOLD` = 5 in the fanout pool), which until now meant the slots
|
|
11
|
+
* were counted in prose. This ledger counts them at the fabric's loader call
|
|
12
|
+
* sites instead — the loader pool's slots, a resident process's keyed worker,
|
|
13
|
+
* a one-shot's load — so proximity is measurable and a cap failure can name
|
|
14
|
+
* the ids actually holding slots.
|
|
15
|
+
*
|
|
16
|
+
* Measurement only: no admission control. The caps are the platform's, they
|
|
17
|
+
* are approximate ("~5–6"), and a gate on an approximate number would refuse
|
|
18
|
+
* work the platform would have run.
|
|
19
|
+
*
|
|
20
|
+
* Keyed weakly off the hosting actor's `ctx`, like the facet slot books: the
|
|
21
|
+
* caps are per Durable Object, and dynamic workers die with the isolate that
|
|
22
|
+
* loaded them, so a ledger that goes away with its host describes nothing
|
|
23
|
+
* that still exists.
|
|
24
|
+
*/
|
|
25
|
+
/** Record a keyed `loader.get(id)` — a permanent slot if the id is new. */
|
|
26
|
+
export declare function recordLoaderId(ctx: object, id: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Count one call into a dynamic worker as a live Loader fetch; the returned
|
|
29
|
+
* function ends it (idempotently), from the caller's own `finally`.
|
|
30
|
+
*
|
|
31
|
+
* A begin/end pair rather than a wrapper on purpose, and the shape is
|
|
32
|
+
* load-bearing: wrapping the stub call in a ledger-owned async frame
|
|
33
|
+
* (`trackLoaderFetch(ctx, () => entrypoint.execute(...))`) left the hosting
|
|
34
|
+
* Durable Object poisoned after every pooled dispatch — the next fabric
|
|
35
|
+
* activity hung the object or reset the instance outright (pid base jumped,
|
|
36
|
+
* every attached WebSocket dropped with no close frame), measured 7/7 on
|
|
37
|
+
* staging and gone 3/3 with the direct call restored. Same seam-quirk class
|
|
38
|
+
* as pipelined `fetch.call`, which workerd refuses for dynamically-loaded
|
|
39
|
+
* workers: an RPC stub call must stay a direct property call awaited by the
|
|
40
|
+
* frame that made it, so the ledger only brackets it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function beginLoaderFetch(ctx: object): () => void;
|
|
43
|
+
/** Snapshot for the diag surface. Pure read; no I/O. */
|
|
44
|
+
export declare function loaderLedgerStats(ctx: object): {
|
|
45
|
+
idsEverGotten: string[];
|
|
46
|
+
liveFetches: number;
|
|
47
|
+
peakLiveFetches: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Name the per-DO accounting on a "Too many concurrent dynamic workers"
|
|
51
|
+
* failure; hand every other error back untouched. The platform's message
|
|
52
|
+
* says only that the cap was hit — which ids hold the slots, and that a
|
|
53
|
+
* keyed id can never give one back, is what the operator needs to know to
|
|
54
|
+
* shrink anything.
|
|
55
|
+
*/
|
|
56
|
+
export declare function withDynamicWorkerCapNamed<E>(ctx: object, error: E): E | Error;
|
|
57
|
+
//# sourceMappingURL=loader-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader-ledger.d.ts","sourceRoot":"","sources":["../src/loader-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loader-ledger.ts — per-DO accounting for the Worker Loader's two caps.
|
|
3
|
+
*
|
|
4
|
+
* Measured on production workerd: a Durable Object admits ~5–6 concurrent
|
|
5
|
+
* dynamic workers before the platform refuses with "Too many concurrent
|
|
6
|
+
* dynamic workers", one DO method can drive at most 4 concurrent Loader
|
|
7
|
+
* fetches, and loader-cache entries are never released — every DISTINCT
|
|
8
|
+
* `loader.get(id)` permanently consumes one of the dynamic-worker slots for
|
|
9
|
+
* the object's lifetime. Nimbus stays under the caps by construction
|
|
10
|
+
* (`IN_DO_THRESHOLD` = 5 in the fanout pool), which until now meant the slots
|
|
11
|
+
* were counted in prose. This ledger counts them at the fabric's loader call
|
|
12
|
+
* sites instead — the loader pool's slots, a resident process's keyed worker,
|
|
13
|
+
* a one-shot's load — so proximity is measurable and a cap failure can name
|
|
14
|
+
* the ids actually holding slots.
|
|
15
|
+
*
|
|
16
|
+
* Measurement only: no admission control. The caps are the platform's, they
|
|
17
|
+
* are approximate ("~5–6"), and a gate on an approximate number would refuse
|
|
18
|
+
* work the platform would have run.
|
|
19
|
+
*
|
|
20
|
+
* Keyed weakly off the hosting actor's `ctx`, like the facet slot books: the
|
|
21
|
+
* caps are per Durable Object, and dynamic workers die with the isolate that
|
|
22
|
+
* loaded them, so a ledger that goes away with its host describes nothing
|
|
23
|
+
* that still exists.
|
|
24
|
+
*/
|
|
25
|
+
import { classifyError } from '@nimbus-sh/core/observability/oom-classify.js';
|
|
26
|
+
const ledgers = new WeakMap();
|
|
27
|
+
function ledger(ctx) {
|
|
28
|
+
let entry = ledgers.get(ctx);
|
|
29
|
+
if (!entry) {
|
|
30
|
+
entry = { ids: new Set(), liveFetches: 0, peakLiveFetches: 0 };
|
|
31
|
+
ledgers.set(ctx, entry);
|
|
32
|
+
}
|
|
33
|
+
return entry;
|
|
34
|
+
}
|
|
35
|
+
/** Record a keyed `loader.get(id)` — a permanent slot if the id is new. */
|
|
36
|
+
export function recordLoaderId(ctx, id) {
|
|
37
|
+
ledger(ctx).ids.add(id);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Count one call into a dynamic worker as a live Loader fetch; the returned
|
|
41
|
+
* function ends it (idempotently), from the caller's own `finally`.
|
|
42
|
+
*
|
|
43
|
+
* A begin/end pair rather than a wrapper on purpose, and the shape is
|
|
44
|
+
* load-bearing: wrapping the stub call in a ledger-owned async frame
|
|
45
|
+
* (`trackLoaderFetch(ctx, () => entrypoint.execute(...))`) left the hosting
|
|
46
|
+
* Durable Object poisoned after every pooled dispatch — the next fabric
|
|
47
|
+
* activity hung the object or reset the instance outright (pid base jumped,
|
|
48
|
+
* every attached WebSocket dropped with no close frame), measured 7/7 on
|
|
49
|
+
* staging and gone 3/3 with the direct call restored. Same seam-quirk class
|
|
50
|
+
* as pipelined `fetch.call`, which workerd refuses for dynamically-loaded
|
|
51
|
+
* workers: an RPC stub call must stay a direct property call awaited by the
|
|
52
|
+
* frame that made it, so the ledger only brackets it.
|
|
53
|
+
*/
|
|
54
|
+
export function beginLoaderFetch(ctx) {
|
|
55
|
+
const entry = ledger(ctx);
|
|
56
|
+
entry.liveFetches++;
|
|
57
|
+
entry.peakLiveFetches = Math.max(entry.peakLiveFetches, entry.liveFetches);
|
|
58
|
+
let ended = false;
|
|
59
|
+
return () => {
|
|
60
|
+
if (ended)
|
|
61
|
+
return;
|
|
62
|
+
ended = true;
|
|
63
|
+
entry.liveFetches--;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Snapshot for the diag surface. Pure read; no I/O. */
|
|
67
|
+
export function loaderLedgerStats(ctx) {
|
|
68
|
+
const entry = ledger(ctx);
|
|
69
|
+
return {
|
|
70
|
+
idsEverGotten: [...entry.ids],
|
|
71
|
+
liveFetches: entry.liveFetches,
|
|
72
|
+
peakLiveFetches: entry.peakLiveFetches,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Name the per-DO accounting on a "Too many concurrent dynamic workers"
|
|
77
|
+
* failure; hand every other error back untouched. The platform's message
|
|
78
|
+
* says only that the cap was hit — which ids hold the slots, and that a
|
|
79
|
+
* keyed id can never give one back, is what the operator needs to know to
|
|
80
|
+
* shrink anything.
|
|
81
|
+
*/
|
|
82
|
+
export function withDynamicWorkerCapNamed(ctx, error) {
|
|
83
|
+
if (classifyError(error) !== 'dynamic_worker_cap')
|
|
84
|
+
return error;
|
|
85
|
+
const entry = ledger(ctx);
|
|
86
|
+
const platform = error instanceof Error ? error.message : String(error);
|
|
87
|
+
return new Error(`${platform} — this Durable Object has ${entry.ids.size} loader id(s) permanently `
|
|
88
|
+
+ `holding dynamic-worker slots (a loader.get id is never released): `
|
|
89
|
+
+ `${[...entry.ids].join(', ') || '(none recorded)'}; live Loader fetches ${entry.liveFetches}, `
|
|
90
|
+
+ `peak ${entry.peakLiveFetches}`, { cause: error });
|
|
91
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loader-pool.ts — Nimbus loader-isolate pool based on cloudflare-parallel.
|
|
3
|
+
*
|
|
4
|
+
* Adds Nimbus-specific behavior to the upstream pool design:
|
|
5
|
+
* 1. **Stable-slot isolate reuse**. Upstream's #counter++ gives every
|
|
6
|
+
* dispatch a fresh isolate — fine for one-off AI calls, terrible for
|
|
7
|
+
* running 67 npm tarball extractions (cold-start dominates). We pin
|
|
8
|
+
* each job to `slot = cursor % concurrency` and use stable loader
|
|
9
|
+
* IDs `nfp:${fnHash}:slot-${i}:g${generation}`, so a pool of
|
|
10
|
+
* concurrency=4 keeps at most 4 warm isolates rather than N fresh ones.
|
|
11
|
+
* 2. **Nimbus defaults**: compatibilityDate = CF_COMPAT_DATE (matches
|
|
12
|
+
* the supervisor worker), compatibilityFlags = ['nodejs_compat'],
|
|
13
|
+
* globalOutbound = undefined (inherit parent network so the facet can
|
|
14
|
+
* reach https://registry.npmjs.org without a proxy binding).
|
|
15
|
+
* 3. **Supervisor autoinjection**. The pool grabs the embedder's
|
|
16
|
+
* registered supervisor entrypoint stub (see `supervisorEntrypoint` in
|
|
17
|
+
* ctx-exports.ts) and forwards it as `env.SUPERVISOR` to every facet,
|
|
18
|
+
* same pattern as git-network-facet.ts. Callers can add more bindings
|
|
19
|
+
* via `extraBindings`.
|
|
20
|
+
* 4. **Fail-loud defaults**: timeout 60s, retries 0, onError 'throw'.
|
|
21
|
+
* Caller opts in to leniency.
|
|
22
|
+
*
|
|
23
|
+
* The vendored directory contains only the upstream serialization, error,
|
|
24
|
+
* and binding types used by this implementation.
|
|
25
|
+
*/
|
|
26
|
+
import type { FacetBindings } from '@nimbus-sh/core/runtime/facet-host.js';
|
|
27
|
+
import type { WorkerLoader } from './vendor/types.js';
|
|
28
|
+
/**
|
|
29
|
+
* A function dispatched into a facet isolate, with the bindings that facet was
|
|
30
|
+
* minted with as its second argument.
|
|
31
|
+
*
|
|
32
|
+
* Declared through a method so the bindings parameter compares BIVARIANTLY: a
|
|
33
|
+
* task body annotates the exact surface it calls (`env.SUPERVISOR` is the
|
|
34
|
+
* embedder's RPC class, which the fabric cannot name), and accepting that
|
|
35
|
+
* narrowing is the whole point of handing the bindings over.
|
|
36
|
+
*/
|
|
37
|
+
export type FacetTaskFn<A, R> = {
|
|
38
|
+
task(args: A, env: FacetBindings): R | Promise<R>;
|
|
39
|
+
}['task'];
|
|
40
|
+
/** The one binding a pool needs off whichever env its host hands it. */
|
|
41
|
+
export interface LoaderPoolEnv {
|
|
42
|
+
LOADER?: WorkerLoader;
|
|
43
|
+
}
|
|
44
|
+
/** Options handed to LoaderPool's constructor. */
|
|
45
|
+
export interface LoaderPoolOptions {
|
|
46
|
+
/** Maximum concurrent in-flight facets. Default 4. */
|
|
47
|
+
concurrency?: number;
|
|
48
|
+
/** Per-task timeout in ms. Default 60_000. */
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Per-task retry attempts AFTER the initial failure. Default 0.
|
|
52
|
+
* Set to a small number only if transient RPC errors are common.
|
|
53
|
+
*/
|
|
54
|
+
retries?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Additional bindings forwarded to each facet. These merge on top of the
|
|
57
|
+
* default `{ SUPERVISOR: supervisorRpc({ doId, pid:0 }) }`. Use this to
|
|
58
|
+
* give facets access to KV, R2, AI, or additional supervisor-level APIs.
|
|
59
|
+
*/
|
|
60
|
+
extraBindings?: Record<string, unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Optional tag used in loader IDs for debugging (e.g. "npm-install").
|
|
63
|
+
* Does NOT affect isolate identity — same fn + same tag = same slot.
|
|
64
|
+
*/
|
|
65
|
+
tag?: string;
|
|
66
|
+
/**
|
|
67
|
+
* If true, omit the default SUPERVISOR binding. Use this for pools
|
|
68
|
+
* that don't need DO callbacks (e.g. a pure CPU compute pool).
|
|
69
|
+
*/
|
|
70
|
+
omitSupervisor?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Loader cache scope. Defaults to `session`, which bakes the owning DO id
|
|
73
|
+
* into the loader key so stateful facets cannot leak bindings or globals
|
|
74
|
+
* across sessions. Use `global` only for stateless compute modules that do
|
|
75
|
+
* not receive a Supervisor binding and do not retain user state.
|
|
76
|
+
*/
|
|
77
|
+
cacheScope?: 'session' | 'global';
|
|
78
|
+
/**
|
|
79
|
+
* Override the `doId` baked into the auto-injected SUPERVISOR binding.
|
|
80
|
+
* Default: `ctx.id.toString()` (the DO that constructs the pool).
|
|
81
|
+
*
|
|
82
|
+
* Used by FanoutPool's peer-DO branch (peer-DO fanout): peer DOs
|
|
83
|
+
* construct their per-task LoaderPool from inside
|
|
84
|
+
* `_rpcFanoutExecute`, where `ctx` is the PEER DO's ctx. Without this
|
|
85
|
+
* override the peer's auto-injected SUPERVISOR routes back to the
|
|
86
|
+
* peer DO itself — so writes (e.g. install-batch-facet's
|
|
87
|
+
* writeBatchStream) land in the peer's VFS instead of the
|
|
88
|
+
* COORDINATOR's. The user's terminal session is on the coordinator;
|
|
89
|
+
* writes-to-peer are invisible. See INSTALL-HONESTY-retro.md.
|
|
90
|
+
*
|
|
91
|
+
* When set, the auto-injected supervisor binding uses this string as the
|
|
92
|
+
* `props.doId`, routing all SUPERVISOR.* calls back to the
|
|
93
|
+
* coordinator. Effective only when `omitSupervisor !== true`.
|
|
94
|
+
*/
|
|
95
|
+
supervisorDoIdOverride?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Process pid baked into the auto-injected SUPERVISOR binding's props.
|
|
98
|
+
* The supervisor derives the write credential from this pid
|
|
99
|
+
* (`SupervisorRPC._pid()` → `processes.cred(pid)`), so any facet that
|
|
100
|
+
* calls a filesystem RPC (`writeBatchStream`, `writeFile`, …) must be
|
|
101
|
+
* dispatched with the invoking process's real pid — otherwise the RPC
|
|
102
|
+
* throws "missing or invalid process pid in props". npm install threads
|
|
103
|
+
* the shell command's `ctx.pid` here so package files land as the user.
|
|
104
|
+
* Left 0 (default) for pools whose facets touch only cache/registry RPCs
|
|
105
|
+
* (npm resolve, pre-bundle), which never call `_pid()`.
|
|
106
|
+
*/
|
|
107
|
+
supervisorPid?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Raw JavaScript source prepended to every generated worker module.
|
|
110
|
+
* Lets callers inject bundled helpers, such as a tar parser. The user
|
|
111
|
+
* function can reference top-level names declared in the preamble as if
|
|
112
|
+
* they were in lexical scope.
|
|
113
|
+
*
|
|
114
|
+
* Example: `preamble: 'export const parse = ...; const helper = ...;'`
|
|
115
|
+
* — the preamble runs at module-load time; any side effects happen
|
|
116
|
+
* inside the facet isolate.
|
|
117
|
+
*
|
|
118
|
+
* Preamble text is bytes-stable for a given pool — it's part of the
|
|
119
|
+
* loader-cache key (fnHash), so changing the preamble invalidates all
|
|
120
|
+
* warm slots.
|
|
121
|
+
*/
|
|
122
|
+
preamble?: string;
|
|
123
|
+
/**
|
|
124
|
+
* WebAssembly modules to ship into the facet via the LOADER's
|
|
125
|
+
* `modules` map. Map keys are module specifier paths (e.g.
|
|
126
|
+
* `'esbuild.wasm'`); values are the raw bytes.
|
|
127
|
+
*
|
|
128
|
+
* Workerd registers each entry as `{ wasm: ArrayBuffer }` in the
|
|
129
|
+
* worker's modules map. The pool prepends a static
|
|
130
|
+
* `import __NIMBUS_WASM_<id> from './<key>';` to the generated
|
|
131
|
+
* worker.js so workerd compiles each at module-load (startup phase,
|
|
132
|
+
* where wasm code generation is permitted). The compiled Modules
|
|
133
|
+
* are exposed via `globalThis.__NIMBUS_WASM[<key>]` for the user
|
|
134
|
+
* function to read at request time.
|
|
135
|
+
*
|
|
136
|
+
* Why this works when other paths don't:
|
|
137
|
+
* - request-time `WebAssembly.compile()` — disallowed by workerd
|
|
138
|
+
* in this deploy.
|
|
139
|
+
* - request-time RPC of a pre-compiled Module — workerd
|
|
140
|
+
* structured-clone refuses ("Unable to deserialize cloned data").
|
|
141
|
+
* - inlining bytes in the preamble — 16 MiB string per dispatch
|
|
142
|
+
* OOMs the supervisor at module-source allocation time.
|
|
143
|
+
* - LOADER modules-map (this) — bytes ride INSIDE the worker code
|
|
144
|
+
* blob; workerd compiles wasm during its own startup pipeline,
|
|
145
|
+
* never crossing structured-clone, never executing JS eval.
|
|
146
|
+
*
|
|
147
|
+
* The bytes ARE part of the loader-cache key (workerd hashes the
|
|
148
|
+
* whole WorkerCode), so changing the wasm bytes invalidates warm
|
|
149
|
+
* slots — desirable when the bundled wasm version changes.
|
|
150
|
+
*/
|
|
151
|
+
wasmModules?: Record<string, ArrayBuffer>;
|
|
152
|
+
}
|
|
153
|
+
/** Per-call override (merged with pool defaults). */
|
|
154
|
+
export interface LoaderCallOptions {
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
retries?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Per-call WebAssembly modules. Merged with the pool's
|
|
159
|
+
* constructor-time `wasmModules` at dispatch time and shipped via
|
|
160
|
+
* the LOADER's modules map (same `{ wasm: ArrayBuffer }` shape).
|
|
161
|
+
*
|
|
162
|
+
* Shipping path validated empirically against prod (see
|
|
163
|
+
* `WebAssembly.instantiate(bytes)` is blocked at request-time but
|
|
164
|
+
* the LOADER-modules path compiles bytes during the inner
|
|
165
|
+
* worker's module-load phase, where wasm code generation IS
|
|
166
|
+
* permitted. The bytes ride INSIDE the worker code blob; workerd
|
|
167
|
+
* never crosses structured-clone, never executes user-eval.
|
|
168
|
+
*
|
|
169
|
+
* Cache key impact: per-call bytes are fingerprinted (length +
|
|
170
|
+
* first/last byte per module) and folded into the loader cache
|
|
171
|
+
* key. Identical bytes on the same slot → warm reuse; different
|
|
172
|
+
* bytes → fresh isolate. The pool's existing `wasmHash` field
|
|
173
|
+
* captures CONSTRUCTOR-time bytes only; per-call bytes get an
|
|
174
|
+
* independent fingerprint mixed into the slot id at dispatch.
|
|
175
|
+
*
|
|
176
|
+
* Naming collision rule: a per-call key MUST NOT collide with a
|
|
177
|
+
* constructor-time key (after identifier sanitisation). The
|
|
178
|
+
* dispatch path throws BindingError if it does — silently
|
|
179
|
+
* shadowing the constructor's wasm would break the cache-key
|
|
180
|
+
* invariant downstream callers rely on.
|
|
181
|
+
*
|
|
182
|
+
* Used by the `wasm-runner` shell command in src/runtime/
|
|
183
|
+
* wasm-runner.ts to ship user-supplied .wasm bytes from VFS into
|
|
184
|
+
* a fresh facet isolate per invocation.
|
|
185
|
+
*/
|
|
186
|
+
wasmModules?: Record<string, ArrayBuffer>;
|
|
187
|
+
}
|
|
188
|
+
/** Per-map override. Adds onError strategy for partial failures. */
|
|
189
|
+
export interface LoaderMapOptions extends LoaderCallOptions {
|
|
190
|
+
/** Concurrency override for this call. Defaults to pool's concurrency. */
|
|
191
|
+
concurrency?: number;
|
|
192
|
+
/**
|
|
193
|
+
* What to do when an individual item fails:
|
|
194
|
+
* - 'throw' (default): reject whole map on first failure.
|
|
195
|
+
* - 'null': replace failed items with null in the result array.
|
|
196
|
+
* - 'skip': omit failed items from the result array.
|
|
197
|
+
* We default to 'throw' — install-time failures are not silently ignored.
|
|
198
|
+
*/
|
|
199
|
+
onError?: 'throw' | 'null' | 'skip';
|
|
200
|
+
}
|
|
201
|
+
export interface LoaderWorkerModuleSourceOptions {
|
|
202
|
+
fnSource: string;
|
|
203
|
+
preamble?: string;
|
|
204
|
+
wasmEntries?: ReadonlyArray<{
|
|
205
|
+
name: string;
|
|
206
|
+
id: string;
|
|
207
|
+
}>;
|
|
208
|
+
hasBindings: boolean;
|
|
209
|
+
}
|
|
210
|
+
/** Assemble the exact JavaScript module parsed by a dynamic loader worker. */
|
|
211
|
+
export declare function assembleLoaderWorkerModuleSource(options: LoaderWorkerModuleSourceOptions): string;
|
|
212
|
+
/**
|
|
213
|
+
* Nimbus-scoped parallel dispatch over `env.LOADER`. Tasks are pure
|
|
214
|
+
* functions whose last argument is an `env` object containing the
|
|
215
|
+
* forwarded bindings (default: `{ SUPERVISOR }`).
|
|
216
|
+
*
|
|
217
|
+
* Typical use:
|
|
218
|
+
*
|
|
219
|
+
* const pool = new LoaderPool(env, ctx, {
|
|
220
|
+
* concurrency: 4,
|
|
221
|
+
* tag: 'npm-install',
|
|
222
|
+
* });
|
|
223
|
+
* const results = await pool.map(
|
|
224
|
+
* async (pkg, env) => env.SUPERVISOR.writeBatch(buildPayload(pkg)),
|
|
225
|
+
* toFetch,
|
|
226
|
+
* );
|
|
227
|
+
*/
|
|
228
|
+
export declare class LoaderPool {
|
|
229
|
+
#private;
|
|
230
|
+
private readonly loader;
|
|
231
|
+
/** The hosting actor, as the loader-ledger's per-DO key. */
|
|
232
|
+
private readonly ctx;
|
|
233
|
+
private readonly concurrency;
|
|
234
|
+
private readonly defaultTimeoutMs;
|
|
235
|
+
private readonly defaultRetries;
|
|
236
|
+
private readonly tag;
|
|
237
|
+
private readonly slotGenerations;
|
|
238
|
+
private bindings;
|
|
239
|
+
private readonly preamble;
|
|
240
|
+
private readonly preambleHash;
|
|
241
|
+
/**
|
|
242
|
+
* WASM modules to ship in the LOADER `modules` map. See
|
|
243
|
+
* LoaderPoolOptions.wasmModules for the rationale. Stored in
|
|
244
|
+
* insertion order so the per-import preamble we generate matches
|
|
245
|
+
* across pool dispatches (cache-key stability).
|
|
246
|
+
*/
|
|
247
|
+
private readonly wasmModules;
|
|
248
|
+
/** Hash of (name + byte length + first/last bytes) of every wasm
|
|
249
|
+
* module, folded into the loader cache key so changes invalidate
|
|
250
|
+
* warm slots. Hashing the FULL bytes would be O(20+ MiB) per dispatch
|
|
251
|
+
* and is unnecessary — wasm bytes are pinned at deploy time, the
|
|
252
|
+
* length+endpoints are a strong-enough fingerprint. */
|
|
253
|
+
private readonly wasmHash;
|
|
254
|
+
/**
|
|
255
|
+
* Short prefix of the owning DO's id, baked into the loader.get()
|
|
256
|
+
* cache key so warm isolates are scoped to ONE session. Without this,
|
|
257
|
+
* session A's pool and session B's pool (same `tag` + `fnHash`) share
|
|
258
|
+
* an isolate — which means B's writeBatch RPCs routed through A's
|
|
259
|
+
* env.SUPERVISOR binding (minted with A's doId at construction
|
|
260
|
+
* time). B's install reports success but the writes land in A's VFS,
|
|
261
|
+
* leaving B with only the git-clone seed files (~119 instead of ~1491).
|
|
262
|
+
* 12 chars is enough entropy for DO ids to collide-free per process.
|
|
263
|
+
*/
|
|
264
|
+
private readonly doIdShort;
|
|
265
|
+
constructor(env: unknown, ctx: DurableObjectState, opts?: LoaderPoolOptions);
|
|
266
|
+
/** Effective concurrency used when no per-call override is supplied. */
|
|
267
|
+
get defaultConcurrency(): number;
|
|
268
|
+
/**
|
|
269
|
+
* Run `fn` once with `arg` on a slot isolate. Returns the result or
|
|
270
|
+
* throws TimeoutError / RetryExhaustedError / ExecutionError.
|
|
271
|
+
*/
|
|
272
|
+
submit<T, R>(fn: FacetTaskFn<T, R>, arg: T, opts?: LoaderCallOptions): Promise<Awaited<R>>;
|
|
273
|
+
/**
|
|
274
|
+
* Run `fn` on every item in `items`, at most `concurrency` at a time,
|
|
275
|
+
* pinned to stable slots so warm isolates are reused.
|
|
276
|
+
*
|
|
277
|
+
* Results are returned in input order. Failure handling per `onError`.
|
|
278
|
+
*/
|
|
279
|
+
map<T, R>(fn: FacetTaskFn<T, R>, items: T[], opts?: LoaderMapOptions): Promise<Array<Awaited<R> | null>>;
|
|
280
|
+
/**
|
|
281
|
+
* Same shape as `map`, but accepts a pre-serialized function source
|
|
282
|
+
* string instead of a live function reference. Used by
|
|
283
|
+
* `FanoutPool`'s peer-DO leg, where the function was already
|
|
284
|
+
* serialized on the coordinator side and forwarded over RPC.
|
|
285
|
+
*
|
|
286
|
+
* The fnSource MUST be the output of `serializeFunction(fn)`
|
|
287
|
+
* (typically forwarded directly from a coordinator RPC). Bytes-
|
|
288
|
+
* stable invariants:
|
|
289
|
+
* - `fnHash = hashSource(fnSource)` must be deterministic so
|
|
290
|
+
* warm slots are correctly keyed.
|
|
291
|
+
* - `fnSource` must NOT reference `this` — same rule as
|
|
292
|
+
* `serializeFunction`.
|
|
293
|
+
*
|
|
294
|
+
* No fn-validation runs here (it already ran on the coordinator);
|
|
295
|
+
* the peer trusts the caller to forward a valid serialization.
|
|
296
|
+
*/
|
|
297
|
+
mapSource<T, R>(fnSource: string, items: T[], opts?: LoaderMapOptions): Promise<Array<Awaited<R> | null>>;
|
|
298
|
+
/**
|
|
299
|
+
* Release any RPC stubs held by the pool. Call this once the caller
|
|
300
|
+
* is done with the pool (post-`map`/`submit`) so the underlying
|
|
301
|
+
* stubs don't linger in workerd's deferred-destruction queue.
|
|
302
|
+
*
|
|
303
|
+
* Primary target: the SUPERVISOR binding stub we minted at
|
|
304
|
+
* construction time (via the registered supervisor entrypoint). It's
|
|
305
|
+
* a cross-isolate RPC stub — without explicit disposal it stays
|
|
306
|
+
* referenced until the parent isolate's event-handler context
|
|
307
|
+
* finishes, which during npm install means "until the whole install
|
|
308
|
+
* completes" — long enough to accumulate alongside other leaked
|
|
309
|
+
* stubs and trip the QueueState::ACTIVE fatal.
|
|
310
|
+
*
|
|
311
|
+
* Safe to call more than once; idempotent.
|
|
312
|
+
*/
|
|
313
|
+
dispose(): void;
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=loader-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader-pool.d.ts","sourceRoot":"","sources":["../src/loader-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAgBH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACnD,CAAC,MAAM,CAAC,CAAC;AAEV,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C;AAED,oEAAoE;AACpE,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CACrC;AAkCD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,8EAA8E;AAC9E,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,+BAA+B,GACvC,MAAM,CAyDR;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,OAAO,CAAC,QAAQ,CAAsC;IAEtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAOzB;IACH;;;;4DAIwD;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGjC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,kBAAkB,EACvB,IAAI,CAAC,EAAE,iBAAiB;IAmG1B,wEAAwE;IACxE,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IA8VD;;;OAGG;IACG,MAAM,CAAC,CAAC,EAAE,CAAC,EACf,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,GAAG,EAAE,CAAC,EACN,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAatB;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,CAAC,EACZ,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAOpC;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,CAAC,EAAE,CAAC,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IA+DpC;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,IAAI;CAQhB"}
|