@onreza/sqlx-js 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -43,7 +43,7 @@ const rows = await sql(
43
43
  - **Shadow database validation** via `migrate dev` / `migrate verify`: auto-create a disposable shadow DB, apply migrations, validate SQL, and drop it afterwards.
44
44
  - **Safe identifier quoting** via `sql.id(...)`, backed by the committed schema snapshot whitelist.
45
45
  - **Single runtime adapter**: Postgres.js backs the runtime on Node/Bun-compatible environments — no Bun.SQL-specific adapter to choose.
46
- - **Watch mode**: debounced re-prepare with a warm `PgClient` + `SchemaCache` on source/SQL changes and scanner/config graph updates.
46
+ - **Incremental watch mode**: debounced re-prepare with a warm `PgClient` + `SchemaCache`; source/SQL edits only rescan affected files and re-describe changed fingerprints, while config/tsconfig/schema changes trigger a full rebuild.
47
47
  - **Cache pruning** removes orphaned entries automatically (toggleable with `--no-prune`).
48
48
  - **Environment doctor** checks runtime versions, config loading, `.env`, database connectivity/permissions, cache metadata, tsconfig inclusion, and pgschema availability.
49
49
  - **Strict inference gate** promotes degraded nullability analysis and generated `unknown` query types to CI errors.
@@ -324,6 +324,25 @@ import { createClient, setClient } from "@onreza/sqlx-js";
324
324
  setClient(createClient(process.env.DATABASE_URL));
325
325
  ```
326
326
 
327
+ For dependency injection, read replicas, tests, or several independent pools in one process, create a scoped runtime instead of replacing the global client:
328
+
329
+ ```ts
330
+ import { createSqlClient } from "@onreza/sqlx-js";
331
+ import type { SqlxJsGeneratedRegistry } from "./sqlx-js-env";
332
+
333
+ const primary = createSqlClient<SqlxJsGeneratedRegistry>(process.env.DATABASE_URL);
334
+ const replica = createSqlClient<SqlxJsGeneratedRegistry>(process.env.REPLICA_DATABASE_URL);
335
+
336
+ await primary.sql(`INSERT INTO audit_log (message) VALUES ($1)`, "created");
337
+ const rows = await replica.sql(`SELECT id, message FROM audit_log ORDER BY id DESC`);
338
+
339
+ await Promise.all([primary.close(), replica.close()]);
340
+ ```
341
+
342
+ Each generated `sqlx-js-env.d.ts` exports its own `SqlxJsGeneratedRegistry`. Passing it to `createSqlClient<...>()` keeps a scoped client on that project's query contract even when a monorepo TypeScript program includes declarations for several databases. The global `sql` export remains available for the single-client convenience path.
343
+
344
+ The scanner recognizes clients assigned directly from an imported `createSqlClient(...)` (including aliased and namespace imports), so `client.sql(...)`, its cardinality helpers, file queries, and transactions participate in `prepare` exactly like the global `sql` surface.
345
+
327
346
  `createClient(url, options)` accepts every Postgres.js option plus sqlx-js runtime options:
328
347
 
329
348
  ```ts
@@ -333,6 +352,9 @@ setClient(createClient(process.env.DATABASE_URL, {
333
352
  statementTimeoutMs: 5000,
334
353
  // Base directory for root-relative sql.file(...) calls.
335
354
  fileRoot: import.meta.dirname,
355
+ // Development-only: re-stat sql.file() files on every call. The default
356
+ // immutable cache avoids synchronous filesystem work in the query hot path.
357
+ reloadSqlFiles: true,
336
358
  // Honored for every unsafe call. Set false for PgBouncer transaction mode
337
359
  // unless protocol-level prepared statements are configured there.
338
360
  prepare: false,
@@ -341,14 +363,15 @@ setClient(createClient(process.env.DATABASE_URL, {
341
363
  if (error) logger.error({ query, error }); // database errors are PgError
342
364
  else if (durationMs > 200) logger.warn({ slow: query, durationMs, rowCount });
343
365
  },
366
+ onQueryHookError: (error) => logger.error({ error }, "query observer failed"),
344
367
  }));
345
368
  ```
346
369
 
347
- The `onQuery` hook is the integration point for metrics, tracing, and slow-query logging — sqlx-js does not log queries itself. The event carries the raw `params`, which may contain personal or sensitive data — don't log them blindly; redact or omit `params` in shared sinks. Database errors are normalized to `PgError`; transport and non-database errors pass through unchanged.
370
+ The `onQuery` hook is the integration point for metrics, tracing, and slow-query logging — sqlx-js does not log queries itself. It is a non-blocking observer: synchronous throws and asynchronous rejections preserve the database result/error and are passed to `onQueryHookError` when configured. The event carries the raw `params`, which may contain personal or sensitive data — don't log them blindly; redact or omit `params` in shared sinks. Database errors are normalized to `PgError`; transport and non-database errors pass through unchanged.
348
371
 
349
372
  ### `clearSqlFileCache()`
350
373
 
351
- Drops the in-memory cache used by `sql.file(...)`. The cache invalidates automatically on file mtime change, so this is rarely needed manually.
374
+ Drops the in-memory cache used by `sql.file(...)`. Files are immutable after their first read by default, avoiding a synchronous `stat` call for every query. Call this after a development-time file change or set `reloadSqlFiles: true` on the client to restore mtime-based reloading.
352
375
 
353
376
  ### Typed errors
354
377
 
@@ -390,26 +413,28 @@ sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]
390
413
  sqlx-js doctor [--root <dir>] [--dts <path>] [--json]
391
414
  sqlx-js db install | check [--root <dir>]
392
415
  sqlx-js db plan | apply [--root <dir>] [-- <pgschema args>]
393
- sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
416
+ sqlx-js prepare [--check | --offline | --verify | --watch] [--json | --jsonl] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
394
417
  sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]
395
418
  sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
396
419
  sqlx-js schema check [--schema <path>] [--shadow-url <url>]
397
420
  sqlx-js --version | --help
398
421
  ```
399
422
 
400
- Regular `prepare` describes queries across a small connection pool (default 8, override with `SQLX_JS_PREPARE_CONCURRENCY`) for faster cold runs on large projects. Watch mode keeps one session warm and reuses it between debounced changes.
423
+ Regular `prepare` describes queries across a small connection pool (default 8, override with `SQLX_JS_PREPARE_CONCURRENCY`) for faster cold runs on large projects. Watch mode keeps one session warm, rescans only affected source files, and reuses cached metadata for unchanged fingerprints. Config, tsconfig, and applied shadow-migration changes invalidate the incremental state and perform a full prepare.
401
424
 
402
425
  | Flag | Meaning |
403
426
  |-----------------------|--------------------------------------------------------------------------------------|
404
- | `--check` | Offline: verify every scanned query is present in cache, no database required. |
427
+ | `--check` | Read-only offline verification of the active query cache, function catalog, and generated declaration. |
428
+ | `--offline` | Regenerate `sqlx-js-env.d.ts` from committed cache without a database. |
405
429
  | `--verify` | Prepare against the live/shadow schema and compare generated artifacts without writing. |
406
430
  | `--watch` | Persistent connection, re-prepare on file change. |
407
431
  | `--root <dir>` | Source/cache/migrations root (default: cwd). |
408
432
  | `--dts <path>` | Root-relative declarations output (default: `<root>/sqlx-js-env.d.ts`). |
409
- | `--no-prune` | Keep orphaned cache entries instead of removing them. |
433
+ | `--no-prune` | Keep orphaned cache entries; they do not invalidate a later `--check`. |
410
434
  | `--migrations <dir>` | Root-relative migrations directory (default: `<root>/migrations`). |
411
435
  | `--dry-run` | For `migrate run` / `migrate revert`: validate without applying to the target DB. |
412
436
  | `--json` | Machine-readable prepare diagnostics, doctor output, migration inspection and dry-runs. |
437
+ | `--jsonl` | Versioned streaming events for `prepare --watch`. |
413
438
  | `--strict-inference` | Fail prepare/dev/verify when nullability degrades or a generated query type contains `unknown`. |
414
439
  | `--force` | For `migrate archive restore`: allow overwriting existing migration files. |
415
440
  | `--lock-timeout <ms>` | Advisory-lock acquisition timeout for `migrate run` / `revert` / `dev` / `verify` / `squash`. |
@@ -424,9 +449,9 @@ Regular `prepare` describes queries across a small connection pool (default 8, o
424
449
 
425
450
  Flags that take a value accept both `--flag value` and `--flag=value` forms.
426
451
 
427
- Prepare and doctor JSON use `formatVersion: 1`. Prepare diagnostics include a stable phase plus root-relative file, 1-based line/column, PostgreSQL code/position/hint when available, and the query text. Degraded inference and generated `unknown` types appear as warnings by default; `--strict-inference` promotes them to errors. This is intended for CI annotations and editor integrations; stdout contains one JSON document and human progress is suppressed.
452
+ Prepare and doctor JSON use `formatVersion: 1`. Prepare diagnostics include a stable phase plus root-relative file, 1-based line/column, PostgreSQL code/position/hint when available, and the query text. Degraded inference and generated `unknown` types appear as warnings by default; `--strict-inference` promotes them to errors. This is intended for CI annotations and editor integrations; stdout contains one JSON document and human progress is suppressed. `prepare --watch --jsonl` emits one `start`, `diagnostic`, `prepared`, `error`, `watching`, or `stopping` event per line so an editor can consume diagnostics without waiting for the watch process to exit. Fatal `error` events include the same structured `diagnostic` object as CLI preflight failures, preserving the prepare phase and source location when available.
428
453
 
429
- `DATABASE_URL` must be set for any command that touches the application database or auto-creates a shadow database. `SHADOW_ADMIN_DATABASE_URL` can point at a maintenance/admin database when the application user cannot `CREATE DATABASE`; `SHADOW_DATABASE_URL` can point at a pre-created disposable shadow database. The internal wire client understands `sslmode`, `sslrootcert`, `sslcert`, `sslkey`, `application_name`, `connect_timeout` (seconds), and `statement_timeout` (milliseconds).
454
+ `DATABASE_URL` must be set for any command that touches the application database or auto-creates a shadow database. `SHADOW_ADMIN_DATABASE_URL` can point at a maintenance/admin database when the application user cannot `CREATE DATABASE`; `SHADOW_DATABASE_URL` can point at a pre-created disposable shadow database. The internal wire client understands `sslmode`, `sslrootcert`, `sslcert`, `sslkey`, `application_name`, `options` (PostgreSQL startup options such as `-c search_path=app,public`), `connect_timeout` (seconds), and `statement_timeout` (milliseconds). Unqualified relations are resolved using the prepare session's real `search_path`; they are not assumed to live in `public`.
430
455
 
431
456
  ### Development and deployment flows
432
457
 
@@ -664,7 +689,7 @@ Commit the generated `sqlx-js-env.d.ts` and the `.sqlx-js/` cache directory to y
664
689
  - run: sqlx-js db install
665
690
  - run: sqlx-js db plan -- --output-json plan.json
666
691
  - run: sqlx-js prepare --verify --strict-inference # live/shadow comparison with complete inference
667
- - run: sqlx-js prepare --check # offline cache/version/config consistency
692
+ - run: sqlx-js prepare --check # read-only offline cache/declaration consistency
668
693
  - run: sqlx-js doctor --json # runtime/config/DB/cache/tsconfig preflight
669
694
  - run: sqlx-js schema check # fails if the committed schema snapshot is stale
670
695
  - run: tsc --noEmit # fails if types are stale
@@ -672,7 +697,7 @@ Commit the generated `sqlx-js-env.d.ts` and the `.sqlx-js/` cache directory to y
672
697
  - run: bun run build # emits publishable JS + declarations under dist/
673
698
  ```
674
699
 
675
- The `migrate verify` step needs `DATABASE_URL` credentials that can either create a temporary database or use `--shadow-admin-url` / `--shadow-url`. It does not write `.sqlx-js/` or `sqlx-js-env.d.ts`. For pgschema projects, `sqlx-js db plan` checks the desired `schema.sql` against the target database and leaves application query typing to `prepare`. The `prepare --check` step then runs without a database; your committed offline cache is the source of truth. Add `prepare --verify` when CI has a canonical database/shadow schema and must prove byte-for-byte artifact freshness. `schema check` intentionally uses a live database because it verifies the committed schema contract against PostgreSQL.
700
+ The `migrate verify` step needs `DATABASE_URL` credentials that can either create a temporary database or use `--shadow-admin-url` / `--shadow-url`. It does not write `.sqlx-js/` or `sqlx-js-env.d.ts`. For pgschema projects, `sqlx-js db plan` checks the desired `schema.sql` against the target database and leaves application query typing to `prepare`. `prepare --check` is read-only and fails when either the committed cache or declaration is stale. Use `prepare --offline` when a developer intentionally needs to restore the declaration from a valid committed cache. Add `prepare --verify` when CI has a canonical database/shadow schema and must prove byte-for-byte artifact freshness. `schema check` intentionally uses a live database because it verifies the committed schema contract against PostgreSQL.
676
701
 
677
702
  The managed pgschema binary is installed under `node_modules/.cache/sqlx-js/pgschema/`, not `.sqlx-js/`, so it is not part of the committed offline cache.
678
703
 
@@ -699,7 +724,7 @@ Releases are automated via `release-please`: pushes to `main` accumulate into a
699
724
  - Column names whose **real** name (not an alias) ends with `!` or `?` are not supported — the runtime strips those suffixes assuming an override. Use `AS "alias"` if you have such a column.
700
725
  - Result columns must have unique names because Postgres.js returns object rows. Alias join projections such as `users.id AS user_id, posts.id AS post_id`; `prepare` rejects duplicate output names before generating declarations.
701
726
  - Migrations run inside `BEGIN/COMMIT`. DDL that disallows transactions (`CREATE INDEX CONCURRENTLY`, `VACUUM`, `REINDEX CONCURRENTLY`, …) will fail; split such operations into separate migrations executed outside the runner.
702
- - The **internal** wire client (used by `migrate run`, `prepare`, and the runtime `migrate()` helper) reads `sslmode`, `sslrootcert`/`sslcert`/`sslkey`, `application_name`, `connect_timeout`, and `statement_timeout` from `DATABASE_URL`. The default runtime `sql()` path delegates connection handling to Postgres.js; configure TLS, pooling, and timeouts through the `DATABASE_URL` and `createClient(...)` options it understands (`statementTimeoutMs` is a convenience that maps to a per-connection `statement_timeout`).
727
+ - The **internal** wire client (used by `migrate run`, `prepare`, and the runtime `migrate()` helper) reads `sslmode`, `sslrootcert`/`sslcert`/`sslkey`, `application_name`, `options`, `connect_timeout`, and `statement_timeout` from `DATABASE_URL`. The default runtime `sql()` path delegates connection handling to Postgres.js; configure TLS, pooling, and timeouts through the `DATABASE_URL` and `createClient(...)` options it understands (`statementTimeoutMs` is a convenience that maps to a per-connection `statement_timeout`).
703
728
  - `connect_timeout` bounds the entire internal-client connect, including the TLS handshake and SCRAM authentication.
704
729
  - Runtime `sql.file(path)` resolves against `fileRoot` while prepare resolves against `--root`. They are both root-relative, but applications started outside the project root must set `fileRoot` explicitly.
705
730
 
@@ -707,7 +732,7 @@ See [ROADMAP.md](./ROADMAP.md) for what's planned.
707
732
 
708
733
  ## Upgrading
709
734
 
710
- ### Cache and parameter contract change (pre-1.0)
735
+ ### Cache, codegen, and parameter contract changes (pre-1.0)
711
736
 
712
737
  Generated cache now includes `.sqlx-js/cache-manifest.json` with an explicit cache format, generator revision, and hash of `jsonbTypes` / `customTypes`. Cache without this manifest is rejected. Delete `.sqlx-js/` and re-run `sqlx-js prepare` against your database — there is no data loss because the cache is generated.
713
738
 
@@ -715,6 +740,10 @@ Generated JSON and PostgreSQL array parameters now require `sql.json(...)` and `
715
740
 
716
741
  CI (`prepare --check`) will also fail loudly until the cache is regenerated; this is intentional so a stale schema can't silently emit incorrect `.d.ts`.
717
742
 
743
+ Generator revision 4 changes the declaration layout so it exports `SqlxJsGeneratedRegistry` for scoped clients while continuing to augment the global `KnownQueries` convenience API. Re-run live `sqlx-js prepare` after upgrading. `prepare --check` is now strictly read-only; use `prepare --offline` when deliberate cache-to-declaration regeneration is required.
744
+
745
+ Runtime observers and SQL-file caching are also stricter production boundaries. An exception from `onQuery` no longer replaces a successful query result; handle it through `onQueryHookError`. `sql.file()` no longer performs an mtime check on every call—use `reloadSqlFiles: true` during development or call `clearSqlFileCache()` explicitly after changing a file.
746
+
718
747
  ## License
719
748
 
720
749
  MIT.
package/ROADMAP.md CHANGED
@@ -6,12 +6,12 @@ Items already shipped live in the [README](./README.md) feature list; this file
6
6
 
7
7
  | Feature | ROI | Notes |
8
8
  |---------|-----|-------|
9
- | pgschema integration hardening | 8 | Basic `init --schema-provider pgschema` plus managed `sqlx-js db install/check/plan/apply` exists. Continue with better docs, CI examples, schema snapshot handoff, and migration guidance for projects that outgrow built-in migrations. |
9
+ | pgschema integration hardening | 6 | Managed install/check/plan/apply now has a real pinned-binary PostgreSQL E2E in CI. Continue with schema snapshot handoff and migration guidance for projects that outgrow built-in migrations. |
10
10
  | Built-in migration lifecycle maintenance | 5 | Keep `migrate run/dev/verify/revert/squash/archive` stable for simple projects and application startup, but avoid expanding it into a full PostgreSQL schema-as-code system. |
11
11
  | Prisma migration assistant | 7 | Import Prisma Migrate SQL history and Prisma TypedSQL/raw SQL into `sqlx-js`; classify Prisma Client CRUD/nested-write sites as assisted/manual instead of promising a fully automatic ORM rewrite. |
12
12
  | Self-join precision (unqualified ColumnRef) | 4 | `SELECT name FROM users u1 JOIN users u2 ON ...` with unqualified `name` can't be attributed to a specific alias. PG would reject ambiguous unqualified refs anyway, but explicit aliasing currently has no narrowing benefit in self-joins. |
13
13
  | Tagged-template literal API (`` sql`SELECT ${x}` ``) | 8 | Restoring sqlx's inline-SQL aesthetic requires either a TS compiler plugin (`ts-patch`) or a Bun preload-time AST rewriter. TS itself hardcodes the first tag argument as `TemplateStringsArray` and refuses to narrow to literal tuples. Significant effort, large UX win. |
14
- | LSP server | 6 | Realtime diagnostics, hover with column types, autocomplete on schema names. Versioned `prepare --json` plus `sqlx-js-diagnostics` already cover GitHub annotations and editor problem matchers; a full server still needs separate VS Code / Neovim clients. |
14
+ | LSP server | 5 | Realtime hover and schema autocomplete. Versioned batch JSON, incremental `prepare --watch --jsonl`, and `sqlx-js-diagnostics` already provide the transport for editor problems; a full server still needs separate VS Code / Neovim clients. |
15
15
  | Schema-aware `jsonb` runtime validation | 5 | Optional opt-in: pass a Zod / Valibot / ArkType schema, validate rows on read. Currently we are compile-time-only by design. |
16
16
  | MySQL backend | 5 | Some runtime clients support it, but MySQL has no `Describe Statement` equivalent. Would need a real SQL parser pass + `INFORMATION_SCHEMA` introspection. |
17
17
  | SQLite backend | 4 | SQLite's column types are dynamic. Would require running `EXPLAIN` and a heuristic mapper, or schema-driven inference per-statement. |
@@ -24,7 +24,7 @@ usage:
24
24
  sqlx-js doctor [--root <dir>] [--dts <path>] [--json]
25
25
  sqlx-js db install | check [--root <dir>]
26
26
  sqlx-js db plan | apply [--root <dir>] [-- <pgschema args>]
27
- sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
27
+ sqlx-js prepare [--check | --offline | --verify | --watch] [--json | --jsonl] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
28
28
  sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]
29
29
  sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
30
30
  sqlx-js schema check [--schema <path>] [--shadow-url <url>]
@@ -32,20 +32,22 @@ usage:
32
32
  sqlx-js-diagnostics github|unix < prepare-diagnostics.json
33
33
 
34
34
  env:
35
- DATABASE_URL=postgres://... (supports sslmode, cert paths, application_name, connect_timeout, statement_timeout)
35
+ DATABASE_URL=postgres://... (supports sslmode, cert paths, application_name, options, connect_timeout, statement_timeout)
36
36
  SHADOW_DATABASE_URL=postgres://... (optional pre-created disposable shadow DB)
37
37
  SHADOW_ADMIN_DATABASE_URL=postgres://... (optional admin URL for auto-created shadow DBs)
38
38
 
39
39
  flags:
40
40
  --root <dir> scan root (default: cwd)
41
41
  --dts <path> declarations output (default: <root>/sqlx-js-env.d.ts)
42
- --check offline mode: verify scanned queries exist in cache, no DB
42
+ --check read-only offline verification of cache and declarations
43
+ --offline regenerate declarations from committed cache, no DB
43
44
  --verify prepare against DB/shadow and compare committed generated artifacts
44
45
  --watch re-prepare on file change (persistent PG connection)
45
46
  --no-prune keep orphaned cache entries (default: remove)
46
47
  --migrations <dir> migrations directory (default: <root>/migrations)
47
48
  --dry-run validate and print migrate run/revert plan without applying migrations
48
49
  --json machine-readable output for prepare and migration inspection/dry-run commands
50
+ --jsonl streaming machine-readable output for prepare --watch
49
51
  --strict-inference fail when query inference degrades or emits unknown types
50
52
  --force allow archive restore to overwrite existing migration files
51
53
  --lock-timeout <ms> advisory-lock acquisition timeout for migrate run/revert/dev/verify/squash
@@ -61,7 +63,7 @@ flags:
61
63
  init: `usage: sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]`,
62
64
  doctor: `usage: sqlx-js doctor [--root <dir>] [--dts <path>] [--json]`,
63
65
  db: `usage: sqlx-js db install | check [--root <dir>] | plan | apply [--root <dir>] [-- <pgschema args>]`,
64
- prepare: `usage: sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]`,
66
+ prepare: `usage: sqlx-js prepare [--check | --offline | --verify | --watch] [--json | --jsonl] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]`,
65
67
  migrate: `usage: sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]`,
66
68
  schema: `usage: sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>] | check [--schema <path>] [--shadow-url <url>]`,
67
69
  };
@@ -112,9 +114,11 @@ function optionsFor(command, subcommand) {
112
114
  ...ROOT_OPTIONS,
113
115
  dts: { type: "string" },
114
116
  check: { type: "boolean" },
117
+ offline: { type: "boolean" },
115
118
  verify: { type: "boolean" },
116
119
  watch: { type: "boolean" },
117
120
  json: { type: "boolean" },
121
+ jsonl: { type: "boolean" },
118
122
  "no-prune": { type: "boolean" },
119
123
  "shadow-url": { type: "string" },
120
124
  migrations: { type: "string" },
@@ -273,7 +277,7 @@ let envError;
273
277
  const needsEnvironment = cmd === "doctor" ||
274
278
  cmd === "schema" ||
275
279
  (cmd === "db" && (positionals[0] === "plan" || positionals[0] === "apply")) ||
276
- (cmd === "prepare" && !flag("--check")) ||
280
+ (cmd === "prepare" && !flag("--check") && !flag("--offline")) ||
277
281
  (cmd === "migrate" && !["add", "check", "archive"].includes(positionals[0]));
278
282
  if (needsEnvironment) {
279
283
  try {
@@ -343,12 +347,22 @@ else if (cmd === "db") {
343
347
  else if (cmd === "prepare") {
344
348
  const { PrepareFatalError, runPrepare } = await import("../src/commands/prepare.js");
345
349
  const prepareCheck = flag("--check");
350
+ const prepareOffline = flag("--offline");
346
351
  const prepareVerify = flag("--verify");
347
352
  const prepareWatch = flag("--watch");
348
353
  const prepareJson = flag("--json");
349
- const prepareMode = prepareVerify ? "verify" : prepareCheck ? "check" : "prepare";
354
+ const prepareJsonl = flag("--jsonl");
355
+ const prepareMode = prepareVerify ? "verify" : prepareCheck ? "check" : prepareOffline ? "offline" : "prepare";
350
356
  const failPrepare = (message, phase, exitCode = 2, location = {}) => {
351
- if (prepareJson) {
357
+ if (prepareJsonl) {
358
+ console.log(JSON.stringify({
359
+ formatVersion: 1,
360
+ event: "error",
361
+ timestamp: new Date().toISOString(),
362
+ diagnostic: { severity: "error", phase, message, ...location },
363
+ }));
364
+ }
365
+ else if (prepareJson) {
352
366
  console.log(JSON.stringify({
353
367
  formatVersion: 1,
354
368
  ok: false,
@@ -366,22 +380,28 @@ else if (cmd === "prepare") {
366
380
  }
367
381
  process.exit(exitCode);
368
382
  };
369
- if ([prepareCheck, prepareVerify, prepareWatch].filter(Boolean).length > 1) {
370
- failPrepare("--check, --verify, and --watch are mutually exclusive", "config");
383
+ if ([prepareCheck, prepareOffline, prepareVerify, prepareWatch].filter(Boolean).length > 1) {
384
+ failPrepare("--check, --offline, --verify, and --watch are mutually exclusive", "config");
371
385
  }
372
- if (prepareCheck && shadowUrlArg) {
373
- failPrepare("--shadow-url cannot be used with prepare --check; use live prepare or schema check --shadow-url", "config");
386
+ if ((prepareCheck || prepareOffline) && shadowUrlArg) {
387
+ failPrepare("--shadow-url cannot be used with offline prepare modes; use live prepare or schema check --shadow-url", "config");
374
388
  }
375
389
  if (prepareWatch && prepareJson) {
376
390
  failPrepare("--watch and --json are mutually exclusive", "config");
377
391
  }
378
- if ((prepareCheck || prepareVerify) && flag("--no-prune")) {
379
- failPrepare("--no-prune is only supported by prepare and prepare --watch", "config");
392
+ if (prepareJson && prepareJsonl) {
393
+ failPrepare("--json and --jsonl are mutually exclusive", "config");
394
+ }
395
+ if (prepareJsonl && !prepareWatch) {
396
+ failPrepare("--jsonl is only supported by prepare --watch", "config");
397
+ }
398
+ if ((prepareCheck || prepareOffline || prepareVerify) && flag("--no-prune")) {
399
+ failPrepare("--no-prune is only supported by live prepare and prepare --watch", "config");
380
400
  }
381
- const prepareShadowUrl = prepareCheck ? undefined : shadowUrl;
401
+ const prepareShadowUrl = prepareCheck || prepareOffline ? undefined : shadowUrl;
382
402
  const prepareDatabaseUrl = prepareShadowUrl ?? databaseUrl;
383
- if (!prepareCheck && !prepareDatabaseUrl) {
384
- failPrepare("DATABASE_URL is required for prepare (use --check for offline)", "connect");
403
+ if (!prepareCheck && !prepareOffline && !prepareDatabaseUrl) {
404
+ failPrepare("DATABASE_URL is required for prepare (use --check or --offline without a database)", "connect");
385
405
  }
386
406
  const opts = {
387
407
  root,
@@ -389,6 +409,7 @@ else if (cmd === "prepare") {
389
409
  cacheDir,
390
410
  dtsPath,
391
411
  check: prepareCheck,
412
+ offline: prepareOffline,
392
413
  verify: prepareVerify,
393
414
  json: prepareJson,
394
415
  prune: !flag("--no-prune"),
@@ -401,6 +422,7 @@ else if (cmd === "prepare") {
401
422
  const { runWatch } = await import("../src/commands/watch.js");
402
423
  await runWatch({
403
424
  ...opts,
425
+ jsonl: prepareJsonl,
404
426
  ...(prepareShadowUrl
405
427
  ? {
406
428
  beforePrepare: async () => {
@@ -1,5 +1,5 @@
1
1
  export declare const CACHE_FORMAT_VERSION = 2;
2
- export declare const GENERATOR_REVISION = 3;
2
+ export declare const GENERATOR_REVISION = 4;
3
3
  export declare const CACHE_MANIFEST_FILE = "cache-manifest.json";
4
4
  export type CacheManifest = {
5
5
  cacheFormat: typeof CACHE_FORMAT_VERSION;
package/dist/src/cache.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlink
3
3
  import { join } from "node:path";
4
4
  import { isBuiltinOid } from "./pg/oids.js";
5
5
  export const CACHE_FORMAT_VERSION = 2;
6
- export const GENERATOR_REVISION = 3;
6
+ export const GENERATOR_REVISION = 4;
7
7
  export const CACHE_MANIFEST_FILE = "cache-manifest.json";
8
8
  export function portableCacheOid(oid) {
9
9
  return isBuiltinOid(oid) ? oid : 0;
@@ -23,7 +23,9 @@ export function emitDts(outPath, entries, functions = []) {
23
23
  lines.push("// AUTO-GENERATED by sqlx-js. Do not edit.");
24
24
  lines.push("// Run `sqlx-js prepare` to regenerate.");
25
25
  lines.push("");
26
- emitModule(lines, "@onreza/sqlx-js", entries, functions);
26
+ emitRegistry(lines, entries, functions);
27
+ lines.push("");
28
+ emitModuleAugmentation(lines, "@onreza/sqlx-js");
27
29
  lines.push("");
28
30
  lines.push("export {};");
29
31
  const tmp = `${outPath}.tmp-${randomBytes(4).toString("hex")}`;
@@ -39,9 +41,8 @@ export function emitDts(outPath, entries, functions = []) {
39
41
  throw err;
40
42
  }
41
43
  }
42
- function emitModule(lines, moduleName, entries, functions) {
43
- lines.push(`declare module ${JSON.stringify(moduleName)} {`);
44
- lines.push(" interface KnownQueries {");
44
+ function emitRegistry(lines, entries, functions) {
45
+ lines.push("export interface SqlxJsGeneratedQueries {");
45
46
  const inlineSeen = new Set();
46
47
  const inlinePairs = [];
47
48
  for (const e of entries) {
@@ -58,12 +59,12 @@ function emitModule(lines, moduleName, entries, functions) {
58
59
  inlineSeen.add(query);
59
60
  const { params, row } = entrySignature(entry);
60
61
  if (entry.degraded)
61
- lines.push(` /** Nullability inference degraded: ${entry.degraded.reason}. All result columns conservatively typed as nullable. */`);
62
- lines.push(` ${JSON.stringify(query)}: { params: ${params}; row: ${row} };`);
62
+ lines.push(` /** Nullability inference degraded: ${entry.degraded.reason}. All result columns conservatively typed as nullable. */`);
63
+ lines.push(` ${JSON.stringify(query)}: { params: ${params}; row: ${row} };`);
63
64
  }
64
- lines.push(" }");
65
+ lines.push("}");
65
66
  lines.push("");
66
- lines.push(" interface KnownFileQueries {");
67
+ lines.push("export interface SqlxJsGeneratedFileQueries {");
67
68
  const filePairs = [];
68
69
  for (const e of entries) {
69
70
  if (!e.filePaths || e.filePaths.length === 0)
@@ -78,21 +79,33 @@ function emitModule(lines, moduleName, entries, functions) {
78
79
  filePathsSeen.add(path);
79
80
  const { params, row } = entrySignature(entry);
80
81
  if (entry.degraded)
81
- lines.push(` /** Nullability inference degraded: ${entry.degraded.reason}. */`);
82
- lines.push(` ${JSON.stringify(path)}: { params: ${params}; row: ${row} };`);
82
+ lines.push(` /** Nullability inference degraded: ${entry.degraded.reason}. */`);
83
+ lines.push(` ${JSON.stringify(path)}: { params: ${params}; row: ${row} };`);
83
84
  }
84
- lines.push(" }");
85
+ lines.push("}");
85
86
  lines.push("");
86
- lines.push(" interface KnownFunctions {");
87
+ lines.push("export interface SqlxJsGeneratedFunctions {");
87
88
  const functionSeen = new Set();
88
89
  for (const fn of functions.slice().sort((a, b) => a.signature.localeCompare(b.signature))) {
89
90
  if (functionSeen.has(fn.signature))
90
91
  continue;
91
92
  functionSeen.add(fn.signature);
92
93
  const params = inputParams(fn);
93
- lines.push(` ${JSON.stringify(fn.signature)}: { kind: ${JSON.stringify(fn.kind)}; params: ${params}; returns: ${fn.returns}; returnsSet: ${fn.returnsSet} };`);
94
+ lines.push(` ${JSON.stringify(fn.signature)}: { kind: ${JSON.stringify(fn.kind)}; params: ${params}; returns: ${fn.returns}; returnsSet: ${fn.returnsSet} };`);
94
95
  }
95
- lines.push(" }");
96
+ lines.push("}");
97
+ lines.push("");
98
+ lines.push("export interface SqlxJsGeneratedRegistry {");
99
+ lines.push(" queries: SqlxJsGeneratedQueries;");
100
+ lines.push(" fileQueries: SqlxJsGeneratedFileQueries;");
101
+ lines.push(" functions: SqlxJsGeneratedFunctions;");
102
+ lines.push("}");
103
+ }
104
+ function emitModuleAugmentation(lines, moduleName) {
105
+ lines.push(`declare module ${JSON.stringify(moduleName)} {`);
106
+ lines.push(" interface KnownQueries extends SqlxJsGeneratedQueries {}");
107
+ lines.push(" interface KnownFileQueries extends SqlxJsGeneratedFileQueries {}");
108
+ lines.push(" interface KnownFunctions extends SqlxJsGeneratedFunctions {}");
96
109
  lines.push("}");
97
110
  }
98
111
  function inputParams(fn) {
@@ -36,10 +36,19 @@ const PGSCHEMA_TEMPLATE = `-- Desired PostgreSQL schema managed by pgschema.
36
36
  const DTS_TEMPLATE = `// AUTO-GENERATED by sqlx-js. Do not edit.
37
37
  // Run \`sqlx-js prepare\` to regenerate.
38
38
 
39
+ export interface SqlxJsGeneratedQueries {}
40
+ export interface SqlxJsGeneratedFileQueries {}
41
+ export interface SqlxJsGeneratedFunctions {}
42
+ export interface SqlxJsGeneratedRegistry {
43
+ queries: SqlxJsGeneratedQueries;
44
+ fileQueries: SqlxJsGeneratedFileQueries;
45
+ functions: SqlxJsGeneratedFunctions;
46
+ }
47
+
39
48
  declare module "@onreza/sqlx-js" {
40
- interface KnownQueries {}
41
- interface KnownFileQueries {}
42
- interface KnownFunctions {}
49
+ interface KnownQueries extends SqlxJsGeneratedQueries {}
50
+ interface KnownFileQueries extends SqlxJsGeneratedFileQueries {}
51
+ interface KnownFunctions extends SqlxJsGeneratedFunctions {}
43
52
  }
44
53
 
45
54
  export {};
@@ -132,6 +141,7 @@ export function runInit(opts) {
132
141
  const defaults = {
133
142
  "sqlx:prepare": "sqlx-js prepare",
134
143
  "sqlx:check": "sqlx-js prepare --check",
144
+ "sqlx:offline": "sqlx-js prepare --offline",
135
145
  "sqlx:verify": "sqlx-js prepare --verify --strict-inference",
136
146
  };
137
147
  let changed = false;
@@ -1,5 +1,6 @@
1
1
  import { PgClient, type ConnConfig, type FieldDescription } from "../pg/wire.js";
2
2
  import { SchemaCache } from "../pg/schema.js";
3
+ import { type QueryCallSite } from "../scan/scanner.js";
3
4
  import { type SqlxJsConfig } from "../config.js";
4
5
  export type PrepareOptions = {
5
6
  root: string;
@@ -7,6 +8,7 @@ export type PrepareOptions = {
7
8
  cacheDir: string;
8
9
  dtsPath: string;
9
10
  check: boolean;
11
+ offline?: boolean;
10
12
  verify?: boolean;
11
13
  json?: boolean;
12
14
  prune?: boolean;
@@ -49,6 +51,11 @@ export type PrepareSession = {
49
51
  schema: SchemaCache;
50
52
  userCfg: SqlxJsConfig;
51
53
  };
54
+ export type PrepareIncrementalInput = {
55
+ sites?: QueryCallSite[];
56
+ reuseCacheFps?: ReadonlySet<string>;
57
+ reuseFunctions?: boolean;
58
+ };
52
59
  export declare function openSession(opts: PrepareOptions): Promise<PrepareSession>;
53
60
  type DescribeOutcome = {
54
61
  ok: true;
@@ -63,7 +70,7 @@ export declare function describeAll(cfg: ConnConfig, sessionClient: PgClient, qu
63
70
  fp: string;
64
71
  query: string;
65
72
  }[], concurrency: number): Promise<Map<string, DescribeOutcome>>;
66
- export declare function prepareOnce(opts: PrepareOptions, session: PrepareSession, log?: (msg: string) => void, err?: (msg: string) => void, concurrency?: number): Promise<PrepareResult>;
73
+ export declare function prepareOnce(opts: PrepareOptions, session: PrepareSession, log?: (msg: string) => void, err?: (msg: string) => void, concurrency?: number, input?: PrepareIncrementalInput): Promise<PrepareResult>;
67
74
  export declare function runPrepare(opts: PrepareOptions): Promise<void>;
68
75
  export declare function verifyPrepareArtifacts(opts: PrepareOptions, log?: (msg: string) => void, err?: (msg: string) => void): Promise<{
69
76
  ok: boolean;