Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

59 KiB
Raw Permalink Blame History

@emdash-cms/cloudflare

0.29.0

Minor Changes

  • #1719 7c5de08 Thanks @swissky! - Adds a taxonomies:read plugin capability with read-only taxonomy access: plugins that declare it get ctx.taxonomies to list taxonomy definitions (getAll()), fetch the terms of a taxonomy (getTerms()), and read the terms assigned to a content entry (getEntryTerms()) — in-process and in both sandbox runners.

Patch Changes

0.28.1

Patch Changes

0.28.0

Patch Changes

0.27.0

Minor Changes

  • #1433 05f4acd Thanks @swissky! - New cloudflareEmail() plugin: production email provider via Cloudflare Email Sending

    Deployments on Cloudflare Workers had no production email provider — only the dev console stub — so magic-link login, invites and notifications failed with "Email is not configured". cloudflareEmail({ from, replyTo?, binding? }) registers the exclusive email:deliver hook and delivers through a send_email Worker binding. Add it to the emdash() plugins array, activate it under Extensions, then select it under Settings → Email.

Patch Changes

0.26.0

Patch Changes

0.25.1

Patch Changes

0.25.0

Minor Changes

  • #1662 942fac6 Thanks @scottbuscemi! - Adds an optional cachedBinding to the hyperdrive() adapter for serving anonymous public-site reads from a caching-enabled Hyperdrive configuration. When set, anonymous reads of public paths route through the cache-enabled binding, while every authenticated request, every write, and every request under /_emdash (admin, setup, auth, internal APIs) stays on the primary (caching-disabled) binding — preserving read-after-write consistency, including for the anonymous post-setup status check. Bind both Hyperdrive configurations in wrangler and pass hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" }). Omitting cachedBinding leaves behavior unchanged.

Patch Changes

0.24.1

Patch Changes

0.24.0

Patch Changes

0.23.0

Minor Changes

  • #1614 b5ea8e7 Thanks @scottbuscemi! - Adds a hyperdrive() database adapter for connecting EmDash on Cloudflare Workers to a PostgreSQL (or PostgreSQL-compatible, e.g. PlanetScale Postgres) database through a Hyperdrive binding. Configure it with database: hyperdrive({ binding: "HYPERDRIVE" }). Each request gets its own pooled connection that is opened and closed within that request — connections cannot be reused across Worker requests. Requires pg >= 8.16.3, the nodejs_compat compatibility flag, and a compatibility date of 2024-09-23 or later. Disable Hyperdrive query caching for the configuration so the admin's read-after-write stays consistent.

    The content read/write path, scheduled publishing, plugin cron, and database-querying plugin hooks are all supported. Sandboxed plugins remain D1-only (the sandbox bridge talks to a D1 binding directly, independent of the configured adapter).

Patch Changes

0.22.0

Minor Changes

  • #1378 640e60a Thanks @scottbuscemi! - Add an optional distributed object cache for query results.

    Content reads (getEmDashCollection, getEmDashEntry, resolveEmDashPath) and chrome reads (site settings, menus, taxonomies) can now be served from a fast key/value store instead of hitting the database on every request. This sits beneath the per-request cache and above the database, dramatically reducing read pressure on D1/SQLite — especially valuable on Cloudflare, where KV handles far more requests than D1.

    The cache is off by default and fully opt-in. Configure a backend in astro.config.mjs:

    import { kvCache } from "@emdash-cms/cloudflare"; // Workers KV (distributed)
    import { memoryCache } from "emdash/astro"; // in-isolate (Node / local dev)
    
    emdash({
    	database: d1({ binding: "DB" }),
    	objectCache: kvCache({ binding: "CACHE" }),
    });
    

    with a matching KV binding in wrangler.jsonc:

    { "kv_namespaces": [{ "binding": "CACHE", "id": "<namespace-id>" }] }
    

    Invalidation is epoch-based and automatic: content, byline, taxonomy, menu, and settings writes bump a per-namespace version, instantly orphaning stale entries (no key enumeration needed). Preview and visual-edit requests bypass the cache, so editors previewing see live content; other reads are served from the cache, which only ever stores published content. After an edit, anonymous visitors may see stale content until isolates pick up the bumped epoch — immediate on the in-isolate memory backend, and on KV bounded by KV's edge-cache propagation (eventually consistent, up to ~60s) plus the revalidate window (default 1s, configurable).

    New public API: cachedQuery, invalidateObjectCache, invalidateCollectionCache, contentNamespace/contentNamespaces, CacheNamespace, the ObjectCache* types (from emdash), memoryCache() (from emdash/astro), and kvCache() (from @emdash-cms/cloudflare). Existing sites are unaffected until they opt in.

  • #1549 a623c6b Thanks @ascorbic! - Fixes responsive image optimization for storage-backed media on Cloudflare. EmDash now wraps Astro's image endpoint to read media bytes directly from your storage adapter instead of fetching them over HTTP, so Image and Portable Text images generate a real responsive srcset even when the site is behind Cloudflare Access (previously these 404'd and fell back to a full-size image). This is on by default and also removes an internal HTTP round-trip on Node. Set images: false in your emdash() config to leave Astro's image endpoint untouched.

