@kequtech/tenon-orm 0.4.0 → 0.4.3
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 +22 -2
- package/README.md +93 -51
- package/dist/check.js +4 -2
- package/dist/cli.js +97 -40
- package/dist/config/init.d.ts +2 -0
- package/dist/config/init.js +34 -0
- package/dist/config/load.d.ts +7 -0
- package/dist/config/load.js +159 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +1 -0
- package/dist/connection.d.ts +5 -0
- package/dist/describe.js +3 -1
- package/dist/generation/output.js +0 -2
- package/dist/index.d.ts +2 -1
- 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 +49 -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/validation.d.ts +2 -0
- package/dist/validation.js +6 -0
- package/docs/adoption-guide.md +94 -153
- package/docs/agent-guide.md +93 -176
- package/docs/configuration.md +146 -0
- package/docs/connections.md +187 -0
- package/docs/human-guide.md +150 -179
- package/docs/migrations.md +140 -0
- package/docs/mutations.md +104 -67
- package/docs/query-checking.md +122 -120
- package/docs/query-inspection.md +83 -76
- package/docs/reference.md +234 -474
- package/docs/schema-types.md +266 -0
- package/docs/sql.md +212 -0
- package/package.json +1 -1
package/docs/reference.md
CHANGED
|
@@ -1,256 +1,165 @@
|
|
|
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
|
-
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
|
-
|
|
12
|
+
```text
|
|
13
|
+
createTenon({ connection, onQuery? }): Database
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
original helpers and types; Pgclear continues to own conversion. Generated schema declarations
|
|
21
|
-
import their helper types from Tenon.
|
|
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.
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
and `close`. The required `connection` accepts a PostgreSQL URI, pgclear `PoolOptions`, or an existing
|
|
25
|
-
pgclear `Pool`, borrowed by identity. `ConnectionOptions` aliases pgclear's `PoolOptions`.
|
|
20
|
+
The returned `Database` provides:
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
connectTimeoutMs: 5000,
|
|
34
|
-
acquireTimeoutMs: 5000,
|
|
35
|
-
startupParameters: { search_path: 'app,public' },
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
```
|
|
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
|
+
| `native` | The underlying pgclear pool |
|
|
27
|
+
| `close({ timeoutMs? }?)` | Disable the wrapper and close an owned pool; default grace 5,000 ms |
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
See the installed pgclear public API documentation for driver configuration.
|
|
29
|
+
`onQuery(event)` observes completed Tenon query calls. It may return void or a promise; it is not
|
|
30
|
+
awaited. Observer failures emit warnings without changing the outcome. `QueryEvent` contains
|
|
31
|
+
`durationMs`, `outcome: 'success' | 'error'`, and optional `label`, `command`, `rowCount`, and
|
|
32
|
+
`errorCode`. SQL and values are excluded. Validation failures before execution are not observed.
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Timeout must be an integer from 0 to 2,147,483,647 milliseconds. Shutdown waits for owned driver
|
|
50
|
-
resources, not arbitrary JavaScript callbacks or proof of remote rollback.
|
|
51
|
-
|
|
52
|
-
The first valid close call fixes its timeout; concurrent and later calls share its promise and
|
|
53
|
-
outcome. Invalid initial options reject without closing. For borrowed pools, close disables the
|
|
54
|
-
Tenon wrapper while the application retains pool ownership and outstanding work.
|
|
55
|
-
|
|
56
|
-
Qualify SQL names or configure runtime `search_path`. CLI `--namespace` selects tooling scope;
|
|
57
|
-
it does not configure application queries. Baseline capture and Describe accept connection strings
|
|
58
|
-
rather than the runtime connection union.
|
|
34
|
+
See [connection configuration and shutdown](connections.md) for pool settings, TLS, environment
|
|
35
|
+
handling, timeout bounds, and lifecycle examples. Runtime `search_path` is independent of tooling
|
|
36
|
+
`namespace` options.
|
|
59
37
|
|
|
60
38
|
### Driver updates
|
|
61
39
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
range and needs an explicit Tenon compatibility update; it is not an automatic upgrade.
|
|
67
|
-
|
|
68
|
-
Install Pgclear directly only when the application imports its native APIs, such as `listen` or
|
|
69
|
-
`createPool`. Keep that version compatible with Tenon's declared range to allow a shared copy. If
|
|
70
|
-
both SQL tags are needed, alias the native one as `pgSql`: Tenon's tag belongs in `db.query`, while
|
|
71
|
-
Pgclear's tag belongs in native `query`. Native `queryClassic(text, values)` avoids a second tag.
|
|
72
|
-
TypeScript and runtime validation reject sending one package's query to the other package's API.
|
|
73
|
-
|
|
74
|
-
## Native driver access
|
|
75
|
-
|
|
76
|
-
`db.native` is the actual pgclear `Pool`, readonly in TypeScript. Native calls bypass Tenon's result
|
|
77
|
-
modes, affected-row guards, SQL checks, and `onQuery`. They do not join a Tenon callback transaction;
|
|
78
|
-
callback clients expose only `tx.query`.
|
|
79
|
-
|
|
80
|
-
Use `queryClassic(text, values?)`, `acquire`/`withSession`, `prepare`, streams, and COPY through pgclear's
|
|
81
|
-
public APIs. Held sessions must be released. Use pgclear `listen` for a dedicated notification
|
|
82
|
-
connection with explicit reconnect and lifecycle policy. Notifications have no replay.
|
|
83
|
-
|
|
84
|
-
Pgclear's native `query` accepts only its own `sql` tag results. Tenon's `sql` and `unsafeSql` results
|
|
85
|
-
belong in `db.query`; use `queryClassic` for finished SQL text at the native boundary.
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
import { listen } from '@kequtech/pgclear';
|
|
89
|
-
|
|
90
|
-
const listener = await listen(connection, {
|
|
91
|
-
channels: ['users_changed'],
|
|
92
|
-
onNotification: ({ payload }) => console.log(payload),
|
|
93
|
-
reconnect: true,
|
|
94
|
-
});
|
|
95
|
-
// At application shutdown:
|
|
96
|
-
await listener.close();
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Close owned pools through `db.close()`. A separate listener has its own lifetime; close it explicitly.
|
|
100
|
-
Borrowed pools remain usable until their owner calls `pool.close()`.
|
|
40
|
+
The dependency range is `@kequtech/pgclear: ^0.3.0`. Compatible patches may be updated independently;
|
|
41
|
+
see [driver updates](connections.md#update-the-driver). Tenon re-exports `builtin`, `json`, `param`,
|
|
42
|
+
`defaultCodecs`, and `createTemporalCodecs`, with the driver's `Codec`, `CodecRegistry`, `Parameter`,
|
|
43
|
+
`PgArray`, `TemporalConstructors`, and `TemporalInput` types.
|
|
101
44
|
|
|
102
45
|
## Queries and result modes
|
|
103
46
|
|
|
104
|
-
`sql<Row>`
|
|
105
|
-
execute. `Row`
|
|
106
|
-
|
|
47
|
+
`sql<Row>` is a template tag returning `SqlQuery<Row>` with readonly `text` and `values`. It does
|
|
48
|
+
not execute. `Row` describes one selected record; without it, fields are `unknown`. Neither result
|
|
49
|
+
annotations nor generated types validate data during execution.
|
|
107
50
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
sql<{ id: number; email: string }>`SELECT id, email FROM public.users WHERE id = ${42}`,
|
|
111
|
-
{ returns: 'oneOrNone', label: 'user-by-id' },
|
|
112
|
-
);
|
|
113
|
-
```
|
|
51
|
+
`db.query` and `tx.query` accept `{ returns?, affectedRows?, label? }`. Unknown options reject.
|
|
52
|
+
A supplied label must be a nonblank string.
|
|
114
53
|
|
|
115
|
-
|
|
116
|
-
|
|
54
|
+
| `returns` | Output | Allowed returned rows |
|
|
55
|
+
| ------------------ | ------------------ | --------------------- |
|
|
56
|
+
| `native` (default) | `QueryResult<Row>` | Any |
|
|
57
|
+
| `many` | `readonly Row[]` | Any |
|
|
58
|
+
| `oneOrNone` | `Row \| undefined` | Zero or one |
|
|
59
|
+
| `one` | `Row` | Exactly one |
|
|
117
60
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| `many` | `readonly Row[]` | Any |
|
|
122
|
-
| `oneOrNone` | `Row \| undefined` | Zero or one |
|
|
123
|
-
| `one` | `Row` | Exactly one |
|
|
61
|
+
`QueryResult` follows pgclear and includes `rows`, `rowCount`, `command`, `commandTag`, and `fields`.
|
|
62
|
+
`rowCount` is `number | null`, independent of `RETURNING`; unsafe integer counts reject. SQL
|
|
63
|
+
`count(*)` is a column value and defaults to string. Row arrays are readonly.
|
|
124
64
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
integer range reject instead of rounding. SQL `count(*)` is an int8 column value and reads as a string.
|
|
128
|
-
`rowCount` is the command's affected-row count, independent of `RETURNING`; it can be null for
|
|
129
|
-
commands without a count. Result modes never add `LIMIT` or `RETURNING`. A returned-row mismatch
|
|
130
|
-
alone does not undo an autocommitted write; use `affectedRows` for write bounds that roll back.
|
|
65
|
+
Modes do not add `LIMIT` or `RETURNING`. Returned-row mismatches throw `CardinalityError`; alone,
|
|
66
|
+
that does not undo an autocommitted write. For write bounds that roll back, use `affectedRows`.
|
|
131
67
|
|
|
132
|
-
|
|
133
|
-
include `QueryOptions`, `QueryReturnMode`,
|
|
68
|
+
`QueryClient` is the query-only interface accepted by either a `Database` or callback `Transaction`.
|
|
69
|
+
Other exported types include `QueryOptions`, `QueryReturnMode`, `QueryOutput<Row, Mode>`,
|
|
70
|
+
`SqlQuery`, `SqlFragment`, and `UnknownRow`.
|
|
134
71
|
|
|
135
72
|
### Adapting transactional results
|
|
136
73
|
|
|
137
|
-
For
|
|
74
|
+
For an adapter expecting `{ rows }`, execute its trusted SQL through the callback's `tx`:
|
|
138
75
|
|
|
139
76
|
```ts
|
|
140
77
|
import { unsafeSql, type QueryClient } from '@kequtech/tenon-orm';
|
|
141
78
|
|
|
142
79
|
async function queryRows(tx: QueryClient, text: string, values: readonly unknown[]) {
|
|
143
80
|
const result = await tx.query(unsafeSql(text, values));
|
|
144
|
-
return { rows: result.rows };
|
|
81
|
+
return { rows: [...result.rows] };
|
|
145
82
|
}
|
|
146
83
|
```
|
|
147
84
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
from the callback, retain commit/rollback tests, and make recording helpers return pgclear native
|
|
152
|
-
results. The adapter's SQL text must be trusted and values must remain separately bound.
|
|
153
|
-
|
|
154
|
-
## SQL parameters and identifiers
|
|
85
|
+
The copy supplies a mutable array if the adapter requires one. Values must already be valid driver
|
|
86
|
+
inputs; external arrays/objects need type-aware `param`, `json`, or text encoding. Await adapter
|
|
87
|
+
work inside the callback. Native pool queries use another checkout and do not join it.
|
|
155
88
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Use pgclear `json(value)` for JSON and `param(value, codec)` for arrays or custom encodings.
|
|
161
|
-
Encoded strings also work where PostgreSQL accepts text. Cast standalone parameters when SQL must
|
|
162
|
-
establish their type. For int8, use exact strings, bigint, or `param(text, builtin.int8)`.
|
|
89
|
+
For a job-library transaction adapter such as pg-boss's, verify actual SQL inputs and timestamp
|
|
90
|
+
expectations. Enqueueing and returning an ID may need no output conversion; returned timestamps
|
|
91
|
+
follow the database-wide codecs. Keep atomic commit/rollback tests. Tenon provides no per-query
|
|
92
|
+
codec override, and input encoders are independent of output codecs.
|
|
163
93
|
|
|
164
|
-
|
|
165
|
-
using server inference; they do not select an output decoder. Temporal calendar dates, wall times,
|
|
166
|
-
instants, and durations have different PostgreSQL targets. Use the intended value and SQL cast,
|
|
167
|
-
rather than treating every temporal value as an interchangeable timestamp. Pgclear does not install
|
|
168
|
-
Temporal or a polyfill automatically.
|
|
94
|
+
## SQL parameters and identifiers
|
|
169
95
|
|
|
170
|
-
|
|
171
|
-
|
|
96
|
+
| API | Contract |
|
|
97
|
+
| ------------------------------ | ------------------------------------------------------------------------------ |
|
|
98
|
+
| `sql<Row>` template tag | Bind ordinary values; interpolate Tenon fragments and identifiers structurally |
|
|
99
|
+
| `identifier(...names)` | Quote components separately, e.g. `"public"."users"` |
|
|
100
|
+
| `unsafeSql<Row>(text, values)` | Trusted external SQL with an explicit values array, including `[]` |
|
|
101
|
+
| `json(value, type?)` | Encode JSON; default type is jsonb |
|
|
102
|
+
| `param(value, codec, format?)` | Choose an explicit driver parameter type and encoder |
|
|
172
103
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```
|
|
104
|
+
Do not quote interpolations or mix manual `$n` placeholders into templates. Plain binds accept null,
|
|
105
|
+
string, finite number, bigint, boolean, valid Date, supported Temporal values, and Uint8Array.
|
|
106
|
+
Undefined, plain objects/arrays, nonfinite numbers, and unsafe integer numbers reject.
|
|
177
107
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
108
|
+
Strings, numbers, bigint, Date, and supported Temporal values use server type inference. Date
|
|
109
|
+
encodes in UTC. Cast standalone parameters when PostgreSQL needs type context. Use `json` for JSON
|
|
110
|
+
and `param` for PostgreSQL arrays/custom encodings. Do not mutate bound values before settlement.
|
|
181
111
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
112
|
+
Identifier components require 1–63 UTF-8 bytes and no NUL. Choose approved names; quoting is not
|
|
113
|
+
authorization. SQL syntax such as `DESC` belongs in fixed fragments or query alternatives. Never
|
|
114
|
+
concatenate request input into `unsafeSql` text.
|
|
185
115
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Migrations deliberately support multi-statement scripts.
|
|
116
|
+
A query executes one statement through the extended protocol. Tenon does not expose a prepared
|
|
117
|
+
statement cache or query `name` option. See [writing queries](sql.md) for examples.
|
|
189
118
|
|
|
190
119
|
## SQL fragments and joins
|
|
191
120
|
|
|
192
|
-
`sql.fragment` creates immutable partial SQL with bound values. It has no row type
|
|
193
|
-
|
|
121
|
+
- `sql.fragment` creates immutable partial SQL with bound values. It has no row type and
|
|
122
|
+
cannot execute alone. Blank fragments reject.
|
|
123
|
+
- `sql.join(fragments, 'AND' | 'OR' | ',')` requires a nonempty array of fragments. It does not add
|
|
124
|
+
parentheses or alter precedence.
|
|
125
|
+
- Nested/repeated fragments preserve ordered parameters. Full `sql`/`unsafeSql` queries cannot be
|
|
126
|
+
nested. Literal `$1` text in SQL strings/comments is not rewritten.
|
|
194
127
|
|
|
195
|
-
|
|
196
|
-
`','`. It does not add parentheses or change SQL precedence. Each fragment use receives correctly
|
|
197
|
-
ordered parameters, including repeated and nested uses; literal `$1` text in strings/comments is
|
|
198
|
-
unchanged.
|
|
128
|
+
See [composition examples](sql.md#compose-sql-fragments), including the empty-filter case.
|
|
199
129
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
WHERE ${sql.join(conditions, 'AND')} ORDER BY id`;
|
|
130
|
+
## Object-based mutation fragments
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
sql.insertValues<Input>(rowOrRows, columns?): SqlFragment
|
|
134
|
+
sql.assignments<Input>(patch, columns?): SqlFragment
|
|
206
135
|
```
|
|
207
136
|
|
|
208
|
-
|
|
209
|
-
|
|
137
|
+
These build fragments, not complete statements. Keep table, predicates, expressions, conflict
|
|
138
|
+
behavior, and `RETURNING` in authored SQL.
|
|
210
139
|
|
|
211
|
-
|
|
140
|
+
| Cell | Insert | Assignment |
|
|
141
|
+
| ----------------------------- | --------------- | --------------- |
|
|
142
|
+
| Missing or undefined | `DEFAULT` | Omitted |
|
|
143
|
+
| Null | Bound SQL NULL | Bound SQL NULL |
|
|
144
|
+
| Other accepted value | Bound parameter | Bound parameter |
|
|
145
|
+
| SQL fragment/identifier/query | Rejected | Rejected |
|
|
212
146
|
|
|
213
|
-
|
|
214
|
-
|
|
147
|
+
Inputs must be plain objects, including null-prototype objects. Only own data properties are read;
|
|
148
|
+
selected accessors reject. Helpers capture fields at construction; mutable bound values must remain
|
|
149
|
+
unchanged until execution settles.
|
|
215
150
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await db.query(sql`UPDATE public.users SET ${sql.assignments({ active: false })} WHERE id = ${42}`);
|
|
221
|
-
```
|
|
151
|
+
Without `columns`, inserts infer the union of enumerable keys with defined values in first-seen
|
|
152
|
+
order. Assignments use own enumerable keys and omit undefined values. Explicit columns must be
|
|
153
|
+
nonempty, unique, and valid identifiers; they select/order keys and ignore unlisted properties.
|
|
154
|
+
TypeScript checks selected names against known input keys. Selection can discard required values.
|
|
222
155
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
| Input | Insert | Assignment |
|
|
228
|
-
| -------------------------------------------- | ---------------------- | --------------- |
|
|
229
|
-
| Missing or `undefined` | `DEFAULT` for the cell | Omitted |
|
|
230
|
-
| `null` | Bound SQL null | Bound SQL null |
|
|
231
|
-
| Other value | Bound parameter | Bound parameter |
|
|
232
|
-
| SQL fragment, identifier, or query as a cell | Rejected | Rejected |
|
|
233
|
-
|
|
234
|
-
Inserts accept one object or an array. Without `columns`, they infer the union of enumerable keys
|
|
235
|
-
with defined values in first-seen order. Heterogeneous rows stay aligned. A single default-only row
|
|
236
|
-
emits `DEFAULT VALUES`. Empty batches fail; multiple default-only rows need explicit columns.
|
|
237
|
-
Assignments omit undefined values and fail when no assignments remain, even if surrounding SQL has
|
|
238
|
-
other assignments.
|
|
239
|
-
|
|
240
|
-
A supplied column list must be nonempty, unique, and valid. It selects and orders fields, ignoring
|
|
241
|
-
unlisted keys. Missing insert cells use defaults; missing update fields are omitted. TypeScript
|
|
242
|
-
checks selected names against known input keys, but selection can still discard required fields.
|
|
243
|
-
Helpers have no schema metadata; PostgreSQL enforces identity, generated-column, and constraint
|
|
244
|
-
rules. Use [generated inputs](#insert-and-update-inputs) and application validation.
|
|
245
|
-
|
|
246
|
-
Write arithmetic, `column = DEFAULT`, and `excluded.column` in the surrounding SQL. There is no
|
|
247
|
-
expression/default sentinel. A complete query accepts at most 65,535 bound parameters, including
|
|
248
|
-
surrounding interpolations; Tenon does not split batches. See the [mutation cookbook](mutations.md).
|
|
156
|
+
A single default-only insert emits `DEFAULT VALUES`. Empty batches reject; multiple default-only
|
|
157
|
+
rows need explicit columns. Empty assignments always reject. There is no expression/default
|
|
158
|
+
sentinel; author `column = DEFAULT` or arithmetic in SQL. The complete query's limit is 65,535
|
|
159
|
+
parameters; Tenon does not split batches. See [writing data](mutations.md).
|
|
249
160
|
|
|
250
161
|
## Transactions, guarded writes, and errors
|
|
251
162
|
|
|
252
|
-
For an existing user with id 42:
|
|
253
|
-
|
|
254
163
|
```ts
|
|
255
164
|
await db.transaction(async (tx) => {
|
|
256
165
|
await tx.query(sql`UPDATE public.users SET active = false WHERE id = ${42}`, {
|
|
@@ -259,329 +168,180 @@ await db.transaction(async (tx) => {
|
|
|
259
168
|
});
|
|
260
169
|
```
|
|
261
170
|
|
|
262
|
-
Use `tx` for every
|
|
263
|
-
|
|
264
|
-
database instance.
|
|
265
|
-
BEGIN/COMMIT/ROLLBACK and recovery.
|
|
171
|
+
Use `tx` for every participating query and await all work. The callback commits on success, rolls
|
|
172
|
+
back on an escaping error, and its client expires when the callback finishes. Transactions belong
|
|
173
|
+
to one database instance. Calling the root `db` uses the pool, not the callback session.
|
|
266
174
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
unguarded SQL errors normally abort the transaction.
|
|
175
|
+
Tenon owns BEGIN/COMMIT/ROLLBACK; direct boundary statements are rejected. `tx` provides no nested
|
|
176
|
+
transaction method. Manual savepoints are supported on the callback session. To choose isolation,
|
|
177
|
+
issue `SET TRANSACTION ISOLATION LEVEL SERIALIZABLE` before data queries; use
|
|
178
|
+
`SET LOCAL statement_timeout = '5s'` to bound statements in that transaction.
|
|
272
179
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
the outcome unknown; applications own retry and idempotency policy.
|
|
276
|
-
|
|
277
|
-
Set isolation before data queries with `SET TRANSACTION ISOLATION LEVEL SERIALIZABLE`, and use
|
|
278
|
-
`SET LOCAL statement_timeout = '5s'` to bound statements in that transaction. Manual savepoints are
|
|
279
|
-
supported on the owned callback session; pgclear chooses guarded-operation savepoints.
|
|
280
|
-
|
|
281
|
-
SQL errors retain native driver details. Tenon's option/composition validation throws ordinary
|
|
282
|
-
`Error` objects; driver encoding failures retain `PGCLEAR_*` codes.
|
|
283
|
-
`CardinalityError` has code `TENON_CARDINALITY_MISMATCH`, `expected` (`one` or `oneOrNone`),
|
|
284
|
-
`actual` (returned row count), and optional `label`. It does not change transaction or autocommit
|
|
285
|
-
behavior. `AffectedRowsError` carries `expected`, `actual`, `command`, and code
|
|
286
|
-
`TENON_AFFECTED_ROWS_MISMATCH` or `TENON_AFFECTED_ROWS_UNSUPPORTED`; its `actual` is `number | null`.
|
|
287
|
-
Work/cleanup failures can produce `AggregateError`.
|
|
288
|
-
|
|
289
|
-
Optional `onQuery` observes completed calls with `label`, `durationMs`, `outcome`, and available
|
|
290
|
-
`command`, `rowCount` (`number | null`), and `errorCode`. It receives no SQL or values. Validation
|
|
291
|
-
failures before execution are not observed. Observers are not awaited; failures emit warnings without changing the
|
|
292
|
-
query outcome. Query success inside a callback does not establish outer commit.
|
|
180
|
+
A write guard is exactly one of `{ exact: n }` or `{ max: n }`, with a nonnegative safe integer.
|
|
181
|
+
It checks the final INSERT/UPDATE/DELETE/MERGE count, independently of `RETURNING`:
|
|
293
182
|
|
|
294
|
-
|
|
183
|
+
| Call | Guard behavior |
|
|
184
|
+
| ----------------------------------- | ---------------------------------------------------------------------------- |
|
|
185
|
+
| `db.query(query, { affectedRows })` | Wrap in a transaction; roll back the operation on a guard/result/SQL failure |
|
|
186
|
+
| `tx.query(query, { affectedRows })` | Wrap in a savepoint; roll back that operation before rejecting |
|
|
295
187
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
It needs no migration files/history. The CLI owns atomic output and freshness checks. Decoder
|
|
300
|
-
representations come from pgclear's selected registry rather than a second conversion registry.
|
|
188
|
+
A caught guarded failure permits continued work. An unguarded PostgreSQL error normally leaves the
|
|
189
|
+
transaction aborted; let it escape so the callback rolls back. Tenon serializes whole guarded
|
|
190
|
+
operations within a transaction, including savepoint handling.
|
|
301
191
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
The default namespace is `public`; default output is `src/generated-schema.ts`. Commit exact output
|
|
308
|
-
and exclude it from formatting. `--check` never rewrites: missing/stale output fails with a bounded
|
|
309
|
-
diff. This checks file freshness, not query annotations, pending migrations, or every database object.
|
|
310
|
-
Upgrading pgclear can change output without schema drift. Preserve reviewed generator-specific
|
|
311
|
-
references for existing adoption guards; see [baseline verification](adoption-guide.md#check-and-adopt-an-existing-database).
|
|
312
|
-
|
|
313
|
-
The generated module exports `Database[table]['row' | 'insert' | 'update']`. Ordinary tables and
|
|
314
|
-
partitioned roots are included. Views, foreign tables, and attached partitions need authored types.
|
|
315
|
-
Relationships, indexes, constraints, and referenced domains appear in comments. Use `Pick` for
|
|
316
|
-
projections; joins and expressions need their actual selected shape.
|
|
317
|
-
|
|
318
|
-
| PostgreSQL type | Default read | Generated write input |
|
|
319
|
-
| ---------------------------------------------- | ------------------------------- | ------------------------------------- |
|
|
320
|
-
| smallint, integer, oid, real, double precision | `number` | `number` |
|
|
321
|
-
| bigint | `string` | `string \| bigint` |
|
|
322
|
-
| numeric | `string` | `string \| bigint` |
|
|
323
|
-
| boolean | `boolean` | `boolean` |
|
|
324
|
-
| text, UUID, timetz | `string` | `string` |
|
|
325
|
-
| date, timestamp, timestamptz | `string` | `string \| Date \| TemporalInput` |
|
|
326
|
-
| time, interval | `string` | `string \| TemporalInput` |
|
|
327
|
-
| enum | String literal union | Same union |
|
|
328
|
-
| json/jsonb | `unknown` | Encoded string or pgclear `Parameter` |
|
|
329
|
-
| bytea | `Uint8Array` | `Uint8Array` |
|
|
330
|
-
| Supported built-in arrays | Recursive readonly `PgArray<T>` | Encoded string or pgclear `Parameter` |
|
|
331
|
-
|
|
332
|
-
Every writable generated field also accepts a pgclear `Parameter`; the scalar entries above show
|
|
333
|
-
plain inputs. SQL NULL is separate; nullable fields add null. Numeric and bigint arrays preserve
|
|
334
|
-
exact strings, and temporal strings retain precision and infinity spellings. Dynamic enum/domain/extension
|
|
335
|
-
arrays remain wire text unless a decoder changes them. Generation follows the selected registry.
|
|
336
|
-
Custom text codecs can declare `text.typescript`, and those expressions are preserved in generated
|
|
337
|
-
row types. A custom decoder without a known representation warns and becomes unknown. JSON member
|
|
338
|
-
shapes remain unknown under default decoding. Database constraints and textual formats still
|
|
339
|
-
require application validation. A scalar codec declaration describes every use of that codec's OID;
|
|
340
|
-
it does not establish different application meanings for individual columns.
|
|
341
|
-
|
|
342
|
-
Default-registry enum rows use catalog label unions. With a custom registry, a declared string
|
|
343
|
-
representation stays string because that decoder may transform the labels; an explicit codec
|
|
344
|
-
representation can declare a narrower output type. Enum write inputs retain the database choices.
|
|
345
|
-
|
|
346
|
-
Programmatic generation uses the registry configured on its connection or supplied pool. The CLI
|
|
347
|
-
uses default codecs. Regenerate committed definitions after a driver mapping change before running
|
|
348
|
-
`generate --check`; custom decoder types that remain unknown need application-owned contracts.
|
|
349
|
-
Catalog queries use default result decoders on the same borrowed session and snapshot, while
|
|
350
|
-
application row types follow that session's configured registry. Custom application codecs therefore
|
|
351
|
-
do not change the generator's own metadata-reading types.
|
|
352
|
-
|
|
353
|
-
Codec type expressions must resolve where they are compiled. Relative import expressions in saved
|
|
354
|
-
declarations resolve from the generated file; the checker's in-memory module resolves from the
|
|
355
|
-
tsconfig directory. Prefer a package or configured type alias that resolves in both places when one
|
|
356
|
-
registry is shared across generation and checking. Tenon does not execute the referenced modules.
|
|
357
|
-
|
|
358
|
-
Output conversions are explicit: for example, `defaultCodecs.with(builtin.int8BigInt)` makes int8
|
|
359
|
-
reads bigint, and `defaultCodecs.with(builtin.timestamptzDate)` makes timestamptz reads Date.
|
|
360
|
-
Pgclear also supplies codecs for caller-provided Temporal implementations. Use the same registry for
|
|
361
|
-
execution, programmatic generation, Describe, and query checking. Defaults stay strings regardless
|
|
362
|
-
of which temporal features the runtime provides.
|
|
363
|
-
|
|
364
|
-
### Application aliases
|
|
365
|
-
|
|
366
|
-
For shorter names, export aliases from a handwritten application module such as `src/schema-types.ts`:
|
|
367
|
-
|
|
368
|
-
```ts
|
|
369
|
-
import type { Database } from './generated-schema.js';
|
|
370
|
-
|
|
371
|
-
export type UserRow = Database['users']['row'];
|
|
372
|
-
export type UserInsert = Database['users']['insert'];
|
|
373
|
-
export type UserUpdate = Database['users']['update'];
|
|
374
|
-
```
|
|
192
|
+
Counts exclude extra trigger/cascade and CTE effects and cannot identify the wrong single row.
|
|
193
|
+
Sequence advances and external effects may survive rollback. There are no automatic retries. A lost
|
|
194
|
+
COMMIT acknowledgment can leave the outcome unknown; the application owns retry/idempotency policy.
|
|
375
195
|
|
|
376
|
-
|
|
377
|
-
|
|
196
|
+
| Error | Code and fields |
|
|
197
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| `CardinalityError` | `TENON_CARDINALITY_MISMATCH`; `expected` (`one` or `oneOrNone`), `actual` returned rows, optional `label` |
|
|
199
|
+
| `AffectedRowsError` | `TENON_AFFECTED_ROWS_MISMATCH` or `TENON_AFFECTED_ROWS_UNSUPPORTED`; `expected` constraint, `actual: number \| null`, `command` |
|
|
200
|
+
| Tenon option/composition validation | Ordinary `Error` |
|
|
201
|
+
| Driver/server failure | Native details, including driver `PGCLEAR_*` codes or PostgreSQL SQLSTATE |
|
|
202
|
+
| Work and cleanup both fail | May be an `AggregateError` |
|
|
378
203
|
|
|
379
|
-
|
|
204
|
+
Use codes and structured fields rather than matching message text. Retain unexpected failures.
|
|
380
205
|
|
|
381
|
-
Generated types
|
|
382
|
-
JSON reads remain unknown. Validate values before narrowing to application domain types.
|
|
383
|
-
|
|
384
|
-
For example, assume this additional table and regenerate `Database` from the resulting schema:
|
|
206
|
+
## Generated types and checking
|
|
385
207
|
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
data jsonb NOT NULL,
|
|
390
|
-
expires_at timestamptz NOT NULL
|
|
391
|
-
);
|
|
208
|
+
```sh
|
|
209
|
+
tenon generate
|
|
210
|
+
tenon generate --check
|
|
392
211
|
```
|
|
393
212
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
function sessionData(value: unknown): { userId: string } {
|
|
401
|
-
if (
|
|
402
|
-
typeof value !== 'object' ||
|
|
403
|
-
value === null ||
|
|
404
|
-
Array.isArray(value) ||
|
|
405
|
-
!('userId' in value) ||
|
|
406
|
-
typeof value.userId !== 'string' ||
|
|
407
|
-
value.userId.length === 0
|
|
408
|
-
) {
|
|
409
|
-
throw new Error('Expected session data with a nonempty userId');
|
|
410
|
-
}
|
|
411
|
-
return { userId: value.userId };
|
|
412
|
-
}
|
|
213
|
+
Generation uses [shared configuration](configuration.md) for its connection/codecs, output path,
|
|
214
|
+
and namespace. Missing/stale output rejects under `--check` without writing. See
|
|
215
|
+
[schema types and values](schema-types.md) for file guarantees, generated shapes, and value mappings.
|
|
216
|
+
`generateDefinitions({ connection, namespace?, onWarning? }): Promise<string>` returns source text
|
|
217
|
+
without file handling for custom tooling. Database instances expose query execution and lifecycle
|
|
218
|
+
methods; generation is a CLI tool.
|
|
413
219
|
|
|
414
|
-
|
|
415
|
-
const date = new Date(value);
|
|
416
|
-
if (!Number.isFinite(date.getTime())) {
|
|
417
|
-
throw new Error('Expected a finite database timestamp');
|
|
418
|
-
}
|
|
419
|
-
return date;
|
|
420
|
-
}
|
|
220
|
+
### Application aliases
|
|
421
221
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
data: JSON.stringify(sessionData({ userId: 'user-42' })),
|
|
425
|
-
expires_at: new Date(Date.now() + 60_000).toISOString(),
|
|
426
|
-
} satisfies Database['sessions']['insert'];
|
|
427
|
-
await db.query(sql`INSERT INTO public.sessions ${sql.insertValues(input)}`);
|
|
428
|
-
|
|
429
|
-
const row = await db.query(
|
|
430
|
-
sql<Database['sessions']['row']>`SELECT id, data, expires_at FROM public.sessions
|
|
431
|
-
WHERE id = ${input.id} AND isfinite(expires_at) AND expires_at > now()`,
|
|
432
|
-
{ returns: 'oneOrNone' },
|
|
433
|
-
);
|
|
434
|
-
const session =
|
|
435
|
-
row === undefined
|
|
436
|
-
? undefined
|
|
437
|
-
: {
|
|
438
|
-
id: row.id,
|
|
439
|
-
data: sessionData(row.data),
|
|
440
|
-
expiresAt: finiteDate(row.expires_at),
|
|
441
|
-
};
|
|
442
|
-
```
|
|
222
|
+
Export aliases such as `UserRow = Database['users']['row']` in a handwritten file. See
|
|
223
|
+
[application aliases](schema-types.md#application-aliases).
|
|
443
224
|
|
|
444
|
-
|
|
445
|
-
validates both without assertions or edits to generated types. Preserve null separately for nullable
|
|
446
|
-
columns. For an authentication policy requiring finite expiry, `isfinite(expires_at)` excludes both
|
|
447
|
-
infinities before the expiry comparison; `expires_at > now()` alone would accept positive infinity.
|
|
448
|
-
Include such predicates in expiry-sensitive updates/deletes too, and test infinite and expired
|
|
449
|
-
values. This example illustrates representation validation; applications must also retain their
|
|
450
|
-
token verification, revocation, and authorization checks. See
|
|
451
|
-
[JSON and PostgreSQL arrays](mutations.md#json-and-postgresql-arrays) for array write boundaries.
|
|
225
|
+
### Persistence and domain values
|
|
452
226
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
driver rejects counts outside the safe integer range.
|
|
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).
|
|
456
229
|
|
|
457
230
|
### Insert and update inputs
|
|
458
231
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const input: Database['users']['insert'] = { email: 'reader@example.test' };
|
|
463
|
-
const patch: Pick<Database['users']['update'], 'active'> = { active: false };
|
|
464
|
-
sql.insertValues(input);
|
|
465
|
-
sql.assignments(patch);
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
Insert fields are required when nonnullable, writable, and without a default. Other writable insert
|
|
469
|
-
fields and all writable update fields are optional and accept explicit undefined, including with
|
|
470
|
-
`exactOptionalPropertyTypes`. Explicit null requires both column and domain-chain permission. Domain
|
|
471
|
-
defaults count; column defaults take precedence.
|
|
472
|
-
|
|
473
|
-
Computed and `GENERATED ALWAYS` identity columns use optional `never` fields. Serial and
|
|
474
|
-
`GENERATED BY DEFAULT` identities are optional on insert and writable on update; other primary keys
|
|
475
|
-
remain writable. Use `Pick`/`Omit` for application restrictions. Apply the generated types through
|
|
476
|
-
annotations, `satisfies`, or helper generic arguments; they do not validate constraints, JSON
|
|
477
|
-
structure, authorization, or nonempty input.
|
|
478
|
-
|
|
479
|
-
For custom representations, provide reviewed application types and encoders. Selected columns
|
|
480
|
-
can discard required fields; see [the cookbook](mutations.md#input-types-and-runtime-validation).
|
|
232
|
+
Generated types account for defaults, identity/computed columns, domains, and nullability. See
|
|
233
|
+
[insert/update rules](schema-types.md#insert-and-update-inputs).
|
|
481
234
|
|
|
482
235
|
## Choosing a validation tool
|
|
483
236
|
|
|
484
|
-
|
|
485
|
-
|
|
237
|
+
Use the intended development/test schema and role through shared configuration. TypeScript itself
|
|
238
|
+
needs no connection.
|
|
486
239
|
|
|
487
|
-
| Tool
|
|
488
|
-
|
|
|
489
|
-
| `tsc --noEmit`
|
|
490
|
-
| `tenon check [file.ts[:line]]`
|
|
491
|
-
| `tenon describe query.sql`
|
|
492
|
-
| `tenon generate --
|
|
493
|
-
|
|
|
240
|
+
| Tool | Question answered | Main limit |
|
|
241
|
+
| ------------------------------ | ------------------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
242
|
+
| `tsc --noEmit` | Does application code satisfy TypeScript contracts? | Does not inspect SQL/schema |
|
|
243
|
+
| `tenon check [file.ts[:line]]` | Does recoverable SQL contradict declared input/result types? | Unsupported sites fail; inconclusive properties remain unproven |
|
|
244
|
+
| `tenon describe query.sql` | What metadata does PostgreSQL report for finished SQL? | Does not compare annotations or bind values |
|
|
245
|
+
| `tenon generate --check` | Does committed output match generation now? | Does not check queries or replace migration status |
|
|
246
|
+
| Database tests | Do exercised queries produce correct rows, effects, permissions, and rollback? | Cover only tested inputs and paths |
|
|
494
247
|
|
|
495
|
-
After
|
|
496
|
-
|
|
497
|
-
|
|
248
|
+
After schema changes, migrate the test database, regenerate/review declarations, and run typechecks,
|
|
249
|
+
applicable source checks, and database tests. In CI, check generated output without replacing it.
|
|
250
|
+
Source checking and inspection parse/analyze application SQL without executing it; analysis can
|
|
251
|
+
take locks. Neither proves actual bound values or write effects.
|
|
498
252
|
|
|
499
253
|
## Source-aware checking
|
|
500
254
|
|
|
501
|
-
|
|
502
|
-
TypeScript
|
|
503
|
-
a one-based line selects one complete query. The TypeScript compiler loads on the checking path.
|
|
255
|
+
`checkQueries(options)` is exported from `@kequtech/tenon-orm/check`, alongside its public types.
|
|
256
|
+
The bundled TypeScript compiler loads only through that path or the check command.
|
|
504
257
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
composition, output contracts, explicit expansion shapes and custom decoder representations.
|
|
258
|
+
Options are `{ connection, target?, project?, session?, timeouts?, codecs?, readTypes? }`.
|
|
259
|
+
Connection is a URI or pool-option values; connection codecs are inherited unless `codecs` is explicit. The report contains `ok`, `project`, `queries`, `diagnostics`, and available
|
|
260
|
+
inspection `context`. Mismatches, unchecked sites, and no sites fail; unproven findings alone do not.
|
|
261
|
+
See [query checking](query-checking.md) for selection, supported composition, and custom decoders.
|
|
510
262
|
|
|
511
263
|
## Query inspection
|
|
512
264
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
265
|
+
```text
|
|
266
|
+
describeQuery({ connection, query, session?, timeouts?, codecs? }): Promise<QueryDescription>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Connection is a URI or pool-option values; query is finished SQL text or `{ text, parameterTypes? }`. A `SqlQuery` fits
|
|
270
|
+
because it has `text`; values and its row annotation are not checked. The report includes
|
|
271
|
+
`parameters`, `columns`, `rowType`, `issues`, and `context`. It uses a separate session with bounded
|
|
272
|
+
timeouts. See [query inspection](query-inspection.md) for fields and finding codes.
|
|
517
273
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
274
|
+
## Native driver access
|
|
275
|
+
|
|
276
|
+
`db.native` is the actual pgclear pool. Calls bypass Tenon's guards and observations and do not join
|
|
277
|
+
callback transactions. Use `queryClassic` for text; native `query` accepts only pgclear's own SQL
|
|
278
|
+
tag. See [native driver features](connections.md#use-native-driver-features) for session ownership,
|
|
279
|
+
prepared statements, and dedicated notification listeners.
|
|
522
280
|
|
|
523
281
|
## Troubleshooting
|
|
524
282
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
`
|
|
528
|
-
|
|
529
|
-
|
|
|
530
|
-
|
|
|
531
|
-
| `
|
|
532
|
-
| `
|
|
533
|
-
|
|
|
534
|
-
|
|
|
535
|
-
|
|
|
536
|
-
|
|
|
537
|
-
|
|
|
538
|
-
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
cleanup
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
see [transactions and errors](#transactions-guarded-writes-and-errors) before adding retries.
|
|
283
|
+
| Symptom | Response |
|
|
284
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
285
|
+
| Missing shared configuration | Run `tenon init` or select an existing config with `--config`. Generation, checking, and inspection require it. |
|
|
286
|
+
| `DATABASE_URL is required` | Supply the variable for migration commands without config, or from your shared config module. No `.env` files are loaded automatically. |
|
|
287
|
+
| `PGCLEAR_ENCODE` | Check inputs: objects/arrays need `json`/`param`; direct undefined, unsafe integers, nonfinite numbers, and invalid Dates reject. JSON still rejects bigint. |
|
|
288
|
+
| `CardinalityError` | Check the expected row count and predicate. To choose one of several rows, author deterministic `ORDER BY ... LIMIT 1`. |
|
|
289
|
+
| `TENON_AFFECTED_ROWS_MISMATCH` | The guarded operation rolled back. Inspect `actual` and the predicate; retain the intended bound. |
|
|
290
|
+
| `TENON_AFFECTED_ROWS_UNSUPPORTED` | The final command lacks a supported write count. Use guards on INSERT/UPDATE/DELETE/MERGE; result modes check returned rows. |
|
|
291
|
+
| Empty assignments | All selected fields were missing/undefined. Skip the update or branch to an expression-only statement. |
|
|
292
|
+
| Duplicate result columns | Alias columns uniquely, especially in joins. The driver rejects duplicate object keys. |
|
|
293
|
+
| SQLSTATE `42P18` / `42P08` during inspection | Add the intended parameter cast or Describe OID hints. Inspection binds no values. |
|
|
294
|
+
| Checker reports `unchecked` | Follow the diagnostic. Runtime fragments and optional patches can execute correctly yet remain unsupported by source recovery. |
|
|
295
|
+
| Missing/stale generated output | Check schema, namespace, registry, driver version, and path. Regenerate, review, and commit intended output. |
|
|
296
|
+
| Migration history conflict | Restore original applied files. Add new changes in a forward migration. Checksums include down SQL too. |
|
|
297
|
+
| Tooling lock wait | Check concurrent migration/DDL work, use direct or session pooling, and configure server timeouts. |
|
|
298
|
+
|
|
299
|
+
After a guarded query rejects, savepoint cleanup has finished. An unguarded server error normally
|
|
300
|
+
aborts a transaction. A timeout or lost connection does not itself establish whether a commit
|
|
301
|
+
happened; review the transaction contract before adding retries.
|
|
545
302
|
|
|
546
303
|
## Command reference
|
|
547
304
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
305
|
+
All commands accept `--config path`. `generate`, `check`, and `describe` require
|
|
306
|
+
[shared configuration](configuration.md) and use its connection/codecs. Migration and baseline
|
|
307
|
+
commands use config when present and fall back to `DATABASE_URL` only when no config is found.
|
|
308
|
+
`init` creates config without importing application code; `new` uses the configured migration
|
|
309
|
+
directory without opening a database; `--help` loads no config.
|
|
310
|
+
Config modules may read environment variables, but Tenon loads no `.env` files automatically.
|
|
311
|
+
Configured paths resolve from the config file; CLI path flags resolve from the working directory.
|
|
312
|
+
Unsupported command flags reject.
|
|
552
313
|
|
|
553
314
|
```text
|
|
315
|
+
tenon init [--config path]
|
|
554
316
|
tenon new <name> [--directory migrations]
|
|
317
|
+
tenon generate [--out path] [--namespace schema] [--check]
|
|
555
318
|
tenon migrate [--directory migrations] [--namespace public]
|
|
556
319
|
tenon status [--directory migrations] [--namespace public]
|
|
557
320
|
tenon rollback [--directory migrations] [--namespace public] [--steps 1]
|
|
558
|
-
tenon
|
|
321
|
+
tenon baseline capture [--directory migrations] [--namespace public] [--schema name ...] [--connection native-uri]
|
|
322
|
+
tenon baseline adopt [--directory migrations] [--namespace public]
|
|
559
323
|
tenon check [file.ts[:line]] [--project tsconfig.json] [--json]
|
|
560
324
|
[--role name] [--search-path value] [--time-zone value]
|
|
561
325
|
[--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
|
|
562
326
|
tenon describe <query.sql> [--role name] [--search-path value] [--time-zone value]
|
|
563
327
|
[--connect-timeout-ms 3000] [--statement-timeout-ms 5000] [--lock-timeout-ms 1000]
|
|
564
|
-
tenon baseline capture [--directory migrations] [--namespace public] [--schema name ...]
|
|
565
|
-
tenon baseline adopt [--directory migrations] [--namespace public]
|
|
566
328
|
```
|
|
567
329
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
`
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
Programmatic
|
|
585
|
-
|
|
586
|
-
completed IDs. `createMigration({ name, directory? })` returns `{ id, upPath, downPath }`;
|
|
587
|
-
`readMigrations(directory?)` returns validated SQL and checksums.
|
|
330
|
+
| Command | Output and behavior |
|
|
331
|
+
| ---------------------- | --------------------------------------------------------------------------------------- |
|
|
332
|
+
| `init` | Creates a minimal TypeScript or JavaScript config, refusing to overwrite existing files |
|
|
333
|
+
| `new` | Paths of the new empty up/down pair; fill before applying |
|
|
334
|
+
| `migrate` / `rollback` | Completed IDs or “nothing to do”; each file commits independently |
|
|
335
|
+
| `status` | JSON `{ applied, pending }`; validates history |
|
|
336
|
+
| `baseline capture` | Writes a reviewed schema-only baseline using native `pg_dump` |
|
|
337
|
+
| `baseline adopt` | Records baseline history without executing its SQL |
|
|
338
|
+
| `generate` | Writes output atomically; `--check` rejects missing/stale output without writing |
|
|
339
|
+
| `check` | Diagnostics/counts, or a structured report with `--json` |
|
|
340
|
+
| `describe` | JSON metadata, including unresolved findings |
|
|
341
|
+
|
|
342
|
+
Successful commands exit 0. Invalid input or a failed operation exits 1. `check` also exits 1 for
|
|
343
|
+
mismatches, unchecked sites, or no sites; unproven findings alone do not fail. `describe` can exit 0
|
|
344
|
+
with unresolved findings. There is no migration dry-run command.
|
|
345
|
+
|
|
346
|
+
Programmatic runner APIs are [documented with migrations](migrations.md#programmatic-api).
|
|
347
|
+
Baseline APIs and prerequisites are in [adoption](adoption-guide.md#baseline-api).
|