@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
@@ -0,0 +1,211 @@
1
+ # Shared runtime and CLI configuration
2
+
3
+ A `TenonConfig` describes one database: its connection, codecs, runtime query observer, and CLI
4
+ defaults. The application and CLI use the same object, so generated types and query checks follow
5
+ the representations returned at runtime.
6
+
7
+ Create the configuration from the project root:
8
+
9
+ ```sh
10
+ npx tenon init
11
+ ```
12
+
13
+ The command writes `tenon.config.ts` with a named `main` export:
14
+
15
+ ```ts
16
+ import type { TenonConfig } from '@kequtech/tenon-orm';
17
+
18
+ export const main = {
19
+ connection: { connectionString: process.env.DATABASE_URL },
20
+ migrations: { directory: 'migrations', namespace: 'public' },
21
+ generate: { out: 'src/generated-schema.d.ts' },
22
+ check: { project: 'tsconfig.json' },
23
+ } satisfies TenonConfig;
24
+ ```
25
+
26
+ Add `connection.codecs` when choosing custom representations. Initialization needs no credentials,
27
+ imports no application code, and never overwrites a file. It refuses to create a default config when
28
+ one is already discoverable in the current directory or a parent. Use `--config path.mjs` for an
29
+ explicit destination or JavaScript output.
30
+
31
+ Import the config into the application and pass it directly to `createTenon`:
32
+
33
+ ```ts
34
+ // src/db.ts
35
+ import { createTenon } from '@kequtech/tenon-orm';
36
+ import { main } from '../tenon.config.ts';
37
+
38
+ export const db = createTenon(main);
39
+ ```
40
+
41
+ ## Initialization and imports
42
+
43
+ Constructing `db` checks the config's shape without validating credentials or creating a pool. The
44
+ first query or transaction initializes one pool, which subsequent and concurrent calls reuse.
45
+ Accessing `db.native` also initializes the pool and validates its settings; the driver connects when
46
+ an operation needs a session. Closing an unused instance creates no pool and needs no credentials.
47
+
48
+ Read environment values in the config without a top-level credential check. This lets builds import
49
+ application modules without database access. An undefined or empty `connection`, or an explicitly
50
+ undefined or empty `connectionString`, fails when the database is used and never selects the driver's
51
+ localhost defaults. Environment values are captured when the module is imported, so supply runtime
52
+ variables before starting the application. Queries performed during a build still need credentials.
53
+
54
+ The `.ts` imports above run with Node.js 24+ TypeScript support. For typechecking with `noEmit`, enable
55
+ `allowImportingTsExtensions`; emitting projects can use `rewriteRelativeImportExtensions`. Config
56
+ files and their imports must run in Node without bundler aliases or transformations. ESM JavaScript
57
+ is also supported.
58
+
59
+ Keep config files free of resource initialization: export connection values and codecs, and let Tenon
60
+ open and close its tooling resources. The application reuses its database instance and closes it at
61
+ shutdown. Programmatic configs can also borrow an existing pool; see [pool ownership](connections.md#use-an-application-owned-pool).
62
+
63
+ ## Multiple databases
64
+
65
+ Export one config per database from the same file. For example, add `analytics` beside `main` in
66
+ `tenon.config.ts`:
67
+
68
+ ```ts
69
+ export const analytics = {
70
+ connection: { connectionString: process.env.ANALYTICS_DATABASE_URL },
71
+ migrations: { directory: 'analytics/migrations', namespace: 'public' },
72
+ generate: { out: 'src/analytics-schema.d.ts' },
73
+ check: { project: 'tsconfig.json' },
74
+ } satisfies TenonConfig;
75
+ ```
76
+
77
+ Each export has its own connection settings and codecs. Use the same `connection.codecs` value when
78
+ databases share a registry, and give each database its own migrations and generated output.
79
+ Application code selects the export through an ordinary import:
80
+
81
+ ```ts
82
+ import { createTenon } from '@kequtech/tenon-orm';
83
+ import { main, analytics } from '../tenon.config.ts';
84
+
85
+ export const db = createTenon(main);
86
+ export const analyticsDb = createTenon(analytics);
87
+ ```
88
+
89
+ The CLI selects the corresponding export with `--database`:
90
+
91
+ ```sh
92
+ npx tenon migrate --database main
93
+ npx tenon migrate --database analytics
94
+ npx tenon generate --database analytics
95
+ npx tenon check src/analytics/queries.ts --database analytics
96
+ npx tenon new add_events --database analytics
97
+ ```
98
+
99
+ A file with one config is selected automatically. Multiple database exports require `--database`;
100
+ an unknown name fails without selecting another connection. Named objects with a `connection` field
101
+ are database configs, while other helper exports are ignored. Existing default exports remain
102
+ supported and can be selected explicitly as `--database default`.
103
+
104
+ Only the selected config's settings are validated, although importing the file evaluates the whole
105
+ module. Keep credentials as plain environment reads so one database does not require another's
106
+ credentials. Runtime selection uses the imported object and does not depend on the working directory.
107
+ Close each instance separately. For databases with different schemas, use separate checker projects
108
+ or select the relevant query file when running `tenon check`.
109
+
110
+ `--config` selects a file and `--database` selects an export within it. For example,
111
+ `tenon init --config config/tenon.production.ts` creates another file, and
112
+ `tenon migrate --config config/tenon.production.ts --database main` selects its `main` export.
113
+
114
+ ## Run commands
115
+
116
+ With the single config generated by `tenon init`:
117
+
118
+ ```sh
119
+ npx tenon new create_users
120
+ npx tenon migrate
121
+ npx tenon generate
122
+ npx tenon generate --check
123
+ npx tenon check
124
+ npx tenon check src/users.ts:23 --json
125
+ npx tenon describe query.sql
126
+ ```
127
+
128
+ Generation, checking, and inspection require configuration and use its connection and codecs. A
129
+ missing or invalid config fails instead of falling back to `DATABASE_URL`. The config chooses which
130
+ environment variables to read; Tenon does not load `.env` files.
131
+
132
+ Migration creation, migration/status/rollback, and baseline commands also discover configuration.
133
+ Without a config or an explicit `--database`, they retain the migration-directory defaults and
134
+ `DATABASE_URL` workflow. `init`, `new`, and `--help` need no database credentials. `new` reads migration
135
+ settings without validating the connection; `--help` loads no config. Any eager checks in your own
136
+ config module still run when that module is imported.
137
+
138
+ The CLI validates the selected config's tooling sections when loading it, including session and
139
+ timeout settings for commands that do not inspect queries. Migration SQL and history reads use
140
+ default decoding, independently of application codec overrides.
141
+
142
+ ## Discovery, paths, and overrides
143
+
144
+ Tenon searches the working directory and then its parents for the nearest directory containing
145
+ `tenon.config.ts`, `.mts`, `.js`, or `.mjs`. Multiple recognized files in that directory require an
146
+ explicit selection:
147
+
148
+ ```sh
149
+ npx tenon generate --config config/tenon.production.mts --check
150
+ ```
151
+
152
+ `--config` resolves from the working directory. A missing file, missing database export, or import
153
+ failure is an error; Tenon does not switch to another configuration after a failure.
154
+
155
+ Configured paths resolve from the config file's directory. An omitted migration directory means
156
+ `migrations` beside the config, and an omitted checker project means `tsconfig.json` beside it. CLI
157
+ path flags resolve from the working directory and override the corresponding configured values.
158
+
159
+ ## Configuration fields
160
+
161
+ | Field | Purpose |
162
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `connection` | PostgreSQL URI or pgclear pool options, including TLS, password callbacks, startup parameters, and codecs. Required when the database is used. |
164
+ | `onQuery` | Optional observer for completed runtime queries. CLI tooling and native calls do not invoke it. |
165
+ | `migrations.directory` | Migration files; defaults to `migrations` beside the config. |
166
+ | `migrations.namespace` | Migration/history schema; defaults to `public`. Also supplies the generation namespace unless overridden. |
167
+ | `generate.out` | Required when `generate` is present. Otherwise supply `--out`. |
168
+ | `generate.namespace` | Schema to generate; overrides the migration namespace for generation. |
169
+ | `baseline.schemas` | Optional nonempty schema list for native baseline capture. Omission captures the whole database. |
170
+ | `baseline.connection` | Optional native libpq connection for baseline capture; `--connection` overrides it. |
171
+ | `check.project` | TypeScript project; defaults to `tsconfig.json` beside the config. |
172
+ | `check.readTypes` | Fallback type expressions for unidentified decoder representations. See [query checking](query-checking.md#programmatic-use-and-custom-codecs). |
173
+ | `session` | Inspection/checking overrides for `role`, `searchPath`, and `timeZone`. |
174
+ | `timeouts` | Inspection/checking bounds for `connectMs`, `statementMs`, and `lockMs`. |
175
+
176
+ Generation, checking, and inspection inherit `connection.codecs`; their internal catalog queries
177
+ keep default decoding. Runtime startup parameters are retained. Inspection applies its statement
178
+ and lock timeout bounds regardless of startup-parameter casing, then applies explicit session and
179
+ CLI overrides. These inspection settings do not change runtime queries or migrations. See
180
+ [connection settings](connections.md) and [inspection limits](query-inspection.md#session-and-timeouts).
181
+
182
+ Keep credentials and deployment addresses in environment variables or the existing secret provider.
183
+ For a separate migration role, add a config export with that role's connection and the shared codecs.
184
+
185
+ ## Native baseline capture
186
+
187
+ Baseline capture uses `pg_dump`, which requires a native libpq connection string. If the main
188
+ connection uses a driver-options object, add the native URI to the config:
189
+
190
+ ```ts
191
+ // Within the database config:
192
+ baseline: { connection: process.env.DATABASE_URL, schemas: ['public'] }
193
+ ```
194
+
195
+ Capture uses `--connection`, then `baseline.connection`, then a string main connection or the
196
+ no-config `DATABASE_URL` fallback. `--schema` flags replace `baseline.schemas`. Adoption ignores
197
+ capture-only settings. Supply the same database and its required native TLS/authentication settings;
198
+ Tenon does not translate driver options into libpq settings.
199
+
200
+ ## Programmatic APIs
201
+
202
+ `createTenon(config)` accepts the shared object and initializes its connection lazily. Other
203
+ programmatic APIs take explicit connection and operation options; none discover config files.
204
+
205
+ The CLI owns generated-file output and freshness checks. `generateDefinitions` returns source text
206
+ for custom tooling without writing files. `describeQuery` and `checkQueries` inherit codecs from
207
+ connection-option values; an explicit `codecs` argument selects their reported read mappings. These
208
+ inspection APIs own separate, short-lived sessions and do not join callback transactions.
209
+
210
+ The CLI imports the selected trusted config and its dependencies. Source checking then reads query
211
+ modules without importing them or executing their SQL. TypeScript loads only for source checking.
@@ -5,31 +5,49 @@ needed only when your application imports native pgclear APIs directly.
5
5
 
6
6
  ## Configure a database instance
7
7
 
8
- ```ts
9
- import { createTenon } from '@kequtech/tenon-orm';
8
+ Define connection settings in the shared config:
10
9
 
11
- const connection = process.env.DATABASE_URL;
12
- if (!connection) throw new Error('DATABASE_URL is required');
10
+ ```ts
11
+ // tenon.config.ts
12
+ import type { TenonConfig } from '@kequtech/tenon-orm';
13
13
 
14
- export const db = createTenon({
14
+ export const main = {
15
15
  connection: {
16
- connectionString: connection,
16
+ connectionString: process.env.DATABASE_URL,
17
17
  maxConnections: 10,
18
18
  connectTimeoutMs: 5000,
19
19
  acquireTimeoutMs: 5000,
20
20
  idleTimeoutMs: 30_000,
21
- startupParameters: { search_path: 'app,public', TimeZone: 'UTC' },
21
+ startupParameters: { search_path: 'app,public' },
22
22
  },
23
- });
23
+ } satisfies TenonConfig;
24
24
  ```
25
25
 
26
- `connection` accepts a PostgreSQL URI, pgclear `PoolOptions`, or an existing pgclear `Pool`.
27
- `ConnectionOptions` is Tenon's alias for `PoolOptions`. Object fields override URI settings.
28
- Reuse an instance for each database configuration.
26
+ Pass the config to the constructor:
29
27
 
30
- The driver does not load environment variables, `.env`, pgpass, or service files. Tenon CLI commands
31
- read `DATABASE_URL`; application code passes its configuration explicitly. An empty options object
32
- uses localhost:5432, the current OS user and a database with that user's name.
28
+ ```ts
29
+ import { createTenon } from '@kequtech/tenon-orm';
30
+ import { main } from '../tenon.config.ts';
31
+
32
+ export const db = createTenon(main);
33
+ ```
34
+
35
+ `createTenon` takes one `TenonConfig` object. Connection validation and pool creation wait until the
36
+ first query, transaction, or `db.native` access. The pool connects when it needs a session. Closing an
37
+ unused instance does not resolve its connection. Subsequent operations reuse the same pool.
38
+
39
+ The config's `connection` field accepts a PostgreSQL URI or pgclear pool-option values.
40
+ `ConnectionOptions` is Tenon's alias for `PoolOptions`. Object fields override URI settings. A
41
+ programmatically constructed config may also supply an existing pool, preserving its ownership.
42
+
43
+ Keep codecs in `connection.codecs` so runtime and tooling use the same representations. See
44
+ [shared configuration](configuration.md) for build-safe environment values and
45
+ [multiple databases](configuration.md#multiple-databases) for named exports and CLI selection.
46
+
47
+ The config supplies environment values to the driver, which does not load `.env`, pgpass, or service
48
+ files. An explicitly missing URI fails when the instance is used. Deliberately configuring
49
+ `connection: {}` uses the driver's local defaults: localhost:5432, the current OS user, and a database
50
+ with that user's name.
33
51
 
34
52
  Use `tls: {}` for TLS with certificate and hostname verification using system trust. Supply the
35
53
  provider's CA or other required TLS settings through pgclear's `tls` options. Preserve the
@@ -67,10 +85,9 @@ without closing the instance.
67
85
  When another part of the application already owns a pgclear pool:
68
86
 
69
87
  ```ts
70
- import { createPool } from '@kequtech/pgclear';
71
88
  import { createTenon } from '@kequtech/tenon-orm';
89
+ import { pool } from './existing-pool.js';
72
90
 
73
- const pool = createPool(connection);
74
91
  const db = createTenon({ connection: pool });
75
92
  // db.native === pool
76
93
 
@@ -83,36 +100,42 @@ outstanding work. Tool APIs that accept `Connection` follow the same ownership r
83
100
 
84
101
  ## Choose output conversions
85
102
 
86
- Outputs follow the pool's codec registry. For example, to read int8 as JavaScript bigint and
87
- `timestamptz` as Date:
103
+ Outputs follow the configured codec registry. To read int8 as JavaScript bigint and `timestamptz` as
104
+ Date, set the registry in `tenon.config.ts`:
88
105
 
89
106
  ```ts
90
- import { builtin, createTenon, defaultCodecs } from '@kequtech/tenon-orm';
107
+ import { builtin, defaultCodecs, type TenonConfig } from '@kequtech/tenon-orm';
91
108
 
92
109
  const codecs = defaultCodecs.with(builtin.int8BigInt).with(builtin.timestamptzDate);
93
- const db = createTenon({ connection: { connectionString: connection, codecs } });
110
+ export const main = {
111
+ connection: { connectionString: process.env.DATABASE_URL, codecs },
112
+ } satisfies TenonConfig;
94
113
  ```
95
114
 
96
115
  These choices apply across the database instance, including callback transactions. Tenon's query
97
116
  options have no per-query codec override. Input encoders remain separate: a `param(value, codec)`
98
117
  carries its own encoder, and passing a Date does not select a Date output decoder.
99
118
 
100
- Use `db.generateTypes` with this instance to inherit its read mappings. Pass the same registry
101
- explicitly to `describeQuery` and `checkQueries`; their CLI forms use default codecs. See
119
+ The CLI uses that registry for generation, inspection, and checking. Programmatic `describeQuery`
120
+ and `checkQueries` inherit codecs from connection-option values too. See
102
121
  [schema types and values](schema-types.md#custom-codecs) for custom representations and Temporal.
103
122
 
104
123
  ## Observe completed queries
105
124
 
125
+ Set `onQuery` on the database config:
126
+
106
127
  ```ts
107
- const db = createTenon({
108
- connection,
128
+ import type { TenonConfig } from '@kequtech/tenon-orm';
129
+
130
+ export const main = {
131
+ connection: { connectionString: process.env.DATABASE_URL },
109
132
  onQuery(event) {
110
133
  console.log(event.label, event.durationMs, event.outcome, event.rowCount);
111
134
  },
112
- });
135
+ } satisfies TenonConfig;
113
136
  ```
114
137
 
115
- Set `{ label: 'user-by-email' }` on a query to identify it. `onQuery` receives duration, outcome,
138
+ Set `{ label: 'user-by-email' }` on a query to identify it. The observer receives duration, outcome,
116
139
  and available command/count/error-code metadata, with no SQL text or bound values. Validation
117
140
  failures before execution are not observed. Native driver calls and tooling are outside this hook.
118
141
 
@@ -151,7 +174,8 @@ import the native one as `pgSql`.
151
174
 
152
175
  ### Listen for notifications
153
176
 
154
- Import `listen` directly from pgclear for a dedicated connection:
177
+ During application startup, pass a validated PostgreSQL URI (`connection` below) to pgclear's
178
+ `listen` for a dedicated notification connection:
155
179
 
156
180
  ```ts
157
181
  import { listen } from '@kequtech/pgclear';
@@ -22,17 +22,45 @@ Supply `DATABASE_URL` through your environment setup. For a local example databa
22
22
  export DATABASE_URL=postgresql://postgres:postgres@localhost:5432/your_database
23
23
  ```
24
24
 
25
- Tenon does not create the database or load `.env` files. Create one shared database module:
25
+ Tenon does not create the database or load `.env` files. Create shared configuration:
26
+
27
+ ```sh
28
+ npx tenon init
29
+ ```
30
+
31
+ The command writes `tenon.config.ts` without opening a database or overwriting existing config:
32
+
33
+ ```ts
34
+ // tenon.config.ts
35
+ import type { TenonConfig } from '@kequtech/tenon-orm';
36
+
37
+ export const main = {
38
+ connection: { connectionString: process.env.DATABASE_URL },
39
+ migrations: { directory: 'migrations', namespace: 'public' },
40
+ generate: { out: 'src/generated-schema.d.ts' },
41
+ check: { project: 'tsconfig.json' },
42
+ } satisfies TenonConfig;
43
+ ```
44
+
45
+ Use it in the application too:
26
46
 
27
47
  ```ts
28
48
  // src/db.ts
29
49
  import { createTenon } from '@kequtech/tenon-orm';
50
+ import { main } from '../tenon.config.ts';
30
51
 
31
- const connection = process.env.DATABASE_URL;
32
- if (!connection) throw new Error('DATABASE_URL is required');
33
- export const db = createTenon({ connection });
52
+ export const db = createTenon(main);
34
53
  ```
35
54
 
55
+ These `.ts` imports use Node.js 24+ TypeScript support. See
56
+ [shared configuration](configuration.md) for import conventions, custom codecs, discovery, and overrides.
57
+
58
+ Importing the config and constructing `db` need no database credentials. The first query or transaction
59
+ validates the connection and initializes its pool; `db.native` access also initializes it. Keep
60
+ database operations out of module imports that run during builds. For another database, add a named
61
+ config export in the same file, pass it to `createTenon`, and select that export in CLI commands with
62
+ `--database`.
63
+
36
64
  Reuse `db` across requests. Call `await db.close()` when a standalone script finishes or a server
37
65
  shuts down. See [connections](connections.md) for pool options and application-owned pools.
38
66
 
@@ -166,35 +194,26 @@ savepoints, and retry limits. For object-based inserts and patches, see [writing
166
194
  ## Generate types
167
195
 
168
196
  Generation is optional. It reads the migrated database and writes table read/insert/update types.
169
- Run it through the configured instance so the types use the same value conversions as your queries:
170
-
171
- ```ts
172
- // scripts/generate-types.ts
173
- import { parseArgs } from 'node:util';
174
- import { db } from '../src/db.ts';
197
+ The CLI uses the shared connection and codecs from `tenon.config.ts`:
175
198
 
176
- const { values } = parseArgs({ options: { check: { type: 'boolean', default: false } } });
177
- try {
178
- await db.generateTypes({ out: 'src/generated-schema.d.ts', check: values.check });
179
- } finally {
180
- await db.close();
181
- }
199
+ ```sh
200
+ npx tenon generate
201
+ npx tenon generate --check
182
202
  ```
183
203
 
184
- This script runs directly on Node.js 24+ using its TypeScript support. For this execution path,
185
- `src/db.ts` must be runnable without build-time transformations or extension rewriting. If your
186
- project compiles scripts, use its normal runner and import conventions instead. To typecheck `.ts`
187
- imports with `noEmit`, enable `allowImportingTsExtensions`; emitting projects can use
188
- `rewriteRelativeImportExtensions`.
204
+ The second command checks committed output without rewriting it. The namespace comes from
205
+ `generate.namespace`, then `migrations.namespace`, then `public`, independently of runtime
206
+ `search_path`. Generate after migrations have committed.
189
207
 
190
- Add these scripts to your existing `package.json`:
208
+ Add these commands to your existing `package.json`:
191
209
 
192
210
  ```json
193
211
  {
194
212
  "scripts": {
195
- "db:generate": "node scripts/generate-types.ts",
196
- "db:check-types": "npm run db:generate -- --check",
197
- "db:migrate": "tenon migrate && npm run db:generate"
213
+ "db:generate": "tenon generate",
214
+ "db:check-types": "tenon generate --check",
215
+ "db:check": "tenon check",
216
+ "db:migrate": "tenon migrate && tenon generate"
198
217
  }
199
218
  }
200
219
  ```
@@ -10,8 +10,10 @@ you intend to [transfer migration ownership](adoption-guide.md#transfer-migratio
10
10
  npx tenon new add_user_display_name
11
11
  ```
12
12
 
13
- `new` works offline. It creates the next numeric pair in `migrations/`, starting at `0001` in an empty
14
- directory. For a directory whose latest migration is `0001`:
13
+ `new` opens no database connection. It creates the next numeric pair in the configured migration
14
+ directory, defaulting to `migrations/`, and starts at `0001` in an empty directory. As with other
15
+ commands, importing your config may require its environment values. For a directory whose latest
16
+ migration is `0001`:
15
17
 
16
18
  ```sql
17
19
  -- migrations/0002_add_user_display_name.up.sql
@@ -23,7 +25,8 @@ ALTER TABLE public.users ADD COLUMN display_name text;
23
25
  ALTER TABLE public.users DROP COLUMN display_name;
24
26
  ```
25
27
 
26
- Set `DATABASE_URL` through your environment, then run:
28
+ Configure `connection` and migration directory/namespace in [shared configuration](configuration.md).
29
+ Without config, migration commands retain the `DATABASE_URL` workflow. Then run:
27
30
 
28
31
  ```sh
29
32
  npx tenon status
@@ -82,8 +85,12 @@ may not be recoverable. Use the application's backup and recovery procedure when
82
85
 
83
86
  ## Directory, schema, and locking
84
87
 
85
- Defaults are `--directory migrations` and `--namespace public`. For a custom schema, use the same
86
- options consistently:
88
+ Set `migrations: { directory: 'db/migrations', namespace: 'app' }` in shared configuration to use
89
+ the same defaults for creation, status, migration, and rollback. Generation inherits that namespace
90
+ unless `generate.namespace` is set. Without configured values, defaults are directory `migrations`
91
+ and namespace `public`.
92
+
93
+ CLI flags override the config for an individual command:
87
94
 
88
95
  ```sh
89
96
  npx tenon new add_setting --directory db/migrations
@@ -95,7 +102,7 @@ npx tenon rollback --directory db/migrations --namespace app --steps 1
95
102
  The namespace selects history location, migration search path, and lock scope. During a file,
96
103
  `search_path` is `<namespace>, pg_catalog`; qualify cross-schema names explicitly. The runner creates
97
104
  the namespace when applying pending migrations. It does not configure runtime application queries.
98
- Pass the matching namespace to type generation when generating that schema.
105
+ Keep generation on the same namespace, through the shared default or an explicit override.
99
106
 
100
107
  Migration and adoption operations use an exclusive session advisory lock for this database and
101
108
  namespace. Status and generation use a shared lock. Different namespaces have independent lock
@@ -4,13 +4,13 @@
4
4
  metadata. It can catch a missing result property, an int8 result declared as number, or an unsupported
5
5
  bound input. It is optional; normal execution still trusts `sql<Row>` annotations.
6
6
 
7
- The checker reads source without importing the application, evaluating input expressions, binding
8
- values, or executing application SQL. PostgreSQL still parses/analyzes each recovered statement,
7
+ The CLI imports the selected configuration module; the checker then reads query source without
8
+ importing those modules, evaluating input expressions, binding values, or executing application SQL. PostgreSQL still parses/analyzes each recovered statement,
9
9
  which can take locks. Use a representative migrated development/test database.
10
10
 
11
11
  ## Run a check
12
12
 
13
- Set `DATABASE_URL`, then select a project, file, or one query:
13
+ Create [shared configuration](configuration.md), then select a project, file, or one query:
14
14
 
15
15
  ```sh
16
16
  npx tenon check
@@ -23,15 +23,16 @@ A one-based line selects the complete query containing it, including multiline t
23
23
  variable declaration line. A line with no query or multiple queries fails. A fragment alone is not
24
24
  a complete query target.
25
25
 
26
- The checker discovers `tsconfig.json` from the target's directory or current working directory.
27
- Use `--project` for another config or project directory; targets must belong to that project.
26
+ The CLI uses `check.project`, defaulting to `tsconfig.json` beside the shared config. Use `--project`
27
+ for another TypeScript config or project directory; targets must belong to that project. Configured
28
+ paths resolve from the shared config; CLI targets and path flags resolve from the working directory.
28
29
  It uses the bundled TypeScript 6 compiler and the project's imports, aliases, indexed access,
29
30
  `Pick`, unions, and path mappings. `strictNullChecks` is required. Invalid configuration and compiler
30
31
  errors in selected files fail the report.
31
32
 
32
33
  Match runtime role, search path, timezone, and codecs. Session/timeout flags are the same as
33
- [Describe](query-inspection.md#cli). CLI checks use default codecs; use the programmatic API for
34
- a custom registry.
34
+ [Describe](query-inspection.md#cli). CLI checks inherit the shared connection
35
+ and codec registry. Inspection-only session settings and flags override runtime startup settings.
35
36
 
36
37
  ## What gets checked
37
38
 
@@ -166,7 +167,9 @@ if (!report.ok) process.exitCode = 1;
166
167
  ```
167
168
 
168
169
  The report has `ok`, `project`, `queries`, `diagnostics`, and available inspection `context`. Options
169
- also accept `session`, `timeouts`, `codecs`, and `readTypes`. `connection` is a URI.
170
+ also accept `session`, `timeouts`, `codecs`, and `readTypes`. `connection` is a URI or pool-option
171
+ values; its `codecs` are inherited unless the explicit inspection `codecs` argument is supplied.
172
+ The programmatic API does not discover shared configuration.
170
173
 
171
174
  For a custom codec, pass the same registry as runtime queries. A text codec may declare its output:
172
175
 
@@ -193,7 +196,7 @@ registry representation. It cannot replace known metadata or claim JSON members
193
196
  decoding. Expressions resolve from the tsconfig directory. A codec declaration is trusted output
194
197
  metadata, not data validation.
195
198
 
196
- Custom configuration belongs in an application-owned checking script; the checker does not load
197
- application configuration automatically. Generated-file freshness remains a separate
198
- `db.generateTypes({ out, check: true })` operation. For behavior tests, see
199
+ The CLI loads the shared configuration for custom codecs; no checking script is required. An
200
+ application-owned script may still call the programmatic API with explicit options. Generated-file
201
+ freshness remains a separate `tenon generate --check` operation. For behavior tests, see
199
202
  [query inspection and testing](query-inspection.md).
@@ -7,10 +7,11 @@ compare TypeScript source annotations instead, use [query checking](query-checki
7
7
  ## Inspect completed SQL
8
8
 
9
9
  ```ts
10
+ // scripts/inspect-query.ts
10
11
  import { describeQuery, sql } from '@kequtech/tenon-orm';
12
+ import { main } from '../tenon.config.ts';
11
13
 
12
- const connection = process.env.DATABASE_URL;
13
- if (!connection) throw new Error('DATABASE_URL is required');
14
+ const connection = main.connection;
14
15
 
15
16
  const query = sql<{ id: number; total: string }>`
16
17
  SELECT ${42}::integer AS id, count(*) AS total FROM pg_catalog.pg_class
@@ -28,8 +29,8 @@ and analyzes the statement, which can take locks.
28
29
 
29
30
  ## Session and timeouts
30
31
 
31
- Inspection opens and closes its own short-lived session. `connection` must be a PostgreSQL URI;
32
- it does not inherit a runtime pool or transaction:
32
+ Inspection opens and closes its own short-lived session. `connection` accepts a PostgreSQL URI or pgclear pool-option values;
33
+ it does not reuse a runtime pool or transaction:
33
34
 
34
35
  ```ts
35
36
  const description = await describeQuery({
@@ -83,7 +84,8 @@ members remain unknown.
83
84
 
84
85
  ## Custom codecs
85
86
 
86
- Pass the runtime registry as `codecs` to report its read representations; omission uses defaults:
87
+ Programmatic inspection inherits `connection.codecs` when connection-option values are supplied.
88
+ With a URI, pass the runtime registry as `codecs`; omission uses defaults:
87
89
 
88
90
  ```ts
89
91
  import { builtin, defaultCodecs } from '@kequtech/tenon-orm';
@@ -157,11 +159,12 @@ npx tenon describe query.sql
157
159
  npx tenon describe query.sql --search-path app,public --time-zone UTC
158
160
  ```
159
161
 
160
- The CLI reads `DATABASE_URL` and prints JSON using default codecs. Session flags are `--role`,
162
+ The CLI requires [shared configuration](configuration.md) and prints JSON using its connection
163
+ and codec registry. Pass `--config path` to select a specific module. Session flags are `--role`,
161
164
  `--search-path`, and `--time-zone`; timeout flags are `--connect-timeout-ms`,
162
165
  `--statement-timeout-ms`, and `--lock-timeout-ms`. Unsupported flags, including `--namespace` and
163
166
  `--check`, reject.
164
167
 
165
168
  Exit 0 means inspection succeeded, even with unresolved findings. Input, connection, timeout, or
166
169
  PostgreSQL analysis failures exit 1. The inspected statement is never executed. Generated-file
167
- freshness uses `db.generateTypes({ out, check: true })`, a separate operation.
170
+ freshness uses `tenon generate --check`, a separate operation.