cursedbelt-server 4.9.0 → 4.11.1

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.
@@ -48,7 +48,29 @@ export declare const WEB_ANALYTICS_SITE_TAG = "d1bea8562544421fbd2db5f11b74d1cc"
48
48
  /** The `cursedalchemy.com` site's `data-cf-beacon` token. This is what the tag carries. */
49
49
  export declare const WEB_ANALYTICS_SITE_TOKEN = "bd88d63678b64bc89f9702f48846bc88";
50
50
  /**
51
- * The snippet, exactly as Cloudflare's own `site_info` endpoint spells it.
51
+ * 🔴 The `version` the beacon's config must carry, or NOTHING is recorded.
52
+ *
53
+ * Measured 2026-09-22, after the RUM API showed every manual-snippet shell recording ZERO
54
+ * pageviews from 2026-09-19 on — collections, music, roms, flix, station and desk, which had
55
+ * been 130–290 a day between them — while the hosts still on the edge's injection kept
56
+ * recording. The beacon copies this value into its payload as `versions.fl`, and
57
+ * `cloudflareinsights.com/cdn-cgi/rum` answers a payload without `fl` with a **404 that carries
58
+ * no CORS header**, which a browser prints as a CORS refusal and nobody reads as "recorded
59
+ * nothing". Same token, same src, driven in a real browser from a `*.cursedalchemy.com` origin:
60
+ * `{"token": …}` → 404 every time; `{"version": "2024.11.0", "token": …}` → 204 every time,
61
+ * whether the script path was pinned or not. Any value was accepted; this one is what the
62
+ * edge's own injection carries, so it is the value Cloudflare is known to send.
63
+ *
64
+ * Cloudflare's own `site_info` snippet OMITS it, so "exactly the vendor's text" was the
65
+ * defect, not the safety it looked like. `tools/check-web-analytics.ts` reds a hand copy
66
+ * without it.
67
+ */
68
+ export declare const WEB_ANALYTICS_BEACON_VERSION = "2024.11.0";
69
+ /** The `data-cf-beacon` value — one spelling for the tag, the bootstrap and every hand copy. */
70
+ export declare function webAnalyticsBeaconConfig(token?: string): string;
71
+ /**
72
+ * The snippet as Cloudflare's own `site_info` endpoint spells it — plus the `version` key it
73
+ * leaves out, without which nothing is recorded (see {@link WEB_ANALYTICS_BEACON_VERSION}).
52
74
  *
53
75
  * 🔴 The single-quoted attributes and the space after `"token":` are Cloudflare's, not a
54
76
  * style choice, and they are kept because this string is COMPARED against what an app's
@@ -104,7 +126,14 @@ export declare function webAnalyticsBootstrap(token?: string): string;
104
126
  *
105
127
  * Deliberately NOT a substring test against {@link WEB_ANALYTICS_TAG}: a shell is authored by
106
128
  * hand and a formatter is allowed to re-wrap the tag across lines or re-order its attributes.
107
- * What must be true is that the beacon's source is there AND that it is carrying the right
108
- * token — the two halves that decide whether a pageview is recorded at all.
129
+ * What must be true is that the beacon's source is there, that it is carrying the right
130
+ * token, and that its config carries a `version` — the three things that decide whether a
131
+ * pageview is recorded at all. The third was missed until 2026-09-22 and cost four days of
132
+ * every manual shell's data; see {@link WEB_ANALYTICS_BEACON_VERSION}.
109
133
  */
110
134
  export declare function htmlCarriesWebAnalytics(html: string, token?: string): boolean;
135
+ /**
136
+ * Every `data-cf-beacon` config in `html` names a `version` — and there is at least one.
137
+ * Read up to the config's closing brace, so key order and a formatter's spacing do not matter.
138
+ */
139
+ export declare function beaconConfigsCarryVersion(html: string): boolean;
@@ -48,7 +48,31 @@ export const WEB_ANALYTICS_SITE_TAG = "d1bea8562544421fbd2db5f11b74d1cc";
48
48
  /** The `cursedalchemy.com` site's `data-cf-beacon` token. This is what the tag carries. */
49
49
  export const WEB_ANALYTICS_SITE_TOKEN = "bd88d63678b64bc89f9702f48846bc88";
