@rulvar/store-sqlite 1.44.1 → 1.45.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/dist/index.d.ts +19 -2
- package/dist/index.js +50 -6
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -19,12 +19,27 @@ interface SqliteStoreOptions {
|
|
|
19
19
|
now?: () => number;
|
|
20
20
|
}
|
|
21
21
|
declare class SqliteStore implements MetaLookupStore, LeasableStore {
|
|
22
|
+
/**
|
|
23
|
+
* The fenced writes promise (fenced run state RFC, phase 2): every
|
|
24
|
+
* lease-carrying mutation of this store (append, putMeta, delete)
|
|
25
|
+
* verifies the lease is the current holder FOR THE MUTATED RUN,
|
|
26
|
+
* atomically with the mutation, and rejects stale or mismatched
|
|
27
|
+
* holders with the typed LeaseHeldError leaving nothing changed.
|
|
28
|
+
*/
|
|
29
|
+
readonly fencedWrites = true;
|
|
22
30
|
private readonly db;
|
|
23
31
|
private readonly ttlMs;
|
|
24
32
|
private readonly now;
|
|
25
33
|
constructor(options: SqliteStoreOptions);
|
|
26
34
|
close(): void;
|
|
27
35
|
private liveLease;
|
|
36
|
+
/**
|
|
37
|
+
* A lease fences exactly the run it names: guarding a mutation of a
|
|
38
|
+
* DIFFERENT run with it would pass the holder check while touching
|
|
39
|
+
* state the lease never protected, so the mismatch rejects typed
|
|
40
|
+
* before any check runs.
|
|
41
|
+
*/
|
|
42
|
+
private requireRunMatch;
|
|
28
43
|
/** Rejects unless `lease` is the CURRENT live lease for its run. */
|
|
29
44
|
private assertFencing;
|
|
30
45
|
/**
|
|
@@ -40,10 +55,12 @@ declare class SqliteStore implements MetaLookupStore, LeasableStore {
|
|
|
40
55
|
private insertEntry;
|
|
41
56
|
append(runId: string, e: JournalEntry, lease?: Lease): Promise<void>;
|
|
42
57
|
load(runId: string): Promise<JournalEntry[]>;
|
|
43
|
-
|
|
58
|
+
private upsertMeta;
|
|
59
|
+
putMeta(m: RunMeta, lease?: Lease): Promise<void>;
|
|
44
60
|
getMeta(runId: string): Promise<RunMeta | undefined>;
|
|
45
61
|
listRuns(f?: RunFilter): Promise<RunMeta[]>;
|
|
46
|
-
|
|
62
|
+
private deleteRows;
|
|
63
|
+
delete(runId: string, lease?: Lease): Promise<void>;
|
|
47
64
|
/**
|
|
48
65
|
* TTL introspection (the LeasableStore optional capability): lets
|
|
49
66
|
* createWorker verify at construction that its renew cadence matches
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,12 @@ import { ConfigError, JournalOrderViolation, LeaseHeldError, metaMatchesFilter }
|
|
|
21
21
|
* cross-process window where a takeover landing between them let the
|
|
22
22
|
* superseded holder append a visible entry, extend the successor's
|
|
23
23
|
* lease, or delete it outright (the fenced-run-state RFC, finding F3).
|
|
24
|
+
* - Fenced writes (`fencedWrites: true`, the RFC's phase 2): putMeta
|
|
25
|
+
* and delete accept the same optional lease under the same atomic
|
|
26
|
+
* rule, and every lease-guarded mutation additionally requires the
|
|
27
|
+
* lease's runId to BE the mutated run, so a superseded worker can
|
|
28
|
+
* neither strand a run through a stale terminal meta write (F1) nor
|
|
29
|
+
* delete live run state (F4).
|
|
24
30
|
* - acquire on a held, unexpired lease rejects with LeaseHeldError; the
|
|
25
31
|
* holder MUST renew at an interval of at most ttl/3; an unrenewed
|
|
26
32
|
* lease is reclaimable after ttl and reclaiming advances the epoch.
|
|
@@ -31,6 +37,14 @@ import { ConfigError, JournalOrderViolation, LeaseHeldError, metaMatchesFilter }
|
|
|
31
37
|
const DEFAULT_LEASE_TTL_MS = 6e4;
|
|
32
38
|
const wallClock = Date.now.bind(globalThis);
|
|
33
39
|
var SqliteStore = class {
|
|
40
|
+
/**
|
|
41
|
+
* The fenced writes promise (fenced run state RFC, phase 2): every
|
|
42
|
+
* lease-carrying mutation of this store (append, putMeta, delete)
|
|
43
|
+
* verifies the lease is the current holder FOR THE MUTATED RUN,
|
|
44
|
+
* atomically with the mutation, and rejects stale or mismatched
|
|
45
|
+
* holders with the typed LeaseHeldError leaving nothing changed.
|
|
46
|
+
*/
|
|
47
|
+
fencedWrites = true;
|
|
34
48
|
db;
|
|
35
49
|
ttlMs;
|
|
36
50
|
now;
|
|
@@ -78,6 +92,15 @@ var SqliteStore = class {
|
|
|
78
92
|
if (row === void 0 || row.expires_at <= this.now()) return;
|
|
79
93
|
return row;
|
|
80
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* A lease fences exactly the run it names: guarding a mutation of a
|
|
97
|
+
* DIFFERENT run with it would pass the holder check while touching
|
|
98
|
+
* state the lease never protected, so the mismatch rejects typed
|
|
99
|
+
* before any check runs.
|
|
100
|
+
*/
|
|
101
|
+
requireRunMatch(lease, runId, mutation) {
|
|
102
|
+
if (lease.runId !== runId) throw new LeaseHeldError(`lease for run '${lease.runId}' (owner ${lease.owner}, epoch ${lease.epoch}) cannot guard a ${mutation} of run '${runId}'; the mutation is rejected and nothing changed`);
|
|
103
|
+
}
|
|
81
104
|
/** Rejects unless `lease` is the CURRENT live lease for its run. */
|
|
82
105
|
assertFencing(lease) {
|
|
83
106
|
const live = this.liveLease(lease.runId);
|
|
@@ -112,6 +135,7 @@ var SqliteStore = class {
|
|
|
112
135
|
}
|
|
113
136
|
async append(runId, e, lease) {
|
|
114
137
|
if (lease !== void 0) {
|
|
138
|
+
this.requireRunMatch(lease, runId, "journal append");
|
|
115
139
|
this.fenced(lease, () => {
|
|
116
140
|
this.insertEntry(runId, e);
|
|
117
141
|
});
|
|
@@ -122,9 +146,19 @@ var SqliteStore = class {
|
|
|
122
146
|
async load(runId) {
|
|
123
147
|
return this.db.prepare("SELECT payload FROM entries WHERE run_id = ? ORDER BY id").all(runId).map((row) => JSON.parse(row.payload));
|
|
124
148
|
}
|
|
125
|
-
|
|
149
|
+
upsertMeta(m) {
|
|
126
150
|
this.db.prepare("INSERT INTO meta (run_id, payload) VALUES (?, ?) ON CONFLICT(run_id) DO UPDATE SET payload = excluded.payload").run(m.runId, JSON.stringify(m));
|
|
127
151
|
}
|
|
152
|
+
async putMeta(m, lease) {
|
|
153
|
+
if (lease !== void 0) {
|
|
154
|
+
this.requireRunMatch(lease, m.runId, "meta write");
|
|
155
|
+
this.fenced(lease, () => {
|
|
156
|
+
this.upsertMeta(m);
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.upsertMeta(m);
|
|
161
|
+
}
|
|
128
162
|
async getMeta(runId) {
|
|
129
163
|
const row = this.db.prepare("SELECT payload FROM meta WHERE run_id = ?").get(runId);
|
|
130
164
|
return row === void 0 ? void 0 : JSON.parse(row.payload);
|
|
@@ -147,13 +181,23 @@ var SqliteStore = class {
|
|
|
147
181
|
const sql = "SELECT payload FROM meta" + (where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`) + " ORDER BY run_id";
|
|
148
182
|
return this.db.prepare(sql).all(...params).map((row) => JSON.parse(row.payload)).filter((meta) => metaMatchesFilter(meta, f));
|
|
149
183
|
}
|
|
150
|
-
|
|
184
|
+
deleteRows(runId) {
|
|
185
|
+
this.db.prepare("DELETE FROM entries WHERE run_id = ?").run(runId);
|
|
186
|
+
this.db.prepare("DELETE FROM meta WHERE run_id = ?").run(runId);
|
|
187
|
+
this.db.prepare("DELETE FROM leases WHERE run_id = ?").run(runId);
|
|
188
|
+
this.db.prepare("DELETE FROM epochs WHERE run_id = ?").run(runId);
|
|
189
|
+
}
|
|
190
|
+
async delete(runId, lease) {
|
|
191
|
+
if (lease !== void 0) {
|
|
192
|
+
this.requireRunMatch(lease, runId, "run deletion");
|
|
193
|
+
this.fenced(lease, () => {
|
|
194
|
+
this.deleteRows(runId);
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
151
198
|
this.db.exec("BEGIN IMMEDIATE");
|
|
152
199
|
try {
|
|
153
|
-
this.
|
|
154
|
-
this.db.prepare("DELETE FROM meta WHERE run_id = ?").run(runId);
|
|
155
|
-
this.db.prepare("DELETE FROM leases WHERE run_id = ?").run(runId);
|
|
156
|
-
this.db.prepare("DELETE FROM epochs WHERE run_id = ?").run(runId);
|
|
200
|
+
this.deleteRows(runId);
|
|
157
201
|
this.db.exec("COMMIT");
|
|
158
202
|
} catch (thrown) {
|
|
159
203
|
this.db.exec("ROLLBACK");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/store-sqlite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"description": "Rulvar SQLite store implementing JournalStore and LeasableStore with a fencing epoch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.45.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.0",
|
|
29
29
|
"tsdown": "^0.22.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/store-conformance": "1.
|
|
31
|
+
"@rulvar/store-conformance": "1.45.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|