@kequtech/tenon-orm 0.1.4 → 0.2.0

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