@kequtech/tenon-orm 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -2
- package/README.md +93 -51
- package/dist/check.js +4 -2
- package/dist/cli.js +97 -40
- package/dist/config/init.d.ts +2 -0
- package/dist/config/init.js +34 -0
- package/dist/config/load.d.ts +7 -0
- package/dist/config/load.js +159 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +1 -0
- package/dist/connection.d.ts +5 -0
- package/dist/describe.js +3 -1
- package/dist/generation/output.js +0 -2
- package/dist/index.d.ts +2 -1
- package/dist/inspection/cli.d.ts +3 -4
- package/dist/inspection/cli.js +3 -13
- package/dist/inspection/connection.js +23 -19
- package/dist/inspection/options.d.ts +5 -7
- package/dist/inspection/options.js +49 -22
- package/dist/inspection/types.d.ts +3 -2
- package/dist/migrations/history.d.ts +1 -1
- package/dist/migrations/history.js +3 -2
- package/dist/validation.d.ts +2 -0
- package/dist/validation.js +6 -0
- package/docs/adoption-guide.md +94 -153
- package/docs/agent-guide.md +93 -176
- package/docs/configuration.md +146 -0
- package/docs/connections.md +187 -0
- package/docs/human-guide.md +150 -179
- package/docs/migrations.md +140 -0
- package/docs/mutations.md +104 -67
- package/docs/query-checking.md +122 -120
- package/docs/query-inspection.md +83 -76
- package/docs/reference.md +234 -474
- package/docs/schema-types.md +266 -0
- package/docs/sql.md +212 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Shared runtime and CLI configuration
|
|
2
|
+
|
|
3
|
+
Keep connection settings and codec choices in one module used by the application and Tenon's CLI.
|
|
4
|
+
From the project root, create the configuration file:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npx tenon init
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Initialization opens no database connection, imports no application code, and never overwrites a
|
|
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
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import type { TenonConfig } from '@kequtech/tenon-orm';
|
|
16
|
+
|
|
17
|
+
const connectionString = process.env.DATABASE_URL;
|
|
18
|
+
if (!connectionString) throw new Error('DATABASE_URL is required');
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
connection: {
|
|
22
|
+
connectionString,
|
|
23
|
+
},
|
|
24
|
+
migrations: { directory: 'migrations', namespace: 'public' },
|
|
25
|
+
generate: { out: 'src/generated-schema.d.ts' },
|
|
26
|
+
check: { project: 'tsconfig.json' },
|
|
27
|
+
} satisfies TenonConfig;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Add the application's registry as `connection.codecs` when choosing custom representations.
|
|
31
|
+
|
|
32
|
+
`TenonConfig` and `ConnectionConfig` are exported TypeScript types; configuration is a plain
|
|
33
|
+
default-exported object. Use that connection when constructing the application instance:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// src/db.ts
|
|
37
|
+
import { createTenon } from '@kequtech/tenon-orm';
|
|
38
|
+
import config from '../tenon.config.ts';
|
|
39
|
+
|
|
40
|
+
export const db = createTenon({ connection: config.connection });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The `.ts` imports above run directly with Node.js 24+ TypeScript support. For typechecking with
|
|
44
|
+
`noEmit`, enable `allowImportingTsExtensions`; emitting projects can use
|
|
45
|
+
`rewriteRelativeImportExtensions`. Config and its runtime imports must be runnable by Node without
|
|
46
|
+
bundler aliases or transformations. ESM JavaScript is also supported.
|
|
47
|
+
|
|
48
|
+
Export configuration values, not an open pool, database instance, or factory. Importing the config
|
|
49
|
+
must not start the server, open listeners, or depend on generated runtime files. Tenon opens and
|
|
50
|
+
closes its own tooling resources. `createTenon` still accepts application-owned pools separately.
|
|
51
|
+
|
|
52
|
+
## Run commands
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npx tenon new create_users
|
|
56
|
+
npx tenon migrate
|
|
57
|
+
npx tenon generate
|
|
58
|
+
npx tenon generate --check
|
|
59
|
+
npx tenon check
|
|
60
|
+
npx tenon check src/users.ts:23 --json
|
|
61
|
+
npx tenon describe query.sql
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`generate`, `check`, and `describe` require configuration. They use its connection and codec registry
|
|
65
|
+
and never fall back to `DATABASE_URL` merely because a config is missing or invalid. Your config
|
|
66
|
+
chooses which environment variables to read; Tenon does not load `.env` files automatically.
|
|
67
|
+
|
|
68
|
+
`new`, migration/status/rollback, and baseline commands also discover configuration. Without a
|
|
69
|
+
config, they retain the existing migration-directory defaults and `DATABASE_URL` connection
|
|
70
|
+
workflow. `init`, `new`, and `--help` open no database connection. Config imports may still require environment
|
|
71
|
+
values, according to the module you wrote. Invalid session/timeout settings are rejected when config
|
|
72
|
+
loads, even for commands that do not inspect queries. Migration SQL and history reads use default decoding;
|
|
73
|
+
application codec overrides do not change migration bookkeeping.
|
|
74
|
+
|
|
75
|
+
## Discovery, paths, and overrides
|
|
76
|
+
|
|
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 are an error.
|
|
79
|
+
Select one explicitly when needed:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
npx tenon generate --config config/tenon.production.mts --check
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`--config` resolves from the working directory. An explicit missing file or an import failure is an
|
|
86
|
+
error; Tenon never switches to another configuration after that failure.
|
|
87
|
+
|
|
88
|
+
Paths stored in config resolve from the config file's directory. Configured migration directories,
|
|
89
|
+
generation output, and checker projects therefore work when a command runs from a nested directory.
|
|
90
|
+
An omitted migration directory means `migrations` beside the config; an omitted checker project
|
|
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.
|
|
93
|
+
|
|
94
|
+
## Configuration fields
|
|
95
|
+
|
|
96
|
+
| Field | Purpose |
|
|
97
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `connection` | Required PostgreSQL URI or pgclear pool-option values, including TLS, password callbacks, startup parameters, and codecs. Open pools are rejected. |
|
|
99
|
+
| `migrations.directory` | Migration files; defaults to `migrations` relative to config. |
|
|
100
|
+
| `migrations.namespace` | Migration/history schema; defaults to `public`. Also supplies the generation namespace when generation does not specify one. |
|
|
101
|
+
| `generate.out` | Required when a `generate` section is present. Otherwise supply `--out` to the generation command. |
|
|
102
|
+
| `generate.namespace` | Schema to generate; overrides the migration namespace for generation only. |
|
|
103
|
+
| `baseline.schemas` | Optional nonempty list of schemas for native baseline capture. Omission captures the whole database, independently of the migration namespace. |
|
|
104
|
+
| `baseline.connection` | Optional native libpq connection for baseline capture; `--connection` overrides it. |
|
|
105
|
+
| `check.project` | TypeScript project; defaults to `tsconfig.json` relative to config. |
|
|
106
|
+
| `check.readTypes` | Explicit type expressions for custom decoders whose representations are unidentified. See [checking](query-checking.md#programmatic-use-and-custom-codecs). |
|
|
107
|
+
| `session` | Optional inspection/checking `role`, `searchPath`, and `timeZone` overrides. They do not configure runtime queries or migrations. |
|
|
108
|
+
| `timeouts` | Optional inspection/checking `connectMs`, `statementMs`, and `lockMs` bounds. |
|
|
109
|
+
|
|
110
|
+
Generation and runtime queries use `connection.codecs`. Checking and inspection inherit that registry
|
|
111
|
+
too. Their internal catalog queries keep default decoding. Runtime startup parameters are retained,
|
|
112
|
+
with inspection's statement/lock timeout bounds applied regardless of startup-parameter casing; explicit `session` settings and CLI flags
|
|
113
|
+
then override role, search path, or timezone for inspection. See [connections](connections.md) and
|
|
114
|
+
[inspection limits](query-inspection.md#session-and-timeouts).
|
|
115
|
+
|
|
116
|
+
Keep credentials and deployment-specific addresses in environment variables or the existing secret
|
|
117
|
+
provider. For a migration role different from the application role, select a separate config that
|
|
118
|
+
imports shared settings and supplies that role's connection. Avoid duplicating codec definitions.
|
|
119
|
+
|
|
120
|
+
Baseline capture uses native `pg_dump`, which accepts a libpq connection string rather than pgclear
|
|
121
|
+
option objects or password callbacks. Configure its native connection and scope explicitly when
|
|
122
|
+
needed:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// Additional fields in the shared config:
|
|
126
|
+
baseline: { connection: connectionString, schemas: ['public'] }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Capture uses `--connection`, then `baseline.connection`, then a string main connection or the
|
|
130
|
+
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-specific settings into libpq settings.
|
|
133
|
+
|
|
134
|
+
## Programmatic APIs
|
|
135
|
+
|
|
136
|
+
The CLI owns generated-file output and freshness checks. `generateDefinitions` returns source text
|
|
137
|
+
without file handling for callers building custom tooling; it accepts explicit connection/options.
|
|
138
|
+
|
|
139
|
+
`describeQuery` and `checkQueries` accept a URI or connection-option values and inherit
|
|
140
|
+
`connection.codecs`. An explicit `codecs` argument selects the read mappings reported by those
|
|
141
|
+
inspection APIs. They own separate, short-lived sessions and do not join callback transactions.
|
|
142
|
+
Migration APIs continue accepting explicit connection/options. No programmatic API discovers config.
|
|
143
|
+
|
|
144
|
+
The CLI imports the selected trusted config and its dependencies. Source checking subsequently reads
|
|
145
|
+
query source without importing those query modules or executing application SQL. TypeScript loads
|
|
146
|
+
only for source checking, not for normal queries, migrations, generation, or inspection.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Connections and driver access
|
|
2
|
+
|
|
3
|
+
`createTenon` uses a pgclear connection pool. Tenon includes the driver; a separate installation is
|
|
4
|
+
needed only when your application imports native pgclear APIs directly.
|
|
5
|
+
|
|
6
|
+
## Configure a database instance
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { createTenon } from '@kequtech/tenon-orm';
|
|
10
|
+
|
|
11
|
+
const connection = process.env.DATABASE_URL;
|
|
12
|
+
if (!connection) throw new Error('DATABASE_URL is required');
|
|
13
|
+
|
|
14
|
+
export const db = createTenon({
|
|
15
|
+
connection: {
|
|
16
|
+
connectionString: connection,
|
|
17
|
+
maxConnections: 10,
|
|
18
|
+
connectTimeoutMs: 5000,
|
|
19
|
+
acquireTimeoutMs: 5000,
|
|
20
|
+
idleTimeoutMs: 30_000,
|
|
21
|
+
startupParameters: { search_path: 'app,public' },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`connection` accepts a PostgreSQL URI, pgclear `PoolOptions`, or an existing pgclear `Pool`.
|
|
27
|
+
`ConnectionOptions` is Tenon's alias for `PoolOptions`. Object fields override URI settings.
|
|
28
|
+
Reuse an instance for each database configuration.
|
|
29
|
+
|
|
30
|
+
The driver does not load environment variables, `.env`, pgpass, or service files.
|
|
31
|
+
[Shared configuration](configuration.md) supplies connection values to the CLI and application.
|
|
32
|
+
Migration commands retain `DATABASE_URL` fallback when no config exists. An empty options object
|
|
33
|
+
uses localhost:5432, the current OS user and a database with that user's name.
|
|
34
|
+
|
|
35
|
+
Use `tls: {}` for TLS with certificate and hostname verification using system trust. Supply the
|
|
36
|
+
provider's CA or other required TLS settings through pgclear's `tls` options. Preserve the
|
|
37
|
+
connection requirements of your deployment. The complete option list is in the installed driver's
|
|
38
|
+
`docs/public-api.md`; unknown options reject.
|
|
39
|
+
|
|
40
|
+
Qualify table names in SQL or set runtime `search_path`. Tooling `namespace` options select the
|
|
41
|
+
schema to migrate or generate; they do not configure your application's queries. The example above
|
|
42
|
+
assumes an `app` schema already exists.
|
|
43
|
+
|
|
44
|
+
## Close resources
|
|
45
|
+
|
|
46
|
+
For a short-lived script, use `try/finally`:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
try {
|
|
50
|
+
// Await database work here.
|
|
51
|
+
} finally {
|
|
52
|
+
await db.close();
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For a server, stop accepting work and finish requests before closing the database. A close call
|
|
57
|
+
immediately rejects new Tenon work and gives an owned pool a default 5,000 ms grace period. Set
|
|
58
|
+
`db.close({ timeoutMs: 1000 })` to change it; `0` requests immediate shutdown. Remaining driver work
|
|
59
|
+
and checkouts expire at the deadline. This bounds database resources, not arbitrary JavaScript
|
|
60
|
+
callbacks or proof that a remote rollback completed.
|
|
61
|
+
|
|
62
|
+
The first valid close call fixes the timeout. Concurrent and later calls share its promise and
|
|
63
|
+
outcome. Timeout must be an integer from 0 to 2,147,483,647 ms; invalid initial options reject
|
|
64
|
+
without closing the instance.
|
|
65
|
+
|
|
66
|
+
### Use an application-owned pool
|
|
67
|
+
|
|
68
|
+
When another part of the application already owns a pgclear pool:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { createPool } from '@kequtech/pgclear';
|
|
72
|
+
import { createTenon } from '@kequtech/tenon-orm';
|
|
73
|
+
|
|
74
|
+
const pool = createPool(connection);
|
|
75
|
+
const db = createTenon({ connection: pool });
|
|
76
|
+
// db.native === pool
|
|
77
|
+
|
|
78
|
+
await db.close(); // Disables this wrapper; pool remains open.
|
|
79
|
+
await pool.close(); // The application owns the pool's shutdown.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Tenon borrows the supplied pool by identity. Closing the wrapper does not cancel that pool's
|
|
83
|
+
outstanding work. Tool APIs that accept `Connection` follow the same ownership rule.
|
|
84
|
+
|
|
85
|
+
## Choose output conversions
|
|
86
|
+
|
|
87
|
+
Outputs follow the pool's codec registry. For example, to read int8 as JavaScript bigint and
|
|
88
|
+
`timestamptz` as Date:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { builtin, createTenon, defaultCodecs } from '@kequtech/tenon-orm';
|
|
92
|
+
|
|
93
|
+
const codecs = defaultCodecs.with(builtin.int8BigInt).with(builtin.timestamptzDate);
|
|
94
|
+
const db = createTenon({ connection: { connectionString: connection, codecs } });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
These choices apply across the database instance, including callback transactions. Tenon's query
|
|
98
|
+
options have no per-query codec override. Input encoders remain separate: a `param(value, codec)`
|
|
99
|
+
carries its own encoder, and passing a Date does not select a Date output decoder.
|
|
100
|
+
|
|
101
|
+
Place the registry in shared `connection.codecs` for runtime queries and CLI generation, inspection,
|
|
102
|
+
and checking. Programmatic
|
|
103
|
+
`describeQuery` and `checkQueries` inherit codecs from connection-option values too. See
|
|
104
|
+
[schema types and values](schema-types.md#custom-codecs) for custom representations and Temporal.
|
|
105
|
+
|
|
106
|
+
## Observe completed queries
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const db = createTenon({
|
|
110
|
+
connection,
|
|
111
|
+
onQuery(event) {
|
|
112
|
+
console.log(event.label, event.durationMs, event.outcome, event.rowCount);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Set `{ label: 'user-by-email' }` on a query to identify it. `onQuery` receives duration, outcome,
|
|
118
|
+
and available command/count/error-code metadata, with no SQL text or bound values. Validation
|
|
119
|
+
failures before execution are not observed. Native driver calls and tooling are outside this hook.
|
|
120
|
+
|
|
121
|
+
Observers are not awaited. A thrown error or rejected observer promise emits a warning and leaves
|
|
122
|
+
the query outcome unchanged. A successful query event inside a transaction does not mean the outer
|
|
123
|
+
transaction committed. Guarded-call duration includes its commit or rollback work.
|
|
124
|
+
|
|
125
|
+
## Use native driver features
|
|
126
|
+
|
|
127
|
+
`db.native` is the actual pgclear pool. Use it for prepared statements, streams, COPY, or held
|
|
128
|
+
sessions. For a finished SQL string:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const result = await db.native.queryClassic('SELECT $1::integer AS value', [42]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Native calls bypass Tenon's result modes, affected-row guards, SQL boundary checks, and `onQuery`.
|
|
135
|
+
They do not join a Tenon callback transaction. A callback's `tx` exposes only `query`.
|
|
136
|
+
|
|
137
|
+
Use the public driver session APIs and release held leases. For example:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
await db.native.withSession(async (session) => {
|
|
141
|
+
const prepared = await session.prepare('SELECT $1::integer AS value');
|
|
142
|
+
try {
|
|
143
|
+
await prepared.query<{ value: number }>([42]);
|
|
144
|
+
} finally {
|
|
145
|
+
await prepared.close();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Native `query` accepts pgclear's own SQL tag. Tenon's `sql` and `unsafeSql` results belong in
|
|
151
|
+
`db.query`; use `queryClassic` for text to avoid needing a second tag. When both tags are used,
|
|
152
|
+
import the native one as `pgSql`.
|
|
153
|
+
|
|
154
|
+
### Listen for notifications
|
|
155
|
+
|
|
156
|
+
Import `listen` directly from pgclear for a dedicated connection:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { listen } from '@kequtech/pgclear';
|
|
160
|
+
|
|
161
|
+
const listener = await listen(connection, {
|
|
162
|
+
channels: ['users_changed'],
|
|
163
|
+
onNotification: ({ payload }) => console.log(payload),
|
|
164
|
+
reconnect: true,
|
|
165
|
+
});
|
|
166
|
+
// At shutdown:
|
|
167
|
+
await listener.close();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
A listener has a separate lifetime and is not closed by `db.close()`. Notifications are not replayed
|
|
171
|
+
after a disconnection; the application owns its recovery policy.
|
|
172
|
+
|
|
173
|
+
## Update the driver
|
|
174
|
+
|
|
175
|
+
Tenon depends on `@kequtech/pgclear: ^0.3.0`, which permits compatible 0.3.x patch updates. With npm:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
npm update @kequtech/pgclear
|
|
179
|
+
npm ls @kequtech/pgclear
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Review the resolved version and lockfile, regenerate affected declarations, and run application
|
|
183
|
+
checks before committing. A lockfile keeps its chosen version until updated. Pgclear 0.4 is outside
|
|
184
|
+
this range and requires an explicit Tenon compatibility update.
|
|
185
|
+
|
|
186
|
+
If the application imports pgclear directly, declare it as a dependency with a compatible version
|
|
187
|
+
so the package manager can share a copy. See the [changelog](../CHANGELOG.md) when updating Tenon.
|