@kequtech/tenon-orm 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +15 -19
- package/dist/affected-rows.d.ts +1 -1
- package/dist/affected-rows.js +6 -6
- package/dist/baseline.d.ts +1 -1
- package/dist/baseline.js +5 -5
- package/dist/cli.js +18 -39
- package/dist/client.js +1 -1
- package/dist/connection.d.ts +18 -13
- package/dist/connection.js +14 -43
- package/dist/database.js +4 -2
- package/dist/describe.d.ts +3 -0
- package/dist/describe.js +21 -0
- package/dist/driver/array.d.ts +5 -0
- package/dist/driver/array.js +70 -0
- package/dist/driver/lifecycle.d.ts +15 -0
- package/dist/driver/lifecycle.js +57 -0
- package/dist/driver/session.d.ts +8 -0
- package/dist/driver/session.js +92 -0
- package/dist/driver/types.d.ts +10 -0
- package/dist/driver/types.js +49 -0
- package/dist/generate.js +7 -7
- package/dist/generation/catalog.d.ts +2 -2
- package/dist/generation/catalog.js +4 -4
- package/dist/generation/output.d.ts +2 -0
- package/dist/generation/output.js +35 -0
- package/dist/generation/schema-summary.d.ts +3 -3
- package/dist/generation/schema-summary.js +11 -11
- package/dist/generation/table-types.js +2 -52
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/inspection/catalog.d.ts +25 -0
- package/dist/inspection/catalog.js +29 -0
- package/dist/inspection/cli.d.ts +22 -0
- package/dist/inspection/cli.js +34 -0
- package/dist/inspection/connection.d.ts +4 -0
- package/dist/inspection/connection.js +44 -0
- package/dist/inspection/options.d.ts +15 -0
- package/dist/inspection/options.js +49 -0
- package/dist/inspection/result.d.ts +4 -0
- package/dist/inspection/result.js +90 -0
- package/dist/inspection/types.d.ts +74 -0
- package/dist/inspection/types.js +1 -0
- package/dist/migrations/baseline-sql.d.ts +1 -1
- package/dist/migrations/baseline-sql.js +1 -1
- package/dist/migrations/connection.d.ts +2 -3
- package/dist/migrations/connection.js +15 -13
- package/dist/migrations/history.d.ts +4 -4
- package/dist/migrations/history.js +5 -5
- package/dist/migrations.js +7 -7
- package/dist/mutations.d.ts +0 -5
- package/dist/mutations.js +33 -41
- package/dist/query-options.d.ts +2 -2
- package/dist/query-options.js +4 -4
- package/dist/query.d.ts +3 -4
- package/dist/query.js +2 -2
- package/dist/sql.d.ts +1 -1
- package/dist/sql.js +1 -1
- package/dist/transaction-sql.js +1 -1
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.js +14 -13
- package/dist/types.d.ts +4 -4
- package/docs/adoption-guide.md +39 -76
- package/docs/agent-guide.md +76 -107
- package/docs/human-guide.md +20 -34
- package/docs/mutations.md +13 -13
- package/docs/query-inspection.md +128 -0
- package/docs/reference.md +66 -288
- package/package.json +10 -31
- package/dist/safeql/composition.d.ts +0 -9
- package/dist/safeql/composition.js +0 -168
- package/dist/safeql/eslint.d.ts +0 -2
- package/dist/safeql/eslint.js +0 -50
- package/dist/safeql/mutations.d.ts +0 -3
- package/dist/safeql/mutations.js +0 -74
- package/dist/safeql/plugin.d.ts +0 -3
- package/dist/safeql/plugin.js +0 -43
- package/dist/safeql/static.d.ts +0 -10
- package/dist/safeql/static.js +0 -59
- package/dist/safeql.d.ts +0 -6
- package/dist/safeql.js +0 -7
package/docs/mutations.md
CHANGED
|
@@ -34,10 +34,10 @@ entire input object contains only allowed keys. Keep narrower application input
|
|
|
34
34
|
`tenon generate` also provides `Rows.Update['profiles']`. Use `Pick` to limit a patch to fields
|
|
35
35
|
the caller may edit, for example `Pick<Rows.Update['profiles'], 'name' | 'bio'>`. Generated inputs
|
|
36
36
|
understand defaults, nullability, domains, and generated columns. They use write representations:
|
|
37
|
-
timestamps accept `Date | string`, bigint accepts `string | bigint`, and JSON
|
|
37
|
+
timestamps accept `Date | string`, bigint accepts `string | bigint`, and JSON accepts native JavaScript values through Tenon's exported `JSONValue` type.
|
|
38
38
|
See the [input-type reference](reference.md#insert-and-update-inputs) for the complete mapping.
|
|
39
39
|
|
|
40
|
-
## Input types
|
|
40
|
+
## Input types and runtime validation
|
|
41
41
|
|
|
42
42
|
Apply input types with an annotation, `satisfies`, or a helper generic argument. Generating the
|
|
43
43
|
file alone does not check every write. Narrow the fields each operation accepts:
|
|
@@ -55,9 +55,9 @@ function createProfile(client: QueryClient, input: ProfileInput) {
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Here `id` and `score` are assumed to be integers. TypeScript rejects an incompatible typed input
|
|
58
|
-
such as `{ name: 'Ada', score: true }`.
|
|
59
|
-
|
|
60
|
-
constraints and application authorization in place.
|
|
58
|
+
such as `{ name: 'Ada', score: true }`. Inspect the completed SQL with `describeQuery` and test its
|
|
59
|
+
returned rows and write effects against the database. Validate external inputs before calling this
|
|
60
|
+
function and keep database constraints and application authorization in place.
|
|
61
61
|
|
|
62
62
|
**Selected columns can discard required fields.** For a table requiring integer `id` and text
|
|
63
63
|
`name` and `location`, this input is valid but the query fails:
|
|
@@ -115,22 +115,22 @@ database default; that alone does not decide what an upsert should overwrite.
|
|
|
115
115
|
|
|
116
116
|
## JSONB arrays and PostgreSQL arrays
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
SQL context determines whether a bound JavaScript array is a PostgreSQL array or a JSON value.
|
|
119
|
+
Pass native JavaScript values; Postgres.js serializes them for the target type:
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
122
|
const handles = ['ada', 'lin'];
|
|
123
123
|
await db.query(
|
|
124
124
|
sql`INSERT INTO deliveries ${sql.insertValues({
|
|
125
|
-
recipients_json:
|
|
125
|
+
recipients_json: handles, // jsonb column
|
|
126
126
|
recipient_handles: handles, // text[] column
|
|
127
127
|
})}`,
|
|
128
128
|
);
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Objects used as JSON values retain
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
Objects used as JSON values retain Postgres.js's ordinary JSON conversion. The helper does not need runtime schema metadata. Generated JSON input types use Tenon's exported
|
|
132
|
+
`JSONValue` type, excluding SQL null for nonnullable columns. Pre-serialized text becomes a JSON
|
|
133
|
+
string; use SQL such as `'null'::jsonb` when JSON null is required. Validate JSON contents in the application.
|
|
134
134
|
|
|
135
135
|
## Batches in one transaction
|
|
136
136
|
|
|
@@ -146,7 +146,7 @@ await db.transaction(async (tx) => {
|
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
A failure rolls back all batches. The helper rejects an empty batch; this loop makes no insert
|
|
149
|
-
calls for empty input.
|
|
149
|
+
calls for empty input. Each query accepts at most 65,533 bound parameters, including parameters outside
|
|
150
150
|
the mutation fragment. An oversized query reports its actual count, for example
|
|
151
|
-
`at most
|
|
151
|
+
`at most 65533 bound parameters; received 65534`. Defaults consume no parameters. Account for the
|
|
152
152
|
selected columns and surrounding query when choosing a batch size; Tenon never splits queries automatically.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Inspect and test queries
|
|
2
|
+
|
|
3
|
+
`describeQuery` asks PostgreSQL what a completed SQL statement accepts and returns, without
|
|
4
|
+
binding its values or executing it. Use it while writing a query or in a database test. It handles
|
|
5
|
+
the actual SQL produced by fragments, helpers, loops, and conditional filters.
|
|
6
|
+
Its `postgres` driver installs with Tenon; no extra
|
|
7
|
+
consumer dependency or configuration is needed. Calling inspection opens a separate connection.
|
|
8
|
+
|
|
9
|
+
## Inspect the query you will execute
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { describeQuery, sql } from '@kequtech/tenon-orm';
|
|
13
|
+
|
|
14
|
+
const connection = process.env.DATABASE_URL;
|
|
15
|
+
if (!connection) throw new Error('DATABASE_URL is required');
|
|
16
|
+
|
|
17
|
+
const query = sql<{ id: number; total: string }>`
|
|
18
|
+
SELECT ${42}::integer AS id, count(*) AS total FROM pg_catalog.pg_class
|
|
19
|
+
`;
|
|
20
|
+
const description = await describeQuery({ connection, query });
|
|
21
|
+
// description.columns reports number/string read types; nullability remains unknown.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For an application, pass the same query object that will go to `db.query`. Use representative inputs when calling its query-building
|
|
25
|
+
function. Keep authorization policy centralized inside that builder or its fragments.
|
|
26
|
+
|
|
27
|
+
`query` accepts a SQL string or an object with a `text` property, including a Tenon `SqlQuery`.
|
|
28
|
+
For SQL strings, use PostgreSQL placeholders such as `$1`. PostgreSQL infers their types from SQL
|
|
29
|
+
context; `SELECT $1::integer AS id` can be described without supplying a value. Ordinary Tenon
|
|
30
|
+
templates bind values with `${value}`.
|
|
31
|
+
|
|
32
|
+
Inspection does not read the type argument of `sql<Row>`, validate `query.values`, or compare an
|
|
33
|
+
annotation with the result. A successful description is useful schema/type evidence, not proof
|
|
34
|
+
that the query will execute or do the intended job.
|
|
35
|
+
|
|
36
|
+
## Match the application's context
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const description = await describeQuery({
|
|
40
|
+
connection,
|
|
41
|
+
query,
|
|
42
|
+
session: { searchPath: 'mail,public', timeZone: 'UTC' },
|
|
43
|
+
timeouts: { connectMs: 3000, statementMs: 5000, lockMs: 1000 },
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Reuse the application's connection URI and the source of its session settings. All overrides are
|
|
48
|
+
optional. Connection URI startup options are preserved. `session.role` can assume a role when the
|
|
49
|
+
connecting account has permission; omitted settings use the new connection's defaults. `SET ROLE`
|
|
50
|
+
does not load that role's login defaults. Unknown options and invalid settings fail explicitly.
|
|
51
|
+
|
|
52
|
+
The result's `context` reports the database, login/effective roles, configured and resolved search
|
|
53
|
+
paths, timezone, and effective server timeouts. Compare these with the execution connection.
|
|
54
|
+
Inspection reports `mode: 'separate-session'` and `readMappings: 'postgres'`. SQL NULL array
|
|
55
|
+
elements are represented as JavaScript null.
|
|
56
|
+
|
|
57
|
+
The default connection/authentication limit is 3 seconds. Every server statement, including
|
|
58
|
+
metadata reads, has a 5-second limit, and each lock wait a 1-second limit. Cleanup is bounded to
|
|
59
|
+
1 second. Timeout overrides are positive integer milliseconds; URI settings cannot disable these
|
|
60
|
+
bounds. Limits apply to individual operations, not one overall deadline.
|
|
61
|
+
|
|
62
|
+
The API owns and closes its connection. It does not borrow an application client or a Tenon transaction.
|
|
63
|
+
Temporary tables, uncommitted changes, transaction-local settings, custom parsers, and other
|
|
64
|
+
pool-only state are not inherited. `PGOPTIONS` environment settings are not automatically copied;
|
|
65
|
+
use URI startup options or explicit session values. Standard read mappings describe Postgres.js
|
|
66
|
+
values, so custom parser behavior needs a deliberate application decision.
|
|
67
|
+
|
|
68
|
+
## Read the result
|
|
69
|
+
|
|
70
|
+
| Field | Meaning |
|
|
71
|
+
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `parameters` | Parameter type OIDs, qualified PostgreSQL type names, and Postgres.js read representations. These are not accepted JavaScript input contracts. |
|
|
73
|
+
| `columns` | Ordered output names, PostgreSQL types, Postgres.js read representations, and source-column metadata where available. |
|
|
74
|
+
| `columns[].nullability` | Always `"unknown"`. A source column's `notNull` constraint does not establish result nullability. |
|
|
75
|
+
| `rowType` | A conservative `DescribedRow` TypeScript declaration. Fields include null; JSON/unmapped types stay unknown. Null when there are no output columns or names are duplicated. |
|
|
76
|
+
| `issues` | Findings with stable `code` fields, human-readable messages, and relevant column/type details. |
|
|
77
|
+
| `context` | The separate inspection session's effective settings and read mapping profile. |
|
|
78
|
+
|
|
79
|
+
Use issue codes for tooling; message text is descriptive:
|
|
80
|
+
|
|
81
|
+
- `nullability-unknown`: result nullability has not been established.
|
|
82
|
+
- `shape-unknown`: listed columns have unknown read types; JSON object structure is not inferred.
|
|
83
|
+
- `unmapped-type`: the PostgreSQL type/OID lacks a verified Postgres.js mapping.
|
|
84
|
+
- `duplicate-column`: repeated names would overwrite earlier values in object rows.
|
|
85
|
+
|
|
86
|
+
Source `notNull` remains informational. Outer joins, grouping, and expressions can introduce nulls.
|
|
87
|
+
An empty result set supplies no runtime samples. Unknown mappings and duplicate names are findings
|
|
88
|
+
to handle explicitly, not a successful verification of a handwritten result type.
|
|
89
|
+
|
|
90
|
+
## CLI
|
|
91
|
+
|
|
92
|
+
Save the statement in `query.sql`, using `$1`, `$2`, and so on for parameters:
|
|
93
|
+
|
|
94
|
+
```sql
|
|
95
|
+
SELECT $1::integer AS id, count(*) AS total FROM pg_catalog.pg_class;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Supply `DATABASE_URL` through the application's normal environment setup:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
npx tenon describe query.sql
|
|
102
|
+
npx tenon describe query.sql --search-path mail,public --time-zone UTC
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The CLI prints the description as JSON. Options are `--role`, `--search-path`, `--time-zone`,
|
|
106
|
+
`--connect-timeout-ms`, `--statement-timeout-ms`, and `--lock-timeout-ms`. Migration/generation
|
|
107
|
+
options such as `--namespace` and `--check` are rejected. No environment files are loaded.
|
|
108
|
+
|
|
109
|
+
Exit code 0 means PostgreSQL described the statement, even if the report contains unresolved
|
|
110
|
+
issues. Exit code 1 means input, connection, timeout, or PostgreSQL analysis failed. No query values
|
|
111
|
+
are bound and no inspected statement is executed, including writes and locking statements.
|
|
112
|
+
|
|
113
|
+
## Keep execution assertions
|
|
114
|
+
|
|
115
|
+
For each meaningful query-builder variant, describe the finished SQL and run it through the
|
|
116
|
+
application's actual Tenon/Postgres.js connection against its test database. Assert complete relevant rows
|
|
117
|
+
and effects: nulls, JSON contents, precision, ordering, empty results, ownership, and rollback.
|
|
118
|
+
Include absent relationships, pagination ties, and empty aggregates where relevant. Keep those
|
|
119
|
+
tests alongside the query and run the application's normal TypeScript compiler.
|
|
120
|
+
|
|
121
|
+
Description can succeed for an invalid bound value, a write that violates a constraint, or an
|
|
122
|
+
operation the runtime role may not execute. Execution tests establish behavior for the exercised
|
|
123
|
+
inputs; they are not a proof for every possible input. Inspecting one variant does not cover
|
|
124
|
+
other branches or detect stale TypeScript annotations elsewhere in the application.
|
|
125
|
+
|
|
126
|
+
Describe does not bind runtime values. Cast standalone parameter projections, for example
|
|
127
|
+
`SELECT $1::boolean AS enabled`, so SQL establishes their types. Execution can otherwise infer a
|
|
128
|
+
type from a JavaScript value that an inspection without values cannot establish.
|