@kequtech/tenon-orm 0.4.2 → 0.4.4

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +16 -5
  3. package/dist/check.js +4 -2
  4. package/dist/cli.js +106 -28
  5. package/dist/config/connection.d.ts +3 -0
  6. package/dist/config/connection.js +22 -0
  7. package/dist/config/init.d.ts +2 -0
  8. package/dist/config/init.js +31 -0
  9. package/dist/config/load.d.ts +7 -0
  10. package/dist/config/load.js +152 -0
  11. package/dist/config/validate.d.ts +3 -0
  12. package/dist/config/validate.js +28 -0
  13. package/dist/config.d.ts +30 -0
  14. package/dist/config.js +1 -0
  15. package/dist/connection.d.ts +8 -0
  16. package/dist/connection.js +1 -1
  17. package/dist/database.d.ts +3 -7
  18. package/dist/database.js +22 -19
  19. package/dist/describe.js +3 -1
  20. package/dist/generate.d.ts +0 -11
  21. package/dist/generate.js +1 -26
  22. package/dist/generation/output.js +2 -2
  23. package/dist/index.d.ts +3 -3
  24. package/dist/inspection/cli.d.ts +3 -4
  25. package/dist/inspection/cli.js +3 -13
  26. package/dist/inspection/connection.js +23 -19
  27. package/dist/inspection/options.d.ts +5 -7
  28. package/dist/inspection/options.js +39 -22
  29. package/dist/inspection/types.d.ts +3 -2
  30. package/dist/migrations/history.d.ts +1 -1
  31. package/dist/migrations/history.js +3 -2
  32. package/dist/types.d.ts +1 -4
  33. package/dist/validation.d.ts +2 -0
  34. package/dist/validation.js +6 -0
  35. package/docs/adoption-guide.md +9 -3
  36. package/docs/agent-guide.md +13 -16
  37. package/docs/configuration.md +211 -0
  38. package/docs/connections.md +51 -27
  39. package/docs/human-guide.md +44 -25
  40. package/docs/migrations.md +13 -6
  41. package/docs/query-checking.md +14 -11
  42. package/docs/query-inspection.md +10 -7
  43. package/docs/reference.md +72 -49
  44. package/docs/schema-types.md +35 -27
  45. package/package.json +1 -1
package/docs/reference.md CHANGED
@@ -10,12 +10,19 @@ Requires Node.js 24+, PostgreSQL 16+, and ESM. TypeScript consumers need TypeScr
10
10
  development typings. Install `@kequtech/tenon-orm`; pgclear is included as a runtime dependency.
11
11
 
12
12
  ```text
13
- createTenon({ connection, onQuery? }): Database
13
+ createTenon(config: TenonConfig): Database
14
14
  ```
15
15
 
16
- `connection` is required: a PostgreSQL URI string, pgclear `PoolOptions`, or an existing pgclear
17
- `Pool`. `Connection` names this union; `ConnectionOptions` aliases `PoolOptions`. A supplied pool
18
- retains application ownership. Unknown Tenon or driver configuration options reject.
16
+ Pass one database config directly. Its `connection` holds a PostgreSQL URI, pgclear pool-option
17
+ values, or an application-owned pool. Its optional `onQuery` callback observes runtime queries.
18
+ `ConnectionOptions` aliases pgclear `PoolOptions`; a supplied pool retains application ownership.
19
+ Unknown config keys reject. Connection validation and pool creation wait until the first query,
20
+ transaction, or native-pool access. Closing an unused instance needs no credentials.
21
+
22
+ `tenon init` generates a named `main` config with plain environment reads and no eager credential
23
+ check. Import it and call `createTenon(main)`. Runtime does not discover files. For multiple databases,
24
+ export one config per database and select the matching export in the CLI with `--database`.
25
+ See [shared configuration](configuration.md) for the complete setup.
19
26
 
20
27
  The returned `Database` provides:
21
28
 
@@ -23,7 +30,6 @@ The returned `Database` provides:
23
30
  | ------------------------ | ---------------------------------------------------------------------------------------- |
