@kequtech/tenon-orm 0.4.0 → 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 +17 -2
- package/README.md +84 -51
- package/dist/cli.js +6 -22
- package/dist/database.js +5 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.js +26 -1
- package/dist/generation/output.js +2 -4
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/docs/adoption-guide.md +88 -153
- package/docs/agent-guide.md +91 -172
- package/docs/connections.md +185 -0
- package/docs/human-guide.md +132 -176
- package/docs/migrations.md +133 -0
- package/docs/mutations.md +104 -67
- package/docs/query-checking.md +119 -120
- package/docs/query-inspection.md +78 -74
- package/docs/reference.md +222 -474
- package/docs/schema-types.md +263 -0
- package/docs/sql.md +212 -0
- package/package.json +1 -1
package/docs/adoption-guide.md
CHANGED
|
@@ -1,114 +1,47 @@
|
|
|
1
1
|
# Adopting Tenon
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Upgrade from Tenon 0.3.x
|
|
8
|
-
|
|
9
|
-
Tenon 0.4 uses pgclear 0.3.x in place of pg. Keep the existing database, credentials,
|
|
10
|
-
migration history, and application query functions. Pgclear is included as a runtime dependency.
|
|
11
|
-
With npm, update Tenon:
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
npm install @kequtech/tenon-orm@^0.4.0
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Use the project's existing package manager and then review these boundaries:
|
|
18
|
-
|
|
19
|
-
1. Translate pg `PoolConfig` fields to pgclear `PoolOptions`, or replace a borrowed pg Pool with a
|
|
20
|
-
pgclear Pool. A URI remains supported. Supply connection credentials explicitly; the driver does
|
|
21
|
-
not read PG environment variables, pgpass, service files, or pg-specific URI options.
|
|
22
|
-
2. Keep Tenon `sql`, `QueryClient`, result modes, and callback transactions. Update native calls to
|
|
23
|
-
pgclear APIs: use `queryClassic(text, values?)` for finished SQL, release acquired sessions, and
|
|
24
|
-
close dedicated listeners separately. Import `sql`, `json`, `param`, and codec helpers from
|
|
25
|
-
`@kequtech/tenon-orm`. If native driver code also uses Pgclear's tag, alias it as `pgSql`; its query
|
|
26
|
-
objects belong in native `query`, while Tenon's query objects belong in `db.query`.
|
|
27
|
-
3. Review temporal strings, readonly result arrays, Uint8Array bytes, exact string integer/numeric
|
|
28
|
-
arrays, and JSON/array inputs. Use `json(value)`, `param(value, codec)`, or encoded text for JSON
|
|
29
|
-
and arrays. Plain Date, bigint, and supported Temporal inputs are accepted with server inference;
|
|
30
|
-
Date encodes in UTC. They do not change output types. Command rowCount remains a number or null.
|
|
31
|
-
See [value mappings](reference.md#generated-types-and-checking).
|
|
32
|
-
4. Regenerate database declarations against the intended development/test schema. Replace former
|
|
33
|
-
`Rows['users']`, `Rows.Insert['users']`, and `Rows.Update['users']` accesses with
|
|
34
|
-
`Database['users']['row']`, `Database['users']['insert']`, and `Database['users']['update']`.
|
|
35
|
-
Tenon owns generation and uses the configured driver's codec representations. If code imports
|
|
36
|
-
`generateDefinitions` from pgclear, import it from Tenon and use Tenon's options instead. Review
|
|
37
|
-
the generated diff and exclude exact generator output from formatting. Generated helper types
|
|
38
|
-
now import from Tenon; applications do not need a separate Pgclear dependency for them.
|
|
39
|
-
5. Run the project's typecheck and database tests for queries, transactions, JSON/arrays, and native
|
|
40
|
-
integrations. Add optional source checking where composition is recoverable; optional patches
|
|
41
|
-
and runtime-dependent SQL may remain unchecked. See [validation tools](reference.md#choosing-a-validation-tool).
|
|
42
|
-
|
|
43
|
-
If other code still uses pg directly, retain it until those callers are migrated. Updating Tenon
|
|
44
|
-
does not require transferring migration ownership or adopting a new baseline.
|
|
3
|
+
You can replace application queries without changing migration ownership. Tenon queries and type
|
|
4
|
+
generation work with an existing schema and any migration runner. For a Tenon upgrade, read the
|
|
5
|
+
[shipped changelog](../CHANGELOG.md) from your resolved version through the target version.
|
|
45
6
|
|
|
46
7
|
## Preserve connections and behavior
|
|
47
8
|
|
|
48
|
-
|
|
49
|
-
|
|
9
|
+
Reuse the existing database URI, credentials, schema, TLS requirements, and data. Pool configuration
|
|
10
|
+
uses pgclear options; timeout names include `Ms`. Configure runtime names with
|
|
11
|
+
`startupParameters: { search_path: 'app,public' }`, or qualify tables in SQL. Tooling `namespace`
|
|
12
|
+
does not configure runtime queries.
|
|
50
13
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const db = createTenon({
|
|
55
|
-
connection: {
|
|
56
|
-
connectionString: existingUri,
|
|
57
|
-
connectTimeoutMs: 5000,
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Translate old driver configuration into pgclear `PoolOptions`. Pool timeouts use milliseconds.
|
|
63
|
-
Set `startupParameters: { search_path: 'app,public' }` for runtime names. pgclear does not read
|
|
64
|
-
PG environment variables, pgpass, or service files; supply connection settings explicitly. Preserve
|
|
65
|
-
credentials and verified TLS requirements. CLI `--namespace` does not change runtime queries. See
|
|
66
|
-
[connection options](reference.md#installation-and-connection).
|
|
67
|
-
|
|
68
|
-
Before conversion, identify behavior supplied by the old ORM: IDs/defaults, timestamps, validation,
|
|
69
|
-
hooks, serialization, relationships, and transaction boundaries. Preserve it explicitly in
|
|
70
|
-
application code or SQL where required.
|
|
14
|
+
Before replacing an ORM, identify behavior it currently supplies: IDs/defaults, timestamps,
|
|
15
|
+
validation, hooks, serialization, relationship loading, and transaction boundaries. Preserve needed
|
|
16
|
+
behavior in application code, constraints, or SQL. Tenon has no model lifecycle or implicit loading.
|
|
71
17
|
|
|
72
18
|
## Replace queries
|
|
73
19
|
|
|
74
|
-
1. Optionally generate persistence types
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
retained by other application code or installed transitively may still be needed.
|
|
94
|
-
|
|
95
|
-
Check cardinality when replacing a first-row accessor. `oneOrNone` rejects multiple rows and `one`
|
|
96
|
-
also rejects zero rows. Use deterministic `ORDER BY` plus `LIMIT 1` when choosing one of several
|
|
97
|
-
matches is intended; modes do not add a limit.
|
|
98
|
-
|
|
99
|
-
Use `sql.insertValues` and `sql.assignments` to replace object-to-SQL utilities, checking omission,
|
|
100
|
-
default, null, and empty-input semantics. Generated `Database[table]['insert']`/`Database[table]['update']` types can replace
|
|
101
|
-
handwritten persistence inputs. See the [mutation cookbook](mutations.md).
|
|
102
|
-
|
|
103
|
-
Use `tx.query` for work that must commit together. Native pool calls do not join Tenon transactions.
|
|
104
|
-
For driver features, preserve resource ownership and recovery policy under the
|
|
105
|
-
[native access contract](reference.md#native-driver-access).
|
|
20
|
+
1. Optionally generate persistence types with
|
|
21
|
+
`await db.generateTypes({ out: 'src/generated-schema.d.ts', namespace: 'public' })`.
|
|
22
|
+
Use the application's schema and codec registry; no Tenon migration history is required.
|
|
23
|
+
2. Replace one workflow at a time with parameterized SQL in ordinary functions. Accept
|
|
24
|
+
`QueryClient` when callers may use either `db` or `tx`. Preserve selected columns, ordering,
|
|
25
|
+
null handling, authorization predicates, and transaction boundaries.
|
|
26
|
+
3. Review input/output mappings and validate domain values at the persistence boundary. Generated
|
|
27
|
+
types do not validate JSON members or application constraints.
|
|
28
|
+
4. Test actual rows, writes, permissions, and rollback. Use source checking and inspection where
|
|
29
|
+
useful. See [choosing a validation tool](reference.md#choosing-a-validation-tool).
|
|
30
|
+
5. Remove unused dependencies/generated code once all their callers are gone.
|
|
31
|
+
|
|
32
|
+
When replacing a first-row accessor, decide whether multiple matches should be an error.
|
|
33
|
+
`oneOrNone` rejects multiple rows and `one` also rejects absence. To choose one match deliberately,
|
|
34
|
+
author deterministic `ORDER BY ... LIMIT 1`; result modes do not add a limit.
|
|
35
|
+
|
|
36
|
+
For object-to-SQL utilities, review omission, defaults, null, and empty patches against
|
|
37
|
+
[the write guide](mutations.md). Use `tx.query` for every operation that must commit together;
|
|
38
|
+
native pool calls do not join Tenon transactions.
|
|
106
39
|
|
|
107
40
|
## Transfer migration ownership
|
|
108
41
|
|
|
109
|
-
Choose
|
|
110
|
-
runner before handover; the runners do not share locks. Capture from a trusted
|
|
111
|
-
preferably a disposable replay of the
|
|
42
|
+
Choose the old runner's final schema checkpoint. Bring every existing database to it and disable
|
|
43
|
+
that runner before handover; the runners do not share locks. Capture one baseline from a trusted
|
|
44
|
+
reference database, preferably a disposable replay of the old migrations.
|
|
112
45
|
|
|
113
46
|
### Capture a baseline
|
|
114
47
|
|
|
@@ -119,72 +52,74 @@ npx tenon baseline capture --directory migrations --namespace app --schema app
|
|
|
119
52
|
```
|
|
120
53
|
|
|
121
54
|
The directory must contain no SQL files. Capture uses native
|
|
122
|
-
`pg_dump --schema-only --no-owner --no-privileges` and writes `0001_baseline.up.sql`
|
|
123
|
-
file. Install compatible PostgreSQL client tools on PATH
|
|
55
|
+
`pg_dump --schema-only --no-owner --no-privileges` and writes `0001_baseline.up.sql` without a down
|
|
56
|
+
file. Install compatible PostgreSQL client tools on PATH, preferably matching the server's major
|
|
57
|
+
version. Capture does not modify source data or history and does not preserve sequence positions.
|
|
124
58
|
|
|
125
|
-
|
|
|
126
|
-
| --------------------- |
|
|
127
|
-
| `--namespace app` | Tenon history table to exclude
|
|
128
|
-
| `--schema app` |
|
|
129
|
-
| Runtime `search_path` |
|
|
59
|
+
| Setting | What it selects |
|
|
60
|
+
| --------------------- | ----------------------------------------------------------------------- |
|
|
61
|
+
| `--namespace app` | Tenon history table to exclude; history/runner scope for later commands |
|
|
62
|
+
| `--schema app` | Schema objects included by pg_dump; repeat for more schemas |
|
|
63
|
+
| Runtime `search_path` | Application name resolution, configured separately |
|
|
130
64
|
|
|
131
|
-
Omitting `--schema` captures the whole database schema, even with a custom namespace.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
handwritten migrations.
|
|
65
|
+
Omitting `--schema` captures the whole database schema, even with a custom namespace. The selected
|
|
66
|
+
Tenon history table is excluded; old-runner metadata tables may remain. Review and commit the SQL.
|
|
67
|
+
Preserve the capture header: it opts this reviewed dump into native baseline parsing. Do not add
|
|
68
|
+
that header to handwritten migrations.
|
|
136
69
|
|
|
137
|
-
A scoped dump
|
|
138
|
-
extensions, and
|
|
139
|
-
|
|
70
|
+
A scoped dump may depend on objects outside its scope. Provision required external schemas,
|
|
71
|
+
extensions, functions, and roles. Fresh replay must exercise representative application operations,
|
|
72
|
+
not merely reproduce table types. Tenon itself requires no particular extension.
|
|
140
73
|
|
|
141
74
|
### Check and adopt an existing database
|
|
142
75
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`generateDefinitions` covers relation metadata, not functions or privileges; include reviewed
|
|
146
|
-
catalog checks for other objects the application needs. Tenon does not perform these comparisons.
|
|
147
|
-
|
|
148
|
-
Generated declaration text is not a version-stable schema fingerprint: driver mappings and generator
|
|
149
|
-
changes can alter it without schema drift. The schema-summary comments expose SQL definitions, but
|
|
150
|
-
their formatting and PostgreSQL's rendering of those definitions are not a stable comparison format
|
|
151
|
-
either. If an existing adoption guard hashes generated text, preserve its original reference and
|
|
152
|
-
freeze a separate reference for each supported generator version from a disposable replay of the
|
|
153
|
-
immutable checkpoint. Compare the replay's metadata with the reviewed checkpoint before accepting
|
|
154
|
-
the new reference; never derive a replacement from whichever live database is configured. Keep
|
|
155
|
-
independent function and privilege checks, and test both matching adoption and refusal after drift.
|
|
156
|
-
This application guard is separate from Tenon's migration checksums; keep applied SQL unchanged.
|
|
76
|
+
Confirm that each existing database matches the reviewed final checkpoint. Tenon does not compare
|
|
77
|
+
the live schema with the baseline. Adoption records its checksum without executing its SQL:
|
|
157
78
|
|
|
158
79
|
```sh
|
|
159
80
|
npx tenon baseline adopt --directory migrations --namespace app
|
|
160
81
|
npx tenon migrate --directory migrations --namespace app
|
|
161
82
|
```
|
|
162
83
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
84
|
+
The namespace must already exist. Repeating adoption with matching history is harmless, including
|
|
85
|
+
after later migrations; conflicting history fails. Once adopted, leave the baseline unchanged and
|
|
86
|
+
add forward migrations.
|
|
87
|
+
|
|
88
|
+
For an application-owned schema guard, keep a frozen checkpoint reference. Generated relation types
|
|
89
|
+
do not cover functions or privileges; check required objects separately. Neither generated text nor
|
|
90
|
+
schema-summary formatting is a version-stable fingerprint. If a guard hashes that text, preserve
|
|
91
|
+
its original reference and freeze a separate reviewed reference for each generator version, taken
|
|
92
|
+
from a disposable replay of the immutable checkpoint. Verify that replay against the checkpoint
|
|
93
|
+
before accepting the reference; do not derive it from whichever live database is configured.
|
|
94
|
+
Test both matching adoption and refusal after drift. This is separate from migration checksums.
|
|
166
95
|
|
|
167
96
|
### Replay a fresh database
|
|
168
97
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
`{
|
|
189
|
-
|
|
190
|
-
|
|
98
|
+
```sh
|
|
99
|
+
npx tenon migrate --directory migrations --namespace app
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Do not adopt a fresh database. Migration executes the committed baseline and subsequent files.
|
|
103
|
+
The dump omits ownership, grants/revokes, and default privileges; objects are recreated under the
|
|
104
|
+
migration role. Deployment provisioning must supply required roles, owners, and grants.
|
|
105
|
+
|
|
106
|
+
For `SECURITY DEFINER` functions, the recreated owner controls authority. PostgreSQL ordinarily grants
|
|
107
|
+
`PUBLIC` execution on new functions. Put fixed policy such as revoking public execution in reviewed
|
|
108
|
+
baseline SQL before first adoption, or in a forward migration. Supply deployment-specific role names
|
|
109
|
+
and grants during provisioning. Verify permitted and denied operations on adopted and fresh databases.
|
|
110
|
+
|
|
111
|
+
Test preserved existing rows, repeatable adoption, later migrations, and fresh replay. A schema-only
|
|
112
|
+
baseline is not a backup; the missing down file prevents rollback across it. Use the application's
|
|
113
|
+
backup/recovery procedure for that boundary.
|
|
114
|
+
|
|
115
|
+
### Baseline API
|
|
116
|
+
|
|
117
|
+
- `captureBaseline({ connection, directory?, namespace?, schemas?, onWarning? })` returns
|
|
118
|
+
`{ id, upPath }`. Connection is a native libpq connection string passed to pg_dump. Omitting
|
|
119
|
+
`schemas` captures the whole database schema; an empty list rejects. Warnings default to
|
|
120
|
+
`console.warn`.
|
|
121
|
+
- `adoptBaseline({ connection, directory?, namespace? })` accepts Tenon's runtime connection forms
|
|
122
|
+
and returns newly recorded IDs. It requires `0001_baseline.up.sql` with no down file.
|
|
123
|
+
|
|
124
|
+
Defaults are directory `migrations` and namespace `public`. See [migrations](migrations.md) for
|
|
125
|
+
history validation, per-file transactions, and locking.
|
package/docs/agent-guide.md
CHANGED
|
@@ -1,55 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agent guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
guide
|
|
5
|
-
[human guide](human-guide.md) gives a step-by-step introduction.
|
|
3
|
+
Use this as a short workflow for changing an existing Tenon application. For first setup, follow
|
|
4
|
+
[Getting started](human-guide.md). Exact contracts are in the [reference](reference.md).
|
|
6
5
|
|
|
7
|
-
##
|
|
6
|
+
## Read the project first
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Inspect the database module, nearby query functions, migration runner, generated types, and check
|
|
9
|
+
commands. Reuse the application's package manager, connection configuration, namespace, and
|
|
10
|
+
conventions. Read the docs shipped with the installed Tenon version.
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
For an upgrade, find the previous resolved version in the lockfile and read the
|
|
13
|
+
[changelog](../CHANGELOG.md) through the target version and follow its API and driver upgrade
|
|
14
|
+
instructions. Check both Tenon and pgclear versions before changing native
|
|
15
|
+
calls or representations.
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
generated-schema imports, namespace, and `QueryClient` convention. Check both resolved Tenon and
|
|
20
|
-
pgclear versions before changing driver calls or value representations.
|
|
21
|
-
|
|
22
|
-
For an upgrade, record the previous resolved Tenon version from the lockfile or installed package
|
|
23
|
-
and read the [Changelog](../CHANGELOG.md) entries through the target version before changing code.
|
|
24
|
-
It ships with the package alongside this guide. Follow its upgrade instructions, regenerate types
|
|
25
|
-
when mappings change, and run the project's typechecks and relevant database tests. Use the
|
|
26
|
-
[adoption guide](adoption-guide.md) when replacing another ORM or transferring migration ownership.
|
|
27
|
-
|
|
28
|
-
## Connect
|
|
29
|
-
|
|
30
|
-
Requires Node.js 24+, PostgreSQL 16+, ESM, TypeScript 6+, and Node development typings. Install with
|
|
31
|
-
`npm install @kequtech/tenon-orm`; retain the project's package manager and TypeScript
|
|
32
|
-
configuration. Tenon includes pgclear 0.3.x as a runtime dependency. Import `sql`, `json`, `param`,
|
|
33
|
-
and codec helpers from Tenon. See [driver updates](reference.md#driver-updates) for independent
|
|
34
|
-
compatible updates; regenerate changed types and rerun database checks.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { createTenon } from '@kequtech/tenon-orm';
|
|
38
|
-
|
|
39
|
-
const connection = process.env.DATABASE_URL;
|
|
40
|
-
if (!connection) throw new Error('DATABASE_URL is required');
|
|
41
|
-
export const db = createTenon({ connection });
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
`connection` accepts a URI string, pgclear `PoolOptions` (including `connectionString`), or an existing
|
|
45
|
-
pgclear `Pool`. Reuse an instance for each database configuration. `db.close()` ends internally created
|
|
46
|
-
pools with a default 5,000 ms grace period; supplied pools remain application-owned. Configure
|
|
47
|
-
shutdown with `db.close({ timeoutMs })`; concurrent callers share its outcome. Finish outstanding
|
|
48
|
-
work before closing. Tenon loads no environment files. Qualify table names or set runtime
|
|
49
|
-
`search_path`; CLI `--namespace` selects tooling scope, not runtime query names. See
|
|
50
|
-
[connection options](reference.md#installation-and-connection).
|
|
51
|
-
|
|
52
|
-
## Query and result contracts
|
|
17
|
+
## Write a query function
|
|
53
18
|
|
|
54
19
|
```ts
|
|
55
20
|
import { sql, type QueryClient } from '@kequtech/tenon-orm';
|
|
@@ -57,139 +22,93 @@ import type { Database } from './generated-schema.js';
|
|
|
57
22
|
|
|
58
23
|
type Summary = Pick<Database['users']['row'], 'id' | 'email'>;
|
|
59
24
|
|
|
60
|
-
function userById(client: QueryClient, { id }: { id: number }) {
|
|
25
|
+
export function userById(client: QueryClient, { id }: { id: number }) {
|
|
61
26
|
return client.query(sql<Summary>`SELECT id, email FROM public.users WHERE id = ${id}`, {
|
|
62
27
|
returns: 'oneOrNone',
|
|
63
28
|
});
|
|
64
29
|
}
|
|
65
30
|
```
|
|
66
31
|
|
|
67
|
-
`QueryClient`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
[JSON/temporal projection](reference.md#persistence-and-domain-values).
|
|
123
|
-
|
|
124
|
-
## Transactions and native access
|
|
125
|
-
|
|
126
|
-
For an existing user with id 42:
|
|
32
|
+
Use a `QueryClient` parameter when callers may pass `db` or `tx`. Keep SQL and execution options
|
|
33
|
+
together. Qualify table names or follow the configured runtime search path.
|
|
34
|
+
|
|
35
|
+
- Bind values without quotes. Do not mix manual `$n` placeholders into templates.
|
|
36
|
+
- Declare the actual projection with `sql<Row>`. Generated table rows do not describe arbitrary
|
|
37
|
+
joins/aggregates, and annotations do not validate runtime output.
|
|
38
|
+
- Choose `many`, `oneOrNone`, `one`, or default `native` deliberately. Modes add no SQL limits.
|
|
39
|
+
A cardinality error alone does not undo an autocommitted write.
|
|
40
|
+
- Use approved `identifier(...)` names and fixed fragments for SQL syntax. Keep mandatory
|
|
41
|
+
authorization predicates in every branch.
|
|
42
|
+
- Use fragments for real reuse/composition. Runtime loops work but may remain unchecked by the
|
|
43
|
+
optional checker. See [writing queries](sql.md).
|
|
44
|
+
|
|
45
|
+
## Preserve representations and validate inputs
|
|
46
|
+
|
|
47
|
+
Bigint/bigserial and `count(*)` read as exact strings by default; command `rowCount` is a number or
|
|
48
|
+
null. Decimals and temporal values default to strings, bytes to Uint8Array, and JSON to unknown.
|
|
49
|
+
Built-in arrays preserve scalar representations and null elements.
|
|
50
|
+
|
|
51
|
+
Plain bigint, valid Date, and supported Temporal inputs are accepted; inputs do not change output
|
|
52
|
+
decoders. Plain arrays/objects require `json`, `param`, or encoded text. Direct undefined rejects.
|
|
53
|
+
JSON encoding still rejects bigint. Import parameter/codec helpers from Tenon.
|
|
54
|
+
|
|
55
|
+
Use the application's registry for queries and generation, and pass it explicitly to programmatic
|
|
56
|
+
inspection/checking. Retain application validation, authorization, domain conversion, and retry
|
|
57
|
+
policy. See [schema types and values](schema-types.md).
|
|
58
|
+
|
|
59
|
+
## Change data deliberately
|
|
60
|
+
|
|
61
|
+
Use `sql.insertValues` and `sql.assignments` for object fields while keeping table, predicates,
|
|
62
|
+
expressions, conflicts, and `RETURNING` in SQL. Apply generated insert/update types and narrow
|
|
63
|
+
editable fields with `Pick`.
|
|
64
|
+
|
|
65
|
+
Missing/undefined insert fields use defaults; assignment fields are omitted; null remains null.
|
|
66
|
+
Empty batches and assignments reject. Explicit column lists select/order fields and can discard
|
|
67
|
+
required inputs. Batch below the complete query's 65,535-parameter limit. See [writing data](mutations.md).
|
|
68
|
+
|
|
69
|
+
For work that commits together, use `db.transaction` and await every operation through `tx`. A
|
|
70
|
+
callback client expires when it finishes; root/native pool queries do not join it. Tenon owns
|
|
71
|
+
transaction boundaries and provides no automatic retries.
|
|
72
|
+
|
|
73
|
+
Use `affectedRows: { exact: n }` or `{ max: n }` for write bounds. A root guard uses a transaction;
|
|
74
|
+
a callback guard uses a savepoint. A rejected guarded operation rolls back before throwing, so a
|
|
75
|
+
caught guard failure permits continued work. An unguarded server error normally aborts the
|
|
76
|
+
transaction. Counts exclude additional trigger/cascade/CTE effects. A lost commit response can
|
|
77
|
+
leave an unknown outcome. See [transactions](reference.md#transactions-guarded-writes-and-errors).
|
|
78
|
+
|
|
79
|
+
## Make schema changes
|
|
80
|
+
|
|
81
|
+
Keep an existing migration runner. With Tenon's runner, create a new numbered SQL pair, fill both
|
|
82
|
+
files or omit an irreversible down file, and apply it. Never change applied SQL; checksums include
|
|
83
|
+
both directions. Each file commits separately. Use direct or session pooling for tooling advisory
|
|
84
|
+
locks. See [migrations](migrations.md).
|
|
85
|
+
|
|
86
|
+
Generate with the configured database instance, after migrations commit:
|
|
127
87
|
|
|
128
88
|
```ts
|
|
129
|
-
await db.
|
|
130
|
-
await tx.query(sql`UPDATE public.users SET active = false WHERE id = ${42}`, {
|
|
131
|
-
affectedRows: { exact: 1 },
|
|
132
|
-
});
|
|
133
|
-
});
|
|
89
|
+
await db.generateTypes({ out: 'src/generated-schema.d.ts', namespace: 'public' });
|
|
134
90
|
```
|
|
135
91
|
|
|
136
|
-
Use
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Bounds are nonnegative safe integers. A guarded `db.query` uses a transaction; a guarded `tx.query`
|
|
143
|
-
uses a savepoint. Rejection rolls back that operation. A caught guarded failure permits continued
|
|
144
|
-
work; an unguarded SQL error normally aborts the transaction. Counts exclude extra trigger/cascade
|
|
145
|
-
and CTE effects. pgclear owns savepoint recovery. A lost commit
|
|
146
|
-
acknowledgment can leave the outcome unknown.
|
|
147
|
-
|
|
148
|
-
`db.native` exposes the existing pool for driver features. Native calls bypass Tenon's guards and
|
|
149
|
-
`onQuery`; they do not join a Tenon transaction. Close owned pools through `db.close()`. Follow the
|
|
150
|
-
[native access contract](reference.md#native-driver-access) for resource ownership and LISTEN.
|
|
151
|
-
Optional `onQuery` reports completion metadata without SQL or values; success inside a callback does
|
|
152
|
-
not establish that the outer transaction committed.
|
|
153
|
-
|
|
154
|
-
## Inspect and validate
|
|
155
|
-
|
|
156
|
-
Choose the tool for the task: `tenon check` compares source contracts, `tenon describe` reports
|
|
157
|
-
finished SQL metadata, and `tenon generate --check` checks generated-file freshness. None replaces
|
|
158
|
-
TypeScript or database tests. See [validation tools](reference.md#choosing-a-validation-tool).
|
|
159
|
-
|
|
160
|
-
Run tenon check on the TypeScript project, a file, or file:line. It recovers authored SQL and types
|
|
161
|
-
without importing application code, then uses pgclear Describe. Mismatches, unchecked queries and
|
|
162
|
-
zero query sites fail; unproven properties do not. Trust declared types when metadata is inconclusive.
|
|
163
|
-
|
|
164
|
-
For checking, object expansions need an explicit finite shape type and required, defined selected
|
|
165
|
-
properties. Prefer explicit column lists. Fixed fragments, const aliases, fixed joins and finite
|
|
166
|
-
ternaries are supported within 16 variants per query. Dynamic fragments, optional-field SQL
|
|
167
|
-
expansion and unknown batch lengths remain unchecked. See [query checking](query-checking.md).
|
|
168
|
-
|
|
169
|
-
A generated update type usually has optional properties. Even with an explicit column list, those
|
|
170
|
-
properties can change the SQL by omission, so the checker may report an ordinary patch as unchecked.
|
|
171
|
-
Keep that runtime behavior when intended. If selected fields are always supplied, validate/narrow
|
|
172
|
-
the input into an explicit required shape. Do not use casts just to suppress a finding. See
|
|
173
|
-
[responding to findings](query-checking.md#responding-to-findings) and
|
|
174
|
-
[troubleshooting](reference.md#troubleshooting).
|
|
92
|
+
Use the project's [generation script](human-guide.md#generate-types), review the diff, and commit
|
|
93
|
+
migrations with exact generated output. Keep handwritten aliases separate and generated files out
|
|
94
|
+
of formatting. In CI, migrate the test database and use `check: true` without first regenerating.
|
|
95
|
+
Production consumes committed declarations.
|
|
96
|
+
|
|
97
|
+
## Verify the changed workflow
|
|
175
98
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
both migration and generation. Omit the down file for an irreversible migration; never edit applied
|
|
193
|
-
history. Each file commits independently with its history entry. Tools use session advisory locks,
|
|
194
|
-
so use a direct or session-pooled connection. Nontransactional DDL needs a separate procedure. See
|
|
195
|
-
the [command reference](reference.md#command-reference).
|
|
99
|
+
Run the project's typecheck and database tests for actual rows, effects, permissions, and rollback.
|
|
100
|
+
Use `tenon check` for source contracts and `tenon describe` for finished SQL metadata when useful.
|
|
101
|
+
See [validation tools](reference.md#choosing-a-validation-tool).
|
|
102
|
+
|
|
103
|
+
The checker fails on definite mismatches and unchecked sites; unproven properties remain notes.
|
|
104
|
+
Fixed composition is supported within bounded variants. Object expansion needs an explicit finite
|
|
105
|
+
shape with required, defined selected fields; optional generated patches may be unchecked. Keep
|
|
106
|
+
intentional omission behavior and test it. Narrow to required fields only when application validation
|
|
107
|
+
establishes that contract; do not cast merely to silence a diagnostic. See
|
|
108
|
+
[responding to findings](query-checking.md#responding-to-findings).
|
|
109
|
+
|
|
110
|
+
Match the runtime role, search path, timezone, and codecs. Describe uses a separate session and
|
|
111
|
+
cannot establish result nullability, JSON members, cardinality, or execution permissions. Native
|
|
112
|
+
calls bypass Tenon's guards/observations and need explicit resource ownership; see
|
|
113
|
+
[connections and driver access](connections.md). For common failures, use
|
|
114
|
+
[troubleshooting](reference.md#troubleshooting).
|