59 KiB
@emdash-cms/cloudflare
0.29.0
Minor Changes
- #1719
7c5de08Thanks @swissky! - Adds ataxonomies:readplugin capability with read-only taxonomy access: plugins that declare it getctx.taxonomiesto 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
-
#1875
b116525Thanks @ascorbic! - Fixes a database stampede on Postgres when a pending migration fails at runtime: requests no longer pile up waiting on the migration lock, failed migrations are retried with a backoff instead of on every request, and failed attempts no longer leak idle database connections. -
Updated dependencies [
582ea2c,77e8968,e12f393,e4e76f5,0360900,cbc7d6b,15f4057,1526f96,b116525,7c5de08,450ea81,58f594b,c57b12b,9c0733f,60811c0]:
0.28.1
Patch Changes
0.28.0
Patch Changes
-
#1724
90883deThanks @swissky! - FixescloudflareEmail()failing the Astro build. It now returns a plugin descriptor with a bundlable entrypoint instead of an in-process plugin definition, so the documentedplugins: [cloudflareEmail({...})]usage builds again. -
Updated dependencies [
1866fa3,b36d15c,a3ec23d,cdca719,ee5bfe6,a9e9dde,a9e9dde,7d16d95,dd05063,e2dd273,92fd412,932f4ba,15b4d2d]:
0.27.0
Minor Changes
-
#1433
05f4acdThanks @swissky! - NewcloudflareEmail()plugin: production email provider via Cloudflare Email SendingDeployments 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 exclusiveemail:deliverhook and delivers through asend_emailWorker binding. Add it to the emdash() plugins array, activate it under Extensions, then select it under Settings → Email.
Patch Changes
-
#1715
b093193Thanks @swissky! - Documents that D1 read replica sessions (session: "auto"/"primary-first") are incompatible with theglobal_fetch_strictly_publiccompatibility flag, which silently blocks the D1 Sessions API and hangs every SSR request without logging an error. -
Updated dependencies [
7422460,e4eab4f,46ef945,cff8498,dea8210,90ffe40,386faf5,2a7063a]:
0.26.0
Patch Changes
- Updated dependencies [
facdcfc,f44a277,971c627,e5b95e1,13b87b7,8f7604d,5dea403,3d80be5,d1116ae,192741f,737da19,5299d38]:
0.25.1
Patch Changes
0.25.0
Minor Changes
- #1662
942fac6Thanks @scottbuscemi! - Adds an optionalcachedBindingto thehyperdrive()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 passhyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" }). OmittingcachedBindingleaves behavior unchanged.
Patch Changes
0.24.1
Patch Changes
0.24.0
Patch Changes
0.23.0
Minor Changes
-
#1614
b5ea8e7Thanks @scottbuscemi! - Adds ahyperdrive()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 withdatabase: 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. Requirespg >= 8.16.3, thenodejs_compatcompatibility flag, and a compatibility date of2024-09-23or 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
-
#1617
e39984fThanks @ascorbic! - Cuts cold-start latency by batching runtime initialization reads into a single round trip. On a fresh isolate (or Worker), the auto-seed check, plugin-state load, and site-info load now run as one batched query on backends that support it (Cloudflare D1, Durable Objects) instead of several serialized round trips. On D1 this roughly halves runtime init time on cold start. Other backends (Node/SQLite) are unaffected. -
Updated dependencies [
b4d7228,d74269d,e39984f,280b877,7fca12f,fd077e0,9ebb47e,8c7bc81,55613e1,c5f58b9,bd6cc3a,c056060,c43c412,b5ea8e7,c5f58b9]:
0.22.0
Minor Changes
-
#1378
640e60aThanks @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
revalidatewindow (default 1s, configurable).New public API:
cachedQuery,invalidateObjectCache,invalidateCollectionCache,contentNamespace/contentNamespaces,CacheNamespace, theObjectCache*types (fromemdash),memoryCache()(fromemdash/astro), andkvCache()(from@emdash-cms/cloudflare). Existing sites are unaffected until they opt in. -
#1549
a623c6bThanks @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, soImageand Portable Text images generate a real responsivesrcseteven 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. Setimages: falsein youremdash()config to leave Astro's image endpoint untouched.
Patch Changes
- Updated dependencies [
0bfab91,cf17c9f,707edee,a36b5f3,d46abfd,ed921d8,6a97bac,c219aff,640e60a,ca47da4,cb1c689,a623c6b]:
0.21.0
Patch Changes
0.20.0
Minor Changes
- #1492
7688f0bThanks @ascorbic! - Adds adurableObjects()database adapter that stores the whole CMS in a single Durable Object's SQLite. Withsession: "auto"(plus theexperimentalandreplica_routingcompatibility flags) reads route to the nearest read replica and writes proxy to the primary, cutting read latency for globally distributed traffic. Register the exportedEmDashDBclass in your worker and add anew_sqlite_classesmigration.
Patch Changes
- Updated dependencies [
141aa11,7688f0b,c7166b0,9c994ad,eddaf91,afc3a0f,3d423a7,b01aa9b,3e344af,7688f0b,8807701]:
0.19.0
Minor Changes
-
#1312
c39789cThanks @ascorbic! - Drive scheduled publishing from a real heartbeat instead of request side effects (#1303).Content scheduled via the admin now actually transitions to
publishedwhen its time arrives. Previously nothing promoted the row —statusstayedscheduledandpublished_atstayed 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-drivenPiggybackScheduleris gone, so there are no maintenance side effects on visitor requests.
@emdash-cms/cloudflareships a Worker entry that wraps Astro's handler with thescheduled()handler (@emdash-cms/cloudflare/worker, pluscreateScheduledHandler()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:afterPublishhooks (search indexing, webhooks, syndication), and records the scheduled time aspublished_aton 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. Localastro devkeeps 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
- Updated dependencies [
e96587f,023893a,f41092b,cedfcc5,c63f9ca,61ea3c9,a4c2af2,850c1b7,c39789c]:
0.18.0
Minor Changes
-
#1410
aa815aaThanks @ascorbic! - New experimentalcoalesceoption for thed1()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
sessionto be enabled; off by default while experimental.
Patch Changes
0.17.2
Patch Changes
0.17.1
Patch Changes
0.17.0
Patch Changes
- Updated dependencies [
cd2dcc6,62c170f,ee67273,28432b9,9422d6a,1f8190d,67f5992,a40e455,69bdc97,5e7f835,590b2f9,cd2dcc6]:
0.16.1
Patch Changes
- Updated dependencies []:
0.16.0
Patch Changes
- Updated dependencies [
e312528,668c5e1,f62c004,47a8350,5456514,60c0b2e,1a4918f,7554bd3,33f76b8,e9877e1]:
0.15.0
Patch Changes
-
#426
02ed8baThanks @BenjaminPrice! - Adds workerd-based plugin sandboxing for Node.js deployments.- emdash: Adds
isHealthy()toSandboxRunnerinterface,SandboxUnavailableErrorclass,sandbox: falseconfig option,mediaStoragefield onSandboxOptions, and exportscreateHttpAccess/createUnrestrictedHttpAccess/PluginStorageRepository/UserRepository/OptionsRepositoryfor platform adapters. - @emdash-cms/cloudflare: Implements
isHealthy()onCloudflareSandboxRunner. FixesstorageQuery()andstorageCount()to honorwhere,orderBy, andcursoroptions (previously ignored, causing infinite pagination loops and incorrect filtered counts). AddsstorageConfigtoPluginBridgePropssoPluginStorageRepositorycan use declared indexes. - @emdash-cms/sandbox-workerd: New package.
WorkerdSandboxRunnerfor production (workerd child process + capnp config + authenticated HTTP backing service) andMiniflareDevRunnerfor development.
- emdash: Adds
-
#1139
88f544dThanks @ask-bonk! - Upgradeskyselyto^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
MigratorandMigrationtypes tokysely/migrationto 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
- Updated dependencies [
6e62b90,c0ce915,23597d0,883b75b,05440b1,94fb50b,0d5843f,0cd8c6d,878a0b6,121f173,f4a9711,dbaea9c,5681eb2,ed917d9,6e62b90]:
0.12.0
Patch Changes
0.11.1
Patch Changes
- Updated dependencies [
dc44989]:
0.11.0
Patch Changes
- Updated dependencies [
f8ee1ed,27e6d58,4c11017,f1d4c0b,7c536e5,d273e9a,514d32d,8116949,c4ee7ad]:
0.10.0
Patch Changes
0.9.0
Minor Changes
-
#816
d4be24fThanks @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:anyqualifier with the more conspicuous:unrestricted. Old names are still accepted with@deprecatedwarnings;emdash plugin bundleandemdash plugin validatewarn for each deprecated name andemdash plugin publishrefuses 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 withread:contentresolve tocontent:readand reach the same code path.Old New read:contentcontent:readwrite:contentcontent:writeread:mediamedia:readwrite:mediamedia:writeread:usersusers:readnetwork:fetchnetwork:requestnetwork:fetch:anynetwork:request:unrestrictedemail:providehooks.email-transport:registeremail:intercepthooks.email-events:registerpage:injecthooks.page-fragments:registerExisting installs keep working — manifests are normalized at every external boundary and
diffCapabilitiesnormalizes 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
- Updated dependencies [
e2b3c6c,9dfc65c,e0dc6fb,c22fb3a,6a4e9b8,0ee372a,22a16ee,1e2b024,81662e9,2f22f57,ef3f076,a9c29ea,e7df21f,d5f7c48,8ae227c,e2d5d16,0d98c62,64bf5b9,e81aa0f,0041d76,cee403d,a8bac5d,5b6f059,a86ff80,d4be24f,eb6dbd0]:- emdash@0.9.0
0.8.0
Patch Changes
- Updated dependencies [
493e317,3eca9d5,3eca9d5,37ada52,0557b62,5a581d9,0ecd3b4,3138432,70924cd,1f0f6f2,3eca9d5,e402890,3eca9d5,f5658f0,3eca9d5,3eca9d5,b6cb2e6,3eca9d5,cf1edae,b352e88,31333dc,da3d065,3eca9d5,3eca9d5,3eca9d5,47978b5,3eca9d5]:- emdash@1.0.0
0.7.0
Patch Changes
-
#740
63509e1Thanks @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:anyandallowedHosts: ["*"]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
445b3bfThanks @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/d1behind a singlecreateRequestScopedDb(opts)function. Core middleware has no D1-specific logic. Adapters opt in via a newsupportsRequestScope: booleanflag onDatabaseDescriptor;d1()sets it to true.Other fixes in the same change:
- Nested
runWithContextcalls 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
!configbail-out now still applies baseline security headers. __ec_d1_bookmarkreferences aligned to__em_d1_bookmarkacross runtime, docs, and JSDoc.
- Nested
-
#569
134f776Thanks @Yusaku01! - Fixes the playground toolbar layout on small screens. -
#653
f97d6abThanks @ascorbic! - Adds opt-in query instrumentation for performance regression testing. SettingEMDASH_QUERY_LOG=1causes 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 anX-Perf-Phaseheader 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/instrumentationso 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
-
#543
7382c9dThanks @ascorbic! - Fixes sandboxed plugin loading in Worker Loader by providing anemdashshim module -
Updated dependencies [
82c6345,64f90d1,598026c,197bc1b,ce873f8]:- emdash@0.5.0
0.4.0
Patch Changes
- Updated dependencies [
5beddc3,8ed7969,f866c9c,1acf174,678cc8c,d56f6c1,5d9120e,9318c56,2a7c68a,6492ea2,b382357,5c0776d,1b743ac]:- emdash@0.4.0
0.3.0
Patch Changes
0.2.0
Patch Changes
- Updated dependencies [
156ba73,80a895b,da957ce,fcd8b7b,8ac15a4,ba2b020,0b108cf,1989e8b,e190324,724191c,ed28089,a293708,c75cc5b,6ebb797,d421ee2,391caf4,6474dae,30c9a96,122c236,5320321,8f44ec2,b712ae3,9cb5a28,7ee7d95,e1014ef,4d4ac53,476cb3a,87b0439,dd708b1,befaeec,c92e7e6,2ba1f1f,a13c4ec,a5e0603]:- emdash@0.2.0
0.1.1
Patch Changes
-
#200
422018aThanks @ascorbic! - Replace placeholder text branding with proper EmDash logo SVGs across admin UI, playground loading page, and preview interstitial -
#16
7924d54Thanks @ascorbic! - DIsplay an interstitial when loading playground -
Updated dependencies [
422018a,4221ba4,9269759,d6cfc43,1bcfc50,8c693b5,5b3e33c,9d10d27,91e31fb,f112ac4,e9a6f7a,b297fdd,d211452,8e28cfc,38af118]:- emdash@0.1.1
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