24
31
  | `query(query, options?)` | Execute a Tenon `SqlQuery` and apply result/count options |
25
32
  | `transaction(callback)` | Commit on callback success, roll back on an escaping error; return the callback's result |
26
- | `generateTypes(options)` | Write or check schema declarations using this instance's pool/codecs |
27
33
  | `native` | The underlying pgclear pool |
28
34
  | `close({ timeoutMs? }?)` | Disable the wrapper and close an owned pool; default grace 5,000 ms |
29
35
 
@@ -206,16 +212,17 @@ Use codes and structured fields rather than matching message text. Retain unexpe
206
212
 
207
213
  ## Generated types and checking
208
214
 
209
- ```text
210
- db.generateTypes({ out, namespace?, check?, onWarning? }): Promise<void>
211
- generateDefinitions({ connection, namespace?, onWarning? }): Promise<string>
215
+ ```sh
216
+ tenon generate
217
+ tenon generate --check
212
218
  ```
213
219
 
214
- `out` is required for file generation. Namespace defaults to `public`; `check` defaults to false.
215
- Generation inherits the database registry. Missing/stale output rejects under `check: true` without
216
- writing. See [schema types and values](schema-types.md) for file guarantees, generated shapes,
217
- value mappings, codecs, and domain validation. The standalone `tenon generate` command was removed;
218
- use an application script.
220
+ Generation uses [shared configuration](configuration.md) for its connection/codecs, output path,
221
+ and namespace. Missing/stale output rejects under `--check` without writing. See
222
+ [schema types and values](schema-types.md) for file guarantees, generated shapes, and value mappings.
223
+ `generateDefinitions({ connection, namespace?, onWarning? }): Promise<string>` returns source text
224
+ without file handling for custom tooling. Database instances expose query execution and lifecycle
225
+ methods; generation is a CLI tool.
219
226
 
220
227
  ### Application aliases
221
228
 
@@ -234,15 +241,16 @@ Generated types account for defaults, identity/computed columns, domains, and nu
234
241
 
235
242
  ## Choosing a validation tool
236
243
 
237
- Use the intended development/test schema and role. TypeScript itself needs no connection.
244
+ Use the intended development/test schema and role through shared configuration. TypeScript itself
245
+ needs no connection.
238
246
 
239
- | Tool | Question answered | Main limit |
240
- | ---------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- |
241
- | `tsc --noEmit` | Does application code satisfy TypeScript contracts? | Does not inspect SQL/schema |
242
- | `tenon check [file.ts[:line]]` | Does recoverable SQL contradict declared input/result types? | Unsupported sites fail; inconclusive properties remain unproven |
243
- | `tenon describe query.sql` | What metadata does PostgreSQL report for finished SQL? | Does not compare annotations or bind values |
244
- | `db.generateTypes({ out, check: true })` | Does committed output match generation now? | Does not check queries or replace migration status |
245
- | Database tests | Do exercised queries produce correct rows, effects, permissions, and rollback? | Cover only tested inputs and paths |
247
+ | Tool | Question answered | Main limit |
248
+ | ------------------------------ | ------------------------------------------------------------------------------ | --------------------------------------------------------------- |
249
+ | `tsc --noEmit` | Does application code satisfy TypeScript contracts? | Does not inspect SQL/schema |
250
+ | `tenon check [file.ts[:line]]` | Does recoverable SQL contradict declared input/result types? | Unsupported sites fail; inconclusive properties remain unproven |
251
+ | `tenon describe query.sql` | What metadata does PostgreSQL report for finished SQL? | Does not compare annotations or bind values |
252
+ | `tenon generate --check` | Does committed output match generation now? | Does not check queries or replace migration status |
253
+ | Database tests | Do exercised queries produce correct rows, effects, permissions, and rollback? | Cover only tested inputs and paths |
246
254
 
247
255
  After schema changes, migrate the test database, regenerate/review declarations, and run typechecks,
248
256
  applicable source checks, and database tests. In CI, check generated output without replacing it.
@@ -255,7 +263,7 @@ take locks. Neither proves actual bound values or write effects.
255
263
  The bundled TypeScript compiler loads only through that path or the check command.
256
264
 
257
265
  Options are `{ connection, target?, project?, session?, timeouts?, codecs?, readTypes? }`.
258
- Connection is a URI. The report contains `ok`, `project`, `queries`, `diagnostics`, and available
266
+ Connection is a URI or pool-option values; connection codecs are inherited unless `codecs` is explicit. The report contains `ok`, `project`, `queries`, `diagnostics`, and available
259
267
  inspection `context`. Mismatches, unchecked sites, and no sites fail; unproven findings alone do not.
260
268
  See [query checking](query-checking.md) for selection, supported composition, and custom decoders.
261
269
 
@@ -265,7 +273,7 @@ See [query checking](query-checking.md) for selection, supported composition, an
265
273
  describeQuery({ connection, query, session?, timeouts?, codecs? }): Promise<QueryDescription>
266
274
  ```
