cursedbelt-server 4.32.1 → 4.33.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.
@@ -38,6 +38,18 @@ export interface BinaryStoreConfig {
38
38
  absenceTtlMs?: number;
39
39
  /** Injected so the memo's expiry is testable without a real clock. */
40
40
  now?: () => number;
41
+ /**
42
+ * 🔴 How THIS PROCESS sends every request to binary-server. Default: the global `fetch`.
43
+ *
44
+ * A Cloudflare Worker on the SAME ZONE as binary-server must pass its service binding to
45
+ * `binary-server-edge-gate` here (`(input, init) => env.BINARY_SERVER.fetch(input, init)`).
46
+ * A same-zone subrequest skips every other Worker's route and goes straight to the zone's
47
+ * ORIGIN — the byte tunnel into the owner's Mac — so a global `fetch` never reached the edge
48
+ * Worker, its R2 reads or its cache. Measured 2026-09-25: the owner powered the Mac off and
49
+ * every face on family's tree was gone, although family's bytes rest in R2. Auth met the same
50
+ * trap first (task 2150); this is the byte half of it.
51
+ */
52
+ fetch?: (input: string | Request, init?: RequestInit) => Promise<Response>;
41
53
  }
42
54
  /**
43
55
  * Which keys this process has read from the wire far too often, worst first —
@@ -260,6 +260,7 @@ export function createBinaryStore(cfg) {
260
260
  * and is named as the remaining gap.
261
261
  */
262
262
  const internalBaseUrl = (cfg.internalBaseUrl ?? cfg.baseUrl).replace(/\/+$/, '');
263
+ const send = cfg.fetch ?? ((input, init) => fetch(input, init));
263
264
  const autoChunkBytes = cfg.autoChunkBytes ?? AUTO_CHUNK_THRESHOLD_BYTES;
264
265
  const now = cfg.now ?? Date.now;
265
266
  const absenceTtlMs = cfg.absenceTtlMs ?? BINARY_ABSENCE_TTL_MS;
