@kequtech/tenon-orm 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -2
- package/README.md +84 -51
- package/dist/cli.js +6 -22
- package/dist/database.js +5 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.js +26 -1
- package/dist/generation/output.js +2 -4
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/docs/adoption-guide.md +88 -153
- package/docs/agent-guide.md +91 -172
- package/docs/connections.md +185 -0
- package/docs/human-guide.md +132 -176
- package/docs/migrations.md +133 -0
- package/docs/mutations.md +104 -67
- package/docs/query-checking.md +119 -120
- package/docs/query-inspection.md +78 -74
- package/docs/reference.md +222 -474
- package/docs/schema-types.md +263 -0
- package/docs/sql.md +212 -0
- package/package.json +1 -1
package/docs/query-checking.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Checking query types
|
|
2
2
|
|
|
3
|
-
`tenon check` compares
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
`tenon check` compares Tenon SQL in TypeScript source with declared input/result types and PostgreSQL
|
|
4
|
+
metadata. It can catch a missing result property, an int8 result declared as number, or an unsupported
|
|
5
|
+
bound input. It is optional; normal execution still trusts `sql<Row>` annotations.
|
|
6
|
+
|
|
7
|
+
The checker reads source without importing the application, evaluating input expressions, binding
|
|
8
|
+
values, or executing application SQL. PostgreSQL still parses/analyzes each recovered statement,
|
|
9
|
+
which can take locks. Use a representative migrated development/test database.
|
|
10
|
+
|
|
11
|
+
## Run a check
|
|
12
|
+
|
|
13
|
+
Set `DATABASE_URL`, then select a project, file, or one query:
|
|
7
14
|
|
|
8
15
|
```sh
|
|
9
16
|
npx tenon check
|
|
@@ -12,18 +19,21 @@ npx tenon check src/users.ts:23
|
|
|
12
19
|
npx tenon check src/users.ts:23 --project tsconfig.json --json
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
A one-based line selects the complete query containing it, including multiline templates and their
|
|
23
|
+
variable declaration line. A line with no query or multiple queries fails. A fragment alone is not
|
|
24
|
+
a complete query target.
|
|
25
|
+
|
|
26
|
+
The checker discovers `tsconfig.json` from the target's directory or current working directory.
|
|
27
|
+
Use `--project` for another config or project directory; targets must belong to that project.
|
|
28
|
+
It uses the bundled TypeScript 6 compiler and the project's imports, aliases, indexed access,
|
|
29
|
+
`Pick`, unions, and path mappings. `strictNullChecks` is required. Invalid configuration and compiler
|
|
30
|
+
errors in selected files fail the report.
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
indexed access, Pick, unions, and path mappings. strictNullChecks is required. Compiler errors in
|
|
24
|
-
selected source files and invalid compiler configuration fail the report.
|
|
32
|
+
Match runtime role, search path, timezone, and codecs. Session/timeout flags are the same as
|
|
33
|
+
[Describe](query-inspection.md#cli). CLI checks use default codecs; use the programmatic API for
|
|
34
|
+
a custom registry.
|
|
25
35
|
|
|
26
|
-
##
|
|
36
|
+
## What gets checked
|
|
27
37
|
|
|
28
38
|
```ts
|
|
29
39
|
import { sql } from '@kequtech/tenon-orm';
|
|
@@ -35,53 +45,45 @@ export function userQuery(id: number) {
|
|
|
35
45
|
}
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
Recognition follows imported symbols
|
|
39
|
-
aliases.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
PostgreSQL Describe parses/analyzes each complete recovered variant and returns parameter OIDs and
|
|
43
|
-
ordered result metadata. It can take analysis locks. Run against the intended schema, database role,
|
|
44
|
-
and search path; a representative development/test database is appropriate. Session and timeout
|
|
45
|
-
flags are the same as [Describe](query-inspection.md#cli): --role, --search-path, --time-zone,
|
|
46
|
-
--connect-timeout-ms, --statement-timeout-ms, and --lock-timeout-ms. DATABASE_URL is required by the CLI.
|
|
47
|
-
|
|
48
|
-
Findings have three kinds:
|
|
49
|
-
|
|
50
|
-
| Kind | Meaning | Exit status |
|
|
51
|
-
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
|
52
|
-
| mismatch | Definite contradiction, such as a missing required result property, int8 declared as number, incompatible array elements, invalid SQL, or an invalid driver input type | Failure |
|
|
53
|
-
| unchecked | SQL structure cannot be recovered, a composition limit is exceeded, or inspection cannot complete | Failure |
|
|
54
|
-
| unproven | Available evidence cannot prove or disprove the declared type | Does not fail |
|
|
55
|
-
|
|
56
|
-
Declared types receive the benefit of the doubt when evidence is inconclusive. SQL result nullability,
|
|
57
|
-
JSON members, branded/literal refinements, and unfamiliar custom decoder types remain unproven. A
|
|
58
|
-
narrow declaration is not rejected merely because Describe supplies a broader representation. Each
|
|
59
|
-
SQL variant must fit at least one member of a declared result union. Additional selected columns are
|
|
60
|
-
allowed by structural result types; missing required properties fail.
|
|
61
|
-
|
|
62
|
-
Input and read contracts are separate: int8 accepts exact text, bigint, or an explicit encoder and decodes
|
|
63
|
-
as exact strings by default. Plain bigint, Date, and supported native/polyfilled Temporal inputs use
|
|
64
|
-
OID 0 and server inference, just as strings and numbers do. Boolean, bytes, and recoverable pgclear
|
|
65
|
-
json/param calls supply the same explicit OID hints as execution. Unions with different hints produce
|
|
66
|
-
variants. Accepted input types do not prove server ranges, valid Date values, or textual formats.
|
|
67
|
-
Plain objects, arrays, and undefined contradict pgclear's bind API; use explicit parameters or omission. A declared opaque pgclear Parameter is trusted, with its OID
|
|
68
|
-
reported as unproven. Describe then uses server inference; dependent result comparisons and inference
|
|
69
|
-
errors are unproven rather than contradictions. This is a stated limitation, not validation of the
|
|
70
|
-
unknown encoder.
|
|
48
|
+
Recognition follows Tenon's imported symbols through aliases, namespaces, barrel re-exports, and
|
|
49
|
+
const aliases. Unrelated functions named `sql` are ignored. Fixed-text `unsafeSql` calls with fixed
|
|
50
|
+
values lists are also supported. Each recoverable SQL variant goes through PostgreSQL Describe;
|
|
51
|
+
parameter OIDs and result columns are compared with the declared contracts.
|
|
71
52
|
|
|
72
|
-
|
|
53
|
+
| Finding | Meaning | Fails the command? |
|
|
54
|
+
| ----------- | --------------------------------------------------------------------------- | ------------------ |
|
|
55
|
+
| `mismatch` | A definite contradiction, invalid SQL, or invalid input type | Yes |
|
|
56
|
+
| `unchecked` | SQL cannot be recovered, a limit is exceeded, or inspection cannot complete | Yes |
|
|
57
|
+
| `unproven` | Metadata cannot prove or disprove the declaration | No |
|
|
58
|
+
|
|
59
|
+
Missing required result properties fail; extra selected columns are allowed by structural types.
|
|
60
|
+
Each variant must fit at least one member of a declared result union. Nullability, JSON members,
|
|
61
|
+
literal/branded refinements, and unfamiliar decoder types may remain unproven. A broader metadata
|
|
62
|
+
type alone does not reject a narrower application declaration.
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
Input and output contracts are separate. For example, an int8 input accepts exact text, bigint, or
|
|
65
|
+
an explicit encoder; its default output is a string. Strings, numbers, bigint, Date, and supported
|
|
66
|
+
Temporal inputs use OID 0/server inference. Booleans, bytes, and recoverable `json`/`param` calls
|
|
67
|
+
supply the same OID hints as execution. Unions with different hints create variants. Input types do
|
|
68
|
+
not prove value ranges, valid Dates, or textual formats.
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
A plain object, array, or undefined input contradicts the driver's bind API. An opaque `Parameter`
|
|
71
|
+
is trusted, with its unknown OID reported as unproven. Describe uses server inference in that case;
|
|
72
|
+
dependent result comparisons and inference errors remain unproven. This does not validate the encoder.
|
|
73
|
+
|
|
74
|
+
## Responding to findings
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
- **Mismatch:** correct the SQL or declared contract, then recheck. For int8 output, use string under
|
|
77
|
+
default codecs; for arrays/objects as inputs, choose the documented encoder.
|
|
78
|
+
- **Unchecked:** read the reason. An explicit shape or fixed composition may make it recoverable.
|
|
79
|
+
Runtime-dependent SQL needs database tests. The command fails while unchecked sites remain in
|
|
80
|
+
the selected scope; narrow the target to check supported sites separately and retain visibility
|
|
81
|
+
of the remaining coverage.
|
|
82
|
+
- **Unproven:** retain the declaration when justified, with validation/tests for what metadata cannot
|
|
83
|
+
establish. A note alone does not fail the command.
|
|
84
|
+
|
|
85
|
+
For example, generated update fields are optional and can disappear from SQL. If application
|
|
86
|
+
validation always supplies a boolean, declare that required contract:
|
|
85
87
|
|
|
86
88
|
```ts
|
|
87
89
|
type ActivePatch = { active: boolean };
|
|
@@ -92,19 +94,14 @@ function setActive(id: number, patch: ActivePatch) {
|
|
|
92
94
|
}
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
hide this difference. Narrow a CLI target to check supported sites independently; report the
|
|
98
|
-
remaining unchecked sites in the project's validation workflow.
|
|
99
|
-
|
|
100
|
-
Exit zero means no definite mismatch or unchecked site was reported in the selected scope. It does
|
|
101
|
-
not mean JSON members, nullability, cardinality, or every runtime branch was verified. No query sites
|
|
102
|
-
also fails, so an empty or incorrectly selected file cannot appear to pass.
|
|
97
|
+
If omission is intended, keep optional fields and test the SQL/effects. An explicit column list does
|
|
98
|
+
not make optional fields required. Avoid assertions that merely hide this distinction.
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
Exit 0 means no mismatch or unchecked site in the selected scope. It does not establish JSON shape,
|
|
101
|
+
nullability, cardinality, or every runtime branch. No query sites also fails. Independent queries
|
|
102
|
+
continue after an unsupported site, and a recoverable branch can expose a mismatch even when another
|
|
103
|
+
branch is unchecked. Diagnostics include locations, codes, parameter/column identifiers, expected
|
|
104
|
+
and observed types, and variant numbers. `--json` returns the structured report.
|
|
108
105
|
|
|
109
106
|
## Supported composition
|
|
110
107
|
|
|
@@ -116,21 +113,22 @@ const query = sql<Summary>`SELECT id, email FROM public.users
|
|
|
116
113
|
```
|
|
117
114
|
|
|
118
115
|
The checker follows inline fragments and const bindings with recoverable initializers, fixed
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
identifiers, fixed join lists/separators, and ternaries with recoverable branches. Both branches are
|
|
117
|
+
checked without evaluating the condition.
|
|
118
|
+
|
|
119
|
+
Reusing a conditional fragment preserves its branch choice. Separate ternaries using the same const
|
|
120
|
+
condition or an unwritten function parameter also share that choice, including simple negation.
|
|
121
|
+
Repeated mutable guards with unresolved correlation are unchecked. General control-flow reachability
|
|
122
|
+
is not inferred.
|
|
123
|
+
|
|
124
|
+
Limits are 16 complete variants per query, including parameter OID alternatives, and 32 levels of
|
|
125
|
+
composition references. Arbitrary fragment factories, function-body reconstruction, loops, dynamic
|
|
126
|
+
identifiers, mutable/escaping fragment lists, and runtime joins are unsupported. These can be valid
|
|
127
|
+
runtime SQL, but need database tests. Unknown source is never replaced with invented SQL.
|
|
130
128
|
|
|
131
129
|
## Object expansion
|
|
132
130
|
|
|
133
|
-
For checking, insertValues and assignments
|
|
131
|
+
For checking, `insertValues` and `assignments` need an explicit finite shape type:
|
|
134
132
|
|
|
135
133
|
```ts
|
|
136
134
|
type NewUser = { email: string; active: boolean };
|
|
@@ -141,24 +139,38 @@ function createUser(input: NewUser) {
|
|
|
141
139
|
}
|
|
142
140
|
```
|
|
143
141
|
|
|
144
|
-
The
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
as unproven. Prefer an explicit list when passing application objects.
|
|
142
|
+
The shape is a trusted expansion contract; the checker does not read the object. Prefer an explicit
|
|
143
|
+
column list so runtime and checking select the same keys/order. Without one, the declared shape is
|
|
144
|
+
assumed to describe exactly the expanded fields. Structural typing cannot prove the absence of
|
|
145
|
+
extra own keys or their order, so that assumption is unproven.
|
|
149
146
|
|
|
150
|
-
Selected fields must be required and exclude undefined. Optional presence changes SQL through
|
|
151
|
-
omission or DEFAULT
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
Selected fields must be required and exclude undefined. Optional presence changes the SQL through
|
|
148
|
+
omission or DEFAULT and is unchecked. Unselected optional properties are fine. Index signatures,
|
|
149
|
+
unresolved generic key sets, shape unions, and unknown shapes are unsupported. Fixed literal batches
|
|
150
|
+
are supported; unknown batch lengths are not. Runtime helpers still accept inferred shapes and
|
|
151
|
+
optional fields.
|
|
155
152
|
|
|
156
153
|
## Programmatic use and custom codecs
|
|
157
154
|
|
|
158
|
-
The checking API has
|
|
155
|
+
The checking API has a separate entry point, so ordinary queries do not load TypeScript:
|
|
159
156
|
|
|
160
157
|
```ts
|
|
161
158
|
import { checkQueries } from '@kequtech/tenon-orm/check';
|
|
159
|
+
|
|
160
|
+
const report = await checkQueries({
|
|
161
|
+
connection,
|
|
162
|
+
project: 'tsconfig.json',
|
|
163
|
+
target: 'src/users.ts',
|
|
164
|
+
});
|
|
165
|
+
if (!report.ok) process.exitCode = 1;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The report has `ok`, `project`, `queries`, `diagnostics`, and available inspection `context`. Options
|
|
169
|
+
also accept `session`, `timeouts`, `codecs`, and `readTypes`. `connection` is a URI.
|
|
170
|
+
|
|
171
|
+
For a custom codec, pass the same registry as runtime queries. A text codec may declare its output:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
162
174
|
import { defaultCodecs } from '@kequtech/tenon-orm';
|
|
163
175
|
|
|
164
176
|
const codecs = defaultCodecs.with({
|
|
@@ -169,32 +181,19 @@ const codecs = defaultCodecs.with({
|
|
|
169
181
|
typescript: 'import("./src/types.js").DecodedInteger',
|
|
170
182
|
},
|
|
171
183
|
});
|
|
172
|
-
const report = await checkQueries({
|
|
173
|
-
connection,
|
|
174
|
-
project: 'tsconfig.json',
|
|
175
|
-
target: 'src/users.ts:23',
|
|
176
|
-
codecs,
|
|
177
|
-
});
|
|
178
|
-
if (!report.ok) process.exitCode = 1;
|
|
184
|
+
const report = await checkQueries({ connection, project: 'tsconfig.json', codecs });
|
|
179
185
|
```
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
script calling this API. The application chooses that script's configuration; the checker does not
|
|
195
|
-
import configuration or an application entry point automatically.
|
|
196
|
-
|
|
197
|
-
pgclear owns execution, Describe, value conversion, and codec representation metadata. Tenon owns
|
|
198
|
-
schema queries and declaration rendering as well as source discovery, composition recovery,
|
|
199
|
-
TypeScript comparison, diagnostics, and file output.
|
|
200
|
-
`tenon generate --check` remains a separate generated-file freshness check.
|
|
187
|
+
Here `src/types.ts` must export `DecodedInteger = { raw: string }`. Registry metadata describes
|
|
188
|
+
non-NULL output, including derived arrays. Type expressions compile in the application's project
|
|
189
|
+
without importing/executing its modules; referenced dependencies/types must be available.
|
|
190
|
+
|
|
191
|
+
Use `readTypes: { 23: 'import("./src/types.js").DecodedInteger' }` only for a decoder with no known
|
|
192
|
+
registry representation. It cannot replace known metadata or claim JSON members for default JSON
|
|
193
|
+
decoding. Expressions resolve from the tsconfig directory. A codec declaration is trusted output
|
|
194
|
+
metadata, not data validation.
|
|
195
|
+
|
|
196
|
+
Custom configuration belongs in an application-owned checking script; the checker does not load
|
|
197
|
+
application configuration automatically. Generated-file freshness remains a separate
|
|
198
|
+
`db.generateTypes({ out, check: true })` operation. For behavior tests, see
|
|
199
|
+
[query inspection and testing](query-inspection.md).
|
package/docs/query-inspection.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Inspecting and testing queries
|
|
2
2
|
|
|
3
|
-
`describeQuery` asks PostgreSQL for a
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`describeQuery` asks PostgreSQL for a finished statement's parameter and result metadata. It does
|
|
4
|
+
not bind values or execute the statement. Use it to inspect SQL and to support database tests. To
|
|
5
|
+
compare TypeScript source annotations instead, use [query checking](query-checking.md).
|
|
6
6
|
|
|
7
7
|
## Inspect completed SQL
|
|
8
8
|
|
|
@@ -18,16 +18,19 @@ const query = sql<{ id: number; total: string }>`
|
|
|
18
18
|
const description = await describeQuery({ connection, query });
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`query` accepts SQL text or an object
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
take locks.
|
|
21
|
+
Pass the same completed query to inspection and execution. `query` accepts SQL text or an object
|
|
22
|
+
with `text`, including a Tenon `SqlQuery`. Raw SQL uses `$1` placeholders; templates use `${value}`.
|
|
23
|
+
Inspection checks neither `query.values` nor its `sql<Row>` annotation.
|
|
24
|
+
|
|
25
|
+
Cast standalone parameters so PostgreSQL can infer their types without values. Alternatively pass
|
|
26
|
+
`query: { text, parameterTypes: [oid, ...] }` for explicit Describe hints. PostgreSQL still parses
|
|
27
|
+
and analyzes the statement, which can take locks.
|
|
28
28
|
|
|
29
29
|
## Session and timeouts
|
|
30
30
|
|
|
31
|
+
Inspection opens and closes its own short-lived session. `connection` must be a PostgreSQL URI;
|
|
32
|
+
it does not inherit a runtime pool or transaction:
|
|
33
|
+
|
|
31
34
|
```ts
|
|
32
35
|
const description = await describeQuery({
|
|
33
36
|
connection,
|
|
@@ -37,48 +40,50 @@ const description = await describeQuery({
|
|
|
37
40
|
});
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
Match runtime settings. Optional `session.role` uses SET ROLE when permitted; it does not load that
|
|
44
|
+
role's login defaults. Omitted settings use the inspection connection's defaults. Explicit undefined
|
|
45
|
+
behaves like omission. Unknown keys and invalid settings reject.
|
|
43
46
|
|
|
44
47
|
`context` reports login/effective roles, configured/resolved search paths, timezone, database, and
|
|
45
|
-
effective statement/lock timeouts.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
| `
|
|
48
|
+
effective statement/lock timeouts. Mode is `separate-session`, read mapping profile `pgclear`.
|
|
49
|
+
Temporary tables, uncommitted changes, and transaction-local state are not inherited. PG environment
|
|
50
|
+
variables, pgpass, service files, and `.env` are not loaded.
|
|
51
|
+
|
|
52
|
+
| Timeout | Default | Scope |
|
|
53
|
+
| ------------- | -------- | ----------------------------- |
|
|
54
|
+
| `connectMs` | 3,000 ms | Connection and authentication |
|
|
55
|
+
| `statementMs` | 5,000 ms | Each server statement |
|
|
56
|
+
| `lockMs` | 1,000 ms | Lock wait |
|
|
57
|
+
|
|
58
|
+
Overrides are positive integer milliseconds up to 2,147,483,647. These are operation bounds, not a
|
|
59
|
+
single overall deadline. URI settings cannot disable statement/lock bounds. pg-specific
|
|
60
|
+
`query_timeout` and `options` URI parameters are unsupported; use the options above.
|
|
61
|
+
|
|
62
|
+
## Read the report
|
|
63
|
+
|
|
64
|
+
| Field | Meaning |
|
|
65
|
+
| ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
66
|
+
| `parameters` | OIDs, qualified PostgreSQL names, and read representations; these are not input contracts |
|
|
67
|
+
| `columns` | Ordered names, PostgreSQL types, read representations, and available source metadata |
|
|
68
|
+
| `columns[].nullability` | Always `"unknown"`; source `notNull` is informational |
|
|
69
|
+
| `rowType` | Conservative `DescribedRow` declaration, or null for no columns/duplicate names |
|
|
70
|
+
| `issues` | Findings with stable `code` and descriptive messages |
|
|
71
|
+
| `context` | Effective inspection-session settings |
|
|
72
|
+
|
|
73
|
+
Known read types in `rowType` include null; unknown already includes it. A source column's NOT NULL
|
|
74
|
+
constraint cannot prove result nullability through outer joins, expressions, or grouping. JSON
|
|
75
|
+
members remain unknown.
|
|
76
|
+
|
|
77
|
+
| Finding code | Meaning |
|
|
78
|
+
| --------------------- | ----------------------------------------------------- |
|
|
79
|
+
| `nullability-unknown` | Result nullability is unresolved |
|
|
80
|
+
| `shape-unknown` | Listed columns have unknown shapes/read types |
|
|
81
|
+
| `unmapped-type` | A custom decoder has no known representation |
|
|
82
|
+
| `duplicate-column` | Repeated result names would fail object-row execution |
|
|
77
83
|
|
|
78
84
|
## Custom codecs
|
|
79
85
|
|
|
80
|
-
Pass the runtime registry
|
|
81
|
-
default codecs; the selected registry determines the reported application read representations.
|
|
86
|
+
Pass the runtime registry as `codecs` to report its read representations; omission uses defaults:
|
|
82
87
|
|
|
83
88
|
```ts
|
|
84
89
|
import { builtin, defaultCodecs } from '@kequtech/tenon-orm';
|
|
@@ -87,21 +92,21 @@ const codecs = defaultCodecs.with(builtin.jsonbText);
|
|
|
87
92
|
const description = await describeQuery({ connection, query, codecs });
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
not
|
|
98
|
-
|
|
95
|
+
That registry reports JSONB output as string. Scalar overrides affect derived built-in arrays;
|
|
96
|
+
explicit array overrides win. Custom `text.typescript` expressions appear in the conservative row
|
|
97
|
+
declaration and are shared with generation/source checking. Without known metadata, a custom
|
|
98
|
+
decoder remains unknown even for a familiar scalar/enum/domain OID. Catalog base types do not
|
|
99
|
+
replace the wire OID's decoder contract.
|
|
100
|
+
|
|
101
|
+
Inspection's own catalog/context queries use default decoding. Reported parameter read types are
|
|
102
|
+
not encoding rules: exact text can be a valid int8 input while its output also uses text by default.
|
|
103
|
+
See [schema mappings and codecs](schema-types.md#custom-codecs).
|
|
99
104
|
|
|
100
105
|
## Test the same statement
|
|
101
106
|
|
|
102
|
-
For
|
|
103
|
-
|
|
104
|
-
`{ id: 2, email: 'lin@example.test', active: false }
|
|
107
|
+
Use a dedicated test database. For `public.users` from [Getting started](human-guide.md), assume a
|
|
108
|
+
fixture contains `{ id: 1, email: 'ada@example.test', active: true }` and
|
|
109
|
+
`{ id: 2, email: 'lin@example.test', active: false }`. Here `db` and `connection` select that database:
|
|
105
110
|
|
|
106
111
|
```ts
|
|
107
112
|
import assert from 'node:assert/strict';
|
|
@@ -133,15 +138,15 @@ for (const activeOnly of [false, true]) {
|
|
|
133
138
|
}
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
rows
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
Describe establishes metadata; execution tests establish behavior. Describe may succeed even when
|
|
142
|
+
bound values are invalid, constraints would reject a write, or the role lacks execution permission.
|
|
143
|
+
Test actual rows, write effects, authorization, JSON validation, and rollback. Exercise meaningful
|
|
144
|
+
composition variants and keep unexpected findings visible. Do not test query behavior by matching
|
|
145
|
+
words in documentation or SQL strings.
|
|
141
146
|
|
|
142
147
|
## CLI
|
|
143
148
|
|
|
144
|
-
Save SQL
|
|
149
|
+
Save raw SQL in `query.sql`:
|
|
145
150
|
|
|
146
151
|
```sql
|
|
147
152
|
SELECT $1::integer AS id, count(*) AS total FROM pg_catalog.pg_class;
|
|
@@ -152,12 +157,11 @@ npx tenon describe query.sql
|
|
|
152
157
|
npx tenon describe query.sql --search-path app,public --time-zone UTC
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
The CLI
|
|
156
|
-
`--time-zone
|
|
157
|
-
`--lock-timeout-ms`.
|
|
158
|
-
|
|
159
|
-
Exit 0 means description succeeded, including reports with unresolved findings. Exit 1 means input,
|
|
160
|
-
connection, timeout, or PostgreSQL analysis failed. The inspected statement is never executed.
|
|
160
|
+
The CLI reads `DATABASE_URL` and prints JSON using default codecs. Session flags are `--role`,
|
|
161
|
+
`--search-path`, and `--time-zone`; timeout flags are `--connect-timeout-ms`,
|
|
162
|
+
`--statement-timeout-ms`, and `--lock-timeout-ms`. Unsupported flags, including `--namespace` and
|
|
163
|
+
`--check`, reject.
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
Exit 0 means inspection succeeded, even with unresolved findings. Input, connection, timeout, or
|
|
166
|
+
PostgreSQL analysis failures exit 1. The inspected statement is never executed. Generated-file
|
|
167
|
+
freshness uses `db.generateTypes({ out, check: true })`, a separate operation.
|