267
275
 
268
- Connection is a URI; query is finished SQL text or `{ text, parameterTypes? }`. A `SqlQuery` fits
276
+ Connection is a URI or pool-option values; query is finished SQL text or `{ text, parameterTypes? }`. A `SqlQuery` fits
269
277
  because it has `text`; values and its row annotation are not checked. The report includes
270
278
  `parameters`, `columns`, `rowType`, `issues`, and `context`. It uses a separate session with bounded
271
279
  timeouts. See [query inspection](query-inspection.md) for fields and finding codes.
@@ -279,20 +287,23 @@ prepared statements, and dedicated notification listeners.
279
287
 
280
288
  ## Troubleshooting
281
289
 
282
- | Symptom | Response |
283
- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
284
- | `DATABASE_URL is required` | Supply the variable to CLI commands. No `.env` files are loaded automatically. Application configuration is explicit. |
285
- | `PGCLEAR_ENCODE` | Check inputs: objects/arrays need `json`/`param`; direct undefined, unsafe integers, nonfinite numbers, and invalid Dates reject. JSON still rejects bigint. |
286
- | `CardinalityError` | Check the expected row count and predicate. To choose one of several rows, author deterministic `ORDER BY ... LIMIT 1`. |
287
- | `TENON_AFFECTED_ROWS_MISMATCH` | The guarded operation rolled back. Inspect `actual` and the predicate; retain the intended bound. |
288
- | `TENON_AFFECTED_ROWS_UNSUPPORTED` | The final command lacks a supported write count. Use guards on INSERT/UPDATE/DELETE/MERGE; result modes check returned rows. |
289
- | Empty assignments | All selected fields were missing/undefined. Skip the update or branch to an expression-only statement. |
290
- | Duplicate result columns | Alias columns uniquely, especially in joins. The driver rejects duplicate object keys. |
291
- | SQLSTATE `42P18` / `42P08` during inspection | Add the intended parameter cast or Describe OID hints. Inspection binds no values. |
292
- | Checker reports `unchecked` | Follow the diagnostic. Runtime fragments and optional patches can execute correctly yet remain unsupported by source recovery. |
293
- | Missing/stale generated output | Check schema, namespace, registry, driver version, and path. Regenerate, review, and commit intended output. |
294
- | Migration history conflict | Restore original applied files. Add new changes in a forward migration. Checksums include down SQL too. |
295
- | Tooling lock wait | Check concurrent migration/DDL work, use direct or session pooling, and configure server timeouts. |
290
+ | Symptom | Response |
291
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
292
+ | Missing shared configuration | Run `tenon init` or select an existing config with `--config`. Generation, checking, and inspection require it. |
293
+ | Multiple databases or unknown database export | Select a named config with `--database name`. Use `--config path` when it belongs to another file. |
294
+ | Missing or empty config connection | Set the environment variable read by that database's config before using it. Imports alone need no credentials. |
295
+ | `DATABASE_URL is required` | Supply the variable for migration commands without config. Tenon does not load `.env` files. |
296
+ | `PGCLEAR_ENCODE` | Check inputs: objects/arrays need `json`/`param`; direct undefined, unsafe integers, nonfinite numbers, and invalid Dates reject. JSON still rejects bigint. |
297
+ | `CardinalityError` | Check the expected row count and predicate. To choose one of several rows, author deterministic `ORDER BY ... LIMIT 1`. |
298
+ | `TENON_AFFECTED_ROWS_MISMATCH` | The guarded operation rolled back. Inspect `actual` and the predicate; retain the intended bound. |
299
+ | `TENON_AFFECTED_ROWS_UNSUPPORTED` | The final command lacks a supported write count. Use guards on INSERT/UPDATE/DELETE/MERGE; result modes check returned rows. |
300
+ | Empty assignments | All selected fields were missing/undefined. Skip the update or branch to an expression-only statement. |
301
+ | Duplicate result columns | Alias columns uniquely, especially in joins. The driver rejects duplicate object keys. |
302
+ | SQLSTATE `42P18` / `42P08` during inspection | Add the intended parameter cast or Describe OID hints. Inspection binds no values. |
303
+ | Checker reports `unchecked` | Follow the diagnostic. Runtime fragments and optional patches can execute correctly yet remain unsupported by source recovery. |
304
+ | Missing/stale generated output | Check schema, namespace, registry, driver version, and path. Regenerate, review, and commit intended output. |
305
+ | Migration history conflict | Restore original applied files. Add new changes in a forward migration. Checksums include down SQL too. |
306
+ | Tooling lock wait | Check concurrent migration/DDL work, use direct or session pooling, and configure server timeouts. |
296
307
 
