@onreza/sqlx-js 0.5.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 +99 -30
- package/ROADMAP.md +2 -2
- package/dist/bin/sqlx-js-diagnostics.d.ts +2 -0
- package/dist/bin/sqlx-js-diagnostics.js +96 -0
- package/dist/bin/sqlx-js.js +291 -92
- package/dist/src/cache.d.ts +1 -1
- package/dist/src/cache.js +1 -1
- package/dist/src/codegen.js +27 -14
- package/dist/src/commands/init.js +97 -3
- package/dist/src/commands/migrate.d.ts +2 -101
- package/dist/src/commands/migrate.js +6 -546
- package/dist/src/commands/prepare.d.ts +10 -2
- package/dist/src/commands/prepare.js +201 -33
- package/dist/src/commands/schema.js +1 -1
- package/dist/src/commands/watch.d.ts +14 -6
- package/dist/src/commands/watch.js +184 -21
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +8 -0
- package/dist/src/function-cache.d.ts +2 -0
- package/dist/src/function-cache.js +6 -3
- package/dist/src/index.d.ts +17 -1
- package/dist/src/index.js +3 -0
- package/dist/src/migration-core.d.ts +157 -0
- package/dist/src/migration-core.js +578 -0
- package/dist/src/pg/schema.d.ts +1 -0
- package/dist/src/pg/schema.js +42 -31
- package/dist/src/pg/wire.d.ts +1 -0
- package/dist/src/pg/wire.js +6 -0
- package/dist/src/postgres-runtime.d.ts +11 -1
- package/dist/src/postgres-runtime.js +22 -5
- package/dist/src/runtime.d.ts +5 -2
- package/dist/src/runtime.js +34 -13
- package/dist/src/scan/scanner.d.ts +1 -1
- package/dist/src/scan/scanner.js +84 -17
- package/dist/src/type-inspection.d.ts +1 -0
- package/dist/src/type-inspection.js +20 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -43,9 +43,11 @@ 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
|
-
- **
|
|
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
|
+
- **Strict inference gate** promotes degraded nullability analysis and generated `unknown` query types to CI errors.
|
|
50
|
+
- **GitHub/editor diagnostics adapter** converts versioned prepare JSON into workflow annotations or Unix problem-matcher output.
|
|
49
51
|
|
|
50
52
|
## Install
|
|
51
53
|
|
|
@@ -57,7 +59,7 @@ bun add @onreza/sqlx-js
|
|
|
57
59
|
|
|
58
60
|
Node.js 24 or newer is required. Bun users need Bun 1.3 or newer.
|
|
59
61
|
|
|
60
|
-
The package installs
|
|
62
|
+
The package installs `sqlx-js` and `sqlx-js-diagnostics` binaries. The CLI examples below use `npx @onreza/sqlx-js`; `bunx @onreza/sqlx-js ...` works the same if your project uses Bun.
|
|
61
63
|
|
|
62
64
|
## Setup
|
|
63
65
|
|
|
@@ -67,7 +69,7 @@ The package installs a `sqlx-js` binary. The CLI examples below use `npx @onreza
|
|
|
67
69
|
npx @onreza/sqlx-js init
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
Creates `sqlx-js.config.ts`, a `migrations/` directory, and `.env.example` if they don't already exist
|
|
72
|
+
Creates `sqlx-js.config.ts`, `sqlx-js-env.d.ts`, a `migrations/` directory, and `.env.example` if they don't already exist. For strict-JSON files it adds missing `sqlx:*` scripts to `package.json` and appends the declaration to an existing `files` or `include` array in `tsconfig.json`, without replacing existing values; JSONC files are left unchanged with a manual-update hint. Skip it if you prefer to wire things up manually.
|
|
71
73
|
|
|
72
74
|
For declarative PostgreSQL schema management, scaffold the pgschema workflow instead:
|
|
73
75
|
|
|
@@ -144,7 +146,7 @@ const users = await sql(
|
|
|
144
146
|
npx @onreza/sqlx-js prepare
|
|
145
147
|
```
|
|
146
148
|
|
|
147
|
-
This generates `sqlx-js-env.d.ts` next to your code. Add it to your `tsconfig.json` `include` if it isn't picked up automatically. Use `--dts <path>` to override the destination
|
|
149
|
+
This generates `sqlx-js-env.d.ts` next to your code. Add it to your `tsconfig.json` `include` if it isn't picked up automatically. Use `--dts <path>` to override the destination relative to `--root`.
|
|
148
150
|
|
|
149
151
|
### 5. Dev loop with watch
|
|
150
152
|
|
|
@@ -322,6 +324,25 @@ import { createClient, setClient } from "@onreza/sqlx-js";
|
|
|
322
324
|
setClient(createClient(process.env.DATABASE_URL));
|
|
323
325
|
```
|
|
324
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
|
+
|
|
325
346
|
`createClient(url, options)` accepts every Postgres.js option plus sqlx-js runtime options:
|
|
326
347
|
|
|
327
348
|
```ts
|
|
@@ -331,6 +352,9 @@ setClient(createClient(process.env.DATABASE_URL, {
|
|
|
331
352
|
statementTimeoutMs: 5000,
|
|
332
353
|
// Base directory for root-relative sql.file(...) calls.
|
|
333
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,
|
|
334
358
|
// Honored for every unsafe call. Set false for PgBouncer transaction mode
|
|
335
359
|
// unless protocol-level prepared statements are configured there.
|
|
336
360
|
prepare: false,
|
|
@@ -339,14 +363,15 @@ setClient(createClient(process.env.DATABASE_URL, {
|
|
|
339
363
|
if (error) logger.error({ query, error }); // database errors are PgError
|
|
340
364
|
else if (durationMs > 200) logger.warn({ slow: query, durationMs, rowCount });
|
|
341
365
|
},
|
|
366
|
+
onQueryHookError: (error) => logger.error({ error }, "query observer failed"),
|
|
342
367
|
}));
|
|
343
368
|
```
|
|
344
369
|
|
|
345
|
-
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.
|
|
346
371
|
|
|
347
372
|
### `clearSqlFileCache()`
|
|
348
373
|
|
|
349
|
-
Drops the in-memory cache used by `sql.file(...)`.
|
|
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.
|
|
350
375
|
|
|
351
376
|
### Typed errors
|
|
352
377
|
|
|
@@ -385,43 +410,48 @@ In addition to `import { sql } from "@onreza/sqlx-js"`, the scanner recognises `
|
|
|
385
410
|
|
|
386
411
|
```
|
|
387
412
|
sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]
|
|
388
|
-
sqlx-js doctor [--root <dir>] [--json]
|
|
389
|
-
sqlx-js db install | check
|
|
390
|
-
sqlx-js
|
|
391
|
-
sqlx-js
|
|
392
|
-
sqlx-js
|
|
413
|
+
sqlx-js doctor [--root <dir>] [--dts <path>] [--json]
|
|
414
|
+
sqlx-js db install | check [--root <dir>]
|
|
415
|
+
sqlx-js db plan | apply [--root <dir>] [-- <pgschema args>]
|
|
416
|
+
sqlx-js prepare [--check | --offline | --verify | --watch] [--json | --jsonl] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
|
|
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]
|
|
418
|
+
sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
|
|
419
|
+
sqlx-js schema check [--schema <path>] [--shadow-url <url>]
|
|
393
420
|
sqlx-js --version | --help
|
|
394
421
|
```
|
|
395
422
|
|
|
396
|
-
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
|
|
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.
|
|
397
424
|
|
|
398
425
|
| Flag | Meaning |
|
|
399
426
|
|-----------------------|--------------------------------------------------------------------------------------|
|
|
400
|
-
| `--check` |
|
|
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. |
|
|
401
429
|
| `--verify` | Prepare against the live/shadow schema and compare generated artifacts without writing. |
|
|
402
430
|
| `--watch` | Persistent connection, re-prepare on file change. |
|
|
403
431
|
| `--root <dir>` | Source/cache/migrations root (default: cwd). |
|
|
404
|
-
| `--dts <path>` |
|
|
405
|
-
| `--no-prune` | Keep orphaned cache entries
|
|
406
|
-
| `--migrations <dir>` |
|
|
432
|
+
| `--dts <path>` | Root-relative declarations output (default: `<root>/sqlx-js-env.d.ts`). |
|
|
433
|
+
| `--no-prune` | Keep orphaned cache entries; they do not invalidate a later `--check`. |
|
|
434
|
+
| `--migrations <dir>` | Root-relative migrations directory (default: `<root>/migrations`). |
|
|
407
435
|
| `--dry-run` | For `migrate run` / `migrate revert`: validate without applying to the target DB. |
|
|
408
436
|
| `--json` | Machine-readable prepare diagnostics, doctor output, migration inspection and dry-runs. |
|
|
437
|
+
| `--jsonl` | Versioned streaming events for `prepare --watch`. |
|
|
438
|
+
| `--strict-inference` | Fail prepare/dev/verify when nullability degrades or a generated query type contains `unknown`. |
|
|
409
439
|
| `--force` | For `migrate archive restore`: allow overwriting existing migration files. |
|
|
410
440
|
| `--lock-timeout <ms>` | Advisory-lock acquisition timeout for `migrate run` / `revert` / `dev` / `verify` / `squash`. |
|
|
411
441
|
| `--shadow-url <url>` | Use an existing disposable shadow DB instead of auto-creating one. |
|
|
412
442
|
| `--shadow-admin-url <url>` | Admin/maintenance DB URL used to auto-create shadow DBs. |
|
|
413
443
|
| `--replace` | For `migrate squash`: archive replaced migration files after writing the baseline. |
|
|
414
444
|
| `--pg-dump <path>` | For `migrate squash`: `pg_dump` executable path (default: `pg_dump`). |
|
|
415
|
-
| `--schema <path>` |
|
|
416
|
-
| `--manifest <path>` | LLM schema manifest path (default: `<root>/.sqlx-js/schema/schema.md`).
|
|
445
|
+
| `--schema <path>` | Root-relative schema snapshot path (default: `<root>/.sqlx-js/schema/schema.json`). |
|
|
446
|
+
| `--manifest <path>` | Root-relative LLM schema manifest path (default: `<root>/.sqlx-js/schema/schema.md`). |
|
|
417
447
|
| `--no-manifest` | Skip writing the LLM schema manifest during `schema dump`. |
|
|
418
448
|
| `--schema-provider <name>` | For `init`: `builtin` (default) or `pgschema`. |
|
|
419
449
|
|
|
420
450
|
Flags that take a value accept both `--flag value` and `--flag=value` forms.
|
|
421
451
|
|
|
422
|
-
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. 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.
|
|
423
453
|
|
|
424
|
-
`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`.
|
|
425
455
|
|
|
426
456
|
### Development and deployment flows
|
|
427
457
|
|
|
@@ -454,7 +484,7 @@ The built-in `migrate` workflow is kept for simple projects and embedded applica
|
|
|
454
484
|
Use `migrate verify` in PR/CI before merge:
|
|
455
485
|
|
|
456
486
|
```bash
|
|
457
|
-
sqlx-js migrate verify
|
|
487
|
+
sqlx-js migrate verify --strict-inference
|
|
458
488
|
sqlx-js prepare --check
|
|
459
489
|
sqlx-js doctor --json
|
|
460
490
|
tsc --noEmit
|
|
@@ -514,6 +544,39 @@ When `prepare` fails, every diagnostic points back to the originating call site:
|
|
|
514
544
|
|
|
515
545
|
Phases reported separately: `describe failed`, `analyze failed`, `paramMap failed`. PostgreSQL `position`, `code`, and `hint` are surfaced when present.
|
|
516
546
|
|
|
547
|
+
### GitHub and editor diagnostics
|
|
548
|
+
|
|
549
|
+
`sqlx-js-diagnostics` converts the versioned prepare JSON document into GitHub workflow commands or a standard Unix problem-matcher stream:
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
set -o pipefail
|
|
553
|
+
sqlx-js prepare --verify --json | sqlx-js-diagnostics github
|
|
554
|
+
sqlx-js prepare --check --json | sqlx-js-diagnostics unix
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The `github` format creates inline workflow annotations. The `unix` format emits `file:line:column: severity: [phase] message`, which can be consumed by VS Code tasks and other editors without a dedicated extension. A minimal VS Code task uses a custom problem matcher:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"label": "sqlx-js: check",
|
|
562
|
+
"type": "shell",
|
|
563
|
+
"command": "sqlx-js prepare --check --json | sqlx-js-diagnostics unix",
|
|
564
|
+
"problemMatcher": {
|
|
565
|
+
"owner": "sqlx-js",
|
|
566
|
+
"fileLocation": ["relative", "${workspaceFolder}"],
|
|
567
|
+
"pattern": {
|
|
568
|
+
"regexp": "^(.+):(\\d+):(\\d+): (error|warning): \\[([^\\]]+)\\] (.*)$",
|
|
569
|
+
"file": 1,
|
|
570
|
+
"line": 2,
|
|
571
|
+
"column": 3,
|
|
572
|
+
"severity": 4,
|
|
573
|
+
"code": 5,
|
|
574
|
+
"message": 6
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
517
580
|
## Configuration
|
|
518
581
|
|
|
519
582
|
`sqlx-js.config.ts` at the project root is optional.
|
|
@@ -527,6 +590,7 @@ export default defineConfig({
|
|
|
527
590
|
scan: {
|
|
528
591
|
include: ["apps/*/src/**/*", "packages/*/src/**/*"],
|
|
529
592
|
exclude: ["**/*.generated.ts"],
|
|
593
|
+
modules: ["@onreza/sqlx-js", "@app/database"],
|
|
530
594
|
},
|
|
531
595
|
schema: {
|
|
532
596
|
provider: "pgschema",
|
|
@@ -541,7 +605,7 @@ export default defineConfig({
|
|
|
541
605
|
});
|
|
542
606
|
```
|
|
543
607
|
|
|
544
|
-
By default the scanner uses the root `tsconfig.json` file list and follows TypeScript project references, so a referenced monorepo is scanned without walking unrelated folders. `scan.include` replaces that source-file universe with TypeScript glob patterns; `scan.exclude` is added to the built-in dependency/build exclusions. If there is no root `tsconfig.json`, the fallback is a recursive TypeScript scan.
|
|
608
|
+
By default the scanner uses the root `tsconfig.json` file list and follows TypeScript project references, so a referenced monorepo is scanned without walking unrelated folders. `scan.include` replaces that source-file universe with TypeScript glob patterns; `scan.exclude` is added to the built-in dependency/build exclusions. `scan.modules` replaces the default `@onreza/sqlx-js` import source list, which lets an application re-export `sql` through a shared database module without requiring arbitrary re-export graph analysis. Include `@onreza/sqlx-js` explicitly when direct imports and application-module imports are both used. If there is no root `tsconfig.json`, the fallback is a recursive TypeScript scan.
|
|
545
609
|
|
|
546
610
|
The `schema` block is optional. Use `provider: "pgschema"` when sqlx-js should delegate schema planning/apply commands to pgschema. `command` can override the managed binary lookup and point at another executable. With the pinned pgschema 1.12.0 CLI, `schemas` must contain exactly one schema name.
|
|
547
611
|
|
|
@@ -620,12 +684,12 @@ Commit the generated `sqlx-js-env.d.ts` and the `.sqlx-js/` cache directory to y
|
|
|
620
684
|
|
|
621
685
|
```yaml
|
|
622
686
|
- run: bun install
|
|
623
|
-
- run: sqlx-js migrate verify
|
|
687
|
+
- run: sqlx-js migrate verify --strict-inference # built-in migration workflow
|
|
624
688
|
# or, when schema.provider is "pgschema":
|
|
625
689
|
- run: sqlx-js db install
|
|
626
690
|
- run: sqlx-js db plan -- --output-json plan.json
|
|
627
|
-
- run: sqlx-js prepare --verify
|
|
628
|
-
- run: sqlx-js prepare --check # offline cache/
|
|
691
|
+
- run: sqlx-js prepare --verify --strict-inference # live/shadow comparison with complete inference
|
|
692
|
+
- run: sqlx-js prepare --check # read-only offline cache/declaration consistency
|
|
629
693
|
- run: sqlx-js doctor --json # runtime/config/DB/cache/tsconfig preflight
|
|
630
694
|
- run: sqlx-js schema check # fails if the committed schema snapshot is stale
|
|
631
695
|
- run: tsc --noEmit # fails if types are stale
|
|
@@ -633,7 +697,7 @@ Commit the generated `sqlx-js-env.d.ts` and the `.sqlx-js/` cache directory to y
|
|
|
633
697
|
- run: bun run build # emits publishable JS + declarations under dist/
|
|
634
698
|
```
|
|
635
699
|
|
|
636
|
-
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`.
|
|
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.
|
|
637
701
|
|
|
638
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.
|
|
639
703
|
|
|
@@ -646,20 +710,21 @@ bun install # installs lefthook + wires git hooks
|
|
|
646
710
|
cargo install cocogitto # or: brew install cocogitto
|
|
647
711
|
```
|
|
648
712
|
|
|
649
|
-
Releases are automated via `release-please`: pushes to `main` accumulate into a release PR that bumps `package.json
|
|
713
|
+
Releases are automated via `release-please`: pushes to `main` accumulate into a release PR that bumps `package.json` and writes `CHANGELOG.md`. Merging that PR creates the tag and release, then the same workflow builds `dist/`, smoke-tests the package entrypoints, checks the tarball contents, and publishes to npm through Trusted Publishing.
|
|
650
714
|
|
|
651
715
|
## Limitations
|
|
652
716
|
|
|
653
717
|
`sqlx-js` is a young library. Known gaps:
|
|
654
718
|
|
|
655
719
|
- PostgreSQL only (no MySQL or SQLite).
|
|
656
|
-
- The scanner only follows direct named imports and namespace imports from `@onreza/sqlx-js
|
|
720
|
+
- The scanner only follows direct named imports and namespace imports from configured `scan.modules` (default: `@onreza/sqlx-js`); it does not discover re-export graphs, dynamic aliases, or tagged-template calls automatically.
|
|
657
721
|
- `SELECT *` falls back to conservative nullability.
|
|
658
722
|
- Plain `sql(...)` keeps returning rows, so statements without `RETURNING` produce an empty typed array. Use `sql.execute(...)` when affected-row count and command metadata matter.
|
|
659
723
|
- Nested CTE references (CTE-`b` referencing CTE-`a` in the same `WITH`) and `WITH RECURSIVE` are not analysed transitively — at worst this produces extra `T | null`. Use `AS "id!"` overrides if needed.
|
|
660
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.
|
|
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.
|
|
661
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.
|
|
662
|
-
- 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`).
|
|
663
728
|
- `connect_timeout` bounds the entire internal-client connect, including the TLS handshake and SCRAM authentication.
|
|
664
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.
|
|
665
730
|
|
|
@@ -667,7 +732,7 @@ See [ROADMAP.md](./ROADMAP.md) for what's planned.
|
|
|
667
732
|
|
|
668
733
|
## Upgrading
|
|
669
734
|
|
|
670
|
-
### Cache and parameter contract
|
|
735
|
+
### Cache, codegen, and parameter contract changes (pre-1.0)
|
|
671
736
|
|
|
672
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.
|
|
673
738
|
|
|
@@ -675,6 +740,10 @@ Generated JSON and PostgreSQL array parameters now require `sql.json(...)` and `
|
|
|
675
740
|
|
|
676
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`.
|
|
677
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
|
+
|
|
678
747
|
## License
|
|
679
748
|
|
|
680
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 |
|
|
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 |
|
|
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. |
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function optionalString(value, name) {
|
|
6
|
+
if (value !== undefined && typeof value !== "string")
|
|
7
|
+
throw new Error(`${name} must be a string`);
|
|
8
|
+
}
|
|
9
|
+
function optionalPosition(value, name) {
|
|
10
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 1)) {
|
|
11
|
+
throw new Error(`${name} must be a positive integer`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function parsePayload(value) {
|
|
15
|
+
if (!isRecord(value) || value.formatVersion !== 1 || typeof value.ok !== "boolean" || !Array.isArray(value.diagnostics)) {
|
|
16
|
+
throw new Error("expected sqlx-js diagnostic formatVersion 1");
|
|
17
|
+
}
|
|
18
|
+
const diagnostics = value.diagnostics.map((item, index) => {
|
|
19
|
+
if (!isRecord(item))
|
|
20
|
+
throw new Error(`diagnostics[${index}] must be an object`);
|
|
21
|
+
if (item.severity !== "error" && item.severity !== "warning") {
|
|
22
|
+
throw new Error(`diagnostics[${index}].severity must be error or warning`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof item.phase !== "string")
|
|
25
|
+
throw new Error(`diagnostics[${index}].phase must be a string`);
|
|
26
|
+
if (typeof item.message !== "string")
|
|
27
|
+
throw new Error(`diagnostics[${index}].message must be a string`);
|
|
28
|
+
optionalString(item.file, `diagnostics[${index}].file`);
|
|
29
|
+
optionalString(item.code, `diagnostics[${index}].code`);
|
|
30
|
+
optionalPosition(item.line, `diagnostics[${index}].line`);
|
|
31
|
+
optionalPosition(item.column, `diagnostics[${index}].column`);
|
|
32
|
+
return {
|
|
33
|
+
severity: item.severity,
|
|
34
|
+
phase: item.phase,
|
|
35
|
+
message: item.message,
|
|
36
|
+
...(item.file !== undefined ? { file: item.file } : {}),
|
|
37
|
+
...(item.line !== undefined ? { line: item.line } : {}),
|
|
38
|
+
...(item.column !== undefined ? { column: item.column } : {}),
|
|
39
|
+
...(item.code !== undefined ? { code: item.code } : {}),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return { formatVersion: 1, ok: value.ok, diagnostics };
|
|
43
|
+
}
|
|
44
|
+
function githubData(value) {
|
|
45
|
+
return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
46
|
+
}
|
|
47
|
+
function githubProperty(value) {
|
|
48
|
+
return githubData(value).replaceAll(":", "%3A").replaceAll(",", "%2C");
|
|
49
|
+
}
|
|
50
|
+
function renderGithub(diagnostic) {
|
|
51
|
+
const properties = [];
|
|
52
|
+
if (diagnostic.file)
|
|
53
|
+
properties.push(`file=${githubProperty(diagnostic.file)}`);
|
|
54
|
+
if (diagnostic.line !== undefined)
|
|
55
|
+
properties.push(`line=${diagnostic.line}`);
|
|
56
|
+
if (diagnostic.column !== undefined)
|
|
57
|
+
properties.push(`col=${diagnostic.column}`);
|
|
58
|
+
const propertyText = properties.length > 0 ? ` ${properties.join(",")}` : "";
|
|
59
|
+
const code = diagnostic.code ? ` ${diagnostic.code}` : "";
|
|
60
|
+
return `::${diagnostic.severity}${propertyText}::${githubData(`[${diagnostic.phase}${code}] ${diagnostic.message}`)}`;
|
|
61
|
+
}
|
|
62
|
+
function renderUnix(diagnostic) {
|
|
63
|
+
const clean = (value) => value.replace(/[\r\n]+/g, " ");
|
|
64
|
+
const file = clean(diagnostic.file ?? "<project>");
|
|
65
|
+
const line = diagnostic.line ?? 1;
|
|
66
|
+
const column = diagnostic.column ?? 1;
|
|
67
|
+
const code = diagnostic.code ? ` ${clean(diagnostic.code)}` : "";
|
|
68
|
+
return `${file}:${line}:${column}: ${diagnostic.severity}: [${clean(diagnostic.phase)}${code}] ${clean(diagnostic.message)}`;
|
|
69
|
+
}
|
|
70
|
+
function usage() {
|
|
71
|
+
console.log("usage: sqlx-js-diagnostics github|unix < prepare-diagnostics.json");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const format = process.argv[2];
|
|
75
|
+
if (format === "--help" || format === "-h")
|
|
76
|
+
usage();
|
|
77
|
+
if (format !== "github" && format !== "unix") {
|
|
78
|
+
console.error("sqlx-js-diagnostics: expected output format github or unix");
|
|
79
|
+
process.exit(2);
|
|
80
|
+
}
|
|
81
|
+
let input = "";
|
|
82
|
+
for await (const chunk of process.stdin)
|
|
83
|
+
input += chunk;
|
|
84
|
+
let payload;
|
|
85
|
+
try {
|
|
86
|
+
payload = parsePayload(JSON.parse(input));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error(`sqlx-js-diagnostics: ${error.message}`);
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
for (const diagnostic of payload.diagnostics) {
|
|
93
|
+
console.log(format === "github" ? renderGithub(diagnostic) : renderUnix(diagnostic));
|
|
94
|
+
}
|
|
95
|
+
process.exit(payload.ok ? 0 : 1);
|
|
96
|
+
export {};
|