@filelayer/core 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -21,9 +21,74 @@ embarrassing. A changelog that only records features is a marketing document.
21
21
 
22
22
  ---
23
23
 
24
- ## [Unreleased] — group grant subjects (RFC-001)
25
-
26
- Breaking schema change, additive API. Migration: `MIGRATIONS.md` Entry 2.
24
+ ## [Unreleased]
25
+
26
+ Two things: **packaging** the published tarball now has no runtime
27
+ dependencies at all — and **group grant subjects** (RFC-001), a breaking schema
28
+ change with an additive API whose migration is `MIGRATIONS.md` Entry 2.
29
+
30
+ ### Changed — packaging
31
+
32
+ - **`@electric-sql/pglite` is no longer a runtime dependency.** It was the only
33
+ one, and it should never have been one: it is an embedded WebAssembly
34
+ PostgreSQL, and a library whose entire premise is "run it against your own
35
+ Postgres" has no business putting a second Postgres into every production
36
+ `node_modules`. It is used by exactly two functions — `createTestDb()` and the
37
+ `Filelayer.quickstart()` built on it — and both are development helpers.
38
+ - It is now a **`devDependency`** (the suite needs it) *and* an **optional
39
+ peer dependency** (`peerDependenciesMeta.optional: true`). A consumer who
40
+ wants `createTestDb()` is told what to install and at which version range; a
41
+ consumer who does not gets no install warning and no WASM blob.
42
+ - **Installing `@filelayer/core` now installs one package.** Asserted from
43
+ inside the installed copy by the release gate and by the packaging job, so
44
+ a runtime dependency cannot be reintroduced without a red build.
45
+ - **Nothing about the public API changed.** `createTestDb`, `SCHEMA_PATH` and
46
+ `loadSchemaSql` are exported exactly as before, and production code — a
47
+ `pg.Pool` (or anything satisfying `Queryable`) passed to `new Filelayer()` —
48
+ never touches the removed dependency. If you use `quickstart()` or
49
+ `createTestDb()`, add
50
+ `npm install --save-dev "@electric-sql/pglite@^0.3.11"`.
51
+ - **`createTestDb()` without PGlite installed now explains itself.** It used to
52
+ surface Node's raw `ERR_MODULE_NOT_FOUND` from inside `dist/`, naming a
53
+ package the caller never asked for. It now throws an error that names
54
+ `@electric-sql/pglite`, gives the exact install command, says why the package
55
+ is optional, and points at the production alternative (`pg.Pool` plus
56
+ `psql -f node_modules/@filelayer/core/schema.sql`). The original resolution
57
+ error is preserved as `cause`. A resolution failure *inside* PGlite — a broken
58
+ install rather than a missing one — is passed through untouched.
59
+ - **The release gate now runs the whole lifecycle twice, in both shapes.** Once
60
+ in the shape a production consumer installs — the packed tarball and nothing
61
+ else, no embedded database on disk, driven against a real PostgreSQL server
62
+ through `pg` — and once through `Filelayer.quickstart()` after explicitly
63
+ installing the optional peer dependency. The assertions are written once and
64
+ run in both. It also asserts that a missing optional peer produces no install
65
+ warning, and that `createTestDb()`'s error names the package and the command.
66
+
67
+ ### Added — testing
68
+
69
+ - **The live S3/R2 suite now runs in CI.** `test/s3-live.test.ts` has always
70
+ skipped itself without credentials; it now has a job that runs it when the
71
+ repository secrets are present. This is the code path every download goes
72
+ through, and a local harness that verifies signatures cannot speak for TLS,
73
+ real IAM evaluation, R2's divergences from S3, read-after-write visibility or
74
+ the error codes a real store returns.
75
+ - **A fork does not go red.** Secrets are unavailable to pull requests from
76
+ forks, so the job decides for itself whether it has credentials and skips
77
+ the work if not.
78
+ - **The skip is visible.** It is written to the job summary and raised as a
79
+ workflow notice, naming the missing secrets. A green tick that ran nothing
80
+ is worse than an honest "skipped: no credentials", because the two look
81
+ identical. The converse is checked too: credentials present and the suite
82
+ skipping itself anyway is a **failure**, not a pass.
83
+ - **`docs/LIVE-S3-TESTS.md`** is the single place the bucket, the minimal R2
84
+ token / IAM policy and the exact secret names are specified. The test file
85
+ header used to restate them and now points at it.
86
+ - The multipart test (~11 MB of uploads) runs on a nightly schedule and on
87
+ manual dispatch, rather than on every push.
88
+ - Fixed while wiring the above: an *empty* `FILELAYER_TEST_S3_PREFIX` — what a
89
+ workflow hands you for an unset repository variable — was taken as a real
90
+ value by `??`, rooting every test key at `/` instead of under the prefix the
91
+ cleanup deletes.
27
92
 