Patch Changes

0.21.0

Patch Changes

0.20.0

Minor Changes

  • #1492 7688f0b Thanks @ascorbic! - Adds a durableObjects() database adapter that stores the whole CMS in a single Durable Object's SQLite. With session: "auto" (plus the experimental and replica_routing compatibility flags) reads route to the nearest read replica and writes proxy to the primary, cutting read latency for globally distributed traffic. Register the exported EmDashDB class in your worker and add a new_sqlite_classes migration.

Patch Changes

0.19.0

Minor Changes

  • #1312 c39789c Thanks @ascorbic! - Drive scheduled publishing from a real heartbeat instead of request side effects (#1303).

    Content scheduled via the admin now actually transitions to published when its time arrives. Previously nothing promoted the row — status stayed scheduled and published_at stayed null forever.

    A new sweep (publishDueContent) promotes due content and runs alongside the existing cron tick and system cleanup:

    • Node / single-process: the timer-based scheduler already drives it — no action needed.
    • Cloudflare Workers: a scheduled() handler driven by a Cron Trigger now runs the sweep. The request-driven PiggybackScheduler is gone, so there are no maintenance side effects on visitor requests.

    @emdash-cms/cloudflare ships a Worker entry that wraps Astro's handler with the scheduled() handler (@emdash-cms/cloudflare/worker, plus createScheduledHandler() for hand-assembled Workers). When a cache provider is configured, the handler also purges edge-cache tags for whatever it published, so stale snapshots produced before the scheduled time are evicted.

    Migration for existing Cloudflare sites. New sites get this from the templates. Existing deployments must update two files:

    // src/worker.ts
    export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";
    
    // wrangler.jsonc
    "triggers": { "crons": ["* * * * *"] }
    

    Without the Cron Trigger, scheduled publishing and plugin cron do not run on Workers.

    Scheduled publishing matches manual publishing exactly: it fires content:afterPublish hooks (search indexing, webhooks, syndication), and records the scheduled time as published_at on first publication rather than the (later) sweep time. The sweep claims each row atomically before promoting it, so an entry unscheduled or rescheduled just before its time is never published, and overlapping sweeps can't double-publish. Local astro dev keeps running the timer-driven sweep even under the Cloudflare adapter (where production relies on the Cron Trigger).

    Each tick promotes at most 100 items per collection (a large backlog drains over successive ticks) so a single Worker invocation can't exhaust its CPU/subrequest budget, and edge-cache tags are purged incrementally after each collection's batch rather than only at the end. On Node, the maintenance interval is capped at 60s so scheduled-publish latency matches the Cloudflare Cron Trigger cadence instead of lagging up to five minutes when no plugin cron is due.

Patch Changes

0.18.0

