latticesql 1.4.0 → 1.6.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 +116 -56
- package/dist/cli.js +6828 -554
- package/dist/index.cjs +6841 -569
- package/dist/index.d.cts +292 -37
- package/dist/index.d.ts +292 -37
- package/dist/index.js +6836 -559
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# latticesql
|
|
2
2
|
|
|
3
|
-
**Persistent memory for AI agents.** Keeps a SQLite database and a set of context files in sync — so every agent session starts with accurate state, and agent output becomes permanent data.
|
|
3
|
+
**Persistent memory for AI agents.** Keeps a SQLite **or Postgres** database and a set of context files in sync — so every agent session starts with accurate state, and agent output becomes permanent data.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/latticesql)
|
|
6
6
|
[](./LICENSE)
|
|
@@ -59,6 +59,7 @@ Lattice has no opinions about your schema, your agents, or your file format. You
|
|
|
59
59
|
- [CLI — lattice generate](#cli--lattice-generate)
|
|
60
60
|
- [Schema migrations](#schema-migrations)
|
|
61
61
|
- [Security](#security)
|
|
62
|
+
- [Pluggable backends (v1.6+)](#pluggable-backends-v16)
|
|
62
63
|
- [Architecture](#architecture)
|
|
63
64
|
- [Examples](#examples)
|
|
64
65
|
- [Staying up to date](#staying-up-to-date)
|
|
@@ -74,7 +75,24 @@ Lattice has no opinions about your schema, your agents, or your file format. You
|
|
|
74
75
|
npm install latticesql
|
|
75
76
|
```
|
|
76
77
|
|
|
77
|
-
Requires **Node.js 18+**.
|
|
78
|
+
Requires **Node.js 18+**. The default backend is SQLite (`better-sqlite3`) — no external database process needed.
|
|
79
|
+
|
|
80
|
+
To use the Postgres backend (for Supabase, Neon, RDS, or any other Postgres-compatible database), install the optional dependencies:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install latticesql pg synckit
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then pass a connection string instead of a file path:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { Lattice } from 'latticesql';
|
|
90
|
+
|
|
91
|
+
const lattice = new Lattice('postgres://user:pass@host:5432/db');
|
|
92
|
+
// rest of your setup is identical to the SQLite path
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
See [Pluggable backends](#pluggable-backends-v16) below for full details.
|
|
78
96
|
|
|
79
97
|
---
|
|
80
98
|
|
|
@@ -861,22 +879,6 @@ Migrations are idempotent — each `version` number is applied exactly once, tra
|
|
|
861
879
|
|
|
862
880
|
`close()` closes the SQLite connection. Call it when the process shuts down.
|
|
863
881
|
|
|
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
|
-
|
|
880
882
|
### `migrate()` (v0.17+)
|
|
881
883
|
|
|
882
884
|
```typescript
|
|
@@ -1068,30 +1070,6 @@ await db.count(table: string, opts?: CountOptions): Promise<number>
|
|
|
1068
1070
|
const n = await db.count('tasks', { where: { status: 'open' } });
|
|
1069
1071
|
```
|
|
1070
1072
|
|
|
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
|
-
|
|
1095
1073
|
---
|
|
1096
1074
|
|
|
1097
1075
|
### Query operators
|
|
@@ -1304,18 +1282,6 @@ const rows = db.db
|
|
|
1304
1282
|
.all('open');
|
|
1305
1283
|
```
|
|
1306
1284
|
|
|
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
|
-
|
|
1319
1285
|
---
|
|
1320
1286
|
|
|
1321
1287
|
### Context optimization (v1.3+)
|
|
@@ -2153,6 +2119,100 @@ const db = new Lattice('./app.db', {
|
|
|
2153
2119
|
|
|
2154
2120
|
---
|
|
2155
2121
|
|
|
2122
|
+
## Pluggable backends (v1.6+)
|
|
2123
|
+
|
|
2124
|
+
Lattice ships with two storage adapters and a pluggable interface so you can bring your own.
|
|
2125
|
+
|
|
2126
|
+
### Picking a backend by connection string
|
|
2127
|
+
|
|
2128
|
+
The `Lattice` constructor inspects the first argument and picks the right adapter:
|
|
2129
|
+
|
|
2130
|
+
| First argument | Adapter | When to use |
|
|
2131
|
+
|---|---|---|
|
|
2132
|
+
| `'/abs/path/to/db.sqlite'` (or any plain path) | `SQLiteAdapter` | Default. Local file, no server. |
|
|
2133
|
+
| `':memory:'` | `SQLiteAdapter` | In-memory SQLite. Great for tests. |
|
|
2134
|
+
| `'file:/abs/path/to/db.sqlite'` | `SQLiteAdapter` | Same as the plain path form, with the scheme spelled out. |
|
|
2135
|
+
| `'postgres://user:pass@host:5432/db'` | `PostgresAdapter` | Postgres-compatible cloud DB (Supabase, Neon, RDS, …). |
|
|
2136
|
+
| `'postgresql://user:pass@host:5432/db'` | `PostgresAdapter` | Same as `postgres://`. |
|
|
2137
|
+
| any string + `{ adapter: myAdapter }` | your adapter | Bring your own implementation. |
|
|
2138
|
+
|
|
2139
|
+
```ts
|
|
2140
|
+
import { Lattice } from 'latticesql';
|
|
2141
|
+
|
|
2142
|
+
// SQLite (default)
|
|
2143
|
+
const local = new Lattice('./data/lattice.db');
|
|
2144
|
+
|
|
2145
|
+
// Postgres (Supabase / Neon / RDS / etc.)
|
|
2146
|
+
const cloud = new Lattice('postgres://postgres:secret@db.example.com:5432/agent');
|
|
2147
|
+
|
|
2148
|
+
// Bring your own
|
|
2149
|
+
const custom = new Lattice('ignored', { adapter: new MyCustomAdapter() });
|
|
2150
|
+
```
|
|
2151
|
+
|
|
2152
|
+
The rest of the API — `define()`, `init()`, `query()`, `insert()`, `render()`, `migrate()`, `watch()`, `reverseSync()`, `reverseSeed()` — is unchanged across both backends.
|
|
2153
|
+
|
|
2154
|
+
### Postgres setup
|
|
2155
|
+
|
|
2156
|
+
`PostgresAdapter` depends on `pg` and `synckit`. Both are listed as `optionalDependencies`, so SQLite-only consumers don't pay the install cost. Install them when you actually use Postgres:
|
|
2157
|
+
|
|
2158
|
+
```bash
|
|
2159
|
+
npm install pg synckit
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
Then point Lattice at any Postgres-compatible database that speaks the standard wire protocol on port 5432:
|
|
2163
|
+
|
|
2164
|
+
```ts
|
|
2165
|
+
const lattice = new Lattice('postgres://user:pass@host:5432/db');
|
|
2166
|
+
await lattice.init();
|
|
2167
|
+
```
|
|
2168
|
+
|
|
2169
|
+
**Connection pooler note:** if you put a pooler (e.g. PgBouncer, Supabase pooler) in front of your database, prefer **session-mode pooling**. Transaction-mode poolers do not support prepared statements across transactions, which would break Lattice's `adapter.prepare()` pattern.
|
|
2170
|
+
|
|
2171
|
+
**Why a worker thread under the hood:** the `StorageAdapter` interface is synchronous because `better-sqlite3` is sync. Every Node Postgres client is async. `PostgresAdapter` runs `pg` inside a `synckit` worker thread and blocks the main thread on `Atomics.wait` until the worker posts its reply. Each query pays ~1–3 ms of message-passing overhead — fine for Lattice's batch-insert + periodic-render workload. If you ever need OLTP-grade throughput, the interface can grow an async variant without breaking SQLite consumers.
|
|
2172
|
+
|
|
2173
|
+
**Schema portability:** Lattice's table definitions are mostly portable SQL. The adapter handles the few dialect differences automatically:
|
|
2174
|
+
|
|
2175
|
+
- `?` placeholders are translated to `$1, $2, …` for Postgres. Single-quoted strings, double-quoted identifiers, and SQL comments are skipped — `?` characters inside those are left alone.
|
|
2176
|
+
- `BLOB` column types are translated to `BYTEA` inside `addColumn`. Use `BLOB` in your `TableDefinition` and it works on both backends.
|
|
2177
|
+
- `datetime('now')` and `RANDOM()` defaults are translated to `NOW()` and `random()` for Postgres.
|
|
2178
|
+
- Use `TEXT PRIMARY KEY` (UUIDs) for portable primary keys. `INTEGER PRIMARY KEY` auto-increments on SQLite but not Postgres — if you need it on Postgres, use a sequence or `GENERATED ALWAYS AS IDENTITY`.
|
|
2179
|
+
|
|
2180
|
+
### Bring your own adapter
|
|
2181
|
+
|
|
2182
|
+
The interface is small enough to implement against any backend:
|
|
2183
|
+
|
|
2184
|
+
```ts
|
|
2185
|
+
export interface StorageAdapter {
|
|
2186
|
+
run(sql: string, params?: unknown[]): void;
|
|
2187
|
+
get(sql: string, params?: unknown[]): Row | undefined;
|
|
2188
|
+
all(sql: string, params?: unknown[]): Row[];
|
|
2189
|
+
prepare(sql: string): PreparedStatement;
|
|
2190
|
+
open(): void;
|
|
2191
|
+
close(): void;
|
|
2192
|
+
introspectColumns(table: string): string[];
|
|
2193
|
+
addColumn(table: string, column: string, typeSpec: string): void;
|
|
2194
|
+
}
|
|
2195
|
+
```
|
|
2196
|
+
|
|
2197
|
+
Pass your implementation via `options.adapter`:
|
|
2198
|
+
|
|
2199
|
+
```ts
|
|
2200
|
+
import { Lattice } from 'latticesql';
|
|
2201
|
+
import type { StorageAdapter } from 'latticesql';
|
|
2202
|
+
|
|
2203
|
+
class MyMySQLAdapter implements StorageAdapter { /* … */ }
|
|
2204
|
+
|
|
2205
|
+
const lattice = new Lattice('ignored', { adapter: new MyMySQLAdapter() });
|
|
2206
|
+
```
|
|
2207
|
+
|
|
2208
|
+
### Limitations
|
|
2209
|
+
|
|
2210
|
+
- `PreparedStatement.run()` returns `lastInsertRowid: 0` on the Postgres path. SQLite consumers that rely on `lastInsertRowid` should switch to `TEXT PRIMARY KEY` (UUIDs) for portability, or write `INSERT … RETURNING id` queries explicitly.
|
|
2211
|
+
- Two SQLite-only paths remain: `fixSchemaConflicts(db)` (the lifecycle helper that takes a raw `Database.Database` argument) and the writeback session-apply machinery. Postgres consumers shouldn't call them.
|
|
2212
|
+
- A built-in migration tool (SQLite → Postgres) is not included. Use a generic SQLite → Postgres migration tool, or `INSERT … SELECT` row-by-row.
|
|
2213
|
+
|
|
2214
|
+
---
|
|
2215
|
+
|
|
2156
2216
|
## Architecture
|
|
2157
2217
|
|
|
2158
2218
|
```
|
|
@@ -2172,7 +2232,7 @@ const db = new Lattice('./app.db', {
|
|
|
2172
2232
|
│ │ entity contexts │ │
|
|
2173
2233
|
├──────────────────┴──────────────────┴───────────────────────────────┤
|
|
2174
2234
|
│ SQLiteAdapter │
|
|
2175
|
-
│
|
|
2235
|
+
│ (better-sqlite3 — synchronous I/O) │
|
|
2176
2236
|
└──────────────────────────────────────────────────────────────────────┘
|
|
2177
2237
|
│ │
|
|
2178
2238
|
│ compileRender() │ lifecycle/
|
|
@@ -2190,7 +2250,7 @@ const db = new Lattice('./app.db', {
|
|
|
2190
2250
|
|
|
2191
2251
|
**Key design decisions:**
|
|
2192
2252
|
|
|
2193
|
-
- **Synchronous SQLite** — `better-sqlite3` gives synchronous reads; all Lattice CRUD methods return Promises for API consistency but resolve synchronously under the hood.
|
|
2253
|
+
- **Synchronous SQLite** — `better-sqlite3` gives synchronous reads; all Lattice CRUD methods return Promises for API consistency but resolve synchronously under the hood.
|
|
2194
2254
|
- **Compile-time render** — `RenderSpec` is compiled to a plain `(rows: Row[]) => string` function at `define()`-time, not at render-time. `RenderEngine` stays unchanged.
|
|
2195
2255
|
- **Atomic writes** — files are written to a `.tmp` sibling then renamed. No partial writes, no reader sees incomplete content.
|
|
2196
2256
|
- **Schema-additive only** — Lattice never drops tables or columns automatically; it only adds missing ones.
|