@@ -425,7 +426,7 @@ export function createBinaryStore(cfg) {
425
426
  const title = opts?.title?.trim();
426
427
  const titleQs = title ? `&title=${encodeURIComponent(title)}` : '';
427
428
  const immutableQs = opts?.immutable ? '&immutable=1' : '';
428
- const res = await fetch(`${baseUrl}/upload/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`, {
429
+ const res = await send(`${baseUrl}/upload/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`, {
429
430
  method: 'PUT',
430
431
  headers: mime ? { 'content-type': mime } : undefined,
431
432
  body: bytes,
@@ -494,7 +495,7 @@ export function createBinaryStore(cfg) {
494
495
  const slice = new Uint8Array(await blob
495
496
  .slice(ci * chunkBytes, Math.min((ci + 1) * chunkBytes, blob.size))
496
497
  .arrayBuffer());
497
- const res = await fetch(`${baseUrl}/upload-chunk/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`, {
498
+ const res = await send(`${baseUrl}/upload-chunk/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`, {
498
499
  method: 'PUT',
499
500
  headers: mime ? { 'content-type': mime } : undefined,
500
501
  body: slice,
@@ -513,7 +514,7 @@ export function createBinaryStore(cfg) {
513
514
  if (!opts?.fresh && knownAbsent(key))
514
515
  return null;
515
516
  countWireRead(key);
516
- const res = await fetch(await internalMediaUrl(key));
517
+ const res = await send(await internalMediaUrl(key));
517
518
  if (res.status === 404) {
518
519
  rememberAbsent(key);
519
520
  return null;
@@ -572,7 +573,7 @@ export function createBinaryStore(cfg) {
572
573
  // of the URL, so a fresh one per request would defeat every cache between here and
573
574
  // the object — see MediaUrlOptions.stableWindowSeconds.
574
575
  const url = await internalMediaUrl(key, { stableWindowSeconds: 60 * 60 });
575
- return await fetch(url, {
576
+ return await send(url, {
576
577
  method: init?.method ?? 'GET',
577
578
  // 🔴 Only when the caller HAS one. `Range: undefined` is fine, but a literal
578
579
  // empty string is a malformed header that some origins answer 416 to.
@@ -589,7 +590,7 @@ export function createBinaryStore(cfg) {
589
590
  return null;
590
591
  }
591
592
  countWireRead(key);
592
- const res = await fetch(await metaUrl(key));
593
+ const res = await send(await metaUrl(key));
593
594
  if (res.status === 404) {
594
595
  rememberAbsent(key);
595
596
  return null;
@@ -633,7 +634,7 @@ export function createBinaryStore(cfg) {
633
634
  // A transport/5xx failure — the probe below is the second opinion.
634
635
  }
635
636
  countWireRead(key);
636
- const res = await fetch(await internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } });
637
+ const res = await send(await internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } });
637
638
  if (res.status === 404) {
638
639
  rememberAbsent(key);
639
640
  return null;
@@ -658,7 +659,7 @@ export function createBinaryStore(cfg) {
658
659
  // (owner ruling #10). `internalHeaders()` throws first if no secret is configured.
659
660
  const headers = internalHeaders();
660
661
  const token = await signToken({ k: appKey(key), op: 'delete' }, DELETE_TOKEN_TTL_SECONDS);
661
- const res = await fetch(`${baseUrl}/media/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}`, { method: 'DELETE', headers });
662
+ const res = await send(`${baseUrl}/media/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}`, { method: 'DELETE', headers });
662
663
  if (!res.ok && res.status !== 404) {
663
664
  throw new Error(`[binary-store] remove ${key}: ${res.status}`);
664
665
  }
@@ -671,7 +672,7 @@ export function createBinaryStore(cfg) {
671
672
  // and the `?container=1` flag. Same two-barrier gate as remove().
672
673
  const headers = internalHeaders();
673
674
  const token = await signToken({ k: appKey(prefix), op: 'delete' }, DELETE_TOKEN_TTL_SECONDS);
674
- const res = await fetch(`${baseUrl}/media/${encodeKeyPath(appKey(prefix))}?container=1&token=${encodeURIComponent(token)}`, { method: 'DELETE', headers });
675
+ const res = await send(`${baseUrl}/media/${encodeKeyPath(appKey(prefix))}?container=1&token=${encodeURIComponent(token)}`, { method: 'DELETE', headers });
675
676
  if (!res.ok && res.status !== 404) {
676
677
  throw new Error(`[binary-store] removePrefix ${prefix}: ${res.status}`);
677
678
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-server",
3
- "version": "4.32.1",
3
+ "version": "4.33.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
@@ -380,7 +380,7 @@
380
380
  "@node-rs/argon2": "^2.0.2",
381
381
  "@types/bun": "^1.3.14",
382
382
  "@types/node": "^24",
383
- "cursedbelt": "^5.7.2",
383
+ "cursedbelt": "^5.9.0",
384
384
  "hono": "4.12.28",
385
385
  "kysely": "^0.28.17",
386
386
  "kysely-bun-sqlite": "^0.4.0",
@@ -673,6 +673,42 @@ describe('internalBaseUrl — reading bytes without leaving the box', () => {
673
673
  });
674
674
  });
675
675
 
676
+ describe('🔴 fetch — a same-zone Worker reads through its service binding, never the tunnel', () => {
677
+ it('sends EVERY method through the injected fetch and none through the global one', async () => {
678
+ // 2026-09-25: family's Worker read portraits with the global fetch, which on the
679
+ // cursedalchemy.com zone skips the edge Worker and lands on the Mac's byte tunnel —
680
+ // the owner turned the Mac off and every face on the tree went with it.
681
+ const bound: string[] = [];
682
+ const store = makeStore({
683
+ internalSecret: 's',
684
+ fetch: (input, init) => {
685
+ bound.push(`${init?.method ?? (input instanceof Request ? input.method : 'GET')} ${typeof input === 'string' ? input : input.url}`);
686
+ const url = typeof input === 'string' ? input : input.url;
687
+ if (url.includes('/meta/')) return Promise.resolve(json({ size: 1 }));
688
+ return Promise.resolve(ok(new Uint8Array([1]), { headers: { 'content-range': 'bytes 0-0/1' } }));
689
+ },
690
+ });
691
+ await store.put('a', new Uint8Array([1]));
692
+ await store.get('b');
693
+ await store.meta('c');
694
+ await store.stat('d');
695
+ await store.has('e');
696
+ await store.fetchMedia('f');
697
+ await store.remove('g');
698
+ await store.removePrefix('h/');
699
+ expect(calls).toEqual([]);
700
+ expect(bound.length).toBeGreaterThanOrEqual(8);
701
+ for (const line of bound) expect(line).toContain(BASE);
702
+ });
703
+
704
+ it('defaults to the global fetch when nothing is injected (a Bun host on the Mac)', async () => {
705
+ const store = makeStore();
706
+ reply = () => ok(new Uint8Array([1]));
707
+ await store.get('b');
708
+ expect(calls).toHaveLength(1);
709
+ });
710
+ });
711
+
676
712
  describe('fetchMedia', () => {
677
713
  it('forwards a Range header untouched — Range IS seeking', async () => {
678
714
  const store = makeStore({ internalBaseUrl: INTERNAL });
@@ -83,6 +83,18 @@ export interface BinaryStoreConfig {
83
83
  absenceTtlMs?: number;
84
84
  /** Injected so the memo's expiry is testable without a real clock. */
85
85
  now?: () => number;
86
+ /**
87
+ * 🔴 How THIS PROCESS sends every request to binary-server. Default: the global `fetch`.
88
+ *
89
+ * A Cloudflare Worker on the SAME ZONE as binary-server must pass its service binding to
90
+ * `binary-server-edge-gate` here (`(input, init) => env.BINARY_SERVER.fetch(input, init)`).
91
+ * A same-zone subrequest skips every other Worker's route and goes straight to the zone's
92
+ * ORIGIN — the byte tunnel into the owner's Mac — so a global `fetch` never reached the edge
93
+ * Worker, its R2 reads or its cache. Measured 2026-09-25: the owner powered the Mac off and
94
+ * every face on family's tree was gone, although family's bytes rest in R2. Auth met the same
95
+ * trap first (task 2150); this is the byte half of it.
96
+ */
97
+ fetch?: (input: string | Request, init?: RequestInit) => Promise<Response>;
86
98
  }
87
99
 
88
100
  /*
@@ -511,6 +523,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
511
523
  * and is named as the remaining gap.
512
524
  */
513
525
  const internalBaseUrl = (cfg.internalBaseUrl ?? cfg.baseUrl).replace(/\/+$/, '');
526
+ const send = cfg.fetch ?? ((input: string | Request, init?: RequestInit) => fetch(input, init));
514
527
  const autoChunkBytes = cfg.autoChunkBytes ?? AUTO_CHUNK_THRESHOLD_BYTES;
515
528
  const now = cfg.now ?? Date.now;
516
529
  const absenceTtlMs = cfg.absenceTtlMs ?? BINARY_ABSENCE_TTL_MS;
@@ -705,7 +718,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
705
718
  const title = opts?.title?.trim();
706
719
  const titleQs = title ? `&title=${encodeURIComponent(title)}` : '';
707
720
  const immutableQs = opts?.immutable ? '&immutable=1' : '';
708
- const res = await fetch(
721
+ const res = await send(
709
722
  `${baseUrl}/upload/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`,
710
723
  {
711
724
  method: 'PUT',
@@ -785,7 +798,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
785
798
  .slice(ci * chunkBytes, Math.min((ci + 1) * chunkBytes, blob.size))
786
799
  .arrayBuffer(),
787
800
  );
788
- const res = await fetch(
801
+ const res = await send(
789
802
  `${baseUrl}/upload-chunk/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}${titleQs}${immutableQs}`,
790
803
  {
791
804
  method: 'PUT',
@@ -807,7 +820,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
807
820
  // short-circuit here — we want the bytes, and only the store has them.
808
821
  if (!opts?.fresh && knownAbsent(key)) return null;
809
822
  countWireRead(key);
810
- const res = await fetch(await internalMediaUrl(key));
823
+ const res = await send(await internalMediaUrl(key));
811
824
  if (res.status === 404) {
812
825
  rememberAbsent(key);
813
826
  return null;
@@ -868,7 +881,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
868
881
  // of the URL, so a fresh one per request would defeat every cache between here and
869
882
  // the object — see MediaUrlOptions.stableWindowSeconds.
870
883
  const url = await internalMediaUrl(key, { stableWindowSeconds: 60 * 60 });
871
- return await fetch(url, {
884
+ return await send(url, {
872
885
  method: init?.method ?? 'GET',
873
886
  // 🔴 Only when the caller HAS one. `Range: undefined` is fine, but a literal
874
887
  // empty string is a malformed header that some origins answer 416 to.
@@ -884,7 +897,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
884
897
  if (knownAbsent(key)) return null;
885
898
  }
886
899
  countWireRead(key);
887
- const res = await fetch(await metaUrl(key));
900
+ const res = await send(await metaUrl(key));
888
901
  if (res.status === 404) {
889
902
  rememberAbsent(key);
890
903
  return null;
@@ -925,7 +938,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
925
938
  // A transport/5xx failure — the probe below is the second opinion.
926
939
  }
927
940
  countWireRead(key);
928
- const res = await fetch(await internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } });
941
+ const res = await send(await internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } });
929
942
  if (res.status === 404) {
930
943
  rememberAbsent(key);
931
944
  return null;
@@ -950,7 +963,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
950
963
  // (owner ruling #10). `internalHeaders()` throws first if no secret is configured.
951
964
  const headers = internalHeaders();
952
965
  const token = await signToken({ k: appKey(key), op: 'delete' }, DELETE_TOKEN_TTL_SECONDS);
953
- const res = await fetch(
966
+ const res = await send(
954
967
  `${baseUrl}/media/${encodeKeyPath(appKey(key))}?token=${encodeURIComponent(token)}`,
955
968
  { method: 'DELETE', headers },
956
969
  );
@@ -967,7 +980,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
967
980
  // and the `?container=1` flag. Same two-barrier gate as remove().
968
981
  const headers = internalHeaders();
969
982
  const token = await signToken({ k: appKey(prefix), op: 'delete' }, DELETE_TOKEN_TTL_SECONDS);
970
- const res = await fetch(
983
+ const res = await send(
971
984
  `${baseUrl}/media/${encodeKeyPath(appKey(prefix))}?container=1&token=${encodeURIComponent(token)}`,
972
985
  { method: 'DELETE', headers },
973
986
  );