@kequtech/tenon-orm 0.3.1 → 0.4.2
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 +66 -0
- package/README.md +81 -26
- package/dist/affected-rows.js +0 -1
- package/dist/baseline.js +3 -5
- package/dist/check/contracts.d.ts +6 -0
- package/dist/check/contracts.js +206 -0
- package/dist/check/project.d.ts +18 -0
- package/dist/check/project.js +224 -0
- package/dist/check/source.d.ts +51 -0
- package/dist/check/source.js +629 -0
- package/dist/check/types.d.ts +36 -0
- package/dist/check/types.js +1 -0
- package/dist/check.d.ts +4 -0
- package/dist/check.js +147 -0
- package/dist/cli.js +40 -27
- package/dist/connection.d.ts +5 -12
- package/dist/connection.js +12 -121
- package/dist/database.js +10 -8
- package/dist/describe.js +10 -3
- package/dist/generate.d.ts +13 -3
- package/dist/generate.js +52 -25
- package/dist/generation/catalog.d.ts +5 -6
- package/dist/generation/catalog.js +7 -9
- package/dist/generation/output.js +2 -4
- package/dist/generation/render.d.ts +3 -0
- package/dist/generation/render.js +60 -0
- package/dist/generation/schema-summary.d.ts +3 -3
- package/dist/generation/schema-summary.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/inspection/catalog.d.ts +3 -6
- package/dist/inspection/catalog.js +9 -18
- package/dist/inspection/cli.d.ts +12 -0
- package/dist/inspection/cli.js +9 -4
- package/dist/inspection/connection.d.ts +2 -3
- package/dist/inspection/connection.js +20 -22
- package/dist/inspection/options.js +0 -6
- package/dist/inspection/result.d.ts +3 -2
- package/dist/inspection/result.js +13 -26
- package/dist/inspection/types.d.ts +6 -4
- package/dist/migrations/connection.d.ts +3 -3
- package/dist/migrations/connection.js +14 -21
- package/dist/migrations/history.d.ts +4 -4
- package/dist/migrations/history.js +5 -5
- package/dist/migrations.js +7 -9
- package/dist/query-options.d.ts +1 -1
- package/dist/query.js +1 -1
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.js +21 -77
- package/dist/types.d.ts +4 -1
- package/docs/adoption-guide.md +88 -111
- package/docs/agent-guide.md +94 -147
- package/docs/connections.md +185 -0
- package/docs/human-guide.md +133 -146
- package/docs/migrations.md +133 -0
- package/docs/mutations.md +110 -60
- package/docs/query-checking.md +199 -0
- package/docs/query-inspection.md +89 -58
- package/docs/reference.md +232 -437
- package/docs/schema-types.md +263 -0
- package/docs/sql.md +212 -0
- package/package.json +11 -7
- package/dist/driver/types.d.ts +0 -10
- package/dist/driver/types.js +0 -49
- package/dist/generation/table-types.d.ts +0 -2
- package/dist/generation/table-types.js +0 -96
- package/dist/inspection/protocol.d.ts +0 -7
- package/dist/inspection/protocol.js +0 -45
package/docs/reference.md
CHANGED
|
@@ -1,299 +1,166 @@
|
|
|
1
|
-
#
|
|
1
|
+
# API and CLI reference
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
|
|
3
|
+
For a complete first setup, follow [Getting started](human-guide.md). Task guides explain
|
|
4
|
+
[queries](sql.md), [writes](mutations.md), [schema types](schema-types.md),
|
|
5
|
+
[migrations](migrations.md), and [connections](connections.md).
|
|
6
6
|
|
|
7
7
|
## Installation and connection
|
|
8
8
|
|
|
9
|
-
Requires Node.js 24+, PostgreSQL 16+, and ESM. TypeScript consumers need TypeScript 6+ and Node
|
|
10
|
-
development typings.
|
|
11
|
-
special compiler plugin is required. A usual Node TypeScript setup uses `module: "NodeNext"`.
|
|
9
|
+
Requires Node.js 24+, PostgreSQL 16+, and ESM. TypeScript consumers need TypeScript 6+ and Node.js
|
|
10
|
+
development typings. Install `@kequtech/tenon-orm`; pgclear is included as a runtime dependency.
|
|
12
11
|
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`createTenon({ connection, onQuery? })` returns a `Database` with `query`, `transaction`, `native`,
|
|
18
|
-
and `close`. The required `connection` accepts:
|
|
19
|
-
|
|
20
|
-
- A PostgreSQL URI string.
|
|
21
|
-
- A pg `PoolConfig` object; an explicit `{}` uses the driver's environment defaults.
|
|
22
|
-
- An existing `pg.Pool`, borrowed by identity.
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
const db = createTenon({
|
|
26
|
-
connection: {
|
|
27
|
-
connectionString: connection,
|
|
28
|
-
max: 10,
|
|
29
|
-
idleTimeoutMillis: 30_000,
|
|
30
|
-
connectionTimeoutMillis: 30_000,
|
|
31
|
-
options: '-c search_path=app,public',
|
|
32
|
-
},
|
|
33
|
-
});
|
|
12
|
+
```text
|
|
13
|
+
createTenon({ connection, onQuery? }): Database
|
|
34
14
|
```
|
|
35
15
|
|
|
36
|
-
|
|
37
|
-
`
|
|
38
|
-
|
|
39
|
-
milliseconds. pg parses the URI for connection fields; matching URI values override client
|
|
40
|
-
configuration, including an `options` URI parameter overriding the configuration's `options` string.
|
|
41
|
-
The pool sizes and timeouts above are application choices. Configure pool policy in `PoolConfig`;
|
|
42
|
-
old driver-specific URI settings such as `idle_timeout` do not configure pg's idle timeout. Preserve
|
|
43
|
-
existing URI startup options when appending settings such as UTC and `search_path`.
|
|
44
|
-
Preserve deployment credentials and TLS requirements; see
|
|
45
|
-
[pg connection configuration](https://node-postgres.com/features/connecting).
|
|
46
|
-
|
|
47
|
-
Reuse an instance for each database configuration. `db.close({ timeoutMs? })` immediately rejects
|
|
48
|
-
new Tenon work. For owned pools, accepted Tenon operations can drain for up to 5,000 ms by default;
|
|
49
|
-
at the deadline, remaining operations reject and Tenon requests driver termination. `0` skips the
|
|
50
|
-
grace period. The timeout must be an integer from 0 to 2,147,483,647 milliseconds; `CloseOptions` is
|
|
51
|
-
exported. This bounds Tenon's wait, not every native transport's teardown or server execution. It
|
|
52
|
-
sends no PostgreSQL cancellation request and does not cancel JavaScript callbacks. An unbounded
|
|
53
|
-
authentication attempt can keep a connection open after close resolves. Configure pg's
|
|
54
|
-
`connectionTimeoutMillis` when connection establishment also needs a deadline.
|
|
55
|
-
|
|
56
|
-
The first valid close call chooses the timeout. Concurrent and later calls share the same promise
|
|
57
|
-
and outcome. Invalid initial options reject without closing the instance. For borrowed pools, close
|
|
58
|
-
only disables the Tenon wrapper; the application retains the pool and outstanding work. Tenon does
|
|
59
|
-
not load environment files.
|
|
60
|
-
|
|
61
|
-
Qualify SQL names or configure runtime `search_path`. CLI `--namespace` selects tooling scope; it
|
|
62
|
-
does not configure application queries. Baseline capture and Describe accept connection strings
|
|
63
|
-
rather than the runtime connection union.
|
|
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.
|
|
64
19
|
|
|
65
|
-
|
|
20
|
+
The returned `Database` provides:
|
|
66
21
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
22
|
+
| Member | Contract |
|
|
23
|
+
| ------------------------ | ---------------------------------------------------------------------------------------- |
|
|
24
|
+
| `query(query, options?)` | Execute a Tenon `SqlQuery` and apply result/count options |
|
|
25
|
+
| `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
|
+
| `native` | The underlying pgclear pool |
|
|
28
|
+
| `close({ timeoutMs? }?)` | Disable the wrapper and close an owned pool; default grace 5,000 ms |
|
|
70
29
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
settled by their caller before closing. A disconnected client does not reconnect. The application owns
|
|
76
|
-
resubscription and catch-up; notifications are not replayed.
|
|
30
|
+
`onQuery(event)` observes completed Tenon query calls. It may return void or a promise; it is not
|
|
31
|
+
awaited. Observer failures emit warnings without changing the outcome. `QueryEvent` contains
|
|
32
|
+
`durationMs`, `outcome: 'success' | 'error'`, and optional `label`, `command`, `rowCount`, and
|
|
33
|
+
`errorCode`. SQL and values are excluded. Validation failures before execution are not observed.
|
|
77
34
|
|
|
78
|
-
|
|
79
|
-
|
|
35
|
+
See [connection configuration and shutdown](connections.md) for pool settings, TLS, environment
|
|
36
|
+
handling, timeout bounds, and lifecycle examples. Runtime `search_path` is independent of tooling
|
|
37
|
+
`namespace` options.
|
|
80
38
|
|
|
81
|
-
###
|
|
39
|
+
### Driver updates
|
|
82
40
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
import { createTenon } from '@kequtech/tenon-orm';
|
|
90
|
-
|
|
91
|
-
async function listenForChanges(
|
|
92
|
-
connectionString: string,
|
|
93
|
-
onChange: (payload: string | undefined) => void,
|
|
94
|
-
onDisconnect: (error: Error) => void,
|
|
95
|
-
) {
|
|
96
|
-
const db = createTenon({
|
|
97
|
-
connection: { connectionString, max: 1, idleTimeoutMillis: 0, connectionTimeoutMillis: 30_000 },
|
|
98
|
-
});
|
|
99
|
-
const client = await db.native.connect().catch(async (error: unknown) => {
|
|
100
|
-
await db.close();
|
|
101
|
-
throw error;
|
|
102
|
-
});
|
|
103
|
-
let active = true;
|
|
104
|
-
let closing: Promise<void> | undefined;
|
|
105
|
-
|
|
106
|
-
function stop(error?: Error): Promise<void> {
|
|
107
|
-
if (closing) return closing;
|
|
108
|
-
active = false;
|
|
109
|
-
closing = Promise.resolve().then(async () => {
|
|
110
|
-
try {
|
|
111
|
-
if (error) onDisconnect(error);
|
|
112
|
-
} finally {
|
|
113
|
-
client.release(true);
|
|
114
|
-
await db.close();
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
return closing;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const failed = (error: Error) => {
|
|
121
|
-
if (active) void stop(error).catch(console.error);
|
|
122
|
-
};
|
|
123
|
-
client.on('error', failed);
|
|
124
|
-
client.on('end', () => failed(new Error('The notification connection ended.')));
|
|
125
|
-
client.on('notification', (event) => {
|
|
126
|
-
if (!active || event.channel !== 'app_changes') return;
|
|
127
|
-
try {
|
|
128
|
-
onChange(event.payload);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(error);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
try {
|
|
134
|
-
await client.query('LISTEN "app_changes"');
|
|
135
|
-
if (!active) throw new Error('The notification connection closed during setup.');
|
|
136
|
-
} catch (error) {
|
|
137
|
-
await stop();
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
140
|
-
return () => stop();
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Both failure and intentional close discard the dedicated checkout with `release(true)` before
|
|
145
|
-
closing its pool, so no UNLISTEN is needed and normal shutdown does not wait for Tenon's deadline.
|
|
146
|
-
The active flag suppresses duplicate error/end callbacks and intentional-close notifications.
|
|
147
|
-
Create a new helper instance to recover, and reconcile missed events through persistent data.
|
|
148
|
-
|
|
149
|
-
This helper's close function is available after readiness. A manager that also supports replacement
|
|
150
|
-
or shutdown while setup is pending must assign a generation token before awaiting setup. Only the
|
|
151
|
-
current generation may change shared readiness/subscription state or notify its consumers. If stale
|
|
152
|
-
setup later succeeds, close that setup's own listener; if it fails or finishes cleanup, leave the
|
|
153
|
-
replacement alone. Invalidate the current token before intentional shutdown. Keep those guards in
|
|
154
|
-
the application's subscription manager. The fixed channel above is trusted; validate and quote
|
|
155
|
-
application-selected channel names before placing them in native SQL.
|
|
41
|
+
The dependency range is `@kequtech/pgclear: ^0.3.0`. Compatible patches may be updated independently;
|
|
42
|
+
see [driver updates](connections.md#update-the-driver). Tenon re-exports `builtin`, `json`, `param`,
|
|
43
|
+
`defaultCodecs`, and `createTemporalCodecs`, with the driver's `Codec`, `CodecRegistry`, `Parameter`,
|
|
44
|
+
`PgArray`, `TemporalConstructors`, and `TemporalInput` types.
|
|
156
45
|
|
|
157
46
|
## Queries and result modes
|
|
158
47
|
|
|
159
|
-
`sql<Row>`
|
|
160
|
-
execute. `Row`
|
|
161
|
-
|
|
48
|
+
`sql<Row>` is a template tag returning `SqlQuery<Row>` with readonly `text` and `values`. It does
|
|
49
|
+
not execute. `Row` describes one selected record; without it, fields are `unknown`. Neither result
|
|
50
|
+
annotations nor generated types validate data during execution.
|
|
162
51
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
sql<{ id: number; email: string }>`SELECT id, email FROM public.users WHERE id = ${42}`,
|
|
166
|
-
{ returns: 'oneOrNone', label: 'user-by-id' },
|
|
167
|
-
);
|
|
168
|
-
```
|
|
52
|
+
`db.query` and `tx.query` accept `{ returns?, affectedRows?, label? }`. Unknown options reject.
|
|
53
|
+
A supplied label must be a nonblank string.
|
|
169
54
|
|
|
170
|
-
|
|
171
|
-
|
|
55
|
+
| `returns` | Output | Allowed returned rows |
|
|
56
|
+
| ------------------ | ------------------ | --------------------- |
|
|
57
|
+
| `native` (default) | `QueryResult<Row>` | Any |
|
|
58
|
+
| `many` | `readonly Row[]` | Any |
|
|
59
|
+
| `oneOrNone` | `Row \| undefined` | Zero or one |
|
|
60
|
+
| `one` | `Row` | Exactly one |
|
|
172
61
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
| `many` | Plain `Row[]` | Any |
|
|
177
|
-
| `oneOrNone` | `Row \| undefined` | Zero or one |
|
|
178
|
-
| `one` | `Row` | Exactly one |
|
|
62
|
+
`QueryResult` follows pgclear and includes `rows`, `rowCount`, `command`, `commandTag`, and `fields`.
|
|
63
|
+
`rowCount` is `number | null`, independent of `RETURNING`; unsafe integer counts reject. SQL
|
|
64
|
+
`count(*)` is a column value and defaults to string. Row arrays are readonly.
|
|
179
65
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
commands without a count. Result modes never add `LIMIT` or `RETURNING`. A returned-row mismatch
|
|
183
|
-
alone does not undo an autocommitted write; use `affectedRows` for write bounds that roll back.
|
|
66
|
+
Modes do not add `LIMIT` or `RETURNING`. Returned-row mismatches throw `CardinalityError`; alone,
|
|
67
|
+
that does not undo an autocommitted write. For write bounds that roll back, use `affectedRows`.
|
|
184
68
|
|
|
185
|
-
|
|
186
|
-
include `QueryOptions`, `QueryReturnMode`,
|
|
69
|
+
`QueryClient` is the query-only interface accepted by either a `Database` or callback `Transaction`.
|
|
70
|
+
Other exported types include `QueryOptions`, `QueryReturnMode`, `QueryOutput<Row, Mode>`,
|
|
71
|
+
`SqlQuery`, `SqlFragment`, and `UnknownRow`.
|
|
187
72
|
|
|
188
73
|
### Adapting transactional results
|
|
189
74
|
|
|
190
|
-
For
|
|
75
|
+
For an adapter expecting `{ rows }`, execute its trusted SQL through the callback's `tx`:
|
|
191
76
|
|
|
192
77
|
```ts
|
|
193
78
|
import { unsafeSql, type QueryClient } from '@kequtech/tenon-orm';
|
|
194
79
|
|
|
195
80
|
async function queryRows(tx: QueryClient, text: string, values: readonly unknown[]) {
|
|
196
81
|
const result = await tx.query(unsafeSql(text, values));
|
|
197
|
-
return { rows: result.rows };
|
|
82
|
+
return { rows: [...result.rows] };
|
|
198
83
|
}
|
|
199
84
|
```
|
|
200
85
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
86
|
+
The copy supplies a mutable array if the adapter requires one. Values must already be valid driver
|
|
87
|
+
inputs; external arrays/objects need type-aware `param`, `json`, or text encoding. Await adapter
|
|
88
|
+
work inside the callback. Native pool queries use another checkout and do not join it.
|
|
89
|
+
|
|
90
|
+
For a job-library transaction adapter such as pg-boss's, verify actual SQL inputs and timestamp
|
|
91
|
+
expectations. Enqueueing and returning an ID may need no output conversion; returned timestamps
|
|
92
|
+
follow the database-wide codecs. Keep atomic commit/rollback tests. Tenon provides no per-query
|
|
93
|
+
codec override, and input encoders are independent of output codecs.
|
|
206
94
|
|
|
207
95
|
## SQL parameters and identifiers
|
|
208
96
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
`
|
|
213
|
-
|
|
97
|
+
| API | Contract |
|
|
98
|
+
| ------------------------------ | ------------------------------------------------------------------------------ |
|
|
99
|
+
| `sql<Row>` template tag | Bind ordinary values; interpolate Tenon fragments and identifiers structurally |
|
|
100
|
+
| `identifier(...names)` | Quote components separately, e.g. `"public"."users"` |
|
|
101
|
+
| `unsafeSql<Row>(text, values)` | Trusted external SQL with an explicit values array, including `[]` |
|
|
102
|
+
| `json(value, type?)` | Encode JSON; default type is jsonb |
|
|
103
|
+
| `param(value, codec, format?)` | Choose an explicit driver parameter type and encoder |
|
|
214
104
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
WHERE id = ANY(${ids}::integer[])`;
|
|
219
|
-
```
|
|
105
|
+
Do not quote interpolations or mix manual `$n` placeholders into templates. Plain binds accept null,
|
|
106
|
+
string, finite number, bigint, boolean, valid Date, supported Temporal values, and Uint8Array.
|
|
107
|
+
Undefined, plain objects/arrays, nonfinite numbers, and unsafe integer numbers reject.
|
|
220
108
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
109
|
+
Strings, numbers, bigint, Date, and supported Temporal values use server type inference. Date
|
|
110
|
+
encodes in UTC. Cast standalone parameters when PostgreSQL needs type context. Use `json` for JSON
|
|
111
|
+
and `param` for PostgreSQL arrays/custom encodings. Do not mutate bound values before settlement.
|
|
224
112
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
113
|
+
Identifier components require 1–63 UTF-8 bytes and no NUL. Choose approved names; quoting is not
|
|
114
|
+
authorization. SQL syntax such as `DESC` belongs in fixed fragments or query alternatives. Never
|
|
115
|
+
concatenate request input into `unsafeSql` text.
|
|
228
116
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
Migrations deliberately support multi-statement scripts.
|
|
117
|
+
A query executes one statement through the extended protocol. Tenon does not expose a prepared
|
|
118
|
+
statement cache or query `name` option. See [writing queries](sql.md) for examples.
|
|
232
119
|
|
|
233
120
|
## SQL fragments and joins
|
|
234
121
|
|
|
235
|
-
`sql.fragment` creates immutable partial SQL with bound values. It has no row type
|
|
236
|
-
|
|
122
|
+
- `sql.fragment` creates immutable partial SQL with bound values. It has no row type and
|
|
123
|
+
cannot execute alone. Blank fragments reject.
|
|
124
|
+
- `sql.join(fragments, 'AND' | 'OR' | ',')` requires a nonempty array of fragments. It does not add
|
|
125
|
+
parentheses or alter precedence.
|
|
126
|
+
- Nested/repeated fragments preserve ordered parameters. Full `sql`/`unsafeSql` queries cannot be
|
|
127
|
+
nested. Literal `$1` text in SQL strings/comments is not rewritten.
|
|
237
128
|
|
|
238
|
-
|
|
239
|
-
`','`. It does not add parentheses or change SQL precedence. Each fragment use receives correctly
|
|
240
|
-
ordered parameters, including repeated and nested uses; literal `$1` text in strings/comments is
|
|
241
|
-
unchanged.
|
|
129
|
+
See [composition examples](sql.md#compose-sql-fragments), including the empty-filter case.
|
|
242
130
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
WHERE ${sql.join(conditions, 'AND')} ORDER BY id`;
|
|
131
|
+
## Object-based mutation fragments
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
sql.insertValues<Input>(rowOrRows, columns?): SqlFragment
|
|
135
|
+
sql.assignments<Input>(patch, columns?): SqlFragment
|
|
249
136
|
```
|
|
250
137
|
|
|
251
|
-
|
|
252
|
-
|
|
138
|
+
These build fragments, not complete statements. Keep table, predicates, expressions, conflict
|
|
139
|
+
behavior, and `RETURNING` in authored SQL.
|
|
253
140
|
|
|
254
|
-
|
|
141
|
+
| Cell | Insert | Assignment |
|
|
142
|
+
| ----------------------------- | --------------- | --------------- |
|
|
143
|
+
| Missing or undefined | `DEFAULT` | Omitted |
|
|
144
|
+
| Null | Bound SQL NULL | Bound SQL NULL |
|
|
145
|
+
| Other accepted value | Bound parameter | Bound parameter |
|
|
146
|
+
| SQL fragment/identifier/query | Rejected | Rejected |
|
|
255
147
|
|
|
256
|
-
|
|
257
|
-
|
|
148
|
+
Inputs must be plain objects, including null-prototype objects. Only own data properties are read;
|
|
149
|
+
selected accessors reject. Helpers capture fields at construction; mutable bound values must remain
|
|
150
|
+
unchanged until execution settles.
|
|
258
151
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await db.query(sql`UPDATE public.users SET ${sql.assignments({ active: false })} WHERE id = ${42}`);
|
|
264
|
-
```
|
|
152
|
+
Without `columns`, inserts infer the union of enumerable keys with defined values in first-seen
|
|
153
|
+
order. Assignments use own enumerable keys and omit undefined values. Explicit columns must be
|
|
154
|
+
nonempty, unique, and valid identifiers; they select/order keys and ignore unlisted properties.
|
|
155
|
+
TypeScript checks selected names against known input keys. Selection can discard required values.
|
|
265
156
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
| Input | Insert | Assignment |
|
|
271
|
-
| -------------------------------------------- | ---------------------- | --------------- |
|
|
272
|
-
| Missing or `undefined` | `DEFAULT` for the cell | Omitted |
|
|
273
|
-
| `null` | Bound SQL null | Bound SQL null |
|
|
274
|
-
| Other value | Bound parameter | Bound parameter |
|
|
275
|
-
| SQL fragment, identifier, or query as a cell | Rejected | Rejected |
|
|
276
|
-
|
|
277
|
-
Inserts accept one object or an array. Without `columns`, they infer the union of enumerable keys
|
|
278
|
-
with defined values in first-seen order. Heterogeneous rows stay aligned. A single default-only row
|
|
279
|
-
emits `DEFAULT VALUES`. Empty batches fail; multiple default-only rows need explicit columns.
|
|
280
|
-
Assignments omit undefined values and fail when no assignments remain, even if surrounding SQL has
|
|
281
|
-
other assignments.
|
|
282
|
-
|
|
283
|
-
A supplied column list must be nonempty, unique, and valid. It selects and orders fields, ignoring
|
|
284
|
-
unlisted keys. Missing insert cells use defaults; missing update fields are omitted. TypeScript
|
|
285
|
-
checks selected names against known input keys, but selection can still discard required fields.
|
|
286
|
-
Helpers have no schema metadata; PostgreSQL enforces identity, generated-column, and constraint
|
|
287
|
-
rules. Use [generated inputs](#insert-and-update-inputs) and application validation.
|
|
288
|
-
|
|
289
|
-
Write arithmetic, `column = DEFAULT`, and `excluded.column` in the surrounding SQL. There is no
|
|
290
|
-
expression/default sentinel. A complete query accepts at most 65,535 bound parameters, including
|
|
291
|
-
surrounding interpolations; Tenon does not split batches. See the [mutation cookbook](mutations.md).
|
|
157
|
+
A single default-only insert emits `DEFAULT VALUES`. Empty batches reject; multiple default-only
|
|
158
|
+
rows need explicit columns. Empty assignments always reject. There is no expression/default
|
|
159
|
+
sentinel; author `column = DEFAULT` or arithmetic in SQL. The complete query's limit is 65,535
|
|
160
|
+
parameters; Tenon does not split batches. See [writing data](mutations.md).
|
|
292
161
|
|
|
293
162
|
## Transactions, guarded writes, and errors
|
|
294
163
|
|
|
295
|
-
For an existing user with id 42:
|
|
296
|
-
|
|
297
164
|
```ts
|
|
298
165
|
await db.transaction(async (tx) => {
|
|
299
166
|
await tx.query(sql`UPDATE public.users SET active = false WHERE id = ${42}`, {
|
|
@@ -302,239 +169,167 @@ await db.transaction(async (tx) => {
|
|
|
302
169
|
});
|
|
303
170
|
```
|
|
304
171
|
|
|
305
|
-
Use `tx` for every
|
|
306
|
-
|
|
307
|
-
database instance.
|
|
308
|
-
BEGIN/COMMIT/ROLLBACK boundaries.
|
|
172
|
+
Use `tx` for every participating query and await all work. The callback commits on success, rolls
|
|
173
|
+
back on an escaping error, and its client expires when the callback finishes. Transactions belong
|
|
174
|
+
to one database instance. Calling the root `db` uses the pool, not the callback session.
|
|
309
175
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
unguarded SQL errors normally abort the transaction.
|
|
176
|
+
Tenon owns BEGIN/COMMIT/ROLLBACK; direct boundary statements are rejected. `tx` provides no nested
|
|
177
|
+
transaction method. Manual savepoints are supported on the callback session. To choose isolation,
|
|
178
|
+
issue `SET TRANSACTION ISOLATION LEVEL SERIALIZABLE` before data queries; use
|
|
179
|
+
`SET LOCAL statement_timeout = '5s'` to bound statements in that transaction.
|
|
315
180
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
the outcome unknown; applications own retry and idempotency policy.
|
|
181
|
+
A write guard is exactly one of `{ exact: n }` or `{ max: n }`, with a nonnegative safe integer.
|
|
182
|
+
It checks the final INSERT/UPDATE/DELETE/MERGE count, independently of `RETURNING`:
|
|
319
183
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
184
|
+
| Call | Guard behavior |
|
|
185
|
+
| ----------------------------------- | ---------------------------------------------------------------------------- |
|
|
186
|
+
| `db.query(query, { affectedRows })` | Wrap in a transaction; roll back the operation on a guard/result/SQL failure |
|
|
187
|
+
| `tx.query(query, { affectedRows })` | Wrap in a savepoint; roll back that operation before rejecting |
|
|
323
188
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
behavior. `AffectedRowsError` carries `expected`, `actual`, `command`, and code
|
|
328
|
-
`TENON_AFFECTED_ROWS_MISMATCH` or `TENON_AFFECTED_ROWS_UNSUPPORTED`. Work/cleanup failures can
|
|
329
|
-
produce `AggregateError`.
|
|
189
|
+
A caught guarded failure permits continued work. An unguarded PostgreSQL error normally leaves the
|
|
190
|
+
transaction aborted; let it escape so the callback rolls back. Tenon serializes whole guarded
|
|
191
|
+
operations within a transaction, including savepoint handling.
|
|
330
192
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
query outcome. Query success inside a callback does not establish outer commit.
|
|
193
|
+
Counts exclude extra trigger/cascade and CTE effects and cannot identify the wrong single row.
|
|
194
|
+
Sequence advances and external effects may survive rollback. There are no automatic retries. A lost
|
|
195
|
+
COMMIT acknowledgment can leave the outcome unknown; the application owns retry/idempotency policy.
|
|
335
196
|
|
|
336
|
-
|
|
197
|
+
| Error | Code and fields |
|
|
198
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `CardinalityError` | `TENON_CARDINALITY_MISMATCH`; `expected` (`one` or `oneOrNone`), `actual` returned rows, optional `label` |
|
|
200
|
+
| `AffectedRowsError` | `TENON_AFFECTED_ROWS_MISMATCH` or `TENON_AFFECTED_ROWS_UNSUPPORTED`; `expected` constraint, `actual: number \| null`, `command` |
|
|
201
|
+
| Tenon option/composition validation | Ordinary `Error` |
|
|
202
|
+
| Driver/server failure | Native details, including driver `PGCLEAR_*` codes or PostgreSQL SQLSTATE |
|
|
203
|
+
| Work and cleanup both fail | May be an `AggregateError` |
|
|
337
204
|
|
|
338
|
-
|
|
339
|
-
consistent read-only snapshot; it does not need migration files or history. The CLI writes them
|
|
340
|
-
atomically. Default namespace is `public`; default output is `src/generated-schema.ts`. Both `.ts`
|
|
341
|
-
and `.d.ts` outputs are supported. Commit exact output and exclude it from formatting.
|
|
205
|
+
Use codes and structured fields rather than matching message text. Retain unexpected failures.
|
|
342
206
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
207
|
+
## Generated types and checking
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
db.generateTypes({ out, namespace?, check?, onWarning? }): Promise<void>
|
|
211
|
+
generateDefinitions({ connection, namespace?, onWarning? }): Promise<string>
|
|
346
212
|
```
|
|
347
213
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
**Generated declarations are not a version-stable schema fingerprint.** TypeScript mappings can
|
|
354
|
-
change without schema drift. The schema-summary comments describe SQL column types, defaults,
|
|
355
|
-
constraints, indexes, and referenced domains independently of those mappings, but their formatting
|
|
356
|
-
and PostgreSQL's rendering of definitions are not a versioned comparison contract either. Preserve
|
|
357
|
-
reviewed generator-specific references if an existing guard hashes this output; see
|
|
358
|
-
[baseline verification](adoption-guide.md#check-and-adopt-an-existing-database).
|
|
359
|
-
|
|
360
|
-
Generation covers ordinary tables and partitioned roots. Views, foreign tables, and attached
|
|
361
|
-
partitions need query-specific types. A missing namespace fails; an existing empty schema is valid.
|
|
362
|
-
Exact table names are available through `Rows['users']`; valid nonreserved identifiers also receive
|
|
363
|
-
named aliases. Use `Pick` for projections; a join or aggregate needs its actual selected shape.
|
|
364
|
-
|
|
365
|
-
| PostgreSQL type | Read representation | Generated write input |
|
|
366
|
-
| ---------------------------------------------- | -------------------- | ------------------------------------- |
|
|
367
|
-
| smallint, integer, oid, real, double precision | `number` | `number` |
|
|
368
|
-
| bigint | `string` | `string \| bigint` |
|
|
369
|
-
| numeric | `string` | `string \| number` |
|
|
370
|
-
| boolean | `boolean` | `boolean` |
|
|
371
|
-
| text, name, varchar, char, uuid, time, timetz | `string` | `string` |
|
|
372
|
-
| date, timestamp, timestamptz | `Date \| number` | `Date \| string` |
|
|
373
|
-
| enum | String literal union | Same union; `never` for an empty enum |
|
|
374
|
-
| json, jsonb | `unknown` | `string` containing encoded JSON |
|
|
375
|
-
| bytea | `Buffer` | `Buffer` |
|
|
376
|
-
|
|
377
|
-
Nullable columns add null; `unknown` already includes it. Domains retain verified base mappings and
|
|
378
|
-
the column's read nullability. Verified arrays use `PgArray<T> = (T | null | PgArray<T>)[]` for null
|
|
379
|
-
elements and multiple dimensions. Numeric arrays contain numbers and can lose precision; select/cast
|
|
380
|
-
them as text when exact values are required. Bigint arrays contain strings; enum arrays and arrays
|
|
381
|
-
of domains are unverified. Unmapped types warn and use `unknown` reads / `never` non-null writes.
|
|
382
|
-
Custom parsers need matching application types.
|
|
383
|
-
|
|
384
|
-
Date-only values and timestamps without time zone use Node's local timezone. Infinite temporal reads
|
|
385
|
-
are numbers (`Infinity` or `-Infinity`); narrow with `value instanceof Date` before date methods.
|
|
386
|
-
Temporal write inputs accept `Date | string`; use PostgreSQL's text `infinity` or `-infinity` when
|
|
387
|
-
needed.
|
|
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.
|
|
388
219
|
|
|
389
|
-
###
|
|
220
|
+
### Application aliases
|
|
390
221
|
|
|
391
|
-
|
|
392
|
-
|
|
222
|
+
Export aliases such as `UserRow = Database['users']['row']` in a handwritten file. See
|
|
223
|
+
[application aliases](schema-types.md#application-aliases).
|
|
393
224
|
|
|
394
|
-
|
|
225
|
+
### Persistence and domain values
|
|
395
226
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
id text PRIMARY KEY,
|
|
399
|
-
data jsonb NOT NULL,
|
|
400
|
-
expires_at timestamptz NOT NULL
|
|
401
|
-
);
|
|
402
|
-
```
|
|
227
|
+
Generated types describe storage representations. Validate JSON, text refinements, and domain
|
|
228
|
+
values at the application boundary; see [the worked example](schema-types.md#persistence-and-domain-values).
|
|
403
229
|
|
|
404
|
-
|
|
230
|
+
### Insert and update inputs
|
|
405
231
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
import type { Rows } from './generated-schema.js';
|
|
409
|
-
|
|
410
|
-
function sessionData(value: unknown): { userId: string } {
|
|
411
|
-
if (
|
|
412
|
-
typeof value !== 'object' ||
|
|
413
|
-
value === null ||
|
|
414
|
-
Array.isArray(value) ||
|
|
415
|
-
!('userId' in value) ||
|
|
416
|
-
typeof value.userId !== 'string' ||
|
|
417
|
-
value.userId.length === 0
|
|
418
|
-
) {
|
|
419
|
-
throw new Error('Expected session data with a nonempty userId');
|
|
420
|
-
}
|
|
421
|
-
return { userId: value.userId };
|
|
422
|
-
}
|
|
232
|
+
Generated types account for defaults, identity/computed columns, domains, and nullability. See
|
|
233
|
+
[insert/update rules](schema-types.md#insert-and-update-inputs).
|
|
423
234
|
|
|
424
|
-
|
|
425
|
-
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
|
|
426
|
-
throw new Error('Expected a finite database timestamp');
|
|
427
|
-
}
|
|
428
|
-
return value;
|
|
429
|
-
}
|
|
235
|
+
## Choosing a validation tool
|
|
430
236
|
|
|
431
|
-
|
|
432
|
-
id: 'example-session',
|
|
433
|
-
data: JSON.stringify(sessionData({ userId: 'user-42' })),
|
|
434
|
-
expires_at: new Date(Date.now() + 60_000),
|
|
435
|
-
} satisfies Rows.Insert['sessions'];
|
|
436
|
-
await db.query(sql`INSERT INTO public.sessions ${sql.insertValues(input)}`);
|
|
437
|
-
|
|
438
|
-
const row = await db.query(
|
|
439
|
-
sql<Rows['sessions']>`SELECT id, data, expires_at FROM public.sessions
|
|
440
|
-
WHERE id = ${input.id} AND isfinite(expires_at) AND expires_at > now()`,
|
|
441
|
-
{ returns: 'oneOrNone' },
|
|
442
|
-
);
|
|
443
|
-
const session =
|
|
444
|
-
row === undefined
|
|
445
|
-
? undefined
|
|
446
|
-
: {
|
|
447
|
-
id: row.id,
|
|
448
|
-
data: sessionData(row.data),
|
|
449
|
-
expiresAt: finiteDate(row.expires_at),
|
|
450
|
-
};
|
|
451
|
-
```
|
|
237
|
+
Use the intended development/test schema and role. TypeScript itself needs no connection.
|
|
452
238
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
[JSON and PostgreSQL arrays](mutations.md#json-and-postgresql-arrays) for array write boundaries.
|
|
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 |
|
|
461
246
|
|
|
462
|
-
|
|
463
|
-
|
|
247
|
+
After schema changes, migrate the test database, regenerate/review declarations, and run typechecks,
|
|
248
|
+
applicable source checks, and database tests. In CI, check generated output without replacing it.
|
|
249
|
+
Source checking and inspection parse/analyze application SQL without executing it; analysis can
|
|
250
|
+
take locks. Neither proves actual bound values or write effects.
|
|
464
251
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
```ts
|
|
468
|
-
import type { Rows } from './generated-schema.js';
|
|
252
|
+
## Source-aware checking
|
|
469
253
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
sql.insertValues(input);
|
|
473
|
-
sql.assignments(patch);
|
|
474
|
-
```
|
|
254
|
+
`checkQueries(options)` is exported from `@kequtech/tenon-orm/check`, alongside its public types.
|
|
255
|
+
The bundled TypeScript compiler loads only through that path or the check command.
|
|
475
256
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
`
|
|
479
|
-
|
|
257
|
+
Options are `{ connection, target?, project?, session?, timeouts?, codecs?, readTypes? }`.
|
|
258
|
+
Connection is a URI. The report contains `ok`, `project`, `queries`, `diagnostics`, and available
|
|
259
|
+
inspection `context`. Mismatches, unchecked sites, and no sites fail; unproven findings alone do not.
|
|
260
|
+
See [query checking](query-checking.md) for selection, supported composition, and custom decoders.
|
|
480
261
|
|
|
481
|
-
|
|
482
|
-
`GENERATED BY DEFAULT` identities are optional on insert and writable on update; other primary keys
|
|
483
|
-
remain writable. Use `Pick`/`Omit` for application restrictions. Apply the generated types through
|
|
484
|
-
annotations, `satisfies`, or helper generic arguments; they do not validate constraints, JSON
|
|
485
|
-
structure, authorization, or nonempty input.
|
|
262
|
+
## Query inspection
|
|
486
263
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
264
|
+
```text
|
|
265
|
+
describeQuery({ connection, query, session?, timeouts?, codecs? }): Promise<QueryDescription>
|
|
266
|
+
```
|
|
490
267
|
|
|
491
|
-
|
|
268
|
+
Connection is a URI; query is finished SQL text or `{ text, parameterTypes? }`. A `SqlQuery` fits
|
|
269
|
+
because it has `text`; values and its row annotation are not checked. The report includes
|
|
270
|
+
`parameters`, `columns`, `rowType`, `issues`, and `context`. It uses a separate session with bounded
|
|
271
|
+
timeouts. See [query inspection](query-inspection.md) for fields and finding codes.
|
|
492
272
|
|
|
493
|
-
|
|
494
|
-
SQL string or `{ text }` object. It returns PostgreSQL metadata, conservative read mappings and row
|
|
495
|
-
declaration, session context, and coded findings. It does not bind values, execute the statement, or
|
|
496
|
-
verify `sql<Row>`.
|
|
273
|
+
## Native driver access
|
|
497
274
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
275
|
+
`db.native` is the actual pgclear pool. Calls bypass Tenon's guards and observations and do not join
|
|
276
|
+
callback transactions. Use `queryClassic` for text; native `query` accepts only pgclear's own SQL
|
|
277
|
+
tag. See [native driver features](connections.md#use-native-driver-features) for session ownership,
|
|
278
|
+
prepared statements, and dedicated notification listeners.
|
|
279
|
+
|
|
280
|
+
## Troubleshooting
|
|
281
|
+
|
|
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. |
|
|
296
|
+
|
|
297
|
+
After a guarded query rejects, savepoint cleanup has finished. An unguarded server error normally
|
|
298
|
+
aborts a transaction. A timeout or lost connection does not itself establish whether a commit
|
|
299
|
+
happened; review the transaction contract before adding retries.
|
|
502
300
|
|
|
503
301
|
## Command reference
|
|
504
302
|
|
|
505
|
-
|
|
506
|
-
loaded. Paths
|
|
507
|
-
rejected; `--check` is only a generation check, not a migration dry run.
|
|
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.
|
|
508
305
|
|
|
509
306
|
```text
|
|
510
307
|
tenon new <name> [--directory migrations]
|
|
511
308
|
tenon migrate [--directory migrations] [--namespace public]
|
|
512
309
|
tenon status [--directory migrations] [--namespace public]
|
|
513
310
|
tenon rollback [--directory migrations] [--namespace public] [--steps 1]
|
|
514
|
-
tenon generate [--namespace public] [--out src/generated-schema.ts] [--check]
|
|
515
|
-
tenon describe <query.sql> [--role name] [--search-path value] [--time-zone value]
|
|
516
|
-
[--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
|
|
517
311
|
tenon baseline capture [--directory migrations] [--namespace public] [--schema name ...]
|
|
518
312
|
tenon baseline adopt [--directory migrations] [--namespace public]
|
|
313
|
+
tenon check [file.ts[:line]] [--project tsconfig.json] [--json]
|
|
314
|
+
[--role name] [--search-path value] [--time-zone value]
|
|
315
|
+
[--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
|
|
316
|
+
tenon describe <query.sql> [--role name] [--search-path value] [--time-zone value]
|
|
317
|
+
[--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
|
|
519
318
|
```
|
|
520
319
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
Programmatic `migrate`, `migrationStatus`, and `rollback` accept
|
|
538
|
-
`{ connection, directory?, namespace? }`; rollback also accepts `steps`. Migration/rollback return
|
|
539
|
-
completed IDs. `createMigration({ name, directory? })` returns `{ id, upPath, downPath }`;
|
|
540
|
-
`readMigrations(directory?)` returns validated SQL and checksums.
|
|
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 |
|
|
329
|
+
|
|
330
|
+
Successful commands exit 0. Invalid input or a failed operation exits 1. `check` also exits 1 for
|
|
331
|
+
mismatches, unchecked sites, or no sites; unproven findings alone do not fail. `describe` can exit 0
|
|
332
|
+
with unresolved findings. There is no migration dry-run command.
|
|
333
|
+
|
|
334
|
+
Programmatic runner APIs are [documented with migrations](migrations.md#programmatic-api).
|
|
335
|
+
Baseline APIs and prerequisites are in [adoption](adoption-guide.md#baseline-api).
|