@jarenjs/db 0.84.3 → 0.85.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.
- package/ARCHITECTURE.md +20 -3
- package/README.md +11 -0
- package/docs/MODEL-FORMAT.md +14 -4
- package/docs/NATIVE-PLANS.md +24 -7
- package/docs/SQLITE-RELATIONAL.md +190 -0
- package/package.json +20 -4
- package/schemas/jaren-model.authoring.schema.json +157 -0
- package/schemas/jaren-model.draft-07.schema.json +157 -0
- package/schemas/jaren-model.schema.json +157 -0
- package/src/capture.js +4 -2
- package/src/ddl.js +8 -3
- package/src/dialects/sqlite-relational.js +314 -0
- package/src/dialects/sqlite-schema.js +142 -0
- package/src/dialects/sqlite.js +17 -0
- package/src/drivers/bun.js +26 -15
- package/src/drivers/node.js +2 -0
- package/src/drivers/snapshot.js +49 -0
- package/src/emit.js +37 -5
- package/src/engine-metadata.js +18 -0
- package/src/index.js +4 -1
- package/src/introspect.js +1 -2
- package/src/jobs.js +4 -2
- package/src/migrate.js +12 -16
- package/src/model-api.js +4 -0
- package/src/model.js +12 -0
- package/src/mutation.js +66 -14
- package/src/physical.js +37 -7
- package/src/plan.js +66 -3
- package/src/query-api.js +5 -0
- package/src/query.js +39 -6
- package/src/relational-api.js +6 -0
- package/src/store.js +6 -4
- package/src/table-migration.js +158 -0
- package/types/bun.d.ts +3 -0
- package/types/entity.d.ts +1 -0
- package/types/index.d.ts +9 -2
- package/types/model.d.ts +1 -0
- package/types/node.d.ts +3 -0
- package/types/query.d.ts +2 -0
- package/types/relational.d.ts +114 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -1167,8 +1167,10 @@ calls with different bound values.
|
|
|
1167
1167
|
`physical.js` compiles column codecs and verifies declarations against that
|
|
1168
1168
|
inventory. The existing entity core, tracker and graph row merger execute both
|
|
1169
1169
|
hybrid and column layouts; there is no separate relational store. The query
|
|
1170
|
-
planner
|
|
1171
|
-
keyset continuation until its identity semantics
|
|
1170
|
+
planner preserves codec semantics through qualified native plans and explicit
|
|
1171
|
+
residuals, and refuses physical keyset continuation until its identity semantics
|
|
1172
|
+
are qualified. Present-null scalar predicates and safe fluent projection
|
|
1173
|
+
flattening join this native subset; strict binding failures stop before a scan.
|
|
1172
1174
|
|
|
1173
1175
|
`sql.js` binds trusted statements to `store.js` transaction views. It reuses the
|
|
1174
1176
|
read classifier's tokenizer and the driver's scope owner. Writes invalidate all
|
|
@@ -1176,6 +1178,21 @@ clean tracked entities; pending edits and incomplete capture populations refuse.
|
|
|
1176
1178
|
`invariants.js` uses the shared Query compiler for store rules; the dialect lowers
|
|
1177
1179
|
a bounded database subset into ordered trigger bodies. `migrate.js` reuses its
|
|
1178
1180
|
existing rebuild/receipt transaction and verifies preservation before publication.
|
|
1179
|
-
The backup publisher
|
|
1181
|
+
The backup publisher uses Node online backup and Bun disk-backed VACUUM INTO.
|
|
1182
|
+
The standalone snapshot helper shares destination reservation and cleanup across
|
|
1183
|
+
both hosts, including transaction failures.
|
|
1180
1184
|
|
|
1181
1185
|
`src/search.js` composes core lexical mechanics and the JSON predicate compiler over complete bounded entity snapshots. Committed capture and data-version checks invalidate derived state; SHA-256 source content validates persisted caches across reopen. Snapshot storage uses existing collection transactions. See [search execution](docs/SEARCH.md); native full-text dialects remain unqualified.
|
|
1186
|
+
|
|
1187
|
+
`dialects/sqlite-relational.js` owns the explicit SQLite expression and statement
|
|
1188
|
+
compiler; `dialects/sqlite-schema.js` reuses it for ordered tables, indexes and
|
|
1189
|
+
triggers. `table-migration.js` owns reviewed source/target guards, native copying
|
|
1190
|
+
and preservation checks, with catalog/pragma spellings in the SQLite dialect.
|
|
1191
|
+
The supplied driver owns transactions and cursors. These programs are explicitly
|
|
1192
|
+
SQLite-semantic and never enter the JSON residual evaluator. See
|
|
1193
|
+
[the native SQLite contract](docs/SQLITE-RELATIONAL.md).
|
|
1194
|
+
|
|
1195
|
+
`engine-metadata.js` holds inert version/table constants so schema inspection
|
|
1196
|
+
does not import runtime owners. `/query`, `/model` and `/entity` expose those
|
|
1197
|
+
mechanisms without the root store import. `compileEntityModel` performs one model
|
|
1198
|
+
normalization for both entities and mapping; openStore reuses that result.
|
package/README.md
CHANGED
|
@@ -1379,6 +1379,10 @@ Every subpath a consumer can import, derived from the manifest by
|
|
|
1379
1379
|
| `@jarenjs/db/package.json` | metadata | — |
|
|
1380
1380
|
| `@jarenjs/db/node-worker` | JavaScript | declared |
|
|
1381
1381
|
| `@jarenjs/db/node-pool` | JavaScript | declared |
|
|
1382
|
+
| `@jarenjs/db/relational` | JavaScript | declared |
|
|
1383
|
+
| `@jarenjs/db/query` | JavaScript | declared |
|
|
1384
|
+
| `@jarenjs/db/model` | JavaScript | declared |
|
|
1385
|
+
| `@jarenjs/db/entity` | JavaScript | declared |
|
|
1382
1386
|
<!--/fact-->
|
|
1383
1387
|
|
|
1384
1388
|
|
|
@@ -1431,3 +1435,10 @@ SQLite constraint/audit triggers for installation through that migration boundar
|
|
|
1431
1435
|
Native column reads and bounded mutation documents are specified in [NATIVE-PLANS](docs/NATIVE-PLANS.md), including SQL census coverage, resource accounting and refusals.
|
|
1432
1436
|
|
|
1433
1437
|
`@jarenjs/db/search` composes the resident ranker with bounded authoritative entity reads and optional atomic snapshot storage. See [persisted search](docs/SEARCH.md).
|
|
1438
|
+
|
|
1439
|
+
Column-first table definitions, guarded same-connection rebuilds, exact matched writes,
|
|
1440
|
+
partial conflicts, raw-text JSON queries and byte-valued operations use
|
|
1441
|
+
[`@jarenjs/db/relational`](docs/SQLITE-RELATIONAL.md). Both host entries export
|
|
1442
|
+
`snapshotDatabase(connection, newPath)` for a disk-backed committed-WAL copy.
|
|
1443
|
+
Existing-connection consumers can import `/query`, `/model` and `/entity`;
|
|
1444
|
+
`compileEntityModel` shares one normalization between queries and mapping.
|
package/docs/MODEL-FORMAT.md
CHANGED
|
@@ -2352,7 +2352,9 @@ relation navigation across physical layouts is not qualified.
|
|
|
2352
2352
|
| `decimal` | exact decimal string, including trailing zeros | TEXT |
|
|
2353
2353
|
| `blob-hex` | lowercase hexadecimal string | BLOB |
|
|
2354
2354
|
|
|
2355
|
-
Unsafe narrowing refuses `JD2003`. A byte handle never enters the
|
|
2355
|
+
Unsafe narrowing refuses `JD2003`. A byte handle never enters the JSON-facing entity;
|
|
2356
|
+
[the native SQLite channel](SQLITE-RELATIONAL.md#exact-writes-and-bytes) preserves
|
|
2357
|
+
Uint8Array/Buffer values on the same synchronous connection.
|
|
2356
2358
|
`null: "null"` maps SQL NULL to present JSON null; `"absent"` omits the property;
|
|
2357
2359
|
`"reject"` refuses it. With the JSON codec, JSON null is stored as the text `null`,
|
|
2358
2360
|
so SQL NULL can independently mean absence. `default: "database"` omits an absent
|
|
@@ -2361,9 +2363,10 @@ writes to the database. A generated integer identity uses the existing
|
|
|
2361
2363
|
`x-entity.default: "auto"` declaration. Direct updates with identical values and
|
|
2362
2364
|
identical tracked saves produce no effective writes.
|
|
2363
2365
|
|
|
2364
|
-
Mapped query documents
|
|
2365
|
-
|
|
2366
|
-
|
|
2366
|
+
Mapped query documents use the qualified native subset in [NATIVE-PLANS](NATIVE-PLANS.md).
|
|
2367
|
+
Present-null scalar equality/ranges preserve Jaren semantics. Unsupported codecs
|
|
2368
|
+
and shapes retain explicit residuals; strict mode also rejects unsupported bound
|
|
2369
|
+
externals before fetching rows. Scalar graph loads use mapped names.
|
|
2367
2370
|
Physical `page` and `after` continuation refuse until codec-aware keysets are
|
|
2368
2371
|
qualified; an explicit `take`/`skip` load remains available.
|
|
2369
2372
|
Capture/live/replication for adopted application triggers is not qualified and
|
|
@@ -2414,3 +2417,10 @@ must stay in the safe-number range.
|
|
|
2414
2417
|
## Native column mutation documents
|
|
2415
2418
|
|
|
2416
2419
|
Asynchronous entity sets expose `mutate(document)` for conditional updates, conflict-aware upserts and bounded same-entity insert-select. The closed grammar, no-op/revision behavior, output bounds and transactional qualifications are specified in [NATIVE-PLANS](NATIVE-PLANS.md).
|
|
2420
|
+
|
|
2421
|
+
Explicit physical DDL metadata and live-schema migration planning are specified
|
|
2422
|
+
in [SQLITE-RELATIONAL](SQLITE-RELATIONAL.md#physical-schema-ownership). Opening a
|
|
2423
|
+
physical entity retains adoption-only behavior. Public plans create complete
|
|
2424
|
+
ordered declarations; an incomplete generated/default definition or view remains
|
|
2425
|
+
adoption metadata. SQLite expressions preserve a separate, explicit semantic
|
|
2426
|
+
contract for raw text, bytes, nulls, collations and floating totals.
|
package/docs/NATIVE-PLANS.md
CHANGED
|
@@ -49,6 +49,13 @@ Floating sums/averages and date/datetime grouping remain residual. Physical text
|
|
|
49
49
|
comparisons explicitly use codepoint collation, independent of a column's declared
|
|
50
50
|
collation; an incompatible index may therefore stop helping that query.
|
|
51
51
|
|
|
52
|
+
Safe identity/object projection chains, including a projected join followed by
|
|
53
|
+
filtering and ordering, flatten without crossing windows, grouping or inner
|
|
54
|
+
ordering. Standalone min/max over supported scalar columns and sum/avg over safe
|
|
55
|
+
integer columns now lower natively, preserving empty-sequence and present-null
|
|
56
|
+
errors. Missing or unbindable external values refuse strict execution and both
|
|
57
|
+
cursor APIs before any decoded fetch; a nullable slot accepts explicit null.
|
|
58
|
+
|
|
52
59
|
`explain` reports the emitted SQL, scan narrative, profile bounds and last
|
|
53
60
|
execution's admitted statement/returned-row/serialized-wire-byte counts. These
|
|
54
61
|
are application admission costs. SQLite does not expose visited-row counts or
|
|
@@ -73,12 +80,13 @@ const result = await store.entity('Inventory').mutate({
|
|
|
73
80
|
});
|
|
74
81
|
```
|
|
75
82
|
|
|
76
|
-
The
|
|
83
|
+
The operations are:
|
|
77
84
|
|
|
78
85
|
| Operation | Required document members | Meaning |
|
|
79
86
|
|---|---|---|
|
|
80
|
-
| `update` | `key`, `set`; `expectedRevision` for a versioned entity |
|
|
81
|
-
| `upsert` | `values`, `conflict
|
|
87
|
+
| `update` | `key` and/or `where`, `set` and/or `expressions`; `expectedRevision` for a versioned entity | Exact assignments with an atomic predicate. Default changed reporting suppresses identical writes; `reporting: 'matched'` executes them. A declared version increments once on a write. |
|
|
88
|
+
| `upsert` | `values`, `conflict`; `update` or `onConflict: 'nothing'` | Ordered logical conflict columns may name a non-primary UNIQUE identity; `conflictWhere` matches a partial index. Default changed reporting suppresses identical updates. |
|
|
89
|
+
| `delete` | `key` and/or `where`; `expectedRevision` for a versioned entity | One conditional or bulk delete, with the same transactional output bounds. |
|
|
82
90
|
| `insert-select` | `source`, `where`, `select`, `conflict`, `onConflict: 'nothing'` | Same-entity scalar/literal projection, bounded source rows, conflict-ignore insertion. Source and target path codecs and NULL policies must match. |
|
|
83
91
|
|
|
84
92
|
`returning` is a nonempty list of logical stored members (default: all). `maxRows`
|
|
@@ -92,14 +100,23 @@ before insertion, even if all rows would conflict. Output bounds and codec/schem
|
|
|
92
100
|
validation run inside the transaction; failure rolls back rows and trigger effects.
|
|
93
101
|
The byte bound checks decoded output, not a database allocation interrupt.
|
|
94
102
|
The statement's `RETURNING` view follows SQLite timing; later AFTER-trigger
|
|
95
|
-
modifications are not an extra readback.
|
|
96
|
-
|
|
103
|
+
modifications are not an extra readback. Changed-reporting same-input literal replay yields no effective write, revision
|
|
104
|
+
increment or additional trigger effects. Matched reporting and arithmetic
|
|
105
|
+
expressions intentionally may write on repeat.
|
|
97
106
|
|
|
98
107
|
Mutations are untracked. Re-read affected rows before subsequent tracked editing;
|
|
99
108
|
a previously tracked revision remains stale and retains normal conflict checks.
|
|
100
109
|
Use `tx.entity(name).mutate(document)` to compose writes, receipts and jobs in one
|
|
101
|
-
transaction. An outer failure rolls everything back.
|
|
102
|
-
|
|
110
|
+
transaction. An outer failure rolls everything back. `entityCore` on a synchronous
|
|
111
|
+
connection also settles these mutations synchronously. `where` accepts a native
|
|
112
|
+
Jaren predicate or structural `sql` expression; `expressions` maps exact logical
|
|
113
|
+
assignment names to `sql` expressions, with logical columns resolved to physical
|
|
114
|
+
names. A member cannot appear in both `set` and `expressions`.
|
|
115
|
+
|
|
116
|
+
For cross-table insert-select, expression conflict updates, raw JSON text, bytes
|
|
117
|
+
and complete SQLite null/collation semantics use the explicit
|
|
118
|
+
[native SQLite surface](SQLITE-RELATIONAL.md). It shares expression emission with
|
|
119
|
+
entity mutations while retaining its own native SQL result contract.
|
|
103
120
|
|
|
104
121
|
## Bounded ranges and live qualification
|
|
105
122
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Native SQLite programs on an existing connection
|
|
2
|
+
|
|
3
|
+
`@jarenjs/db/relational` provides structural SQL authoring, physical table DDL,
|
|
4
|
+
and guarded migrations. `relational(connection)` requires an available
|
|
5
|
+
synchronous SQLite connection from `@jarenjs/db/node` or `@jarenjs/db/bun`.
|
|
6
|
+
It opens no store and uses the supplied connection and transaction. Expressions
|
|
7
|
+
are closed `$sql` nodes built by `sql`; strings are bound values, never SQL
|
|
8
|
+
fragments. Identifiers are quoted as single names. `planRelational` returns
|
|
9
|
+
`{ sql, params, access }` for review without reading the database.
|
|
10
|
+
|
|
11
|
+
## Choose the expression semantics
|
|
12
|
+
|
|
13
|
+
| Surface | Null, text and numeric semantics | Execution |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| LINQ / JSON query over mapped entities | Jaren equality, codepoint text comparison, declared codecs; present null remains null | Qualified native plan; `strict: true` refuses any required residual, including unsupported external bindings |
|
|
16
|
+
| `sql` expressions and `relational` | SQLite three-valued logic, native affinities, explicit BINARY/NOCASE/RTRIM, native aggregates | One native statement; no decoded-row fallback |
|
|
17
|
+
|
|
18
|
+
For example, Jaren equality regards two present nulls as equal, while SQLite
|
|
19
|
+
`=` yields SQL NULL and `IS` supplies a null-safe comparison. Jaren sum over an
|
|
20
|
+
empty sequence is zero and a present null is a type error. SQLite `sum` skips
|
|
21
|
+
null and returns null without inputs; `total` returns floating zero. SQLite
|
|
22
|
+
floating accumulation and ASCII NOCASE are deliberate choices on this surface.
|
|
23
|
+
No mapping must omit a null member to use native SQL.
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { relational, sql } from '@jarenjs/db/relational';
|
|
27
|
+
const r = relational(connection);
|
|
28
|
+
const c = sql.column;
|
|
29
|
+
const query = {
|
|
30
|
+
from: { table: 'staging', as: 's' },
|
|
31
|
+
columns: {
|
|
32
|
+
id: c('id', 's'), raw: c('body', 's'),
|
|
33
|
+
price: sql.cast(sql.call('json_extract', [c('body', 's'), '$.price']), 'REAL'),
|
|
34
|
+
},
|
|
35
|
+
where: sql.binary('IS', c('supplier', 's'), sql.param('supplier')),
|
|
36
|
+
orderBy: [{ by: sql.collate(c('sku', 's'), 'NOCASE'), nulls: 'first' }],
|
|
37
|
+
};
|
|
38
|
+
for (const row of r.iterate(query, { externals: { supplier: null } })) {
|
|
39
|
+
consume(row); // original raw text and native byte values are preserved
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`all`, `get`, `iterate` and `execute` settle synchronously. `iterate` uses the
|
|
44
|
+
shared cursor lifecycle, releases its statement on early return or failure,
|
|
45
|
+
and reports `streaming`/`barrier` from the driver. `all` intentionally collects
|
|
46
|
+
the result. SQLite may itself sort or build temporary query structures.
|
|
47
|
+
|
|
48
|
+
Selections support table and subquery sources, inner/left/cross joins, correlated
|
|
49
|
+
`sql.scalar`/`sql.exists`, CASE, IN/NOT IN, DISTINCT, grouped and distinct
|
|
50
|
+
aggregates, HAVING, UNION/UNION ALL and ordered windows. The declaration file
|
|
51
|
+
lists the closed operators and functions, including trim/coalesce, LIKE, JSON
|
|
52
|
+
extraction, casts and SQLite date functions. Neither arbitrary function names
|
|
53
|
+
nor a raw-expression escape hatch is accepted.
|
|
54
|
+
|
|
55
|
+
## Exact writes and bytes
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const result = r.execute({
|
|
59
|
+
op: 'update', table: 'claims',
|
|
60
|
+
set: { revision: sql.binary('+', c('revision'), 1), status: 'done' },
|
|
61
|
+
where: sql.binary('AND',
|
|
62
|
+
sql.binary('=', c('revision'), sql.param('expected')),
|
|
63
|
+
sql.binary('=', c('owner'), sql.param('owner'))),
|
|
64
|
+
}, { externals: { expected: 3, owner: 'worker-1' } });
|
|
65
|
+
// result.affected is SQLite's direct statement count, excluding trigger writes.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
UPDATE emits exactly the supplied assignments. `reporting: 'matched'` is the
|
|
69
|
+
native surface's default and executes identical assignments, so UPDATE OF and
|
|
70
|
+
immutability triggers run. `reporting: 'changed'` adds a BINARY, null-safe
|
|
71
|
+
comparison to suppress unchanged rows. UPDATE and DELETE require an explicit
|
|
72
|
+
`where`; literal `1` authorizes every row. Predicates and arithmetic belong to
|
|
73
|
+
one statement. The existing entity updater retains changed-row behavior;
|
|
74
|
+
`entity.mutate` adds explicit matched reporting and expression assignments.
|
|
75
|
+
|
|
76
|
+
INSERT accepts `values` or a `source` selection with ordered target `columns`.
|
|
77
|
+
A source may read another table or an intermediate legacy shape. `ignore: true`
|
|
78
|
+
uses INSERT OR IGNORE. `conflict` accepts ordered column/expression targets, an
|
|
79
|
+
optional partial-index `where`, and `action: 'nothing'` or `'update'` with an
|
|
80
|
+
exact `set` and optional `updateWhere`. Use `sql.column(name, 'excluded')` for
|
|
81
|
+
incoming values. Conflict-target expressions contain literal schema values so
|
|
82
|
+
SQLite can match its index. No read-then-write emulation occurs.
|
|
83
|
+
|
|
84
|
+
`Uint8Array` and Node `Buffer` values bind as bytes; returned binary values stay
|
|
85
|
+
bytes. Subarray offsets and zero bytes survive without hex strings or JSON
|
|
86
|
+
arrays. Combine metadata, image, conflict and history writes inside the existing
|
|
87
|
+
synchronous transaction; failure rolls back all of them. Optional `returning`
|
|
88
|
+
collects rows using SQLite RETURNING timing, before subsequent AFTER-trigger
|
|
89
|
+
changes. Unlike bounded entity mutation documents, this explicit native surface
|
|
90
|
+
has no automatic result row/byte limit; use a bounded selection or streaming read
|
|
91
|
+
when handling large results.
|
|
92
|
+
|
|
93
|
+
## Physical schema ownership
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { defineTable, planTable, planTableMigration, applyTableMigration } from '@jarenjs/db/relational';
|
|
97
|
+
const history = defineTable({
|
|
98
|
+
name: 'history', primaryKey: ['id'],
|
|
99
|
+
columns: [
|
|
100
|
+
{ name: 'id', type: 'INTEGER', identity: 'autoincrement', nullable: false },
|
|
101
|
+
{ name: 'body', type: 'TEXT', nullable: false },
|
|
102
|
+
{ name: 'revision', type: 'INTEGER', default: 1, nullable: false },
|
|
103
|
+
],
|
|
104
|
+
indexes: [{ name: 'history_revision', terms: [{ by: c('revision'), direction: 'desc' }] }],
|
|
105
|
+
triggers: [{ name: 'history_immutable', timing: 'before', event: 'update',
|
|
106
|
+
steps: [{ raise: { action: 'abort', message: 'immutable history' } }] }],
|
|
107
|
+
});
|
|
108
|
+
const ddl = planTable(history).createSql;
|
|
109
|
+
const migration = planTableMigration(connection, history, { id: 'history-v2', allowRebuild: true });
|
|
110
|
+
// Inspect migration.statements and migration.finish before applying the plan.
|
|
111
|
+
applyTableMigration(connection, migration);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Columns retain declaration order and exact INTEGER/REAL/TEXT/BLOB/NUMERIC/ANY
|
|
115
|
+
types. Definitions support ordered primary keys, rowid or AUTOINCREMENT identity,
|
|
116
|
+
nullability, database defaults, generated columns, STRICT/WITHOUT ROWID, named
|
|
117
|
+
UNIQUE/CHECK/foreign-key constraints and delete/update actions. Index terms
|
|
118
|
+
support expressions, direction and collation, with an optional partial predicate.
|
|
119
|
+
Triggers support BEFORE/AFTER, INSERT/UPDATE/DELETE, UPDATE OF, OLD/NEW conditions,
|
|
120
|
+
mutation steps and RAISE. Schema expressions use the same structural emitter,
|
|
121
|
+
with quoted inline literals because SQLite disallows schema parameters.
|
|
122
|
+
|
|
123
|
+
Entity `.physical(...)` metadata can also supply column `type`, `defaultValue`,
|
|
124
|
+
`collation`, `identity`, `check`, `generatedExpression` and `stored`, plus table
|
|
125
|
+
`constraints`, `indexes`, `triggers`, `strict` and `withoutRowid`.
|
|
126
|
+
`planEntity(...).createSql` now emits explicit table DDL for complete writable
|
|
127
|
+
declarations; views and incomplete generated/default definitions remain adoption
|
|
128
|
+
metadata. `openStore` still verifies physical tables without creating them.
|
|
129
|
+
Identical physical models produce an empty `planModelMigration`; changed
|
|
130
|
+
physical tables use the live-schema `planTableMigration` API.
|
|
131
|
+
|
|
132
|
+
## Guarded upgrades
|
|
133
|
+
|
|
134
|
+
Planning inspects the existing schema without modifying it. A differing existing
|
|
135
|
+
table requires `allowRebuild: true`; additions currently use the same guarded
|
|
136
|
+
rebuild. Every removed column needs `dropColumns`; removing an existing explicit
|
|
137
|
+
index/trigger requires `dropObjects`. Unmentioned indexes and triggers survive.
|
|
138
|
+
An optional `copy` maps writable non-key target columns to structural expressions;
|
|
139
|
+
new columns otherwise use their defaults. The plan retains the source schema and
|
|
140
|
+
an exact canonical checksum. Applying an edited plan or a stale source refuses.
|
|
141
|
+
A target already matching the reviewed plan is a no-op on repeat.
|
|
142
|
+
|
|
143
|
+
Execution creates a replacement, copies natively, verifies row counts and
|
|
144
|
+
unchanged values/storage classes, drops/renames atomically, restores indexes and
|
|
145
|
+
triggers, and checks foreign keys and the target schema. Primary-key columns,
|
|
146
|
+
unshadowed hidden rowids, raw text, bytes and AUTOINCREMENT high-water marks are
|
|
147
|
+
preserved. A key change, rowid-ownership change or ambiguous rowid alias refuses.
|
|
148
|
+
No migration history table or model adoption is needed.
|
|
149
|
+
|
|
150
|
+
Rebuilds require SQLite's foreign-key transition outside a transaction. For a
|
|
151
|
+
nested upgrade use `withForeignKeysSuspended(connection, () => { ... })` as the
|
|
152
|
+
outer scope, then nested `connection.transaction`/migration calls share savepoints.
|
|
153
|
+
The helper owns an IMMEDIATE transaction, checks references, and restores
|
|
154
|
+
`foreign_keys` and `legacy_alter_table` after success or failure. Its callback must
|
|
155
|
+
settle synchronously. A rebuild inside an already open FK-enabled transaction
|
|
156
|
+
refuses before DDL. Node/Bun regressions cover populated history and references,
|
|
157
|
+
failed copies, repeat reopening, nested rollback, and process death after DROP
|
|
158
|
+
with WAL recovery. These tests do not establish power-loss durability.
|
|
159
|
+
|
|
160
|
+
## Read-only inspection and disk snapshots
|
|
161
|
+
|
|
162
|
+
`readSchema` from `@jarenjs/db/model` inventories tables without adopting them,
|
|
163
|
+
but excludes engine bookkeeping. For a scanner that must include framework tables,
|
|
164
|
+
select table names directly from `sqlite_schema` with `relational`, or use the
|
|
165
|
+
public `connection.dialect.introspect.tables()` statement. A native selection can use `sql.call('typeof', [sql.column(name)])` and
|
|
166
|
+
`sql.column('rowid')` to inspect actual text storage in otherwise unknown shapes.
|
|
167
|
+
Declared affinity does not determine a value's storage class. WITHOUT ROWID tables
|
|
168
|
+
and shadowed rowid aliases require an explicit different identity selection.
|
|
169
|
+
|
|
170
|
+
Both host driver entries export `snapshotDatabase(connection, newPath)`. It
|
|
171
|
+
reserves a new destination, refuses any existing file (even empty), includes
|
|
172
|
+
committed WAL data through VACUUM INTO, syncs the output and cleans up a failed
|
|
173
|
+
copy. It allocates no database-sized JavaScript image. SQLite page caches and
|
|
174
|
+
its temporary storage govern native working memory. The copy runs synchronously
|
|
175
|
+
inside the asynchronous filesystem operation; it is not an incremental-progress
|
|
176
|
+
or interruption API. A snapshot inside an active transaction fails and cleans up.
|
|
177
|
+
Explicit keys, retained histories and bytes survive; [SQLite VACUUM](https://www.sqlite.org/lang_vacuum.html)
|
|
178
|
+
may renumber unaliased rowids, so this is not a page-identical archival copy. Bun store backups
|
|
179
|
+
use the same disk-backed path; Node store backup keeps its online-backup binding.
|
|
180
|
+
|
|
181
|
+
## Lightweight engines
|
|
182
|
+
|
|
183
|
+
Use `@jarenjs/db/query`, `/model` and `/entity` for existing-connection consumers;
|
|
184
|
+
`/relational` needs neither entity normalization nor a query plan cache.
|
|
185
|
+
`compileEntityModel(model)` returns `{ entities, mapping }` from one normalization,
|
|
186
|
+
avoiding the duplicated work of separate `normalizeEntities` and `explainMapping`
|
|
187
|
+
calls. No global strong model cache is introduced. Keep connection/query caches
|
|
188
|
+
bounded and reuse compiled metadata. Smaller import graphs alone do not prove
|
|
189
|
+
the complete application meets its RSS budget; measure application memory with
|
|
190
|
+
representative workloads.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/db",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.85.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
@@ -48,6 +48,22 @@
|
|
|
48
48
|
"./node-pool": {
|
|
49
49
|
"types": "./types/node-pool.d.ts",
|
|
50
50
|
"default": "./src/drivers/node-pool.js"
|
|
51
|
+
},
|
|
52
|
+
"./relational": {
|
|
53
|
+
"types": "./types/relational.d.ts",
|
|
54
|
+
"default": "./src/relational-api.js"
|
|
55
|
+
},
|
|
56
|
+
"./query": {
|
|
57
|
+
"types": "./types/query.d.ts",
|
|
58
|
+
"default": "./src/query-api.js"
|
|
59
|
+
},
|
|
60
|
+
"./model": {
|
|
61
|
+
"types": "./types/model.d.ts",
|
|
62
|
+
"default": "./src/model-api.js"
|
|
63
|
+
},
|
|
64
|
+
"./entity": {
|
|
65
|
+
"types": "./types/entity.d.ts",
|
|
66
|
+
"default": "./src/entity.js"
|
|
51
67
|
}
|
|
52
68
|
},
|
|
53
69
|
"files": [
|
|
@@ -88,9 +104,9 @@
|
|
|
88
104
|
"prepack": "npm run build:types"
|
|
89
105
|
},
|
|
90
106
|
"dependencies": {
|
|
91
|
-
"@jarenjs/core": "^0.
|
|
92
|
-
"@jarenjs/json": "^0.
|
|
93
|
-
"@jarenjs/validate": "^0.
|
|
107
|
+
"@jarenjs/core": "^0.85.0",
|
|
108
|
+
"@jarenjs/json": "^0.85.0",
|
|
109
|
+
"@jarenjs/validate": "^0.85.0"
|
|
94
110
|
},
|
|
95
111
|
"bin": {
|
|
96
112
|
"jaren-db": "./src/cli.js"
|
|
@@ -233,10 +233,167 @@
|
|
|
233
233
|
},
|
|
234
234
|
"generated": {
|
|
235
235
|
"type": "boolean"
|
|
236
|
+
},
|
|
237
|
+
"type": {
|
|
238
|
+
"enum": [
|
|
239
|
+
"INTEGER",
|
|
240
|
+
"REAL",
|
|
241
|
+
"TEXT",
|
|
242
|
+
"BLOB",
|
|
243
|
+
"NUMERIC"
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
"defaultValue": {
|
|
247
|
+
"description": "A literal SQLite value or closed structural $sql expression; planTable validates its operator and contextual semantics.",
|
|
248
|
+
"anyOf": [
|
|
249
|
+
{
|
|
250
|
+
"type": [
|
|
251
|
+
"string",
|
|
252
|
+
"number",
|
|
253
|
+
"null"
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"type": "object",
|
|
258
|
+
"required": [
|
|
259
|
+
"$sql"
|
|
260
|
+
],
|
|
261
|
+
"properties": {
|
|
262
|
+
"$sql": {
|
|
263
|
+
"enum": [
|
|
264
|
+
"column",
|
|
265
|
+
"value",
|
|
266
|
+
"binary",
|
|
267
|
+
"not",
|
|
268
|
+
"in",
|
|
269
|
+
"call",
|
|
270
|
+
"cast",
|
|
271
|
+
"collate",
|
|
272
|
+
"case",
|
|
273
|
+
"scalar",
|
|
274
|
+
"exists"
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
"collation": {
|
|
282
|
+
"enum": [
|
|
283
|
+
"BINARY",
|
|
284
|
+
"NOCASE",
|
|
285
|
+
"RTRIM"
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
"identity": {
|
|
289
|
+
"enum": [
|
|
290
|
+
"rowid",
|
|
291
|
+
"autoincrement"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
"check": {
|
|
295
|
+
"description": "A literal SQLite value or closed structural $sql expression; planTable validates its operator and contextual semantics.",
|
|
296
|
+
"anyOf": [
|
|
297
|
+
{
|
|
298
|
+
"type": [
|
|
299
|
+
"string",
|
|
300
|
+
"number",
|
|
301
|
+
"null"
|
|
302
|
+
]
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"type": "object",
|
|
306
|
+
"required": [
|
|
307
|
+
"$sql"
|
|
308
|
+
],
|
|
309
|
+
"properties": {
|
|
310
|
+
"$sql": {
|
|
311
|
+
"enum": [
|
|
312
|
+
"column",
|
|
313
|
+
"value",
|
|
314
|
+
"binary",
|
|
315
|
+
"not",
|
|
316
|
+
"in",
|
|
317
|
+
"call",
|
|
318
|
+
"cast",
|
|
319
|
+
"collate",
|
|
320
|
+
"case",
|
|
321
|
+
"scalar",
|
|
322
|
+
"exists"
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
"generatedExpression": {
|
|
330
|
+
"description": "A literal SQLite value or closed structural $sql expression; planTable validates its operator and contextual semantics.",
|
|
331
|
+
"anyOf": [
|
|
332
|
+
{
|
|
333
|
+
"type": [
|
|
334
|
+
"string",
|
|
335
|
+
"number",
|
|
336
|
+
"null"
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"type": "object",
|
|
341
|
+
"required": [
|
|
342
|
+
"$sql"
|
|
343
|
+
],
|
|
344
|
+
"properties": {
|
|
345
|
+
"$sql": {
|
|
346
|
+
"enum": [
|
|
347
|
+
"column",
|
|
348
|
+
"value",
|
|
349
|
+
"binary",
|
|
350
|
+
"not",
|
|
351
|
+
"in",
|
|
352
|
+
"call",
|
|
353
|
+
"cast",
|
|
354
|
+
"collate",
|
|
355
|
+
"case",
|
|
356
|
+
"scalar",
|
|
357
|
+
"exists"
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
"stored": {
|
|
365
|
+
"type": "boolean"
|
|
236
366
|
}
|
|
237
367
|
},
|
|
238
368
|
"additionalProperties": false
|
|
239
369
|
}
|
|
370
|
+
},
|
|
371
|
+
"constraints": {
|
|
372
|
+
"type": "array",
|
|
373
|
+
"items": {
|
|
374
|
+
"type": "object"
|
|
375
|
+
},
|
|
376
|
+
"description": "Structural SQLite declarations validated by planTable."
|
|
377
|
+
},
|
|
378
|
+
"indexes": {
|
|
379
|
+
"type": "array",
|
|
380
|
+
"items": {
|
|
381
|
+
"type": "object"
|
|
382
|
+
},
|
|
383
|
+
"description": "Structural SQLite declarations validated by planTable."
|
|
384
|
+
},
|
|
385
|
+
"triggers": {
|
|
386
|
+
"type": "array",
|
|
387
|
+
"items": {
|
|
388
|
+
"type": "object"
|
|
389
|
+
},
|
|
390
|
+
"description": "Structural SQLite declarations validated by planTable."
|
|
391
|
+
},
|
|
392
|
+
"strict": {
|
|
393
|
+
"type": "boolean"
|
|
394
|
+
},
|
|
395
|
+
"withoutRowid": {
|
|
396
|
+
"type": "boolean"
|
|
240
397
|
}
|
|
241
398
|
},
|
|
242
399
|
"additionalProperties": false
|