297
308
  After a guarded query rejects, savepoint cleanup has finished. An unguarded server error normally
298
309
  aborts a transaction. A timeout or lost connection does not itself establish whether a commit
@@ -300,15 +311,25 @@ happened; review the transaction contract before adding retries.
300
311
 
301
312
  ## Command reference
302
313
 
303
- All database commands read `DATABASE_URL`. `new` and `--help` work offline. No environment files
304
- are loaded. Paths resolve from the working directory; unsupported command flags reject.
314
+ All commands accept `--config path`. Commands using a database config also accept `--database name`
315
+ to select its export. A file with one config is selected automatically; multiple configs require an
316
+ explicit selection. `generate`, `check`, and `describe` require
317
+ [shared configuration](configuration.md) and use its connection/codecs. Migration and baseline
318
+ commands use config when present and fall back to `DATABASE_URL` only when no config is found.
319
+ `init` creates config without importing application code; `new` uses the configured migration
320
+ directory without opening a database; `--help` loads no config. Config modules may read environment variables, but Tenon loads no `.env`
321
+ files automatically.
322
+ Configured paths resolve from the config file; CLI path flags resolve from the working directory.
323
+ Unsupported command flags reject.
305
324
 
306
325
  ```text
326
+ tenon init [--config path]
307
327
  tenon new <name> [--directory migrations]
328
+ tenon generate [--out path] [--namespace schema] [--check]
308
329
  tenon migrate [--directory migrations] [--namespace public]
309
330
  tenon status [--directory migrations] [--namespace public]
310
331
  tenon rollback [--directory migrations] [--namespace public] [--steps 1]
311
- tenon baseline capture [--directory migrations] [--namespace public] [--schema name ...]
332
+ tenon baseline capture [--directory migrations] [--namespace public] [--schema name ...] [--connection native-uri]
312
333
  tenon baseline adopt [--directory migrations] [--namespace public]
313
334
  tenon check [file.ts[:line]] [--project tsconfig.json] [--json]
314
335
  [--role name] [--search-path value] [--time-zone value]
@@ -317,15 +338,17 @@ tenon describe <query.sql> [--role name] [--search-path value] [--time-zone valu
317
338
  [--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
318
339
  ```
319
340
 
