Repository navigation
feat(postgres-storage): namespace generated stores per environment - #699
Merged
Merged
Conversation
The hono and vercel targets' generated stores.mjs always built the checkpointer, threads and permissions stores in public.b4_*, so a Neon database shared by a Vercel project's preview and production environments shared its tables too. The stores already accepted schema and tablePrefix; only the generated wiring lacked a way to set them. stores.mjs now reads B4_PG_SCHEMA and B4_PG_TABLE_PREFIX per request through the same binding lookup as DATABASE_URL. Each takes a lowercase identifier or a $NAME reference resolved the same way, so B4_PG_SCHEMA=$VERCEL_ENV is one setting that yields the preview and production schemas. Unset keeps the package defaults. A bad value or dangling reference throws by binding name before the pool exists rather than falling back to public. The isolate's migrated flag is keyed by schema.prefix. assertIdentifier tightens to /^[a-z_][a-z0-9_]*$/ and the package exports IDENTIFIER_PATTERN: unquoted DDL folds mixed case to lowercase, so a mixed-case value never named the tables it appeared to and took a different advisory-lock key from the lowercase spelling. Closes #682 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
blove
temporarily deployed
to
vercel-preview
September 17, 2026 05:39 — with
GitHub Actions
Inactive
The persistence, edge deployment and postgres-storage API reference pages changed in this branch, so the generated manifest's source digests no longer matched and `generate-seo-lastmod --check` failed the web lane. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 17, 2026 16:08
blove
disabled auto-merge
September 17, 2026 16:14
Three defects found reviewing the branch. The isolate-level migration memo was keyed by schema and prefix alone. The generated entry binds env per request precisely so a later request can reach a different database, so request 2 against a virgin database was told it had already been migrated because request 1 migrated a different one under the same public.b4 — and every query then failed with undefined_table for the life of the isolate. The key now includes an FNV-1a hash of the connection string, hashed rather than stored so a password does not outlive the request. A binding is not necessarily a string. A host can bind the name to a namespace handle from one config typo, and calling startsWith on that threw a TypeError naming no binding at all, defeating the named errors this path exists to give. The hint is target-specific so the Vercel bundle carries no Workers vocabulary. `$` alone named nothing and reported "references , which is not set". The changeset now states the mixed-case rejection as a behavior change rather than mentioning it in passing. It stays a patch: on this 0.x fixed group a minor would bump every package to 1.0.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 17, 2026 16:16
blove
disabled auto-merge
September 17, 2026 16:17
#698 restructured the generated stores.mjs around selectPostgresDriver and moved the emitted-stores harness into test/helpers/emitted-stores.ts, both in the code this branch rewrites. Resolved by taking main's structure and re-applying this branch's work on top: the naming bindings resolve before the driver is selected (still before any pool is opened), NAMING_STORAGE_STUB joins the shared helper beside the other store stubs, and the new tests use the helper's (appRoot, storesDir, envs) signature. The inline cliStub gained describeConnectionTarget and formatErrorChain, which the emitted file now imports. The edge docs line about per-isolate migration tracking now says per database, schema and prefix, matching the memo key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 17, 2026 16:21
blove
temporarily deployed
to
vercel-preview
September 17, 2026 16:21 — with
GitHub Actions
Inactive
blove
added a commit
that referenced
this pull request
Sep 17, 2026
…cing Two more vercel-target changes landed while this branch was open, and both contradicted the guide: - #697 closed #681 by adding `build.vercel.reconcileVercelJson`. The guide said flatly that there is no opt-out. It now documents the flag, what `b4 build` stops doing when it is off, the four `B4_E1003` near misses that are rejected rather than read as configured, and that Fluid must then be enabled in the project settings because nothing checks it from source. - #699 added `B4_PG_SCHEMA` and `B4_PG_TABLE_PREFIX`. The guide said a generated app requires an app-dedicated database. It now carries the `B4_PG_SCHEMA=$VERCEL_ENV` recipe, which is the Vercel-shaped reason the feature exists, and links to the edge page's table for the rest. cli.mdx conflicted and takes main's `vercel` bullet, which already covers the opt-out, with a link to this page appended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 17, 2026
blove
added a commit
that referenced
this pull request
Sep 17, 2026
Two follow-ups from #699. memory-pgvector kept its own copy of assertIdentifier, still case-insensitive, while postgres-storage tightened to lowercase. Both interpolate the value into DDL unquoted and Postgres folds an unquoted identifier to lowercase, so `MySchema` created `myschema` and never named the tables the store then queried. The two packages had come to disagree about what a valid identifier is. The copies stay separate, because memory-pgvector depends on @b4run/memory rather than on the agent-state stores and a dependency edge purely to share four lines would drag that package's LangGraph peers in with it; each now carries a comment pointing at the other. The api-reference inventory test allowed 60s for a child analysis that takes ~80s on a laptop, so it failed with ETIMEDOUT while reporting zero analysis failures — red, and saying nothing about the docs. Raised to 180s, sized for a loaded machine rather than close to the observed time. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #682.
Problem
b4 buildfor thehonoandverceltargets emitsstores.mjs, which built the checkpointer, threads and permissions stores with the package defaults: schemapublic, prefixb4. One Neon database attached to both the preview and production environments of a Vercel project therefore sharedpublic.b4_*between them. The stores already acceptedschemaandtablePrefix; the generated wiring exposed neither.Change
Generated
stores.mjsreads two new per-request bindings through the same lookup asDATABASE_URL:B4_PG_SCHEMAschemapublicB4_PG_TABLE_PREFIXtablePrefixb4Each value is a lowercase identifier or a
$NAMEreference resolved the same way, soB4_PG_SCHEMA=$VERCEL_ENVis one setting that puts preview deployments inpreviewand production inproduction.B4_ENVor any other variable works the same way.Unset keeps the defaults, so existing deployments continue to use
public.b4_*.Bad values fail loudly. A non-identifier, a reference to an unset variable, or a reference whose value is not an identifier throws an error naming the binding, before the pool is built. It does not fall back to
public: a typo silently writing production data into the shared schema is the bug this prevents.The isolate's
migratedboolean becomes aSetkeyed byschema.prefix, since the bindings are per request.@b4run/postgres-storage:assertIdentifiertightens from/^[a-z_][a-z0-9_]*$/ito the case-sensitive pattern, exported asIDENTIFIER_PATTERN. Unquoted DDL folds mixed case to lowercase, soMySchemacreatedmyschemawhile its advisory-lock key differed from the lowercase spelling of the same tables.Migrations already run
CREATE SCHEMA IF NOT EXISTS, so a fresh schema needs no extra step.Design notes
$NAMEindirection was chosen over readingVERCEL_ENVautomatically: Vercel setsVERCEL_ENVon every deployment, so an automatic default would move existing production deployments out ofpublicon their next deploy.schema), the emitted check names the binding the operator set.Tests
postgres-storage/test/schema.test.ts: lowercase-only pattern,IDENTIFIER_PATTERNshape.postgres-storage/test/threads.test.ts(gated real Postgres,B4_TEST_PGSTORAGE=1): two stores with one prefix in two schemas hold independent data and create their tables under their own schema. Ran locally in the Testcontainers lane: 88/88.cli/test/hono-target.test.ts: the emittedstores.mjsis executed with stubbed drivers for the default, literal,$VERCEL_ENVreference, runtime-env fallback, per-namespace migration pass, and four failure cases (with zero pools opened).deployment/edge.mdxgains a "Namespace preview and production" section;persistence.mdx, the API reference and the package README updated;check-docs.mjspinned phrases updated to the new wording.node scripts/check-docs.mjspasses.Local verification:
postgres-storageunit + gated lane,hono-target55/55,vercel-target95/95, lint and typecheck for both packages, docs check. The webapi-reference-inventoryanalysis reports zero failures but exceeds its 60 s spawn limit on my laptop (78 s), which is unrelated to this change.No release: changeset only.
🤖 Generated with Claude Code