28
93
  ### Fixed
29
94
 
@@ -103,6 +168,134 @@ Breaking schema change, additive API. Migration: `MIGRATIONS.md` Entry 2.
103
168
 
104
169
  ---
105
170
 
171
+ ## [0.4.1] — 2026-09-06
172
+
173
+ A single defect, in the first command a new user runs. Nothing else changed: no
174
+ API change, no schema change, no behaviour change. If you already have a working
175
+ install, this release does nothing for you.
176
+
177
+ ### Fixed
178
+
179
+ - **The documented command for installing PGlite could not resolve against the
180
+ peer range this package declares.** `0.4.0` declares
181
+ `peerDependencies: { "@electric-sql/pglite": "^0.3.11" }`, as an optional peer.
182
+ The install command in the `createTestDb()` error message, in `README.md`, in
183
+ `docs/QUICKSTART.md`, in `llms.txt` and in the changelog entry above carried no
184
+ version at all. PGlite's `latest` on npm is `0.5.8`, outside `^0.3.11`, so
185
+ following our own written instructions could put a version on disk that the
186
+ declared range does not admit — and npm then refuses the whole tree:
187
+
188
+ ```
189
+ npm error Could not resolve dependency:
190
+ npm error peerOptional @electric-sql/pglite@"^0.3.11" from @filelayer/core@0.4.0
191
+ ```
192
+
193
+ It is deterministic for anyone whose project already has PGlite, or who asks
194
+ for a specific version, and it is a hard stop about sixty seconds in. Every
195
+ install command in this repository is now version-explicit:
196
+
197
+ ```bash
198
+ npm install --save-dev "@electric-sql/pglite@^0.3.11"
199
+ ```
200
+
201
+ The quotes are for the shell, not for npm — `^` is a glob operator under `zsh`
202
+ with `extendedglob` and an escape character in `cmd.exe`, and a command that
203
+ breaks in a common shell is the same defect wearing a different hat.
204
+
205
+ ### Not changed, deliberately
206
+
207
+ - **The peer range is still `^0.3.11`. PGlite 0.5.x is not supported.** Before
208
+ choosing between widening the range and fixing the instructions, the full
209
+ suite was run against `0.5.8`. It does not pass. `tsc --noEmit` is clean and
210
+ every assertion that executes passes, but seven test files are killed by the
211
+ operating system and the run aborts after 92 of 313 tests. Reproduced in
212
+ isolation with 3.6 GB free, so it is not a plain out-of-memory: one suite runs
213
+ 20 of 21 subtests green and is then killed. Against `0.3.16` the identical
214
+ suite is 313 of 313.
215
+
216
+ We have not diagnosed it further, because the supported range is the decision
217
+ in front of us and the cause is upstream. What we will not do is widen the
218
+ range to whatever npm installs by default and describe an untested
219
+ configuration as supported. When 0.5.x passes, the range moves and this entry
220
+ gets a successor.
221
+
222
+ ### Added
223
+
224
+ - **`tools/check-install-commands.mjs`, wired into `npm run verify`.** It reads
225
+ every tracked file and fails the build if an install command names a
226
+ version-constrained package without a version constraint, or pins one to a
227
+ range that is not the range `packages/core/package.json` declares. The
228
+ comparison is semver intervals rather than string equality and runs in both
229
+ directions, so widening the peer range without updating the documentation
230
+ fails, and so does the reverse. No network and no install: it is a check on
231
+ what we wrote, evaluated against what we declared.
232
+
233
+ It carries a negative control that runs on every invocation, before the real
234
+ scan: fourteen commands whose correct classification is known — including the
235
+ exact unversioned command `0.4.0` shipped, in each of the five forms it was
236
+ written in — plus seven pieces of text that must *not* be read as install
237
+ commands. If the detector misclassifies any of them the check exits `2` and
238
+ says the detector is broken rather than reporting a clean repository. A
239
+ checker that has never rejected anything is a green tick of unknown value.
240
+
241
+ ### Changed
242
+
243
+ - **The release gate now runs the documented command instead of its own.**
244
+ `tools/verify-release.mjs` reads the install command out of `README.md` at run
245
+ time and executes that string in its empty consumer directory. It used to
246
+ write its own equivalent, which is why a green gate and a broken instruction
247
+ could coexist for a whole release: the gate was testing a command no user
248
+ would ever type.
249
+ - **The gate also installs in the other order.** Phases 1 and 2 install the
250
+ package first and PGlite second, and in that order npm can rescue a bad
251
+ instruction by quietly walking `latest` back into the peer range — which is
252
+ precisely how `0.4.0`'s command passed. A new phase does it the other way
253
+ round, in a second directory that has never contained anything: the documented
254
+ command first, then `@filelayer/core` on top. There is nothing left to rescue
255
+ there, so the resolution failure is either real or absent. It asserts, too,
256
+ that the version actually installed satisfies the declared range, because an
257
+ install that succeeds by giving the reader something other than what they
258
+ asked for is still a broken instruction.
259
+ - `README.md`, `docs/QUICKSTART.md` and the `createTestDb()` error message now
260
+ state the supported range in words as well as in the command: the 0.3.x line
261
+ is supported, 0.5.x is not, and the reason is that the suite does not pass
262
+ against it.
263
+
264
+ ---
265
+
266
+ ## [0.4.0] — 2026-09-06
267
+
268
+ ### Changed
269
+
270
+ - **`@electric-sql/pglite` is no longer a runtime dependency.** It is now a
271
+ dev dependency and an *optional* peer. The published package declares zero
272
+ runtime dependencies. A library whose premise is "run it against your own
273
+ Postgres" should not install an embedded WASM Postgres into every production
274
+ deployment; it did, and that was wrong.
275
+ `createTestDb()` and `Filelayer.quickstart()` still need it, and now say so
276
+ with an actionable message instead of a module-resolution error. If you use
277
+ either in tests, add
278
+ `npm install --save-dev "@electric-sql/pglite@^0.3.11"`. Nothing
279
+ else changes; production code paths never imported it.
280
+
281
+ *(The command in this entry originally omitted the version constraint. That
282
+ omission is the defect fixed in 0.4.1, and the command has been corrected here
283
+ so that nobody reading the history copies the broken one.)*
284
+
285
+ ### Added
286
+
287
+ - A CI job that exercises the S3/R2 storage adapter against live object storage
288
+ when credentials are configured, and states plainly in the build summary when
289
+ they are not. See `docs/LIVE-S3-TESTS.md`.
290
+ - `TRUST.md` — the current state of this project in numbers, including the ones
291
+ that are zero, and what would change them.
292
+
293
+ ### Fixed
294
+
295
+ - `FILELAYER_TEST_S3_PREFIX` used `??` rather than `||`, so the empty string CI
296
+ supplies for an unset variable became a real value and rooted test objects at
297
+ the bucket root instead of under the prefix cleanup deletes.
298
+
106
299
  ## [0.3.0] — 2026-09-05