Minor Changes

  • #1410 aa815aa Thanks @ascorbic! - New experimental coalesce option for the d1() adapter, for much faster uncached page loads:

    emdash({
    	database: d1({ binding: "DB", session: "auto", coalesce: true }),
    });
    

    When enabled, read queries that a page issues at the same time are sent to D1 as a single round trip instead of one at a time. A page that runs half a dozen queries — settings, menus, the entry, related posts — pays for one trip to the database instead of six, which can cut uncached render time by more than half. Each query still gets its own results and its own errors, and writes are unaffected. Requires session to be enabled; off by default while experimental.

Patch Changes

0.17.2

Patch Changes

0.17.1

Patch Changes

0.17.0

Patch Changes

0.16.1

Patch Changes

0.16.0

Patch Changes

0.15.0

Patch Changes

  • #426 02ed8ba Thanks @BenjaminPrice! - Adds workerd-based plugin sandboxing for Node.js deployments.

    • emdash: Adds isHealthy() to SandboxRunner interface, SandboxUnavailableError class, sandbox: false config option, mediaStorage field on SandboxOptions, and exports createHttpAccess/createUnrestrictedHttpAccess/PluginStorageRepository/UserRepository/OptionsRepository for platform adapters.
    • @emdash-cms/cloudflare: Implements isHealthy() on CloudflareSandboxRunner. Fixes storageQuery() and storageCount() to honor where, orderBy, and cursor options (previously ignored, causing infinite pagination loops and incorrect filtered counts). Adds storageConfig to PluginBridgeProps so PluginStorageRepository can use declared indexes.
    • @emdash-cms/sandbox-workerd: New package. WorkerdSandboxRunner for production (workerd child process + capnp config + authenticated HTTP backing service) and MiniflareDevRunner for development.
  • #1139 88f544d Thanks @ask-bonk! - Upgrades kysely to ^0.29.0 (was ^0.27.0) to resolve three high-severity advisories fixed in >=0.28.17:

    • GHSA-wmrf-hv6w-mr66 SQL injection via unsanitized JSON path keys
    • GHSA-pv5w-4p9q-p3v2 JSON-path traversal injection via JSONPathBuilder.key() / .at()
    • GHSA-8cpq-38p9-67gx MySQL SQL injection via sql.lit(string)

    Also updates import paths for Migrator and Migration types to kysely/migration to comply with kysely 0.29 export changes.

  • Updated dependencies [02ed8ba, 11b3001, fae97ee, 88f544d, 9a30607, d0ff94b]:

0.14.0

Patch Changes

0.13.0

Patch Changes

0.12.0

Patch Changes

0.11.1

Patch Changes

0.11.0

Patch Changes

0.10.0

Patch Changes

0.9.0

Minor Changes

  • #816 d4be24f Thanks @ask-bonk! - Unifies plugin capability names under a single <resource>[.<sub-resource>]:<verb>[:<qualifier>] formula so capabilities read like RBAC permissions, separates hook-registration permissions from data-access ones for clearer audits, and replaces the overloaded :any qualifier with the more conspicuous :unrestricted. Old names are still accepted with @deprecated warnings; emdash plugin bundle and emdash plugin validate warn for each deprecated name and emdash plugin publish refuses manifests that still use them.

    The Cloudflare sandbox bridge and HTTP fetch helper now enforce canonical names (content:read, content:write, media:read, media:write, users:read, network:request, network:request:unrestricted). Manifests that still declare legacy names continue to work — the runner normalizes capabilities before passing them into the bridge, so installed plugins with read:content resolve to content:read and reach the same code path.

    Old New
    read:content content:read
    write:content content:write
    read:media media:read
    write:media media:write
    read:users users:read
    network:fetch network:request
    network:fetch:any network:request:unrestricted
    email:provide hooks.email-transport:register
    email:intercept hooks.email-events:register
    page:inject hooks.page-fragments:register

    Existing installs keep working — manifests are normalized at every external boundary and diffCapabilities normalizes both sides so version upgrades that only rename do not trigger a "capability changed" prompt. Deprecated names will be removed in the next minor.

Patch Changes

0.8.0

Patch Changes

0.7.0