320
- | Command | Output and behavior |
321
- | ---------------------- | ----------------------------------------------------------------- |
322
- | `new` | Paths of the new empty up/down pair; fill before applying |
323
- | `migrate` / `rollback` | Completed IDs or “nothing to do”; each file commits independently |
324
- | `status` | JSON `{ applied, pending }`; validates history |
325
- | `baseline capture` | Writes a reviewed schema-only baseline using native `pg_dump` |
326
- | `baseline adopt` | Records baseline history without executing its SQL |
327
- | `check` | Diagnostics/counts, or a structured report with `--json` |
328
- | `describe` | JSON metadata, including unresolved findings |
341
+ | Command | Output and behavior |
342
+ | ---------------------- | --------------------------------------------------------------------------------------- |
343
+ | `init` | Creates a minimal TypeScript or JavaScript config, refusing to overwrite existing files |
344
+ | `new` | Paths of the new empty up/down pair; fill before applying |
345
+ | `migrate` / `rollback` | Completed IDs or “nothing to do”; each file commits independently |
346
+ | `status` | JSON `{ applied, pending }`; validates history |
347
+ | `baseline capture` | Writes a reviewed schema-only baseline using native `pg_dump` |
348
+ | `baseline adopt` | Records baseline history without executing its SQL |
349
+ | `generate` | Writes output atomically; `--check` rejects missing/stale output without writing |
350
+ | `check` | Diagnostics/counts, or a structured report with `--json` |
351
+ | `describe` | JSON metadata, including unresolved findings |
329
352
 
330
353
  Successful commands exit 0. Invalid input or a failed operation exits 1. `check` also exits 1 for
331
354
  mismatches, unchecked sites, or no sites; unproven findings alone do not fail. `describe` can exit 0
@@ -6,33 +6,35 @@ the selected columns, joins, and expressions.
6
6
 
7
7
  ## Generate and check a file
8
8
 
9
- Use the application's configured database instance, after migrations have committed:
9
+ Configure `generate.out` in [shared configuration](configuration.md), then run after migrations
10
+ have committed:
10
11
 
11
- ```ts
12
- await db.generateTypes({ out: 'src/generated-schema.d.ts' });
13
- await db.generateTypes({ out: 'src/generated-schema.d.ts', check: true });
12
+ ```sh
13
+ npx tenon generate
14
+ npx tenon generate --check
14
15
  ```
15
16
 
16
- The first call writes declarations. The second rejects if they are missing or stale, without
17
- rewriting them. Use it in CI to verify committed output. The
18
- [getting-started script](human-guide.md#generate-types) provides a complete command-line wrapper.
17
+ The first command writes declarations. The second rejects missing or stale output without rewriting
18
+ it. Both use the configured connection and codecs. Relative configured paths resolve from the config
19
+ file; `--out` paths resolve from the working directory. The generation namespace comes from
20
+ `generate.namespace`, then `migrations.namespace`, then `public`; `--namespace` overrides it. It is
21
+ independent of runtime `search_path`.
19
22
 
20
- `namespace` defaults to `public`, independently of runtime `search_path`. Pass it explicitly for a
21
- different schema. Paths resolve from the working directory. Keep exact output unchanged and exclude
22
- it from automatic formatting.
23
+ Keep exact output unchanged and exclude it from automatic formatting. See
24
+ [getting started](human-guide.md#generate-types) for npm commands.
23
25
 
24
- Generation uses the instance's pool and codecs. It is available on `db`, outside callback
25
- transactions. It uses a repeatable-read catalog snapshot and shares the migration advisory lock;
26
- use a direct or session-pooled connection. It needs no migration files or Tenon history.
26
+ Generation runs outside callback transactions. It uses a repeatable-read catalog snapshot and
27
+ shares the migration advisory lock; use a direct or session-pooled connection. It needs no migration
28
+ files or Tenon history.
27
29
 
28
- Tenon creates parent directories and replaces the entire output file atomically. With `check: true`,
29
- it creates no directories, writes no files, and includes a bounded diff for stale output. An optional
30
- `onWarning(message)` can be synchronous or asynchronous; the default is `console.warn`. Warning
31
- handler failures reject generation before output changes. Other filesystem errors propagate.
30
+ Tenon creates parent directories and replaces the entire output file atomically. With `--check`,
31
+ it creates no directories, writes no files, and includes a bounded diff for stale output. The CLI
32
+ prints warnings to stderr. Generation failures preserve the existing file; filesystem errors propagate.
32
33
 
33
34
  For source text without file handling, use
34
35
  `generateDefinitions({ connection, namespace?, onWarning? }): Promise<string>`. Pass the configured
35
- pool or connection options to retain runtime read mappings.
36
+ pool or connection options to retain runtime read mappings. Its optional `onWarning` callback may
37
+ be synchronous or asynchronous and defaults to `console.warn`; callback failures reject generation.
36
38
 
37
39
  ## Read the generated types
38
40
 
@@ -161,17 +163,22 @@ both become null. For raw JSON text or Date/bigint outputs, configure codecs.
161
163
 
162
164
  ## Custom codecs
163
165
 
164
- Configure the registry on the application's connection. For example:
166
+ Configure the registry in `tenon.config.ts` so runtime queries and generated declarations use the
167
+ same representations:
165
168
 
166
169
  ```ts
