Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/spec-browser-condition-pg-grammar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@objectstack/spec": patch
---

Declare the package's browser boundary in the `exports` map (#11072): the five
entries whose module graph reaches the driver-config validators (`.`, `./data`,
`./system`, `./kernel`, `./cloud`) now carry a `browser` export condition
pointing at bundles (`dist/browser/**`) in which the postgres `url`
refinement's pg-grammar arm is excluded. `pg-connection-string` — the parser
`pg` itself uses, and the one the #9091 refusal deliberately asks — statically
resolves `require('fs')`, so any browser bundler whose client graph reached one
of these entries failed on `Can't resolve 'fs'` (measured on objectui's docs
site, Next.js/Turbopack).

Patch, not minor/major, because the change is additive resolution surface with
zero Node-side movement: Node's resolver never matches `browser`, every
existing `import`/`require` condition still points at the same files, and the
full #9091 DSN refusal (multi-host, non-numeric port, scheme-less non-URL)
still runs for every Node consumer — the existing `postgres.test.ts` pins hold
it. In the browser-conditioned bundles the refinement degrades to the
shape-only checks it already performs before `parse` (the unix-socket
short-circuit and the fs-reading `?sslcert=`/`?sslkey=`/`?sslrootcert=`
refusal); publish-time validation never legitimately runs in a browser.

The boundary is enforced at this producer from now on:
`check:browser-reachable-entries` refuses any browser-resolvable bundle —
browser-conditioned or not — that links a Node builtin or a declared
server-only package, with a positive control on the Node side, so the next
Node-only import fails this package's own CI instead of a downstream bundler.
15 changes: 15 additions & 0 deletions packages/spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ bundlers may drop what a consumer does not reach — module-scope side effects
in any published module are therefore also a defect (measured, not assumed;
see objectstack#10031).

**Node-only imports are refused everywhere a browser bundler resolves**
(#11072). Every entry is loadable from a browser build by construction —
schema-bearing entries included — so no published bundle may link a Node
builtin or a server-only package. The one sanctioned exception is the
pg-grammar arm of the postgres `url` refinement (`pg-connection-string`, whose
`parse` statically resolves `require('fs')`): the entries that reach it carry a
`browser` export condition pointing at `dist/browser/**` bundles in which that
arm is swapped for a dependency-free twin (`src/data/driver/pg-url-grammar.*`,
`tsup.config.ts` → `swapServerOnlyGrammarArm`), and Node consumers keep the
full DSN refusal unchanged. `check:browser-reachable-entries` enforces both
halves — a browser-conditioned bundle that still links the parser, and a
non-conditioned bundle that links anything Node-only, are each a red — with a
positive control on the Node side. Need Node-only work in a schema? Route it
through a seam module the browser pass swaps, never import it directly.

## Usage

**Recommended: Use `ObjectSchema.create()` with `Field.*` helpers for strict TypeScript validation:**
Expand Down
50 changes: 50 additions & 0 deletions packages/spec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
"sideEffects": false,
"exports": {
".": {
"browser": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/browser/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/browser/index.js"
}
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
Expand All @@ -18,6 +28,16 @@
}
},
"./data": {
"browser": {
"import": {
"types": "./dist/data/index.d.mts",
"default": "./dist/browser/data/index.mjs"
},
"require": {
"types": "./dist/data/index.d.ts",
"default": "./dist/browser/data/index.js"
}
},
"import": {
"types": "./dist/data/index.d.mts",
"default": "./dist/data/index.mjs"
Expand All @@ -28,6 +48,16 @@
}
},
"./system": {
"browser": {
"import": {
"types": "./dist/system/index.d.mts",
"default": "./dist/browser/system/index.mjs"
},
"require": {
"types": "./dist/system/index.d.ts",
"default": "./dist/browser/system/index.js"
}
},
"import": {
"types": "./dist/system/index.d.mts",
"default": "./dist/system/index.mjs"
Expand All @@ -38,6 +68,16 @@
}
},
"./kernel": {
"browser": {
"import": {
"types": "./dist/kernel/index.d.mts",
"default": "./dist/browser/kernel/index.mjs"
},
"require": {
"types": "./dist/kernel/index.d.ts",
"default": "./dist/browser/kernel/index.js"
}
},
"import": {
"types": "./dist/kernel/index.d.mts",
"default": "./dist/kernel/index.mjs"
Expand Down Expand Up @@ -128,6 +168,16 @@
}
},
"./cloud": {
"browser": {
"import": {
"types": "./dist/cloud/index.d.mts",
"default": "./dist/browser/cloud/index.mjs"
},
"require": {
"types": "./dist/cloud/index.d.ts",
"default": "./dist/browser/cloud/index.js"
}
},
"import": {
"types": "./dist/cloud/index.d.mts",
"default": "./dist/cloud/index.mjs"
Expand Down
Loading
Loading