@kequtech/tenon-orm 0.2.0 → 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.
@@ -1,147 +1,135 @@
1
- # Adopting Tenon from another ORM
1
+ # Adopting Tenon
2
2
 
3
- This guide covers replacing an existing ORM and, separately, transferring migration ownership. For a
4
- query tutorial use the [human guide](human-guide.md); for API details use the
5
- [reference](reference.md). Preserve the existing schema, connection configuration, data, and
6
- application behavior while replacing the old ORM. Tenon does not need to own migrations to execute
7
- queries or generate types; keep the existing migration runner if that is the intended scope.
3
+ Replace application queries independently of migration ownership. Tenon can query an existing
4
+ schema and generate types while the current migration runner stays in place. For a Tenon version
5
+ upgrade, read the [Changelog](../CHANGELOG.md).
8
6
 
9
- ## Replace application queries
7
+ ## Preserve connections and behavior
10
8
 
11
- 1. Locate the old ORM's connection setup, schema, migrations, and call sites. Identify behavior
12
- supplied by the ORM: validation, IDs, timestamps, hooks, defaults, relation loading,
13
- serialization, and transaction boundaries.
14
- 2. If row and mutation input types will help your application, generate them from the existing database:
9
+ Keep the existing database URI, credentials, TLS requirements, schema, and data. Tenon can add
10
+ driver settings without parsing the URI:
15
11
 
16
- ```sh
17
- npx tenon generate --out src/generated-schema.d.ts
18
- ```
12
+ ```ts
13
+ import { createTenon } from '@kequtech/tenon-orm';
19
14
 
20
- Use `--namespace name` for a non-public schema. Generation reads the actual database and requires
21
- no Tenon migration history.
15
+ const db = createTenon({
16
+ connection: { url: existingUri, options: { connect_timeout: 5, idle_timeout: 10 } },
17
+ });
18
+ ```
22
19
 
