@yieldstar/sqlite-runtime 0.5.0 → 0.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.
Files changed (81) hide show
  1. package/dist/bun.js +15 -17
  2. package/dist/dao/effect-waiters-dao.d.ts +19 -0
  3. package/dist/dao/effect-waiters-dao.d.ts.map +1 -0
  4. package/dist/dao/effects-dao.d.ts +17 -0
  5. package/dist/dao/effects-dao.d.ts.map +1 -0
  6. package/dist/dao/executions-dao.d.ts +23 -0
  7. package/dist/dao/executions-dao.d.ts.map +1 -0
  8. package/dist/dao/start-requests-dao.d.ts +15 -0
  9. package/dist/dao/start-requests-dao.d.ts.map +1 -0
  10. package/dist/dao/step-response-dao.d.ts +1 -0
  11. package/dist/dao/step-response-dao.d.ts.map +1 -1
  12. package/dist/dao/store-waiters-dao.d.ts +26 -0
  13. package/dist/dao/store-waiters-dao.d.ts.map +1 -0
  14. package/dist/index-083bmj5k.js +800 -0
  15. package/dist/index-4kybaxga.js +798 -0
  16. package/dist/index-a8thbaee.js +718 -0
  17. package/dist/index-ag93sv33.js +664 -0
  18. package/dist/index-eabcgmaa.js +996 -0
  19. package/dist/index-gjfs8rn6.js +997 -0
  20. package/dist/index-h3h7r4b6.js +981 -0
  21. package/dist/index-ma984fze.js +718 -0
  22. package/dist/index-ne28djfn.js +718 -0
  23. package/dist/index-v6rn0pr6.js +664 -0
  24. package/dist/index-wx6vk994.js +669 -0
  25. package/dist/index.d.ts +3 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +11 -13
  28. package/dist/node.js +15 -17
  29. package/dist/sqlite-effect.d.ts +30 -0
  30. package/dist/sqlite-effect.d.ts.map +1 -0
  31. package/dist/sqlite-execution.d.ts +54 -0
  32. package/dist/sqlite-execution.d.ts.map +1 -0
  33. package/dist/sqlite-heap.d.ts +12 -4
  34. package/dist/sqlite-heap.d.ts.map +1 -1
  35. package/dist/sqlite-runtime.d.ts +22 -0
  36. package/dist/sqlite-runtime.d.ts.map +1 -0
  37. package/dist/sqlite-store.d.ts +31 -25
  38. package/dist/sqlite-store.d.ts.map +1 -1
  39. package/dist/sqlite-transaction.d.ts +9 -0
  40. package/dist/sqlite-transaction.d.ts.map +1 -0
  41. package/package.json +2 -2
  42. package/src/dao/effect-waiters-dao.ts +63 -0
  43. package/src/dao/effects-dao.ts +123 -0
  44. package/src/dao/executions-dao.ts +188 -0
  45. package/src/dao/start-requests-dao.ts +72 -0
  46. package/src/dao/step-response-dao.ts +16 -1
  47. package/src/dao/store-waiters-dao.ts +92 -0
  48. package/src/index.ts +6 -4
  49. package/src/sqlite-effect.ts +99 -0
  50. package/src/sqlite-execution.ts +238 -0
  51. package/src/sqlite-heap.ts +64 -12
  52. package/src/sqlite-runtime.ts +36 -0
  53. package/src/sqlite-store.ts +184 -520
  54. package/src/sqlite-transaction.ts +19 -0
  55. package/dist/dao/task-queue-dao.d.ts +0 -21
  56. package/dist/dao/task-queue-dao.d.ts.map +0 -1
  57. package/dist/dao/timers-dao.d.ts +0 -22
  58. package/dist/dao/timers-dao.d.ts.map +0 -1
  59. package/dist/index-4vq70w0r.js +0 -796
  60. package/dist/index-s8wqgfwt.js +0 -19
  61. package/dist/index-x891hxwg.js +0 -796
  62. package/dist/node-driver.d.ts +0 -10
  63. package/dist/node-driver.d.ts.map +0 -1
  64. package/dist/sqlite-event-loop.d.ts +0 -18
  65. package/dist/sqlite-event-loop.d.ts.map +0 -1
  66. package/dist/sqlite-scheduler.d.ts +0 -13
  67. package/dist/sqlite-scheduler.d.ts.map +0 -1
  68. package/dist/sqlite-store.test.d.ts +0 -2
  69. package/dist/sqlite-store.test.d.ts.map +0 -1
  70. package/dist/sqlite-task-queue.d.ts +0 -26
  71. package/dist/sqlite-task-queue.d.ts.map +0 -1
  72. package/dist/sqlite-timers.d.ts +0 -18
  73. package/dist/sqlite-timers.d.ts.map +0 -1
  74. package/src/dao/task-queue-dao.ts +0 -79
  75. package/src/dao/timers-dao.ts +0 -81
  76. package/src/sqlite-event-loop.ts +0 -41
  77. package/src/sqlite-scheduler.ts +0 -24
  78. package/src/sqlite-task-queue.ts +0 -64
  79. package/src/sqlite-timers.ts +0 -40
  80. package/tsconfig.tsbuildinfo +0 -1
  81. /package/dist/{index-h7bm9xg4.js → index-dapqh99v.js} +0 -0
