@kequtech/tenon-orm 0.4.3 → 0.4.5
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 -0
- package/README.md +10 -8
- package/dist/cli.js +12 -7
- package/dist/config/connection.d.ts +3 -0
- package/dist/config/connection.js +22 -0
- package/dist/config/init.js +2 -5
- package/dist/config/load.d.ts +2 -2
- package/dist/config/load.js +29 -36
- package/dist/config/validate.d.ts +3 -0
- package/dist/config/validate.js +28 -0
- package/dist/config.d.ts +7 -2
- package/dist/connection.d.ts +4 -1
- package/dist/connection.js +1 -1
- package/dist/database.d.ts +3 -7
- package/dist/database.js +23 -15
- package/dist/generation/render.js +5 -4
- package/dist/index.d.ts +0 -1
- package/dist/inspection/options.js +3 -13
- package/dist/inspection/types.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/agent-guide.md +2 -3
- package/docs/configuration.md +148 -83
- package/docs/connections.md +63 -32
- package/docs/human-guide.md +10 -6
- package/docs/query-inspection.md +2 -2
- package/docs/reference.md +34 -23
- package/docs/schema-types.md +31 -23
- package/package.json +2 -2
package/docs/configuration.md
CHANGED
|
@@ -1,56 +1,120 @@
|
|
|
1
1
|
# Shared runtime and CLI configuration
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A `TenonConfig` describes one database: its connection, codecs, runtime query observer, and CLI
|
|
4
|
+
defaults. The application and CLI use the same object, so generated types and query checks follow
|
|
5
|
+
the representations returned at runtime.
|
|
6
|
+
|
|
7
|
+
Create the configuration from the project root:
|
|
5
8
|
|
|
6
9
|
```sh
|
|
7
10
|
npx tenon init
|
|
8
11
|
```
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
file. Without `--config`, it refuses when a config is already discoverable in this directory or a
|
|
12
|
-
parent. Use `tenon init --config path.mjs` for an explicit additional destination or JavaScript output. The generated TypeScript config looks like this:
|
|
13
|
+
The command writes `tenon.config.ts` with a named `main` export:
|
|
13
14
|
|
|
14
15
|
```ts
|
|
15
16
|
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export default {
|
|
21
|
-
connection: {
|
|
22
|
-
connectionString,
|
|
23
|
-
},
|
|
18
|
+
export const main = {
|
|
19
|
+
connection: { connectionString: process.env.DATABASE_URL },
|
|
24
20
|
migrations: { directory: 'migrations', namespace: 'public' },
|
|
25
21
|
generate: { out: 'src/generated-schema.d.ts' },
|
|
26
22
|
check: { project: 'tsconfig.json' },
|
|
27
23
|
} satisfies TenonConfig;
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
Add
|
|
26
|
+
Add `connection.codecs` when choosing custom representations. Initialization needs no credentials,
|
|
27
|
+
imports no application code, and never overwrites a file. It refuses to create a default config when
|
|
28
|
+
one is already discoverable in the current directory or a parent. Use `--config path.mjs` for an
|
|
29
|
+
explicit destination or JavaScript output.
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
default-exported object. Use that connection when constructing the application instance:
|
|
31
|
+
Import the config into the application and pass it directly to `createTenon`:
|
|
34
32
|
|
|
35
33
|
```ts
|
|
36
34
|
// src/db.ts
|
|
37
35
|
import { createTenon } from '@kequtech/tenon-orm';
|
|
38
|
-
import
|
|
36
|
+
import { main } from '../tenon.config.ts';
|
|
39
37
|
|
|
40
|
-
export const db = createTenon(
|
|
38
|
+
export const db = createTenon(main);
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`
|
|
46
|
-
|
|
41
|
+
## Initialization and imports
|
|
42
|
+
|
|
43
|
+
Constructing `db` checks the config's shape without validating credentials or creating a pool. The
|
|
44
|
+
first query or transaction initializes one pool, which subsequent and concurrent calls reuse.
|
|
45
|
+
Accessing `db.native` also initializes the pool and validates its settings; the driver connects when
|
|
46
|
+
an operation needs a session. Closing an unused instance creates no pool and needs no credentials.
|
|
47
|
+
|
|
48
|
+
Read environment values in the config without a top-level credential check. This lets builds import
|
|
49
|
+
application modules without database access. An undefined or empty `connection`, or an explicitly
|
|
50
|
+
undefined or empty `connectionString`, fails when the database is used and never selects the driver's
|
|
51
|
+
localhost defaults. Environment values are captured when the module is imported, so supply runtime
|
|
52
|
+
variables before starting the application. Queries performed during a build still need credentials.
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
The `.ts` imports above run with Node.js 24+ TypeScript support. For typechecking with `noEmit`, enable
|
|
55
|
+
`allowImportingTsExtensions`; emitting projects can use `rewriteRelativeImportExtensions`. Config
|
|
56
|
+
files and their imports must run in Node without bundler aliases or transformations. ESM JavaScript
|
|
57
|
+
is also supported.
|
|
58
|
+
|
|
59
|
+
Keep config files free of resource initialization: export connection values and codecs, and let Tenon
|
|
60
|
+
open and close its tooling resources. The application reuses its database instance and closes it at
|
|
61
|
+
shutdown. Programmatic configs can also borrow an existing pool; see [pool ownership](connections.md#use-an-application-owned-pool).
|
|
62
|
+
|
|
63
|
+
## Multiple databases
|
|
64
|
+
|
|
65
|
+
Export one config per database from the same file. For example, add `analytics` beside `main` in
|
|
66
|
+
`tenon.config.ts`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export const analytics = {
|
|
70
|
+
connection: { connectionString: process.env.ANALYTICS_DATABASE_URL },
|
|
71
|
+
migrations: { directory: 'analytics/migrations', namespace: 'public' },
|
|
72
|
+
generate: { out: 'src/analytics-schema.d.ts' },
|
|
73
|
+
check: { project: 'tsconfig.json' },
|
|
74
|
+
} satisfies TenonConfig;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each export has its own connection settings and codecs. Use the same `connection.codecs` value when
|
|
78
|
+
databases share a registry, and give each database its own migrations and generated output.
|
|
79
|
+
Application code selects the export through an ordinary import:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { createTenon } from '@kequtech/tenon-orm';
|
|
83
|
+
import { main, analytics } from '../tenon.config.ts';
|
|
84
|
+
|
|
85
|
+
export const db = createTenon(main);
|
|
86
|
+
export const analyticsDb = createTenon(analytics);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The CLI selects the corresponding export with `--database`:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
npx tenon migrate --database main
|
|
93
|
+
npx tenon migrate --database analytics
|
|
94
|
+
npx tenon generate --database analytics
|
|
95
|
+
npx tenon check src/analytics/queries.ts --database analytics
|
|
96
|
+
npx tenon new add_events --database analytics
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A file with one config is selected automatically. Multiple database exports require `--database`;
|
|
100
|
+
an unknown name fails without selecting another connection. Named objects with a `connection` field
|
|
101
|
+
are database configs, while other helper exports are ignored. Existing default exports remain
|
|
102
|
+
supported and can be selected explicitly as `--database default`.
|
|
103
|
+
|
|
104
|
+
Only the selected config's settings are validated, although importing the file evaluates the whole
|
|
105
|
+
module. Keep credentials as plain environment reads so one database does not require another's
|
|
106
|
+
credentials. Runtime selection uses the imported object and does not depend on the working directory.
|
|
107
|
+
Close each instance separately. For databases with different schemas, use separate checker projects
|
|
108
|
+
or select the relevant query file when running `tenon check`.
|
|
109
|
+
|
|
110
|
+
`--config` selects a file and `--database` selects an export within it. For example,
|
|
111
|
+
`tenon init --config config/tenon.production.ts` creates another file, and
|
|
112
|
+
`tenon migrate --config config/tenon.production.ts --database main` selects its `main` export.
|
|
51
113
|
|
|
52
114
|
## Run commands
|
|
53
115
|
|
|
116
|
+
With the single config generated by `tenon init`:
|
|
117
|
+
|
|
54
118
|
```sh
|
|
55
119
|
npx tenon new create_users
|
|
56
120
|
npx tenon migrate
|
|
@@ -61,86 +125,87 @@ npx tenon check src/users.ts:23 --json
|
|
|
61
125
|
npx tenon describe query.sql
|
|
62
126
|
```
|
|
63
127
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
128
|
+
Generation, checking, and inspection require configuration and use its connection and codecs. A
|
|
129
|
+
missing or invalid config fails instead of falling back to `DATABASE_URL`. The config chooses which
|
|
130
|
+
environment variables to read; Tenon does not load `.env` files.
|
|
67
131
|
|
|
68
|
-
|
|
69
|
-
config
|
|
70
|
-
workflow. `init`, `new`, and `--help`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
132
|
+
Migration creation, migration/status/rollback, and baseline commands also discover configuration.
|
|
133
|
+
Without a config or an explicit `--database`, they retain the migration-directory defaults and
|
|
134
|
+
`DATABASE_URL` workflow. `init`, `new`, and `--help` need no database credentials. `new` reads migration
|
|
135
|
+
settings without validating the connection; `--help` loads no config. Any eager checks in your own
|
|
136
|
+
config module still run when that module is imported.
|
|
137
|
+
|
|
138
|
+
The CLI validates the selected config's tooling sections when loading it, including session and
|
|
139
|
+
timeout settings for commands that do not inspect queries. Migration SQL and history reads use
|
|
140
|
+
default decoding, independently of application codec overrides.
|
|
74
141
|
|
|
75
142
|
## Discovery, paths, and overrides
|
|
76
143
|
|
|
77
|
-
Tenon searches the working directory and then parents for the nearest directory containing
|
|
78
|
-
`tenon.config.ts`, `.mts`, `.js`, or `.mjs`. Multiple recognized files in that directory
|
|
79
|
-
|
|
144
|
+
Tenon searches the working directory and then its parents for the nearest directory containing
|
|
145
|
+
`tenon.config.ts`, `.mts`, `.js`, or `.mjs`. Multiple recognized files in that directory require an
|
|
146
|
+
explicit selection:
|
|
80
147
|
|
|
81
148
|
```sh
|
|
82
149
|
npx tenon generate --config config/tenon.production.mts --check
|
|
83
150
|
```
|
|
84
151
|
|
|
85
|
-
`--config` resolves from the working directory.
|
|
86
|
-
error; Tenon
|
|
152
|
+
`--config` resolves from the working directory. A missing file, missing database export, or import
|
|
153
|
+
failure is an error; Tenon does not switch to another configuration after a failure.
|
|
87
154
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
means `tsconfig.json` beside it. Relative CLI path arguments and flags resolve from the working
|
|
92
|
-
directory. CLI flags override the corresponding configured values.
|
|
155
|
+
Configured paths resolve from the config file's directory. An omitted migration directory means
|
|
156
|
+
`migrations` beside the config, and an omitted checker project means `tsconfig.json` beside it. CLI
|
|
157
|
+
path flags resolve from the working directory and override the corresponding configured values.
|
|
93
158
|
|
|
94
159
|
## Configuration fields
|
|
95
160
|
|
|
96
|
-
| Field | Purpose
|
|
97
|
-
| ---------------------- |
|
|
98
|
-
| `connection` |
|
|
99
|
-
| `
|
|
100
|
-
| `migrations.
|
|
101
|
-
| `
|
|
102
|
-
| `generate.
|
|
103
|
-
| `
|
|
104
|
-
| `baseline.
|
|
105
|
-
| `
|
|
106
|
-
| `check.
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
| Field | Purpose |
|
|
162
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| `connection` | PostgreSQL URI or pgclear pool options, including TLS, password callbacks, startup parameters, and codecs. Required when the database is used. |
|
|
164
|
+
| `onQuery` | Optional observer for completed runtime queries. CLI tooling and native calls do not invoke it. |
|
|
165
|
+
| `migrations.directory` | Migration files; defaults to `migrations` beside the config. |
|
|
166
|
+
| `migrations.namespace` | Migration/history schema; defaults to `public`. Also supplies the generation namespace unless overridden. |
|
|
167
|
+
| `generate.out` | Required when `generate` is present. Otherwise supply `--out`. |
|
|
168
|
+
| `generate.namespace` | Schema to generate; overrides the migration namespace for generation. |
|
|
169
|
+
| `baseline.schemas` | Optional nonempty schema list for native baseline capture. Omission captures the whole database. |
|
|
170
|
+
| `baseline.connection` | Optional native libpq connection for baseline capture; `--connection` overrides it. |
|
|
171
|
+
| `check.project` | TypeScript project; defaults to `tsconfig.json` beside the config. |
|
|
172
|
+
| `check.readTypes` | Fallback type expressions for unidentified decoder representations. See [query checking](query-checking.md#programmatic-use-and-custom-codecs). |
|
|
173
|
+
| `session` | Inspection/checking overrides for `role`, `searchPath`, and `timeZone`. |
|
|
174
|
+
| `timeouts` | Inspection/checking bounds for `connectMs`, `statementMs`, and `lockMs`. |
|
|
175
|
+
|
|
176
|
+
Generation, checking, and inspection inherit `connection.codecs`; their internal catalog queries
|
|
177
|
+
keep default decoding. Runtime startup parameters are retained. Inspection applies its statement
|
|
178
|
+
and lock timeout bounds regardless of startup-parameter casing, then applies explicit session and
|
|
179
|
+
CLI overrides. These inspection settings do not change runtime queries or migrations. See
|
|
180
|
+
[connection settings](connections.md) and [inspection limits](query-inspection.md#session-and-timeouts).
|
|
181
|
+
|
|
182
|
+
Keep credentials and deployment addresses in environment variables or the existing secret provider.
|
|
183
|
+
For a separate migration role, add a config export with that role's connection and the shared codecs.
|
|
184
|
+
|
|
185
|
+
## Native baseline capture
|
|
186
|
+
|
|
187
|
+
Baseline capture uses `pg_dump`, which requires a native libpq connection string. If the main
|
|
188
|
+
connection uses a driver-options object, add the native URI to the config:
|
|
123
189
|
|
|
124
190
|
```ts
|
|
125
|
-
//
|
|
126
|
-
baseline: { connection:
|
|
191
|
+
// Within the database config:
|
|
192
|
+
baseline: { connection: process.env.DATABASE_URL, schemas: ['public'] }
|
|
127
193
|
```
|
|
128
194
|
|
|
129
195
|
Capture uses `--connection`, then `baseline.connection`, then a string main connection or the
|
|
130
196
|
no-config `DATABASE_URL` fallback. `--schema` flags replace `baseline.schemas`. Adoption ignores
|
|
131
|
-
capture-only settings. Supply the same database and required native TLS/authentication settings;
|
|
132
|
-
Tenon does not translate driver
|
|
197
|
+
capture-only settings. Supply the same database and its required native TLS/authentication settings;
|
|
198
|
+
Tenon does not translate driver options into libpq settings.
|
|
133
199
|
|
|
134
200
|
## Programmatic APIs
|
|
135
201
|
|
|
136
|
-
|
|
137
|
-
|
|
202
|
+
`createTenon(config)` accepts the shared object and initializes its connection lazily. Other
|
|
203
|
+
programmatic APIs take explicit connection and operation options; none discover config files.
|
|
138
204
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
205
|
+
The CLI owns generated-file output and freshness checks. `generateDefinitions` returns source text
|
|
206
|
+
for custom tooling without writing files. `describeQuery` and `checkQueries` inherit codecs from
|
|
207
|
+
connection-option values; an explicit `codecs` argument selects their reported read mappings. These
|
|
208
|
+
inspection APIs own separate, short-lived sessions and do not join callback transactions.
|
|
143
209
|
|
|
144
|
-
The CLI imports the selected trusted config and its dependencies. Source checking
|
|
145
|
-
|
|
146
|
-
only for source checking, not for normal queries, migrations, generation, or inspection.
|
|
210
|
+
The CLI imports the selected trusted config and its dependencies. Source checking then reads query
|
|
211
|
+
modules without importing them or executing their SQL. TypeScript loads only for source checking.
|
package/docs/connections.md
CHANGED
|
@@ -5,32 +5,49 @@ needed only when your application imports native pgclear APIs directly.
|
|
|
5
5
|
|
|
6
6
|
## Configure a database instance
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
import { createTenon } from '@kequtech/tenon-orm';
|
|
8
|
+
Define connection settings in the shared config:
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
```ts
|
|
11
|
+
// tenon.config.ts
|
|
12
|
+
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
13
13
|
|
|
14
|
-
export const
|
|
14
|
+
export const main = {
|
|
15
15
|
connection: {
|
|
16
|
-
connectionString:
|
|
16
|
+
connectionString: process.env.DATABASE_URL,
|
|
17
17
|
maxConnections: 10,
|
|
18
18
|
connectTimeoutMs: 5000,
|
|
19
19
|
acquireTimeoutMs: 5000,
|
|
20
20
|
idleTimeoutMs: 30_000,
|
|
21
21
|
startupParameters: { search_path: 'app,public' },
|
|
22
22
|
},
|
|
23
|
-
}
|
|
23
|
+
} satisfies TenonConfig;
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
`ConnectionOptions` is Tenon's alias for `PoolOptions`. Object fields override URI settings.
|
|
28
|
-
Reuse an instance for each database configuration.
|
|
26
|
+
Pass the config to the constructor:
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
```ts
|
|
29
|
+
import { createTenon } from '@kequtech/tenon-orm';
|
|
30
|
+
import { main } from '../tenon.config.ts';
|
|
31
|
+
|
|
32
|
+
export const db = createTenon(main);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`createTenon` takes one `TenonConfig` object. Connection validation and pool creation wait until the
|
|
36
|
+
first query, transaction, or `db.native` access. The pool connects when it needs a session. Closing an
|
|
37
|
+
unused instance does not resolve its connection. Subsequent operations reuse the same pool.
|
|
38
|
+
|
|
39
|
+
The config's `connection` field accepts a PostgreSQL URI or pgclear pool-option values.
|
|
40
|
+
`ConnectionOptions` is Tenon's alias for `PoolOptions`. Object fields override URI settings. A
|
|
41
|
+
programmatically constructed config may also supply an existing pool, preserving its ownership.
|
|
42
|
+
|
|
43
|
+
Keep codecs in `connection.codecs` so runtime and tooling use the same representations. See
|
|
44
|
+
[shared configuration](configuration.md) for build-safe environment values and
|
|
45
|
+
[multiple databases](configuration.md#multiple-databases) for named exports and CLI selection.
|
|
46
|
+
|
|
47
|
+
The config supplies environment values to the driver, which does not load `.env`, pgpass, or service
|
|
48
|
+
files. An explicitly missing URI fails when the instance is used. Deliberately configuring
|
|
49
|
+
`connection: {}` uses the driver's local defaults: localhost:5432, the current OS user, and a database
|
|
50
|
+
with that user's name.
|
|
34
51
|
|
|
35
52
|
Use `tls: {}` for TLS with certificate and hostname verification using system trust. Supply the
|
|
36
53
|
provider's CA or other required TLS settings through pgclear's `tls` options. Preserve the
|
|
@@ -68,10 +85,9 @@ without closing the instance.
|
|
|
68
85
|
When another part of the application already owns a pgclear pool:
|
|
69
86
|
|
|
70
87
|
```ts
|
|
71
|
-
import { createPool } from '@kequtech/pgclear';
|
|
72
88
|
import { createTenon } from '@kequtech/tenon-orm';
|
|
89
|
+
import { pool } from './existing-pool.js';
|
|
73
90
|
|
|
74
|
-
const pool = createPool(connection);
|
|
75
91
|
const db = createTenon({ connection: pool });
|
|
76
92
|
// db.native === pool
|
|
77
93
|
|
|
@@ -84,37 +100,42 @@ outstanding work. Tool APIs that accept `Connection` follow the same ownership r
|
|
|
84
100
|
|
|
85
101
|
## Choose output conversions
|
|
86
102
|
|
|
87
|
-
Outputs follow the
|
|
88
|
-
|
|
103
|
+
Outputs follow the configured codec registry. To read int8 as JavaScript bigint and `timestamptz` as
|
|
104
|
+
Date, set the registry in `tenon.config.ts`:
|
|
89
105
|
|
|
90
106
|
```ts
|
|
91
|
-
import { builtin,
|
|
107
|
+
import { builtin, defaultCodecs, type TenonConfig } from '@kequtech/tenon-orm';
|
|
92
108
|
|
|
93
109
|
const codecs = defaultCodecs.with(builtin.int8BigInt).with(builtin.timestamptzDate);
|
|
94
|
-
const
|
|
110
|
+
export const main = {
|
|
111
|
+
connection: { connectionString: process.env.DATABASE_URL, codecs },
|
|
112
|
+
} satisfies TenonConfig;
|
|
95
113
|
```
|
|
96
114
|
|
|
97
115
|
These choices apply across the database instance, including callback transactions. Tenon's query
|
|
98
116
|
options have no per-query codec override. Input encoders remain separate: a `param(value, codec)`
|
|
99
117
|
carries its own encoder, and passing a Date does not select a Date output decoder.
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
and
|
|
103
|
-
`describeQuery` and `checkQueries` inherit codecs from connection-option values too. See
|
|
119
|
+
The CLI uses that registry for generation, inspection, and checking. Programmatic `describeQuery`
|
|
120
|
+
and `checkQueries` inherit codecs from connection-option values too. See
|
|
104
121
|
[schema types and values](schema-types.md#custom-codecs) for custom representations and Temporal.
|
|
105
122
|
|
|
106
123
|
## Observe completed queries
|
|
107
124
|
|
|
125
|
+
Set `onQuery` on the database config:
|
|
126
|
+
|
|
108
127
|
```ts
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
129
|
+
|
|
130
|
+
export const main = {
|
|
131
|
+
connection: { connectionString: process.env.DATABASE_URL },
|
|
111
132
|
onQuery(event) {
|
|
112
133
|
console.log(event.label, event.durationMs, event.outcome, event.rowCount);
|
|
113
134
|
},
|
|
114
|
-
}
|
|
135
|
+
} satisfies TenonConfig;
|
|
115
136
|
```
|
|
116
137
|
|
|
117
|
-
Set `{ label: 'user-by-email' }` on a query to identify it.
|
|
138
|
+
Set `{ label: 'user-by-email' }` on a query to identify it. The observer receives duration, outcome,
|
|
118
139
|
and available command/count/error-code metadata, with no SQL text or bound values. Validation
|
|
119
140
|
failures before execution are not observed. Native driver calls and tooling are outside this hook.
|
|
120
141
|
|
|
@@ -153,26 +174,36 @@ import the native one as `pgSql`.
|
|
|
153
174
|
|
|
154
175
|
### Listen for notifications
|
|
155
176
|
|
|
156
|
-
|
|
177
|
+
During application startup, pass a validated PostgreSQL URI (`connection` below) to pgclear's
|
|
178
|
+
`listen` for a dedicated notification connection:
|
|
157
179
|
|
|
158
180
|
```ts
|
|
159
181
|
import { listen } from '@kequtech/pgclear';
|
|
160
182
|
|
|
161
183
|
const listener = await listen(connection, {
|
|
162
|
-
channels: [
|
|
184
|
+
channels: [],
|
|
163
185
|
onNotification: ({ payload }) => console.log(payload),
|
|
164
186
|
reconnect: true,
|
|
165
187
|
});
|
|
188
|
+
// Membership can change on this same connection. Await server registration:
|
|
189
|
+
await listener.subscribe('users_changed');
|
|
190
|
+
// When the channel is no longer needed:
|
|
191
|
+
await listener.unsubscribe('users_changed');
|
|
166
192
|
// At shutdown:
|
|
167
193
|
await listener.close();
|
|
168
194
|
```
|
|
169
195
|
|
|
170
|
-
A listener has a separate lifetime and is not closed by `db.close()`.
|
|
171
|
-
|
|
196
|
+
A listener has a separate lifetime and is not closed by `db.close()`. Subscription changes share its
|
|
197
|
+
dedicated connection; reconnect restores the last acknowledged channel set. Subscriptions are set
|
|
198
|
+
operations: repeated subscribes do not add reference counts. Applications own subscriber counts,
|
|
199
|
+
authorization, and routing. Notifications are not replayed after a disconnection; use the listener's
|
|
200
|
+
status/generation changes to detect a possible gap and reconcile persistent state. See
|
|
201
|
+
[pgclear's notification guide](https://github.com/kequtech/pgclear/blob/main/docs/notifications.md)
|
|
202
|
+
for deadlines, cancellation, and changes queued during reconnect.
|
|
172
203
|
|
|
173
204
|
## Update the driver
|
|
174
205
|
|
|
175
|
-
Tenon depends on `@kequtech/pgclear: ^0.3.
|
|
206
|
+
Tenon depends on `@kequtech/pgclear: ^0.3.3`, which permits compatible 0.3.x patch updates. With npm:
|
|
176
207
|
|
|
177
208
|
```sh
|
|
178
209
|
npm update @kequtech/pgclear
|
package/docs/human-guide.md
CHANGED
|
@@ -34,10 +34,8 @@ The command writes `tenon.config.ts` without opening a database or overwriting e
|
|
|
34
34
|
// tenon.config.ts
|
|
35
35
|
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
export default {
|
|
40
|
-
connection: { connectionString },
|
|
37
|
+
export const main = {
|
|
38
|
+
connection: { connectionString: process.env.DATABASE_URL },
|
|
41
39
|
migrations: { directory: 'migrations', namespace: 'public' },
|
|
42
40
|
generate: { out: 'src/generated-schema.d.ts' },
|
|
43
41
|
check: { project: 'tsconfig.json' },
|
|
@@ -49,14 +47,20 @@ Use it in the application too:
|
|
|
49
47
|
```ts
|
|
50
48
|
// src/db.ts
|
|
51
49
|
import { createTenon } from '@kequtech/tenon-orm';
|
|
52
|
-
import
|
|
50
|
+
import { main } from '../tenon.config.ts';
|
|
53
51
|
|
|
54
|
-
export const db = createTenon(
|
|
52
|
+
export const db = createTenon(main);
|
|
55
53
|
```
|
|
56
54
|
|
|
57
55
|
These `.ts` imports use Node.js 24+ TypeScript support. See
|
|
58
56
|
[shared configuration](configuration.md) for import conventions, custom codecs, discovery, and overrides.
|
|
59
57
|
|
|
58
|
+
Importing the config and constructing `db` need no database credentials. The first query or transaction
|
|
59
|
+
validates the connection and initializes its pool; `db.native` access also initializes it. Keep
|
|
60
|
+
database operations out of module imports that run during builds. For another database, add a named
|
|
61
|
+
config export in the same file, pass it to `createTenon`, and select that export in CLI commands with
|
|
62
|
+
`--database`.
|
|
63
|
+
|
|
60
64
|
Reuse `db` across requests. Call `await db.close()` when a standalone script finishes or a server
|
|
61
65
|
shuts down. See [connections](connections.md) for pool options and application-owned pools.
|
|
62
66
|
|
package/docs/query-inspection.md
CHANGED
|
@@ -9,9 +9,9 @@ compare TypeScript source annotations instead, use [query checking](query-checki
|
|
|
9
9
|
```ts
|
|
10
10
|
// scripts/inspect-query.ts
|
|
11
11
|
import { describeQuery, sql } from '@kequtech/tenon-orm';
|
|
12
|
-
import
|
|
12
|
+
import { main } from '../tenon.config.ts';
|
|
13
13
|
|
|
14
|
-
const connection =
|
|
14
|
+
const connection = main.connection;
|
|
15
15
|
|
|
16
16
|
const query = sql<{ id: number; total: string }>`
|
|
17
17
|
SELECT ${42}::integer AS id, count(*) AS total FROM pg_catalog.pg_class
|