23
- 3. Replace queries with parameterized SQL in ordinary functions with named object inputs where
24
- useful. Preserve each caller's projection, null handling, ordering, and transaction behavior.
25
- Generated types describe Postgres.js values: check bigint/numeric strings, dates, JSON, and arrays when
26
- the old ORM transformed them.
27
- 4. Inspect completed queries and test their behavior using the
28
- [inspection workflow](#inspect-queries-during-adoption) below. Declare result types explicitly
29
- and run tests against a dedicated test database. Verify
30
- writes and rollback, relationships, validation, and any hooks you moved into application code or
31
- SQL. A schema-preserving first release makes application rollback easier.
32
- 5. Remove the old runtime dependency and unused generated code after its callers are gone. Keep the
33
- migration tooling until the migration transition below is complete, if that transition is part of
34
- the work.
20
+ Translate old driver configuration into Postgres.js options rather than passing it through.
21
+ For example, pg's `connectionTimeoutMillis: 5000` becomes `connect_timeout: 5`, and
22
+ `idleTimeoutMillis: 10000` becomes `idle_timeout: 10`; both native options use seconds. Preserve
23
+ runtime `search_path` through URI startup parameters or `options.connection.search_path`.
24
+ URI startup parameters take precedence when both are supplied. CLI `--namespace` does not change
25
+ runtime queries. See [connection options](reference.md#installation-and-connection).
35
26
 
36
- ## Replace local write helpers
27
+ Before conversion, identify behavior supplied by the old ORM: IDs/defaults, timestamps,
28
+ validation, hooks, serialization, relationships, and transaction boundaries. Preserve it explicitly
29
+ in application code or SQL where required.
37
30
 
38
- Use `sql.insertValues(rows)` and `sql.assignments(patch)` in place of local object-to-SQL utilities:
31
+ ## Replace queries
39
32
 
40
- ```ts
41
- import { sql } from '@kequtech/tenon-orm';
33
+ 1. Optionally generate persistence types from the existing schema:
42
34
 
43
- const insert = sql`INSERT INTO profiles ${sql.insertValues(rows)} RETURNING id`;
44
- const update = sql`UPDATE profiles SET ${sql.assignments(patch)} WHERE id = ${id}`;
45
- ```
35
+ ```sh
36
+ npx tenon generate --namespace public --out src/generated-schema.d.ts
37
+ ```
46
38
 
47
- Compare omission, defaults, nulls, and empty-input behavior with the old helpers. Keep expressions
48
- in SQL and remove the local utilities after their callers move. Generated `Rows.Insert` and
49
- `Rows.Update` types can replace handwritten table inputs; retain application-specific refinements
50
- and validation. Run the existing write and rollback workflows. See the [mutation cookbook](mutations.md).
39
+ This needs no Tenon migration history. Use the application's namespace and commit exact output.
51
40
 
52
- ## Inspect queries during adoption
41
+ 2. Replace queries with parameterized SQL in ordinary functions accepting `QueryClient` where
42
+ callers may supply `db` or `tx`. Retain projections, ordering, null handling, and authorization.
43
+ 3. Review value conversions against [Tenon's mappings](reference.md#generated-types-and-checking):
44
+ bigint/numeric strings, dates, arrays, and native JSON may differ from the previous driver.
45
+ Generated declarations do not replace domain validation.
46
+ 4. Inspect meaningful completed query variants and run database assertions for actual rows,
47
+ permissions, write effects, and rollback. See [query inspection](query-inspection.md).
48
+ 5. Remove unused runtime dependencies and generated code after their callers are gone. Dependencies
49
+ retained by other application code or installed transitively may still be needed.
53
50
 
54
- Use `describeQuery` from `@kequtech/tenon-orm` on the completed query object, or
55
- `npx tenon describe query.sql` on a SQL file. Inspection is available with the normal Tenon
56
- installation and uses no TypeScript compiler API. It works with helpers and runtime-built
57
- fragments because it receives the final SQL. See [query inspection](query-inspection.md).
51
+ Check cardinality when replacing a first-row accessor. `oneOrNone` rejects multiple rows and `one`
52
+ also rejects zero rows. Use deterministic `ORDER BY` plus `LIMIT 1` when choosing one of several
53
+ matches is intended; modes do not add a limit.
58
54
 
59
- Reuse the application's connection URI and session configuration; compare the reported role,
60
- search path, and timezone with execution. Inspection uses a separate connection and Postgres.js
61
- read mappings. Custom parsers and transaction-local state remain application responsibilities.
55
+ Use `sql.insertValues` and `sql.assignments` to replace object-to-SQL utilities, checking omission,
56
+ default, null, and empty-input semantics. Generated `Rows.Insert`/`Rows.Update` types can replace
57
+ handwritten persistence inputs. See the [mutation cookbook](mutations.md).
62
58
 
63
- Pair descriptions with durable database tests of actual rows and effects. For each converted
64
- query, cover the meaningful filter/ownership branches, null relationships, JSON structure, empty
65
- results, ordering/cursors, and transaction failure/replay behavior as applicable. Describe's success
66
- does not verify a TypeScript annotation or prove that execution is permitted or succeeds. Preserve
67
- existing authorization policy and transaction boundaries while making queries available to tests.
59
+ Use `tx.query` for work that must commit together. Native pool calls do not join Tenon transactions.
60
+ For driver features, preserve resource ownership and recovery policy under the
61
+ [native access contract](reference.md#native-driver-access).
68
62
 
69
- Keep these tests in routine validation. A described query instance does not cover untested source
70
- queries or detect stale annotations elsewhere.
63
+ ## Transfer migration ownership
71
64
 
72
- ## Move migration ownership to Tenon
65
+ Choose one final checkpoint for the old runner. Bring existing databases to it and disable that
66
+ runner before handover; the runners do not share locks. Capture from a trusted reference database,
67
+ preferably a disposable replay of the legacy migrations.
73
68
 
74
- Choose one final schema checkpoint for the old runner and bring existing target databases to it.
75
- Disable its migration/schema synchronization before handing ownership to Tenon. The tools do not
76
- share locks.
69
+ ### Capture a baseline
77
70
 
78
- Capture one baseline from the reference database into an empty migration directory:
71
+ For an application owning `app` in a shared database:
79
72
 
80
73
  ```sh
81
- npx tenon baseline capture --directory migrations --namespace public
74
+ npx tenon baseline capture --directory migrations --namespace app --schema app
82
75
  ```
83
76
 
84
- This uses native `pg_dump --schema-only --no-owner --no-privileges` and writes
85
- `0001_baseline.up.sql` without a down file. It requires a compatible `pg_dump` on PATH; prefer the
86
- server's major. Default scope is the whole database schema. Repeat `--schema name` to select schemas
87
- explicitly, accounting for dependencies, extensions, and required roles. Tenon's selected history
88
- table is excluded; old ORM metadata is preserved for deliberate cleanup later.
89
-
90
- The baseline recreates application schema, not a complete deployment environment. It omits
91
- ownership, grants/revokes, and default privileges. PostgreSQL creates replayed objects under the
92
- migration role. Role definitions and memberships also remain outside the dump. Functions,
93
- policies, or other object definitions can still refer to roles; provision any referenced roles
94
- before replay. See the [PostgreSQL pg_dump reference](https://www.postgresql.org/docs/16/app-pgdump.html).
95
-
96
- For `SECURITY DEFINER` functions, the owner determines execution authority, and newly created
97
- functions ordinarily permit `PUBLIC` execution. A captured function body alone does not preserve
98
- its original security boundary. Keep fixed application policy, such as revoking `PUBLIC` execution,
99
- in reviewed SQL in the baseline before its first adoption or in a subsequent application migration.
100
- Deployment provisioning assigns the intended owners, schema access, and grants to runtime roles.
101
- Keeping all security setup in provisioning is also supported, provided it runs before application access.
102
-
103
- On fresh environments, provision required roles, apply migrations, then apply the deployment's
104
- ownership/grant policy before starting the application. Verify both allowed and denied operations,
105
- including execution of sensitive functions and access to their underlying tables. For a shared
106
- database, apply only the selected application's policy. On existing environments, confirm the same
107
- provisioning procedure is repeatable. After adoption, use subsequent migrations rather than editing
108
- checksummed history.
109
-
110
- Capture changes no source data, migration history, or sequence positions. Review and commit the SQL.
111
- Keep the capture header: it identifies native dump SQL that bypasses the authored-migration scanner.
112
- It is a convention, not proof of provenance; do not use it on handwritten migrations. Normal
113
- transactional execution and checksums still apply.
114
-
115
- On each existing database already at that checkpoint:
77
+ The directory must contain no SQL files. Capture uses native `pg_dump --schema-only --no-owner
78
+ --no-privileges` and writes `0001_baseline.up.sql` with no down file. Install compatible PostgreSQL
79
+ client tools on PATH; prefer the server's major version.
116
80
 
117
- ```sh
118
- npx tenon baseline adopt
119
- ```
81
+ | Option | Scope |
82
+ | --------------------- | ------------------------------------------------------------------------------------ |
83
+ | `--namespace app` | Tenon history table to exclude from capture; history/runner scope for later commands |
84
+ | `--schema app` | Objects selected by native `pg_dump`; repeat to select additional schemas |
85
+ | Runtime `search_path` | Configured independently on the application connection |
120
86
 
121
- Adoption records the baseline checksum without executing the SQL or comparing schemas. It requires
122
- the selected namespace to exist. Repeating matching adoption is harmless, including after later
123
- migrations; conflicting history fails.
87
+ Omitting `--schema` captures the whole database schema, even with a custom namespace. Capture does
88
+ not modify source data/history or preserve sequence positions. It excludes Tenon's selected history
89
+ table but can retain the old runner's metadata tables. Review and commit the SQL. Preserve the
90
+ capture header, which opts this reviewed dump into native baseline parsing; do not add it to
91
+ handwritten migrations.
124
92
 
125
- On a fresh database, run migrations instead:
93
+ A scoped dump can refer to objects it does not include. Provision external schemas, functions,
94
+ extensions, and referenced roles before they are needed. A fresh replay must execute representative
95
+ operations, not just reproduce generated relation types. Tenon requires no particular extension.
126
96
 
127
- ```sh
128
- npx tenon migrate
129
- ```
97
+ ### Check and adopt an existing database
130
98
 
131
- That executes the baseline and subsequent migrations. Use the same committed baseline for every
132
- environment. Do not run adoption unconditionally on fresh databases. Existing databases adopt once
133
- and then use ordinary `migrate`.
99
+ Verify the final legacy checkpoint before adoption. If using schema comparisons, freeze separate
100
+ reference definitions rather than comparing against types regenerated after later migrations.
101
+ `generateDefinitions` covers relation metadata, not functions or privileges; include reviewed
102
+ catalog checks for other objects the application needs. Tenon does not perform these comparisons.
134
103
 
135
- Use the development [`db:migrate` script](human-guide.md#create-a-table) to apply subsequent
136
- migrations and update table types together. Production continues to run only `npx tenon migrate`.
137
- Verify both an existing database's upgrade and a fresh replay. Baselines contain schema only; they
138
- do not back up rows or sequence positions. The missing down file prevents normal rollback through
139
- the baseline. Keep the application's backup and recovery procedure.
140
-
141
- Programmatic equivalents:
104
+ ```sh
105
+ npx tenon baseline adopt --directory migrations --namespace app
106
+ npx tenon migrate --directory migrations --namespace app
107
+ ```
142
108
 
143
- - `captureBaseline({ connection, directory?, namespace?, schemas?, onWarning? })` requires a
144
- nonempty native libpq connection string and returns `{ id, upPath }`. Omit `schemas` for the whole
145
- database schema; an explicitly empty list is rejected.
146
- - `adoptBaseline({ connection, directory?, namespace? })` accepts the usual Tenon connection options
147
- and returns newly recorded migration IDs.
109
+ Adoption records the baseline checksum without executing its SQL. The namespace must already
110
+ exist. Repeating matching adoption is harmless, including after later migrations; history conflicts
111
+ fail. Once Tenon owns history, add forward migrations rather than editing the baseline.
112
+
113
+ ### Replay a fresh database
114
+
115
+ Run `tenon migrate --directory migrations --namespace app`, without adoption. It executes the same
116
+ committed baseline and subsequent migrations. Apply the deployment's required roles, ownership,
117
+ and grants before application access.
118
+
119
+ The portable baseline omits ownership, grants/revokes, and default privileges. Objects are recreated
120
+ under the migration role. For `SECURITY DEFINER` functions, that owner controls execution authority,
121
+ and new functions ordinarily permit `PUBLIC` execution. Keep fixed policy such as revoking public
122
+ execution in reviewed baseline SQL before first adoption or in a forward migration. Deployment
123
+ provisioning supplies named roles, owners, and grants. Verify permitted and denied operations on
124
+ both adopted and fresh environments. See the
125
+ [pg_dump contract](https://www.postgresql.org/docs/16/app-pgdump.html).
126
+
127
+ For development, generate types after migrating; production runs migrations and consumes committed
128
+ types. Test existing-row preservation, repeatable adoption, subsequent migrations, and fresh replay.
129
+ A schema-only baseline and down migration are not backups. The missing down file prevents rollback
130
+ across the baseline; use the application's backup/recovery procedure for that boundary.
131
+
132
+ Programmatic capture is `captureBaseline({ connection, directory?, namespace?, schemas?, onWarning? })`
133
+ and returns `{ id, upPath }`. It requires a native libpq connection string; an explicitly empty
134
+ `schemas` list fails. `adoptBaseline({ connection, directory?, namespace? })` accepts Tenon's runtime
135
+ connection forms and returns newly recorded IDs.