@kequtech/tenon-orm 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +4 -2
- package/dist/generation/diff.d.ts +2 -0
- package/dist/generation/diff.js +30 -0
- package/docs/agent-guide.md +17 -17
- package/docs/human-guide.md +20 -25
- package/docs/mutations.md +2 -7
- package/docs/reference.md +45 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ migrations and table type generation are independent conveniences.
|
|
|
68
68
|
|
|
69
69
|
For object-based writes, `sql.insertValues(rows)` preserves database defaults across heterogeneous
|
|
70
70
|
rows and `sql.assignments(patch)` omits undefined fields. Keep the surrounding SQL explicit. Both
|
|
71
|
-
support selected columns and [SafeQL checking](docs/reference.md#
|
|
71
|
+
support selected columns and [SafeQL checking](docs/reference.md#checking-mutation-fragments).
|
|
72
72
|
Use [generated input types](docs/reference.md#insert-and-update-inputs) to check those objects
|
|
73
73
|
against your schema.
|
|
74
74
|
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { adoptBaseline, captureBaseline } from './baseline.js';
|
|
7
7
|
import { generateDefinitions } from './generate.js';
|
|
8
|
+
import { definitionDiff } from './generation/diff.js';
|
|
8
9
|
import { migrate, migrationStatus, rollback } from './migrations.js';
|
|
9
10
|
import { createMigration } from './migrations/files.js';
|
|
10
11
|
const help = `tenon migrate|status|rollback [--directory migrations] [--namespace public]
|
|
@@ -30,9 +31,10 @@ async function writeDefinitions(source, filename, check) {
|
|
|
30
31
|
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
31
32
|
throw error;
|
|
32
33
|
}
|
|
34
|
+
throw new Error(`Generated definitions file is missing: ${output}\nRun tenon generate with the same options, omitting --check, then commit the result.`);
|
|
33
35
|
}
|
|
34
36
|
if (existing !== source) {
|
|
35
|
-
throw new Error(
|
|
37
|
+
throw new Error(`Generated definitions differ: ${output}\n${definitionDiff(existing, source)}\nRun tenon generate with the same options, omitting --check, then commit the result.`);
|
|
36
38
|
}
|
|
37
39
|
process.stdout.write('Generated definitions are current.\n');
|
|
38
40
|
return;
|
|
@@ -152,7 +154,7 @@ async function main() {
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
main().catch((error) => {
|
|
155
|
-
//
|
|
157
|
+
// Report diagnostics without connection configuration or query parameter payloads.
|
|
156
158
|
process.stderr.write(`${error instanceof Error ? error.message : 'Database command failed.'}\n`);
|
|
157
159
|
process.exitCode = 1;
|
|
158
160
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** A bounded text excerpt, not a schema comparison or an applicable patch. */
|
|
2
|
+
export function definitionDiff(saved, generated) {
|
|
3
|
+
const before = saved.split('\n');
|
|
4
|
+
const after = generated.split('\n');
|
|
5
|
+
let start = 0;
|
|
6
|
+
while (start < before.length && start < after.length && before[start] === after[start]) {
|
|
7
|
+
start++;
|
|
8
|
+
}
|
|
9
|
+
if (start === before.length && start === after.length)
|
|
10
|
+
return '';
|
|
11
|
+
let beforeEnd = before.length;
|
|
12
|
+
let afterEnd = after.length;
|
|
13
|
+
while (beforeEnd > start && afterEnd > start && before[beforeEnd - 1] === after[afterEnd - 1]) {
|
|
14
|
+
beforeEnd--;
|
|
15
|
+
afterEnd--;
|
|
16
|
+
}
|
|
17
|
+
function excerpt(lines, end, marker) {
|
|
18
|
+
const shown = lines.slice(start, Math.min(end, start + 8));
|
|
19
|
+
const output = shown.map((line) => `${marker} ${line.length > 200 ? line.slice(0, 200) + '…' : line}`);
|
|
20
|
+
if (end - start > shown.length) {
|
|
21
|
+
output.push(` … ${end - start - shown.length} more ${marker} lines omitted`);
|
|
22
|
+
}
|
|
23
|
+
return output;
|
|
24
|
+
}
|
|
25
|
+
return [
|
|
26
|
+
`Diff excerpt from line ${start + 1} (- saved, + generated):`,
|
|
27
|
+
...excerpt(before, beforeEnd, '-'),
|
|
28
|
+
...excerpt(after, afterEnd, '+'),
|
|
29
|
+
].join('\n');
|
|
30
|
+
}
|
package/docs/agent-guide.md
CHANGED
|
@@ -88,11 +88,9 @@ is an optional nonempty diagnostic string.
|
|
|
88
88
|
as `'DESC'` remain bound values, not SQL syntax.
|
|
89
89
|
- `sql.fragment` carries SQL and bound values without a row type. Use it for shared predicates or
|
|
90
90
|
partial SQL. Fragments cannot execute alone; full `sql` and `unsafeSql` queries cannot be nested.
|
|
91
|
-
- `sql.join(fragments, separator)`
|
|
92
|
-
`'OR'`, or `','`.
|
|
93
|
-
|
|
94
|
-
- Joins add no parentheses. Group mixed `AND`/`OR` predicates explicitly and preserve authorization
|
|
95
|
-
conditions when adding optional filters.
|
|
91
|
+
- `sql.join(fragments, separator)` combines a variable-length list of fragments using `'AND'`,
|
|
92
|
+
`'OR'`, or `','`. Use it for optional filters, search terms, or lists of SQL expressions. The list
|
|
93
|
+
must be nonempty; group mixed `AND`/`OR` predicates with parentheses.
|
|
96
94
|
- `unsafeSql<Row>(text, values)` accepts trusted external SQL with an explicit values array. Never
|
|
97
95
|
concatenate request input into its text. Each query executes one statement.
|
|
98
96
|
|
|
@@ -106,13 +104,8 @@ const count = sql<{ count: string }>`SELECT count(*) AS count FROM public."User"
|
|
|
106
104
|
WHERE ${search}`;
|
|
107
105
|
```
|
|
108
106
|
|
|
109
|
-
`pattern` and `limit` are validated application inputs.
|
|
110
|
-
|
|
111
|
-
literal/const-string separators, and [official mutation helpers](reference.md#object-based-mutation-fragments).
|
|
112
|
-
It rejects mutable or runtime-dependent SQL structure. Prefer fixed SQL for simple optional filters, e.g.
|
|
113
|
-
`(${email}::text IS NULL OR email = ${email})`. If runtime composition is required, retain an
|
|
114
|
-
explicit result type, suppress `tenon/check-composition` only on that query with a reason, and test
|
|
115
|
-
each branch. Parameter binding alone does not establish result correctness.
|
|
107
|
+
`pattern` and `limit` are validated application inputs. See the
|
|
108
|
+
[composition reference](reference.md#sql-fragments-and-joins) for grouping and list examples.
|
|
116
109
|
|
|
117
110
|
## Object-based writes
|
|
118
111
|
|
|
@@ -129,11 +122,9 @@ narrow editable fields with `Pick`. They account for defaults, nullability, doma
|
|
|
129
122
|
columns. Write mappings differ from returned rows: timestamps use `Date | string`, bigint uses
|
|
130
123
|
`string | bigint`, and JSON uses serialized text. See the [input types](reference.md#insert-and-update-inputs).
|
|
131
124
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
SQL and result annotations, not input values, constraints, or nonempty runtime input. See the
|
|
136
|
-
[mutation contract](reference.md#object-based-mutation-fragments) and [cookbook](mutations.md).
|
|
125
|
+
Selected columns choose and order fields, e.g. `sql.insertValues(rows, ['name', 'score'])`. Unlisted
|
|
126
|
+
fields are ignored. See the [mutation contract](reference.md#object-based-mutation-fragments) and
|
|
127
|
+
[cookbook](mutations.md).
|
|
137
128
|
|
|
138
129
|
## Configure and run SQL checking
|
|
139
130
|
|
|
@@ -205,6 +196,15 @@ Reuse existing lint scripts or add:
|
|
|
205
196
|
SQL and annotation errors come from `@ts-safeql/check-sql`; unsupported composition errors come from
|
|
206
197
|
`tenon/check-composition`. Keep both rules enabled.
|
|
207
198
|
|
|
199
|
+
The checker supports constant fragments and `sql.join` with inline nonempty arrays and literal or
|
|
200
|
+
const-string separators. It does not follow runtime-built lists or fragment-returning functions.
|
|
201
|
+
For tested runtime variants, keep an explicit result type and suppress `tenon/check-composition`
|
|
202
|
+
only on the particular query with a reason.
|
|
203
|
+
|
|
204
|
+
Mutation helpers accept inline object literals for checking. Runtime inputs, spreads, and
|
|
205
|
+
data-builder results need inline selected columns, e.g. `sql.insertValues(rows, ['name', 'score'])`.
|
|
206
|
+
The check uses all selected columns; it does not validate runtime input values or omission decisions.
|
|
207
|
+
|
|
208
208
|
For existing applications, use the [incremental audit workflow](adoption-guide.md#adopt-sql-checking-incrementally)
|
|
209
209
|
to preview fixes and review database types versus application refinements.
|
|
210
210
|
|
package/docs/human-guide.md
CHANGED
|
@@ -184,9 +184,9 @@ Generated insert types distinguish required fields from defaults and nullable fi
|
|
|
184
184
|
make writable fields optional; use `Pick` to limit what an action may edit. See the
|
|
185
185
|
[input-type reference](reference.md#insert-and-update-inputs) for write-value mappings.
|
|
186
186
|
|
|
187
|
-
Keep expressions, authorization, conflict handling and `RETURNING` in SQL.
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
Keep expressions, authorization, conflict handling and `RETURNING` in SQL. Selected column lists
|
|
188
|
+
choose which fields to write; they do not validate the complete object. See the
|
|
189
|
+
[mutation cookbook](mutations.md) for batches and upserts.
|
|
190
190
|
|
|
191
191
|
## Check your SQL and result types
|
|
192
192
|
|
|
@@ -215,6 +215,12 @@ an incorrect declared result type before the query runs.
|
|
|
215
215
|
Input validation and database tests still matter. Checking a query does not prove that a request is
|
|
216
216
|
authorized, that its values are valid, or that returned JSON has an application-specific shape.
|
|
217
217
|
|
|
218
|
+
The checker supports constant fragments and `sql.join` with inline arrays. Runtime-built lists and
|
|
219
|
+
fragment-returning functions remain outside static analysis. Keep result annotations and database
|
|
220
|
+
tests for those queries; when using the checker, suppress `tenon/check-composition` only on the
|
|
221
|
+
tested query with a reason. Runtime-built mutation inputs need inline selected columns for SQL
|
|
222
|
+
checking. See the [checking reference](reference.md#supported-sql-and-limitations) for details.
|
|
223
|
+
|
|
218
224
|
## Reuse or assemble parts of SQL when needed
|
|
219
225
|
|
|
220
226
|
For fixed conditions, write them directly:
|
|
@@ -239,37 +245,26 @@ const count = sql<{ count: string }>`SELECT count(*) AS count FROM public."User"
|
|
|
239
245
|
```
|
|
240
246
|
|
|
241
247
|
Here `pattern` is an application-supplied search pattern and `limit` is a validated page size.
|
|
242
|
-
Execute each query with `db.query`.
|
|
243
|
-
result type. The checker can inspect these constant fragments.
|
|
248
|
+
Execute each query with `db.query`. The complete query supplies the result type.
|
|
244
249
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// email is string | null; null means no email filter.
|
|
249
|
-
const query = sql<UserSummary>`SELECT id, email FROM public."User"
|
|
250
|
-
WHERE active = ${true} AND (${email}::text IS NULL OR email = ${email})`;
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
If you need a variable list of conditions, `sql.join` combines fragments using `'AND'`, `'OR'`, or
|
|
254
|
-
`','`. **SafeQL cannot check runtime-built lists.** Keep an explicit result type and test each
|
|
255
|
-
variant; if you use SafeQL, suppress only this query with an explanation. Parameter binding still
|
|
256
|
-
works, but it does not prove that every variant is valid SQL or returns the declared columns.
|
|
250
|
+
Use `sql.join` when the number of fragments varies, such as one search condition per word or a set
|
|
251
|
+
of optional filters. It combines fragments with `'AND'`, `'OR'`, or `','` and preserves their bound
|
|
252
|
+
values:
|
|
257
253
|
|
|
258
254
|
```ts
|
|
255
|
+
const terms = ['reader', 'example'];
|
|
259
256
|
const filters = [sql.fragment`active = ${true}`];
|
|
260
|
-
|
|
257
|
+
for (const term of terms) {
|
|
258
|
+
filters.push(sql.fragment`email ILIKE ${'%' + term + '%'}`);
|
|
259
|
+
}
|
|
261
260
|
|
|
262
|
-
/* eslint-disable tenon/check-composition -- Runtime filters are covered by database tests. */
|
|
263
261
|
const query = sql<UserSummary>`SELECT id, email FROM public."User"
|
|
264
262
|
WHERE ${sql.join(filters, 'AND')} ORDER BY id`;
|
|
265
|
-
/* eslint-enable tenon/check-composition */
|
|
266
263
|
```
|
|
267
264
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
mixing `AND` and `OR`.
|
|
271
|
-
[composition reference](reference.md#sql-fragments-and-joins) for static checking rules and other
|
|
272
|
-
details.
|
|
265
|
+
This matches active users whose email contains every term. With no terms, the active condition
|
|
266
|
+
still applies. The list must be nonempty; `join` inserts separators, so use parentheses as usual
|
|
267
|
+
when mixing `AND` and `OR`. See the [composition reference](reference.md#sql-fragments-and-joins).
|
|
273
268
|
|
|
274
269
|
For arrays, identifiers, type mappings, CLI options, and error behavior, use the
|
|
275
270
|
[reference](reference.md). You do not need fragments or a query builder to use PostgreSQL joins,
|
package/docs/mutations.md
CHANGED
|
@@ -19,7 +19,7 @@ The first row uses the score default. The second uses the bio default. Explicit
|
|
|
19
19
|
row stays null. Columns are aligned across rows. Omit generated columns and let PostgreSQL enforce
|
|
20
20
|
required fields and constraints.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Use a column list to select and order fields in runtime-built rows:
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
import type { Rows } from './generated-schema.js';
|
|
@@ -97,8 +97,7 @@ if (patch.name !== undefined || patch.bio !== undefined) {
|
|
|
97
97
|
|
|
98
98
|
## Preserve an omitted field during an upsert
|
|
99
99
|
|
|
100
|
-
Use
|
|
101
|
-
checked independently; the checker need not choose a JavaScript branch:
|
|
100
|
+
Use separate queries when an option changes conflict behavior:
|
|
102
101
|
|
|
103
102
|
```ts
|
|
104
103
|
if (email !== undefined) {
|
|
@@ -151,7 +150,3 @@ calls for empty input. The query-wide limit is 65,535 bound parameters, includin
|
|
|
151
150
|
the mutation fragment. An oversized query reports its actual count, for example
|
|
152
151
|
`at most 65535 bound parameters; received 65536`. Defaults consume no parameters. Account for the
|
|
153
152
|
selected columns and surrounding query when choosing a batch size; Tenon never splits queries automatically.
|
|
154
|
-
|
|
155
|
-
Reusable authorization fragments remain valid at runtime. Keep shared policy in application
|
|
156
|
-
functions and test it rather than duplicating it for static coverage. Function-generated SQL,
|
|
157
|
-
conditional fragments, and dynamic filter lists remain outside static checking.
|
package/docs/reference.md
CHANGED
|
@@ -86,22 +86,22 @@ const query = sql<{ id: number }>`SELECT id FROM public."User"
|
|
|
86
86
|
NUL, and fit PostgreSQL's 63-byte identifier limit. Choose names from application-approved
|
|
87
87
|
alternatives; quoting does not authorize access. Ordinary strings such as `'DESC'` are bound values,
|
|
88
88
|
not SQL syntax. For ordering, use complete query alternatives or application-selected fragments
|
|
89
|
-
containing `ASC` or `DESC
|
|
89
|
+
containing `ASC` or `DESC`.
|
|
90
90
|
|
|
91
91
|
`unsafeSql<Row>(text, values)` accepts externally authored, trusted SQL and an explicit values
|
|
92
|
-
array, including `[]` when empty.
|
|
93
|
-
|
|
92
|
+
array, including `[]` when empty. Never concatenate request input into its text.
|
|
93
|
+
Full `sql` or `unsafeSql` queries cannot be inserted into other queries; use
|
|
94
94
|
fragments for partial SQL.
|
|
95
95
|
|
|
96
96
|
## SQL fragments and joins
|
|
97
97
|
|
|
98
98
|
`sql.fragment` creates an immutable piece of SQL with bound values. It has no row type and cannot
|
|
99
|
-
execute alone. Insert it into a complete `sql<Row>` query.
|
|
100
|
-
|
|
101
|
-
directly in the query.
|
|
99
|
+
execute alone. Insert it into a complete `sql<Row>` query. Use fragments to share predicates or
|
|
100
|
+
other SQL expressions between queries. Blank fragments throw.
|
|
102
101
|
|
|
103
|
-
`sql.join(fragments, separator)` combines
|
|
104
|
-
|
|
102
|
+
`sql.join(fragments, separator)` combines fragments into a new fragment. Use it when the number of
|
|
103
|
+
parts varies: optional filters, a condition for each search term, or a list of SQL expressions.
|
|
104
|
+
Write fixed structure directly in SQL. Supported separators are:
|
|
105
105
|
|
|
106
106
|
| Argument | Inserted SQL | Typical use |
|
|
107
107
|
| -------- | ------------ | -------------------------- |
|
|
@@ -109,35 +109,22 @@ Separators are exactly:
|
|
|
109
109
|
| `'OR'` | `OR` | Any condition may hold |
|
|
110
110
|
| `','` | `, ` | Projections or assignments |
|
|
111
111
|
|
|
112
|
-
No default, arbitrary string, or fragment separator is accepted. Runtime validation also protects
|
|
113
|
-
JavaScript callers. A single item is returned as a new fragment without extra separators. Empty
|
|
114
|
-
lists and blank fragments throw; choose empty-list behavior explicitly in application code.
|
|
115
|
-
|
|
116
|
-
Joins do not add parentheses or alter SQL precedence. Group alternatives before combining them with
|
|
117
|
-
other conditions:
|
|
118
|
-
|
|
119
112
|
```ts
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
'AND',
|
|
127
|
-
);
|
|
128
|
-
const query = sql<{ id: number }>`SELECT id FROM public."User" WHERE ${conditions}`;
|
|
113
|
+
const conditions = [sql.fragment`active = ${true}`];
|
|
114
|
+
if (email !== undefined) conditions.push(sql.fragment`email = ${email}`);
|
|
115
|
+
if (afterId !== undefined) conditions.push(sql.fragment`id > ${afterId}`);
|
|
116
|
+
|
|
117
|
+
const query = sql<{ id: number }>`SELECT id FROM public."User"
|
|
118
|
+
WHERE ${sql.join(conditions, 'AND')} ORDER BY id`;
|
|
129
119
|
```
|
|
130
120
|
|
|
121
|
+
The list must be nonempty; the example keeps the active condition even when both optional filters
|
|
122
|
+
are absent. Joins insert separators without adding parentheses or changing SQL precedence. Group
|
|
123
|
+
mixed AND/OR expressions with parentheses, as in ordinary SQL.
|
|
124
|
+
|
|
131
125
|
Each fragment use receives correctly ordered parameters, including repeated and nested uses. SQL
|
|
132
126
|
strings and comments containing literal `$1` text are left intact.
|
|
133
127
|
|
|
134
|
-
Static checking supports constant fragments, literal join arrays, literal or const-string
|
|
135
|
-
separators, and [official mutation helpers](#object-based-mutation-fragments). Runtime-built lists
|
|
136
|
-
and selected fragments execute safely with respect to parameter binding, but the checker cannot
|
|
137
|
-
verify their SQL or result types. Prefer fixed SQL when it fits.
|
|
138
|
-
Otherwise keep an explicit result annotation, use a narrow explained lint suppression, and test
|
|
139
|
-
every variant.
|
|
140
|
-
|
|
141
128
|
## Object-based mutation fragments
|
|
142
129
|
|
|
143
130
|
`sql.insertValues(input, columns?)` and `sql.assignments(input, columns?)` return fragments. Keep the
|
|
@@ -188,8 +175,7 @@ intended update consists only of those expressions.
|
|
|
188
175
|
An optional nonempty column list selects and orders fields. It does not validate that the input
|
|
189
176
|
object has no additional keys. Unlisted fields are ignored; missing insert cells use `DEFAULT`,
|
|
190
177
|
while missing update fields are omitted. Duplicate or invalid column
|
|
191
|
-
names throw. TypeScript checks selected names against the input's known keys
|
|
192
|
-
way to make runtime objects and batches available to static SQL checking:
|
|
178
|
+
names throw. TypeScript checks selected names against the input's known keys:
|
|
193
179
|
|
|
194
180
|
```ts
|
|
195
181
|
const rows: { name: string; bio?: string | null }[] = buildRows();
|
|
@@ -211,24 +197,8 @@ Tenon rejects larger queries and does not split them automatically. Choose batch
|
|
|
211
197
|
transaction boundaries in the caller. Helper validation errors identify invalid rows/columns
|
|
212
198
|
without printing cell values.
|
|
213
199
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
SafeQL recognizes these official helpers directly through their Tenon function declarations,
|
|
217
|
-
including aliases. It never analyzes their implementation or executes application data builders.
|
|
218
|
-
Without an explicit column list, checking accepts inline object literals and inline insert arrays
|
|
219
|
-
of object literals. Keys must be literal names; object spreads, computed keys, methods, accessors,
|
|
220
|
-
and objects stored in variables require an explicit inline column list. Existing constant string
|
|
221
|
-
resolution works for entries of that list; column arrays stored in variables are unsupported.
|
|
222
|
-
|
|
223
|
-
For runtime inputs with explicit columns, the checker analyzes a representative nonempty insert
|
|
224
|
-
row or assignment list containing **all selected columns**. This checks column references, SQL
|
|
225
|
-
composition, conflict clauses, and `RETURNING` annotations. Runtime omission/default decisions and
|
|
226
|
-
batch length do not require interpreting JavaScript. The check is conservative: an optional
|
|
227
|
-
selected column is checked even when a particular runtime call omits it. It does not establish
|
|
228
|
-
nonempty input, valid runtime values, successful constraints, or every default's behavior; runtime
|
|
229
|
-
validation and database tests cover those contracts. Unknown inputs without explicit columns
|
|
230
|
-
produce `tenon/check-composition` diagnostics. See the [mutation cookbook](mutations.md) for
|
|
231
|
-
empty patches, conditional upserts, arrays, and batching.
|
|
200
|
+
See the [mutation cookbook](mutations.md) for common patterns and
|
|
201
|
+
[checking mutation fragments](#checking-mutation-fragments) for optional SQL checking.
|
|
232
202
|
|
|
233
203
|
## Transactions, guarded writes, and errors
|
|
234
204
|
|
|
@@ -285,7 +255,8 @@ the existing file without rewriting it:
|
|
|
285
255
|
npx tenon generate --out src/generated-schema.d.ts --check
|
|
286
256
|
```
|
|
287
257
|
|
|
288
|
-
`--check`
|
|
258
|
+
`--check` reports a missing file or shows a short text diff when the saved output differs from the
|
|
259
|
+
current database. It never rewrites the file. Pending migration files
|
|
289
260
|
are a separate concern. The representation includes table columns, constraints, indexes, and
|
|
290
261
|
referenced domains, not every PostgreSQL object.
|
|
291
262
|
|
|
@@ -511,7 +482,7 @@ the intended relation is explicit in both checking and execution.
|
|
|
511
482
|
|
|
512
483
|
Within the configured targets, the adapter recognizes Tenon's `sql` export through imports, local
|
|
513
484
|
aliases, namespace imports, and object properties that retain its TypeScript type. It checks
|
|
514
|
-
constant fragments and `sql.join` with nonempty
|
|
485
|
+
constant fragments and `sql.join` with inline nonempty array literals, including nested fragments.
|
|
515
486
|
Separators and `identifier()` names must resolve to literal strings or const string initializers.
|
|
516
487
|
Values use the same parameter placeholders in static checking and execution, inside or outside
|
|
517
488
|
fragments. PostgreSQL infers their types from SQL context; Tenon does not add casts based on
|
|
@@ -521,7 +492,8 @@ The checker follows const fragment initializers, including imported constants wi
|
|
|
521
492
|
source. It does not inspect function bodies, substitute helper arguments/defaults, or choose
|
|
522
493
|
JavaScript conditional branches. Function-generated fragments and conditional SQL structure are
|
|
523
494
|
unsupported even when a particular helper appears simple or a condition is a literal. Mutable
|
|
524
|
-
references
|
|
495
|
+
fragment references and dynamic identifiers are also unsupported. Arrays stored in variables or
|
|
496
|
+
assembled in loops are valid `sql.join` inputs at runtime but are not analyzed. Ordinary value
|
|
525
497
|
expressions remain bound parameters; calls that produce data for an official mutation helper can
|
|
526
498
|
use its explicit column-list contract.
|
|
527
499
|
|
|
@@ -580,6 +552,25 @@ tests; a checker cannot infer your parser policy. Column keys are `Table.column`
|
|
|
580
552
|
names across schemas need care. Type overrides merge with the adapter's defaults. `unsafeSql` is
|
|
581
553
|
outside static checking, and arbitrary JSON still needs application validation.
|
|
582
554
|
|
|
555
|
+
### Checking mutation fragments
|
|
556
|
+
|
|
557
|
+
SafeQL recognizes these official helpers directly through their Tenon function declarations,
|
|
558
|
+
including aliases. It never analyzes their implementation or executes application data builders.
|
|
559
|
+
Without an explicit column list, checking accepts inline object literals and inline insert arrays
|
|
560
|
+
of object literals. Keys must be literal names; object spreads, computed keys, methods, accessors,
|
|
561
|
+
and objects stored in variables require an explicit inline column list. Existing constant string
|
|
562
|
+
resolution works for entries of that list; column arrays stored in variables are unsupported.
|
|
563
|
+
|
|
564
|
+
For runtime inputs with explicit columns, the checker analyzes a representative nonempty insert
|
|
565
|
+
row or assignment list containing **all selected columns**. This checks column references, SQL
|
|
566
|
+
composition, conflict clauses, and `RETURNING` annotations. Runtime omission/default decisions and
|
|
567
|
+
batch length do not require interpreting JavaScript. The check is conservative: an optional
|
|
568
|
+
selected column is checked even when a particular runtime call omits it. It does not establish
|
|
569
|
+
nonempty input, valid runtime values, successful constraints, or every default's behavior; runtime
|
|
570
|
+
validation and database tests cover those contracts. Unknown inputs without explicit columns
|
|
571
|
+
produce `tenon/check-composition` diagnostics. See the [mutation cookbook](mutations.md) for
|
|
572
|
+
empty patches, conditional upserts, arrays, and batching.
|
|
573
|
+
|
|
583
574
|
## Command reference
|
|
584
575
|
|
|
585
576
|
All database commands use `DATABASE_URL`; environment files are not loaded automatically. For
|