diff --git a/.github/workflows/d1-stress.yml b/.github/workflows/d1-stress.yml new file mode 100644 index 00000000..443be835 --- /dev/null +++ b/.github/workflows/d1-stress.yml @@ -0,0 +1,73 @@ +name: D1 stress + +# #190: runs the Miniflare halves (D1, Durable Object SQLite, R2) of every package that tests with +# the pinned Miniflare, many times on fresh runners, under the same load as CI +# (`turbo --concurrency=2`). Run it by hand after a Miniflare bump or a change to its patch +# (ADR-0012, addendum 2026-11-10); pull requests that touch the patch run it on their own. + +on: + workflow_dispatch: + inputs: + runs: + description: Runs per Node version + type: number + default: 10 + hogs: + description: Busy loops beside the tests (0 = CI's own load) + type: number + default: 4 + pull_request: + paths: + - patches/miniflare@*.patch + - .github/workflows/d1-stress.yml + +permissions: + contents: read + +concurrency: + group: d1-stress-${{ github.ref }} + cancel-in-progress: true + +jobs: + plan: + runs-on: ubuntu-latest + outputs: + runs: ${{ steps.runs.outputs.runs }} + steps: + - id: runs + env: + RUNS: ${{ inputs.runs || 10 }} + run: | + echo "runs=$(jq -nc --argjson n "$RUNS" '[range(1; ([$n, 50] | min) + 1)]')" >> "$GITHUB_OUTPUT" + + d1: + name: D1 halves (Node ${{ matrix.node }}, run ${{ matrix.run }}) + needs: plan + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + node: [24, 26] + run: ${{ fromJSON(needs.plan.outputs.runs) }} + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.node }} + cache: pnpm + - run: pnpm install --frozen-lockfile + # CI's test step (`turbo --concurrency=2`), plus one busy loop per vCPU: the proxy race needs a + # preempted thread, and an oversubscribed runner preempts far more often than a quiet one. + # `-t` keeps only the suites labelled "(Miniflare)". + - name: Miniflare halves on an oversubscribed runner + env: + HOGS: ${{ inputs.hogs || 4 }} + run: | + for i in $(seq "$HOGS"); do sh -c 'while :; do :; done' & done + trap 'kill $(jobs -p) 2>/dev/null' EXIT + pnpm turbo run test --concurrency=2 \ + --filter=@cloudhal/storage --filter=@cloudhal/core-ts --filter=@cloudhal/api \ + --filter=@cloudhal/schedules --filter=@cloudhal/comments --filter=@cloudhal/analytics \ + -- -t '\(Miniflare\)' diff --git a/AGENTS.md b/AGENTS.md index eb10029d..653be5ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ pnpm i18n:export / pnpm i18n:import [--locale xx] # translations as nested ICU - `packages/api` — Hono app (`createApi`), Better Auth (`src/auth.ts`), error mapping; shared suite `src/testing.ts` on sqlite + D1. Build chat is `build-turn.ts`, its model eval `build-eval.ts`; `design-pass.ts` is RFC-0021 21c (Build chat looks at its render, the eval's vision-judged score). - `packages/ui` — `export * from "@cloudflare/kumo"` + blocks; `styles.css` is the only Tailwind entry for admin UIs. Heavy kits have lazy subpaths: `./charts` (ECharts), `./editor` (the Tiptap markdown body editor, RFC-0014). - `packages/messages` — RFC-0032 / ADR-0032: the ICU MessageFormat 1 subset of Studio's catalogs (`plural`, `select`, `selectordinal`, `number`). `./compile` (build-time only: the Vite plugin `./vite`, the type generator, tests; `bundle-budget.ts` keeps it out of every chunk) and `./format` (Worker-safe runtime, cached `Intl.PluralRules`/`NumberFormat`), which is also Studio's `Intl` layer: `formatNumber`, `formatDate`, `formatRelative`, `formatList` and cached `numberFormat`/`dateTimeFormat`/`relativeTimeFormat`/`displayNames`/`collator` — screens and blocks never build `Intl` formatters themselves (lint `i18n-format`). Catalogs compile to data, never code. -- `patches/` — `pnpm patch` files (`patchedDependencies` in `pnpm-workspace.yaml`). `@cloudflare__kumo@2.14.0.patch` is **generated** by `node scripts/kumo-patch.ts`: the hand-written changes in `patches/kumo/*.patch` (`tooltip-timestamp.patch` adds `TimeseriesChart`'s `tooltipTimestampFormat`, DESIGN.md §2v "Kumo locale") plus every physical utility in Kumo's `dist` made logical (ADR-0033 A1). Never edit it; on a Kumo upgrade rename it and the `patchedDependencies` entry, re-run the script, `pnpm install`, re-check the gaps in `docs/design/kumo-upstream-notes.md`. `packages/ui/test/kumo-rtl.test.tsx` fails when the installed Kumo is not patched. +- `patches/` — `pnpm patch` files (`patchedDependencies` in `pnpm-workspace.yaml`). `@cloudflare__kumo@2.14.0.patch` is **generated** by `node scripts/kumo-patch.ts`: the hand-written changes in `patches/kumo/*.patch` (`tooltip-timestamp.patch` adds `TimeseriesChart`'s `tooltipTimestampFormat`, DESIGN.md §2v "Kumo locale") plus every physical utility in Kumo's `dist` made logical (ADR-0033 A1). Never edit it; on a Kumo upgrade rename it and the `patchedDependencies` entry, re-run the script, `pnpm install`, re-check the gaps in `docs/design/kumo-upstream-notes.md`. `packages/ui/test/kumo-rtl.test.tsx` fails when the installed Kumo is not patched. `miniflare@4.20260730.0.patch` backports Miniflare 5's sync binding-proxy fix (workers-sdk#15552) that the D1 harness needs (#190, ADR-0012); `packages/storage/test/miniflare-patch.test.ts` fails without it. - `apps/studio` — SPA; routes in `src/router.tsx`, screens in `src/routes`, typed API client `src/lib/api.ts` (a barrel over `src/lib/api/.ts`), i18n in `src/i18n` (messages are ICU MF1, compiled at build time, with parameter types generated into `src/i18n/generated/params.ts` by `pnpm gen` — never edit it; languages registered in `locales.ts`; add keys to `en.ts` and every `.ts`, or to a lazy namespace `src/i18n/lazy/..ts` (the list and how to add one: `src/i18n/namespaces.ts`; DESIGN.md §1); only the active language of the core catalogs loads, `tr.ts` through `loadLocale`, and each other language's lazy-namespace loaders come with it from the generated `src/i18n/loaders/.ts`; Arabic and Persian format with Western digits through the registry's `intl` tag). - `docs/adr/` decisions (with sources) · `docs/rfcs/` proposals · `docs/design/DESIGN.md` the binding admin design system (Kumo). - `.agents/skills/` — skills for you: `contract-change`, `add-capability`, `write-rfc`, `studio-ui`, `translate`, and Cloudflare's official **`kumo-design`** (installed from `cloudflare/kumo`, tracked in `skills-lock.json`; update with `npx skills update`). Any admin-UI change loads `kumo-design` + `studio-ui` and follows `docs/design/DESIGN.md`. @@ -59,7 +59,7 @@ pnpm i18n:export / pnpm i18n:import [--locale xx] # translations as nested ICU 13. **Regexes over external input** (request paths and bodies, agent or editor markdown, uploads, fetched feeds) must be linear (#168, #174): no unanchored or end-anchored quantified runs (`/-+$/`, `/\s+$/`, `/\/+$/`), no lazy scans that rescan to the end when the closer is missing (`/[\s\S]*?<\/x>/`, `/<[^>]*>/`), no adjacent quantifiers over overlapping classes (`\s+[^:]+`, `\s*(.*)`). Use `trimChar`/`trimEndChar` (`@cloudhal/core-ts/names`), `trimEnd()`, `indexOf`/`lastIndexOf` scanners, and check lengths before patterns. A rewrite gets an equivalence test against the old regex and a 100k-character adversarial case. ## Versions (pinned via pnpm catalog / Cargo workspace) -Node 24 · pnpm 12 · TypeScript 7.0 · Biome 2.6 · Vitest 5 · Turborepo 2.11 · Rust 2024 edition · json-schema-to-typescript 16.0.0 · cargo-typify 0.8.0 · ajv 8.20 · jsonschema 0.60 · better-sqlite3 13 · miniflare 4.20260730 (last stable 4.x; its sharp overridden to 0.35.5; see ADR-0012) · wrangler 4.158 · @astrojs/cloudflare 14.4 · Astro 7.4 · Tailwind 4.3 · marked 18 · Hono 4.13 · Better Auth 1.7.12 · Kumo 2.14 · React 19.3 · TanStack Router 1.170 / Query 5.104 · Vite 8.3. +Node 24 · pnpm 12 · TypeScript 7.0 · Biome 2.6 · Vitest 5 · Turborepo 2.11 · Rust 2024 edition · json-schema-to-typescript 16.0.0 · cargo-typify 0.8.0 · ajv 8.20 · jsonschema 0.60 · better-sqlite3 13 · miniflare 4.20260730 (last stable 4.x, patched for #190; its sharp overridden to 0.35.5; see ADR-0012) · wrangler 4.158 · @astrojs/cloudflare 14.4 · Astro 7.4 · Tailwind 4.3 · marked 18 · Hono 4.13 · Better Auth 1.7.12 · Kumo 2.14 · React 19.3 · TanStack Router 1.170 / Query 5.104 · Vite 8.3. ## When unsure Read `docs/adr/README.md` and the relevant ADR; each ADR links its primary sources. If the change alters a contract or a public interface, write an RFC first (`.agents/skills/write-rfc`). diff --git a/docs/adr/0012-cloudflare-deployment.md b/docs/adr/0012-cloudflare-deployment.md index a3bfe8d7..7f854d2d 100644 --- a/docs/adr/0012-cloudflare-deployment.md +++ b/docs/adr/0012-cloudflare-deployment.md @@ -53,3 +53,15 @@ Decision 6 pins the direct `miniflare` devDependency (the D1, R2 and Durable Obj - **undici.** The undici versions Miniflare pins (7.28.0 in 4.20260730.0, 7.29.1 in the 5.x alpha) are accepted for local development and tests: Miniflare uses undici to reach its own workerd on localhost and, outside tests, to fetch Cloudflare's `cf.json` from workers.cloudflare.com, a trusted endpoint (set `CLOUDFLARE_CF_FETCH_ENABLED=false` to turn that off). The fixed 7.30.1 is a minor move inside `dispatchFetch`, which the D1 harness and Miniflare's binding proxy depend on (the Durable Object harness bypasses it since #116), so an override waits for its own serial test run. *Update 2026-11-08:* that run happened. #159 (Dependabot alerts #5–#32) added the override `"undici@<7.30.1": 7.30.2` to `pnpm-workspace.yaml`, so Miniflare's pinned 7.28.0 resolves to 7.30.2 in monorepo installs; the D1 and Durable Object harnesses passed on it. Like the sharp override it is not published, and it goes when Miniflare 4 is no longer pinned. Sources: https://github.com/advisories/GHSA-wq5f-xc86-pv6w · https://github.com/advisories/GHSA-rgj7-g3m4-5g8c · https://pnpm.io/settings#overrides · `npm view miniflare dist-tags`, `npm view sharp time`, `npm view wrangler time` (checked 2026-10-25) · Miniflare 4.20260730.0 `dist/src/index.js` (`imagesLocalFetcher`, `cfImageLocalFetcher`, `setupCf`). + +## Addendum (2026-11-10): patching the pinned Miniflare's binding proxy (#190) + +The D1 suites failed now and then on CI, never on sqlite or Durable Object SQLite, and passed on rerun. Both failures in #190 were the same assertion, `message?.id === id` at `miniflare/src/plugins/core/proxy/fetch-sync.ts:147`, reached from `D1Database#prepare`/`bind`, which Miniflare's binding proxy runs as synchronous calls. The "statement budget" test in `core-ts` was not a counting or timing failure: its stack ends in the same assertion. + +- **Cause.** `SynchronousFetcher` blocks the caller on a shared 0/1 flag: the worker thread posts the reply, stores 1 and notifies. If the worker is preempted between its store and its notify while the caller has already read the reply (the flag was 1 when it reached `Atomics.wait`), the late notify wakes the *next* call before its reply exists. `receiveMessageOnPort` returns `undefined`, the assert throws, and every later call reads the previous call's reply, which is the cascade of 1–3 ms failures in `roles.test.ts`. A loaded runner makes the preemption likely: `turbo --concurrency=2` runs two packages of up to three Vitest workers each (two in `@cloudhal/api`), and every D1 or Durable Object file runs its own workerd process plus the proxy's worker thread, on four vCPUs. The race reproduces on the real Miniflare once both preemptions are forced (see the test below), and in a standalone model of the handshake (the shipped protocol desyncs at call 3, the fixed one runs 5,000 calls). It does not show up on demand without them: 20 unpatched runs of the D1 halves passed on CI's own load, and 20 more with one busy loop per vCPU. Upstream: cloudflare/workers-sdk#15552, merged 2026-09-16 into Miniflare 5 only. +- **Decision.** There is no newer Miniflare 4.x (4.20260730.0 is still `latest`; 5.x is beta), so the pin stays and `patches/miniflare@4.20260730.0.patch` backports #15552. It also closes the synchronous proxy's idle sockets after 1 s instead of undici's 4 s, well before workerd's 5 s idle timeout (#16445, part A). That pool lives on the proxy's own worker thread, which only waits for requests, so its timers fire even while a test blocks the main thread. The patch is applied through `patchedDependencies` in `pnpm-workspace.yaml`, so it covers monorepo installs only. Projects that use `@cloudhal/storage/testing` with their own Miniflare 4 keep the bug unless they apply the same patch. `packages/storage/test/miniflare-patch.test.ts` fails if the patch is missing: a `node -r` preload (Node loads it in worker threads too) stalls the proxy thread between its store and its notify and the caller before its wait, on every other call, and 300 `prepare`/`bind` calls must stay in step. Unpatched, the first stalled call fails the CI assertion; patched, all pass. No retries were added and D1 files still run in parallel. +- **On a Miniflare upgrade.** The patch is hand-written against `dist/src/index.js` (no generator, unlike Kumo's). If the new release carries #15552, delete the patch and its `patchedDependencies` entry. Otherwise `pnpm patch miniflare@`, re-apply the two changes in the edit directory (in `SynchronousFetcher`'s worker script: store `(id + 1) | 0` instead of 1 and set `keepAliveTimeout`/`keepAliveMaxTimeout` to 1000 on its `Pool`; in `fetch()`: drop the reset to 0 and loop `Atomics.wait` until the generation is seen), `pnpm patch-commit`, rename the `patchedDependencies` entry, and run `packages/storage/test/miniflare-patch.test.ts`, which fails until the patch applies. +- **Not taken: keep-alive `dispatchFetch`.** Every asynchronous proxy call (`first`, `all`, `run`, `batch`) goes through `dispatchFetch`, which sets undici's `reset`: one connection and one port in `TIME_WAIT` per query (2,000 queries left 2,002 `TIME_WAIT` sockets on macOS, where they last 30 s). On a Mac a single `@cloudhal/core-ts` run fills the whole ephemeral range (16,340 of 16,384 ports in `TIME_WAIT`, also at half the workers) and fails with `EADDRNOTAVAIL`; Linux CI has not shown it. cloudflare/workers-sdk#15781 keeps those connections alive in Miniflare 5, and it was backported here first: core-ts then passed locally, but one `roles.test.ts` run in 20 failed with `ECONNRESET` on two kept-alive sockets in a row, which cannot be retried safely because a write may have run. A main-thread stall past workerd's 5 s idle timeout reproduces that error (the client's idle timers cannot fire during it), but a normal run stalls at most about 0.5 s, so the cause is not settled. The backport was dropped from #190, which is about CI; the local port shortage is older than this change and is a follow-up. +- **Not patched:** #16445 part B (a `FREE` racing an in-flight `batch([prepare()])`). It needs a garbage collection during a workerd stall of over 100 ms, and it has not been seen in CI. When Miniflare 5 is stable, remove the patch together with the sharp and undici overrides. + +Sources: https://github.com/cloudflare/workers-sdk/pull/15552 · https://github.com/cloudflare/workers-sdk/pull/15781 · https://github.com/cloudflare/workers-sdk/issues/16445 · `npm view miniflare dist-tags` (checked 2026-11-10) · Miniflare 4.20260730.0 and 5.20261006.0-alpha `dist/src/index.js` (`SynchronousFetcher`, `DispatchFetchDispatcher`) · CI runs 42789152142 and 42793831919 (first attempts). diff --git a/packages/storage/test/fixtures/preempt-sync-proxy.cjs b/packages/storage/test/fixtures/preempt-sync-proxy.cjs new file mode 100644 index 00000000..dde16b19 --- /dev/null +++ b/packages/storage/test/fixtures/preempt-sync-proxy.cjs @@ -0,0 +1,26 @@ +// Preloaded with `node -r` (#190, test/miniflare-patch.test.ts). Node loads `-r` modules in worker +// threads too, so this reaches Miniflare's synchronous binding proxy thread. On every other call +// it stalls the caller between posting its request and waiting, and the proxy thread between +// storing its "done" flag and notifying: the two preemptions behind cloudflare/workers-sdk#15552. +const { isMainThread, MessagePort } = require("node:worker_threads"); + +const spin = (ms) => { + const end = process.hrtime.bigint() + BigInt(ms * 1_000_000); + while (process.hrtime.bigint() < end); +}; + +if (isMainThread) { + const post = MessagePort.prototype.postMessage; + MessagePort.prototype.postMessage = function (message, ...rest) { + const result = post.call(this, message, ...rest); + if (process.env.PREEMPT && typeof message?.id === "number" && message.id % 2 === 0) spin(3); + return result; + }; +} else { + const notify = Atomics.notify; + let calls = 0; + Atomics.notify = (...args) => { + if (calls++ % 2 === 0) spin(6); + return notify(...args); + }; +} diff --git a/packages/storage/test/fixtures/sync-proxy-calls.cjs b/packages/storage/test/fixtures/sync-proxy-calls.cjs new file mode 100644 index 00000000..1ea0d66a --- /dev/null +++ b/packages/storage/test/fixtures/sync-proxy-calls.cjs @@ -0,0 +1,24 @@ +// Run with `node -r ./preempt-sync-proxy.cjs` (#190, test/miniflare-patch.test.ts): 300 +// synchronous D1 proxy calls (`prepare`/`bind`) under the simulated preemption. Prints "ok", or +// the first failure. +const { Miniflare } = require("miniflare"); + +(async () => { + const mf = new Miniflare({ + modules: true, + script: "export default { fetch() { return new Response('ok'); } }", + d1Databases: ["DB"], + }); + const db = await mf.getD1Database("DB"); + process.env.PREEMPT = "1"; + let i = 0; + try { + for (; i < 300; i++) db.prepare("SELECT ?1").bind(i); + console.log("ok"); + } catch (e) { + console.log(`call ${i}: ${String(e?.message).split("\n").slice(0, 3).join(" ")}`); + } finally { + delete process.env.PREEMPT; + await mf.dispose(); + } +})(); diff --git a/packages/storage/test/miniflare-patch.test.ts b/packages/storage/test/miniflare-patch.test.ts new file mode 100644 index 00000000..920f1a8c --- /dev/null +++ b/packages/storage/test/miniflare-patch.test.ts @@ -0,0 +1,28 @@ +/** + * #190: `patches/miniflare@4.20260730.0.patch` backports cloudflare/workers-sdk#15552, the fix for + * the synchronous binding proxy's desync that the D1 harness hit on CI (ADR-0012, addendum + * 2026-11-10). This file fails when a Miniflare change arrives without it, or the patch did not + * apply: re-check the patch against the new release (drop it once the release carries #15552) + * and `pnpm install`. + */ +import { execFile } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { describe, expect, it } from "vitest"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const fixture = (name: string) => path.join(here, "fixtures", name); + +describe("Miniflare carries the #190 fix (ADR-0012)", () => { + it("synchronous D1 calls stay in step when the proxy thread is preempted (workers-sdk#15552)", async () => { + // A preload stalls both sides of the handshake on every other call, as a busy runner does + // now and then. Unpatched, the first stalled call fails `message?.id === id`. + const { stdout } = await promisify(execFile)( + process.execPath, + ["-r", fixture("preempt-sync-proxy.cjs"), fixture("sync-proxy-calls.cjs")], + { timeout: 60_000 }, + ); + expect(stdout.trim()).toBe("ok"); + }); +}); diff --git a/patches/miniflare@4.20260730.0.patch b/patches/miniflare@4.20260730.0.patch new file mode 100644 index 00000000..4232bf86 --- /dev/null +++ b/patches/miniflare@4.20260730.0.patch @@ -0,0 +1,62 @@ +diff --git a/dist/src/index.js b/dist/src/index.js +index ae57410a970d7d47f9f1810567529df69e9d8df1..31275022fef212b8bacc9a42929bcd9ccf0e34ca 100644 +--- a/dist/src/index.js ++++ b/dist/src/index.js +@@ -92215,6 +92215,10 @@ port.addEventListener("message", async (event) => { + dispatcherUrl = url; + dispatcher = new Pool(new URL(url).origin, { + connect: { rejectUnauthorized: false }, ++ // Cloudhal patch (cloudflare/workers-sdk#16445 A): close idle sockets well before ++ // workerd's 5s idle timeout. This thread only waits for requests, so its timers run. ++ keepAliveTimeout: 1000, ++ keepAliveMaxTimeout: 1000, + // Disable timeouts for local dev \u2014 long-running responses (streaming, + // slow uploads, long-polling) should not be killed by undici defaults. + headersTimeout: 0, +@@ -92247,7 +92251,10 @@ port.addEventListener("message", async (event) => { + port.postMessage({ id, error: new Error(String(error)) }); + } + } finally { +- Atomics.store(notifyHandle, /* index */ 0, /* value */ 1); ++ // Cloudhal patch (cloudflare/workers-sdk#15552): publish THIS request's generation ++ // (never 0), not a bare flag, so a late notify from the previous reply cannot wake the ++ // next call before its reply is queued. ++ Atomics.store(notifyHandle, /* index */ 0, /* generation */ (id + 1) | 0); + Atomics.notify(notifyHandle, /* index */ 0); + } + }); +@@ -92278,13 +92285,6 @@ var SynchronousFetcher = class { + } + fetch(url44, init2) { + this.#ensureWorker(); +- Atomics.store( +- this.#notifyHandle, +- /* index */ +- 0, +- /* value */ +- 0 +- ); + const id = this.#nextId++; + this.#channel.port1.postMessage({ + id, +@@ -92293,13 +92293,13 @@ var SynchronousFetcher = class { + headers: init2.headers, + body: init2.body + }); +- Atomics.wait( +- this.#notifyHandle, +- /* index */ +- 0, +- /* value */ +- 0 +- ); ++ // Cloudhal patch (cloudflare/workers-sdk#15552): a stale notify from the previous ++ // reply wakes the wait early; re-check the generation and sleep again. The worker ++ // stores the generation after posting, so seeing it means the reply is queued. ++ const generation = (id + 1) | 0; ++ for (let seen = Atomics.load(this.#notifyHandle, 0); seen !== generation; seen = Atomics.load(this.#notifyHandle, 0)) { ++ Atomics.wait(this.#notifyHandle, 0, seen); ++ } + const message = (0, import_node_worker_threads.receiveMessageOnPort)( + this.#channel.port1 + )?.message; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cefe6306..2ff31598 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -384,6 +384,7 @@ overrides: patchedDependencies: '@cloudflare/kumo@2.14.0': 198f9be784beeb608a5d59b175d6085d7b4f0291567d57b089a85da1fef5f9d7 + miniflare@4.20260730.0: 6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46 importers: @@ -582,7 +583,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) typescript: specifier: 'catalog:' version: 7.0.2 @@ -670,7 +671,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) typescript: specifier: 'catalog:' version: 7.0.2 @@ -869,7 +870,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) typescript: specifier: 'catalog:' version: 7.0.2 @@ -934,7 +935,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) typescript: specifier: 'catalog:' version: 7.0.2 @@ -1045,7 +1046,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) rrule-temporal: specifier: 'catalog:' version: 2.2.8 @@ -1076,7 +1077,7 @@ importers: version: 13.0.3 miniflare: specifier: 'catalog:' - version: 4.20260730.0(@types/node@26.6.5) + version: 4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5) tsx: specifier: 'catalog:' version: 4.23.15 @@ -7920,7 +7921,7 @@ snapshots: min-indent@1.0.1: {} - miniflare@4.20260730.0(@types/node@26.6.5): + miniflare@4.20260730.0(patch_hash=6d7c8031fbf8682db97d0bab3227b4bb2c8c4bc8e77fc8b69019ca2dafd5bd46)(@types/node@26.6.5): dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.35.5(@types/node@26.6.5) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 289ad5ba..bbaea4c8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -108,5 +108,11 @@ overrides: "sharp@<0.35.5": 0.35.5 "undici@<7.30.1": 7.30.2 +# The Miniflare patch backports cloudflare/workers-sdk#15552 (the sync binding proxy's off-by-one +# desync, the `fetch-sync.ts:147` assertion; merged for Miniflare 5 only) and closes the sync +# pool's idle sockets after 1 s, before workerd's 5 s idle timeout (#16445 A). #190, ADR-0012 +# addendum 2026-11-10. Not published, like the overrides. +# `packages/storage/test/miniflare-patch.test.ts` fails when it is missing. Remove with the 4.x pin. patchedDependencies: '@cloudflare/kumo@2.14.0': patches/@cloudflare__kumo@2.14.0.patch + miniflare@4.20260730.0: patches/miniflare@4.20260730.0.patch