sqlite-zod-orm 3.3.0 → 3.3.1
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 +32 -7
- package/dist/index.js +5 -2
- package/package.json +1 -1
- package/src/database.ts +16 -4
- package/src/query-builder.ts +5 -5
package/README.md
CHANGED
|
@@ -236,30 +236,55 @@ unsub();
|
|
|
236
236
|
┌──────────────────────────────────────────────────┐
|
|
237
237
|
│ Every {interval}ms: │
|
|
238
238
|
│ │
|
|
239
|
-
│ 1. Check in-memory
|
|
239
|
+
│ 1. Check revision (in-memory + data_version) │
|
|
240
240
|
│ 2. Run: SELECT COUNT(*), MAX(id) │
|
|
241
241
|
│ FROM users WHERE role = 'admin' │
|
|
242
242
|
│ │
|
|
243
|
-
│ 3. Combine into fingerprint: "count:max:rev"
|
|
243
|
+
│ 3. Combine into fingerprint: "count:max:rev:dv" │
|
|
244
244
|
│ │
|
|
245
245
|
│ 4. If fingerprint changed → re-run full query │
|
|
246
246
|
│ and call your callback │
|
|
247
247
|
└──────────────────────────────────────────────────┘
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
Two signals combine to detect **all** changes from **any** source:
|
|
251
251
|
|
|
252
|
-
|
|
|
252
|
+
| Signal | Catches | How |
|
|
253
253
|
|---|---|---|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
254
|
+
| **In-memory revision** | Same-process writes | Bumped by CRUD methods |
|
|
255
|
+
| **PRAGMA data_version** | Cross-process writes | SQLite bumps it on external commits |
|
|
256
|
+
|
|
257
|
+
| Operation | Detected | Source |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| INSERT | ✅ | Same or other process |
|
|
260
|
+
| DELETE | ✅ | Same or other process |
|
|
261
|
+
| UPDATE | ✅ | Same or other process |
|
|
262
|
+
|
|
263
|
+
No triggers. No `_changes` table. Zero disk overhead. WAL mode is enabled by default for concurrent read/write.
|
|
264
|
+
|
|
265
|
+
### Multi-process example
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Process A — watches for new/edited messages
|
|
269
|
+
const unsub = db.messages.select()
|
|
270
|
+
.orderBy('id', 'asc')
|
|
271
|
+
.subscribe((messages) => {
|
|
272
|
+
console.log('Messages:', messages);
|
|
273
|
+
}, { interval: 200 });
|
|
274
|
+
|
|
275
|
+
// Process B — writes to the same DB file (different process)
|
|
276
|
+
// sqlite3 chat.db "INSERT INTO messages (text, author) VALUES ('hello', 'Bob')"
|
|
277
|
+
// → Process A's callback fires with updated message list!
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Run `bun examples/messages-demo.ts` for a full working demo.
|
|
257
281
|
|
|
258
282
|
**Use cases:**
|
|
259
283
|
- Live dashboards (poll every 1-5s)
|
|
260
284
|
- Real-time chat / message lists
|
|
261
285
|
- Auto-refreshing data tables
|
|
262
286
|
- Watching filtered subsets of data
|
|
287
|
+
- Cross-process data synchronization
|
|
263
288
|
|
|
264
289
|
---
|
|
265
290
|
|
package/dist/index.js
CHANGED
|
@@ -334,7 +334,7 @@ class QueryBuilder {
|
|
|
334
334
|
try {
|
|
335
335
|
const fpRows = this.executor(fingerprintSQL.sql, fingerprintSQL.params, true);
|
|
336
336
|
const fpRow = fpRows[0];
|
|
337
|
-
const rev = this.revisionGetter?.() ?? 0;
|
|
337
|
+
const rev = this.revisionGetter?.() ?? "0";
|
|
338
338
|
const currentFingerprint = `${fpRow?._cnt ?? 0}:${fpRow?._max ?? 0}:${rev}`;
|
|
339
339
|
if (currentFingerprint !== lastFingerprint) {
|
|
340
340
|
lastFingerprint = currentFingerprint;
|
|
@@ -4659,6 +4659,7 @@ class _Database {
|
|
|
4659
4659
|
_revisions = {};
|
|
4660
4660
|
constructor(dbFile, schemas, options = {}) {
|
|
4661
4661
|
this.db = new SqliteDatabase(dbFile);
|
|
4662
|
+
this.db.run("PRAGMA journal_mode = WAL");
|
|
4662
4663
|
this.db.run("PRAGMA foreign_keys = ON");
|
|
4663
4664
|
this.schemas = schemas;
|
|
4664
4665
|
this.options = options;
|
|
@@ -4724,7 +4725,9 @@ class _Database {
|
|
|
4724
4725
|
this._revisions[entityName] = (this._revisions[entityName] ?? 0) + 1;
|
|
4725
4726
|
}
|
|
4726
4727
|
_getRevision(entityName) {
|
|
4727
|
-
|
|
4728
|
+
const rev = this._revisions[entityName] ?? 0;
|
|
4729
|
+
const dataVersion = this.db.query("PRAGMA data_version").get()?.data_version ?? 0;
|
|
4730
|
+
return `${rev}:${dataVersion}`;
|
|
4728
4731
|
}
|
|
4729
4732
|
insert(entityName, data) {
|
|
4730
4733
|
const schema = this.schemas[entityName];
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -36,6 +36,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
36
36
|
|
|
37
37
|
constructor(dbFile: string, schemas: Schemas, options: DatabaseOptions = {}) {
|
|
38
38
|
this.db = new SqliteDatabase(dbFile);
|
|
39
|
+
this.db.run('PRAGMA journal_mode = WAL'); // WAL enables concurrent read + write
|
|
39
40
|
this.db.run('PRAGMA foreign_keys = ON');
|
|
40
41
|
this.schemas = schemas;
|
|
41
42
|
this.options = options;
|
|
@@ -112,7 +113,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
// ===========================================================================
|
|
115
|
-
// Revision Tracking (in-memory
|
|
116
|
+
// Revision Tracking (in-memory + cross-process)
|
|
116
117
|
// ===========================================================================
|
|
117
118
|
|
|
118
119
|
/** Bump the revision counter for a table. Called on every write. */
|
|
@@ -120,9 +121,20 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
120
121
|
this._revisions[entityName] = (this._revisions[entityName] ?? 0) + 1;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Get a composite revision string for a table.
|
|
126
|
+
*
|
|
127
|
+
* Combines two signals:
|
|
128
|
+
* - In-memory counter: catches writes from THIS process (our CRUD methods bump it)
|
|
129
|
+
* - PRAGMA data_version: catches writes from OTHER processes (SQLite bumps it
|
|
130
|
+
* whenever another connection commits, but NOT for the current connection)
|
|
131
|
+
*
|
|
132
|
+
* Together they detect ALL changes regardless of source, with zero disk overhead.
|
|
133
|
+
*/
|
|
134
|
+
public _getRevision(entityName: string): string {
|
|
135
|
+
const rev = this._revisions[entityName] ?? 0;
|
|
136
|
+
const dataVersion = (this.db.query('PRAGMA data_version').get() as any)?.data_version ?? 0;
|
|
137
|
+
return `${rev}:${dataVersion}`;
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
// ===========================================================================
|
package/src/query-builder.ts
CHANGED
|
@@ -167,7 +167,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
167
167
|
private singleExecutor: (sql: string, params: any[], raw: boolean) => any | null;
|
|
168
168
|
private joinResolver: ((fromTable: string, toTable: string) => { fk: string; pk: string } | null) | null;
|
|
169
169
|
private conditionResolver: ((conditions: Record<string, any>) => Record<string, any>) | null;
|
|
170
|
-
private revisionGetter: (() =>
|
|
170
|
+
private revisionGetter: (() => string) | null;
|
|
171
171
|
|
|
172
172
|
constructor(
|
|
173
173
|
tableName: string,
|
|
@@ -175,7 +175,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
175
175
|
singleExecutor: (sql: string, params: any[], raw: boolean) => any | null,
|
|
176
176
|
joinResolver?: ((fromTable: string, toTable: string) => { fk: string; pk: string } | null) | null,
|
|
177
177
|
conditionResolver?: ((conditions: Record<string, any>) => Record<string, any>) | null,
|
|
178
|
-
revisionGetter?: (() =>
|
|
178
|
+
revisionGetter?: (() => string) | null,
|
|
179
179
|
) {
|
|
180
180
|
this.tableName = tableName;
|
|
181
181
|
this.executor = executor;
|
|
@@ -459,9 +459,9 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
459
459
|
// Run lightweight fingerprint check
|
|
460
460
|
const fpRows = this.executor(fingerprintSQL.sql, fingerprintSQL.params, true);
|
|
461
461
|
const fpRow = fpRows[0] as any;
|
|
462
|
-
// Include in-memory
|
|
463
|
-
// This
|
|
464
|
-
const rev = this.revisionGetter?.() ?? 0;
|
|
462
|
+
// Include revision in fingerprint (combines in-memory counter + PRAGMA data_version).
|
|
463
|
+
// This detects ALL changes: same-process and cross-process.
|
|
464
|
+
const rev = this.revisionGetter?.() ?? '0';
|
|
465
465
|
const currentFingerprint = `${fpRow?._cnt ?? 0}:${fpRow?._max ?? 0}:${rev}`;
|
|
466
466
|
|
|
467
467
|
if (currentFingerprint !== lastFingerprint) {
|