50
50
  /**
51
- * The snippet, exactly as Cloudflare's own `site_info` endpoint spells it.
51
+ * 🔴 The `version` the beacon's config must carry, or NOTHING is recorded.
52
+ *
53
+ * Measured 2026-09-22, after the RUM API showed every manual-snippet shell recording ZERO
54
+ * pageviews from 2026-09-19 on — collections, music, roms, flix, station and desk, which had
55
+ * been 130–290 a day between them — while the hosts still on the edge's injection kept
56
+ * recording. The beacon copies this value into its payload as `versions.fl`, and
57
+ * `cloudflareinsights.com/cdn-cgi/rum` answers a payload without `fl` with a **404 that carries
58
+ * no CORS header**, which a browser prints as a CORS refusal and nobody reads as "recorded
59
+ * nothing". Same token, same src, driven in a real browser from a `*.cursedalchemy.com` origin:
60
+ * `{"token": …}` → 404 every time; `{"version": "2024.11.0", "token": …}` → 204 every time,
61
+ * whether the script path was pinned or not. Any value was accepted; this one is what the
62
+ * edge's own injection carries, so it is the value Cloudflare is known to send.
63
+ *
64
+ * Cloudflare's own `site_info` snippet OMITS it, so "exactly the vendor's text" was the
65
+ * defect, not the safety it looked like. `tools/check-web-analytics.ts` reds a hand copy
66
+ * without it.
67
+ */
68
+ export const WEB_ANALYTICS_BEACON_VERSION = "2024.11.0";
69
+ /** The `data-cf-beacon` value — one spelling for the tag, the bootstrap and every hand copy. */
70
+ export function webAnalyticsBeaconConfig(token = WEB_ANALYTICS_SITE_TOKEN) {
71
+ return `{"version": "${WEB_ANALYTICS_BEACON_VERSION}", "token": "${token}"}`;
72
+ }
73
+ /**
74
+ * The snippet as Cloudflare's own `site_info` endpoint spells it — plus the `version` key it
75
+ * leaves out, without which nothing is recorded (see {@link WEB_ANALYTICS_BEACON_VERSION}).
52
76
  *
53
77
  * 🔴 The single-quoted attributes and the space after `"token":` are Cloudflare's, not a
54
78
  * style choice, and they are kept because this string is COMPARED against what an app's
@@ -62,7 +86,7 @@ export const WEB_ANALYTICS_SITE_TOKEN = "bd88d63678b64bc89f9702f48846bc88";
62
86
  export function webAnalyticsTag(token = WEB_ANALYTICS_SITE_TOKEN) {
63
87
  return ("<!-- Cloudflare Web Analytics -->" +
64
88
  `<script type='module' src='${WEB_ANALYTICS_BEACON_SRC}' ` +
65
- `data-cf-beacon='{"token": "${token}"}'></script>` +
89
+ `data-cf-beacon='${webAnalyticsBeaconConfig(token)}'></script>` +
66
90
  "<!-- End Cloudflare Web Analytics -->");
67
91
  }
68
92
  /** The fleet's tag, for the one site this generation serves. */