167
- import { builtin, createTenon, defaultCodecs } from '@kequtech/tenon-orm';
170
+ import { builtin, defaultCodecs, type TenonConfig } from '@kequtech/tenon-orm';
168
171
 
169
172
  const codecs = defaultCodecs.with(builtin.int8BigInt).with(builtin.timestamptzDate);
170
- const db = createTenon({ connection: { connectionString: connection, codecs } });
173
+ export const main = {
174
+ connection: { connectionString: process.env.DATABASE_URL, codecs },
175
+ generate: { out: 'src/generated-schema.d.ts' },
176
+ } satisfies TenonConfig;
171
177
  ```
172
178
 
173
- Int8 now reads as bigint, and timestamptz reads as a millisecond-precision Date. Regenerate using this
174
- instance. Scalar overrides affect derived built-in arrays; explicit array overrides win. Date and
179
+ Int8 now reads as bigint, and timestamptz reads as a millisecond-precision Date. Run `tenon generate`
180
+ with this config to update declarations. Scalar overrides affect derived built-in arrays; explicit
181
+ array overrides win. Date and
175
182
  Temporal output codecs reject infinity and values outside their supported ranges.
176
183
 
177
184
  For Temporal, import `createTemporalCodecs` from Tenon and supply a native or compatible polyfill
@@ -196,10 +203,11 @@ checking consume the registry's metadata. An unidentified decoder warns and beco
196
203
  a declared representation is trusted, not runtime validation. Under a custom registry, enum output
197
204
  may remain string because a decoder can transform labels; enum write inputs retain catalog choices.
198
205
 
199
- Use the same registry for execution, generation, inspection, and source checking. Relative type-import
206
+ Place the registry in shared `connection.codecs` so runtime, CLI generation, inspection, and source
207
+ checking use it consistently. Programmatic inspection/checking inherit `connection.codecs` too. Relative type-import
200
208
  expressions resolve from the generated file for saved declarations and from the tsconfig directory
201
- for checking. Prefer a package or configured alias that resolves in both places. Tenon does not
202
- execute referenced modules. See [custom-codec checking](query-checking.md#programmatic-use-and-custom-codecs).
209
+ for checking. Prefer a package or configured alias that resolves in both places. Resolving these
210
+ type expressions does not execute their referenced modules. See [custom-codec checking](query-checking.md#programmatic-use-and-custom-codecs).
203
211
 
204
212
  Catalog queries use default result decoding on the same borrowed session, so application codecs do
205
213
  not alter Tenon's metadata inputs. Output codec choices do not select input encoders.
@@ -256,7 +264,7 @@ application's authentication and authorization checks.
256
264
 
257
265
  ## What freshness checks establish
258
266
 
259
- `check: true` compares the exact generated file with output from this database and codec registry.
267
+ `tenon generate --check` compares the exact generated file with output from this database and codec registry.
260
268
  It does not check query annotations, pending migrations, functions, privileges, or every database
261
269
  object. A driver or generator update can change output without schema drift. Review generated diffs
262
270
  after upgrades. Generated text and schema-summary formatting are not version-stable schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kequtech/tenon-orm",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "A PostgreSQL ORM designed for coding agents, with explicit SQL, typed queries, and migrations",