@kequtech/tenon-orm 0.2.1 → 0.2.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 +15 -0
- package/README.md +27 -70
- package/dist/connection.d.ts +8 -2
- package/dist/connection.js +13 -0
- package/dist/database.js +34 -8
- package/dist/driver/listener.d.ts +3 -0
- package/dist/driver/listener.js +63 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/inspection/cli.js +3 -3
- package/dist/inspection/options.d.ts +3 -3
- package/dist/inspection/options.js +9 -1
- package/dist/inspection/types.d.ts +8 -8
- package/dist/query-options.js +2 -2
- package/dist/types.d.ts +5 -1
- package/docs/adoption-guide.md +105 -121
- package/docs/agent-guide.md +114 -223
- package/docs/human-guide.md +35 -47
- package/docs/mutations.md +57 -78
- package/docs/query-inspection.md +99 -89
- package/docs/reference.md +266 -319
- package/package.json +2 -3
package/docs/mutations.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# Mutation cookbook
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[reference](reference.md#object-based-mutation-fragments)
|
|
5
|
-
|
|
3
|
+
Use `sql.insertValues` and `sql.assignments` for object fields while keeping SQL structure visible.
|
|
4
|
+
The [reference](reference.md#object-based-mutation-fragments) defines the complete contract.
|
|
5
|
+
Examples assume an existing `db` and this table, with generated `Rows` from the same schema:
|
|
6
|
+
|
|
7
|
+
```sql
|
|
8
|
+
CREATE TABLE profiles (
|
|
9
|
+
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
10
|
+
name text NOT NULL UNIQUE,
|
|
11
|
+
bio text DEFAULT 'unwritten',
|
|
12
|
+
score integer NOT NULL DEFAULT 0,
|
|
13
|
+
revision integer NOT NULL DEFAULT 0
|
|
14
|
+
);
|
|
15
|
+
```
|
|
6
16
|
|
|
7
17
|
## Defaults, undefined, and null in batches
|
|
8
18
|
|
|
@@ -12,36 +22,16 @@ await db.query(
|
|
|
12
22
|
{ name: 'Ada', bio: null },
|
|
13
23
|
{ name: 'Lin', score: 4, bio: undefined },
|
|
14
24
|
])} RETURNING id`,
|
|
25
|
+
{ returns: 'many' },
|
|
15
26
|
);
|
|
16
27
|
```
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Use a column list to select and order fields in runtime-built rows:
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import type { Rows } from './generated-schema.js';
|
|
26
|
-
|
|
27
|
-
const rows: Rows.Insert['profiles'][] = buildRows();
|
|
28
|
-
await db.query(sql`INSERT INTO profiles ${sql.insertValues(rows, ['name', 'bio', 'score'])}`);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
This list selects and orders fields; unlisted fields are ignored. It does not assert that the
|
|
32
|
-
entire input object contains only allowed keys. Keep narrower application input validation.
|
|
33
|
-
|
|
34
|
-
`tenon generate` also provides `Rows.Update['profiles']`. Use `Pick` to limit a patch to fields
|
|
35
|
-
the caller may edit, for example `Pick<Rows.Update['profiles'], 'name' | 'bio'>`. Generated inputs
|
|
36
|
-
understand defaults, nullability, domains, and generated columns. They use write representations:
|
|
37
|
-
timestamps accept `Date | string`, bigint accepts `string | bigint`, and JSON accepts native JavaScript values through Tenon's exported `JSONValue` type.
|
|
38
|
-
See the [input-type reference](reference.md#insert-and-update-inputs) for the complete mapping.
|
|
29
|
+
Ada gets the score default and SQL null for bio. Lin gets score 4 and the bio default. Columns stay
|
|
30
|
+
aligned across heterogeneous rows. Empty batches fail; a single default-only object emits
|
|
31
|
+
`DEFAULT VALUES` and still depends on the table's required-column rules.
|
|
39
32
|
|
|
40
33
|
## Input types and runtime validation
|
|
41
34
|
|
|
42
|
-
Apply input types with an annotation, `satisfies`, or a helper generic argument. Generating the
|
|
43
|
-
file alone does not check every write. Narrow the fields each operation accepts:
|
|
44
|
-
|
|
45
35
|
```ts
|
|
46
36
|
import { sql, type QueryClient } from '@kequtech/tenon-orm';
|
|
47
37
|
import type { Rows } from './generated-schema.js';
|
|
@@ -49,46 +39,44 @@ import type { Rows } from './generated-schema.js';
|
|
|
49
39
|
type ProfileInput = Pick<Rows.Insert['profiles'], 'name' | 'score'>;
|
|
50
40
|
|
|
51
41
|
function createProfile(client: QueryClient, input: ProfileInput) {
|
|
52
|
-
return client.query(
|
|
53
|
-
|
|
42
|
+
return client.query(
|
|
43
|
+
sql<{ id: number }>`INSERT INTO profiles
|
|
44
|
+
${sql.insertValues(input, ['name', 'score'])} RETURNING id`,
|
|
45
|
+
{ returns: 'one' },
|
|
46
|
+
);
|
|
54
47
|
}
|
|
55
48
|
```
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
function and keep database constraints and application authorization in place.
|
|
50
|
+
Annotations, `satisfies`, or helper generic arguments apply generated input types; merely generating
|
|
51
|
+
the file does not check writes. Use `Pick` to narrow accepted fields and retain runtime validation.
|
|
52
|
+
Generated inputs describe defaults and driver representations, not authorization or constraints.
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
Selected columns choose and order fields, ignoring unlisted keys. They can discard a required
|
|
55
|
+
field even when the original object is valid:
|
|
64
56
|
|
|
65
57
|
```ts
|
|
66
|
-
const
|
|
67
|
-
const query = sql`INSERT INTO profiles ${sql.insertValues(
|
|
68
|
-
//
|
|
58
|
+
const input: Rows.Insert['profiles'] = { name: 'Ada' };
|
|
59
|
+
const query = sql`INSERT INTO profiles ${sql.insertValues(input, ['bio', 'score'])}`;
|
|
60
|
+
// Executing this fails: name was discarded and has no default.
|
|
69
61
|
```
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
## Patches mixed with SQL expressions
|
|
63
|
+
## Patches mixed with expressions
|
|
74
64
|
|
|
75
65
|
```ts
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
const patch: Pick<Rows.Update['profiles'], 'name' | 'bio'> = { bio: null };
|
|
67
|
+
await db.query(sql`UPDATE profiles SET ${sql.assignments(patch)}, revision = revision + 1
|
|
78
68
|
WHERE id = ${id}`);
|
|
79
69
|
```
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
`
|
|
71
|
+
Missing/undefined fields are omitted; null clears a nullable field. Put arithmetic and
|
|
72
|
+
`column = DEFAULT` in SQL. Helpers accept values, not SQL expression objects.
|
|
83
73
|
|
|
84
|
-
An empty patch
|
|
85
|
-
expression-only
|
|
74
|
+
An empty patch fails even if the surrounding SQL has other assignments. Branch when an
|
|
75
|
+
expression-only update is intended:
|
|
86
76
|
|
|
87
77
|
```ts
|
|
88
|
-
const patch: { name?: string; bio?: string | null } = readPatch();
|
|
89
78
|
if (patch.name !== undefined || patch.bio !== undefined) {
|
|
90
|
-
await db.query(sql`UPDATE profiles
|
|
91
|
-
SET ${sql.assignments(patch, ['name', 'bio'])}, revision = revision + 1
|
|
79
|
+
await db.query(sql`UPDATE profiles SET ${sql.assignments(patch)}, revision = revision + 1
|
|
92
80
|
WHERE id = ${id}`);
|
|
93
81
|
} else {
|
|
94
82
|
await db.query(sql`UPDATE profiles SET revision = revision + 1 WHERE id = ${id}`);
|
|
@@ -97,44 +85,37 @@ if (patch.name !== undefined || patch.bio !== undefined) {
|
|
|
97
85
|
|
|
98
86
|
## Preserve an omitted field during an upsert
|
|
99
87
|
|
|
100
|
-
|
|
88
|
+
Given validated `name`, `score`, and optional `bio`, choose conflict behavior explicitly:
|
|
101
89
|
|
|
102
90
|
```ts
|
|
103
|
-
if (
|
|
104
|
-
await db.query(sql`INSERT INTO
|
|
105
|
-
ON CONFLICT (
|
|
106
|
-
SET in_app = excluded.in_app, email = excluded.email`);
|
|
91
|
+
if (bio !== undefined) {
|
|
92
|
+
await db.query(sql`INSERT INTO profiles ${sql.insertValues({ name, score, bio })}
|
|
93
|
+
ON CONFLICT (name) DO UPDATE SET score = excluded.score, bio = excluded.bio`);
|
|
107
94
|
} else {
|
|
108
|
-
await db.query(sql`INSERT INTO
|
|
109
|
-
ON CONFLICT (
|
|
95
|
+
await db.query(sql`INSERT INTO profiles ${sql.insertValues({ name, score })}
|
|
96
|
+
ON CONFLICT (name) DO UPDATE SET score = excluded.score`);
|
|
110
97
|
}
|
|
111
98
|
```
|
|
112
99
|
|
|
113
|
-
The second query
|
|
114
|
-
|
|
100
|
+
The second query preserves an existing bio. Omitting an insert value uses its database default;
|
|
101
|
+
that alone does not decide which fields a conflict update overwrites.
|
|
115
102
|
|
|
116
|
-
##
|
|
103
|
+
## JSON and PostgreSQL arrays
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
Pass native JavaScript values; Postgres.js serializes them for the target type:
|
|
105
|
+
For a table with `payload jsonb` and `tags text[]`, native arrays work in either context:
|
|
120
106
|
|
|
121
107
|
```ts
|
|
122
|
-
const
|
|
123
|
-
await db.query(
|
|
124
|
-
sql`INSERT INTO deliveries ${sql.insertValues({
|
|
125
|
-
recipients_json: handles, // jsonb column
|
|
126
|
-
recipient_handles: handles, // text[] column
|
|
127
|
-
})}`,
|
|
128
|
-
);
|
|
108
|
+
const tags = ['ada', 'lin'];
|
|
109
|
+
await db.query(sql`INSERT INTO deliveries ${sql.insertValues({ payload: tags, tags })}`);
|
|
129
110
|
```
|
|
130
111
|
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
112
|
+
PostgreSQL establishes the target types and the driver serializes values. Pass native JSON rather
|
|
113
|
+
than `JSON.stringify` output, which would store a JSON string. Generated JSON writes accept Tenon's
|
|
114
|
+
`JSONValue` type, excluding SQL null for nonnullable columns. Use `'null'::jsonb` for JSON null.
|
|
134
115
|
|
|
135
116
|
## Batches in one transaction
|
|
136
117
|
|
|
137
|
-
|
|
118
|
+
For an array `rows` of generated profile inputs:
|
|
138
119
|
|
|
139
120
|
```ts
|
|
140
121
|
await db.transaction(async (tx) => {
|
|
@@ -145,8 +126,6 @@ await db.transaction(async (tx) => {
|
|
|
145
126
|
});
|
|
146
127
|
```
|
|
147
128
|
|
|
148
|
-
A failure rolls back all batches.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`at most 65533 bound parameters; received 65534`. Defaults consume no parameters. Account for the
|
|
152
|
-
selected columns and surrounding query when choosing a batch size; Tenon never splits queries automatically.
|
|
129
|
+
A failure rolls back all batches. Empty input makes no insert calls. Choose a batch size below the
|
|
130
|
+
query-wide 65,533-parameter limit, including surrounding parameters; default cells consume none.
|
|
131
|
+
Tenon does not split batches or choose transaction boundaries for you.
|
package/docs/query-inspection.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Inspect and test queries
|
|
2
2
|
|
|
3
|
-
`describeQuery` asks PostgreSQL
|
|
4
|
-
binding its values or executing it.
|
|
5
|
-
|
|
6
|
-
Its `postgres` driver installs with Tenon; no extra
|
|
7
|
-
consumer dependency or configuration is needed. Calling inspection opens a separate connection.
|
|
3
|
+
`describeQuery` asks PostgreSQL for a completed statement's parameter/result metadata without
|
|
4
|
+
binding its values or executing it. Inspection is included with Tenon and opens a separate,
|
|
5
|
+
short-lived connection. It works with SQL assembled from fragments and mutation helpers.
|
|
8
6
|
|
|
9
|
-
## Inspect
|
|
7
|
+
## Inspect completed SQL
|
|
10
8
|
|
|
11
9
|
```ts
|
|
12
10
|
import { describeQuery, sql } from '@kequtech/tenon-orm';
|
|
@@ -18,111 +16,123 @@ const query = sql<{ id: number; total: string }>`
|
|
|
18
16
|
SELECT ${42}::integer AS id, count(*) AS total FROM pg_catalog.pg_class
|
|
19
17
|
`;
|
|
20
18
|
const description = await describeQuery({ connection, query });
|
|
21
|
-
// description.columns reports number/string read types; nullability remains unknown.
|
|
22
19
|
```
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
`query` accepts SQL text or an object with `text`, including `SqlQuery`. Pass the same completed
|
|
22
|
+
query to inspection and execution. Strings use PostgreSQL `$1` placeholders; templates use
|
|
23
|
+
`${value}`. Cast standalone parameters so PostgreSQL can infer their types without runtime values.
|
|
24
|
+
Inspection does not read `sql<Row>`, check `query.values`, or compare an annotation with SQL.
|
|
26
25
|
|
|
27
|
-
|
|
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
|
|
26
|
+
## Session and timeouts
|
|
37
27
|
|
|
38
28
|
```ts
|
|
39
29
|
const description = await describeQuery({
|
|
40
30
|
connection,
|
|
41
31
|
query,
|
|
42
|
-
session: { searchPath: '
|
|
32
|
+
session: { searchPath: 'app,public', timeZone: 'UTC' },
|
|
43
33
|
timeouts: { connectMs: 3000, statementMs: 5000, lockMs: 1000 },
|
|
44
34
|
});
|
|
45
35
|
```
|
|
46
36
|
|
|
47
|
-
Reuse the
|
|
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
|
-
|
|
77
|
-
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
37
|
+
Reuse the runtime URI and session settings. Optional `session.role` uses SET ROLE when permitted;
|
|
38
|
+
it does not load that role's login defaults. Omitted settings use the inspection connection's
|
|
39
|
+
defaults. Explicit undefined behaves like omission. Unknown keys and invalid settings fail.
|
|
40
|
+
|
|
41
|
+
`context` reports login/effective roles, configured/resolved search paths, timezone, database, and
|
|
42
|
+
effective statement/lock timeouts. Its mode is `separate-session` and read mapping profile is
|
|
43
|
+
`postgres`. Custom parsers, temporary tables, uncommitted changes, and transaction-local settings
|
|
44
|
+
are not inherited. `PGOPTIONS` is not automatically copied; use URI startup options or session
|
|
45
|
+
settings. Array mappings include Tenon's SQL NULL correction.
|
|
46
|
+
|
|
47
|
+
Defaults are a 3-second connection/authentication timeout, a 5-second limit per server statement,
|
|
48
|
+
and a 1-second lock wait. Overrides are positive integer milliseconds, at most 2,147,483,647.
|
|
49
|
+
URI settings cannot disable the statement/lock bounds. Cleanup uses a 1-second driver shutdown
|
|
50
|
+
timeout. These are operation limits, not a single overall deadline.
|
|
51
|
+
|
|
52
|
+
## Result fields
|
|
53
|
+
|
|
54
|
+
| Field | Meaning |
|
|
55
|
+
| ----------------------- | ------------------------------------------------------------------------------------------ |
|
|
56
|
+
| `parameters` | OIDs, qualified PostgreSQL names, and read representations; not JavaScript input contracts |
|
|
57
|
+
| `columns` | Ordered names, PostgreSQL types, read representations, and source metadata where available |
|
|
58
|
+
| `columns[].nullability` | Always `"unknown"`; source `notNull` is informational |
|
|
59
|
+
| `rowType` | Conservative `DescribedRow` declaration, or null for no columns/duplicate names |
|
|
60
|
+
| `issues` | Findings with stable `code` fields and descriptive messages |
|
|
61
|
+
| `context` | Effective inspection-session settings |
|
|
62
|
+
|
|
63
|
+
Known read types in `rowType` include null; unknown types already include it. Source constraints
|
|
64
|
+
cannot establish result nullability through joins, expressions, or grouping. JSON structure stays
|
|
65
|
+
unknown. Handle findings by code:
|
|
66
|
+
|
|
67
|
+
| Code | Meaning |
|
|
68
|
+
| --------------------- | --------------------------------------------------------------- |
|
|
69
|
+
| `nullability-unknown` | Result nullability is unresolved |
|
|
70
|
+
| `shape-unknown` | Listed output columns have unknown shapes/read types |
|
|
71
|
+
| `unmapped-type` | A parameter or result type has no verified driver mapping |
|
|
72
|
+
| `duplicate-column` | Repeated result names would overwrite earlier object properties |
|
|
73
|
+
|
|
74
|
+
## Test the same statement
|
|
75
|
+
|
|
76
|
+
For the `public.users` table from the [human guide](human-guide.md), assume a dedicated test fixture
|
|
77
|
+
has rows `{ id: 1, email: 'ada@example.test', active: true }` and
|
|
78
|
+
`{ id: 2, email: 'lin@example.test', active: false }`, and `db` is its runtime connection:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import assert from 'node:assert/strict';
|
|
82
|
+
|
|
83
|
+
function usersQuery(activeOnly: boolean) {
|
|
84
|
+
const filters = [sql.fragment`email LIKE ${'%@example.test'}`];
|
|
85
|
+
if (activeOnly) filters.push(sql.fragment`active = ${true}`);
|
|
86
|
+
return sql<{ id: number; email: string }>`SELECT id, email FROM public.users
|
|
87
|
+
WHERE ${sql.join(filters, 'AND')} ORDER BY id`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const ada = { id: 1, email: 'ada@example.test' };
|
|
91
|
+
const lin = { id: 2, email: 'lin@example.test' };
|
|
92
|
+
for (const activeOnly of [false, true]) {
|
|
93
|
+
const query = usersQuery(activeOnly);
|
|
94
|
+
const description = await describeQuery({ connection, query });
|
|
95
|
+
assert.deepEqual(
|
|
96
|
+
description.columns.map(({ name, readType }) => [name, readType]),
|
|
97
|
+
[
|
|
98
|
+
['id', 'number'],
|
|
99
|
+
['email', 'string'],
|
|
100
|
+
],
|
|
101
|
+
);
|
|
102
|
+
assert.deepEqual(
|
|
103
|
+
description.issues.map(({ code }) => code),
|
|
104
|
+
['nullability-unknown'],
|
|
105
|
+
);
|
|
106
|
+
assert.deepEqual(await db.query(query, { returns: 'many' }), activeOnly ? [ada] : [ada, lin]);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Description and execution establish different facts. Describe can succeed for invalid bound values,
|
|
111
|
+
a write that violates constraints, or an operation the runtime role cannot execute. Check actual
|
|
112
|
+
rows/effects, JSON, permissions, and rollback in database tests, and run TypeScript. Inspect
|
|
113
|
+
meaningful query variants; one instance does not cover unexercised branches. Expect only reviewed
|
|
114
|
+
findings and keep unexpected ones visible.
|
|
115
|
+
|
|
116
|
+
A query builder is optional. When a test needs to retain executed statements, a test-only
|
|
117
|
+
`QueryClient` wrapper can forward them and retain `{ text }` without parameter values. Production
|
|
118
|
+
`onQuery` deliberately supplies neither SQL nor values.
|
|
89
119
|
|
|
90
120
|
## CLI
|
|
91
121
|
|
|
92
|
-
Save
|
|
122
|
+
Save SQL with PostgreSQL placeholders in `query.sql`:
|
|
93
123
|
|
|
94
124
|
```sql
|
|
95
125
|
SELECT $1::integer AS id, count(*) AS total FROM pg_catalog.pg_class;
|
|
96
126
|
```
|
|
97
127
|
|
|
98
|
-
Supply `DATABASE_URL` through the application's normal environment setup:
|
|
99
|
-
|
|
100
128
|
```sh
|
|
101
129
|
npx tenon describe query.sql
|
|
102
|
-
npx tenon describe query.sql --search-path
|
|
130
|
+
npx tenon describe query.sql --search-path app,public --time-zone UTC
|
|
103
131
|
```
|
|
104
132
|
|
|
105
|
-
The CLI
|
|
106
|
-
`--connect-timeout-ms`, `--statement-timeout-ms`, and
|
|
107
|
-
|
|
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.
|
|
133
|
+
The CLI uses `DATABASE_URL` and prints JSON. Session flags are `--role`, `--search-path`, and
|
|
134
|
+
`--time-zone`. Timeout flags are `--connect-timeout-ms`, `--statement-timeout-ms`, and
|
|
135
|
+
`--lock-timeout-ms`. Migration/generation flags such as `--namespace` and `--check` are rejected.
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
type from a JavaScript value that an inspection without values cannot establish.
|
|
137
|
+
Exit 0 means description succeeded, including reports with unresolved findings. Exit 1 means input,
|
|
138
|
+
connection, timeout, or PostgreSQL analysis failed. The inspected statement is never executed.
|