@@ -111,7 +135,7 @@ export function webAnalyticsBootstrap(token = WEB_ANALYTICS_SITE_TOKEN) {
111
135
  `\t\tvar beacon = document.createElement("script");`,
112
136
  `\t\tbeacon.type = "module";`,
113
137
  `\t\tbeacon.src = "${WEB_ANALYTICS_BEACON_SRC}";`,
114
- `\t\tbeacon.setAttribute("data-cf-beacon", '{"token": "${token}"}');`,
138
+ `\t\tbeacon.setAttribute("data-cf-beacon", '${webAnalyticsBeaconConfig(token)}');`,
115
139
  `\t\tdocument.head.appendChild(beacon);`,
116
140
  "\t}",
117
141
  "</script>",
@@ -122,9 +146,19 @@ export function webAnalyticsBootstrap(token = WEB_ANALYTICS_SITE_TOKEN) {
122
146
  *
123
147
  * Deliberately NOT a substring test against {@link WEB_ANALYTICS_TAG}: a shell is authored by
124
148
  * hand and a formatter is allowed to re-wrap the tag across lines or re-order its attributes.
125
- * What must be true is that the beacon's source is there AND that it is carrying the right
126
- * token — the two halves that decide whether a pageview is recorded at all.
149
+ * What must be true is that the beacon's source is there, that it is carrying the right
150
+ * token, and that its config carries a `version` — the three things that decide whether a
151
+ * pageview is recorded at all. The third was missed until 2026-09-22 and cost four days of
152
+ * every manual shell's data; see {@link WEB_ANALYTICS_BEACON_VERSION}.
127
153
  */
128
154
  export function htmlCarriesWebAnalytics(html, token = WEB_ANALYTICS_SITE_TOKEN) {
129
- return html.includes(WEB_ANALYTICS_BEACON_SRC) && html.includes(token);
155
+ return html.includes(WEB_ANALYTICS_BEACON_SRC) && html.includes(token) && beaconConfigsCarryVersion(html);
156
+ }
157
+ /**
158
+ * Every `data-cf-beacon` config in `html` names a `version` — and there is at least one.
159
+ * Read up to the config's closing brace, so key order and a formatter's spacing do not matter.
160
+ */
161
+ export function beaconConfigsCarryVersion(html) {
162
+ const configs = [...html.matchAll(/data-cf-beacon[^{]{0,24}(\{[^}]*\})/g)].map((m) => m[1] ?? "");
163
+ return configs.length > 0 && configs.every((c) => /"version"\s*:\s*"[^"]+"/.test(c));
130
164
  }
@@ -40,6 +40,10 @@ export interface CpuClock {
40
40
  * That is why {@link runCpuBench} drives its cases SERIALLY — a serial driver is what
41
41
  * makes this proxy sound enough to gate on. Mounted in production it is advisory: useful
42
42
  * for ranking routes, not for a billing claim.
43
+ *
44
+ * Where the runtime has no such reading, this returns {@link unavailableCpuClock} — a
45
+ * clock, never a throw. {@link probeProcessCpuUsage} is what decides, and it decides by
46
+ * CALLING.
43
47
  */
44
48
  export declare function processCpuClock(): CpuClock;
45
49
  /**
@@ -26,11 +26,15 @@
26
26
  * That is why {@link runCpuBench} drives its cases SERIALLY — a serial driver is what
27
27
  * makes this proxy sound enough to gate on. Mounted in production it is advisory: useful
28
28
  * for ranking routes, not for a billing claim.
29
+ *
30
+ * Where the runtime has no such reading, this returns {@link unavailableCpuClock} — a
31
+ * clock, never a throw. {@link probeProcessCpuUsage} is what decides, and it decides by
32
+ * CALLING.
29
33
  */
30
34
  export function processCpuClock() {
31
- const usable = typeof process !== 'undefined' && typeof process.cpuUsage === 'function';
32
- if (!usable)
33
- return unavailableCpuClock('process.cpuUsage (absent)');
35
+ const probe = probeProcessCpuUsage();
36
+ if (!probe.ok)
37
+ return unavailableCpuClock(`process.cpuUsage (${probe.why})`);
34
38
  return {
35
39
  source: 'process.cpuUsage',
36
40
  proxy: true,
@@ -44,6 +48,56 @@ export function processCpuClock() {
44
48
  },
45
49
  };
46
50
  }
51
+ /**
52
+ * 🔴 **Is `process.cpuUsage()` a clock here? Answered by CALLING it, never by `typeof`.**
53
+ *
54
+ * Measured 2026-09-19 on the first real deploy of a Worker mounting `cpuBudget()`: under
55
+ * `nodejs_compat`, workerd's strategy for the parts of `node:process` it does not implement
56
+ * is to PROVIDE the method and throw when it is called —
57
+ *
58
+ * ```
59
+ * Error [ERR_METHOD_NOT_IMPLEMENTED]: The process.cpuUsage method is not implemented
60
+ * at process.cpuUsage (node-internal:public_process:235:11)
61
+ * ```
62
+ *
63
+ * — so `typeof process.cpuUsage === 'function'` answers *yes* about the one runtime the
64
+ * probe exists to answer *no* about. The clock that came back threw on first use, and
65
+ * because `cpuBudget()` is mounted FIRST by design, the throw landed in front of the whole
66
+ * app: every path 500, with 843 green tests behind it, because `bun test` runs where the
67
+ * method works.
68
+ *
69
+ * This is a capability CLASS, not one method: `nodejs_compat` stubs a great deal of
70
+ * `node:process`, `node:os` and `node:v8` the same way. `src/nodeBuiltinsAreProbedByCalling.spec.ts`
71
+ * is the check that keeps a `typeof` gate from coming back anywhere in this library.
72
+ *
73
+ * Both call shapes are exercised, because the span uses both and only one of them is
74
+ * probed by the first: `cpuUsage()` for the baseline and `cpuUsage(before)` for the delta.
75
+ * That costs two calls at wiring time — the same trade {@link workerCpuClock} argues for on
76
+ * the other side, and the alternative is a gate that reads zero for ever.
77
+ */
78
+ function probeProcessCpuUsage() {
79
+ // Present-but-throwing and absent are both unavailable; the distinction only LABELS the
80
+ // clock, and is never the decision — which is why this line may carry the excuse that
81
+ // `src/nodeBuiltinsAreProbedByCalling.spec.ts` refuses everywhere else.
82
+ // probe-by-calling:ignore — label only; the call below is what decides.
83
+ const present = typeof process !== 'undefined' && typeof process.cpuUsage === 'function';
84
+ let reading;
85
+ try {
86
+ // A bare `process` that does not exist throws a ReferenceError in here, which is the
87
+ // same answer as a method that throws: not a clock.
88
+ const before = process.cpuUsage();
89
+ const delta = process.cpuUsage(before);
90
+ reading = before.user + before.system + delta.user + delta.system;
91
+ }
92
+ catch {
93
+ return { ok: false, why: present ? 'throws' : 'absent' };
94
+ }
95
+ // A stub that returns something non-numeric is no more usable than one that throws, and
96
+ // would otherwise reach the recorder as a confident 0.00 for every route.
97
+ if (!Number.isFinite(reading))
98
+ return { ok: false, why: 'non-numeric' };
99
+ return { ok: true };
100
+ }
47
101
  /**
48
102
  * A clock over a CPU-ms reader the runtime supplies. `proxy: false` — this is the real
49
103
  * quantity Cloudflare bills, so a report built on it may be quoted as one.
@@ -31,6 +31,33 @@ export interface BenchCase {
31
31
  * that never ran — a green budget over a broken route.
32
32
  */
33
33
  expectStatus?: number | number[] | ((status: number) => boolean);
34
+ /**
35
+ * Called before EVERY iteration of this case — warm-up included. For a **destructive**
36
+ * route, rebuild the fixture here; without it the second call measures an empty database.
37
+ *
38
+ * 🔴 This is the hook a destructive route cannot be benched without. `runCpuBench` fires a
39
+ * case `warmup + iterations` times against ONE app instance, which is exactly right for a
40
+ * read and a lie for a purge: the first call runs against a populated library and the next
41
+ * twenty-nine against an empty one, so the p99 is a small number for the wrong reason and
42
+ * the budget it satisfies means nothing. `expectStatus` cannot catch it either — the second
43
+ * call legitimately answers 200 with `{ deleted: 0 }`.
44
+ *
45
+ * It is **per case**, not run-wide, so the cheap read cases pay nothing for it — neither
46
+ * the rebuild nor the accounting {@link runCpuBench} does around it.
47
+ *
48
+ * 🔴 **Its cost is not measured, and that is checked rather than assumed.** The recorder is
49
+ * written through by the `cpuBudget()` middleware inside `app.fetch`, so a hook that runs
50
+ * outside that call is already excluded by ordering — but ordering is an accident, and a
51
+ * fixture rebuild that cost 200 ms and got attributed to the route would make this feature
52
+ * a worse lie than the hole it closes. So {@link runCpuBench} refuses a hook that drives
53
+ * the app under bench (see the error it throws), and `cpuBudget.spec.ts` proves a hook that
54
+ * burns real CPU leaves the route's number alone.
55
+ *
56
+ * `iteration` is 0-based and monotonic across the WHOLE case, warm-up first: warm-up gets
57
+ * `0 … warmup-1` and the measured pass continues from `warmup`. A hook that seeds ids from
58
+ * it therefore never collides between the two passes.
59
+ */
60
+ beforeEach?: (c: BenchCase, iteration: number) => void | Promise<void>;
34
61
  }
35
62
  export interface RunCpuBenchOpts {
36
63
  /** Anything with Hono's `fetch` shape. */
@@ -25,6 +25,39 @@ function statusAllowed(c, status) {
25
25
  function describe(c) {
26
26
  return `${(c.method ?? 'GET').toUpperCase()} ${c.path}`;
27
27
  }
28
+ /**
29
+ * Every request the recorder has attributed to any route so far. `samples` counts what was
30
+ * SEEN rather than what the reservoir retained, so this only ever goes up within a phase.
31
+ */
32
+ function totalSamples(recorder) {
33
+ let n = 0;
34
+ for (const r of recorder.report().routes)
35
+ n += r.samples;
36
+ return n;
37
+ }
38
+ /**
39
+ * Run a case's {@link BenchCase.beforeEach}, and prove it stayed out of the histogram.
40
+ *
41
+ * 🔴 The obvious way to rebuild a fixture is to drive the app that already has the routes —
42
+ * `POST /api/upload` thirty times to re-seed a library. Those requests go through
43
+ * `cpuBudget()` like any other, so the rebuild lands in the report as real samples on real
44
+ * routes, and the destructive route this hook exists to measure honestly is now benched
45
+ * beside rows nobody asked for. Refusing it here is cheap and only costs a case that has a
46
+ * hook at all.
47
+ */
48
+ async function prepare(opts, c, iteration) {
49
+ if (!c.beforeEach)
50
+ return;
51
+ const before = totalSamples(opts.recorder);
52
+ await c.beforeEach(c, iteration);
53
+ const after = totalSamples(opts.recorder);
54
+ if (after !== before) {
55
+ throw new Error(`cpu-bench: ${describe(c)}'s beforeEach drove ${after - before} request(s) through the ` +
56
+ 'app under bench, so the fixture rebuild is now IN the measurement it exists to keep ' +
57
+ 'out of it. Rebuild the fixture directly — against the database, the store, the ' +
58
+ 'seed helper — never through app.fetch.');
59
+ }
60
+ }
28
61
  async function fire(opts, c) {
29
62
  const method = (c.method ?? c.init?.method ?? 'GET').toUpperCase();
30
63
  const req = new Request(`${opts.origin ?? BENCH_ORIGIN}${c.path}`, { ...c.init, method });
@@ -46,16 +79,23 @@ export async function runCpuBench(opts) {
46
79
  const warmup = opts.warmup ?? 5;
47
80
  if (!(iterations > 0))
48
81
  throw new Error('runCpuBench: iterations must be > 0');
82
+ // `beforeEach` runs over the warm-up too, deliberately. Warm-up exists to reach a handler's
83
+ // steady state, and a destructive route warmed against the empty database it emptied warms
84
+ // the wrong branch — the loop the p99 is about never runs.
49
85
  for (const c of opts.cases) {
50
- for (let i = 0; i < warmup; i += 1)
86
+ for (let i = 0; i < warmup; i += 1) {
87
+ await prepare(opts, c, i);
51
88
  await fire(opts, c);
89
+ }
52
90
  }
53
91
  // 🔴 Everything above was warm-up. Discard it — measuring it is the bug this guards.
54
92
  opts.recorder.reset();
55
93
  for (const c of opts.cases) {
56
94
  const n = c.iterations ?? iterations;
57
- for (let i = 0; i < n; i += 1)
95
+ for (let i = 0; i < n; i += 1) {
96
+ await prepare(opts, c, warmup + i);
58
97
  await fire(opts, c);
98
+ }
59
99
  }
60
100
  return opts.recorder.report();
61
101
  }
@@ -15,7 +15,7 @@
15
15
  * ```
16
16
  */
17
17
  export { type BackupPoint, createTimeTravelBackup, type DatabaseBackup, type TimeTravelOpts, } from './backup';
18
- export { type InvocationD1, perInvocation } from './invocation';
18
+ export { createPendingWrites, type InvocationD1, type PendingWrites, perInvocation, type WriteCollector } from './invocation';
19
19
  export { assertBatchSize, assertWithinLimits, chunkForBind, LIMITS } from './limits';
20
20
  export { createLocalD1, refuseInteractiveTransaction } from './local';
21
21
  export { createRemoteD1, type D1BindingLike, type D1BindingMeta, type D1BindingResult, type D1BindingStatement, } from './remote';
@@ -33,7 +33,7 @@ export { createTimeTravelBackup, } from './backup';
33
33
  // that does not use the query builder — which per `../db/kysely.ts`'s own header is most
34
34
  // of them, since business tables stay on raw statements. Measured 2026-09-18 against the
35
35
  // published 4.3.0 tarball. `barrelsReachNoOptionalPeer.spec.ts` is what keeps it out.
36
- export { perInvocation } from './invocation';
36
+ export { createPendingWrites, perInvocation } from './invocation';
37
37
  export { assertBatchSize, assertWithinLimits, chunkForBind, LIMITS } from './limits';
38
38
  export { createLocalD1, refuseInteractiveTransaction } from './local';
39
39
  export { createRemoteD1, } from './remote';
@@ -39,6 +39,14 @@
39
39
  * It wraps either driver, because it sits ABOVE the seam and only counts. That is what
40
40
  * makes the local gate able to prove a production-only limit: wrap a local database in a
41
41
  * test, run the loop, and the laptop fails exactly where the Worker would.
42
+ *
43
+ * ## The other thing that is true for the span of one request: {@link createPendingWrites}
44
+ *
45
+ * A Worker port's synchronous stores answer from a snapshot and queue their durable writes
46
+ * as promises, which the entry point awaits BEFORE the response leaves. That collector has
47
+ * the same lifetime as the budget above — one per invocation, constructed by the caller in
48
+ * the same breath — so it lives in the same file. See its own header for why it was moved
49
+ * here on 2026-09-19.
42
50
  */
43
51
  import { type D1LikeDatabase } from './types';
44
52
  /**
@@ -70,3 +78,67 @@ export interface InvocationD1 extends D1LikeDatabase {
70
78
  export declare function perInvocation(db: D1LikeDatabase, opts?: {
71
79
  max?: number;
72
80
  }): InvocationD1;
81
+ /**
82
+ * Where a durable write goes on its way out of one invocation.
83
+ *
84
+ * Deliberately two members, and the narrower `add`-only half is what a STORE takes. A store
85
+ * that also held `settle` could end the request's writes from inside the request, which is
86
+ * the entry point's job and nobody else's. `cursedauth/d1-stores` declares exactly this
87
+ * two-method shape under its own name (`WriteCollector`) because that package sits UNDER
88
+ * this one and may not import it — structural typing means an object made here satisfies it
89
+ * verbatim, with no dependency edge in either direction.
90
+ */
91
+ export interface WriteCollector {
92
+ add(promise: Promise<unknown>): void;
93
+ }
94
+ /** A write that must land before the response is returned. See {@link createPendingWrites}. */
95
+ export interface PendingWrites extends WriteCollector {
96
+ /** Await every collected write. Called BEFORE the response is returned, never after. */
97
+ settle(): Promise<void>;
98
+ }
99
+ /**
100
+ * Collect the durable writes of ONE invocation, to be awaited before the response leaves.
101
+ *
102
+ * ## Why this is the Worker-write primitive and not a convenience
103
+ *
104
+ * A ported app's synchronous stores cannot await — their signatures are somebody else's
105
+ * published contract — so the shape every port lands on is: one read fills a snapshot, every
106
+ * sync call answers from it, and every mutation both updates the snapshot and pushes its
107
+ * statement's promise in here. The entry point then does the awaiting:
108
+ *
109
+ * ```ts
110
+ * export default {
111
+ * async fetch(req: Request, env: Env) {
112
+ * const db = perInvocation(createRemoteD1(env.DB));
113
+ * const writes = createPendingWrites();
114
+ * const res = await app.fetch(req, { db, writes });
115
+ * await writes.settle(); // 🔴 BEFORE the response, never after
116
+ * return res;
117
+ * },
118
+ * };
119
+ * ```
120
+ *
121
+ * 🔴 **`settle()` before the response, never `ctx.waitUntil`.** A Worker isolate is per-colo
122
+ * and short-lived; a write acknowledged by a 200 and then dropped means "sign out" reported
123
+ * success and left the credential live — a failure invisible to any single-process test and
124
+ * indistinguishable, to the person, from the feature being broken.
125
+ *
126
+ * 🔴 **`Promise.all`, not `allSettled`.** A failed durable write must not be reported as a
127
+ * success. It surfaces as a 500 from the entry point, which is the honest answer to "did my
128
+ * sign-out take". `allSettled` here would convert every lost write into a silent 200 — the
129
+ * same outcome as not collecting them at all, reached by a road that looks careful.
130
+ *
131
+ * ## Why it lives here
132
+ *
133
+ * It was written three times before it was published once — `apps/patterns/worker/stores.ts`,
134
+ * `apps/collections/worker/stores.ts` and `libs/cursedauth/src/d1Stores.ts`, byte-identical
135
+ * but for a comment, with six more ports queued behind them. It is a D1-INVOCATION primitive
136
+ * rather than an auth one, so it belongs beside {@link perInvocation}: both are constructed
137
+ * per request, both describe what is true for the span of one, and neither knows anything
138
+ * about who is signed in.
139
+ *
140
+ * Ordering is NOT what this gives you. Everything added is already in flight, so two writes
141
+ * that must land in order go in one `batch()`, never in two `add`s — a DELETE that must
142
+ * precede its INSERT will otherwise race and trip a unique index.
143
+ */
144
+ export declare function createPendingWrites(): PendingWrites;
@@ -39,6 +39,14 @@
39
39
  * It wraps either driver, because it sits ABOVE the seam and only counts. That is what
40
40
  * makes the local gate able to prove a production-only limit: wrap a local database in a
41
41
  * test, run the loop, and the laptop fails exactly where the Worker would.
42
+ *
43
+ * ## The other thing that is true for the span of one request: {@link createPendingWrites}
44
+ *
45
+ * A Worker port's synchronous stores answer from a snapshot and queue their durable writes
46
+ * as promises, which the entry point awaits BEFORE the response leaves. That collector has
47
+ * the same lifetime as the budget above — one per invocation, constructed by the caller in
48
+ * the same breath — so it lives in the same file. See its own header for why it was moved
49
+ * here on 2026-09-19.
42
50
  */
43
51
  import { LIMITS } from './limits';
44
52
  import { D1LimitError } from './types';
@@ -164,3 +172,60 @@ export function perInvocation(db, opts = {}) {
164
172
  },
165
173
  };
166
174
  }
175
+ /**
176
+ * Collect the durable writes of ONE invocation, to be awaited before the response leaves.
177
+ *
178
+ * ## Why this is the Worker-write primitive and not a convenience
179
+ *
180
+ * A ported app's synchronous stores cannot await — their signatures are somebody else's
181
+ * published contract — so the shape every port lands on is: one read fills a snapshot, every
182
+ * sync call answers from it, and every mutation both updates the snapshot and pushes its
183
+ * statement's promise in here. The entry point then does the awaiting:
184
+ *
185
+ * ```ts
186
+ * export default {
187
+ * async fetch(req: Request, env: Env) {
188
+ * const db = perInvocation(createRemoteD1(env.DB));
189
+ * const writes = createPendingWrites();
190
+ * const res = await app.fetch(req, { db, writes });
191
+ * await writes.settle(); // 🔴 BEFORE the response, never after
192
+ * return res;
193
+ * },
194
+ * };
195
+ * ```
196
+ *
197
+ * 🔴 **`settle()` before the response, never `ctx.waitUntil`.** A Worker isolate is per-colo
198
+ * and short-lived; a write acknowledged by a 200 and then dropped means "sign out" reported
199
+ * success and left the credential live — a failure invisible to any single-process test and
200
+ * indistinguishable, to the person, from the feature being broken.
201
+ *
202
+ * 🔴 **`Promise.all`, not `allSettled`.** A failed durable write must not be reported as a
203
+ * success. It surfaces as a 500 from the entry point, which is the honest answer to "did my
204
+ * sign-out take". `allSettled` here would convert every lost write into a silent 200 — the
205
+ * same outcome as not collecting them at all, reached by a road that looks careful.
206
+ *
207
+ * ## Why it lives here
208
+ *
209
+ * It was written three times before it was published once — `apps/patterns/worker/stores.ts`,
210
+ * `apps/collections/worker/stores.ts` and `libs/cursedauth/src/d1Stores.ts`, byte-identical
211
+ * but for a comment, with six more ports queued behind them. It is a D1-INVOCATION primitive
212
+ * rather than an auth one, so it belongs beside {@link perInvocation}: both are constructed
213
+ * per request, both describe what is true for the span of one, and neither knows anything
214
+ * about who is signed in.
215
+ *
216
+ * Ordering is NOT what this gives you. Everything added is already in flight, so two writes
217
+ * that must land in order go in one `batch()`, never in two `add`s — a DELETE that must
218
+ * precede its INSERT will otherwise race and trip a unique index.
219
+ */
220
+ export function createPendingWrites() {
221
+ const pending = [];
222
+ return {
223
+ add: (promise) => void pending.push(promise),
224
+ async settle() {
225
+ // `all`, not `allSettled`: a failed durable write must not be reported as a
226
+ // success. It surfaces as a 500 from the entry point, which is the honest answer
227
+ // to "did my sign-out take".
228
+ await Promise.all(pending);
229
+ },
230
+ };
231
+ }
@@ -51,9 +51,14 @@ export interface MasterLockGuardOptions extends LockPageOptions {
51
51
  * loosening it: nothing loads from anywhere but this origin, the form posts nowhere (the
52
52
  * script does the POST), and the page cannot be framed.
53
53
  *
54
- * 🔴 **EXPORTED so that nothing has to copy it.** `apps/collections` had a hand-typed
55
- * duplicate of this string in a test until 2026-09-18, which is a policy that can drift
56
- * without anything going red. It is also what
54
+ * 🔴 **EXPORTED so that nothing has to copy it** — and exporting it is not the same as nothing
55
+ * copying it. This sentence used to read *"`apps/collections` had a hand-typed duplicate … until
56
+ * 2026-09-18"*, and it was false the day it was written: the export landed, and BOTH re-typed
57
+ * copies stayed — one in `apps/collections/scripts/injectedScripts.test.ts` and one three
58
+ * directories from here in `../analytics/injectedScripts.spec.ts`. They were collapsed onto this
59
+ * constant on 2026-09-21. A policy that is typed twice can be loosened in one place and stay
60
+ * green in the other, which is a wall that no longer refuses what its own test says it does.
61
+ * It is also what
57
62
  * `beaconNeverReachesTheWall.spec.ts` measures the beacon against: the wall refuses
58
63
  * `static.cloudflareinsights.com`, so the analytics tag and this response are mutually
59
64
  * exclusive BY CONSTRUCTION rather than by anyone remembering. The owner's reason, 2026-09-18:
@@ -20,9 +20,14 @@ const noStore = { "cache-control": "no-store, no-cache, must-revalidate", pragma
20
20
  * loosening it: nothing loads from anywhere but this origin, the form posts nowhere (the
21
21
  * script does the POST), and the page cannot be framed.
22
22
  *
23
- * 🔴 **EXPORTED so that nothing has to copy it.** `apps/collections` had a hand-typed
24
- * duplicate of this string in a test until 2026-09-18, which is a policy that can drift
25
- * without anything going red. It is also what
23
+ * 🔴 **EXPORTED so that nothing has to copy it** — and exporting it is not the same as nothing
24
+ * copying it. This sentence used to read *"`apps/collections` had a hand-typed duplicate … until
25
+ * 2026-09-18"*, and it was false the day it was written: the export landed, and BOTH re-typed
26
+ * copies stayed — one in `apps/collections/scripts/injectedScripts.test.ts` and one three
27
+ * directories from here in `../analytics/injectedScripts.spec.ts`. They were collapsed onto this
28
+ * constant on 2026-09-21. A policy that is typed twice can be loosened in one place and stay
29
+ * green in the other, which is a wall that no longer refuses what its own test says it does.
30
+ * It is also what
26
31
  * `beaconNeverReachesTheWall.spec.ts` measures the beacon against: the wall refuses
27
32
  * `static.cloudflareinsights.com`, so the analytics tag and this response are mutually
28
33
  * exclusive BY CONSTRUCTION rather than by anyone remembering. The owner's reason, 2026-09-18:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-server",
3
- "version": "4.9.0",
3
+ "version": "4.11.1",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The app-facing Bun/Hono server tier of the cursedbelt split \u2014 storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",