107
300
 
108
301
  The release that makes the package installable, and the one that closes the
package/NOTICE CHANGED
@@ -16,20 +16,23 @@ and anyone reviewing this distribution's licence obligations. It is
16
16
  NOT a required attribution notice under Apache-2.0 section 4(d), and
17
17
  redistributors of Filelayer do not inherit an obligation to reproduce it.
18
18
 
19
- Filelayer has exactly one runtime dependency:
19
+ Filelayer has NO runtime dependencies. Installing @filelayer/core installs
20
+ @filelayer/core and nothing else. One third-party package is used, and only
21
+ outside production:
20
22
 
21
23
  @electric-sql/pglite
22
24
  PostgreSQL compiled to WebAssembly, used for the embedded database in
23
- `Filelayer.quickstart()` and in the test suite.
25
+ `Filelayer.quickstart()`, in `createTestDb()` and in the test suite.
24
26
  Licensed under the Apache License, Version 2.0.
25
27
  https://github.com/electric-sql/pglite
26
28
 
27
- It is declared as a normal npm dependency and is installed from the
28
- registry by the consumer. No part of it is vendored into, copied into, or
29
- redistributed inside this repository or the published tarball, so this
30
- distribution is not a Derivative Work of it. It ships no NOTICE file of
31
- its own, so Apache-2.0 section 4(d) imposes no propagation requirement
32
- even where a redistributor bundles it.
29
+ It is declared as a devDependency and as an OPTIONAL peer dependency, so a
30
+ consumer who does not call those two helpers never installs it. When it is
31
+ installed it comes from the registry. No part of it is vendored into,
32
+ copied into, or redistributed inside this repository or the published
33
+ tarball, so this distribution is not a Derivative Work of it. It ships no
34
+ NOTICE file of its own, so Apache-2.0 section 4(d) imposes no propagation
35
+ requirement even where a redistributor bundles it.
33
36
 
34
37
  No other third-party code is vendored, copied or embedded in this
35
38
  distribution. The comparison implementations under `benchmark/` are original
package/README.md CHANGED
@@ -12,6 +12,9 @@
12
12
  resolve on their own with no edit here. The licence badge is live now. -->
