@kequtech/tenon-orm 0.2.2 → 0.3.0
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 +43 -42
- package/README.md +4 -4
- package/dist/affected-rows.js +6 -6
- package/dist/baseline.js +4 -4
- package/dist/client.js +1 -1
- package/dist/connection.d.ts +15 -22
- package/dist/connection.js +122 -25
- package/dist/database.js +10 -17
- package/dist/driver/types.d.ts +1 -1
- package/dist/driver/types.js +11 -11
- package/dist/generate.js +7 -7
- package/dist/generation/catalog.d.ts +2 -2
- package/dist/generation/catalog.js +4 -4
- package/dist/generation/schema-summary.d.ts +3 -3
- package/dist/generation/schema-summary.js +11 -11
- package/dist/generation/table-types.js +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/inspection/catalog.d.ts +4 -3
- package/dist/inspection/catalog.js +13 -10
- package/dist/inspection/connection.d.ts +2 -2
- package/dist/inspection/connection.js +22 -22
- package/dist/inspection/protocol.d.ts +7 -0
- package/dist/inspection/protocol.js +45 -0
- package/dist/inspection/result.d.ts +1 -1
- package/dist/inspection/result.js +6 -6
- package/dist/inspection/types.d.ts +2 -2
- package/dist/migrations/connection.d.ts +3 -2
- package/dist/migrations/connection.js +18 -20
- package/dist/migrations/history.d.ts +4 -4
- package/dist/migrations/history.js +5 -5
- package/dist/migrations.js +7 -7
- package/dist/query-options.js +4 -4
- package/dist/query.js +2 -2
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.js +19 -14
- package/dist/types.d.ts +2 -2
- package/docs/adoption-guide.md +30 -27
- package/docs/agent-guide.md +53 -48
- package/docs/human-guide.md +21 -20
- package/docs/mutations.md +13 -10
- package/docs/query-inspection.md +12 -14
- package/docs/reference.md +130 -132
- package/package.json +3 -2
- package/dist/driver/array.d.ts +0 -5
- package/dist/driver/array.js +0 -70
- package/dist/driver/lifecycle.d.ts +0 -15
- package/dist/driver/lifecycle.js +0 -57
- package/dist/driver/listener.d.ts +0 -3
- package/dist/driver/listener.js +0 -63
- package/dist/driver/session.d.ts +0 -8
- package/dist/driver/session.js +0 -92
package/docs/reference.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Tenon reference
|
|
2
2
|
|
|
3
|
-
Exact API contracts and limitations. The [human guide](human-guide.md) introduces the workflow;
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Exact API contracts and limitations. The [human guide](human-guide.md) introduces the workflow; the
|
|
4
|
+
[agent guide](agent-guide.md) covers routine implementation. Examples below use the `public.users`
|
|
5
|
+
table from the human guide and an existing `db` instance, unless stated otherwise.
|
|
6
6
|
|
|
7
7
|
## Installation and connection
|
|
8
8
|
|
|
9
9
|
Requires Node.js 24+, PostgreSQL 16+, and ESM. TypeScript consumers need TypeScript 6+ and Node
|
|
10
|
-
development typings. Tenon includes `
|
|
11
|
-
required. A usual Node TypeScript setup uses `module: "NodeNext"`.
|
|
10
|
+
development typings. Tenon includes `pg@8.23.1` and its TypeScript declarations; no driver peer or
|
|
11
|
+
special compiler plugin is required. A usual Node TypeScript setup uses `module: "NodeNext"`.
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
npm install @kequtech/tenon-orm
|
|
@@ -18,71 +18,66 @@ npm install @kequtech/tenon-orm
|
|
|
18
18
|
and `close`. The required `connection` accepts:
|
|
19
19
|
|
|
20
20
|
- A PostgreSQL URI string.
|
|
21
|
-
- `
|
|
22
|
-
-
|
|
23
|
-
- An existing Postgres.js client, borrowed by identity.
|
|
21
|
+
- A pg `PoolConfig` object; an explicit `{}` uses the driver's environment defaults.
|
|
22
|
+
- An existing `pg.Pool`, borrowed by identity.
|
|
24
23
|
|
|
25
24
|
```ts
|
|
26
25
|
const db = createTenon({
|
|
27
26
|
connection: {
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
connectionString: connection,
|
|
28
|
+
connectionTimeoutMillis: 5000,
|
|
29
|
+
options: '-c search_path=app,public',
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
34
|
+
Strings become pg's `connectionString`; configuration objects pass to `new Pool(config)`.
|
|
35
|
+
`ConnectionOptions` is an alias for pg's `PoolConfig`. Driver options and defaults are unchanged,
|
|
36
|
+
including no acquisition timeout unless `connectionTimeoutMillis` is configured. Pool timeouts use
|
|
37
|
+
milliseconds. pg parses the URI for connection fields; matching URI values override client
|
|
38
|
+
configuration, including an `options` URI parameter overriding the configuration's `options` string.
|
|
39
|
+
Preserve deployment credentials and TLS requirements; see
|
|
40
|
+
[pg connection configuration](https://node-postgres.com/features/connecting).
|
|
41
|
+
|
|
42
|
+
Reuse an instance for each database configuration. `db.close({ timeoutMs? })` immediately rejects
|
|
43
|
+
new Tenon work. For owned pools, accepted Tenon operations can drain for up to 5,000 ms by default;
|
|
44
|
+
at the deadline, remaining operations reject and Tenon requests driver termination. `0` skips the
|
|
45
|
+
grace period. The timeout must be an integer from 0 to 2,147,483,647 milliseconds; `CloseOptions` is
|
|
46
|
+
exported. This bounds Tenon's wait, not every native transport's teardown or server execution. It
|
|
47
|
+
sends no PostgreSQL cancellation request and does not cancel JavaScript callbacks. An unbounded
|
|
48
|
+
authentication attempt can keep a connection open after close resolves. Configure pg's
|
|
49
|
+
`connectionTimeoutMillis` when connection establishment also needs a deadline.
|
|
50
|
+
|
|
51
|
+
The first valid close call chooses the timeout. Concurrent and later calls share the same promise
|
|
52
|
+
and outcome. Invalid initial options reject without closing the instance. For borrowed pools, close
|
|
53
|
+
only disables the Tenon wrapper; the application retains the pool and outstanding work. Tenon does
|
|
54
|
+
not load environment files.
|
|
55
|
+
|
|
56
|
+
Qualify SQL names or configure runtime `search_path`. CLI `--namespace` selects tooling scope; it
|
|
57
|
+
does not configure application queries. Baseline capture and Describe accept connection strings
|
|
58
|
+
rather than the runtime connection union.
|
|
57
59
|
|
|
58
60
|
## Native driver access
|
|
59
61
|
|
|
60
|
-
`db.native` is the actual
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
native preparation defaults and do not join a Tenon transaction; callback clients expose only
|
|
64
|
-
`tx.query`.
|
|
62
|
+
`db.native` is the actual `pg.Pool`, readonly in TypeScript. Native calls bypass Tenon's result
|
|
63
|
+
modes, affected-row guards, SQL checks, and `onQuery`. They follow pg's query configuration and do
|
|
64
|
+
not join a Tenon transaction; callback clients expose only `tx.query`.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
Use pg's [native APIs](https://node-postgres.com/apis/pool) and compatible cursor/COPY packages when
|
|
67
|
+
needed. For LISTEN, check out a client with `await db.native.connect()`, attach `notification` and
|
|
68
|
+
`error` listeners, and issue `LISTEN` on that client. Keep it checked out while listening; release
|
|
69
|
+
it after `UNLISTEN`. Native queued acquisitions follow pg's shutdown behavior and must be settled by
|
|
70
|
+
their caller before closing. A disconnected client does not reconnect. The application owns
|
|
71
|
+
resubscription and catch-up; notifications are not replayed.
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
owns catch-up and callback errors. That dedicated client installs its own close handler, so a
|
|
72
|
-
constructor `onclose` does not report its outages. Explicit LISTEN on a dedicated reserved native
|
|
73
|
-
connection can use constructor callbacks when disconnect detection is required. Subscription
|
|
74
|
-
cancellation follows the pinned driver; known reconnect limitations are recorded in
|
|
75
|
-
[maintainer considerations](https://github.com/kequtech/tenon-orm/blob/main/CONSIDERATIONS.md#native-driver-access-and-listen-lifecycle).
|
|
76
|
-
|
|
77
|
-
Close owned clients through `db.close()` to prevent queued LISTEN reconnects from reopening them.
|
|
78
|
-
Direct `db.native.end()` bypasses that protection. Borrowed clients and their listeners remain
|
|
79
|
-
usable after wrapper close until their owner ends them.
|
|
73
|
+
Close owned pools through `db.close()` for bounded shutdown, including checked-out native clients.
|
|
74
|
+
Borrowed pools and their listeners remain usable after wrapper close until their owner ends them.
|
|
80
75
|
|
|
81
76
|
## Queries and result modes
|
|
82
77
|
|
|
83
78
|
`sql<Row>` creates a detached `SqlQuery<Row>` with SQL `text` and ordered `values`; it does not
|
|
84
|
-
execute. `Row` declares one selected row. Without an annotation, fields are `unknown`. An
|
|
85
|
-
|
|
79
|
+
execute. `Row` declares one selected row. Without an annotation, fields are `unknown`. An annotation
|
|
80
|
+
does not verify the SQL or validate returned data.
|
|
86
81
|
|
|
87
82
|
```ts
|
|
88
83
|
const user = await db.query(
|
|
@@ -94,17 +89,17 @@ const user = await db.query(
|
|
|
94
89
|
`query` options are `{ returns?, affectedRows?, label? }`; unknown keys fail. A supplied `label`
|
|
95
90
|
must be a nonempty string.
|
|
96
91
|
|
|
97
|
-
| `returns` | Output
|
|
98
|
-
| ------------------ |
|
|
99
|
-
| `native` (default) | `QueryResult<Row>` driver
|
|
100
|
-
| `many` | Plain `Row[]`
|
|
101
|
-
| `oneOrNone` | `Row \| undefined`
|
|
102
|
-
| `one` | `Row`
|
|
92
|
+
| `returns` | Output | Allowed returned rows |
|
|
93
|
+
| ------------------ | -------------------------------- | --------------------- |
|
|
94
|
+
| `native` (default) | `QueryResult<Row>` driver object | Any |
|
|
95
|
+
| `many` | Plain `Row[]` | Any |
|
|
96
|
+
| `oneOrNone` | `Row \| undefined` | Zero or one |
|
|
97
|
+
| `one` | `Row` | Exactly one |
|
|
103
98
|
|
|
104
|
-
Native
|
|
105
|
-
the command's affected-row count, independent of `RETURNING`; it can be null for
|
|
106
|
-
a count. Result modes never add `LIMIT` or `RETURNING`. A returned-row mismatch
|
|
107
|
-
undo an autocommitted write; use `affectedRows` for write bounds that roll back
|
|
99
|
+
Native results expose `rows`, `rowCount`, `command`, and `fields`, following pg's `QueryResult`.
|
|
100
|
+
`rowCount` is the command's affected-row count, independent of `RETURNING`; it can be null for
|
|
101
|
+
commands without a count. Result modes never add `LIMIT` or `RETURNING`. A returned-row mismatch
|
|
102
|
+
alone does not undo an autocommitted write; use `affectedRows` for write bounds that roll back.
|
|
108
103
|
|
|
109
104
|
Functions accepting `QueryClient` can use either `Database` or `Transaction`. Public types also
|
|
110
105
|
include `QueryOptions`, `QueryReturnMode`, and `QueryOutput<Row, Mode>`.
|
|
@@ -112,9 +107,10 @@ include `QueryOptions`, `QueryReturnMode`, and `QueryOutput<Row, Mode>`.
|
|
|
112
107
|
## SQL parameters and identifiers
|
|
113
108
|
|
|
114
109
|
Ordinary `sql` interpolations bind values. Do not quote them or write manual `$n` placeholders
|
|
115
|
-
inside templates.
|
|
116
|
-
arrays
|
|
117
|
-
|
|
110
|
+
inside templates. Values follow pg serialization: `null` and `undefined` bind as SQL null, arrays
|
|
111
|
+
bind as PostgreSQL arrays, and objects use pg's JSON serialization. For JSON input, use
|
|
112
|
+
`JSON.stringify(value)`, including for JSON arrays, strings, and JSON null. Generated JSON write
|
|
113
|
+
types accept encoded strings. Cast standalone parameters when SQL must establish their type.
|
|
118
114
|
|
|
119
115
|
```ts
|
|
120
116
|
const ids = [1, 2, 3];
|
|
@@ -122,26 +118,27 @@ const query = sql<{ id: number }>`SELECT id FROM public.users
|
|
|
122
118
|
WHERE id = ANY(${ids}::integer[])`;
|
|
123
119
|
```
|
|
124
120
|
|
|
125
|
-
`identifier(schema, table)` quotes components separately. Names must contain 1–63 UTF-8 bytes and
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
`identifier(schema, table)` quotes components separately. Names must contain 1–63 UTF-8 bytes and no
|
|
122
|
+
NUL. Choose application-approved names; quoting does not authorize access. SQL syntax such as `DESC`
|
|
123
|
+
belongs in fixed query alternatives or fragments, not a string parameter.
|
|
128
124
|
|
|
129
125
|
`unsafeSql<Row>(text, values)` accepts trusted external SQL and an explicit values array, including
|
|
130
126
|
`[]`. Never concatenate request input into its text. Full `sql` and `unsafeSql` queries cannot be
|
|
131
127
|
nested; use fragments for partial SQL.
|
|
132
128
|
|
|
133
|
-
Each Tenon query executes one statement through the extended protocol. Application execution
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
Each Tenon query executes one statement through the extended protocol. Application execution uses
|
|
130
|
+
unnamed queries without a `name` or prepared-statement cache. Values remain bound separately.
|
|
131
|
+
Migrations deliberately support multi-statement scripts.
|
|
136
132
|
|
|
137
133
|
## SQL fragments and joins
|
|
138
134
|
|
|
139
135
|
`sql.fragment` creates immutable partial SQL with bound values. It has no row type, cannot execute
|
|
140
136
|
alone, and rejects blank SQL. Insert it into a complete `sql<Row>` query.
|
|
141
137
|
|
|
142
|
-
`sql.join(fragments, separator)` requires a nonempty list. Separators are `'AND'`, `'OR'`, and
|
|
143
|
-
It does not add parentheses or change SQL precedence. Each fragment use receives correctly
|
|
144
|
-
parameters, including repeated and nested uses; literal `$1` text in strings/comments is
|
|
138
|
+
`sql.join(fragments, separator)` requires a nonempty list. Separators are `'AND'`, `'OR'`, and
|
|
139
|
+
`','`. It does not add parentheses or change SQL precedence. Each fragment use receives correctly
|
|
140
|
+
ordered parameters, including repeated and nested uses; literal `$1` text in strings/comments is
|
|
141
|
+
unchanged.
|
|
145
142
|
|
|
146
143
|
```ts
|
|
147
144
|
const email: string | undefined = 'reader@example.test';
|
|
@@ -178,10 +175,10 @@ fragment construction; nested objects retain ordinary driver serialization behav
|
|
|
178
175
|
| SQL fragment, identifier, or query as a cell | Rejected | Rejected |
|
|
179
176
|
|
|
180
177
|
Inserts accept one object or an array. Without `columns`, they infer the union of enumerable keys
|
|
181
|
-
with defined values in first-seen order. Heterogeneous rows stay aligned. A single default-only
|
|
182
|
-
|
|
183
|
-
Assignments omit undefined values and fail when no assignments remain, even if surrounding SQL
|
|
184
|
-
|
|
178
|
+
with defined values in first-seen order. Heterogeneous rows stay aligned. A single default-only row
|
|
179
|
+
emits `DEFAULT VALUES`. Empty batches fail; multiple default-only rows need explicit columns.
|
|
180
|
+
Assignments omit undefined values and fail when no assignments remain, even if surrounding SQL has
|
|
181
|
+
other assignments.
|
|
185
182
|
|
|
186
183
|
A supplied column list must be nonempty, unique, and valid. It selects and orders fields, ignoring
|
|
187
184
|
unlisted keys. Missing insert cells use defaults; missing update fields are omitted. TypeScript
|
|
@@ -190,7 +187,7 @@ Helpers have no schema metadata; PostgreSQL enforces identity, generated-column,
|
|
|
190
187
|
rules. Use [generated inputs](#insert-and-update-inputs) and application validation.
|
|
191
188
|
|
|
192
189
|
Write arithmetic, `column = DEFAULT`, and `excluded.column` in the surrounding SQL. There is no
|
|
193
|
-
expression/default sentinel. A complete query accepts at most 65,
|
|
190
|
+
expression/default sentinel. A complete query accepts at most 65,535 bound parameters, including
|
|
194
191
|
surrounding interpolations; Tenon does not split batches. See the [mutation cookbook](mutations.md).
|
|
195
192
|
|
|
196
193
|
## Transactions, guarded writes, and errors
|
|
@@ -221,8 +218,8 @@ Sequence advances and external effects are not reversible. A lost commit acknowl
|
|
|
221
218
|
the outcome unknown; applications own retry and idempotency policy.
|
|
222
219
|
|
|
223
220
|
Set isolation before data queries with `SET TRANSACTION ISOLATION LEVEL SERIALIZABLE`, and use
|
|
224
|
-
`SET LOCAL statement_timeout = '5s'` to bound statements in that transaction. Manual savepoints are
|
|
225
|
-
reserve names beginning with `tenon_affected_rows_` for Tenon.
|
|
221
|
+
`SET LOCAL statement_timeout = '5s'` to bound statements in that transaction. Manual savepoints are
|
|
222
|
+
supported; reserve names beginning with `tenon_affected_rows_` for Tenon.
|
|
226
223
|
|
|
227
224
|
SQL errors retain native driver details. Invalid inputs throw ordinary `Error` objects.
|
|
228
225
|
`CardinalityError` has code `TENON_CARDINALITY_MISMATCH`, `expected` (`one` or `oneOrNone`),
|
|
@@ -233,59 +230,59 @@ produce `AggregateError`.
|
|
|
233
230
|
|
|
234
231
|
Optional `onQuery` observes completed calls with `label`, `durationMs`, `outcome`, and available
|
|
235
232
|
`command`, `rowCount`, and `errorCode`. It receives no SQL or values. Validation failures before
|
|
236
|
-
execution are not observed. Observers are not awaited; failures emit warnings without changing
|
|
237
|
-
|
|
233
|
+
execution are not observed. Observers are not awaited; failures emit warnings without changing the
|
|
234
|
+
query outcome. Query success inside a callback does not establish outer commit.
|
|
238
235
|
|
|
239
236
|
## Generated types and checking
|
|
240
237
|
|
|
241
|
-
`generateDefinitions({ connection, namespace?, onWarning? })` returns TypeScript declarations
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
`generateDefinitions({ connection, namespace?, onWarning? })` returns TypeScript declarations from a
|
|
239
|
+
consistent read-only snapshot; it does not need migration files or history. The CLI writes them
|
|
240
|
+
atomically. Default namespace is `public`; default output is `src/generated-schema.ts`. Both `.ts`
|
|
241
|
+
and `.d.ts` outputs are supported. Commit exact output and exclude it from formatting.
|
|
245
242
|
|
|
246
243
|
```sh
|
|
247
244
|
npx tenon generate --out src/generated-schema.d.ts
|
|
248
245
|
npx tenon generate --out src/generated-schema.d.ts --check
|
|
249
246
|
```
|
|
250
247
|
|
|
251
|
-
`--check` never rewrites the file. Missing/stale output fails with a diagnostic and a short diff
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
`--check` never rewrites the file. Missing/stale output fails with a diagnostic and a short diff for
|
|
249
|
+
changes. It compares the generated representation, not pending migrations or every database object.
|
|
250
|
+
Output includes relation/constraint/index summaries and referenced domains, not function bodies,
|
|
251
|
+
grants, or a complete schema fingerprint.
|
|
255
252
|
|
|
256
253
|
Generation covers ordinary tables and partitioned roots. Views, foreign tables, and attached
|
|
257
254
|
partitions need query-specific types. A missing namespace fails; an existing empty schema is valid.
|
|
258
255
|
Exact table names are available through `Rows['users']`; valid nonreserved identifiers also receive
|
|
259
256
|
named aliases. Use `Pick` for projections; a join or aggregate needs its actual selected shape.
|
|
260
257
|
|
|
261
|
-
| PostgreSQL type | Read representation | Generated write input
|
|
262
|
-
| ---------------------------------------------- | -------------------- |
|
|
263
|
-
| smallint, integer, oid, real, double precision | `number` | `number`
|
|
264
|
-
| bigint | `string` | `string \| bigint`
|
|
265
|
-
| numeric | `string` | `string \| number`
|
|
266
|
-
| boolean | `boolean` | `boolean`
|
|
267
|
-
| text, name, varchar, char, uuid, time, timetz | `string` | `string`
|
|
268
|
-
| date, timestamp, timestamptz | `Date`
|
|
269
|
-
| enum | String literal union | Same union; `never` for an empty enum
|
|
270
|
-
| json, jsonb | `unknown` | `
|
|
271
|
-
| bytea | `Buffer` | `Buffer`
|
|
272
|
-
|
|
273
|
-
Nullable columns add null; `unknown` already includes it. Domains retain verified base mappings
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
258
|
+
| PostgreSQL type | Read representation | Generated write input |
|
|
259
|
+
| ---------------------------------------------- | -------------------- | ------------------------------------- |
|
|
260
|
+
| smallint, integer, oid, real, double precision | `number` | `number` |
|
|
261
|
+
| bigint | `string` | `string \| bigint` |
|
|
262
|
+
| numeric | `string` | `string \| number` |
|
|
263
|
+
| boolean | `boolean` | `boolean` |
|
|
264
|
+
| text, name, varchar, char, uuid, time, timetz | `string` | `string` |
|
|
265
|
+
| date, timestamp, timestamptz | `Date \| number` | `Date \| string` |
|
|
266
|
+
| enum | String literal union | Same union; `never` for an empty enum |
|
|
267
|
+
| json, jsonb | `unknown` | `string` containing encoded JSON |
|
|
268
|
+
| bytea | `Buffer` | `Buffer` |
|
|
269
|
+
|
|
270
|
+
Nullable columns add null; `unknown` already includes it. Domains retain verified base mappings and
|
|
271
|
+
the column's read nullability. Verified arrays use `PgArray<T> = (T | null | PgArray<T>)[]` for null
|
|
272
|
+
elements and multiple dimensions. Numeric arrays contain numbers and can lose precision; select/cast
|
|
273
|
+
them as text when exact values are required. Bigint arrays contain strings; enum arrays and arrays
|
|
274
|
+
of domains are unverified. Unmapped types warn and use `unknown` reads / `never` non-null writes.
|
|
277
275
|
Custom parsers need matching application types.
|
|
278
276
|
|
|
279
|
-
Date-only
|
|
280
|
-
|
|
281
|
-
|
|
277
|
+
Date-only values and timestamps without time zone use Node's local timezone. Infinite temporal reads
|
|
278
|
+
are numbers (`Infinity` or `-Infinity`); narrow with `value instanceof Date` before date methods.
|
|
279
|
+
Temporal write inputs accept `Date | string`; use PostgreSQL's text `infinity` or `-infinity` when
|
|
280
|
+
needed.
|
|
282
281
|
|
|
283
282
|
### Persistence and domain values
|
|
284
283
|
|
|
285
|
-
Generated types describe storage representations. Text `CHECK` constraints do not become unions,
|
|
286
|
-
|
|
287
|
-
payloads into validated native JSON-compatible objects rather than asserting compatibility or
|
|
288
|
-
pre-serializing them. A bound null means SQL null; use `'null'::jsonb` for JSON null.
|
|
284
|
+
Generated types describe storage representations. Text `CHECK` constraints do not become unions, and
|
|
285
|
+
JSON reads remain unknown. Validate values before narrowing to application domain types.
|
|
289
286
|
|
|
290
287
|
Bigint and `count(*)` reads are decimal strings. Use SQL or BigInt for exact arithmetic and decimal
|
|
291
288
|
strings at JSON boundaries; convert to number only when bounded or approximate values are intended.
|
|
@@ -303,14 +300,14 @@ sql.assignments(patch);
|
|
|
303
300
|
|
|
304
301
|
Insert fields are required when nonnullable, writable, and without a default. Other writable insert
|
|
305
302
|
fields and all writable update fields are optional and accept explicit undefined, including with
|
|
306
|
-
`exactOptionalPropertyTypes`. Explicit null requires both column and domain-chain permission.
|
|
307
|
-
|
|
303
|
+
`exactOptionalPropertyTypes`. Explicit null requires both column and domain-chain permission. Domain
|
|
304
|
+
defaults count; column defaults take precedence.
|
|
308
305
|
|
|
309
306
|
Computed and `GENERATED ALWAYS` identity columns use optional `never` fields. Serial and
|
|
310
|
-
`GENERATED BY DEFAULT` identities are optional on insert and writable on update; other primary
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
`GENERATED BY DEFAULT` identities are optional on insert and writable on update; other primary keys
|
|
308
|
+
remain writable. Use `Pick`/`Omit` for application restrictions. Apply the generated types through
|
|
309
|
+
annotations, `satisfies`, or helper generic arguments; they do not validate constraints, JSON
|
|
310
|
+
structure, authorization, or nonempty input.
|
|
314
311
|
|
|
315
312
|
For unverified write mappings, provide a reviewed application-owned replacement, for example
|
|
316
313
|
`Omit<Rows.Insert['events'], 'delay'> & { delay: string }` for interval text input. Selected columns
|
|
@@ -319,19 +316,20 @@ can discard required fields; see [the cookbook](mutations.md#input-types-and-run
|
|
|
319
316
|
## Query inspection
|
|
320
317
|
|
|
321
318
|
`describeQuery({ connection, query, session?, timeouts? })` accepts a PostgreSQL URI and a finished
|
|
322
|
-
SQL string or `{ text }` object. It returns PostgreSQL metadata, conservative read mappings and
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
SQL string or `{ text }` object. It returns PostgreSQL metadata, conservative read mappings and row
|
|
320
|
+
declaration, session context, and coded findings. It does not bind values, execute the statement, or
|
|
321
|
+
verify `sql<Row>`.
|
|
325
322
|
|
|
326
323
|
Inspection owns a separate session with bounded timeouts. Match runtime settings; temporary tables,
|
|
327
|
-
uncommitted changes
|
|
324
|
+
and uncommitted changes are not inherited. Reported mappings assume pg's default parsers; parser
|
|
325
|
+
overrides need matching application types. Nullability stays unknown. See
|
|
328
326
|
[query inspection](query-inspection.md) for settings, result fields, issue codes, and testing.
|
|
329
327
|
|
|
330
328
|
## Command reference
|
|
331
329
|
|
|
332
|
-
Database commands require `DATABASE_URL`. `new` and `--help` work offline. No environment files
|
|
333
|
-
|
|
334
|
-
|
|
330
|
+
Database commands require `DATABASE_URL`. `new` and `--help` work offline. No environment files are
|
|
331
|
+
loaded. Paths are relative to the working directory. Explicit flags unsupported by a command are
|
|
332
|
+
rejected; `--check` is only a generation check, not a migration dry run.
|
|
335
333
|
|
|
336
334
|
```text
|
|
337
335
|
tenon new <name> [--directory migrations]
|
|
@@ -356,10 +354,10 @@ file has down SQL before executing any; `steps` is a positive safe integer. Use
|
|
|
356
354
|
function bodies and explicit `E'...'` backslash escapes. Nontransactional DDL such as
|
|
357
355
|
`CREATE INDEX CONCURRENTLY` requires a separate procedure.
|
|
358
356
|
|
|
359
|
-
Migration/status/rollback/adoption use session advisory locks per database and namespace;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
357
|
+
Migration/status/rollback/adoption use session advisory locks per database and namespace; generation
|
|
358
|
+
uses the same lock in shared mode. Use direct or session-pooled connections and server timeouts to
|
|
359
|
+
bound waits. Baseline capture uses a local creation lock and native `pg_dump`; it does not acquire
|
|
360
|
+
the runner's database advisory lock. See [adoption](adoption-guide.md).
|
|
363
361
|
|
|
364
362
|
Programmatic `migrate`, `migrationStatus`, and `rollback` accept
|
|
365
363
|
`{ connection, directory?, namespace? }`; rollback also accepts `steps`. Migration/rollback return
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kequtech/tenon-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A PostgreSQL ORM designed for coding agents, with explicit SQL, typed queries, and migrations",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"benchmark": "npm run build && node scripts/benchmark.mjs"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"
|
|
57
|
+
"pg": "8.23.1",
|
|
58
|
+
"@types/pg": "8.23.1"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@types/node": "^24.19.2",
|
package/dist/driver/array.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type postgres from 'postgres';
|
|
2
|
-
/** Preserve driver scalar parsers and serializers; replace only lossy array parsing. */
|
|
3
|
-
export declare function prepareArrays(sql: postgres.Sql): Promise<void>;
|
|
4
|
-
/** PostgreSQL text arrays: only unquoted NULL is null; quoted values use the scalar parser. */
|
|
5
|
-
export declare function parseArray(text: string, parse: (value: string) => unknown, delimiter?: string): unknown[];
|
package/dist/driver/array.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const initialization = new WeakMap();
|
|
2
|
-
/** Preserve driver scalar parsers and serializers; replace only lossy array parsing. */
|
|
3
|
-
export function prepareArrays(sql) {
|
|
4
|
-
const existing = initialization.get(sql);
|
|
5
|
-
if (existing)
|
|
6
|
-
return existing;
|
|
7
|
-
const ready = (async () => {
|
|
8
|
-
const customArrays = new Set(Object.values(sql.options.types).flatMap((type) => type.from));
|
|
9
|
-
const arrays = await sql `
|
|
10
|
-
SELECT t.typarray AS oid, t.oid AS element, t.typdelim AS delimiter
|
|
11
|
-
FROM pg_catalog.pg_type t WHERE t.typarray <> 0`;
|
|
12
|
-
for (const type of arrays) {
|
|
13
|
-
if (customArrays.has(type.oid))
|
|
14
|
-
continue;
|
|
15
|
-
const scalar = sql.options.parsers[type.element] ?? ((value) => value);
|
|
16
|
-
sql.options.parsers[type.oid] = (value) => parseArray(value, scalar, type.delimiter);
|
|
17
|
-
}
|
|
18
|
-
})();
|
|
19
|
-
initialization.set(sql, ready);
|
|
20
|
-
void ready.catch(() => initialization.delete(sql));
|
|
21
|
-
return ready;
|
|
22
|
-
}
|
|
23
|
-
/** PostgreSQL text arrays: only unquoted NULL is null; quoted values use the scalar parser. */
|
|
24
|
-
export function parseArray(text, parse, delimiter = ',') {
|
|
25
|
-
let position = text.startsWith('[') ? text.indexOf('=') + 1 : 0;
|
|
26
|
-
function array() {
|
|
27
|
-
if (text[position++] !== '{')
|
|
28
|
-
throw new Error('Invalid PostgreSQL array.');
|
|
29
|
-
const values = [];
|
|
30
|
-
while (position < text.length && text[position] !== '}') {
|
|
31
|
-
if (text[position] === '{')
|
|
32
|
-
values.push(array());
|
|
33
|
-
else {
|
|
34
|
-
let value = '';
|
|
35
|
-
const quoted = text[position] === '"';
|
|
36
|
-
if (quoted)
|
|
37
|
-
position++;
|
|
38
|
-
while (position < text.length) {
|
|
39
|
-
const char = text[position];
|
|
40
|
-
if (char === '\\') {
|
|
41
|
-
position++;
|
|
42
|
-
value += text[position++] ?? '';
|
|
43
|
-
}
|
|
44
|
-
else if (quoted && char === '"') {
|
|
45
|
-
position++;
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
else if (!quoted && (char === delimiter || char === '}'))
|
|
49
|
-
break;
|
|
50
|
-
else {
|
|
51
|
-
value += char;
|
|
52
|
-
position++;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
values.push(!quoted && value === 'NULL' ? null : parse(value));
|
|
56
|
-
}
|
|
57
|
-
if (text[position] === delimiter)
|
|
58
|
-
position++;
|
|
59
|
-
else if (text[position] !== '}')
|
|
60
|
-
throw new Error('Invalid PostgreSQL array separator.');
|
|
61
|
-
}
|
|
62
|
-
if (text[position++] !== '}')
|
|
63
|
-
throw new Error('Unterminated PostgreSQL array.');
|
|
64
|
-
return values;
|
|
65
|
-
}
|
|
66
|
-
const result = array();
|
|
67
|
-
if (position !== text.length)
|
|
68
|
-
throw new Error('Unexpected PostgreSQL array suffix.');
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type postgres from 'postgres';
|
|
2
|
-
export interface Watch {
|
|
3
|
-
failure?: Error;
|
|
4
|
-
check(): void;
|
|
5
|
-
closed: Promise<never>;
|
|
6
|
-
reject(error: Error): void;
|
|
7
|
-
}
|
|
8
|
-
interface State {
|
|
9
|
-
acquiring: Set<(error: Error) => void>;
|
|
10
|
-
active: Map<number, Watch>;
|
|
11
|
-
capture(marker: string, watch: Watch): () => void;
|
|
12
|
-
}
|
|
13
|
-
/** Observe the driver's public connection callbacks; never reuse a reconnected transaction. */
|
|
14
|
-
export declare function observeConnections(sql: postgres.Sql): State;
|
|
15
|
-
export {};
|
package/dist/driver/lifecycle.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const states = new WeakMap();
|
|
2
|
-
function disconnected() {
|
|
3
|
-
return Object.assign(new Error('The reserved PostgreSQL connection closed.'), {
|
|
4
|
-
code: 'CONNECTION_CLOSED',
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
/** Observe the driver's public connection callbacks; never reuse a reconnected transaction. */
|
|
8
|
-
export function observeConnections(sql) {
|
|
9
|
-
const previous = states.get(sql);
|
|
10
|
-
if (previous)
|
|
11
|
-
return previous;
|
|
12
|
-
const markers = new Map();
|
|
13
|
-
let debug = sql.options.debug;
|
|
14
|
-
// Capture the bootstrap connection ID, then restore debug before application work.
|
|
15
|
-
// Leaving it enabled would make the driver include SQL and values in enumerable errors.
|
|
16
|
-
const captureDebug = (id, text, values, types) => {
|
|
17
|
-
const watch = markers.get(text);
|
|
18
|
-
if (watch) {
|
|
19
|
-
state.active.set(id, watch);
|
|
20
|
-
markers.delete(text);
|
|
21
|
-
}
|
|
22
|
-
if (!markers.size)
|
|
23
|
-
sql.options.debug = debug;
|
|
24
|
-
if (typeof debug === 'function')
|
|
25
|
-
debug(id, text, values, types);
|
|
26
|
-
};
|
|
27
|
-
const state = {
|
|
28
|
-
acquiring: new Set(),
|
|
29
|
-
active: new Map(),
|
|
30
|
-
capture(marker, watch) {
|
|
31
|
-
if (!markers.size) {
|
|
32
|
-
debug = sql.options.debug;
|
|
33
|
-
sql.options.debug = captureDebug;
|
|
34
|
-
}
|
|
35
|
-
markers.set(marker, watch);
|
|
36
|
-
return () => {
|
|
37
|
-
markers.delete(marker);
|
|
38
|
-
if (!markers.size)
|
|
39
|
-
sql.options.debug = debug;
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
const onclose = sql.options.onclose;
|
|
44
|
-
sql.options.onclose = (id) => {
|
|
45
|
-
const error = disconnected();
|
|
46
|
-
const watch = state.active.get(id);
|
|
47
|
-
if (watch) {
|
|
48
|
-
watch.failure ??= error;
|
|
49
|
-
watch.reject(watch.failure);
|
|
50
|
-
}
|
|
51
|
-
for (const reject of state.acquiring)
|
|
52
|
-
reject(error);
|
|
53
|
-
onclose?.(id);
|
|
54
|
-
};
|
|
55
|
-
states.set(sql, state);
|
|
56
|
-
return state;
|
|
57
|
-
}
|