@dee-wan/cache 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +208 -3
  2. package/dist/chunk-A4WV6RY4.js +311 -0
  3. package/dist/chunk-A4WV6RY4.js.map +1 -0
  4. package/dist/chunk-AENVFT2S.js +6 -0
  5. package/dist/chunk-AENVFT2S.js.map +1 -0
  6. package/dist/chunk-E6OZZ5UX.js +133 -0
  7. package/dist/chunk-E6OZZ5UX.js.map +1 -0
  8. package/dist/chunk-JPK2AF7X.js +18 -0
  9. package/dist/chunk-JPK2AF7X.js.map +1 -0
  10. package/dist/chunk-LAXBP5DQ.js +330 -0
  11. package/dist/chunk-LAXBP5DQ.js.map +1 -0
  12. package/dist/chunk-LHOK33GI.js +12 -0
  13. package/dist/chunk-LHOK33GI.js.map +1 -0
  14. package/dist/chunk-M4UOCIDQ.js +557 -0
  15. package/dist/chunk-M4UOCIDQ.js.map +1 -0
  16. package/dist/chunk-NFBMWKY5.js +54 -0
  17. package/dist/chunk-NFBMWKY5.js.map +1 -0
  18. package/dist/chunk-O3KHMFHX.js +425 -0
  19. package/dist/chunk-O3KHMFHX.js.map +1 -0
  20. package/dist/chunk-WJNCFFF3.js +637 -0
  21. package/dist/chunk-WJNCFFF3.js.map +1 -0
  22. package/dist/chunk-WKZ7TKGM.js +82 -0
  23. package/dist/chunk-WKZ7TKGM.js.map +1 -0
  24. package/dist/chunk-ZBYAKDNZ.js +45 -0
  25. package/dist/chunk-ZBYAKDNZ.js.map +1 -0
  26. package/dist/chunk-ZCMZJKGN.js +78 -0
  27. package/dist/chunk-ZCMZJKGN.js.map +1 -0
  28. package/dist/client.d.ts +63 -0
  29. package/dist/client.js +260 -0
  30. package/dist/client.js.map +1 -0
  31. package/dist/cloudflare/index.d.ts +15 -2
  32. package/dist/cloudflare/index.js +413 -45
  33. package/dist/cloudflare/index.js.map +1 -1
  34. package/dist/d1/config.d.ts +41 -0
  35. package/dist/d1/config.js +102 -0
  36. package/dist/d1/config.js.map +1 -0
  37. package/dist/d1/index.d.ts +80 -0
  38. package/dist/d1/index.js +218 -0
  39. package/dist/d1/index.js.map +1 -0
  40. package/dist/d1/response-lease.d.ts +9 -0
  41. package/dist/d1/response-lease.js +44 -0
  42. package/dist/d1/response-lease.js.map +1 -0
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.js +3 -616
  45. package/dist/index.js.map +1 -1
  46. package/dist/profile--LBtbQI_.d.ts +30 -0
  47. package/dist/response-lease-DeeM-ofO.d.ts +19 -0
  48. package/dist/response-lease.d.ts +7 -0
  49. package/dist/response-lease.js +14 -0
  50. package/dist/response-lease.js.map +1 -0
  51. package/dist/response-warmup-BWQvnT1G.d.ts +38 -0
  52. package/dist/scoped-read.d.ts +12 -0
  53. package/dist/scoped-read.js +30 -0
  54. package/dist/scoped-read.js.map +1 -0
  55. package/dist/{types-DwtU0KpX.d.ts → types-CtErcz2a.d.ts} +25 -1
  56. package/docs/cache-contract.md +49 -1
  57. package/package.json +31 -2
@@ -1,4 +1,9 @@
1
- import { MAX_TTL_MS, MAX_VALUE_BYTES, MAX_COORDINATOR_GENERATION_LENGTH, MAX_LEASE_MS, MAX_LOCK_MS, GLOBAL_FENCE } from '../chunk-OQRNVGQE.js';
1
+ export { createDurableObjectStore } from '../chunk-NFBMWKY5.js';
2
+ export { createResponseWarmup } from '../chunk-A4WV6RY4.js';
3
+ import { boundedD1Store, withinCoordinatorDeadline } from '../chunk-WKZ7TKGM.js';
4
+ import { createRegionalCache } from '../chunk-E6OZZ5UX.js';
5
+ import { GLOBAL_FENCE, MAX_TTL_MS, MAX_VALUE_BYTES, MAX_COORDINATOR_GENERATION_LENGTH, MAX_LEASE_MS, MAX_LOCK_MS } from '../chunk-OQRNVGQE.js';
6
+ import '../chunk-ZCMZJKGN.js';
2
7
  import { DurableObject } from 'cloudflare:workers';
