@nimbus-sh/fabric 0.9.0 → 0.10.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 +72 -0
- package/dist/bindings.d.ts +31 -33
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +108 -97
- package/dist/budgets.d.ts +61 -9
- package/dist/budgets.d.ts.map +1 -1
- package/dist/budgets.js +205 -31
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +4 -3
- package/dist/fenced-work.d.ts +3 -3
- package/dist/fenced-work.js +3 -3
- package/dist/inner-do-env.d.ts +83 -0
- package/dist/inner-do-env.d.ts.map +1 -0
- package/dist/inner-do-env.js +181 -0
- package/dist/isolate-pool.d.ts +9 -0
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +55 -27
- package/dist/workerd-facet-host.d.ts.map +1 -1
- package/dist/workerd-facet-host.js +5 -0
- package/package.json +2 -2
- package/src/bindings.ts +121 -98
- package/src/budgets.ts +244 -33
- package/src/fanout.ts +5 -3
- package/src/fenced-work.ts +3 -3
- package/src/inner-do-env.ts +213 -0
- package/src/isolate-pool.ts +65 -27
- package/src/workerd-facet-host.ts +4 -0
package/dist/budgets.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* the width fan-outs have claimed and not yet released. A fan-out spends only
|
|
16
16
|
* the {@link dynamicWorkerHeadroom} that leaves, so work a Durable Object
|
|
17
17
|
* already has in flight — a resident process, the esbuild facet, a git
|
|
18
|
-
* network op, another fan-out — keeps its slots.
|
|
18
|
+
* network op, another fan-out — keeps its slots. Work that would rather wait
|
|
19
|
+
* than be refused waits on the ledger ({@link beginLoaderFetchWhenFree}) and
|
|
20
|
+
* is let in, in the order it asked, by whichever release makes room.
|
|
19
21
|
*
|
|
20
22
|
* Keyed weakly off the hosting actor's `ctx`, like the facet slot books: the
|
|
21
23
|
* limit is per Durable Object, and dynamic workers die with the isolate that
|
|
@@ -31,23 +33,142 @@ import { hostWasmIdentity } from './host-wasm.js';
|
|
|
31
33
|
* https://developers.cloudflare.com/changelog/post/2026-08-28-durable-objects-dynamic-workers-limit/
|
|
32
34
|
*/
|
|
33
35
|
export const DO_DYNAMIC_WORKER_LIMIT = 10;
|
|
36
|
+
/**
|
|
37
|
+
* The first pause after a limit refusal, doubling while refusals continue,
|
|
38
|
+
* up to {@link REFUSAL_PAUSE_MAX_MS}. A deployed Durable Object admitted a
|
|
39
|
+
* batch it had refused after a 6 s pause.
|
|
40
|
+
*/
|
|
41
|
+
const REFUSAL_PAUSE_MS = 50;
|
|
42
|
+
const REFUSAL_PAUSE_MAX_MS = 2_000;
|
|
34
43
|
const ledgers = new WeakMap();
|
|
44
|
+
const claimEntries = new WeakMap();
|
|
35
45
|
function ledger(ctx) {
|
|
36
46
|
let entry = ledgers.get(ctx);
|
|
37
47
|
if (!entry) {
|
|
38
|
-
entry = {
|
|
48
|
+
entry = {
|
|
49
|
+
inFlight: new Map(), claims: new Set(), peak: 0,
|
|
50
|
+
waiters: [], pauseMs: 0, pauseTimer: undefined, epoch: 0, refusals: 0,
|
|
51
|
+
};
|
|
39
52
|
ledgers.set(ctx, entry);
|
|
40
53
|
}
|
|
41
54
|
return entry;
|
|
42
55
|
}
|
|
56
|
+
/** Distinct workers counted: each claim's width (or more, if its holds exceed it), plus held keys no claim covers. */
|
|
43
57
|
function inUse(entry) {
|
|
44
|
-
|
|
58
|
+
let count = 0;
|
|
59
|
+
const covered = new Set();
|
|
60
|
+
for (const claim of entry.claims) {
|
|
61
|
+
count += Math.max(claim.width, claim.keys.size);
|
|
62
|
+
for (const key of claim.keys.keys())
|
|
63
|
+
covered.add(key);
|
|
64
|
+
}
|
|
65
|
+
for (const key of entry.inFlight.keys())
|
|
66
|
+
if (!covered.has(key))
|
|
67
|
+
count++;
|
|
68
|
+
return count;
|
|
69
|
+
}
|
|
70
|
+
function headroom(entry) {
|
|
71
|
+
return entry.pauseMs > 0 ? 0 : Math.max(0, DO_DYNAMIC_WORKER_LIMIT - inUse(entry));
|
|
72
|
+
}
|
|
73
|
+
function claimOf(ctx, claim) {
|
|
74
|
+
if (claim === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const owned = claimEntries.get(claim);
|
|
77
|
+
if (owned === undefined || owned.ledger !== ledger(ctx)) {
|
|
78
|
+
throw new Error('Nimbus: a Dynamic Worker claim is used only on the ledger of the actor that claimed it');
|
|
79
|
+
}
|
|
80
|
+
return owned.ledger.claims.has(owned.entry) ? owned.entry : undefined;
|
|
81
|
+
}
|
|
82
|
+
function count(map, key, by) {
|
|
83
|
+
const open = (map.get(key) ?? 0) + by;
|
|
84
|
+
if (open > 0)
|
|
85
|
+
map.set(key, open);
|
|
86
|
+
else
|
|
87
|
+
map.delete(key);
|
|
88
|
+
}
|
|
89
|
+
/** Take one hold; the caller admits waiters after. */
|
|
90
|
+
function hold(entry, workerKey, claim) {
|
|
91
|
+
count(entry.inFlight, workerKey, 1);
|
|
92
|
+
if (claim)
|
|
93
|
+
count(claim.keys, workerKey, 1);
|
|
94
|
+
entry.peak = Math.max(entry.peak, inUse(entry));
|
|
95
|
+
const epoch = entry.epoch;
|
|
96
|
+
let ended = false;
|
|
97
|
+
return (failure) => {
|
|
98
|
+
if (ended)
|
|
99
|
+
return;
|
|
100
|
+
ended = true;
|
|
101
|
+
count(entry.inFlight, workerKey, -1);
|
|
102
|
+
if (claim)
|
|
103
|
+
count(claim.keys, workerKey, -1);
|
|
104
|
+
if (classifyError(failure) === 'dynamic_worker_cap')
|
|
105
|
+
refused(entry, epoch);
|
|
106
|
+
else if (epoch === entry.epoch && entry.pauseMs === 0)
|
|
107
|
+
entry.refusals = 0;
|
|
108
|
+
admitWaiters(entry);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The platform refused a worker this ledger counted room for: it still
|
|
113
|
+
* counts workers the ledger has released, which no release here can show.
|
|
114
|
+
* So nothing new is admitted until a pause has passed. A refusal of a call
|
|
115
|
+
* that began before the latest pause started or ended is the same lag and
|
|
116
|
+
* changes nothing; one of a call let in after it doubles the next pause.
|
|
117
|
+
*/
|
|
118
|
+
function refused(entry, epoch) {
|
|
119
|
+
if (epoch !== entry.epoch)
|
|
120
|
+
return;
|
|
121
|
+
if (entry.pauseTimer !== undefined)
|
|
122
|
+
clearTimeout(entry.pauseTimer);
|
|
123
|
+
entry.pauseMs = Math.min(REFUSAL_PAUSE_MAX_MS, REFUSAL_PAUSE_MS * 2 ** entry.refusals);
|
|
124
|
+
entry.refusals++;
|
|
125
|
+
entry.epoch++;
|
|
126
|
+
entry.pauseTimer = setTimeout(() => {
|
|
127
|
+
entry.pauseMs = 0;
|
|
128
|
+
entry.pauseTimer = undefined;
|
|
129
|
+
entry.epoch++;
|
|
130
|
+
admitWaiters(entry);
|
|
131
|
+
}, entry.pauseMs);
|
|
132
|
+
}
|
|
133
|
+
function admissible(entry, waiter) {
|
|
134
|
+
// Requests to a worker already in flight count once, even while paused.
|
|
135
|
+
if (entry.inFlight.has(waiter.key))
|
|
136
|
+
return true;
|
|
137
|
+
if (entry.pauseMs > 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (waiter.claim && entry.claims.has(waiter.claim) && waiter.claim.keys.size < waiter.claim.width)
|
|
140
|
+
return true;
|
|
141
|
+
return inUse(entry) < DO_DYNAMIC_WORKER_LIMIT;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Let in every waiter that fits, in the order they asked: each takes its
|
|
145
|
+
* hold here, so a freed slot goes to exactly one waiter and is never left
|
|
146
|
+
* between a wake and a begin. Run after every change that can make room.
|
|
147
|
+
* A waiter let in on a new key lets in the later ones on that key, and the
|
|
148
|
+
* earlier ones too: the scan starts over.
|
|
149
|
+
*/
|
|
150
|
+
function admitWaiters(entry) {
|
|
151
|
+
for (let i = 0; i < entry.waiters.length;) {
|
|
152
|
+
const waiter = entry.waiters[i];
|
|
153
|
+
if (!admissible(entry, waiter)) {
|
|
154
|
+
i++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const joins = entry.inFlight.has(waiter.key);
|
|
158
|
+
entry.waiters.splice(i, 1);
|
|
159
|
+
waiter.admit(hold(entry, waiter.key, waiter.claim));
|
|
160
|
+
if (!joins)
|
|
161
|
+
i = 0;
|
|
162
|
+
}
|
|
45
163
|
}
|
|
46
164
|
/**
|
|
47
165
|
* Hold the Dynamic Worker `workerKey` in flight on this actor's ledger; the
|
|
48
166
|
* returned function ends the hold (idempotently), from the caller's own
|
|
49
167
|
* `finally`. Holds on one key nest: the worker counts once until the last
|
|
50
|
-
* one ends, as the platform counts it.
|
|
168
|
+
* one ends, as the platform counts it. Under a `claim`, the hold counts
|
|
169
|
+
* inside the claim's width. This never waits: it is for work the actor
|
|
170
|
+
* starts regardless (a resident process); {@link beginLoaderFetchWhenFree}
|
|
171
|
+
* waits for room.
|
|
51
172
|
*
|
|
52
173
|
* A begin/end pair rather than a wrapper on purpose, and the shape is
|
|
53
174
|
* load-bearing: wrapping the stub call in a ledger-owned async frame
|
|
@@ -60,51 +181,96 @@ function inUse(entry) {
|
|
|
60
181
|
* workers: an RPC stub call must stay a direct property call awaited by the
|
|
61
182
|
* frame that made it, so the ledger only brackets it.
|
|
62
183
|
*/
|
|
63
|
-
export function beginLoaderFetch(ctx, workerKey) {
|
|
184
|
+
export function beginLoaderFetch(ctx, workerKey, claim) {
|
|
64
185
|
const entry = ledger(ctx);
|
|
65
|
-
entry
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
186
|
+
const end = hold(entry, workerKey, claimOf(ctx, claim));
|
|
187
|
+
admitWaiters(entry);
|
|
188
|
+
return end;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* {@link beginLoaderFetch} once the ledger has room: resolves, holding
|
|
192
|
+
* `workerKey`, as soon as that worker is already in flight (holds on it
|
|
193
|
+
* count once) or a distinct worker more fits — within the `claim`'s width,
|
|
194
|
+
* or the actor's headroom. Waits are let in in the order they asked, by
|
|
195
|
+
* whoever's release makes the room: a hold's end, a claim's release, a
|
|
196
|
+
* pause's end. The hold is taken as the wait is let in, so a freed slot
|
|
197
|
+
* wakes one waiter and no other caller can take it first; once resolved, it
|
|
198
|
+
* is the caller's to end.
|
|
199
|
+
*
|
|
200
|
+
* A call refused with "Dynamic worker concurrency limit exceeded" ends its
|
|
201
|
+
* hold with the refusal (`end(error)`) and waits again: the refusal pauses
|
|
202
|
+
* admission (50 ms, doubling to 2 s while refusals continue), because the
|
|
203
|
+
* platform counts a worker for a moment after its call returns and no
|
|
204
|
+
* release can show that.
|
|
205
|
+
*
|
|
206
|
+
* `signal` abandons the wait: it rejects with the signal's reason and holds
|
|
207
|
+
* nothing. A wait outlives nothing on its own: bound it with a signal when
|
|
208
|
+
* room may never come (a resident process holds its worker for as long as
|
|
209
|
+
* it runs).
|
|
210
|
+
*
|
|
211
|
+
* const end = await beginLoaderFetchWhenFree(ctx, key, { signal });
|
|
212
|
+
* try { return await worker.getEntrypoint().run(); }
|
|
213
|
+
* catch (error) { end(error); throw error; }
|
|
214
|
+
* finally { end(); }
|
|
215
|
+
*/
|
|
216
|
+
export function beginLoaderFetchWhenFree(ctx, workerKey, options = {}) {
|
|
217
|
+
const { signal } = options;
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
if (signal?.aborted) {
|
|
220
|
+
reject(signal.reason);
|
|
70
221
|
return;
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
entry.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
222
|
+
}
|
|
223
|
+
const entry = ledger(ctx);
|
|
224
|
+
const abandon = () => {
|
|
225
|
+
const at = entry.waiters.indexOf(waiter);
|
|
226
|
+
if (at < 0)
|
|
227
|
+
return;
|
|
228
|
+
entry.waiters.splice(at, 1);
|
|
229
|
+
reject(signal?.reason);
|
|
230
|
+
};
|
|
231
|
+
const waiter = {
|
|
232
|
+
key: workerKey,
|
|
233
|
+
claim: claimOf(ctx, options.claim),
|
|
234
|
+
admit(end) {
|
|
235
|
+
signal?.removeEventListener('abort', abandon);
|
|
236
|
+
resolve(end);
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
entry.waiters.push(waiter);
|
|
240
|
+
signal?.addEventListener('abort', abandon, { once: true });
|
|
241
|
+
admitWaiters(entry);
|
|
242
|
+
});
|
|
78
243
|
}
|
|
79
244
|
/**
|
|
80
245
|
* Distinct Dynamic Workers this actor may still put in flight: the limit
|
|
81
|
-
* less what is held and claimed right now
|
|
246
|
+
* less what is held and claimed right now, and none while a limit refusal's
|
|
247
|
+
* pause lasts. Never negative.
|
|
82
248
|
*/
|
|
83
249
|
export function dynamicWorkerHeadroom(ctx) {
|
|
84
|
-
return
|
|
250
|
+
return headroom(ledger(ctx));
|
|
85
251
|
}
|
|
86
252
|
/**
|
|
87
253
|
* Claim `width` distinct Dynamic Workers for one fan-out, or null when the
|
|
88
254
|
* headroom cannot hold it. The claim counts until `release` (idempotent), so
|
|
89
255
|
* a second fan-out sizing itself meanwhile sees it; the claimant's own
|
|
90
|
-
* dispatches
|
|
91
|
-
* toward sending that second fan-out elsewhere.
|
|
256
|
+
* dispatches, held under the claim, count inside it.
|
|
92
257
|
*/
|
|
93
258
|
export function claimDynamicWorkers(ctx, width) {
|
|
94
259
|
const entry = ledger(ctx);
|
|
95
|
-
if (width < 1 || width >
|
|
260
|
+
if (width < 1 || width > headroom(entry))
|
|
96
261
|
return null;
|
|
97
|
-
|
|
262
|
+
const claim = { width, keys: new Map() };
|
|
263
|
+
entry.claims.add(claim);
|
|
98
264
|
entry.peak = Math.max(entry.peak, inUse(entry));
|
|
99
|
-
|
|
100
|
-
return {
|
|
265
|
+
const handle = {
|
|
101
266
|
release() {
|
|
102
|
-
if (
|
|
267
|
+
if (!entry.claims.delete(claim))
|
|
103
268
|
return;
|
|
104
|
-
|
|
105
|
-
entry.claimed -= width;
|
|
269
|
+
admitWaiters(entry);
|
|
106
270
|
},
|
|
107
271
|
};
|
|
272
|
+
claimEntries.set(handle, { ledger: entry, entry: claim });
|
|
273
|
+
return handle;
|
|
108
274
|
}
|
|
109
275
|
/** Snapshot for the diag surface. Pure read; no I/O. */
|
|
110
276
|
export function loaderLedgerStats(ctx) {
|
|
@@ -112,11 +278,19 @@ export function loaderLedgerStats(ctx) {
|
|
|
112
278
|
return {
|
|
113
279
|
limit: DO_DYNAMIC_WORKER_LIMIT,
|
|
114
280
|
inFlightWorkers: [...entry.inFlight.keys()],
|
|
115
|
-
claimed: entry
|
|
116
|
-
headroom:
|
|
281
|
+
claimed: claimedWidth(entry),
|
|
282
|
+
headroom: headroom(entry),
|
|
117
283
|
peak: entry.peak,
|
|
284
|
+
waiting: entry.waiters.length,
|
|
285
|
+
pauseMs: entry.pauseMs,
|
|
118
286
|
};
|
|
119
287
|
}
|
|
288
|
+
function claimedWidth(entry) {
|
|
289
|
+
let width = 0;
|
|
290
|
+
for (const claim of entry.claims)
|
|
291
|
+
width += claim.width;
|
|
292
|
+
return width;
|
|
293
|
+
}
|
|
120
294
|
/**
|
|
121
295
|
* Name the per-DO accounting on a "Dynamic worker concurrency limit exceeded"
|
|
122
296
|
* failure; hand every other error back untouched. The platform's message
|
|
@@ -129,7 +303,7 @@ export function withDynamicWorkerCapNamed(ctx, error) {
|
|
|
129
303
|
const entry = ledger(ctx);
|
|
130
304
|
const platform = error instanceof Error ? error.message : String(error);
|
|
131
305
|
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
|
|
306
|
+
+ `(${[...entry.inFlight.keys()].join(', ') || 'none recorded'}) and ${claimedWidth(entry)} claimed by fan-outs, `
|
|
133
307
|
+ `against a limit of ${DO_DYNAMIC_WORKER_LIMIT}; peak ${entry.peak}`, { cause: error });
|
|
134
308
|
}
|
|
135
309
|
// ── Dynamic-worker module-map ceiling ───────────────────────────────────────
|
package/dist/fanout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD;;+EAE+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,qBAAqB,UAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,uCAAuC;AACvC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,mEAAmE;IACnE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa;IAoBzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,UAAU,CAAC,CAAC,EAAE,CAAC,EACnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC;IAcf,2EAA2E;IAC3E,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,OAAO;IAKxD;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;YAOvC,aAAa;
|
|
1
|
+
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"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"}
|
package/dist/fanout.js
CHANGED
|
@@ -135,7 +135,7 @@ export class Fanout {
|
|
|
135
135
|
if (!claim)
|
|
136
136
|
return this._dispatchPeerDo(tasks, fn);
|
|
137
137
|
try {
|
|
138
|
-
return await this._dispatchInDo(tasks, fn);
|
|
138
|
+
return await this._dispatchInDo(tasks, fn, claim);
|
|
139
139
|
}
|
|
140
140
|
finally {
|
|
141
141
|
claim.release();
|
|
@@ -159,11 +159,12 @@ export class Fanout {
|
|
|
159
159
|
return `nbf:${this.opts.tag}:${this.coordDoIdShort}:${shard}`;
|
|
160
160
|
}
|
|
161
161
|
// ── Private: in-DO dispatch (in-DO fanout) ──────────────────────────────
|
|
162
|
-
async _dispatchInDo(tasks, fn) {
|
|
162
|
+
async _dispatchInDo(tasks, fn, claim) {
|
|
163
163
|
// One slot — one Dynamic Worker — per task; submitMany has claimed
|
|
164
|
-
// that width on the ledger.
|
|
164
|
+
// that width on the ledger, and the pool's dispatches are held inside it.
|
|
165
165
|
const pool = new IsolatePool(this.env, this.ctx, {
|
|
166
166
|
concurrency: tasks.length,
|
|
167
|
+
claim,
|
|
167
168
|
timeoutMs: this.opts.timeoutMs,
|
|
168
169
|
tag: this.opts.tag,
|
|
169
170
|
preamble: this.opts.preamble,
|
package/dist/fenced-work.d.ts
CHANGED
|
@@ -195,9 +195,9 @@ export declare class FencedWork<R extends FencedWorkRecord> {
|
|
|
195
195
|
* Runs once per instance — re-calls in the same instance are no-ops — and
|
|
196
196
|
* re-drives every row whose pid is `> 0` and at or below `generationBase()`
|
|
197
197
|
* with `attempt < FENCED_WORK_MAX_ATTEMPT`; the rest are abandoned. What the
|
|
198
|
-
* re-drive resolver receives is the journalled
|
|
199
|
-
* env and
|
|
200
|
-
*
|
|
198
|
+
* re-drive resolver receives is the journalled record and nothing else: a
|
|
199
|
+
* host keeps env and secrets out of it, so the resolver's embedder
|
|
200
|
+
* re-resolves them rather than reading them back.
|
|
201
201
|
*/
|
|
202
202
|
recoverInterrupted(): Promise<void>;
|
|
203
203
|
/**
|
package/dist/fenced-work.js
CHANGED
|
@@ -183,9 +183,9 @@ export class FencedWork {
|
|
|
183
183
|
* Runs once per instance — re-calls in the same instance are no-ops — and
|
|
184
184
|
* re-drives every row whose pid is `> 0` and at or below `generationBase()`
|
|
185
185
|
* with `attempt < FENCED_WORK_MAX_ATTEMPT`; the rest are abandoned. What the
|
|
186
|
-
* re-drive resolver receives is the journalled
|
|
187
|
-
* env and
|
|
188
|
-
*
|
|
186
|
+
* re-drive resolver receives is the journalled record and nothing else: a
|
|
187
|
+
* host keeps env and secrets out of it, so the resolver's embedder
|
|
188
|
+
* re-resolves them rather than reading them back.
|
|
189
189
|
*/
|
|
190
190
|
async recoverInterrupted() {
|
|
191
191
|
if (this.recovered)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inner-do-env.ts — a classic Durable Object binding inside an inner Worker.
|
|
3
|
+
*
|
|
4
|
+
* `nimbus wrangler dev` loads the user's Worker as a dynamic worker, and its
|
|
5
|
+
* Durable Object bindings run as facets of the session Durable Object. The
|
|
6
|
+
* binding the loader passes (`NimbusDurableObjectNamespace`, an entrypoint of
|
|
7
|
+
* the session's isolate) answers RPC, so it cannot be the inner Worker's
|
|
8
|
+
* namespace: a DurableObjectNamespace's API is synchronous (`env.P.get(
|
|
9
|
+
* env.P.idFromName('x'))` takes no await), and an RpcPromise cannot travel as
|
|
10
|
+
* an argument ("Could not serialize object of type RpcPromise").
|
|
11
|
+
*
|
|
12
|
+
* So a module the inner Worker runs before any of its own code (its main
|
|
13
|
+
* module's first import, `innerWorkerModules`) replaces each such binding in
|
|
14
|
+
* the isolate's env, which every handler, entrypoint and Durable Object of the
|
|
15
|
+
* isolate sees, with a local namespace. Ids are made locally, and `get`
|
|
16
|
+
* answers at once an RPC stub (`new RpcStub(target)`) of a local target that
|
|
17
|
+
* relays each member the stub's caller reaches (a call, a read, or a path
|
|
18
|
+
* through members, fetch included) to the binding's `callOn` or `getOn`,
|
|
19
|
+
* which the session runs on the object's facet. An RPC stub is what a
|
|
20
|
+
* Durable Object stub is to the runtime: callable by any method name, read by
|
|
21
|
+
* any property name, pipelined, bound to its request, and transferable, as an
|
|
22
|
+
* argument or an answer, where an entrypoint of a dynamically-loaded Worker
|
|
23
|
+
* is not. Arguments and answers cross natively, stubs, functions and streams
|
|
24
|
+
* included.
|
|
25
|
+
*
|
|
26
|
+
* Its prototype is not RpcStub's but one shaped as a Durable Object stub's:
|
|
27
|
+
* its constructor is a class `DurableObject` that cannot be constructed, its
|
|
28
|
+
* tag is 'DurableObject', and it has no `dup` or Symbol.dispose of its own,
|
|
29
|
+
* so `dup` is a member, which the runtime refuses as on Cloudflare. It
|
|
30
|
+
* differs from a Durable Object stub in two ways the runtime fixes. `typeof`
|
|
31
|
+
* is 'function'. And it is not persistent, so a Worker Loader env cannot carry
|
|
32
|
+
* it ("RpcStub cannot be serialized in this context because it is not a
|
|
33
|
+
* persistent stub"): the loader shim (NimbusLoaderRPC) keeps a child's code
|
|
34
|
+
* and loads it again in each later request, so the child's env can carry
|
|
35
|
+
* nothing made in one request, the session's own stubs included.
|
|
36
|
+
*/
|
|
37
|
+
import type { RpcStub, WorkerEntrypoint } from 'cloudflare:workers';
|
|
38
|
+
/**
|
|
39
|
+
* The id string a name gives: deterministic (FNV-style, 64-bit hex), with the
|
|
40
|
+
* prefix `name:` so it never collides with a `uniq:` id. Self-contained: its
|
|
41
|
+
* source also runs in the inner isolate (innerDoAdapter).
|
|
42
|
+
*/
|
|
43
|
+
export declare function innerDoIdFromName(name: string): string;
|
|
44
|
+
/** What the binding the loader passes answers: one access to one object. */
|
|
45
|
+
export interface InnerDoRemote {
|
|
46
|
+
/** The member of object `id` at `path` (names from the object down), called with `args`. */
|
|
47
|
+
callOn(id: string, path: string[], args: unknown[]): Promise<unknown>;
|
|
48
|
+
/** The member of object `id` at `path`, read. */
|
|
49
|
+
getOn(id: string, path: string[]): Promise<unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** What the adapter runs over: the inner isolate's `cloudflare:workers`. */
|
|
52
|
+
export interface InnerDoRuntime {
|
|
53
|
+
env: object;
|
|
54
|
+
WorkerEntrypoint: typeof WorkerEntrypoint;
|
|
55
|
+
RpcStub: typeof RpcStub;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The adapter, as it runs in the inner isolate: it replaces each of `names`
|
|
59
|
+
* in `runtime.env` that holds the binding with a local DurableObjectNamespace,
|
|
60
|
+
* and answers the class check the main module exports. `main` is the main
|
|
61
|
+
* module's namespace. Self-contained (serialized with toString): it reaches
|
|
62
|
+
* nothing outside itself but its arguments.
|
|
63
|
+
*/
|
|
64
|
+
export declare function innerDoAdapter(idFromName: (name: string) => string, names: readonly string[], main: object, runtime: InnerDoRuntime): {
|
|
65
|
+
NimbusDurableObjectClasses: unknown;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The modules an inner Worker runs with Durable Object bindings `names`: its
|
|
69
|
+
* bundle as the main module, whose first import is the adapter (so the
|
|
70
|
+
* adapter has run before any of the Worker's code) and which exports the
|
|
71
|
+
* class check as `classesEntrypoint`, and the adapter. The import shares the
|
|
72
|
+
* bundle's first line, so line numbers stay the bundle's. A Worker with no
|
|
73
|
+
* such binding runs its bundle as it is, and has no class check (null).
|
|
74
|
+
*
|
|
75
|
+
* `classesEntrypoint` is a name the bundle never spells, so it exports no
|
|
76
|
+
* such name itself (a bundler prints an ASCII name as it is).
|
|
77
|
+
*/
|
|
78
|
+
export declare function innerWorkerModules(bundle: string, names: readonly string[]): {
|
|
79
|
+
mainModule: string;
|
|
80
|
+
modules: Record<string, string>;
|
|
81
|
+
classesEntrypoint: string | null;
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=inner-do-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inner-do-env.d.ts","sourceRoot":"","sources":["../src/inner-do-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGpE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWtD;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,iDAAiD;IACjD,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,OAAO,EAAE,OAAO,OAAO,CAAC;CACzB;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACpC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACtB;IAAE,0BAA0B,EAAE,OAAO,CAAA;CAAE,CAuFzC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAiBA"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inner-do-env.ts — a classic Durable Object binding inside an inner Worker.
|
|
3
|
+
*
|
|
4
|
+
* `nimbus wrangler dev` loads the user's Worker as a dynamic worker, and its
|
|
5
|
+
* Durable Object bindings run as facets of the session Durable Object. The
|
|
6
|
+
* binding the loader passes (`NimbusDurableObjectNamespace`, an entrypoint of
|
|
7
|
+
* the session's isolate) answers RPC, so it cannot be the inner Worker's
|
|
8
|
+
* namespace: a DurableObjectNamespace's API is synchronous (`env.P.get(
|
|
9
|
+
* env.P.idFromName('x'))` takes no await), and an RpcPromise cannot travel as
|
|
10
|
+
* an argument ("Could not serialize object of type RpcPromise").
|
|
11
|
+
*
|
|
12
|
+
* So a module the inner Worker runs before any of its own code (its main
|
|
13
|
+
* module's first import, `innerWorkerModules`) replaces each such binding in
|
|
14
|
+
* the isolate's env, which every handler, entrypoint and Durable Object of the
|
|
15
|
+
* isolate sees, with a local namespace. Ids are made locally, and `get`
|
|
16
|
+
* answers at once an RPC stub (`new RpcStub(target)`) of a local target that
|
|
17
|
+
* relays each member the stub's caller reaches (a call, a read, or a path
|
|
18
|
+
* through members, fetch included) to the binding's `callOn` or `getOn`,
|
|
19
|
+
* which the session runs on the object's facet. An RPC stub is what a
|
|
20
|
+
* Durable Object stub is to the runtime: callable by any method name, read by
|
|
21
|
+
* any property name, pipelined, bound to its request, and transferable, as an
|
|
22
|
+
* argument or an answer, where an entrypoint of a dynamically-loaded Worker
|
|
23
|
+
* is not. Arguments and answers cross natively, stubs, functions and streams
|
|
24
|
+
* included.
|
|
25
|
+
*
|
|
26
|
+
* Its prototype is not RpcStub's but one shaped as a Durable Object stub's:
|
|
27
|
+
* its constructor is a class `DurableObject` that cannot be constructed, its
|
|
28
|
+
* tag is 'DurableObject', and it has no `dup` or Symbol.dispose of its own,
|
|
29
|
+
* so `dup` is a member, which the runtime refuses as on Cloudflare. It
|
|
30
|
+
* differs from a Durable Object stub in two ways the runtime fixes. `typeof`
|
|
31
|
+
* is 'function'. And it is not persistent, so a Worker Loader env cannot carry
|
|
32
|
+
* it ("RpcStub cannot be serialized in this context because it is not a
|
|
33
|
+
* persistent stub"): the loader shim (NimbusLoaderRPC) keeps a child's code
|
|
34
|
+
* and loads it again in each later request, so the child's env can carry
|
|
35
|
+
* nothing made in one request, the session's own stubs included.
|
|
36
|
+
*/
|
|
37
|
+
import { ESBUILD_NAME_MODULE_SHIM } from '@nimbus-sh/core/_shared/esbuild-facet-shim.js';
|
|
38
|
+
/**
|
|
39
|
+
* The id string a name gives: deterministic (FNV-style, 64-bit hex), with the
|
|
40
|
+
* prefix `name:` so it never collides with a `uniq:` id. Self-contained: its
|
|
41
|
+
* source also runs in the inner isolate (innerDoAdapter).
|
|
42
|
+
*/
|
|
43
|
+
export function innerDoIdFromName(name) {
|
|
44
|
+
let h1 = 0xdeadbeef ^ name.length;
|
|
45
|
+
let h2 = 0x41c6ce57 ^ name.length;
|
|
46
|
+
for (let i = 0; i < name.length; i++) {
|
|
47
|
+
const ch = name.charCodeAt(i);
|
|
48
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
49
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
50
|
+
}
|
|
51
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
52
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
53
|
+
return 'name:' + (h1 >>> 0).toString(16).padStart(8, '0') + (h2 >>> 0).toString(16).padStart(8, '0');
|
|
54
|
+
}
|
|
55
|
+
/** The entrypoint a build asks which Durable Object classes are missing, unless the bundle spells it. */
|
|
56
|
+
const CLASSES_ENTRYPOINT = 'NimbusDurableObjectClasses';
|
|
57
|
+
/**
|
|
58
|
+
* The adapter, as it runs in the inner isolate: it replaces each of `names`
|
|
59
|
+
* in `runtime.env` that holds the binding with a local DurableObjectNamespace,
|
|
60
|
+
* and answers the class check the main module exports. `main` is the main
|
|
61
|
+
* module's namespace. Self-contained (serialized with toString): it reaches
|
|
62
|
+
* nothing outside itself but its arguments.
|
|
63
|
+
*/
|
|
64
|
+
export function innerDoAdapter(idFromName, names, main, runtime) {
|
|
65
|
+
/** A Durable Object id: its string, and the name it was made from. */
|
|
66
|
+
class DurableObjectId {
|
|
67
|
+
name;
|
|
68
|
+
#id;
|
|
69
|
+
constructor(id, name) {
|
|
70
|
+
this.#id = id;
|
|
71
|
+
if (name !== undefined)
|
|
72
|
+
this.name = name;
|
|
73
|
+
}
|
|
74
|
+
toString() { return this.#id; }
|
|
75
|
+
equals(other) { return other instanceof DurableObjectId && String(other) === this.#id; }
|
|
76
|
+
}
|
|
77
|
+
/** Whether `value` is the binding the loader passes (an RPC stub's methods are its properties). */
|
|
78
|
+
function isRemote(value) {
|
|
79
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
80
|
+
&& typeof Reflect.get(value, 'callOn') === 'function' && typeof Reflect.get(value, 'getOn') === 'function';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The member of object `id` at `path`, as the runtime reaches it in an RPC
|
|
84
|
+
* to the stub: called, read (it is thenable, and the runtime awaits what a
|
|
85
|
+
* read answers), or walked through to one of its own members (the runtime
|
|
86
|
+
* walks a path through own properties only, so every name is reported as
|
|
87
|
+
* one). At the empty path it is the stub's target.
|
|
88
|
+
*/
|
|
89
|
+
function member(remote, id, path) {
|
|
90
|
+
const next = (name) => member(remote, id, [...path, name]);
|
|
91
|
+
return new Proxy((..._args) => undefined, {
|
|
92
|
+
apply: (_target, _self, args) => remote.callOn(id, [...path], args),
|
|
93
|
+
get: (target, name) => {
|
|
94
|
+
if (name === 'then') {
|
|
95
|
+
return (resolve, reject) => remote.getOn(id, [...path]).then(resolve, reject);
|
|
96
|
+
}
|
|
97
|
+
return typeof name === 'string' ? next(name) : Reflect.get(target, name);
|
|
98
|
+
},
|
|
99
|
+
getOwnPropertyDescriptor: (target, name) => (typeof name === 'string' && name !== 'then'
|
|
100
|
+
? { value: next(name), writable: true, enumerable: true, configurable: true }
|
|
101
|
+
: Reflect.getOwnPropertyDescriptor(target, name)),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A stub's prototype, as a Durable Object stub's shows: its constructor
|
|
106
|
+
* cannot be called or constructed, and it is tagged 'DurableObject'.
|
|
107
|
+
*/
|
|
108
|
+
const stubPrototype = Object.create(Object.prototype, {
|
|
109
|
+
constructor: {
|
|
110
|
+
value: function DurableObject() { throw new TypeError('Illegal constructor'); },
|
|
111
|
+
writable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
},
|
|
114
|
+
[Symbol.toStringTag]: { value: 'DurableObject', configurable: true },
|
|
115
|
+
});
|
|
116
|
+
/** Asked once by a build: which classes the main module does not export. */
|
|
117
|
+
class NimbusDurableObjectClasses extends runtime.WorkerEntrypoint {
|
|
118
|
+
missing(classNames) {
|
|
119
|
+
return classNames.filter((name) => typeof Reflect.get(main, name) !== 'function');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** env.MY_DO: the namespace, made locally; its stubs relay to `remote`. */
|
|
123
|
+
class DurableObjectNamespace {
|
|
124
|
+
#remote;
|
|
125
|
+
constructor(remote) { this.#remote = remote; }
|
|
126
|
+
idFromName(name) { return new DurableObjectId(idFromName(String(name)), String(name)); }
|
|
127
|
+
newUniqueId() { return new DurableObjectId('uniq:' + crypto.randomUUID().replaceAll('-', '')); }
|
|
128
|
+
idFromString(id) { return new DurableObjectId(String(id)); }
|
|
129
|
+
get(id) {
|
|
130
|
+
const at = id instanceof DurableObjectId ? id : new DurableObjectId(String(id));
|
|
131
|
+
const stub = Object.setPrototypeOf(new runtime.RpcStub(member(this.#remote, String(at), [])), stubPrototype);
|
|
132
|
+
// As a Durable Object stub has them: its own, enumerable, in this order.
|
|
133
|
+
return Object.defineProperties(stub, {
|
|
134
|
+
name: { value: at.name, enumerable: true },
|
|
135
|
+
id: { value: at, enumerable: true },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
getByName(name) { return this.get(this.idFromName(name)); }
|
|
139
|
+
jurisdiction() { return this; }
|
|
140
|
+
}
|
|
141
|
+
for (const name of names) {
|
|
142
|
+
const remote = Reflect.get(runtime.env, name);
|
|
143
|
+
if (isRemote(remote))
|
|
144
|
+
Reflect.set(runtime.env, name, new DurableObjectNamespace(remote));
|
|
145
|
+
}
|
|
146
|
+
return { NimbusDurableObjectClasses };
|
|
147
|
+
}
|
|
148
|
+
/** The inner Worker's own module, as bundled, and the adapter's. */
|
|
149
|
+
const MAIN_MODULE = 'worker.js';
|
|
150
|
+
const ADAPTER_MODULE = 'nimbus-do-env.js';
|
|
151
|
+
/**
|
|
152
|
+
* The modules an inner Worker runs with Durable Object bindings `names`: its
|
|
153
|
+
* bundle as the main module, whose first import is the adapter (so the
|
|
154
|
+
* adapter has run before any of the Worker's code) and which exports the
|
|
155
|
+
* class check as `classesEntrypoint`, and the adapter. The import shares the
|
|
156
|
+
* bundle's first line, so line numbers stay the bundle's. A Worker with no
|
|
157
|
+
* such binding runs its bundle as it is, and has no class check (null).
|
|
158
|
+
*
|
|
159
|
+
* `classesEntrypoint` is a name the bundle never spells, so it exports no
|
|
160
|
+
* such name itself (a bundler prints an ASCII name as it is).
|
|
161
|
+
*/
|
|
162
|
+
export function innerWorkerModules(bundle, names) {
|
|
163
|
+
if (names.length === 0)
|
|
164
|
+
return { mainModule: MAIN_MODULE, modules: { [MAIN_MODULE]: bundle }, classesEntrypoint: null };
|
|
165
|
+
let classesEntrypoint = CLASSES_ENTRYPOINT;
|
|
166
|
+
for (let n = 2; bundle.includes(classesEntrypoint); n++)
|
|
167
|
+
classesEntrypoint = `${CLASSES_ENTRYPOINT}_${n}`;
|
|
168
|
+
const head = `export { ${CLASSES_ENTRYPOINT} as ${classesEntrypoint} } from './${ADAPTER_MODULE}';`;
|
|
169
|
+
// A hashbang must stay first.
|
|
170
|
+
const at = bundle.startsWith('#!') ? bundle.indexOf('\n') + 1 : 0;
|
|
171
|
+
const main = bundle.slice(0, at) + head + bundle.slice(at);
|
|
172
|
+
const adapter = [
|
|
173
|
+
"import { env, RpcStub, WorkerEntrypoint } from 'cloudflare:workers';",
|
|
174
|
+
`import * as main from './${MAIN_MODULE}';`,
|
|
175
|
+
// The functions below are serialized from the bundled worker, which wraps them in __name.
|
|
176
|
+
ESBUILD_NAME_MODULE_SHIM,
|
|
177
|
+
`const { ${CLASSES_ENTRYPOINT} } = (${innerDoAdapter.toString()})(${innerDoIdFromName.toString()}, ${JSON.stringify(names)}, main, { env, RpcStub, WorkerEntrypoint });`,
|
|
178
|
+
`export { ${CLASSES_ENTRYPOINT} };`,
|
|
179
|
+
].join('\n');
|
|
180
|
+
return { mainModule: MAIN_MODULE, modules: { [MAIN_MODULE]: main, [ADAPTER_MODULE]: adapter }, classesEntrypoint };
|
|
181
|
+
}
|