@kequtech/tenon-orm 0.3.1 → 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.
Files changed (68) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +81 -26
  3. package/dist/affected-rows.js +0 -1
  4. package/dist/baseline.js +3 -5
  5. package/dist/check/contracts.d.ts +6 -0
  6. package/dist/check/contracts.js +206 -0
  7. package/dist/check/project.d.ts +18 -0
  8. package/dist/check/project.js +224 -0
  9. package/dist/check/source.d.ts +51 -0
  10. package/dist/check/source.js +629 -0
  11. package/dist/check/types.d.ts +36 -0
  12. package/dist/check/types.js +1 -0
  13. package/dist/check.d.ts +4 -0
  14. package/dist/check.js +147 -0
  15. package/dist/cli.js +40 -27
  16. package/dist/connection.d.ts +5 -12
  17. package/dist/connection.js +12 -121
  18. package/dist/database.js +10 -8
  19. package/dist/describe.js +10 -3
  20. package/dist/generate.d.ts +13 -3
  21. package/dist/generate.js +52 -25
  22. package/dist/generation/catalog.d.ts +5 -6
  23. package/dist/generation/catalog.js +7 -9
  24. package/dist/generation/output.js +2 -4
  25. package/dist/generation/render.d.ts +3 -0
  26. package/dist/generation/render.js +60 -0
  27. package/dist/generation/schema-summary.d.ts +3 -3
  28. package/dist/generation/schema-summary.js +2 -2
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.js +2 -0
  31. package/dist/inspection/catalog.d.ts +3 -6
  32. package/dist/inspection/catalog.js +9 -18
  33. package/dist/inspection/cli.d.ts +12 -0
  34. package/dist/inspection/cli.js +9 -4
  35. package/dist/inspection/connection.d.ts +2 -3
  36. package/dist/inspection/connection.js +20 -22
  37. package/dist/inspection/options.js +0 -6
  38. package/dist/inspection/result.d.ts +3 -2
  39. package/dist/inspection/result.js +13 -26
  40. package/dist/inspection/types.d.ts +6 -4
  41. package/dist/migrations/connection.d.ts +3 -3
  42. package/dist/migrations/connection.js +14 -21
  43. package/dist/migrations/history.d.ts +4 -4
  44. package/dist/migrations/history.js +5 -5
  45. package/dist/migrations.js +7 -9
  46. package/dist/query-options.d.ts +1 -1
  47. package/dist/query.js +1 -1
  48. package/dist/transaction.d.ts +2 -2
  49. package/dist/transaction.js +21 -77
  50. package/dist/types.d.ts +4 -1
  51. package/docs/adoption-guide.md +88 -111
  52. package/docs/agent-guide.md +94 -147
  53. package/docs/connections.md +185 -0
  54. package/docs/human-guide.md +133 -146
  55. package/docs/migrations.md +133 -0
  56. package/docs/mutations.md +110 -60
  57. package/docs/query-checking.md +199 -0
  58. package/docs/query-inspection.md +89 -58
  59. package/docs/reference.md +232 -437
  60. package/docs/schema-types.md +263 -0
  61. package/docs/sql.md +212 -0
  62. package/package.json +11 -7
  63. package/dist/driver/types.d.ts +0 -10
  64. package/dist/driver/types.js +0 -49
  65. package/dist/generation/table-types.d.ts +0 -2
  66. package/dist/generation/table-types.js +0 -96
  67. package/dist/inspection/protocol.d.ts +0 -7
  68. package/dist/inspection/protocol.js +0 -45
@@ -1,167 +1,114 @@
1
- # Tenon agent guide
1
+ # Agent guide
2
2
 
3
- Tenon executes explicit PostgreSQL SQL through pg. Use ordinary functions for reusable queries. This
4
- guide covers routine implementation; the [reference](reference.md) defines the full API and the
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
- ## Start with the existing project
6
+ ## Read the project first
8
7
 
9
- Inspect its connection configuration, migration runner, generated types, query functions, and check
10
- commands. Preserve those conventions. Tenon queries and type generation do not require Tenon
11
- migration history. Keep input validation, authorization, domain conversion, and retry policy in the
12
- application.
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.
13
11
 