Patch Changes

  • #740 63509e1 Thanks @ascorbic! - Sandboxed plugin HTTP requests now follow redirects manually and re-validate the destination at every hop. The allowedHosts list is checked on each redirect target (not just the initial URL), so an allowed host that 302s to a disallowed one no longer bypasses the scope. Credential headers (Authorization, Cookie, Proxy-Authorization) are stripped on cross-origin redirects. network:fetch:any and allowedHosts: ["*"] now still reject literal private IPs, cloud-metadata addresses, and known internal hostnames — the allowlist scopes which public hosts a plugin may reach, not whether SSRF protection applies. Non-http(s) URL schemes are rejected. Caps redirect chains at 5 hops.

  • Updated dependencies [8ebdf1a, 7186961, e9ecec2, e3e18aa, fae63bd, 30d8fe0, d4a95bf, a31db7d, adb118c, 080a4f1, 81fe93b, c26442b]:

    • emdash@0.7.0

0.6.0

Patch Changes

  • #605 445b3bf Thanks @ascorbic! - Fixes D1 read replicas being bypassed for anonymous public page traffic. The middleware fast path now asks the database adapter for a per-request scoped Kysely, so anonymous reads land on the nearest replica instead of the primary-pinned singleton binding.

    All D1-specific semantics (Sessions API, constraint selection, bookmark cookie) live in @emdash-cms/cloudflare/db/d1 behind a single createRequestScopedDb(opts) function. Core middleware has no D1-specific logic. Adapters opt in via a new supportsRequestScope: boolean flag on DatabaseDescriptor; d1() sets it to true.

    Other fixes in the same change:

    • Nested runWithContext calls in the request-context middleware now merge the parent context instead of replacing it, so an outer per-request db override is preserved through edit/preview flows.
    • Baseline security headers now forward Astro's cookie symbol across the response clone so cookies.set() calls in middleware survive.
    • Any write (authenticated or anonymous) now forces first-primary, so an anonymous form/comment POST isn't racing across replicas.
    • The session user is read once per request and reused in both the fast path and the full runtime init (previously read twice on authenticated public-page traffic).
    • Bookmark cookies are validated only for length (≤1024) and absence of control characters — no stricter shape check, so a future D1 bookmark format change won't silently degrade consistency.
    • The !config bail-out now still applies baseline security headers.
    • __ec_d1_bookmark references aligned to __em_d1_bookmark across runtime, docs, and JSDoc.
  • #569 134f776 Thanks @Yusaku01! - Fixes the playground toolbar layout on small screens.

  • #653 f97d6ab Thanks @ascorbic! - Adds opt-in query instrumentation for performance regression testing. Setting EMDASH_QUERY_LOG=1 causes the Kysely log hook to emit [emdash-query-log]-prefixed NDJSON on stdout for every DB query executed inside a request, tagged with the route, method, and an X-Perf-Phase header value. Zero runtime overhead when the flag is unset — the log option is only attached to Kysely when enabled.

    Also exposes the helpers at emdash/database/instrumentation so first-party adapters (e.g. @emdash-cms/cloudflare) can wire the same hook into their per-request Kysely instances.

  • Updated dependencies [ada4ac7, f279320, 7f75193, cfd01f3, 38d637b, 31d2f4e, 445b3bf, 943d540, 2cb3165, 1859347, 14c923b, c5ef0f5, f839381, 002d0ac, 0a61ef4, 6d41fe1, b158e40, f97d6ab, e67b940, 0896ec8, 629fe1d, f52154d, 8221c2a, 8fb93eb, 6d7f288, 4ffa141, 04e6cca, 9295cc1]:

    • emdash@0.6.0

0.5.0

Patch Changes

0.4.0

Patch Changes

0.3.0

Patch Changes

0.2.0

Patch Changes

0.1.1

Patch Changes

0.1.0

Minor Changes

Patch Changes

  • Updated dependencies [755b501]:
    • emdash@0.1.0

0.0.3

Patch Changes

  • Updated dependencies [3c319ed]:
    • emdash@0.0.3

0.0.2

Patch Changes

  • Updated dependencies [b09bfd5]:
    • emdash@0.0.2