latticesql 1.3.0 → 1.4.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 +67 -12
- package/dist/cli.js +389 -32
- package/dist/index.cjs +375 -26
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +389 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -861,6 +861,22 @@ Migrations are idempotent — each `version` number is applied exactly once, tra
|
|
|
861
861
|
|
|
862
862
|
`close()` closes the SQLite connection. Call it when the process shuts down.
|
|
863
863
|
|
|
864
|
+
#### Migration validation (v1.4+)
|
|
865
|
+
|
|
866
|
+
Pass a `validateMigrationSQL` function in `InitOptions` to validate migration SQL before any migrations execute. If validation fails, no migrations run and an error is thrown.
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
await db.init({
|
|
870
|
+
migrations: [{ version: 1, sql: 'ALTER TABLE tasks ADD COLUMN due_date TEXT' }],
|
|
871
|
+
validateMigrationSQL: (sql) => {
|
|
872
|
+
if (sql.trim().length === 0) return { valid: false, errors: ['Empty SQL'] };
|
|
873
|
+
return { valid: true };
|
|
874
|
+
},
|
|
875
|
+
});
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
Multi-statement migrations are fully supported — each migration's SQL can contain multiple semicolon-separated statements, all of which are executed.
|
|
879
|
+
|
|
864
880
|
### `migrate()` (v0.17+)
|
|
865
881
|
|
|
866
882
|
```typescript
|
|
@@ -1052,6 +1068,30 @@ await db.count(table: string, opts?: CountOptions): Promise<number>
|
|
|
1052
1068
|
const n = await db.count('tasks', { where: { status: 'open' } });
|
|
1053
1069
|
```
|
|
1054
1070
|
|
|
1071
|
+
#### `isDirty()` / `markDirty()` (v1.4+)
|
|
1072
|
+
|
|
1073
|
+
```typescript
|
|
1074
|
+
db.isDirty(): boolean
|
|
1075
|
+
db.markDirty(table?: string): this
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
Lattice tracks a per-table write version counter. `isDirty()` returns `true` when any table has been written to since the last `render()` call — useful for consumers implementing custom polling loops to skip redundant render cycles.
|
|
1079
|
+
|
|
1080
|
+
`markDirty()` forces one or all tables to be considered changed. Call it after writing directly via the `db` escape hatch.
|
|
1081
|
+
|
|
1082
|
+
```typescript
|
|
1083
|
+
// Custom watch loop that skips redundant renders
|
|
1084
|
+
setInterval(async () => {
|
|
1085
|
+
if (db.isDirty()) {
|
|
1086
|
+
await db.render(outputDir);
|
|
1087
|
+
}
|
|
1088
|
+
}, 5000);
|
|
1089
|
+
|
|
1090
|
+
// After direct DB writes, mark dirty so next render picks up changes
|
|
1091
|
+
db.db.prepare('UPDATE tasks SET status = ? WHERE id = ?').run('done', taskId);
|
|
1092
|
+
db.markDirty('tasks');
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1055
1095
|
---
|
|
1056
1096
|
|
|
1057
1097
|
### Query operators
|
|
@@ -1264,6 +1304,18 @@ const rows = db.db
|
|
|
1264
1304
|
.all('open');
|
|
1265
1305
|
```
|
|
1266
1306
|
|
|
1307
|
+
After direct writes via the escape hatch, call `db.markDirty('table')` so `isDirty()` reflects the change.
|
|
1308
|
+
|
|
1309
|
+
---
|
|
1310
|
+
|
|
1311
|
+
### Performance (v1.4+)
|
|
1312
|
+
|
|
1313
|
+
Lattice v1.4 includes two internal performance improvements that require no API changes:
|
|
1314
|
+
|
|
1315
|
+
- **Prepared statement cache** — `SQLiteAdapter` caches compiled `better-sqlite3` statements. Repeated calls with the same SQL string reuse the compiled statement instead of recompiling. DDL statements bypass the cache. The cache clears automatically after schema changes and on `close()`.
|
|
1316
|
+
|
|
1317
|
+
- **Batch entity query resolution** — Entity context rendering pre-fetches related rows for all entities in a single `WHERE IN (...)` query per source, replacing the previous per-entity query pattern. `hasMany`, `manyToMany`, and `belongsTo` sources are batched automatically. `custom` and `enriched` sources fall back to per-entity resolution.
|
|
1318
|
+
|
|
1267
1319
|
---
|
|
1268
1320
|
|
|
1269
1321
|
### Context optimization (v1.3+)
|
|
@@ -1279,8 +1331,8 @@ db.define('tickets', {
|
|
|
1279
1331
|
columns: { id: 'TEXT PRIMARY KEY', title: 'TEXT', updated_at: 'TEXT' },
|
|
1280
1332
|
render: (rows) => rows.map((r) => `- ${r.title}`).join('\n'),
|
|
1281
1333
|
outputFile: 'TICKETS.md',
|
|
1282
|
-
tokenBudget: 4000,
|
|
1283
|
-
prioritizeBy: 'updated_at',
|
|
1334
|
+
tokenBudget: 4000, // max estimated tokens (~4 chars/token)
|
|
1335
|
+
prioritizeBy: 'updated_at', // keep most recent rows when pruning
|
|
1284
1336
|
});
|
|
1285
1337
|
```
|
|
1286
1338
|
|
|
@@ -1314,11 +1366,12 @@ db.define('incidents', {
|
|
|
1314
1366
|
render: (rows) => JSON.stringify(rows, null, 2),
|
|
1315
1367
|
outputFile: 'incidents.json',
|
|
1316
1368
|
enrich: [
|
|
1317
|
-
(rows) =>
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1369
|
+
(rows) =>
|
|
1370
|
+
rows.map((r) => ({
|
|
1371
|
+
...r,
|
|
1372
|
+
_age_hours: Math.round((Date.now() - new Date(r.created_at as string).getTime()) / 3600000),
|
|
1373
|
+
})),
|
|
1374
|
+
(rows) => (rows.length > 100 ? [{ _summary: `${rows.length} incidents` }] : rows),
|
|
1322
1375
|
],
|
|
1323
1376
|
});
|
|
1324
1377
|
```
|
|
@@ -1332,8 +1385,8 @@ db.define('tips', {
|
|
|
1332
1385
|
columns: { id: 'TEXT PRIMARY KEY', tip: 'TEXT', deleted_at: 'TEXT' },
|
|
1333
1386
|
render: (rows) => rows.map((r) => `- ${r.tip}`).join('\n'),
|
|
1334
1387
|
outputFile: 'TIPS.md',
|
|
1335
|
-
rewardTracking: true,
|
|
1336
|
-
pruneBelow: 0.3,
|
|
1388
|
+
rewardTracking: true, // auto-adds _reward_total, _reward_count columns
|
|
1389
|
+
pruneBelow: 0.3, // soft-delete rows with reward < 0.3 (requires deleted_at column)
|
|
1337
1390
|
});
|
|
1338
1391
|
|
|
1339
1392
|
await db.init();
|
|
@@ -1381,7 +1434,9 @@ Validate agent-written data before persisting. Reject low-quality or hallucinate
|
|
|
1381
1434
|
db.defineWriteback({
|
|
1382
1435
|
file: './agent-output/*.md',
|
|
1383
1436
|
parse: (content, offset) => ({ entries: [content.slice(offset)], nextOffset: content.length }),
|
|
1384
|
-
persist: async (entry) => {
|
|
1437
|
+
persist: async (entry) => {
|
|
1438
|
+
/* save to DB */
|
|
1439
|
+
},
|
|
1385
1440
|
validate: async (entry) => {
|
|
1386
1441
|
const text = entry as string;
|
|
1387
1442
|
const hasRequiredFields = text.includes('## Title') && text.includes('## Body');
|
|
@@ -2117,7 +2172,7 @@ const db = new Lattice('./app.db', {
|
|
|
2117
2172
|
│ │ entity contexts │ │
|
|
2118
2173
|
├──────────────────┴──────────────────┴───────────────────────────────┤
|
|
2119
2174
|
│ SQLiteAdapter │
|
|
2120
|
-
│
|
|
2175
|
+
│ (better-sqlite3 — synchronous I/O, statement cache) │
|
|
2121
2176
|
└──────────────────────────────────────────────────────────────────────┘
|
|
2122
2177
|
│ │
|
|
2123
2178
|
│ compileRender() │ lifecycle/
|
|
@@ -2135,7 +2190,7 @@ const db = new Lattice('./app.db', {
|
|
|
2135
2190
|
|
|
2136
2191
|
**Key design decisions:**
|
|
2137
2192
|
|
|
2138
|
-
- **Synchronous SQLite** — `better-sqlite3` gives synchronous reads; all Lattice CRUD methods return Promises for API consistency but resolve synchronously under the hood.
|
|
2193
|
+
- **Synchronous SQLite** — `better-sqlite3` gives synchronous reads; all Lattice CRUD methods return Promises for API consistency but resolve synchronously under the hood. Prepared statements are cached and reused automatically (v1.4+).
|
|
2139
2194
|
- **Compile-time render** — `RenderSpec` is compiled to a plain `(rows: Row[]) => string` function at `define()`-time, not at render-time. `RenderEngine` stays unchanged.
|
|
2140
2195
|
- **Atomic writes** — files are written to a `.tmp` sibling then renamed. No partial writes, no reader sees incomplete content.
|
|
2141
2196
|
- **Schema-additive only** — Lattice never drops tables or columns automatically; it only adds missing ones.
|