@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.
- package/CHANGELOG.md +18 -0
- package/README.md +16 -5
- package/dist/check.js +4 -2
- package/dist/cli.js +106 -28
- package/dist/config/connection.d.ts +3 -0
- package/dist/config/connection.js +22 -0
- package/dist/config/init.d.ts +2 -0
- package/dist/config/init.js +31 -0
- package/dist/config/load.d.ts +7 -0
- package/dist/config/load.js +152 -0
- package/dist/config/validate.d.ts +3 -0
- package/dist/config/validate.js +28 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +1 -0
- package/dist/connection.d.ts +8 -0
- package/dist/connection.js +1 -1
- package/dist/database.d.ts +3 -7
- package/dist/database.js +22 -19
- package/dist/describe.js +3 -1
- package/dist/generate.d.ts +0 -11
- package/dist/generate.js +1 -26
- package/dist/generation/output.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/inspection/cli.d.ts +3 -4
- package/dist/inspection/cli.js +3 -13
- package/dist/inspection/connection.js +23 -19
- package/dist/inspection/options.d.ts +5 -7
- package/dist/inspection/options.js +39 -22
- package/dist/inspection/types.d.ts +3 -2
- package/dist/migrations/history.d.ts +1 -1
- package/dist/migrations/history.js +3 -2
- package/dist/types.d.ts +1 -4
- package/dist/validation.d.ts +2 -0
- package/dist/validation.js +6 -0
- package/docs/adoption-guide.md +9 -3
- package/docs/agent-guide.md +13 -16
- package/docs/configuration.md +211 -0
- package/docs/connections.md +51 -27
- package/docs/human-guide.md +44 -25
- package/docs/migrations.md +13 -6
- package/docs/query-checking.md +14 -11
- package/docs/query-inspection.md +10 -7
- package/docs/reference.md +72 -49
- package/docs/schema-types.md +35 -27
- 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.
|
package/docs/connections.md
CHANGED
|
@@ -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
|
-
|
|
9
|
-
import { createTenon } from '@kequtech/tenon-orm';
|
|
8
|
+
Define connection settings in the shared config:
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
```ts
|
|
11
|
+
// tenon.config.ts
|
|
12
|
+
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
13
13
|
|
|
14
|
-
export const
|
|
14
|
+
export const main = {
|
|
15
15
|
connection: {
|
|
16
|
-
connectionString:
|
|
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'
|
|
21
|
+
startupParameters: { search_path: 'app,public' },
|
|
22
22
|
},
|
|
23
|
-
}
|
|
23
|
+
} satisfies TenonConfig;
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
87
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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.
|
|
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
|
-
|
|
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';
|
package/docs/human-guide.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} finally {
|
|
180
|
-
await db.close();
|
|
181
|
-
}
|
|
199
|
+
```sh
|
|
200
|
+
npx tenon generate
|
|
201
|
+
npx tenon generate --check
|
|
182
202
|
```
|
|
183
203
|
|
|
184
|
-
|
|
185
|
-
`
|
|
186
|
-
|
|
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
|
|
208
|
+
Add these commands to your existing `package.json`:
|
|
191
209
|
|
|
192
210
|
```json
|
|
193
211
|
{
|
|
194
212
|
"scripts": {
|
|
195
|
-
"db:generate": "
|
|
196
|
-
"db:check-types": "
|
|
197
|
-
"db:
|
|
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
|
```
|
package/docs/migrations.md
CHANGED
|
@@ -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`
|
|
14
|
-
directory
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
package/docs/query-checking.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
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
|
|
34
|
-
|
|
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
|
-
|
|
197
|
-
application
|
|
198
|
-
|
|
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).
|
package/docs/query-inspection.md
CHANGED
|
@@ -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 =
|
|
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`
|
|
32
|
-
it does not
|
|
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
|
-
|
|
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
|
|
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 `
|
|
170
|
+
freshness uses `tenon generate --check`, a separate operation.
|