14
- For an upgrade, record the previous resolved Tenon version from the lockfile or installed package
15
- and read the [Changelog](../CHANGELOG.md) entries through the target version before changing code.
16
- It ships with the package alongside this guide. Follow its upgrade instructions, regenerate types
17
- when mappings change, and run the project's typechecks and relevant database tests. Use the
18
- [adoption guide](adoption-guide.md) when replacing another ORM or transferring migration ownership.
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.
19
16
 
20
- ## Connect
21
-
22
- Requires Node.js 24+, PostgreSQL 16+, ESM, TypeScript 6+, and Node development typings. Install with
23
- `npm install @kequtech/tenon-orm`; retain the project's package manager and TypeScript
24
- configuration.
25
-
26
- ```ts
27
- import { createTenon } from '@kequtech/tenon-orm';
28
-
29
- const connection = process.env.DATABASE_URL;
30
- if (!connection) throw new Error('DATABASE_URL is required');
31
- export const db = createTenon({ connection });
32
- ```
33
-
34
- `connection` accepts a URI string, pg `PoolConfig` (including `connectionString`), or an existing
35
- `pg.Pool`. Reuse an instance for each database configuration. `db.close()` ends internally created
36
- pools with a default 5,000 ms grace period; supplied pools remain application-owned. Configure
37
- shutdown with `db.close({ timeoutMs })`; concurrent callers share its outcome. Finish outstanding
38
- work before closing. Tenon loads no environment files. Qualify table names or set runtime
39
- `search_path`; CLI `--namespace` selects tooling scope, not runtime query names. See
40
- [connection options](reference.md#installation-and-connection).
41
-
42
- ## Query and result contracts
17
+ ## Write a query function
43
18
 
44
19
  ```ts
45
20
  import { sql, type QueryClient } from '@kequtech/tenon-orm';
46
- import type { Rows } from './generated-schema.js';
21
+ import type { Database } from './generated-schema.js';
47
22
 
48
- type Summary = Pick<Rows['users'], 'id' | 'email'>;
23
+ type Summary = Pick<Database['users']['row'], 'id' | 'email'>;
49
24
 
50
- function userById(client: QueryClient, { id }: { id: number }) {
25
+ export function userById(client: QueryClient, { id }: { id: number }) {
51
26
  return client.query(sql<Summary>`SELECT id, email FROM public.users WHERE id = ${id}`, {
52
27
  returns: 'oneOrNone',
53
28
  });
54
29
  }
55
30
  ```
56
31
 
57
- `QueryClient` accepts either `db` or a transaction's `tx`. Keep SQL and execution options together.
58
- `sql<Row>` declares one selected row; it does not verify SQL or validate data. Unannotated fields
59
- are `unknown`. Generated table types can describe full rows or `Pick` projections; joins and
60
- aggregates need types for their actual output.
61
-
62
- | `returns` | Result | Required returned rows |
63
- | ------------------ | -------------------------------------------------- | ---------------------- |
64
- | `native` (default) | Driver object with `.rows`, `.rowCount`, `.fields` | Any |
65
- | `many` | Plain `Row[]` | Any |
66
- | `oneOrNone` | `Row \| undefined` | Zero or one |
67
- | `one` | `Row` | Exactly one |
68
-
69
- Options are `{ returns?, affectedRows?, label? }`; unknown keys fail. Modes do not add SQL limits or
70
- `RETURNING`. A returned-row mismatch throws `CardinalityError` with code
71
- `TENON_CARDINALITY_MISMATCH`; it alone does not undo an autocommitted write.
72
-
73
- ## Parameters and composition
74
-
75
- - Interpolate values without quotes. Do not author `$n` placeholders inside templates. `undefined`
76
- and `null` bind as SQL null, following pg.
77
- - Use `id = ANY(${ids}::integer[])` for a set of IDs. Cast standalone parameters such as
78
- `SELECT ${value}::integer AS value` when SQL must establish the type for inspection.
79
- - `identifier(schema, table)` quotes separate approved names. Strings such as `'DESC'` remain bound
80
- values; choose SQL syntax through fixed query alternatives or fragments.
81
- - `sql.fragment` creates partial SQL with bound values; `sql.join(parts, 'AND' | 'OR' | ',')`
82
- combines a nonempty list. Blank fragments fail. Group mixed conditions with parentheses.
83
- - Full `sql` or `unsafeSql` queries cannot be nested. `unsafeSql<Row>(text, values)` is for trusted
84
- external SQL with an explicit values array. Each Tenon query executes one statement.
85
-
86
- Use fragments for actual reuse or variable composition. Keep fixed structure in SQL, and retain
87
- mandatory authorization predicates when optional filters are absent.
88
-
89
- ## Writes and generated values
90
-
91
- `sql.insertValues(rowOrRows, columns?)` builds column/value fragments;
92
- `sql.assignments(patch, columns?)` builds assignments. Missing/undefined insert fields use defaults;
93
- update fields are omitted. Explicit null stays null. Empty batches and empty assignments fail.
94
- Selected columns ignore unlisted fields and can discard required inputs. Put expressions, conflict
95
- policy, and `RETURNING` in the surrounding SQL. Batch in the caller below the query-wide
96
- 65,535-parameter limit.
97
-
98
- Use generated `Rows.Insert['users']` and `Rows.Update['users']` inputs; narrow editable fields with
99
- `Pick`. These types account for defaults, writable columns, domains, and driver representations.
100
- They do not validate constraints or JSON contents. See [mutation examples](mutations.md).
101
-
102
- Use `JSON.stringify(value)` for JSON write inputs; arrays otherwise bind as PostgreSQL arrays.
103
- Bigint and numeric scalars read as strings, while numeric arrays read as numbers and can lose
104
- precision. Temporal reads use `Date | number` for finite dates and infinities. SQL NULL array
105
- elements remain JavaScript null. Keep domain validation at the persistence boundary. See
106
- [value mappings](reference.md#generated-types-and-checking) and the
107
- [JSON/finite-date projection](reference.md#persistence-and-domain-values). For authentication policy
108
- requiring finite expiry, include `isfinite(expires_at)` alongside the SQL expiry comparison.
109
-
110
- ## Transactions and native access
111
-
112
- 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:
113
87
 
114
88
  ```ts
115
- await db.transaction(async (tx) => {
116
- await tx.query(sql`UPDATE public.users SET active = false WHERE id = ${42}`, {
117
- affectedRows: { exact: 1 },
118
- });
119
- });
89
+ await db.generateTypes({ out: 'src/generated-schema.d.ts', namespace: 'public' });
120
90
  ```
121
91
 
122
- Use `tx` for every operation in the callback and await the work. Tenon commits on success and rolls
123
- back on an escaping error; the callback client expires when the callback finishes. Direct
124
- BEGIN/COMMIT/ROLLBACK boundaries, nested callback transactions, and automatic retries are not
125
- provided. Transactions belong to one database instance.
126
-
127
- `affectedRows: { exact: n }` or `{ max: n }` checks the final INSERT/UPDATE/DELETE/MERGE count.
128
- Bounds are nonnegative safe integers. A guarded `db.query` uses a transaction; a guarded `tx.query`
129
- uses a savepoint. Rejection rolls back that operation. A caught guarded failure permits continued
130
- work; an unguarded SQL error normally aborts the transaction. Counts exclude extra trigger/cascade
131
- and CTE effects. Reserve `tenon_affected_rows_` savepoint names for Tenon. A lost commit
132
- acknowledgment can leave the outcome unknown.
133
-
134
- `db.native` exposes the existing pool for driver features. Native calls bypass Tenon's guards and
135
- `onQuery`; they do not join a Tenon transaction. Close owned pools through `db.close()`. Follow the
136
- [native access contract](reference.md#native-driver-access) for resource ownership and LISTEN.
137
- Optional `onQuery` reports completion metadata without SQL or values; success inside a callback does
138
- not establish that the outer transaction committed.
139
-
140
- ## Inspect and validate
141
-
142
- Use `describeQuery({ connection, query })` on completed SQL or `tenon describe query.sql` on a file.
143
- Inspection owns a separate connection. Match the runtime role, search path, and timezone. Temporary
144
- tables and transaction-local state are not inherited; reported mappings assume pg's default parsers.
145
- Result nullability is unknown and JSON structure is unchecked. A successful description does not
146
- verify `sql<Row>`, bound values, execution permission, or effects.
147
-
148
- Pair inspection with database assertions for complete relevant rows, meaningful query variants, and
149
- rollback. A small query-building function can share the same statement with inspection and
150
- execution; keep simple queries inline when that is sufficient. See
151
- [query inspection](query-inspection.md).
152
-
153
- ## Schema changes
154
-
155
- With Tenon's runner, development can use
156
- `tenon migrate && tenon generate --out src/generated-schema.d.ts`. Commit migrations and exact
157
- generated output together; exclude the generated file from automatic formatting. Production runs
158
- migrations and deploys committed types. `generate --check` detects representation drift, not pending
159
- migrations or every schema change.
160
-
161
- All database CLI commands require `DATABASE_URL`. `tenon new <name>` creates empty up/down files
162
- offline; fill them before migrating. Migration commands use `--directory migrations` and
163
- `--namespace public` by default; generation also accepts `--namespace`. Pass a custom namespace to
164
- both migration and generation. Omit the down file for an irreversible migration; never edit applied
165
- history. Each file commits independently with its history entry. Tools use session advisory locks,
166
- so use a direct or session-pooled connection. Nontransactional DDL needs a separate procedure. See
167
- the [command reference](reference.md#command-reference).
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
98
+
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).
@@ -0,0 +1,185 @@
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', TimeZone: 'UTC' },
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. Tenon CLI commands
31
+ read `DATABASE_URL`; application code passes its configuration explicitly. An empty options object
32
+ uses localhost:5432, the current OS user and a database with that user's name.
33
+
34
+ Use `tls: {}` for TLS with certificate and hostname verification using system trust. Supply the
35
+ provider's CA or other required TLS settings through pgclear's `tls` options. Preserve the
36
+ connection requirements of your deployment. The complete option list is in the installed driver's
37
+ `docs/public-api.md`; unknown options reject.
38
+
39
+ Qualify table names in SQL or set runtime `search_path`. Tooling `namespace` options select the
40
+ schema to migrate or generate; they do not configure your application's queries. The example above
41
+ assumes an `app` schema already exists.
42
+
43
+ ## Close resources
44
+
45
+ For a short-lived script, use `try/finally`:
46
+
47
+ ```ts
48
+ try {
49
+ // Await database work here.
50
+ } finally {
51
+ await db.close();
52
+ }
53
+ ```
54
+
55
+ For a server, stop accepting work and finish requests before closing the database. A close call
56
+ immediately rejects new Tenon work and gives an owned pool a default 5,000 ms grace period. Set
57
+ `db.close({ timeoutMs: 1000 })` to change it; `0` requests immediate shutdown. Remaining driver work
58
+ and checkouts expire at the deadline. This bounds database resources, not arbitrary JavaScript
59
+ callbacks or proof that a remote rollback completed.
60
+
61
+ The first valid close call fixes the timeout. Concurrent and later calls share its promise and
62
+ outcome. Timeout must be an integer from 0 to 2,147,483,647 ms; invalid initial options reject
63
+ without closing the instance.
64
+
65
+ ### Use an application-owned pool
66
+
67
+ When another part of the application already owns a pgclear pool:
68
+
69
+ ```ts
70
+ import { createPool } from '@kequtech/pgclear';
71
+ import { createTenon } from '@kequtech/tenon-orm';
72
+
73
+ const pool = createPool(connection);
74
+ const db = createTenon({ connection: pool });
75
+ // db.native === pool
76
+
77
+ await db.close(); // Disables this wrapper; pool remains open.
78
+ await pool.close(); // The application owns the pool's shutdown.
79
+ ```
80
+
81
+ Tenon borrows the supplied pool by identity. Closing the wrapper does not cancel that pool's
82
+ outstanding work. Tool APIs that accept `Connection` follow the same ownership rule.
83
+
84
+ ## Choose output conversions
85
+
86
+ Outputs follow the pool's codec registry. For example, to read int8 as JavaScript bigint and
87
+ `timestamptz` as Date:
88
+
89
+ ```ts
90
+ import { builtin, createTenon, defaultCodecs } from '@kequtech/tenon-orm';
91
+
92
+ const codecs = defaultCodecs.with(builtin.int8BigInt).with(builtin.timestamptzDate);
93
+ const db = createTenon({ connection: { connectionString: connection, codecs } });
94
+ ```
95
+
96
+ These choices apply across the database instance, including callback transactions. Tenon's query
97
+ options have no per-query codec override. Input encoders remain separate: a `param(value, codec)`
98
+ carries its own encoder, and passing a Date does not select a Date output decoder.
99
+
100
+ Use `db.generateTypes` with this instance to inherit its read mappings. Pass the same registry
101
+ explicitly to `describeQuery` and `checkQueries`; their CLI forms use default codecs. See
102
+ [schema types and values](schema-types.md#custom-codecs) for custom representations and Temporal.
103
+
104
+ ## Observe completed queries
105
+
106
+ ```ts
107
+ const db = createTenon({
108
+ connection,
109
+ onQuery(event) {
110
+ console.log(event.label, event.durationMs, event.outcome, event.rowCount);
111
+ },
112
+ });
113
+ ```
114
+
115
+ Set `{ label: 'user-by-email' }` on a query to identify it. `onQuery` receives duration, outcome,
116
+ and available command/count/error-code metadata, with no SQL text or bound values. Validation
117
+ failures before execution are not observed. Native driver calls and tooling are outside this hook.
118
+
119
+ Observers are not awaited. A thrown error or rejected observer promise emits a warning and leaves
120
+ the query outcome unchanged. A successful query event inside a transaction does not mean the outer
121
+ transaction committed. Guarded-call duration includes its commit or rollback work.
122
+
123
+ ## Use native driver features
124
+
125
+ `db.native` is the actual pgclear pool. Use it for prepared statements, streams, COPY, or held
126
+ sessions. For a finished SQL string:
127
+
128
+ ```ts
129
+ const result = await db.native.queryClassic('SELECT $1::integer AS value', [42]);
130
+ ```
131
+
132
+ Native calls bypass Tenon's result modes, affected-row guards, SQL boundary checks, and `onQuery`.
133
+ They do not join a Tenon callback transaction. A callback's `tx` exposes only `query`.
134
+
135
+ Use the public driver session APIs and release held leases. For example:
136
+
137
+ ```ts
138
+ await db.native.withSession(async (session) => {
139
+ const prepared = await session.prepare('SELECT $1::integer AS value');
140
+ try {
141
+ await prepared.query<{ value: number }>([42]);
142
+ } finally {
143
+ await prepared.close();
144
+ }
145
+ });
146
+ ```
147
+
148
+ Native `query` accepts pgclear's own SQL tag. Tenon's `sql` and `unsafeSql` results belong in
149
+ `db.query`; use `queryClassic` for text to avoid needing a second tag. When both tags are used,
150
+ import the native one as `pgSql`.
151
+
152
+ ### Listen for notifications
153
+
154
+ Import `listen` directly from pgclear for a dedicated connection:
155
+
156
+ ```ts
157
+ import { listen } from '@kequtech/pgclear';
158
+
159
+ const listener = await listen(connection, {
160
+ channels: ['users_changed'],
161
+ onNotification: ({ payload }) => console.log(payload),
162
+ reconnect: true,
163
+ });
164
+ // At shutdown:
165
+ await listener.close();
166
+ ```
167
+
168
+ A listener has a separate lifetime and is not closed by `db.close()`. Notifications are not replayed
169
+ after a disconnection; the application owns its recovery policy.
170
+
171
+ ## Update the driver
172
+
173
+ Tenon depends on `@kequtech/pgclear: ^0.3.0`, which permits compatible 0.3.x patch updates. With npm:
174
+
175
+ ```sh
176
+ npm update @kequtech/pgclear
177
+ npm ls @kequtech/pgclear
178
+ ```
179
+
180
+ Review the resolved version and lockfile, regenerate affected declarations, and run application
181
+ checks before committing. A lockfile keeps its chosen version until updated. Pgclear 0.4 is outside
182
+ this range and requires an explicit Tenon compatibility update.
183
+
184
+ If the application imports pgclear directly, declare it as a dependency with a compatible version
185
+ so the package manager can share a copy. See the [changelog](../CHANGELOG.md) when updating Tenon.