@@ -0,0 +1,669 @@
1
+ // src/sqlite-runtime.ts
2
+ import { Runtime } from "@yieldstar/core";
3
+
4
+ // src/sqlite-execution.ts
5
+ import {
6
+ acknowledgeExecution,
7
+ claimExecution,
8
+ createExecutionState,
9
+ DEFAULT_REDELIVERY_TIMEOUT,
10
+ executionIdConflict,
11
+ executionNotFound,
12
+ isExecutionClaimable,
13
+ prepareExecutionAdmission,
14
+ requestExecutionWake,
15
+ toExecutionRecord
16
+ } from "@yieldstar/core";
17
+
18
+ // src/dao/executions-dao.ts
19
+ import { nextClaimAt } from "@yieldstar/core";
20
+ class ExecutionsDao {
21
+ db;
22
+ constructor(db) {
23
+ this.db = db;
24
+ this.setupDb();
25
+ }
26
+ setupDb() {
27
+ this.db.run(`
28
+ CREATE TABLE IF NOT EXISTS executions (
29
+ execution_id TEXT PRIMARY KEY,
30
+ workflow_id TEXT NOT NULL,
31
+ params TEXT NOT NULL,
32
+ context TEXT NOT NULL,
33
+ status TEXT NOT NULL,
34
+ outcome TEXT,
35
+ admitted_at INTEGER NOT NULL,
36
+ updated_at INTEGER NOT NULL,
37
+ due_at INTEGER,
38
+ claim_token TEXT,
39
+ claimed_due_at INTEGER,
40
+ claim_expires_at INTEGER,
41
+ next_claim_at INTEGER
42
+ );
43
+ CREATE INDEX IF NOT EXISTS idx_executions_next_claim_at
44
+ ON executions(next_claim_at) WHERE next_claim_at IS NOT NULL;
45
+ `);
46
+ }
47
+ getState(executionId) {
48
+ const row = this.db.query(`SELECT * FROM executions WHERE execution_id = $executionId`).get({ $executionId: executionId });
49
+ return row ? toState(row) : undefined;
50
+ }
51
+ getClaimableIds(now) {
52
+ return this.db.query(`SELECT execution_id FROM executions
53
+ WHERE next_claim_at IS NOT NULL AND next_claim_at <= $now
54
+ ORDER BY next_claim_at, execution_id`).all({ $now: now }).map((row) => row.execution_id);
55
+ }
56
+ insertState(state) {
57
+ const result = this.db.query(`INSERT INTO executions (
58
+ execution_id, workflow_id, params, context, status, outcome,
59
+ admitted_at, updated_at, due_at, claim_token, claimed_due_at,
60
+ claim_expires_at, next_claim_at
61
+ ) VALUES (
62
+ $executionId, $workflowId, $params, $context, $status, $outcome,
63
+ $admittedAt, $updatedAt, $dueAt, $claimToken, $claimedDueAt,
64
+ $claimExpiresAt, $nextClaimAt
65
+ )
66
+ ON CONFLICT(execution_id) DO NOTHING`).run(toBindings(state));
67
+ return Number(result.changes) === 1;
68
+ }
69
+ updateState(state) {
70
+ this.db.query(`UPDATE executions SET
71
+ workflow_id = $workflowId,
72
+ params = $params,
73
+ context = $context,
74
+ status = $status,
75
+ outcome = $outcome,
76
+ admitted_at = $admittedAt,
77
+ updated_at = $updatedAt,
78
+ due_at = $dueAt,
79
+ claim_token = $claimToken,
80
+ claimed_due_at = $claimedDueAt,
81
+ claim_expires_at = $claimExpiresAt,
82
+ next_claim_at = $nextClaimAt
83
+ WHERE execution_id = $executionId`).run(toBindings(state));
84
+ }
85
+ }
86
+ function toState(row) {
87
+ return {
88
+ executionId: row.execution_id,
89
+ workflowId: row.workflow_id,
90
+ params: row.params,
91
+ context: row.context,
92
+ status: row.status,
93
+ outcome: row.outcome,
94
+ admittedAt: Number(row.admitted_at),
95
+ updatedAt: Number(row.updated_at),
96
+ dueAt: toNumberOrNull(row.due_at),
97
+ claimToken: row.claim_token,
98
+ claimedDueAt: toNumberOrNull(row.claimed_due_at),
99
+ claimExpiresAt: toNumberOrNull(row.claim_expires_at)
100
+ };
101
+ }
102
+ function toBindings(state) {
103
+ return {
104
+ $executionId: state.executionId,
105
+ $workflowId: state.workflowId,
106
+ $params: state.params,
107
+ $context: state.context,
108
+ $status: state.status,
109
+ $outcome: state.outcome,
110
+ $admittedAt: state.admittedAt,
111
+ $updatedAt: state.updatedAt,
112
+ $dueAt: state.dueAt,
113
+ $claimToken: state.claimToken,
114
+ $claimedDueAt: state.claimedDueAt,
115
+ $claimExpiresAt: state.claimExpiresAt,
116
+ $nextClaimAt: nextClaimAt(state)
117
+ };
118
+ }
119
+ function toNumberOrNull(value) {
120
+ return value === null ? null : Number(value);
121
+ }
122
+
123
+ // src/sqlite-transaction.ts
124
+ function runTransaction(db, fn) {
125
+ db.run("BEGIN IMMEDIATE");
126
+ try {
127
+ const result = fn();
128
+ db.run("COMMIT");
129
+ return result;
130
+ } catch (err) {
131
+ db.run("ROLLBACK");
132
+ throw err;
133
+ }
134
+ }
135
+
136
+ // src/sqlite-execution.ts
137
+ class SqliteExecutionClient2 {
138
+ db;
139
+ executionsDao;
140
+ clock;
141
+ redeliveryTimeout;
142
+ constructor(db, options = {}) {
143
+ this.db = db;
144
+ this.executionsDao = new ExecutionsDao(db);
145
+ this.clock = options.clock ?? (() => Date.now());
146
+ this.redeliveryTimeout = options.redeliveryTimeout ?? DEFAULT_REDELIVERY_TIMEOUT;
147
+ }
148
+ async admit(request) {
149
+ const prepared = prepareExecutionAdmission(request);
150
+ if (!prepared.ok) {
151
+ return {
152
+ status: "rejected",
153
+ code: prepared.code,
154
+ message: prepared.message
155
+ };
156
+ }
157
+ return runTransaction(this.db, () => {
158
+ const state = createExecutionState({
159
+ executionId: prepared.executionId,
160
+ workflowId: request.workflowId,
161
+ encoded: prepared.encoded,
162
+ now: this.clock()
163
+ });
164
+ if (!this.executionsDao.insertState(state)) {
165
+ return executionIdConflict(prepared.executionId);
166
+ }
167
+ return { status: "accepted", executionId: prepared.executionId };
168
+ });
169
+ }
170
+ async get(executionId) {
171
+ const state = this.executionsDao.getState(executionId);
172
+ return state ? toExecutionRecord(state) : undefined;
173
+ }
174
+ async requestWake(request) {
175
+ return runTransaction(this.db, () => {
176
+ const state = this.executionsDao.getState(request.executionId);
177
+ if (!state)
178
+ return executionNotFound(request.executionId);
179
+ const next = requestExecutionWake(state, request.wakeAt, this.clock());
180
+ if (!next)
181
+ return { status: "ignored" };
182
+ this.executionsDao.updateState(next);
183
+ return { status: "requested" };
184
+ });
185
+ }
186
+ async claimDue() {
187
+ return runTransaction(this.db, () => {
188
+ const now = this.clock();
189
+ const deliveries = [];
190
+ for (const executionId of this.executionsDao.getClaimableIds(now)) {
191
+ const state = this.executionsDao.getState(executionId);
192
+ if (!state || !isExecutionClaimable(state, now))
193
+ continue;
194
+ const token = crypto.randomUUID();
195
+ this.executionsDao.updateState(claimExecution(state, {
196
+ now,
197
+ token,
198
+ redeliveryTimeout: this.redeliveryTimeout
199
+ }));
200
+ deliveries.push({ executionId, token });
201
+ }
202
+ return deliveries;
203
+ });
204
+ }
205
+ async acknowledge(request) {
206
+ return runTransaction(this.db, () => {
207
+ const executionId = request.delivery.executionId;
208
+ const state = this.executionsDao.getState(executionId);
209
+ if (!state) {
210
+ throw new Error(executionNotFound(executionId).message);
211
+ }
212
+ const acknowledged = acknowledgeExecution(state, {
213
+ delivery: request.delivery,
214
+ outcome: request.outcome,
215
+ now: this.clock()
216
+ });
217
+ if (acknowledged.state !== state) {
218
+ this.executionsDao.updateState(acknowledged.state);
219
+ }
220
+ return acknowledged.result;
221
+ });
222
+ }
223
+ wakeInTransaction(executionId, wakeAt = this.clock()) {
224
+ const state = this.executionsDao.getState(executionId);
225
+ if (!state)
226
+ return;
227
+ const next = requestExecutionWake(state, wakeAt, this.clock());
228
+ if (next)
229
+ this.executionsDao.updateState(next);
230
+ }
231
+ }
232
+
233
+ // src/dao/step-response-dao.ts
234
+ class StepResponsesDao {
235
+ db;
236
+ constructor(db) {
237
+ this.db = db;
238
+ this.setupDb();
239
+ }
240
+ setupDb() {
241
+ this.db.run(`
242
+ CREATE TABLE IF NOT EXISTS step_responses (
243
+ execution_id TEXT NOT NULL,
244
+ step_key TEXT NOT NULL,
245
+ step_attempt INTEGER NOT NULL,
246
+ step_done BOOL NOT NULL,
247
+ step_response JSONB,
248
+ PRIMARY KEY (execution_id, step_key, step_attempt)
249
+ );
250
+ CREATE INDEX IF NOT EXISTS idx_execution_step_attempt
251
+ ON step_responses(execution_id, step_key, step_attempt DESC);
252
+ `);
253
+ }
254
+ getAllSteps() {
255
+ const query = this.db.query(`SELECT * FROM step_responses`);
256
+ return query.all();
257
+ }
258
+ deleteAll() {
259
+ const query = this.db.query(`DELETE FROM step_responses`);
260
+ query.run();
261
+ }
262
+ getLatestStepResponse(executionId, stepKey) {
263
+ const query = this.db.query(`SELECT * FROM step_responses
264
+ WHERE execution_id = $executionId
265
+ AND step_key = $stepKey
266
+ ORDER BY step_attempt DESC LIMIT 1`);
267
+ const result = query.get({
268
+ $executionId: executionId,
269
+ $stepKey: stepKey
270
+ });
271
+ return result;
272
+ }
273
+ insertStepResponse(executionId, stepKey, stepAttempt, stepDone, stepResponseJson) {
274
+ const query = this.db.query(`INSERT INTO step_responses (execution_id, step_key, step_attempt, step_done, step_response)
275
+ VALUES ($executionId, $stepKey, $stepAttempt, $stepDone, $stepResponse)`);
276
+ query.run({
277
+ $executionId: executionId,
278
+ $stepKey: stepKey,
279
+ $stepAttempt: stepAttempt,
280
+ $stepDone: stepDone,
281
+ $stepResponse: stepResponseJson
282
+ });
283
+ }
284
+ }
285
+
286
+ // src/dao/store-waiters-dao.ts
287
+ class StoreWaitersDao {
288
+ db;
289
+ constructor(db) {
290
+ this.db = db;
291
+ this.setupDb();
292
+ }
293
+ setupDb() {
294
+ this.db.run(`
295
+ CREATE TABLE IF NOT EXISTS store_waiters (
296
+ workflow_id TEXT NOT NULL,
297
+ execution_id TEXT NOT NULL,
298
+ step_key TEXT NOT NULL,
299
+ store_name TEXT NOT NULL,
300
+ store_id TEXT NOT NULL,
301
+ since_version INTEGER NOT NULL,
302
+ read_paths TEXT NOT NULL,
303
+ PRIMARY KEY (store_name, store_id, execution_id, step_key)
304
+ );
305
+ CREATE INDEX IF NOT EXISTS idx_store_waiters_step
306
+ ON store_waiters(execution_id, step_key);
307
+ `);
308
+ }
309
+ upsertWaiter(waiter) {
310
+ this.db.query(`INSERT INTO store_waiters
311
+ (workflow_id, execution_id, step_key, store_name, store_id, since_version, read_paths)
312
+ VALUES
313
+ ($workflowId, $executionId, $stepKey, $storeName, $storeId, $sinceVersion, $readPaths)
314
+ ON CONFLICT(store_name, store_id, execution_id, step_key)
315
+ DO UPDATE SET
316
+ workflow_id = excluded.workflow_id,
317
+ since_version = excluded.since_version,
318
+ read_paths = excluded.read_paths`).run({
319
+ $workflowId: waiter.workflowId,
320
+ $executionId: waiter.executionId,
321
+ $stepKey: waiter.stepKey,
322
+ $storeName: waiter.storeName,
323
+ $storeId: waiter.storeId,
324
+ $sinceVersion: waiter.sinceVersion,
325
+ $readPaths: JSON.stringify(waiter.readPaths)
326
+ });
327
+ }
328
+ getStoreWaiters(storeName, storeId) {
329
+ return this.db.query(`SELECT * FROM store_waiters
330
+ WHERE store_name = $storeName AND store_id = $storeId`).all({ $storeName: storeName, $storeId: storeId });
331
+ }
332
+ deleteStoreWaiters(storeName, storeId) {
333
+ this.db.query(`DELETE FROM store_waiters WHERE store_name = $storeName AND store_id = $storeId`).run({ $storeName: storeName, $storeId: storeId });
334
+ }
335
+ deleteStepWaiters(executionId, stepKey) {
336
+ this.db.query(`DELETE FROM store_waiters WHERE execution_id = $executionId AND step_key = $stepKey`).run({ $executionId: executionId, $stepKey: stepKey });
337
+ }
338
+ }
339
+
340
+ // src/sqlite-heap.ts
341
+ class SqliteHeapClient2 {
342
+ db;
343
+ stepResponsesDao;
344
+ storeWaitersDao;
345
+ constructor(db) {
346
+ this.db = db;
347
+ this.stepResponsesDao = new StepResponsesDao(db);
348
+ this.storeWaitersDao = new StoreWaitersDao(db);
349
+ }
350
+ async getAllSteps() {
351
+ return this.stepResponsesDao.getAllSteps();
352
+ }
353
+ async deleteAll() {
354
+ this.stepResponsesDao.deleteAll();
355
+ }
356
+ async readStep(params) {
357
+ const result = this.stepResponsesDao.getLatestStepResponse(params.executionId, params.stepKey);
358
+ if (!result?.step_response) {
359
+ return null;
360
+ }
361
+ return {
362
+ stepResponseJson: result.step_response,
363
+ meta: {
364
+ attempt: result.step_attempt,
365
+ done: Boolean(result.step_done)
366
+ }
367
+ };
368
+ }
369
+ async writeStep(params) {
370
+ runTransaction(this.db, () => {
371
+ this.stepResponsesDao.insertStepResponse(params.executionId, params.stepKey, params.stepAttempt, params.stepDone, params.stepResponseJson);
372
+ if (params.stepDone) {
373
+ this.storeWaitersDao.deleteStepWaiters(params.executionId, params.stepKey);
374
+ }
375
+ });
376
+ }
377
+ }
378
+
379
+ // src/sqlite-store.ts
380
+ import { v7 as uuidv7 } from "uuid";
381
+ import {
382
+ cloneStoreState,
383
+ CasStoreClient,
384
+ StoreNotFoundError,
385
+ storePathsIntersect,
386
+ validateStoreState
387
+ } from "@yieldstar/core";
388
+ class SqliteStoreClient2 extends CasStoreClient {
389
+ db;
390
+ executions;
391
+ storeWaitersDao;
392
+ constructor(params) {
393
+ super();
394
+ this.db = params.db;
395
+ this.executions = params.executions ?? new SqliteExecutionClient2(params.db);
396
+ this.setupDb();
397
+ this.storeWaitersDao = new StoreWaitersDao(params.db);
398
+ }
399
+ async getOrCreateStore(params) {
400
+ const existing = this.getStoreRow(params.definition.name, params.id);
401
+ if (existing) {
402
+ return toSnapshot(existing);
403
+ }
404
+ if (!params.initial) {
405
+ throw new StoreNotFoundError(params.definition.name, params.id);
406
+ }
407
+ const initialValue = params.initial;
408
+ const initial = typeof initialValue === "function" ? await initialValue() : initialValue;
409
+ const state = await validateStoreState(params.definition, initial);
410
+ return runTransaction(this.db, () => {
411
+ const existingTx = this.getStoreRow(params.definition.name, params.id);
412
+ if (existingTx) {
413
+ return toSnapshot(existingTx);
414
+ }
415
+ const instanceId = uuidv7();
416
+ this.db.query(`INSERT INTO stores (instance_id, store_name, store_id, version, state)
417
+ VALUES ($instanceId, $storeName, $storeId, 0, $state)`).run({
418
+ $instanceId: instanceId,
419
+ $storeName: params.definition.name,
420
+ $storeId: params.id,
421
+ $state: JSON.stringify(state)
422
+ });
423
+ return {
424
+ state: cloneStoreState(state),
425
+ instanceId,
426
+ version: 0
427
+ };
428
+ });
429
+ }
430
+ async getStore(params) {
431
+ const row = this.getStoreRow(params.definition.name, params.id);
432
+ if (!row) {
433
+ throw new StoreNotFoundError(params.definition.name, params.id);
434
+ }
435
+ return toSnapshot(row);
436
+ }
437
+ async getAppliedStoreStep(params) {
438
+ const row = this.getAppliedStepRow({
439
+ storeName: params.definition.name,
440
+ storeId: params.id,
441
+ stepId: params.stepId
442
+ });
443
+ return row ? { result: JSON.parse(row.result) } : undefined;
444
+ }
445
+ async commitStoreMutation(mutation) {
446
+ return runTransaction(this.db, () => {
447
+ if (mutation.stepId) {
448
+ const applied = this.getAppliedStepRow({
449
+ storeName: mutation.definition.name,
450
+ storeId: mutation.id,
451
+ stepId: mutation.stepId
452
+ });
453
+ if (applied) {
454
+ return {
455
+ status: "already-applied",
456
+ result: JSON.parse(applied.result)
457
+ };
458
+ }
459
+ }
460
+ const row = this.getStoreRow(mutation.definition.name, mutation.id);
461
+ if (!row) {
462
+ throw new StoreNotFoundError(mutation.definition.name, mutation.id);
463
+ }
464
+ if (row.instance_id !== mutation.expected.instanceId || row.version !== mutation.expected.version) {
465
+ return { status: "conflict", snapshot: toSnapshot(row) };
466
+ }
467
+ const version = mutation.expected.version + 1;
468
+ const changed = this.db.query(`UPDATE stores
469
+ SET version = $version, state = $state
470
+ WHERE instance_id = $instanceId AND version = $expectedVersion`).run({
471
+ $instanceId: mutation.expected.instanceId,
472
+ $expectedVersion: mutation.expected.version,
473
+ $version: version,
474
+ $state: JSON.stringify(mutation.nextState)
475
+ });
476
+ if (Number(changed.changes) !== 1) {
477
+ throw new Error("Store changed during its mutation transaction");
478
+ }
479
+ if (mutation.stepId) {
480
+ this.insertAppliedStepRow({
481
+ storeName: mutation.definition.name,
482
+ storeId: mutation.id,
483
+ stepId: mutation.stepId,
484
+ result: JSON.stringify(mutation.result)
485
+ });
486
+ }
487
+ const executionIds = this.selectMatchingExecutionIds({
488
+ storeName: mutation.definition.name,
489
+ storeId: mutation.id,
490
+ version,
491
+ changedPaths: mutation.changedPaths
492
+ });
493
+ for (const executionId of executionIds) {
494
+ this.executions.wakeInTransaction(executionId);
495
+ }
496
+ return { status: "committed" };
497
+ });
498
+ }
499
+ async listStores(definition) {
500
+ const rows = this.db.query(`SELECT store_id FROM stores
501
+ WHERE store_name = $storeName
502
+ ORDER BY store_id ASC`).all({ $storeName: definition.name });
503
+ return rows.map((row) => row.store_id);
504
+ }
505
+ async deleteStore(params) {
506
+ runTransaction(this.db, () => {
507
+ this.db.query(`DELETE FROM stores WHERE store_name = $storeName AND store_id = $storeId`).run({
508
+ $storeName: params.definition.name,
509
+ $storeId: params.id
510
+ });
511
+ this.storeWaitersDao.deleteStoreWaiters(params.definition.name, params.id);
512
+ });
513
+ }
514
+ async deleteStoreFrom(params) {
515
+ return runTransaction(this.db, () => {
516
+ if (params.stepId) {
517
+ const applied = this.getAppliedStepRow({
518
+ storeName: params.definition.name,
519
+ storeId: params.id,
520
+ stepId: params.stepId
521
+ });
522
+ if (applied) {
523
+ return JSON.parse(applied.result);
524
+ }
525
+ }
526
+ this.assertSnapshotHasInstanceId(params.snapshot);
527
+ const row = this.getStoreRow(params.definition.name, params.id);
528
+ if (!row) {
529
+ return {
530
+ deleted: false,
531
+ reason: "not-found",
532
+ expectedInstanceId: params.snapshot.instanceId,
533
+ expectedVersion: params.snapshot.version
534
+ };
535
+ }
536
+ if (row.instance_id !== params.snapshot.instanceId || row.version !== params.snapshot.version) {
537
+ return {
538
+ deleted: false,
539
+ reason: "conflict",
540
+ expectedInstanceId: params.snapshot.instanceId,
541
+ actualInstanceId: row.instance_id,
542
+ expectedVersion: params.snapshot.version,
543
+ actualVersion: row.version
544
+ };
545
+ }
546
+ this.storeWaitersDao.deleteStoreWaiters(params.definition.name, params.id);
547
+ this.db.query(`DELETE FROM stores WHERE instance_id = $instanceId`).run({ $instanceId: row.instance_id });
548
+ const result = { deleted: true };
549
+ if (params.stepId) {
550
+ this.insertAppliedStepRow({
551
+ storeName: params.definition.name,
552
+ storeId: params.id,
553
+ stepId: params.stepId,
554
+ result: JSON.stringify(result)
555
+ });
556
+ }
557
+ return result;
558
+ });
559
+ }
560
+ async registerWaiter(waiter) {
561
+ runTransaction(this.db, () => {
562
+ this.storeWaitersDao.upsertWaiter(waiter);
563
+ const row = this.getStoreRow(waiter.storeName, waiter.storeId);
564
+ if (!row || row.instance_id !== waiter.instanceId || row.version > waiter.sinceVersion) {
565
+ this.executions.wakeInTransaction(waiter.executionId);
566
+ }
567
+ });
568
+ }
569
+ setupDb() {
570
+ this.createStoresTable();
571
+ this.db.run(`
572
+ CREATE TABLE IF NOT EXISTS store_applied_steps (
573
+ store_name TEXT NOT NULL,
574
+ store_id TEXT NOT NULL,
575
+ execution_id TEXT NOT NULL,
576
+ step_key TEXT NOT NULL,
577
+ result TEXT NOT NULL,
578
+ PRIMARY KEY (store_name, store_id, execution_id, step_key)
579
+ );
580
+ `);
581
+ }
582
+ createStoresTable() {
583
+ this.db.run(`
584
+ CREATE TABLE IF NOT EXISTS stores (
585
+ instance_id TEXT PRIMARY KEY,
586
+ store_name TEXT NOT NULL,
587
+ store_id TEXT NOT NULL,
588
+ version INTEGER NOT NULL,
589
+ state TEXT NOT NULL,
590
+ UNIQUE (store_name, store_id)
591
+ )
592
+ `);
593
+ }
594
+ getAppliedStepRow(params) {
595
+ return this.db.query(`SELECT result FROM store_applied_steps
596
+ WHERE store_name = $storeName
597
+ AND store_id = $storeId
598
+ AND execution_id = $executionId
599
+ AND step_key = $stepKey`).get({
600
+ $storeName: params.storeName,
601
+ $storeId: params.storeId,
602
+ $executionId: params.stepId.executionId,
603
+ $stepKey: params.stepId.stepKey
604
+ });
605
+ }
606
+ insertAppliedStepRow(params) {
607
+ this.db.query(`INSERT INTO store_applied_steps
608
+ (store_name, store_id, execution_id, step_key, result)
609
+ VALUES
610
+ ($storeName, $storeId, $executionId, $stepKey, $result)`).run({
611
+ $storeName: params.storeName,
612
+ $storeId: params.storeId,
613
+ $executionId: params.stepId.executionId,
614
+ $stepKey: params.stepId.stepKey,
615
+ $result: params.result
616
+ });
617
+ }
618
+ getStoreRow(storeName, storeId) {
619
+ return this.db.query(`SELECT instance_id, state, version FROM stores
620
+ WHERE store_name = $storeName AND store_id = $storeId`).get({
621
+ $storeName: storeName,
622
+ $storeId: storeId
623
+ });
624
+ }
625
+ selectMatchingExecutionIds(params) {
626
+ const executionIds = new Set;
627
+ const rows = this.storeWaitersDao.getStoreWaiters(params.storeName, params.storeId);
628
+ for (const row of rows) {
629
+ if (row.since_version >= params.version)
630
+ continue;
631
+ const readPaths = JSON.parse(row.read_paths);
632
+ if (!storePathsIntersect(readPaths, params.changedPaths))
633
+ continue;
634
+ executionIds.add(row.execution_id);
635
+ }
636
+ return executionIds;
637
+ }
638
+ assertSnapshotHasInstanceId(snapshot) {
639
+ if (!snapshot.instanceId) {
640
+ throw new Error("Store snapshot is missing instanceId; read a fresh snapshot");
641
+ }
642
+ }
643
+ }
644
+ function toSnapshot(row) {
645
+ return {
646
+ state: JSON.parse(row.state),
647
+ instanceId: row.instance_id,
648
+ version: row.version
649
+ };
650
+ }
651
+
652
+ // src/sqlite-runtime.ts
653
+ class SqliteRuntime2 extends Runtime {
654
+ clock;
655
+ executions;
656
+ heap;
657
+ stores;
658
+ constructor(db, options = {}) {
659
+ super();
660
+ this.clock = options.clock ?? (() => Date.now());
661
+ this.executions = new SqliteExecutionClient2(db, {
662
+ ...options,
663
+ clock: this.clock
664
+ });
665
+ this.heap = new SqliteHeapClient2(db);
666
+ this.stores = new SqliteStoreClient2({ db, executions: this.executions });
667
+ }
668
+ }
669
+ export { SqliteExecutionClient2, SqliteHeapClient2, SqliteStoreClient2, SqliteRuntime2 };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
+ export { SqliteRuntime, type SqliteRuntimeOptions } from "./sqlite-runtime";
2
+ export { SqliteExecutionClient, type SqliteExecutionClientOptions, } from "./sqlite-execution";
3
+ export { SqliteEffectClient } from "./sqlite-effect";
1
4
  export { SqliteHeapClient } from "./sqlite-heap";
2
5
  export { SqliteStoreClient } from "./sqlite-store";
3
- export { SqliteSchedulerClient } from "./sqlite-scheduler";
4
- export { SqliteEventLoop } from "./sqlite-event-loop";
5
- export { SqliteTaskQueueClient } from "./sqlite-task-queue";
6
- export { SqliteTimersClient } from "./sqlite-timers";
7
6
  export type { SqliteDriver, SqliteParameters, SqliteParameterValue, SqliteRunResult, SqliteStatement, } from "./sqlite-driver";
8
7
  export type { SqliteDbOpts } from "./sqlite-db";
9
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}