@dawn-ai/postgres-storage@0.8.21
Patch Changes
-
c2c19da:
@dawn-ai/postgres-storage:assumeMigrated— a new opt-out on every store
option type.ready()resolves immediately instead of opening a transaction,
takingpg_advisory_xact_lockand re-running theCREATE … IF NOT EXISTSpass.
Set it only when the same process has already migrated that database to the
store's current version. It exists for per-request store lifetimes: a store
memoizes its migration on the instance, so a factory that rebuilds stores every
request paid three migration transactions per request — and the three advisory
locks serialized concurrent requests on the same component key. The lock itself
is unchanged; what is skipped is a pass already known to have completed.honobuild target fixes.- The generated
stores.mjsnow migrates once per isolate behind a module-scope
flag and passesassumeMigratedthereafter. wrangler.toml: the generated marker is read back, so a rebuild recognizes
its own scaffold instead of warning about it, writing a duplicate into
.dawn/build/, and reporting that duplicate as the artifact. A marked file is
still never overwritten.- The build now fails, naming the config key, when
checkpointer,
threadsStore,permissions.storeormemory.storeis configured: the
handle cannot cross the build boundary, and the emitted Postgres store was
taking its place with nothing said. - The provider import map is exhaustive or the build fails. A route that cannot
be imported, or an agent whose provider cannot be inferred, is an error rather
than a silently narrower map;summarization.modelis included, so an app
with openai routes and an anthropic summarization model no longer builds green
and fails at request time on a package that was never bundled. - All validation now runs before the first artifact is written.
- The emitted entry throws, naming the cause, when no Workers env is bound to a
request orDATABASE_URLis unset, rather than building a pool with no
connection string. - Worker names generated from a package name now start with a letter, which
Cloudflare requires. honois no longer a dependency of@dawn-ai/cli, which does not import it.
The generated app does, and the build's dependency notice names it along with
@dawn-ai/postgres-storageand@neondatabase/serverless.
- The generated
-
c2c19da: Breaking (shipped as a patch):
poolis now required on
@dawn-ai/postgres-storage's main entry, andconnectionStringhas moved to
@dawn-ai/postgres-storage/node.In
0.8.19the main entry accepted either, and built its ownpgpool from a
connectionStringwhen you passed one. It no longer does: the main entry
importspgfor types only, so it links on a runtime where a raw TCP driver
cannot be bundled at all — which is what makes Cloudflare Workers possible.
connectionStringis no longer part of the main entry's option type, so passing
it there is a type error, and the factory throws at construction naming the
missing pool and pointing at the/nodesubpath. It does not fail silently or
later.Two ways to migrate, both mechanical:
// 1. Change the import. Same three factories, connectionString still works, // the store still builds and owns its pool. import { createPostgresPermissionsStore, createPostgresThreadsStore, postgresCheckpointer, } from "@dawn-ai/postgres-storage/node"; // 2. Or build the pool yourself and keep the main entry — which is what you // want anyway if you are sharing one pool across all three stores. import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // Do not skip this. See below. pool.on("error", (error) => { console.error("postgres pool client error (connection dropped):", error); }); postgresCheckpointer({ pool });
If you take option 2, attach an
'error'listener to the pool you build.
0.8.19shipped that listener as a fix, and it attached it to the pool it built
for you; now that you own the pool, you own its error handling, and these stores
deliberately do not attach one to a pool you passed in —pgputs that
responsibility on the pool owner, and attaching one silently would mask it.
pgemits'error'on the pool when an idle client fails, and an
EventEmitter'error'with no listener is an uncaught exception that ends the
process. Idle connections are dropped as a matter of course (server restart,
failover,idle_session_timeout), so a pool without one turns a routine
Postgres blip into an outage. The/nodeentry in option 1 still attaches it to
pools it builds.The
/nodeentry is the same three factories with theconnectionString
convenience layered on, and it re-exports everything the main entry does, so
option 1 is usually a one-line change. Pool ownership is unchanged in both
shapes: a pool the store built is ended byclose(), an injected pool is left
alone (ownsPool, defaultfalse).This is a breaking change against a published version, and it is going out as a
patch deliberately: the packages are in a fixed0.xgroup, where a minor
bump would move the entire group to1.0.0. -
c2c19da:
@dawn-ai/postgres-storageruns on Cloudflare Workers. That was an open
question when the package shipped in0.8.19, and its README said so: workerd
provides no raw TCP socket, sopgis unusable there.What changed is the main entry's typing. The pool is now structural —
SqlPool = { query, connect, end }— which bothpg.Pooland a
@neondatabase/serverlessWebSocketPoolsatisfy with no driver abstraction in
between. Injecting the latter runs the full contract, transactions included. The
narrowness is itself the guard:neon()'s transaction-incapable HTTP query
function fails to satisfySqlPoolat compile time, correctly, because the
checkpointer needs realBEGIN/COMMIT.dawn build'shonotarget generates
that wiring for you. Hyperdrive should also work but is untested — it needs a
Cloudflare account.Three separate pieces of evidence back this, and they are worth keeping apart:
- A throwaway spike ran this package's built
distinside real workerd
againstpostgres:16-alpineplus awsproxy. It is the only thing that has
checked the driver's transaction semantics there:BEGIN/COMMIT/ROLLBACK
are genuine session transactions,pg_advisory_xact_lockreally blocks a
second session, and eight concurrent cold-start migrations converged. The spike
was not retained; its findings are recorded in
docs/superpowers/specs/2026-08-05-edge-targets-design.md. - The package's own suite is the standing regression guard for the migration
behavior — concurrent cold starts against a virgin database, for all three
stores — but it runs on Node withpgand Testcontainers, gated on
DAWN_TEST_PGSTORAGE, not inside workerd. - The gated
edge-workerdCI lane is what runs continuously inside workerd,
and it asserts at the application level rather than the SQL level: four
sequential AG-UI turns each carrying the model's reply, identical event shapes
across turns,/healthz, and out-of-bandpsqlchecks that the thread,
checkpoint, and pending-write rows are really in Postgres. It exercises the
stores through the runtime; it does not assert on transactions, the advisory
lock, or concurrent cold starts.
One rule that lane settled the hard way: on workerd, build the pool and the
stores per request. A connection is bound to the I/O context of the request
that opened it, so a module-scope pool hands the next request a dead socket and
hangs rather than erroring.assumeMigratedexists for exactly that lifetime —
it lets a per-request store skip the migration pass that a module-scope boolean
has already completed for the isolate.Correcting one line of
0.8.19's entry while it is fresh: migrations are
memoized on the store instance, not per process. That distinction did not
matter when a process built its stores once; it is the whole reason
assumeMigratedhad to exist once stores are per request.- @dawn-ai/permissions@0.8.21
- A throwaway spike ran this package's built