@kequtech/tenon-orm 0.1.1 → 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 +12 -10
- package/dist/cli.js +4 -2
- package/dist/generate.js +2 -2
- package/dist/generation/catalog.d.ts +3 -0
- package/dist/generation/catalog.js +5 -2
- package/dist/generation/diff.d.ts +2 -0
- package/dist/generation/diff.js +30 -0
- package/dist/generation/table-types.d.ts +2 -0
- package/dist/generation/table-types.js +146 -0
- package/dist/mutations.d.ts +1 -1
- package/dist/query.d.ts +2 -2
- package/dist/query.js +1 -1
- package/dist/safeql/composition.js +9 -0
- package/dist/safeql/eslint.js +16 -11
- package/docs/adoption-guide.md +97 -4
- package/docs/agent-guide.md +38 -56
- package/docs/human-guide.md +37 -37
- package/docs/mutations.md +49 -12
- package/docs/reference.md +128 -94
- package/package.json +1 -1
- package/dist/generation/row-types.d.ts +0 -2
- package/dist/generation/row-types.js +0 -115
package/docs/agent-guide.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tenon agent guide
|
|
2
2
|
|
|
3
3
|
Tenon executes PostgreSQL SQL through `pg`, with parameter binding, transactions, optional write
|
|
4
|
-
limits, SQL migrations, and generated
|
|
4
|
+
limits, SQL migrations, and generated row and mutation input types. Use ordinary functions for reusable queries.
|
|
5
5
|
This guide contains the setup and contracts needed for routine implementation; the
|
|
6
6
|
[reference](reference.md) covers additional options.
|
|
7
7
|
|
|
@@ -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
|
|
|
@@ -124,11 +117,14 @@ batches/updates throw; a single default-only insert works. Omit generated column
|
|
|
124
117
|
writes explicitly below the query-wide limit of 65,535 parameters. Expressions and queries are
|
|
125
118
|
rejected as cell values; write arithmetic, `DEFAULT`, and `excluded.column` in the surrounding SQL.
|
|
126
119
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
Use generated `Rows.Insert['table']` and `Rows.Update['table']` for mutation inputs when available;
|
|
121
|
+
narrow editable fields with `Pick`. They account for defaults, nullability, domains, and generated
|
|
122
|
+
columns. Write mappings differ from returned rows: timestamps use `Date | string`, bigint uses
|
|
123
|
+
`string | bigint`, and JSON uses serialized text. See the [input types](reference.md#insert-and-update-inputs).
|
|
124
|
+
|
|
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).
|
|
132
128
|
|
|
133
129
|
## Configure and run SQL checking
|
|
134
130
|
|
|
@@ -140,10 +136,8 @@ Configure it normally and add both exports from `@kequtech/tenon-orm/safeql`:
|
|
|
140
136
|
- `eslintPlugin` goes in ESLint's `plugins`, with `tenon/check-composition` enabled. It reports
|
|
141
137
|
unsupported forms that the adapter skips. It does not wrap SafeQL's rule.
|
|
142
138
|
|
|
143
|
-
These tools
|
|
144
|
-
|
|
145
|
-
and imports SafeQL and typescript-eslint itself. Use a TypeScript version supported by the lint
|
|
146
|
-
toolchain. Generated table types are optional and not required by SafeQL.
|
|
139
|
+
These tools run during development and CI. Reuse the application's TypeScript parser settings and
|
|
140
|
+
use a TypeScript version supported by the lint toolchain. Generated table types are optional.
|
|
147
141
|
|
|
148
142
|
Reuse existing configuration and scripts. For a new setup:
|
|
149
143
|
|
|
@@ -202,6 +196,18 @@ Reuse existing lint scripts or add:
|
|
|
202
196
|
SQL and annotation errors come from `@ts-safeql/check-sql`; unsupported composition errors come from
|
|
203
197
|
`tenon/check-composition`. Keep both rules enabled.
|
|
204
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
|
+
For existing applications, use the [incremental audit workflow](adoption-guide.md#adopt-sql-checking-incrementally)
|
|
209
|
+
to preview fixes and review database types versus application refinements.
|
|
210
|
+
|
|
205
211
|
1. Run the development `db:migrate` script below against the intended development/test database.
|
|
206
212
|
2. Run `npm run lint`. Fix SQL mistakes before accepting generated types.
|
|
207
213
|
3. Run `npm run lint:fix` for missing/stale annotations. It can add
|
|
@@ -209,38 +215,14 @@ SQL and annotation errors come from `@ts-safeql/check-sql`; unsupported composit
|
|
|
209
215
|
enabled ESLint rules may also fix files.
|
|
210
216
|
4. Run the application's typecheck and database tests. CI runs `lint` without `--fix`.
|
|
211
217
|
|
|
212
|
-
For multiple databases,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
the wrong type for an unqualified table despite the connection's `search_path`; explicit schema
|
|
221
|
-
names avoid that ambiguity.
|
|
222
|
-
|
|
223
|
-
The checker recognizes Tenon imports, aliases, and typed object properties. It resolves constant
|
|
224
|
-
fragments and literal/const identifier names without executing application code. Imported
|
|
225
|
-
fragments need inspectable source initializers. It recognizes the official mutation helpers
|
|
226
|
-
directly; it never inspects function bodies or substitutes helper arguments/defaults. Conditional
|
|
227
|
-
fragments, function-generated fragments, and runtime filter lists require narrow explained
|
|
228
|
-
suppressions and database tests. Ordinary functions containing complete `sql` templates remain
|
|
229
|
-
checkable. Keep reusable authorization policy centralized even when it is test-owned rather than
|
|
230
|
-
statically checked. Use literal newlines and bound string values: outer-template JavaScript escapes
|
|
231
|
-
are rejected because SafeQL reads raw text while Tenon executes decoded text. Bound values and
|
|
232
|
-
constant fragments are decoded consistently.
|
|
233
|
-
|
|
234
|
-
Checking verifies SQL and result annotations, not JavaScript input compatibility, authorization, or
|
|
235
|
-
JSON contents. PostgreSQL owns parameter inference; the checker adds no casts based on JavaScript
|
|
236
|
-
types. `unsafeSql` is unchecked. Never use `any` to hide an unsupported query.
|
|
237
|
-
|
|
238
|
-
Default scalar mappings: bigint/numeric are strings; JSON is `unknown`; temporal values are
|
|
239
|
-
`Date | number` because infinities are numbers. Generated `PgArray<T>` supports recursive dimensions
|
|
240
|
-
and null elements. Numeric arrays contain numbers and can lose precision. Custom pg parsers and
|
|
241
|
-
recursive array projections need matching checker overrides and tests, for example
|
|
242
|
-
`overrides: { columns: { 'User.tags': 'PgArray<string>' } }` on the SafeQL connection. Import
|
|
243
|
-
`PgArray` from the generated definitions when using that annotation.
|
|
218
|
+
For multiple databases, give each connection distinct tag targets or ESLint file scopes. Qualify
|
|
219
|
+
same-named tables with their schemas. See the [connection reference](reference.md#configuration-choices-and-multiple-databases).
|
|
220
|
+
|
|
221
|
+
Checking verifies SQL and result annotations. Input validation, authorization, JSON contents, and
|
|
222
|
+
runtime query variants still need application checks and database tests. Keep shared policy in
|
|
223
|
+
ordinary functions even when its fragments cannot be checked statically. Use literal newlines in
|
|
224
|
+
SQL templates and bind string values. The [reference](reference.md#supported-sql-and-limitations)
|
|
225
|
+
covers supported composition and parser overrides.
|
|
244
226
|
|
|
245
227
|
## Transactions and writes
|
|
246
228
|
|
|
@@ -273,7 +255,7 @@ development script in `package.json`:
|
|
|
273
255
|
```json
|
|
274
256
|
{
|
|
275
257
|
"scripts": {
|
|
276
|
-
"db:migrate": "tenon migrate && tenon generate --out src/generated-schema.ts"
|
|
258
|
+
"db:migrate": "tenon migrate && tenon generate --out src/generated-schema.d.ts"
|
|
277
259
|
}
|
|
278
260
|
}
|
|
279
261
|
```
|
|
@@ -282,7 +264,7 @@ development script in `package.json`:
|
|
|
282
264
|
npx tenon new create_users
|
|
283
265
|
# Fill the generated up/down SQL before migrating.
|
|
284
266
|
npm run db:migrate
|
|
285
|
-
npx tenon generate --out src/generated-schema.ts --check
|
|
267
|
+
npx tenon generate --out src/generated-schema.d.ts --check
|
|
286
268
|
npx tenon status
|
|
287
269
|
npx tenon rollback --steps 1
|
|
288
270
|
```
|
package/docs/human-guide.md
CHANGED
|
@@ -59,12 +59,12 @@ CREATE TABLE public."User" (
|
|
|
59
59
|
DROP TABLE public."User";
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
For development, add a script to `package.json` that applies migrations and updates
|
|
62
|
+
For development, add a script to `package.json` that applies migrations and updates generated types:
|
|
63
63
|
|
|
64
64
|
```json
|
|
65
65
|
{
|
|
66
66
|
"scripts": {
|
|
67
|
-
"db:migrate": "tenon migrate && tenon generate --out src/generated-schema.ts"
|
|
67
|
+
"db:migrate": "tenon migrate && tenon generate --out src/generated-schema.d.ts"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
```
|
|
@@ -170,15 +170,23 @@ Explicit `null` stays null. Empty batches and updates throw. Heterogeneous inser
|
|
|
170
170
|
aligned, with `DEFAULT` for missing cells.
|
|
171
171
|
|
|
172
172
|
```ts
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
import type { Rows } from './generated-schema.js';
|
|
174
|
+
|
|
175
|
+
const input: Rows.Insert['User'] = { email: 'another@example.test' };
|
|
176
|
+
await db.query(sql`INSERT INTO public."User" ${sql.insertValues(input, ['email'])}`);
|
|
177
|
+
|
|
178
|
+
const patch: Pick<Rows.Update['User'], 'active'> = { active: false };
|
|
179
|
+
await db.query(sql`UPDATE public."User" SET ${sql.assignments(patch, ['active'])}
|
|
180
|
+
WHERE email = ${input.email}`);
|
|
175
181
|
```
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
Generated insert types distinguish required fields from defaults and nullable fields. Update types
|
|
184
|
+
make writable fields optional; use `Pick` to limit what an action may edit. See the
|
|
185
|
+
[input-type reference](reference.md#insert-and-update-inputs) for write-value mappings.
|
|
186
|
+
|
|
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.
|
|
182
190
|
|
|
183
191
|
## Check your SQL and result types
|
|
184
192
|
|
|
@@ -188,13 +196,9 @@ There are three different jobs here:
|
|
|
188
196
|
- TypeScript checks the application code and the result types you declare.
|
|
189
197
|
- SafeQL checks the SQL against your database and verifies or generates those types.
|
|
190
198
|
|
|
191
|
-
SafeQL is optional development tooling run by ESLint.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
database analysis and annotation fixes. Your application's ESLint configuration imports these tools;
|
|
195
|
-
Tenon's database runtime does not need them. Generated table types are a separate convenience and
|
|
196
|
-
are not required by SafeQL. Follow the one-time
|
|
197
|
-
[SQL checking setup](reference.md#sql-checking-with-safeql).
|
|
199
|
+
SafeQL is optional development tooling run by ESLint. Tenon's adapter supports queries, constant
|
|
200
|
+
fragments, and mutation helpers; `tenon/check-composition` reports queries it cannot inspect.
|
|
201
|
+
Follow the one-time [SQL checking setup](reference.md#sql-checking-with-safeql).
|
|
198
202
|
|
|
199
203
|
Use the application's existing lint commands. If it has none, the setup shows `lint` and `lint:fix`.
|
|
200
204
|
They run ESLint, including all other enabled rules. Once configured, the normal workflow is:
|
|
@@ -211,6 +215,12 @@ an incorrect declared result type before the query runs.
|
|
|
211
215
|
Input validation and database tests still matter. Checking a query does not prove that a request is
|
|
212
216
|
authorized, that its values are valid, or that returned JSON has an application-specific shape.
|
|
213
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
|
+
|
|
214
224
|
## Reuse or assemble parts of SQL when needed
|
|
215
225
|
|
|
216
226
|
For fixed conditions, write them directly:
|
|
@@ -235,36 +245,26 @@ const count = sql<{ count: string }>`SELECT count(*) AS count FROM public."User"
|
|
|
235
245
|
```
|
|
236
246
|
|
|
237
247
|
Here `pattern` is an application-supplied search pattern and `limit` is a validated page size.
|
|
238
|
-
Execute each query with `db.query`.
|
|
239
|
-
result type. The checker can inspect these constant fragments.
|
|
240
|
-
|
|
241
|
-
For a simple optional filter, a fixed SQL predicate often suffices:
|
|
248
|
+
Execute each query with `db.query`. The complete query supplies the result type.
|
|
242
249
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
WHERE active = ${true} AND (${email}::text IS NULL OR email = ${email})`;
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
If you need a variable list of conditions, `sql.join` combines fragments using `'AND'`, `'OR'`, or
|
|
250
|
-
`','`. **SafeQL cannot check runtime-built lists.** Keep an explicit result type and test each
|
|
251
|
-
variant; if you use SafeQL, suppress only this query with an explanation. Parameter binding still
|
|
252
|
-
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:
|
|
253
253
|
|
|
254
254
|
```ts
|
|
255
|
+
const terms = ['reader', 'example'];
|
|
255
256
|
const filters = [sql.fragment`active = ${true}`];
|
|
256
|
-
|
|
257
|
+
for (const term of terms) {
|
|
258
|
+
filters.push(sql.fragment`email ILIKE ${'%' + term + '%'}`);
|
|
259
|
+
}
|
|
257
260
|
|
|
258
|
-
// eslint-disable-next-line tenon/check-composition -- Runtime filters are covered by database tests.
|
|
259
261
|
const query = sql<UserSummary>`SELECT id, email FROM public."User"
|
|
260
262
|
WHERE ${sql.join(filters, 'AND')} ORDER BY id`;
|
|
261
263
|
```
|
|
262
264
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
mixing `AND` and `OR`.
|
|
266
|
-
[composition reference](reference.md#sql-fragments-and-joins) for static checking rules and other
|
|
267
|
-
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).
|
|
268
268
|
|
|
269
269
|
For arrays, identifiers, type mappings, CLI options, and error behavior, use the
|
|
270
270
|
[reference](reference.md). You do not need fragments or a query builder to use PostgreSQL joins,
|
package/docs/mutations.md
CHANGED
|
@@ -19,16 +19,57 @@ 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';
|
|
26
|
+
|
|
27
|
+
const rows: Rows.Insert['profiles'][] = buildRows();
|
|
26
28
|
await db.query(sql`INSERT INTO profiles ${sql.insertValues(rows, ['name', 'bio', 'score'])}`);
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
This list selects and orders fields; unlisted fields are ignored. It does not assert that the
|
|
30
32
|
entire input object contains only allowed keys. Keep narrower application input validation.
|
|
31
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 uses serialized text.
|
|
38
|
+
See the [input-type reference](reference.md#insert-and-update-inputs) for the complete mapping.
|
|
39
|
+
|
|
40
|
+
## Input types, SQL checking, and runtime validation
|
|
41
|
+
|
|
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
|
+
```ts
|
|
46
|
+
import { sql, type QueryClient } from '@kequtech/tenon-orm';
|
|
47
|
+
import type { Rows } from './generated-schema.js';
|
|
48
|
+
|
|
49
|
+
type ProfileInput = Pick<Rows.Insert['profiles'], 'name' | 'score'>;
|
|
50
|
+
|
|
51
|
+
function createProfile(client: QueryClient, input: ProfileInput) {
|
|
52
|
+
return client.query(sql<{ id: number }>`INSERT INTO profiles
|
|
53
|
+
${sql.insertValues(input, ['name', 'score'])} RETURNING id`);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Here `id` and `score` are assumed to be integers. TypeScript rejects an incompatible typed input
|
|
58
|
+
such as `{ name: 'Ada', score: true }`. SafeQL checks selected columns and `RETURNING`; it does not
|
|
59
|
+
inspect runtime values. Validate external inputs before calling this function and keep database
|
|
60
|
+
constraints and application authorization in place.
|
|
61
|
+
|
|
62
|
+
**Selected columns can discard required fields.** For a table requiring integer `id` and text
|
|
63
|
+
`name` and `location`, this input is valid but the query fails:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const row: Rows.Insert['profiles'] = { id: 1, name: 'Ada', location: 'London' };
|
|
67
|
+
const query = sql`INSERT INTO profiles ${sql.insertValues(row, ['id', 'name'])}`;
|
|
68
|
+
// PostgreSQL rejects the insert: location was dropped and has no default.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A column list selects fields; it does not prove that all required fields will be written.
|
|
72
|
+
|
|
32
73
|
## Patches mixed with SQL expressions
|
|
33
74
|
|
|
34
75
|
```ts
|
|
@@ -56,8 +97,7 @@ if (patch.name !== undefined || patch.bio !== undefined) {
|
|
|
56
97
|
|
|
57
98
|
## Preserve an omitted field during an upsert
|
|
58
99
|
|
|
59
|
-
Use
|
|
60
|
-
checked independently; the checker need not choose a JavaScript branch:
|
|
100
|
+
Use separate queries when an option changes conflict behavior:
|
|
61
101
|
|
|
62
102
|
```ts
|
|
63
103
|
if (email !== undefined) {
|
|
@@ -89,8 +129,8 @@ await db.query(
|
|
|
89
129
|
```
|
|
90
130
|
|
|
91
131
|
Objects used as JSON values retain pg's ordinary JSON conversion. The helper does not infer JSON
|
|
92
|
-
versus array encoding from the table schema.
|
|
93
|
-
|
|
132
|
+
versus array encoding from the table schema. Generated JSON input types use serialized text for
|
|
133
|
+
consistent handling of objects and arrays. Validate JSON contents in the application.
|
|
94
134
|
|
|
95
135
|
## Batches in one transaction
|
|
96
136
|
|
|
@@ -107,9 +147,6 @@ await db.transaction(async (tx) => {
|
|
|
107
147
|
|
|
108
148
|
A failure rolls back all batches. The helper rejects an empty batch; this loop makes no insert
|
|
109
149
|
calls for empty input. The query-wide limit is 65,535 bound parameters, including parameters outside
|
|
110
|
-
the mutation fragment.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Reusable authorization fragments remain valid at runtime. Keep shared policy in application
|
|
114
|
-
functions and test it rather than duplicating it for static coverage. Function-generated SQL,
|
|
115
|
-
conditional fragments, and dynamic filter lists remain outside static checking.
|
|
150
|
+
the mutation fragment. An oversized query reports its actual count, for example
|
|
151
|
+
`at most 65535 bound parameters; received 65536`. Defaults consume no parameters. Account for the
|
|
152
|
+
selected columns and surrounding query when choosing a batch size; Tenon never splits queries automatically.
|