13
13
 
14
14
  > ## ⚠️ Alpha — developer preview. Not production software.
15
+
16
+ > **[Should you depend on this?](https://github.com/filelayer/filelayer/blob/main/TRUST.md)** — the real numbers, including the ones
17
+ > that are zero, and exactly what would change them.
15
18
  >
16
19
  > We would rather you trust us later for good reasons than trust us now for bad
17
20
  > ones, so here is the honest state of this project.
@@ -184,6 +187,26 @@ declarations in `dist/`; the TypeScript source and the full test suite are in
184
187
  the tarball as well, so every claim on this page is inspectable from what you
185
188
  installed.
186
189
 
190
+ **That install has zero runtime dependencies.** Filelayer talks to *your*
191
+ Postgres and *your* bucket, so it ships neither. The throwaway instance below is
192
+ the one exception: `quickstart()` runs on an embedded WebAssembly Postgres,
193
+ declared as an *optional peer dependency* so that it never lands in a production
194
+ `node_modules`. Add it if you want the throwaway instance, and skip it
195
+ otherwise — `createTestDb()` will tell you, by name, if you need it:
196
+
197
+ ```bash
198
+ npm install --save-dev "@electric-sql/pglite@^0.3.11"
199
+ ```
200
+
201
+ **Install PGlite with that version constraint.** Filelayer supports the
202
+ **0.3.x** line, which is what `peerDependencies` declares and what the suite runs
203
+ against. **0.5.x is not supported**: we ran the full suite against 0.5.8 and it
204
+ does not pass, so the range has not been widened. PGlite's `latest` on npm is a
205
+ 0.5.x release, so omitting the constraint can install a version outside the
206
+ supported range — and npm then refuses the whole tree with `ERESOLVE`. The quotes
207
+ are for your shell, not for npm: `^` is a glob operator under `zsh` with
208
+ `extendedglob` and an escape character in `cmd.exe`.
209
+
187
210
  ```ts
188
211
  import { Filelayer } from '@filelayer/core';
189
212
 
@@ -278,11 +301,13 @@ tarball, so the grant travels with the artifact rather than only with the
278
301
  repository. There are no per-file licence headers — the grant is carried by
279
302
  `LICENSE`, `NOTICE` and the `license` field of every `package.json`.
280
303
 
281
- **Dependencies.** Filelayer has exactly one runtime dependency,
282
- [`@electric-sql/pglite`](https://github.com/electric-sql/pglite), itself
283
- Apache-2.0. It is installed from the registry rather than vendored, ships no
284
- `NOTICE` file of its own, and is recorded in ours for convenience. No
285
- third-party code is copied or embedded anywhere in this repository.
304
+ **Dependencies.** Filelayer has **no runtime dependencies**. Its one third-party
305
+ package, [`@electric-sql/pglite`](https://github.com/electric-sql/pglite)
306
+ (Apache-2.0), is a `devDependency` and an *optional peer dependency*: the test
307
+ suite and `quickstart()` run on it, and a production install does not contain
308
+ it. It is installed from the registry rather than vendored, ships no `NOTICE`
309
+ file of its own, and is recorded in ours for convenience. No third-party code is
310
+ copied or embedded anywhere in this repository.
286
311
 
287
312
  ## Contributing, security and conduct
288
313
 
package/dist/db.d.ts CHANGED
@@ -135,6 +135,10 @@ export declare function loadSchemaSql(): Promise<string>;
135
135
  * real enums, real arrays, real rules, real transactional semantics. The one
136
136
  * thing it is NOT is multi-process, which matters for exactly one test; see
137
137
  * test/security.test.ts, "atomic download cap", for what that weakens.
138
+ *
139
+ * REQUIRES the optional peer dependency `@electric-sql/pglite`. Production code
140
+ * does not need it: hand `new Filelayer(...)` your own `pg.Pool`, or anything
141
+ * else that satisfies `Queryable`, and this function is never reached.
138
142
  */
139
143
  export declare function createTestDb(): Promise<{
140
144
  db: Queryable & {
package/dist/db.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAMH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACtD,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC;;;;OAIG;IACH,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACpE;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,EAAG,SAAQ,SAAS;IACnC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;IAC7B,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,EAAE,CAE5C;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBACZ,KAAK,EAAE,OAAO;CAK3B;AAoBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAqCZ;AA6CD,eAAO,MAAM,WAAW,QAAiC,CAAC;AAE1D,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAC5C,EAAE,EAAE,SAAS,GAAG;QAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC3C,GAAG,EAAE,OAAO,CAAC;CACd,CAAC,CAOD"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAMH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACtD,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC;;;;OAIG;IACH,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACpE;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,EAAG,SAAQ,SAAS;IACnC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;IAC7B,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,SAAS,GAAG,EAAE,IAAI,EAAE,CAE5C;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBACZ,KAAK,EAAE,OAAO;CAK3B;AAoBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAqCZ;AA6CD,eAAO,MAAM,WAAW,QAAiC,CAAC;AAE1D,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAC5C,EAAE,EAAE,SAAS,GAAG;QAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC3C,GAAG,EAAE,OAAO,CAAC;CACd,CAAC,CAMD"}
package/dist/db.js CHANGED
@@ -198,6 +198,51 @@ export const SCHEMA_PATH = join(HERE, '..', 'schema.sql');
198
198
  export async function loadSchemaSql() {
199
199
  return readFile(SCHEMA_PATH, 'utf8');
200
200
  }
201
+ /**
202
+ * The one package this library needs and deliberately does not depend on.
203
+ *
204
+ * PGlite is an embedded WASM build of PostgreSQL. It is what `createTestDb()`
205
+ * and `Filelayer.quickstart()` run on, and it is superb for that -- but a
206
+ * library whose premise is "point it at YOUR Postgres" has no business putting
207
+ * a second Postgres into every production `node_modules`. So it is declared as
208
+ * an OPTIONAL PEER DEPENDENCY: named, version-ranged and discoverable, but not
209
+ * installed for anyone who never calls the two helpers that use it.
210
+ *
211
+ * The cost of that choice is this constant and the `catch` below. Without them
212
+ * the failure mode for a consumer who calls `createTestDb()` is a raw
213
+ * ERR_MODULE_NOT_FOUND naming a package they never asked for, from a stack
214
+ * inside our `dist`, and no indication that installing one thing fixes it.
215
+ */
216
+ const PGLITE = '@electric-sql/pglite';
217
+ /**
218
+ * The install command a caller is handed, spelled out in full.
219
+ *
220
+ * The version constraint is not decoration. PGlite's `latest` on npm is a 0.5.x
221
+ * release and this package declares `peerDependencies` of `^0.3.11`, so an
222
+ * install with no constraint on it can land a version outside the declared
223
+ * range -- at which point npm refuses the whole tree with ERESOLVE and the
224
+ * developer is stuck one minute in, having typed what we told them to type.
225
+ * That happened; it is the defect 0.4.1 fixes.
226
+ *
227
+ * The range is a literal rather than a template so that
228
+ * `tools/check-install-commands.mjs` can read it out of this file and fail the
229
+ * build if it ever stops matching `peerDependencies` in package.json. The two
230
+ * are the same fact written in two places, and the check is what keeps them
231
+ * one fact.
232
+ *
233
+ * The quotes are for the shell, not for npm: `^` is a glob operator under zsh
234
+ * with `extendedglob`, and an escape character in cmd.exe.
235
+ */
236
+ const PGLITE_INSTALL = 'npm install --save-dev "@electric-sql/pglite@^0.3.11"';
237
+ /** True when `err` is Node refusing to resolve `spec`, and not some other failure. */
238
+ function isModuleNotFound(err, spec) {
239
+ const code = err?.code;
240
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND')
241
+ return false;
242
+ // A resolution failure *inside* pglite itself is a broken install, not a
243
+ // missing one, and must not be reported as "run npm install".
244
+ return String(err.message ?? '').includes(spec);
245
+ }
201
246
  /**
202
247
  * Create an in-process Postgres (PGlite) with the Filelayer schema applied.
203
248
  *
@@ -205,13 +250,52 @@ export async function loadSchemaSql() {
205
250
  * real enums, real arrays, real rules, real transactional semantics. The one
206
251
  * thing it is NOT is multi-process, which matters for exactly one test; see
207
252
  * test/security.test.ts, "atomic download cap", for what that weakens.
253
+ *
254
+ * REQUIRES the optional peer dependency `@electric-sql/pglite`. Production code
255
+ * does not need it: hand `new Filelayer(...)` your own `pg.Pool`, or anything
256
+ * else that satisfies `Queryable`, and this function is never reached.
208
257
  */
209
258
  export async function createTestDb() {
210
- const { PGlite } = await import('@electric-sql/pglite');
211
- const { pgcrypto } = await import('@electric-sql/pglite/contrib/pgcrypto');
259
+ const { PGlite, pgcrypto } = await importPglite();
212
260
  const pg = await PGlite.create({ extensions: { pgcrypto } });
213
261
  const sql = await loadSchemaSql();
214
262
  await pg.exec(sql);
215
263
  return { db: pg, raw: pg };
216
264
  }
265
+ async function importPglite() {
266
+ try {
267
+ const [mod, contrib] = await Promise.all([
268
+ import('@electric-sql/pglite'),
269
+ import('@electric-sql/pglite/contrib/pgcrypto'),
270
+ ]);
271
+ return { PGlite: mod.PGlite, pgcrypto: contrib.pgcrypto };
272
+ }
273
+ catch (err) {
274
+ if (!isModuleNotFound(err, PGLITE))
275
+ throw err;
276
+ throw new Error(`createTestDb() needs "${PGLITE}", which is not installed.\n` +
277
+ `\n` +
278
+ ` ${PGLITE_INSTALL}\n` +
279
+ `\n` +
280
+ `The version is part of the command. @filelayer/core supports the 0.3.x line\n` +
281
+ `of ${PGLITE}; 0.5.x is not supported, because the test suite does\n` +
282
+ `not pass against it. Installing without the constraint can resolve to a\n` +
283
+ `version outside the supported range, and npm then refuses the install with\n` +
284
+ `ERESOLVE rather than giving you a working tree.\n` +
285
+ `\n` +
286
+ `It is an OPTIONAL peer dependency of @filelayer/core, on purpose: it is an\n` +
287
+ `embedded WASM PostgreSQL used by createTestDb() and Filelayer.quickstart()\n` +
288
+ `for tests and local development, and shipping it to production installs of\n` +
289
+ `a library that talks to your own Postgres would be wrong.\n` +
290
+ `\n` +
291
+ `In production, do not call this. Pass your own database instead:\n` +
292
+ `\n` +
293
+ ` import { Pool } from 'pg';\n` +
294
+ ` new Filelayer(new Pool({ connectionString: process.env.DATABASE_URL }), storage, opts)\n` +
295
+ `\n` +
296
+ `and apply the schema once with:\n` +
297
+ `\n` +
298
+ ` psql "$DATABASE_URL" -f node_modules/@filelayer/core/schema.sql\n`, { cause: err });
299
+ }
300
+ }
217
301
  //# sourceMappingURL=db.js.map
package/dist/db.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filelayer/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Multi-tenant file authorization and lifecycle for Postgres: one decision point for public and private files, revocable URLs, delegated share links and a hash-chained audit trail.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -67,10 +67,16 @@
67
67
  "typecheck": "tsc --noEmit",
68
68
  "verify": "npm run typecheck && npm test && npm run build"
69
69
  },
70
- "dependencies": {
70
+ "peerDependencies": {
71
71
  "@electric-sql/pglite": "^0.3.11"
72
72
  },
73
+ "peerDependenciesMeta": {
74
+ "@electric-sql/pglite": {
75
+ "optional": true
76
+ }
77
+ },
73
78
  "devDependencies": {
79
+ "@electric-sql/pglite": "^0.3.11",
74
80
  "typescript": "^5.7.0",
75
81
  "@types/node": "^22.10.0"
76
82
  }
package/src/db.ts CHANGED
@@ -250,6 +250,53 @@ export async function loadSchemaSql(): Promise<string> {
250
250
  return readFile(SCHEMA_PATH, 'utf8');
251
251
  }
252
252
 
253
+ /**
254
+ * The one package this library needs and deliberately does not depend on.
255
+ *
256
+ * PGlite is an embedded WASM build of PostgreSQL. It is what `createTestDb()`
257
+ * and `Filelayer.quickstart()` run on, and it is superb for that -- but a
258
+ * library whose premise is "point it at YOUR Postgres" has no business putting
259
+ * a second Postgres into every production `node_modules`. So it is declared as
260
+ * an OPTIONAL PEER DEPENDENCY: named, version-ranged and discoverable, but not
261
+ * installed for anyone who never calls the two helpers that use it.
262
+ *
263
+ * The cost of that choice is this constant and the `catch` below. Without them
264
+ * the failure mode for a consumer who calls `createTestDb()` is a raw
265
+ * ERR_MODULE_NOT_FOUND naming a package they never asked for, from a stack
266
+ * inside our `dist`, and no indication that installing one thing fixes it.
267
+ */
268
+ const PGLITE = '@electric-sql/pglite';
269
+
270
+ /**
271
+ * The install command a caller is handed, spelled out in full.
272
+ *
273
+ * The version constraint is not decoration. PGlite's `latest` on npm is a 0.5.x
274
+ * release and this package declares `peerDependencies` of `^0.3.11`, so an
275
+ * install with no constraint on it can land a version outside the declared
276
+ * range -- at which point npm refuses the whole tree with ERESOLVE and the
277
+ * developer is stuck one minute in, having typed what we told them to type.
278
+ * That happened; it is the defect 0.4.1 fixes.
279
+ *
280
+ * The range is a literal rather than a template so that
281
+ * `tools/check-install-commands.mjs` can read it out of this file and fail the
282
+ * build if it ever stops matching `peerDependencies` in package.json. The two
283
+ * are the same fact written in two places, and the check is what keeps them
284
+ * one fact.
285
+ *
286
+ * The quotes are for the shell, not for npm: `^` is a glob operator under zsh
287
+ * with `extendedglob`, and an escape character in cmd.exe.
288
+ */
289
+ const PGLITE_INSTALL = 'npm install --save-dev "@electric-sql/pglite@^0.3.11"';
290
+
291
+ /** True when `err` is Node refusing to resolve `spec`, and not some other failure. */
292
+ function isModuleNotFound(err: unknown, spec: string): boolean {
293
+ const code = (err as { code?: unknown } | null)?.code;
294
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') return false;
295
+ // A resolution failure *inside* pglite itself is a broken install, not a
296
+ // missing one, and must not be reported as "run npm install".
297
+ return String((err as Error).message ?? '').includes(spec);
298
+ }
299
+
253
300
  /**
254
301
  * Create an in-process Postgres (PGlite) with the Filelayer schema applied.
255
302
  *
@@ -257,15 +304,59 @@ export async function loadSchemaSql(): Promise<string> {
257
304
  * real enums, real arrays, real rules, real transactional semantics. The one
258
305
  * thing it is NOT is multi-process, which matters for exactly one test; see
259
306
  * test/security.test.ts, "atomic download cap", for what that weakens.
307
+ *
308
+ * REQUIRES the optional peer dependency `@electric-sql/pglite`. Production code
309
+ * does not need it: hand `new Filelayer(...)` your own `pg.Pool`, or anything
310
+ * else that satisfies `Queryable`, and this function is never reached.
260
311
  */
261
312
  export async function createTestDb(): Promise<{
262
313
  db: Queryable & { close(): Promise<void> };
263
314
  raw: unknown;
264
315
  }> {
265
- const { PGlite } = await import('@electric-sql/pglite');
266
- const { pgcrypto } = await import('@electric-sql/pglite/contrib/pgcrypto');
316
+ const { PGlite, pgcrypto } = await importPglite();
267
317
  const pg = await PGlite.create({ extensions: { pgcrypto } });
268
318
  const sql = await loadSchemaSql();
269
319
  await pg.exec(sql);
270
320
  return { db: pg as unknown as Queryable & { close(): Promise<void> }, raw: pg };
271
321
  }
322
+
323
+ async function importPglite(): Promise<{
324
+ PGlite: typeof import('@electric-sql/pglite').PGlite;
325
+ pgcrypto: typeof import('@electric-sql/pglite/contrib/pgcrypto').pgcrypto;
326
+ }> {
327
+ try {
328
+ const [mod, contrib] = await Promise.all([
329
+ import('@electric-sql/pglite'),
330
+ import('@electric-sql/pglite/contrib/pgcrypto'),
331
+ ]);
332
+ return { PGlite: mod.PGlite, pgcrypto: contrib.pgcrypto };
333
+ } catch (err) {
334
+ if (!isModuleNotFound(err, PGLITE)) throw err;
335
+ throw new Error(
336
+ `createTestDb() needs "${PGLITE}", which is not installed.\n` +
337
+ `\n` +
338
+ ` ${PGLITE_INSTALL}\n` +
339
+ `\n` +
340
+ `The version is part of the command. @filelayer/core supports the 0.3.x line\n` +
341
+ `of ${PGLITE}; 0.5.x is not supported, because the test suite does\n` +
342
+ `not pass against it. Installing without the constraint can resolve to a\n` +
343
+ `version outside the supported range, and npm then refuses the install with\n` +
344
+ `ERESOLVE rather than giving you a working tree.\n` +
345
+ `\n` +
346
+ `It is an OPTIONAL peer dependency of @filelayer/core, on purpose: it is an\n` +
347
+ `embedded WASM PostgreSQL used by createTestDb() and Filelayer.quickstart()\n` +
348
+ `for tests and local development, and shipping it to production installs of\n` +
349
+ `a library that talks to your own Postgres would be wrong.\n` +
350
+ `\n` +
351
+ `In production, do not call this. Pass your own database instead:\n` +
352
+ `\n` +
353
+ ` import { Pool } from 'pg';\n` +
354
+ ` new Filelayer(new Pool({ connectionString: process.env.DATABASE_URL }), storage, opts)\n` +
355
+ `\n` +
356
+ `and apply the schema once with:\n` +
357
+ `\n` +
358
+ ` psql "$DATABASE_URL" -f node_modules/@filelayer/core/schema.sql\n`,
359
+ { cause: err },
360
+ );
361
+ }
362
+ }
@@ -10,36 +10,21 @@
10
10
  * WHAT A MAINTAINER MUST SET
11
11
  * -----------------------------------------------------------------------------
12
12
  *
13
- * REQUIRED (all five, or the suite skips):
13
+ * The bucket, the token permissions and the exact secret names are specified in
14
+ * ONE place -- `docs/LIVE-S3-TESTS.md` -- and this file deliberately does not
15
+ * restate them. Two copies of a setup procedure drift, and the copy that drifts
16
+ * is the one nobody followed most recently.
14
17
  *
15
- * FILELAYER_TEST_S3_ENDPOINT https://<account>.r2.cloudflarestorage.com
16
- * or https://s3.<region>.amazonaws.com
17
- * FILELAYER_TEST_S3_BUCKET a bucket that may be written to and
18
- * emptied. USE A DEDICATED TEST BUCKET.
19
- * FILELAYER_TEST_S3_REGION 'auto' for R2, the real region for AWS
20
- * FILELAYER_TEST_S3_ACCESS_KEY_ID
21
- * FILELAYER_TEST_S3_SECRET_ACCESS_KEY
18
+ * What this file reads, and nothing more:
22
19
  *
23
- * OPTIONAL:
20
+ * REQUIRED, all five or the suite skips:
21
+ * FILELAYER_TEST_S3_ENDPOINT, FILELAYER_TEST_S3_BUCKET,
22
+ * FILELAYER_TEST_S3_REGION, FILELAYER_TEST_S3_ACCESS_KEY_ID,
23
+ * FILELAYER_TEST_S3_SECRET_ACCESS_KEY
24
24
  *
25
- * FILELAYER_TEST_S3_SESSION_TOKEN for STS / temporary credentials
26
- * FILELAYER_TEST_S3_PATH_STYLE 'false' to exercise virtual-hosted
27
- * addressing (AWS default style).
28
- * Defaults to path-style, which is what
29
- * R2 requires.
30
- * FILELAYER_TEST_S3_PREFIX key prefix. Defaults to
31
- * 'filelayer-ci/'. Everything the suite
32
- * writes lives under it and is deleted
33
- * afterwards.
34
- * FILELAYER_TEST_S3_MULTIPART '1' to run the multipart test, which
35
- * uploads ~11 MB. Off by default so the
36
- * suite stays cheap on every push;
37
- * turn it ON in the nightly job.
38
- *
39
- * IAM PERMISSIONS REQUIRED on the bucket:
40
- * s3:PutObject, s3:GetObject, s3:DeleteObject, s3:ListBucket,
41
- * s3:AbortMultipartUpload (multipart cleanup),
42
- * s3:ListBucketMultipartUploads (only if you enable the multipart test)
25
+ * OPTIONAL:
26
+ * FILELAYER_TEST_S3_SESSION_TOKEN, FILELAYER_TEST_S3_PATH_STYLE,
27
+ * FILELAYER_TEST_S3_PREFIX, FILELAYER_TEST_S3_MULTIPART
43
28
  *
44
29
  * -----------------------------------------------------------------------------
45
30
  * WHY THIS FILE EXISTS EVEN THOUGH test/storage.test.ts PASSES
@@ -75,9 +60,12 @@ const missing = REQUIRED.filter((k) => !env[k]);
75
60
  const enabled = missing.length === 0;
76
61
  const skip = enabled
77
62
  ? false
78
- : `live S3 credentials not present (missing: ${missing.join(', ')}); see the header of this file`;
63
+ : `live S3 credentials not present (missing: ${missing.join(', ')}); see docs/LIVE-S3-TESTS.md`;
79
64
 
80
- const PREFIX = (env['FILELAYER_TEST_S3_PREFIX'] ?? 'filelayer-ci/').replace(/\/*$/, '/');
65
+ // `||`, not `??`: a workflow that forwards an unset repository variable hands
66
+ // us the EMPTY STRING, and `'' ?? default` is `''` -- which would root every
67
+ // test key at '/' instead of under the prefix that gets cleaned up.
68
+ const PREFIX = (env['FILELAYER_TEST_S3_PREFIX'] || 'filelayer-ci/').replace(/\/*$/, '/');
81
69
  const RUN = `${PREFIX}${Date.now()}-${Math.random().toString(36).slice(2, 8)}/`;
82
70
  const MULTIPART = env['FILELAYER_TEST_S3_MULTIPART'] === '1';
83
71