@nimbus-sh/fabric 0.3.0 → 0.4.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.
@@ -1,3 +1,3 @@
1
- export { adoptCtxExports, composeFabric, getCtxExports, stagedBootAssembler, supervisorEntrypoint, supervisorEntrypointName, } from '@nimbus-sh/platform/composition.js';
2
- export type { CtxExports, EntrypointLoopbackFactory, FabricComposition, StagedBootAssembler, } from '@nimbus-sh/platform/composition.js';
1
+ export * from '@nimbus-sh/platform/composition.js';
2
+ export type * from '@nimbus-sh/platform/composition.js';
3
3
  //# sourceMappingURL=composition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,oCAAoC,CAAC;AAE5C,YAAY,EACV,UAAU,EACV,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AACA,cAAc,oCAAoC,CAAC;AACnD,mBAAmB,oCAAoC,CAAC"}
@@ -1,2 +1,2 @@
1
1
  // Core and fabric share one holder without introducing a dependency cycle.
2
- export { adoptCtxExports, composeFabric, getCtxExports, stagedBootAssembler, supervisorEntrypoint, supervisorEntrypointName, } from '@nimbus-sh/platform/composition.js';
2
+ export * from '@nimbus-sh/platform/composition.js';
@@ -1 +1 @@
1
- {"version":3,"file":"fenced-work.d.ts","sourceRoot":"","sources":["../src/fenced-work.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fenced-work.d.ts","sourceRoot":"","sources":["../src/fenced-work.ts"],"names":[],"mappings":"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"}
@@ -145,6 +145,11 @@ export class FencedWork {
145
145
  let inflight = this.drives.get(key);
146
146
  if (inflight === undefined) {
147
147
  inflight = (async () => {
148
+ if (record.attempt >= FENCED_WORK_MAX_ATTEMPT) {
149
+ this.host.onAbandoned?.(record);
150
+ await this.supersede(key);
151
+ return true;
152
+ }
148
153
  let failed = false;
149
154
  try {
150
155
  await this.host.redrive(record, record.attempt + 1);
@@ -235,6 +240,7 @@ export class FencedWork {
235
240
  * a previous generation the terminal hook will never fire for.
236
241
  */
237
242
  async supersede(key) {
243
+ this.journalledPids.delete(Number(key.slice(FENCED_WORK_KEY_PREFIX.length)));
238
244
  await this.storage.delete(key);
239
245
  await this.storage.sync();
240
246
  }
@@ -114,7 +114,7 @@ export interface PacedWorkHost {
114
114
  * reason. Without it the pump degrades to a same-context timer — see
115
115
  * {@link PacedWork.nextTurn}.
116
116
  */
117
- requestTurn?: () => void;
117
+ requestTurn?: (notBefore?: number) => void;
118
118
  }
119
119
  /**
120
120
  * The granting side of {@link TurnScheduler}: parks suspended launches
@@ -149,7 +149,7 @@ export declare class PacedWork implements TurnScheduler {
149
149
  * always performed, so a harness or a runtime without alarms loses the
150
150
  * responsiveness but keeps the behaviour.
151
151
  */
152
- nextTurn(chunkEnded: Promise<void>): Promise<void>;
152
+ nextTurn(chunkEnded: Promise<void>, notBefore?: number): Promise<void>;
153
153
  /**
154
154
  * Run one chunk of every launch waiting for a turn.
155
155
  *
@@ -1 +1 @@
1
- {"version":3,"file":"turn-budget.d.ts","sourceRoot":"","sources":["../src/turn-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,qBAAa,UAAU;IAkBnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,qEAAqE;IACrE,MAAM,SAAK;IACX,iDAAiD;IACjD,KAAK,SAAK;IAEV,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAA8D;IAEhF;;;;;;;OAOG;gBAEgB,SAAS,EAAE,aAAa,EACxB,aAAa,GAAE,MAA6B,EAC5C,WAAW,CAAC,GAAE,MAAM,IAAI,aAAA;IAG3C;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC;;;;OAIG;IACH,MAAM,IAAI,IAAI;CAIf;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,CAAC,GAAG,IAAI,KAAK;IAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAAE,CAI/G;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,qBAAa,SAAU,YAAW,aAAa;IAkB3C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAlBvB;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAgE;IAE/E;;;OAGG;gBAEgB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa;IAGtC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,0DAA0D;IAC1D,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMtD"}
1
+ {"version":3,"file":"turn-budget.d.ts","sourceRoot":"","sources":["../src/turn-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,qBAAa,UAAU;IAkBnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,qEAAqE;IACrE,MAAM,SAAK;IACX,iDAAiD;IACjD,KAAK,SAAK;IAEV,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAA8D;IAEhF;;;;;;;OAOG;gBAEgB,SAAS,EAAE,aAAa,EACxB,aAAa,GAAE,MAA6B,EAC5C,WAAW,CAAC,GAAE,MAAM,IAAI,aAAA;IAG3C;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC;;;;OAIG;IACH,MAAM,IAAI,IAAI;CAIf;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,CAAC,GAAG,IAAI,KAAK;IAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAAE,CAI/G;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED;;;GAGG;AACH,qBAAa,SAAU,YAAW,aAAa;IAkB3C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAlBvB;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAmF;IAElG;;;OAGG;gBAEgB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa;IAGtC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjE;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,0DAA0D;IAC1D,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMtD"}
@@ -148,14 +148,14 @@ export class PacedWork {
148
148
  * always performed, so a harness or a runtime without alarms loses the
149
149
  * responsiveness but keeps the behaviour.
150
150
  */
151
- nextTurn(chunkEnded) {
151
+ nextTurn(chunkEnded, notBefore = 0) {
152
152
  return new Promise((resume) => {
153
- this.waiters.push({ resume, chunkEnded });
153
+ this.waiters.push({ resume, chunkEnded, notBefore });
154
154
  if (this.host.requestTurn) {
155
- this.host.requestTurn();
155
+ this.host.requestTurn(notBefore);
156
156
  return;
157
157
  }
158
- setTimeout(() => { void this.pump(); }, 0);
158
+ setTimeout(() => { void this.pump(); }, Math.max(0, notBefore - Date.now()));
159
159
  });
160
160
  }
161
161
  /**
@@ -172,10 +172,13 @@ export class PacedWork {
172
172
  // is what an alarm calls, and the first turn after a reset is the
173
173
  // re-delivered alarm of a launch the reset interrupted.
174
174
  await runColdStart(this.ctx);
175
- const waiting = this.waiters;
175
+ const now = Date.now();
176
+ const waiting = this.waiters.filter((waiter) => waiter.notBefore <= now);
177
+ this.waiters = this.waiters.filter((waiter) => waiter.notBefore > now);
178
+ if (this.waiters.length > 0)
179
+ this.host.requestTurn?.(Math.min(...this.waiters.map((waiter) => waiter.notBefore)));
176
180
  if (waiting.length === 0)
177
181
  return;
178
- this.waiters = [];
179
182
  for (const waiter of waiting)
180
183
  waiter.resume();
181
184
  await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/fabric",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The Cloudflare half of Nimbus — Durable Object facet hosting, dynamic-worker loader pools, the resident-process fabric, and DO alarm/hibernation machinery.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -50,8 +50,8 @@
50
50
  "typecheck": "tsc --noEmit"
51
51
  },
52
52
  "dependencies": {
53
- "@nimbus-sh/core": "^0.7.0",
54
- "@nimbus-sh/platform": "^0.2.0",
53
+ "@nimbus-sh/core": "^0.8.0",
54
+ "@nimbus-sh/platform": "^0.3.0",
55
55
  "zod": "^4.4.3"
56
56
  },
57
57
  "devDependencies": {
@@ -1,16 +1,3 @@
1
1
  // Core and fabric share one holder without introducing a dependency cycle.
2
- export {
3
- adoptCtxExports,
4
- composeFabric,
5
- getCtxExports,
6
- stagedBootAssembler,
7
- supervisorEntrypoint,
8
- supervisorEntrypointName,
9
- } from '@nimbus-sh/platform/composition.js';
10
-
11
- export type {
12
- CtxExports,
13
- EntrypointLoopbackFactory,
14
- FabricComposition,
15
- StagedBootAssembler,
16
- } from '@nimbus-sh/platform/composition.js';
2
+ export * from '@nimbus-sh/platform/composition.js';
3
+ export type * from '@nimbus-sh/platform/composition.js';
@@ -225,6 +225,11 @@ export class FencedWork<R extends FencedWorkRecord> {
225
225
  let inflight = this.drives.get(key);
226
226
  if (inflight === undefined) {
227
227
  inflight = (async (): Promise<boolean> => {
228
+ if (record.attempt >= FENCED_WORK_MAX_ATTEMPT) {
229
+ this.host.onAbandoned?.(record);
230
+ await this.supersede(key);
231
+ return true;
232
+ }
228
233
  let failed = false;
229
234
  try {
230
235
  await this.host.redrive(record, record.attempt + 1);
@@ -310,6 +315,7 @@ export class FencedWork<R extends FencedWorkRecord> {
310
315
  * a previous generation the terminal hook will never fire for.
311
316
  */
312
317
  private async supersede(key: string): Promise<void> {
318
+ this.journalledPids.delete(Number(key.slice(FENCED_WORK_KEY_PREFIX.length)));
313
319
  await this.storage.delete(key);
314
320
  await this.storage.sync();
315
321
  }
@@ -142,7 +142,7 @@ export interface PacedWorkHost {
142
142
  * reason. Without it the pump degrades to a same-context timer — see
143
143
  * {@link PacedWork.nextTurn}.
144
144
  */
145
- requestTurn?: () => void;
145
+ requestTurn?: (notBefore?: number) => void;
146
146
  }
147
147
 
148
148
  /**
@@ -160,7 +160,7 @@ export class PacedWork implements TurnScheduler {
160
160
  * half-built work for pids that no longer exist, where re-driving a launch
161
161
  * from its inputs is the same idempotent work again.
162
162
  */
163
- private waiters: Array<{ resume: () => void; chunkEnded: Promise<void> }> = [];
163
+ private waiters: Array<{ resume: () => void; chunkEnded: Promise<void>; notBefore: number }> = [];
164
164
 
165
165
  /**
166
166
  * `ctx` keys the cold-start queue the pump drains first on every turn it
@@ -181,14 +181,14 @@ export class PacedWork implements TurnScheduler {
181
181
  * always performed, so a harness or a runtime without alarms loses the
182
182
  * responsiveness but keeps the behaviour.
183
183
  */
184
- nextTurn(chunkEnded: Promise<void>): Promise<void> {
184
+ nextTurn(chunkEnded: Promise<void>, notBefore = 0): Promise<void> {
185
185
  return new Promise<void>((resume) => {
186
- this.waiters.push({ resume, chunkEnded });
186
+ this.waiters.push({ resume, chunkEnded, notBefore });
187
187
  if (this.host.requestTurn) {
188
- this.host.requestTurn();
188
+ this.host.requestTurn(notBefore);
189
189
  return;
190
190
  }
191
- setTimeout(() => { void this.pump(); }, 0);
191
+ setTimeout(() => { void this.pump(); }, Math.max(0, notBefore - Date.now()));
192
192
  });
193
193
  }
194
194
 
@@ -206,9 +206,11 @@ export class PacedWork implements TurnScheduler {
206
206
  // is what an alarm calls, and the first turn after a reset is the
207
207
  // re-delivered alarm of a launch the reset interrupted.
208
208
  await runColdStart(this.ctx);
209
- const waiting = this.waiters;
209
+ const now = Date.now();
210
+ const waiting = this.waiters.filter((waiter) => waiter.notBefore <= now);
211
+ this.waiters = this.waiters.filter((waiter) => waiter.notBefore > now);
212
+ if (this.waiters.length > 0) this.host.requestTurn?.(Math.min(...this.waiters.map((waiter) => waiter.notBefore)));
210
213
  if (waiting.length === 0) return;
211
- this.waiters = [];
212
214
  for (const waiter of waiting) waiter.resume();
213
215
  await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
214
216
  }