3
8
 
4
9
  // src/cloudflare/coordinator-core.ts
@@ -213,6 +218,13 @@ function createCoordinatorCore(sql, newToken, transact, limits = {}) {
213
218
  return snapshot !== null && matches(snapshot, epochs(Object.keys(snapshot)));
214
219
  }
215
220
  return {
221
+ inspectFences(request, now) {
222
+ assertRequest(isFenceList(request.fences) && isGeneration(request.generation), "invalid inspectFences request");
223
+ return transact(() => {
224
+ observeGeneration(request.generation, now);
225
+ return { epochs: epochs(request.fences), pending: pending(request.fences, now) };
226
+ });
227
+ },
216
228
  beginWrite(request, now) {
217
229
  assertRequest(
218
230
  isFenceList(request.fences) && isDuration(request.leaseMs) && isGeneration(request.generation) && isToken(request.token),
@@ -261,6 +273,7 @@ function createCoordinatorCore(sql, newToken, transact, limits = {}) {
261
273
  const refreshable = request.refresh === true && now >= Number(entry.soft_expires_at) && !lockLive && !pending(request.fences, now);
262
274
  return {
263
275
  epochs: Object.fromEntries(request.fences.map((fence) => [fence, current[fence] ?? 0])),
276
+ freshUntil: Math.min(Number(entry.soft_expires_at), Number(entry.expires_at)),
264
277
  kind: "hit",
265
278
  refreshToken: refreshable ? writeLock(request.key, request.lockMs, now, "refresh") : null,
266
279
  value: String(entry.value)
@@ -331,6 +344,9 @@ var CacheCoordinator = class extends DurableObject {
331
344
  migrate(ctx.storage.sql, transact);
332
345
  this.core = createCoordinatorCore(ctx.storage.sql, () => crypto.randomUUID(), transact);
333
346
  }
347
+ inspectFences(request) {
348
+ return this.core.inspectFences(request, Date.now());
349
+ }
334
350
  read(request) {
335
351
  return this.core.read(request, Date.now());
336
352
  }
@@ -351,56 +367,408 @@ var CacheCoordinator = class extends DurableObject {
351
367
  }
352
368
  };
353
369
 
354
- // src/cloudflare/retry.ts
355
- var BASE_BACKOFF_MS = 50;
356
- var isRetryable = (error) => !!error && typeof error === "object" && Reflect.get(error, "retryable") === true && Reflect.get(error, "overloaded") !== true;
357
- async function retryTransient(call, attempts, sleep) {
358
- for (let attempt = 0; ; attempt++) {
370
+ // src/cloudflare/regional-store.ts
371
+ var states = /* @__PURE__ */ new Map();
372
+ var MAX_NAMESPACES = 4096;
373
+ var MAX_FILLS = 1024;
374
+ var MAX_REFRESHES = 1024;
375
+ var MAX_FAILED_ENDS = 1024;
376
+ var MAX_TOKEN_QUEUES = 1024;
377
+ var MAX_TOKEN_OPERATIONS = 1024;
378
+ var REPAIR_BATCH = 4;
379
+ var REPAIR_BACKOFF_MS = 1e3;
380
+ var fencesFor = (fences) => [...new Set(fences)].sort();
381
+ function stateFor(identity) {
382
+ const existing = states.get(identity);
383
+ if (existing) return existing;
384
+ if (states.size >= MAX_NAMESPACES) return void 0;
385
+ const state = {
386
+ revision: 0,
387
+ floor: -1,
388
+ pending: /* @__PURE__ */ new Set(),
389
+ tokenQueues: /* @__PURE__ */ new Map(),
390
+ invalidations: 0,
391
+ unsafe: false,
392
+ unsafeVersion: 0,
393
+ overflow: false,
394
+ failedEnds: /* @__PURE__ */ new Map(),
395
+ repairing: false,
396
+ nextRepairAt: 0,
397
+ fills: /* @__PURE__ */ new Map(),
398
+ refreshes: /* @__PURE__ */ new Set()
399
+ };
400
+ states.set(identity, state);
401
+ return state;
402
+ }
403
+ function createRegionalStore(store, options) {
404
+ const { namespace, generation, ttlMs } = options;
405
+ if (!namespace || !generation || namespace.length > 8192 || generation.length > 8192 || !Number.isFinite(ttlMs) || ttlMs <= 0 || ttlMs > 5e3 || new TextEncoder().encode(JSON.stringify([namespace, generation])).byteLength > 16384)
406
+ return store;
407
+ const state = stateFor(namespace);
408
+ if (!state) return store;
409
+ const now = options.now ?? Date.now;
410
+ const regional = createRegionalCache(5e3, void 0, {
411
+ cache: options.cache,
412
+ now
413
+ });
414
+ const renewalStore = boundedD1Store(store);
415
+ const inspectFences = store.inspectFences?.bind(store);
416
+ const enabled = options.enabled !== false;
417
+ const blocked = () => state.unsafe || state.overflow || state.invalidations > 0 || state.pending.size > 0 || state.tokenQueues.size > 0;
418
+ const markUnsafe = () => {
419
+ state.unsafe = true;
420
+ state.unsafeVersion++;
421
+ };
422
+ const deferRepair = () => {
423
+ const at = now();
424
+ if (!Number.isSafeInteger(at)) {
425
+ markUnsafe();
426
+ state.nextRepairAt = Infinity;
427
+ return;
428
+ }
429
+ state.nextRepairAt = Math.max(state.nextRepairAt, at + REPAIR_BACKOFF_MS);
430
+ };
431
+ const bump = () => {
432
+ state.revision++;
433
+ const at = now();
434
+ if (!Number.isSafeInteger(at)) markUnsafe();
435
+ else state.floor = Math.max(state.floor, at);
436
+ };
437
+ const serializeToken = (token, action) => {
438
+ let queue = state.tokenQueues.get(token);
439
+ if (!queue && state.tokenQueues.size >= MAX_TOKEN_QUEUES || queue && queue.count >= MAX_TOKEN_OPERATIONS) {
440
+ state.overflow = true;
441
+ bump();
442
+ return Promise.reject(
443
+ new Error("Regional write queue capacity exceeded")
444
+ );
445
+ }
446
+ if (!queue) {
447
+ queue = { tail: Promise.resolve(), count: 0 };
448
+ state.tokenQueues.set(token, queue);
449
+ }
450
+ const current = queue;
451
+ current.count++;
452
+ const result = current.tail.then(action).finally(() => {
453
+ current.count--;
454
+ if (current.count === 0 && state.tokenQueues.get(token) === current)
455
+ state.tokenQueues.delete(token);
456
+ });
457
+ current.tail = result.then(
458
+ () => void 0,
459
+ () => void 0
460
+ );
461
+ return result;
462
+ };
463
+ const keyFor = (key, fences) => regional.regionalKey(ttlMs, [
464
+ "cache-store-v2",
465
+ namespace,
466
+ generation,
467
+ key,
468
+ fencesFor(fences)
469
+ ]);
470
+ const clean = () => {
471
+ for (const [token, fill] of state.fills)
472
+ if (fill.expires <= now()) state.fills.delete(token);
473
+ };
474
+ const sameFences = (left, right) => left.length === right.length && left.every((value, i) => value === right[i]);
475
+ const remember = (token, request, epochs, revision, started) => {
476
+ if (!enabled || !token || request.generation !== generation || !Number.isFinite(request.lockMs) || request.lockMs <= 0 || !sameFences(fencesFor(request.fences), fencesFor(Object.keys(epochs))) || Object.values(epochs).some(
477
+ (epoch) => !Number.isSafeInteger(epoch) || epoch < 0
478
+ ) || blocked() || revision !== state.revision)
479
+ return;
480
+ clean();
481
+ if (state.fills.size >= MAX_FILLS && !state.fills.has(token)) return;
482
+ state.fills.set(token, {
483
+ key: request.key,
484
+ generation,
485
+ fences: fencesFor(request.fences),
486
+ epochs: Object.fromEntries(Object.entries(epochs)),
487
+ revision,
488
+ started,
489
+ expires: now() + request.lockMs
490
+ });
491
+ };
492
+ const payload = (value) => {
493
+ if (!value || typeof value !== "object" || !("value" in value) || typeof value.value !== "string" || !("sourceStarted" in value) || typeof value.sourceStarted !== "number" || !Number.isSafeInteger(value.sourceStarted) || value.sourceStarted < 0 || value.sourceStarted > now() || !("epochs" in value) || !value.epochs || typeof value.epochs !== "object" || Array.isArray(value.epochs))
494
+ return null;
495
+ const epochs = {};
496
+ for (const [name, epoch] of Object.entries(value.epochs)) {
497
+ if (!Number.isSafeInteger(epoch) || typeof epoch !== "number" || epoch < 0)
498
+ return null;
499
+ Object.defineProperty(epochs, name, {
500
+ value: epoch,
501
+ enumerable: true,
502
+ configurable: true
503
+ });
504
+ }
505
+ return { value: value.value, epochs, sourceStarted: value.sourceStarted };
506
+ };
507
+ const promote = async (revision, key, fences, started, lifetime, value) => {
508
+ if (!enabled || blocked() || revision !== state.revision || started <= state.floor || lifetime <= 0)
509
+ return;
510
+ const regionalKey = await keyFor(key, fences);
511
+ if (blocked() || revision !== state.revision || started <= state.floor)
512
+ return;
513
+ await regional.regionalWrite(
514
+ regionalKey,
515
+ Math.min(ttlMs, lifetime),
516
+ started,
517
+ { ...value, sourceStarted: started }
518
+ );
519
+ };
520
+ const refreshAhead = (regionalKey, request, revision) => {
521
+ if (!options.waitUntil || blocked() || revision !== state.revision || state.refreshes.has(regionalKey) || state.refreshes.size >= MAX_REFRESHES)
522
+ return;
523
+ const copy = { ...request, fences: [...request.fences], refresh: false };
524
+ state.refreshes.add(regionalKey);
525
+ let cancelled = false;
526
+ const task = Promise.resolve().then(async () => {
527
+ if (cancelled || blocked() || revision !== state.revision) return;
528
+ const started = now();
529
+ const result = await renewalStore.read(copy);
530
+ const token = result.kind === "miss" ? result.lockToken : result.refreshToken;
531
+ if (token) {
532
+ try {
533
+ await renewalStore.release({ key: copy.key, lockToken: token });
534
+ } catch {
535
+ }
536
+ return;
537
+ }
538
+ if (result.kind !== "hit" || typeof result.freshUntil !== "number" || !Number.isSafeInteger(result.freshUntil) || result.freshUntil <= started || !sameFences(
539
+ fencesFor(copy.fences),
540
+ fencesFor(Object.keys(result.epochs))
541
+ ) || Object.values(result.epochs).some(
542
+ (epoch) => !Number.isSafeInteger(epoch) || epoch < 0
543
+ ))
544
+ return;
545
+ await promote(
546
+ revision,
547
+ copy.key,
548
+ copy.fences,
549
+ started,
550
+ result.freshUntil - started,
551
+ { value: result.value, epochs: result.epochs }
552
+ );
553
+ }).catch(() => void 0).finally(() => {
554
+ state.refreshes.delete(regionalKey);
555
+ });
359
556
  try {
360
- return await call();
361
- } catch (error) {
362
- if (attempt + 1 >= attempts || !isRetryable(error)) throw error;
363
- await sleep(BASE_BACKOFF_MS * Math.random() * 2 ** attempt);
557
+ options.waitUntil(task);
558
+ } catch {
559
+ cancelled = true;
560
+ }
561
+ };
562
+ const repair = () => {
563
+ if (!options.waitUntil || state.repairing || now() < state.nextRepairAt || !state.unsafe && state.failedEnds.size === 0)
564
+ return;
565
+ state.repairing = true;
566
+ const failed = [...state.failedEnds.entries()].filter(([token]) => !state.tokenQueues.has(token)).slice(0, REPAIR_BATCH);
567
+ const unsafeVersion = state.unsafe ? state.unsafeVersion : null;
568
+ let cancelled = false;
569
+ const task = Promise.resolve().then(async () => {
570
+ if (cancelled) return;
571
+ let failedAttempt = false;
572
+ for (const [token, request] of failed) {
573
+ if (state.failedEnds.get(token) !== request) continue;
574
+ try {
575
+ if (state.tokenQueues.has(token)) continue;
576
+ await withinCoordinatorDeadline(
577
+ serializeToken(token, async () => {
578
+ if (state.failedEnds.get(token) !== request) return;
579
+ await store.endWrite({
580
+ token: request.token,
581
+ fences: [...request.fences]
582
+ });
583
+ if (state.failedEnds.get(token) === request) {
584
+ state.failedEnds.delete(token);
585
+ state.pending.delete(token);
586
+ bump();
587
+ }
588
+ })
589
+ );
590
+ } catch {
591
+ failedAttempt = true;
592
+ if (state.failedEnds.get(token) === request) {
593
+ state.failedEnds.delete(token);
594
+ state.failedEnds.set(token, request);
595
+ }
596
+ }
597
+ }
598
+ if (unsafeVersion !== null) {
599
+ try {
600
+ await withinCoordinatorDeadline(store.invalidate([GLOBAL_FENCE]));
601
+ if (state.unsafeVersion === unsafeVersion) {
602
+ state.unsafe = false;
603
+ bump();
604
+ }
605
+ } catch {
606
+ failedAttempt = true;
607
+ }
608
+ }
609
+ if (failedAttempt) deferRepair();
610
+ }).catch(() => {
611
+ deferRepair();
612
+ }).finally(() => {
613
+ state.repairing = false;
614
+ });
615
+ try {
616
+ options.waitUntil(task);
617
+ } catch {
618
+ cancelled = true;
619
+ deferRepair();
364
620
  }
365
- }
366
- }
367
-
368
- // src/cloudflare/store-options.ts
369
- var WRITE_PATH_ATTEMPTS = 3;
370
- var MAX_COORDINATOR_NAME_LENGTH = 256;
371
- var LOCATION_HINTS = ["wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me"];
372
- var isLocationHint = (value) => LOCATION_HINTS.some((hint) => hint === value);
373
- function resolveStoreOptions(options) {
374
- const { attempts = WRITE_PATH_ATTEMPTS, locationHint, name } = options;
375
- const problems = [
376
- !(typeof name === "string" && name.length > 0 && name.length <= MAX_COORDINATOR_NAME_LENGTH) && `name must be 1..${MAX_COORDINATOR_NAME_LENGTH} characters and identify one D1 database`,
377
- !(Number.isInteger(attempts) && attempts >= 1 && attempts <= 10) && "attempts must be an integer in 1..10",
378
- locationHint !== void 0 && !isLocationHint(locationHint) && `locationHint must be one of ${LOCATION_HINTS.join(", ")}`
379
- ].filter((problem) => typeof problem === "string");
380
- if (problems.length > 0) throw new TypeError(`cache store: ${problems.join("; ")}`);
381
- return {
382
- attempts,
383
- name,
384
- sleep: options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
385
- ...locationHint ? { locationHint } : {}
386
621
  };
387
- }
388
-
389
- // src/cloudflare/store.ts
390
- function createDurableObjectStore(namespace, options) {
391
- const { attempts, locationHint, name, sleep } = resolveStoreOptions(options);
392
- const stub = () => namespace.get(namespace.idFromName(name), locationHint ? { locationHint } : void 0);
393
- const withRetry = (call) => retryTransient(() => call(stub()), attempts, sleep);
394
622
  return {
395
- beginWrite: (request) => withRetry(async (coordinator) => void await coordinator.beginWrite(request)),
396
- endWrite: (request) => withRetry(async (coordinator) => void await coordinator.endWrite(request)),
397
- invalidate: (fences) => withRetry(async (coordinator) => void await coordinator.invalidate(fences)),
398
- read: async (request) => stub().read(request),
399
- release: async (request) => void await stub().release(request),
400
- write: async (request) => stub().write(request)
623
+ ...inspectFences ? { inspectFences } : {},
624
+ beginWrite(request) {
625
+ const attempted = { ...request, fences: [...request.fences] };
626
+ if (state.failedEnds.has(attempted.token)) state.overflow = true;
627
+ return serializeToken(attempted.token, async () => {
628
+ const existed = state.pending.has(attempted.token);
629
+ if (state.failedEnds.has(attempted.token)) state.overflow = true;
630
+ state.pending.add(attempted.token);
631
+ bump();
632
+ try {
633
+ await store.beginWrite(attempted);
634
+ } catch (error) {
635
+ if (!existed) state.pending.delete(attempted.token);
636
+ bump();
637
+ throw error;
638
+ }
639
+ });
640
+ },
641
+ endWrite(request) {
642
+ const attempted = { token: request.token, fences: [...request.fences] };
643
+ return serializeToken(attempted.token, async () => {
644
+ const priorFailure = state.failedEnds.get(attempted.token);
645
+ state.pending.add(attempted.token);
646
+ state.invalidations++;
647
+ bump();
648
+ try {
649
+ await store.endWrite({
650
+ token: attempted.token,
651
+ fences: [...attempted.fences]
652
+ });
653
+ if (state.failedEnds.get(attempted.token) === priorFailure && (!priorFailure || sameFences(
654
+ fencesFor(priorFailure.fences),
655
+ fencesFor(attempted.fences)
656
+ ))) {
657
+ state.failedEnds.delete(attempted.token);
658
+ state.pending.delete(attempted.token);
659
+ }
660
+ } catch (error) {
661
+ const previous = state.failedEnds.get(attempted.token);
662
+ if (previous && !sameFences(fencesFor(previous.fences), fencesFor(attempted.fences)))
663
+ state.overflow = true;
664
+ else if (previous || state.failedEnds.size < MAX_FAILED_ENDS)
665
+ state.failedEnds.set(attempted.token, attempted);
666
+ else state.overflow = true;
667
+ deferRepair();
668
+ throw error;
669
+ } finally {
670
+ state.invalidations--;
671
+ bump();
672
+ }
673
+ });
674
+ },
675
+ async invalidate(fences) {
676
+ state.invalidations++;
677
+ bump();
678
+ try {
679
+ await store.invalidate(fences);
680
+ } catch (error) {
681
+ markUnsafe();
682
+ deferRepair();
683
+ throw error;
684
+ } finally {
685
+ state.invalidations--;
686
+ bump();
687
+ }
688
+ },
689
+ async read(request) {
690
+ if (blocked()) repair();
691
+ const started = now();
692
+ const revision = state.revision;
693
+ if (enabled && request.generation === generation && !blocked()) {
694
+ const key = await keyFor(request.key, request.fences);
695
+ const hit = await regional.regionalRead(key, ttlMs);
696
+ const cached = hit && payload(hit.value);
697
+ if (hit && cached && revision === state.revision && !blocked() && cached.sourceStarted > state.floor && hit.expires > now() && hit.expires > cached.sourceStarted && hit.expires <= cached.sourceStarted + ttlMs && sameFences(
698
+ fencesFor(request.fences),
699
+ fencesFor(Object.keys(cached.epochs))
700
+ )) {
701
+ if (key && hit.expires - now() <= ttlMs / 2)
702
+ refreshAhead(key, request, revision);
703
+ return {
704
+ kind: "hit",
705
+ value: cached.value,
706
+ epochs: cached.epochs,
707
+ refreshToken: null,
708
+ freshUntil: hit.expires
709
+ };
710
+ }
711
+ }
712
+ const result = await store.read(request);
713
+ if (result.kind === "miss")
714
+ remember(
715
+ result.pending ? null : result.lockToken,
716
+ request,
717
+ result.epochs,
718
+ revision,
719
+ started
720
+ );
721
+ else {
722
+ remember(
723
+ result.refreshToken,
724
+ request,
725
+ result.epochs,
726
+ revision,
727
+ started
728
+ );
729
+ if (request.generation === generation && result.refreshToken === null && typeof result.freshUntil === "number" && Number.isSafeInteger(result.freshUntil) && result.freshUntil > started && sameFences(
730
+ fencesFor(request.fences),
731
+ fencesFor(Object.keys(result.epochs))
732
+ )) {
733
+ await promote(
734
+ revision,
735
+ request.key,
736
+ request.fences,
737
+ started,
738
+ result.freshUntil - started,
739
+ { value: result.value, epochs: result.epochs }
740
+ );
741
+ }
742
+ }
743
+ return result;
744
+ },
745
+ async release(request) {
746
+ state.fills.delete(request.lockToken);
747
+ await store.release(request);
748
+ },
749
+ async write(request) {
750
+ const started = now();
751
+ clean();
752
+ const fill = state.fills.get(request.lockToken);
753
+ state.fills.delete(request.lockToken);
754
+ const result = await store.write(request);
755
+ if (result.ok && fill && fill.key === request.key && fill.generation === generation && fill.expires > started && Object.keys(request.snapshot).length > 0 && Object.entries(request.snapshot).every(
756
+ ([fence, epoch]) => Object.hasOwn(fill.epochs, fence) && fill.epochs[fence] === epoch
757
+ )) {
758
+ await promote(
759
+ fill.revision,
760
+ request.key,
761
+ fill.fences,
762
+ Math.min(started, fill.started),
763
+ Math.min(ttlMs, request.ttlMs, request.softTtlMs),
764
+ { value: request.value, epochs: fill.epochs }
765
+ );
766
+ }
767
+ return result;
768
+ }
401
769
  };
402
770
  }
403
771
 
404
- export { CacheCoordinator, createDurableObjectStore };
772
+ export { CacheCoordinator, createRegionalStore };
405
773
  //# sourceMappingURL=index.js.map
406
774
  //# sourceMappingURL=index.js.map