latticesql 0.16.2 → 0.17.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/README.md +79 -5
- package/dist/cli.js +59 -6
- package/dist/index.cjs +59 -6
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +59 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Lattice has no opinions about your schema, your agents, or your file format. You
|
|
|
35
35
|
- [defineEntityContext()](#defineentitycontext-v05)
|
|
36
36
|
- [defineWriteback()](#definewriteback)
|
|
37
37
|
- [init() / close()](#init--close)
|
|
38
|
+
- [migrate()](#migrate-v017)
|
|
38
39
|
- [CRUD operations](#crud-operations)
|
|
39
40
|
- [Query operators](#query-operators)
|
|
40
41
|
- [Render, sync, watch, and reconcile](#render-sync-watch-and-reconcile)
|
|
@@ -202,11 +203,12 @@ interface TableDefinition {
|
|
|
202
203
|
* - A render function: (rows: Row[]) => string
|
|
203
204
|
* - A built-in template name: 'default-list' | 'default-table' | 'default-detail' | 'default-json'
|
|
204
205
|
* - A template spec with hooks: { template: BuiltinTemplateName, hooks?: RenderHooks }
|
|
206
|
+
* Optional (v0.17+) — omit render and outputFile for schema-only tables.
|
|
205
207
|
*/
|
|
206
|
-
render
|
|
208
|
+
render?: RenderSpec;
|
|
207
209
|
|
|
208
|
-
/** Output file path, relative to the outputDir passed to render()/watch() */
|
|
209
|
-
outputFile
|
|
210
|
+
/** Output file path, relative to the outputDir passed to render()/watch(). Optional (v0.17+). */
|
|
211
|
+
outputFile?: string;
|
|
210
212
|
|
|
211
213
|
/** Optional row filter applied before rendering */
|
|
212
214
|
filter?: (rows: Row[]) => Row[];
|
|
@@ -215,10 +217,12 @@ interface TableDefinition {
|
|
|
215
217
|
* Primary key column name or [col1, col2] for composite PKs.
|
|
216
218
|
* Defaults to 'id'. When 'id' is the PK and the field is absent on insert,
|
|
217
219
|
* a UUID v4 is generated automatically.
|
|
220
|
+
* Composite PKs (v0.17+): auto-generates a PRIMARY KEY(...) constraint —
|
|
221
|
+
* no need to add it manually via tableConstraints.
|
|
218
222
|
*/
|
|
219
223
|
primaryKey?: string | string[];
|
|
220
224
|
|
|
221
|
-
/** Additional SQL constraints (required for composite PKs) */
|
|
225
|
+
/** Additional SQL constraints (e.g., UNIQUE, CHECK). No longer required for composite PKs (v0.17+). */
|
|
222
226
|
tableConstraints?: string[];
|
|
223
227
|
|
|
224
228
|
/** Declared relationships used by template rendering */
|
|
@@ -267,6 +271,22 @@ await db.update('pages', 'about-us', { title: 'About' });
|
|
|
267
271
|
await db.delete('pages', 'about-us');
|
|
268
272
|
```
|
|
269
273
|
|
|
274
|
+
**Schema-only table (v0.17+):**
|
|
275
|
+
|
|
276
|
+
Tables without `render` and `outputFile` get full schema support (columns, indexes, constraints, CRUD) but produce no output files during `render()` or `watch()`. Useful for junction tables, internal tracking tables, or any table that doesn't need a context file.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
db.define('agent_skills', {
|
|
280
|
+
columns: {
|
|
281
|
+
agent_id: 'TEXT NOT NULL',
|
|
282
|
+
skill_id: 'TEXT NOT NULL',
|
|
283
|
+
proficiency: 'TEXT DEFAULT "basic"',
|
|
284
|
+
},
|
|
285
|
+
primaryKey: ['agent_id', 'skill_id'],
|
|
286
|
+
// No render, no outputFile — schema-only
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
270
290
|
**Composite primary key:**
|
|
271
291
|
|
|
272
292
|
```typescript
|
|
@@ -276,7 +296,6 @@ db.define('event_seats', {
|
|
|
276
296
|
seat_no: 'INTEGER NOT NULL',
|
|
277
297
|
holder: 'TEXT',
|
|
278
298
|
},
|
|
279
|
-
tableConstraints: ['PRIMARY KEY (event_id, seat_no)'],
|
|
280
299
|
primaryKey: ['event_id', 'seat_no'],
|
|
281
300
|
render: 'default-table',
|
|
282
301
|
outputFile: 'seats.md',
|
|
@@ -835,6 +854,26 @@ Migrations are idempotent — each `version` number is applied exactly once, tra
|
|
|
835
854
|
|
|
836
855
|
`close()` closes the SQLite connection. Call it when the process shuts down.
|
|
837
856
|
|
|
857
|
+
### `migrate()` (v0.17+)
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
await db.migrate(migrations: Migration[]): Promise<void>
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
Run migrations after `init()`. Works exactly like `init({ migrations })` but callable any time — useful when migrations are loaded dynamically or added by plugins after startup.
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
await db.init();
|
|
867
|
+
|
|
868
|
+
// Later — e.g., after loading a plugin that needs new columns
|
|
869
|
+
await db.migrate([
|
|
870
|
+
{ version: 'plugin-v1', sql: 'ALTER TABLE tasks ADD COLUMN tags TEXT' },
|
|
871
|
+
{ version: 'plugin-v2', sql: 'CREATE INDEX IF NOT EXISTS idx_tasks_tags ON tasks (tags)' },
|
|
872
|
+
]);
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
`Migration.version` accepts `number | string` — use numbers for sequential migrations, or strings for named/namespaced versions (e.g., `'plugin-v1'`). Each version is applied at most once, tracked in the same `__lattice_migrations` table used by `init()`.
|
|
876
|
+
|
|
838
877
|
---
|
|
839
878
|
|
|
840
879
|
### CRUD operations
|
|
@@ -860,6 +899,24 @@ await db.insert('pages', { slug: 'about', title: 'About Us' });
|
|
|
860
899
|
await db.insert('tasks', { id: 'task-001', title: 'Specific task' });
|
|
861
900
|
```
|
|
862
901
|
|
|
902
|
+
#### `insertReturning()` (v0.17+)
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
await db.insertReturning(table: string, row: Row): Promise<Row>
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
Insert a row and get the full row back — including the auto-generated `id`, defaults, and any other columns. Equivalent to `insert()` + `get()` in a single call.
|
|
909
|
+
|
|
910
|
+
```typescript
|
|
911
|
+
const task = await db.insertReturning('tasks', { title: 'Write docs', status: 'open' });
|
|
912
|
+
// task → { id: 'f47ac10b-...', title: 'Write docs', status: 'open', priority: 0, ... }
|
|
913
|
+
|
|
914
|
+
// Useful when you need the generated id or default values immediately
|
|
915
|
+
const agent = await db.insertReturning('agents', { name: 'Gamma' });
|
|
916
|
+
console.log(agent.id); // auto-generated UUID
|
|
917
|
+
console.log(agent.active); // default value from schema
|
|
918
|
+
```
|
|
919
|
+
|
|
863
920
|
#### `upsert()`
|
|
864
921
|
|
|
865
922
|
```typescript
|
|
@@ -899,6 +956,23 @@ await db.update('tasks', 'task-001', { status: 'done' });
|
|
|
899
956
|
await db.update('event_seats', { event_id: 'e-1', seat_no: 3 }, { holder: 'Bob' });
|
|
900
957
|
```
|
|
901
958
|
|
|
959
|
+
#### `updateReturning()` (v0.17+)
|
|
960
|
+
|
|
961
|
+
```typescript
|
|
962
|
+
await db.updateReturning(table: string, id: PkLookup, row: Partial<Row>): Promise<Row>
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
Update specific columns and get the full updated row back. Equivalent to `update()` + `get()` in a single call.
|
|
966
|
+
|
|
967
|
+
```typescript
|
|
968
|
+
const task = await db.updateReturning('tasks', 'task-001', { status: 'done' });
|
|
969
|
+
// task → { id: 'task-001', title: 'Write docs', status: 'done', priority: 3, ... }
|
|
970
|
+
|
|
971
|
+
// Composite PK
|
|
972
|
+
const seat = await db.updateReturning('event_seats', { event_id: 'e-1', seat_no: 3 }, { holder: 'Bob' });
|
|
973
|
+
// seat → { event_id: 'e-1', seat_no: 3, holder: 'Bob' }
|
|
974
|
+
```
|
|
975
|
+
|
|
902
976
|
#### `delete()`
|
|
903
977
|
|
|
904
978
|
```typescript
|
package/dist/cli.js
CHANGED
|
@@ -492,24 +492,37 @@ var SchemaManager = class {
|
|
|
492
492
|
*/
|
|
493
493
|
applySchema(adapter) {
|
|
494
494
|
for (const [name, def] of this._tables) {
|
|
495
|
-
this.
|
|
495
|
+
const pkCols = this._tablePK.get(name) ?? ["id"];
|
|
496
|
+
let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
|
|
497
|
+
if (pkCols.length > 1) {
|
|
498
|
+
const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
|
|
499
|
+
if (!alreadyHasPK) {
|
|
500
|
+
constraints.unshift(`PRIMARY KEY (${pkCols.map((c) => `"${c}"`).join(", ")})`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this._ensureTable(adapter, name, def.columns, constraints.length ? constraints : void 0);
|
|
496
504
|
}
|
|
497
505
|
this._ensureTable(adapter, "__lattice_migrations", {
|
|
498
|
-
version: "
|
|
506
|
+
version: "TEXT PRIMARY KEY",
|
|
499
507
|
applied_at: "TEXT NOT NULL"
|
|
500
508
|
});
|
|
501
509
|
}
|
|
502
510
|
/** Run explicit versioned migrations in order, idempotently */
|
|
503
511
|
applyMigrations(adapter, migrations) {
|
|
504
|
-
const sorted = [...migrations].sort((a, b) =>
|
|
512
|
+
const sorted = [...migrations].sort((a, b) => {
|
|
513
|
+
const va = String(a.version);
|
|
514
|
+
const vb = String(b.version);
|
|
515
|
+
return va.localeCompare(vb, void 0, { numeric: true });
|
|
516
|
+
});
|
|
505
517
|
for (const m of sorted) {
|
|
518
|
+
const versionStr = String(m.version);
|
|
506
519
|
const exists = adapter.get("SELECT 1 FROM __lattice_migrations WHERE version = ?", [
|
|
507
|
-
|
|
520
|
+
versionStr
|
|
508
521
|
]);
|
|
509
522
|
if (!exists) {
|
|
510
523
|
adapter.run(m.sql);
|
|
511
524
|
adapter.run("INSERT INTO __lattice_migrations (version, applied_at) VALUES (?, ?)", [
|
|
512
|
-
|
|
525
|
+
versionStr,
|
|
513
526
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
514
527
|
]);
|
|
515
528
|
}
|
|
@@ -1612,7 +1625,8 @@ var Lattice = class {
|
|
|
1612
1625
|
this._assertNotInit("define");
|
|
1613
1626
|
const compiledDef = {
|
|
1614
1627
|
...def,
|
|
1615
|
-
render: compileRender(def, table, this._schema, this._adapter)
|
|
1628
|
+
render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
|
|
1629
|
+
outputFile: def.outputFile ?? `.schema-only/${table}.md`
|
|
1616
1630
|
};
|
|
1617
1631
|
this._schema.define(table, compiledDef);
|
|
1618
1632
|
return this;
|
|
@@ -1654,6 +1668,23 @@ var Lattice = class {
|
|
|
1654
1668
|
this._initialized = true;
|
|
1655
1669
|
return Promise.resolve();
|
|
1656
1670
|
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Run additional migrations after init(). Useful for package-level schema
|
|
1673
|
+
* changes applied at runtime (e.g. update hooks that add columns).
|
|
1674
|
+
*
|
|
1675
|
+
* @since 0.17.0
|
|
1676
|
+
*/
|
|
1677
|
+
migrate(migrations) {
|
|
1678
|
+
if (!this._initialized) {
|
|
1679
|
+
return Promise.reject(new Error("Lattice: not initialized \u2014 call init() first"));
|
|
1680
|
+
}
|
|
1681
|
+
this._schema.applyMigrations(this._adapter, migrations);
|
|
1682
|
+
for (const tableName of this._schema.getTables().keys()) {
|
|
1683
|
+
const rows = this._adapter.all(`PRAGMA table_info("${tableName}")`);
|
|
1684
|
+
this._columnCache.set(tableName, new Set(rows.map((r) => r.name)));
|
|
1685
|
+
}
|
|
1686
|
+
return Promise.resolve();
|
|
1687
|
+
}
|
|
1657
1688
|
close() {
|
|
1658
1689
|
this._adapter.close();
|
|
1659
1690
|
this._columnCache.clear();
|
|
@@ -1686,6 +1717,17 @@ var Lattice = class {
|
|
|
1686
1717
|
this._fireWriteHooks(table, "insert", rowWithPk, pkValue);
|
|
1687
1718
|
return Promise.resolve(pkValue);
|
|
1688
1719
|
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Insert a row and return the full inserted row (including auto-generated
|
|
1722
|
+
* fields and defaults). Equivalent to `insert()` followed by `get()`.
|
|
1723
|
+
*
|
|
1724
|
+
* @since 0.17.0
|
|
1725
|
+
*/
|
|
1726
|
+
insertReturning(table, row) {
|
|
1727
|
+
return this.insert(table, row).then(
|
|
1728
|
+
(pk) => this.get(table, pk).then((result) => result ?? { ...row, id: pk })
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1689
1731
|
upsert(table, row) {
|
|
1690
1732
|
const notInit = this._notInitError();
|
|
1691
1733
|
if (notInit) return notInit;
|
|
@@ -1740,6 +1782,17 @@ var Lattice = class {
|
|
|
1740
1782
|
this._fireWriteHooks(table, "update", sanitized, auditId, Object.keys(sanitized));
|
|
1741
1783
|
return Promise.resolve();
|
|
1742
1784
|
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Update a row and return the full updated row. Equivalent to `update()`
|
|
1787
|
+
* followed by `get()`.
|
|
1788
|
+
*
|
|
1789
|
+
* @since 0.17.0
|
|
1790
|
+
*/
|
|
1791
|
+
updateReturning(table, id, row) {
|
|
1792
|
+
return this.update(table, id, row).then(
|
|
1793
|
+
() => this.get(table, id).then((result) => result ?? row)
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1743
1796
|
delete(table, id) {
|
|
1744
1797
|
const notInit = this._notInitError();
|
|
1745
1798
|
if (notInit) return notInit;
|
package/dist/index.cjs
CHANGED
|
@@ -240,24 +240,37 @@ var SchemaManager = class {
|
|
|
240
240
|
*/
|
|
241
241
|
applySchema(adapter) {
|
|
242
242
|
for (const [name, def] of this._tables) {
|
|
243
|
-
this.
|
|
243
|
+
const pkCols = this._tablePK.get(name) ?? ["id"];
|
|
244
|
+
let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
|
|
245
|
+
if (pkCols.length > 1) {
|
|
246
|
+
const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
|
|
247
|
+
if (!alreadyHasPK) {
|
|
248
|
+
constraints.unshift(`PRIMARY KEY (${pkCols.map((c) => `"${c}"`).join(", ")})`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this._ensureTable(adapter, name, def.columns, constraints.length ? constraints : void 0);
|
|
244
252
|
}
|
|
245
253
|
this._ensureTable(adapter, "__lattice_migrations", {
|
|
246
|
-
version: "
|
|
254
|
+
version: "TEXT PRIMARY KEY",
|
|
247
255
|
applied_at: "TEXT NOT NULL"
|
|
248
256
|
});
|
|
249
257
|
}
|
|
250
258
|
/** Run explicit versioned migrations in order, idempotently */
|
|
251
259
|
applyMigrations(adapter, migrations) {
|
|
252
|
-
const sorted = [...migrations].sort((a, b) =>
|
|
260
|
+
const sorted = [...migrations].sort((a, b) => {
|
|
261
|
+
const va = String(a.version);
|
|
262
|
+
const vb = String(b.version);
|
|
263
|
+
return va.localeCompare(vb, void 0, { numeric: true });
|
|
264
|
+
});
|
|
253
265
|
for (const m of sorted) {
|
|
266
|
+
const versionStr = String(m.version);
|
|
254
267
|
const exists = adapter.get("SELECT 1 FROM __lattice_migrations WHERE version = ?", [
|
|
255
|
-
|
|
268
|
+
versionStr
|
|
256
269
|
]);
|
|
257
270
|
if (!exists) {
|
|
258
271
|
adapter.run(m.sql);
|
|
259
272
|
adapter.run("INSERT INTO __lattice_migrations (version, applied_at) VALUES (?, ?)", [
|
|
260
|
-
|
|
273
|
+
versionStr,
|
|
261
274
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
262
275
|
]);
|
|
263
276
|
}
|
|
@@ -1648,7 +1661,8 @@ var Lattice = class {
|
|
|
1648
1661
|
this._assertNotInit("define");
|
|
1649
1662
|
const compiledDef = {
|
|
1650
1663
|
...def,
|
|
1651
|
-
render: compileRender(def, table, this._schema, this._adapter)
|
|
1664
|
+
render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
|
|
1665
|
+
outputFile: def.outputFile ?? `.schema-only/${table}.md`
|
|
1652
1666
|
};
|
|
1653
1667
|
this._schema.define(table, compiledDef);
|
|
1654
1668
|
return this;
|
|
@@ -1690,6 +1704,23 @@ var Lattice = class {
|
|
|
1690
1704
|
this._initialized = true;
|
|
1691
1705
|
return Promise.resolve();
|
|
1692
1706
|
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Run additional migrations after init(). Useful for package-level schema
|
|
1709
|
+
* changes applied at runtime (e.g. update hooks that add columns).
|
|
1710
|
+
*
|
|
1711
|
+
* @since 0.17.0
|
|
1712
|
+
*/
|
|
1713
|
+
migrate(migrations) {
|
|
1714
|
+
if (!this._initialized) {
|
|
1715
|
+
return Promise.reject(new Error("Lattice: not initialized \u2014 call init() first"));
|
|
1716
|
+
}
|
|
1717
|
+
this._schema.applyMigrations(this._adapter, migrations);
|
|
1718
|
+
for (const tableName of this._schema.getTables().keys()) {
|
|
1719
|
+
const rows = this._adapter.all(`PRAGMA table_info("${tableName}")`);
|
|
1720
|
+
this._columnCache.set(tableName, new Set(rows.map((r) => r.name)));
|
|
1721
|
+
}
|
|
1722
|
+
return Promise.resolve();
|
|
1723
|
+
}
|
|
1693
1724
|
close() {
|
|
1694
1725
|
this._adapter.close();
|
|
1695
1726
|
this._columnCache.clear();
|
|
@@ -1722,6 +1753,17 @@ var Lattice = class {
|
|
|
1722
1753
|
this._fireWriteHooks(table, "insert", rowWithPk, pkValue);
|
|
1723
1754
|
return Promise.resolve(pkValue);
|
|
1724
1755
|
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Insert a row and return the full inserted row (including auto-generated
|
|
1758
|
+
* fields and defaults). Equivalent to `insert()` followed by `get()`.
|
|
1759
|
+
*
|
|
1760
|
+
* @since 0.17.0
|
|
1761
|
+
*/
|
|
1762
|
+
insertReturning(table, row) {
|
|
1763
|
+
return this.insert(table, row).then(
|
|
1764
|
+
(pk) => this.get(table, pk).then((result) => result ?? { ...row, id: pk })
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1725
1767
|
upsert(table, row) {
|
|
1726
1768
|
const notInit = this._notInitError();
|
|
1727
1769
|
if (notInit) return notInit;
|
|
@@ -1776,6 +1818,17 @@ var Lattice = class {
|
|
|
1776
1818
|
this._fireWriteHooks(table, "update", sanitized, auditId, Object.keys(sanitized));
|
|
1777
1819
|
return Promise.resolve();
|
|
1778
1820
|
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Update a row and return the full updated row. Equivalent to `update()`
|
|
1823
|
+
* followed by `get()`.
|
|
1824
|
+
*
|
|
1825
|
+
* @since 0.17.0
|
|
1826
|
+
*/
|
|
1827
|
+
updateReturning(table, id, row) {
|
|
1828
|
+
return this.update(table, id, row).then(
|
|
1829
|
+
() => this.get(table, id).then((result) => result ?? row)
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1779
1832
|
delete(table, id) {
|
|
1780
1833
|
const notInit = this._notInitError();
|
|
1781
1834
|
if (notInit) return notInit;
|
package/dist/index.d.cts
CHANGED
|
@@ -714,9 +714,9 @@ interface TableDefinition {
|
|
|
714
714
|
* `'default-detail'`, `'default-json'`) to use a built-in template.
|
|
715
715
|
* - Pass a `TemplateRenderSpec` to use a built-in template with lifecycle hooks.
|
|
716
716
|
*/
|
|
717
|
-
render
|
|
717
|
+
render?: RenderSpec;
|
|
718
718
|
/** Output path relative to the outputDir passed to render/watch */
|
|
719
|
-
outputFile
|
|
719
|
+
outputFile?: string;
|
|
720
720
|
/** Optional pre-filter applied before render */
|
|
721
721
|
filter?: (rows: Row[]) => Row[];
|
|
722
722
|
/**
|
|
@@ -825,7 +825,7 @@ interface InitOptions {
|
|
|
825
825
|
migrations?: Migration[];
|
|
826
826
|
}
|
|
827
827
|
interface Migration {
|
|
828
|
-
version: number;
|
|
828
|
+
version: number | string;
|
|
829
829
|
sql: string;
|
|
830
830
|
}
|
|
831
831
|
interface WatchOptions {
|
|
@@ -1110,11 +1110,32 @@ declare class Lattice {
|
|
|
1110
1110
|
defineWriteHook(hook: WriteHook): this;
|
|
1111
1111
|
defineWriteback(def: WritebackDefinition): this;
|
|
1112
1112
|
init(options?: InitOptions): Promise<void>;
|
|
1113
|
+
/**
|
|
1114
|
+
* Run additional migrations after init(). Useful for package-level schema
|
|
1115
|
+
* changes applied at runtime (e.g. update hooks that add columns).
|
|
1116
|
+
*
|
|
1117
|
+
* @since 0.17.0
|
|
1118
|
+
*/
|
|
1119
|
+
migrate(migrations: Migration[]): Promise<void>;
|
|
1113
1120
|
close(): void;
|
|
1114
1121
|
insert(table: string, row: Row): Promise<string>;
|
|
1122
|
+
/**
|
|
1123
|
+
* Insert a row and return the full inserted row (including auto-generated
|
|
1124
|
+
* fields and defaults). Equivalent to `insert()` followed by `get()`.
|
|
1125
|
+
*
|
|
1126
|
+
* @since 0.17.0
|
|
1127
|
+
*/
|
|
1128
|
+
insertReturning(table: string, row: Row): Promise<Row>;
|
|
1115
1129
|
upsert(table: string, row: Row): Promise<string>;
|
|
1116
1130
|
upsertBy(table: string, col: string, val: unknown, row: Row): Promise<string>;
|
|
1117
1131
|
update(table: string, id: PkLookup, row: Partial<Row>): Promise<void>;
|
|
1132
|
+
/**
|
|
1133
|
+
* Update a row and return the full updated row. Equivalent to `update()`
|
|
1134
|
+
* followed by `get()`.
|
|
1135
|
+
*
|
|
1136
|
+
* @since 0.17.0
|
|
1137
|
+
*/
|
|
1138
|
+
updateReturning(table: string, id: PkLookup, row: Partial<Row>): Promise<Row>;
|
|
1118
1139
|
delete(table: string, id: PkLookup): Promise<void>;
|
|
1119
1140
|
get(table: string, id: PkLookup): Promise<Row | null>;
|
|
1120
1141
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -714,9 +714,9 @@ interface TableDefinition {
|
|
|
714
714
|
* `'default-detail'`, `'default-json'`) to use a built-in template.
|
|
715
715
|
* - Pass a `TemplateRenderSpec` to use a built-in template with lifecycle hooks.
|
|
716
716
|
*/
|
|
717
|
-
render
|
|
717
|
+
render?: RenderSpec;
|
|
718
718
|
/** Output path relative to the outputDir passed to render/watch */
|
|
719
|
-
outputFile
|
|
719
|
+
outputFile?: string;
|
|
720
720
|
/** Optional pre-filter applied before render */
|
|
721
721
|
filter?: (rows: Row[]) => Row[];
|
|
722
722
|
/**
|
|
@@ -825,7 +825,7 @@ interface InitOptions {
|
|
|
825
825
|
migrations?: Migration[];
|
|
826
826
|
}
|
|
827
827
|
interface Migration {
|
|
828
|
-
version: number;
|
|
828
|
+
version: number | string;
|
|
829
829
|
sql: string;
|
|
830
830
|
}
|
|
831
831
|
interface WatchOptions {
|
|
@@ -1110,11 +1110,32 @@ declare class Lattice {
|
|
|
1110
1110
|
defineWriteHook(hook: WriteHook): this;
|
|
1111
1111
|
defineWriteback(def: WritebackDefinition): this;
|
|
1112
1112
|
init(options?: InitOptions): Promise<void>;
|
|
1113
|
+
/**
|
|
1114
|
+
* Run additional migrations after init(). Useful for package-level schema
|
|
1115
|
+
* changes applied at runtime (e.g. update hooks that add columns).
|
|
1116
|
+
*
|
|
1117
|
+
* @since 0.17.0
|
|
1118
|
+
*/
|
|
1119
|
+
migrate(migrations: Migration[]): Promise<void>;
|
|
1113
1120
|
close(): void;
|
|
1114
1121
|
insert(table: string, row: Row): Promise<string>;
|
|
1122
|
+
/**
|
|
1123
|
+
* Insert a row and return the full inserted row (including auto-generated
|
|
1124
|
+
* fields and defaults). Equivalent to `insert()` followed by `get()`.
|
|
1125
|
+
*
|
|
1126
|
+
* @since 0.17.0
|
|
1127
|
+
*/
|
|
1128
|
+
insertReturning(table: string, row: Row): Promise<Row>;
|
|
1115
1129
|
upsert(table: string, row: Row): Promise<string>;
|
|
1116
1130
|
upsertBy(table: string, col: string, val: unknown, row: Row): Promise<string>;
|
|
1117
1131
|
update(table: string, id: PkLookup, row: Partial<Row>): Promise<void>;
|
|
1132
|
+
/**
|
|
1133
|
+
* Update a row and return the full updated row. Equivalent to `update()`
|
|
1134
|
+
* followed by `get()`.
|
|
1135
|
+
*
|
|
1136
|
+
* @since 0.17.0
|
|
1137
|
+
*/
|
|
1138
|
+
updateReturning(table: string, id: PkLookup, row: Partial<Row>): Promise<Row>;
|
|
1118
1139
|
delete(table: string, id: PkLookup): Promise<void>;
|
|
1119
1140
|
get(table: string, id: PkLookup): Promise<Row | null>;
|
|
1120
1141
|
/**
|
package/dist/index.js
CHANGED
|
@@ -178,24 +178,37 @@ var SchemaManager = class {
|
|
|
178
178
|
*/
|
|
179
179
|
applySchema(adapter) {
|
|
180
180
|
for (const [name, def] of this._tables) {
|
|
181
|
-
this.
|
|
181
|
+
const pkCols = this._tablePK.get(name) ?? ["id"];
|
|
182
|
+
let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
|
|
183
|
+
if (pkCols.length > 1) {
|
|
184
|
+
const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
|
|
185
|
+
if (!alreadyHasPK) {
|
|
186
|
+
constraints.unshift(`PRIMARY KEY (${pkCols.map((c) => `"${c}"`).join(", ")})`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
this._ensureTable(adapter, name, def.columns, constraints.length ? constraints : void 0);
|
|
182
190
|
}
|
|
183
191
|
this._ensureTable(adapter, "__lattice_migrations", {
|
|
184
|
-
version: "
|
|
192
|
+
version: "TEXT PRIMARY KEY",
|
|
185
193
|
applied_at: "TEXT NOT NULL"
|
|
186
194
|
});
|
|
187
195
|
}
|
|
188
196
|
/** Run explicit versioned migrations in order, idempotently */
|
|
189
197
|
applyMigrations(adapter, migrations) {
|
|
190
|
-
const sorted = [...migrations].sort((a, b) =>
|
|
198
|
+
const sorted = [...migrations].sort((a, b) => {
|
|
199
|
+
const va = String(a.version);
|
|
200
|
+
const vb = String(b.version);
|
|
201
|
+
return va.localeCompare(vb, void 0, { numeric: true });
|
|
202
|
+
});
|
|
191
203
|
for (const m of sorted) {
|
|
204
|
+
const versionStr = String(m.version);
|
|
192
205
|
const exists = adapter.get("SELECT 1 FROM __lattice_migrations WHERE version = ?", [
|
|
193
|
-
|
|
206
|
+
versionStr
|
|
194
207
|
]);
|
|
195
208
|
if (!exists) {
|
|
196
209
|
adapter.run(m.sql);
|
|
197
210
|
adapter.run("INSERT INTO __lattice_migrations (version, applied_at) VALUES (?, ?)", [
|
|
198
|
-
|
|
211
|
+
versionStr,
|
|
199
212
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
200
213
|
]);
|
|
201
214
|
}
|
|
@@ -1586,7 +1599,8 @@ var Lattice = class {
|
|
|
1586
1599
|
this._assertNotInit("define");
|
|
1587
1600
|
const compiledDef = {
|
|
1588
1601
|
...def,
|
|
1589
|
-
render: compileRender(def, table, this._schema, this._adapter)
|
|
1602
|
+
render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
|
|
1603
|
+
outputFile: def.outputFile ?? `.schema-only/${table}.md`
|
|
1590
1604
|
};
|
|
1591
1605
|
this._schema.define(table, compiledDef);
|
|
1592
1606
|
return this;
|
|
@@ -1628,6 +1642,23 @@ var Lattice = class {
|
|
|
1628
1642
|
this._initialized = true;
|
|
1629
1643
|
return Promise.resolve();
|
|
1630
1644
|
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Run additional migrations after init(). Useful for package-level schema
|
|
1647
|
+
* changes applied at runtime (e.g. update hooks that add columns).
|
|
1648
|
+
*
|
|
1649
|
+
* @since 0.17.0
|
|
1650
|
+
*/
|
|
1651
|
+
migrate(migrations) {
|
|
1652
|
+
if (!this._initialized) {
|
|
1653
|
+
return Promise.reject(new Error("Lattice: not initialized \u2014 call init() first"));
|
|
1654
|
+
}
|
|
1655
|
+
this._schema.applyMigrations(this._adapter, migrations);
|
|
1656
|
+
for (const tableName of this._schema.getTables().keys()) {
|
|
1657
|
+
const rows = this._adapter.all(`PRAGMA table_info("${tableName}")`);
|
|
1658
|
+
this._columnCache.set(tableName, new Set(rows.map((r) => r.name)));
|
|
1659
|
+
}
|
|
1660
|
+
return Promise.resolve();
|
|
1661
|
+
}
|
|
1631
1662
|
close() {
|
|
1632
1663
|
this._adapter.close();
|
|
1633
1664
|
this._columnCache.clear();
|
|
@@ -1660,6 +1691,17 @@ var Lattice = class {
|
|
|
1660
1691
|
this._fireWriteHooks(table, "insert", rowWithPk, pkValue);
|
|
1661
1692
|
return Promise.resolve(pkValue);
|
|
1662
1693
|
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Insert a row and return the full inserted row (including auto-generated
|
|
1696
|
+
* fields and defaults). Equivalent to `insert()` followed by `get()`.
|
|
1697
|
+
*
|
|
1698
|
+
* @since 0.17.0
|
|
1699
|
+
*/
|
|
1700
|
+
insertReturning(table, row) {
|
|
1701
|
+
return this.insert(table, row).then(
|
|
1702
|
+
(pk) => this.get(table, pk).then((result) => result ?? { ...row, id: pk })
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1663
1705
|
upsert(table, row) {
|
|
1664
1706
|
const notInit = this._notInitError();
|
|
1665
1707
|
if (notInit) return notInit;
|
|
@@ -1714,6 +1756,17 @@ var Lattice = class {
|
|
|
1714
1756
|
this._fireWriteHooks(table, "update", sanitized, auditId, Object.keys(sanitized));
|
|
1715
1757
|
return Promise.resolve();
|
|
1716
1758
|
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Update a row and return the full updated row. Equivalent to `update()`
|
|
1761
|
+
* followed by `get()`.
|
|
1762
|
+
*
|
|
1763
|
+
* @since 0.17.0
|
|
1764
|
+
*/
|
|
1765
|
+
updateReturning(table, id, row) {
|
|
1766
|
+
return this.update(table, id, row).then(
|
|
1767
|
+
() => this.get(table, id).then((result) => result ?? row)
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1717
1770
|
delete(table, id) {
|
|
1718
1771
|
const notInit = this._notInitError();
|
|
1719
1772
|
if (notInit) return notInit;
|