@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
@@ -1,796 +0,0 @@
1
- // src/dao/step-response-dao.ts
2
- class StepResponsesDao {
3
- db;
4
- constructor(db) {
5
- this.db = db;
6
- this.setupDb();
7
- }
8
- setupDb() {
9
- this.db.run(`
10
- CREATE TABLE IF NOT EXISTS step_responses (
11
- execution_id TEXT NOT NULL,
12
- step_key TEXT NOT NULL,
13
- step_attempt INTEGER NOT NULL,
14
- step_done BOOL NOT NULL,
15
- step_response JSONB,
16
- PRIMARY KEY (execution_id, step_key, step_attempt)
17
- );
18
- CREATE INDEX IF NOT EXISTS idx_execution_step_attempt
19
- ON step_responses(execution_id, step_key, step_attempt DESC);
20
- `);
21
- }
22
- getAllSteps() {
23
- const query = this.db.query(`SELECT * FROM step_responses`);
24
- return query.all();
25
- }
26
- deleteAll() {
27
- const query = this.db.query(`DELETE FROM step_responses`);
28
- query.run();
29
- }
30
- getLatestStepResponse(executionId, stepKey) {
31
- const query = this.db.query(`SELECT * FROM step_responses
32
- WHERE execution_id = $executionId
33
- AND step_key = $stepKey
34
- ORDER BY step_attempt DESC LIMIT 1`);
35
- const result = query.get({
36
- $executionId: executionId,
37
- $stepKey: stepKey
38
- });
39
- return result;
40
- }
41
- insertStepResponse(executionId, stepKey, stepAttempt, stepDone, stepResponseJson) {
42
- const query = this.db.query(`INSERT INTO step_responses (execution_id, step_key, step_attempt, step_done, step_response)
43
- VALUES ($executionId, $stepKey, $stepAttempt, $stepDone, $stepResponse)`);
44
- query.run({
45
- $executionId: executionId,
46
- $stepKey: stepKey,
47
- $stepAttempt: stepAttempt,
48
- $stepDone: stepDone,
49
- $stepResponse: stepResponseJson
50
- });
51
- }
52
- }
53
-
54
- // src/sqlite-heap.ts
55
- class SqliteHeapClient {
56
- stepResponsesDao;
57
- constructor(db) {
58
- this.stepResponsesDao = new StepResponsesDao(db);
59
- }
60
- async getAllSteps() {
61
- return this.stepResponsesDao.getAllSteps();
62
- }
63
- async deleteAll() {
64
- this.stepResponsesDao.deleteAll();
65
- }
66
- async readStep(params) {
67
- const result = this.stepResponsesDao.getLatestStepResponse(params.executionId, params.stepKey);
68
- if (!result?.step_response) {
69
- return null;
70
- }
71
- return {
72
- stepResponseJson: result.step_response,
73
- meta: {
74
- attempt: result.step_attempt,
75
- done: Boolean(result.step_done)
76
- }
77
- };
78
- }
79
- async writeStep(params) {
80
- this.stepResponsesDao.insertStepResponse(params.executionId, params.stepKey, params.stepAttempt, params.stepDone, params.stepResponseJson);
81
- }
82
- }
83
- // src/sqlite-store.ts
84
- import { v7 as uuidv7 } from "uuid";
85
- import {
86
- cloneStoreState,
87
- CasStoreClient,
88
- storePathsIntersect,
89
- validateStoreState
90
- } from "@yieldstar/core";
91
- class SqliteStoreClient extends CasStoreClient {
92
- db;
93
- schedulerClient;
94
- writeLock = Promise.resolve();
95
- constructor(params) {
96
- super();
97
- this.db = params.db;
98
- this.schedulerClient = params.schedulerClient;
99
- this.setupDb();
100
- this.enqueueWrite(() => this.drainWakeOutboxBestEffort());
101
- }
102
- enqueueWrite(fn) {
103
- const result = this.writeLock.then(fn, fn);
104
- this.writeLock = result.catch(() => {});
105
- return result;
106
- }
107
- async getOrCreateStore(params) {
108
- const existing = this.getStoreRow(params.definition.name, params.id);
109
- if (existing) {
110
- return {
111
- state: JSON.parse(existing.state),
112
- instanceId: existing.instance_id,
113
- version: existing.version
114
- };
115
- }
116
- if (!params.initial) {
117
- throw new Error(`Store "${params.definition.name}:${params.id}" does not exist`);
118
- }
119
- const initialValue = params.initial;
120
- const initial = typeof initialValue === "function" ? await initialValue() : initialValue;
121
- const state = await validateStoreState(params.definition, initial);
122
- return this.enqueueWrite(async () => {
123
- this.db.run("BEGIN IMMEDIATE");
124
- try {
125
- const existingTx = this.getStoreRow(params.definition.name, params.id);
126
- if (existingTx) {
127
- this.db.run("COMMIT");
128
- return {
129
- state: JSON.parse(existingTx.state),
130
- instanceId: existingTx.instance_id,
131
- version: existingTx.version
132
- };
133
- }
134
- const instanceId = uuidv7();
135
- this.db.query(`INSERT INTO stores (instance_id, store_name, store_id, version, state)
136
- VALUES ($instanceId, $storeName, $storeId, 0, $state)`).run({
137
- $instanceId: instanceId,
138
- $storeName: params.definition.name,
139
- $storeId: params.id,
140
- $state: JSON.stringify(state)
141
- });
142
- this.db.run("COMMIT");
143
- return {
144
- state: cloneStoreState(state),
145
- instanceId,
146
- version: 0
147
- };
148
- } catch (err) {
149
- this.db.run("ROLLBACK");
150
- throw err;
151
- }
152
- throw new Error("Store creation transaction returned unexpectedly");
153
- });
154
- }
155
- async getStore(params) {
156
- const row = this.getStoreRow(params.definition.name, params.id);
157
- if (!row) {
158
- throw new Error(`Store "${params.definition.name}:${params.id}" does not exist`);
159
- }
160
- return {
161
- state: JSON.parse(row.state),
162
- instanceId: row.instance_id,
163
- version: row.version
164
- };
165
- }
166
- async getAppliedStoreStep(params) {
167
- const row = this.getAppliedStepRow({
168
- storeName: params.definition.name,
169
- storeId: params.id,
170
- stepId: params.stepId
171
- });
172
- return row ? { result: JSON.parse(row.result) } : undefined;
173
- }
174
- commitStoreMutation(mutation) {
175
- return this.enqueueWrite(async () => {
176
- this.db.run("BEGIN IMMEDIATE");
177
- try {
178
- if (mutation.stepId) {
179
- const applied = this.getAppliedStepRow({
180
- storeName: mutation.definition.name,
181
- storeId: mutation.id,
182
- stepId: mutation.stepId
183
- });
184
- if (applied) {
185
- this.db.run("COMMIT");
186
- return {
187
- status: "already-applied",
188
- result: JSON.parse(applied.result)
189
- };
190
- }
191
- }
192
- const row = this.getStoreRow(mutation.definition.name, mutation.id);
193
- if (!row) {
194
- throw new Error(`Store "${mutation.definition.name}:${mutation.id}" does not exist`);
195
- }
196
- if (row.instance_id !== mutation.expected.instanceId || row.version !== mutation.expected.version) {
197
- this.db.run("COMMIT");
198
- return {
199
- status: "conflict",
200
- snapshot: {
201
- state: JSON.parse(row.state),
202
- instanceId: row.instance_id,
203
- version: row.version
204
- }
205
- };
206
- }
207
- const version = mutation.expected.version + 1;
208
- const changed = this.db.query(`UPDATE stores
209
- SET version = $version, state = $state
210
- WHERE instance_id = $instanceId AND version = $expectedVersion`).run({
211
- $instanceId: mutation.expected.instanceId,
212
- $expectedVersion: mutation.expected.version,
213
- $version: version,
214
- $state: JSON.stringify(mutation.nextState)
215
- });
216
- if (Number(changed.changes) !== 1) {
217
- throw new Error("Store changed during its mutation transaction");
218
- }
219
- if (mutation.stepId) {
220
- this.insertAppliedStepRow({
221
- storeName: mutation.definition.name,
222
- storeId: mutation.id,
223
- stepId: mutation.stepId,
224
- result: JSON.stringify(mutation.result)
225
- });
226
- }
227
- const waiters = this.selectMatchingWaiters({
228
- storeName: mutation.definition.name,
229
- storeId: mutation.id,
230
- version,
231
- changedPaths: mutation.changedPaths
232
- });
233
- for (const waiter of waiters) {
234
- this.insertWakeOutboxRow({
235
- storeName: mutation.definition.name,
236
- storeId: mutation.id,
237
- executionId: waiter.executionId,
238
- stepKey: waiter.stepKey
239
- });
240
- }
241
- this.db.run("COMMIT");
242
- } catch (error) {
243
- this.db.run("ROLLBACK");
244
- throw error;
245
- }
246
- await this.drainWakeOutboxBestEffort();
247
- return { status: "committed" };
248
- });
249
- }
250
- async listStores(definition) {
251
- const rows = this.db.query(`SELECT store_id FROM stores
252
- WHERE store_name = $storeName
253
- ORDER BY store_id ASC`).all({ $storeName: definition.name });
254
- return rows.map((row) => row.store_id);
255
- }
256
- async deleteStore(params) {
257
- return this.enqueueWrite(async () => {
258
- const bindings = {
259
- $storeName: params.definition.name,
260
- $storeId: params.id
261
- };
262
- this.db.run("BEGIN IMMEDIATE");
263
- try {
264
- this.db.query(`DELETE FROM stores WHERE store_name = $storeName AND store_id = $storeId`).run(bindings);
265
- this.db.query(`DELETE FROM store_waiters WHERE store_name = $storeName AND store_id = $storeId`).run(bindings);
266
- this.db.query(`DELETE FROM store_wake_outbox WHERE store_name = $storeName AND store_id = $storeId`).run(bindings);
267
- this.db.run("COMMIT");
268
- } catch (err) {
269
- this.db.run("ROLLBACK");
270
- throw err;
271
- }
272
- });
273
- }
274
- async deleteStoreFrom(params) {
275
- return this.enqueueWrite(async () => {
276
- const bindings = {
277
- $storeName: params.definition.name,
278
- $storeId: params.id
279
- };
280
- this.db.run("BEGIN IMMEDIATE");
281
- try {
282
- if (params.stepId) {
283
- const applied = this.getAppliedStepRow({
284
- storeName: params.definition.name,
285
- storeId: params.id,
286
- stepId: params.stepId
287
- });
288
- if (applied) {
289
- this.db.run("COMMIT");
290
- return JSON.parse(applied.result);
291
- }
292
- }
293
- this.assertSnapshotHasInstanceId(params.snapshot);
294
- const row = this.getStoreRow(params.definition.name, params.id);
295
- if (!row) {
296
- this.db.run("COMMIT");
297
- return {
298
- deleted: false,
299
- reason: "not-found",
300
- expectedInstanceId: params.snapshot.instanceId,
301
- expectedVersion: params.snapshot.version
302
- };
303
- }
304
- if (row.instance_id !== params.snapshot.instanceId || row.version !== params.snapshot.version) {
305
- this.db.run("COMMIT");
306
- return {
307
- deleted: false,
308
- reason: "conflict",
309
- expectedInstanceId: params.snapshot.instanceId,
310
- actualInstanceId: row.instance_id,
311
- expectedVersion: params.snapshot.version,
312
- actualVersion: row.version
313
- };
314
- }
315
- this.db.query(`DELETE FROM store_waiters WHERE store_name = $storeName AND store_id = $storeId`).run(bindings);
316
- this.db.query(`DELETE FROM store_wake_outbox WHERE store_name = $storeName AND store_id = $storeId`).run(bindings);
317
- this.db.query(`DELETE FROM stores WHERE instance_id = $instanceId`).run({ $instanceId: row.instance_id });
318
- const result = { deleted: true };
319
- if (params.stepId) {
320
- this.insertAppliedStepRow({
321
- storeName: params.definition.name,
322
- storeId: params.id,
323
- stepId: params.stepId,
324
- result: JSON.stringify(result)
325
- });
326
- }
327
- this.db.run("COMMIT");
328
- return result;
329
- } catch (err) {
330
- this.db.run("ROLLBACK");
331
- throw err;
332
- }
333
- });
334
- }
335
- async registerWaiter(waiter) {
336
- return this.enqueueWrite(async () => {
337
- let versionAdvanced = false;
338
- this.db.run("BEGIN IMMEDIATE");
339
- try {
340
- this.db.query(`INSERT INTO store_waiters
341
- (workflow_id, execution_id, step_key, event, store_name, store_id, since_version, read_paths)
342
- VALUES
343
- ($workflowId, $executionId, $stepKey, $event, $storeName, $storeId, $sinceVersion, $readPaths)
344
- ON CONFLICT(store_name, store_id, execution_id, step_key)
345
- DO UPDATE SET
346
- event = excluded.event,
347
- since_version = excluded.since_version,
348
- read_paths = excluded.read_paths`).run({
349
- $workflowId: waiter.workflowId,
350
- $executionId: waiter.executionId,
351
- $stepKey: waiter.stepKey,
352
- $event: JSON.stringify(serializeEvent(waiter.event)),
353
- $storeName: waiter.storeName,
354
- $storeId: waiter.storeId,
355
- $sinceVersion: waiter.sinceVersion,
356
- $readPaths: JSON.stringify(waiter.readPaths)
357
- });
358
- const row = this.getStoreRow(waiter.storeName, waiter.storeId);
359
- if (!row || row.instance_id !== waiter.instanceId || row.version > waiter.sinceVersion) {
360
- versionAdvanced = true;
361
- this.insertWakeOutboxRow({
362
- storeName: waiter.storeName,
363
- storeId: waiter.storeId,
364
- executionId: waiter.executionId,
365
- stepKey: waiter.stepKey
366
- });
367
- }
368
- this.db.run("COMMIT");
369
- } catch (err) {
370
- this.db.run("ROLLBACK");
371
- throw err;
372
- }
373
- if (versionAdvanced) {
374
- await this.drainWakeOutboxBestEffort();
375
- }
376
- });
377
- }
378
- setupDb() {
379
- this.createStoresTable();
380
- this.db.run(`
381
- CREATE TABLE IF NOT EXISTS store_waiters (
382
- workflow_id TEXT NOT NULL,
383
- execution_id TEXT NOT NULL,
384
- step_key TEXT NOT NULL,
385
- event TEXT NOT NULL,
386
- store_name TEXT NOT NULL,
387
- store_id TEXT NOT NULL,
388
- since_version INTEGER NOT NULL,
389
- read_paths TEXT NOT NULL,
390
- PRIMARY KEY (store_name, store_id, execution_id, step_key)
391
- );
392
-
393
- CREATE TABLE IF NOT EXISTS store_applied_steps (
394
- store_name TEXT NOT NULL,
395
- store_id TEXT NOT NULL,
396
- execution_id TEXT NOT NULL,
397
- step_key TEXT NOT NULL,
398
- result TEXT NOT NULL,
399
- PRIMARY KEY (store_name, store_id, execution_id, step_key)
400
- );
401
-
402
- CREATE TABLE IF NOT EXISTS store_wake_outbox (
403
- store_name TEXT NOT NULL,
404
- store_id TEXT NOT NULL,
405
- execution_id TEXT NOT NULL,
406
- step_key TEXT NOT NULL,
407
- PRIMARY KEY (store_name, store_id, execution_id, step_key)
408
- );
409
- `);
410
- }
411
- createStoresTable() {
412
- this.db.run(`
413
- CREATE TABLE IF NOT EXISTS stores (
414
- instance_id TEXT PRIMARY KEY,
415
- store_name TEXT NOT NULL,
416
- store_id TEXT NOT NULL,
417
- version INTEGER NOT NULL,
418
- state TEXT NOT NULL,
419
- UNIQUE (store_name, store_id)
420
- )
421
- `);
422
- }
423
- getAppliedStepRow(params) {
424
- return this.db.query(`SELECT result FROM store_applied_steps
425
- WHERE store_name = $storeName
426
- AND store_id = $storeId
427
- AND execution_id = $executionId
428
- AND step_key = $stepKey`).get({
429
- $storeName: params.storeName,
430
- $storeId: params.storeId,
431
- $executionId: params.stepId.executionId,
432
- $stepKey: params.stepId.stepKey
433
- });
434
- }
435
- insertAppliedStepRow(params) {
436
- this.db.query(`INSERT INTO store_applied_steps
437
- (store_name, store_id, execution_id, step_key, result)
438
- VALUES
439
- ($storeName, $storeId, $executionId, $stepKey, $result)`).run({
440
- $storeName: params.storeName,
441
- $storeId: params.storeId,
442
- $executionId: params.stepId.executionId,
443
- $stepKey: params.stepId.stepKey,
444
- $result: params.result
445
- });
446
- }
447
- getStoreRow(storeName, storeId) {
448
- return this.db.query(`SELECT instance_id, state, version FROM stores
449
- WHERE store_name = $storeName AND store_id = $storeId`).get({
450
- $storeName: storeName,
451
- $storeId: storeId
452
- });
453
- }
454
- selectMatchingWaiters(params) {
455
- const waiters = [];
456
- const rows = this.db.query(`SELECT * FROM store_waiters
457
- WHERE store_name = $storeName AND store_id = $storeId`).all({
458
- $storeName: params.storeName,
459
- $storeId: params.storeId
460
- });
461
- for (const row of rows) {
462
- if (row.since_version >= params.version)
463
- continue;
464
- const readPaths = JSON.parse(row.read_paths);
465
- if (!storePathsIntersect(readPaths, params.changedPaths))
466
- continue;
467
- waiters.push({
468
- executionId: row.execution_id,
469
- stepKey: row.step_key,
470
- event: deserializeEvent(JSON.parse(row.event))
471
- });
472
- }
473
- return waiters;
474
- }
475
- deleteWaiterRow(params) {
476
- this.db.query(`DELETE FROM store_waiters
477
- WHERE store_name = $storeName
478
- AND store_id = $storeId
479
- AND execution_id = $executionId
480
- AND step_key = $stepKey
481
- AND ($sinceVersion IS NULL OR since_version = $sinceVersion)`).run({
482
- $storeName: params.storeName,
483
- $storeId: params.storeId,
484
- $executionId: params.executionId,
485
- $stepKey: params.stepKey,
486
- $sinceVersion: params.sinceVersion ?? null
487
- });
488
- }
489
- insertWakeOutboxRow(params) {
490
- this.db.query(`INSERT OR IGNORE INTO store_wake_outbox
491
- (store_name, store_id, execution_id, step_key)
492
- VALUES ($storeName, $storeId, $executionId, $stepKey)`).run({
493
- $storeName: params.storeName,
494
- $storeId: params.storeId,
495
- $executionId: params.executionId,
496
- $stepKey: params.stepKey
497
- });
498
- }
499
- async drainWakeOutbox() {
500
- const rows = this.db.query(`SELECT
501
- o.store_name,
502
- o.store_id,
503
- o.execution_id,
504
- o.step_key,
505
- w.event,
506
- w.since_version
507
- FROM store_wake_outbox o
508
- LEFT JOIN store_waiters w
509
- ON w.store_name = o.store_name
510
- AND w.store_id = o.store_id
511
- AND w.execution_id = o.execution_id
512
- AND w.step_key = o.step_key
513
- ORDER BY o.store_name, o.store_id, o.execution_id, o.step_key`).all();
514
- for (const row of rows) {
515
- try {
516
- if (row.event !== null) {
517
- await this.schedulerClient.requestWakeUp(deserializeEvent(JSON.parse(row.event)));
518
- this.deleteWaiterRow({
519
- storeName: row.store_name,
520
- storeId: row.store_id,
521
- executionId: row.execution_id,
522
- stepKey: row.step_key,
523
- sinceVersion: row.since_version ?? undefined
524
- });
525
- }
526
- this.db.query(`DELETE FROM store_wake_outbox
527
- WHERE store_name = $storeName
528
- AND store_id = $storeId
529
- AND execution_id = $executionId
530
- AND step_key = $stepKey`).run({
531
- $storeName: row.store_name,
532
- $storeId: row.store_id,
533
- $executionId: row.execution_id,
534
- $stepKey: row.step_key
535
- });
536
- } catch (error) {
537
- console.error("Failed to deliver pending store wake", error);
538
- }
539
- }
540
- }
541
- async drainWakeOutboxBestEffort() {
542
- try {
543
- await this.drainWakeOutbox();
544
- } catch (error) {
545
- console.error("Failed to drain pending store wakes", error);
546
- }
547
- }
548
- assertSnapshotHasInstanceId(snapshot) {
549
- if (!snapshot.instanceId) {
550
- throw new Error("Store snapshot is missing instanceId; read a fresh snapshot");
551
- }
552
- }
553
- }
554
- function serializeEvent(event) {
555
- return {
556
- workflowId: event.workflowId,
557
- executionId: event.executionId,
558
- params: event.params,
559
- context: event.context instanceof Map ? [...event.context.entries()] : event.context ? Object.entries(event.context) : []
560
- };
561
- }
562
- function deserializeEvent(event) {
563
- return {
564
- workflowId: event.workflowId,
565
- executionId: event.executionId,
566
- params: event.params,
567
- context: new Map(event.context ?? [])
568
- };
569
- }
570
- // src/sqlite-scheduler.ts
571
- class SqliteSchedulerClient {
572
- taskQueue;
573
- timersClient;
574
- constructor(params) {
575
- this.taskQueue = params.taskQueueClient;
576
- this.timersClient = params.timersClient;
577
- }
578
- async requestWakeUp(event, resumeIn) {
579
- if (resumeIn) {
580
- this.timersClient.createTimer(event, resumeIn);
581
- } else {
582
- this.taskQueue.add(event);
583
- }
584
- }
585
- }
586
- // src/dao/task-queue-dao.ts
587
- class TaskQueueDao {
588
- db;
589
- constructor(db) {
590
- this.db = db;
591
- }
592
- setupDb() {
593
- this.db.run(`
594
- CREATE TABLE IF NOT EXISTS task_queue (
595
- task_id INTEGER PRIMARY KEY AUTOINCREMENT,
596
- workflow_id TEXT NOT NULL,
597
- execution_id TEXT NOT NULL,
598
- params TEXT,
599
- context TEXT,
600
- visible_from INTEGER DEFAULT (strftime('%s', 'now'))
601
- )
602
- `);
603
- }
604
- insertTask(event) {
605
- const query = this.db.query(`INSERT INTO task_queue (workflow_id, execution_id, params, context)
606
- VALUES ($workflowId, $executionId, $params, $context)`);
607
- query.run({
608
- $workflowId: event.workflowId,
609
- $executionId: event.executionId,
610
- $params: event.params ? JSON.stringify(event.params) : null,
611
- $context: event.context ? JSON.stringify(Array.from(event.context.entries())) : null
612
- });
613
- }
614
- getNextTask() {
615
- const query = this.db.query(`SELECT * FROM task_queue WHERE visible_from < strftime('%s', 'now') ORDER BY task_id LIMIT 1`);
616
- return query.get();
617
- }
618
- updateTaskVisibility(taskId, visibleFrom) {
619
- const query = this.db.query("UPDATE task_queue SET visible_from = $visibleFrom WHERE task_id = $taskId");
620
- query.run({ $taskId: taskId, $visibleFrom: visibleFrom });
621
- }
622
- deleteTaskById(id) {
623
- const query = this.db.query(`DELETE FROM task_queue WHERE task_id = $taskId`);
624
- query.run({ $taskId: id });
625
- }
626
- getTaskCount() {
627
- const query = this.db.query(`SELECT COUNT(*) as count FROM task_queue WHERE visible_from < strftime('%s', 'now')`);
628
- return query.get().count;
629
- }
630
- }
631
-
632
- // src/sqlite-task-queue.ts
633
- var VISIBILITY_WINDOW = 300000;
634
-
635
- class SqliteTaskQueue {
636
- taskQueueDao;
637
- constructor(db) {
638
- this.taskQueueDao = new TaskQueueDao(db);
639
- this.taskQueueDao.setupDb();
640
- }
641
- add(event) {
642
- this.taskQueueDao.insertTask(event);
643
- }
644
- process() {
645
- const row = this.taskQueueDao.getNextTask();
646
- if (!row)
647
- return;
648
- const now = Date.now();
649
- const visibilityTimeout = now + VISIBILITY_WINDOW;
650
- this.taskQueueDao.updateTaskVisibility(row.task_id, visibilityTimeout);
651
- return {
652
- taskId: row.task_id,
653
- visibilityTimeout,
654
- event: {
655
- workflowId: row.workflow_id,
656
- executionId: row.execution_id,
657
- params: row.params ? JSON.parse(row.params) : undefined,
658
- context: row.context ? new Map(JSON.parse(row.context)) : new Map
659
- }
660
- };
661
- }
662
- remove(taskId) {
663
- this.taskQueueDao.deleteTaskById(taskId);
664
- }
665
- makeVisible(taskId) {
666
- this.taskQueueDao.updateTaskVisibility(taskId, 0);
667
- }
668
- get isEmpty() {
669
- return this.taskQueueDao.getTaskCount() === 0;
670
- }
671
- }
672
-
673
- class SqliteTaskQueueClient {
674
- taskQueueDao;
675
- constructor(db) {
676
- this.taskQueueDao = new TaskQueueDao(db);
677
- }
678
- add(event) {
679
- this.taskQueueDao.insertTask(event);
680
- }
681
- }
682
-
683
- // src/dao/timers-dao.ts
684
- class TimersDao {
685
- db;
686
- constructor(db) {
687
- this.db = db;
688
- this.setupDb();
689
- }
690
- setupDb() {
691
- this.db.run(`
692
- CREATE TABLE IF NOT EXISTS scheduled_tasks (
693
- id INTEGER PRIMARY KEY AUTOINCREMENT,
694
- delay INTEGER NOT NULL,
695
- workflow_id TEXT NOT NULL,
696
- execution_id TEXT NOT NULL,
697
- created_at INTEGER NOT NULL,
698
- params TEXT,
699
- context TEXT
700
- );
701
- `);
702
- }
703
- getExpiredTimers() {
704
- const query = this.db.query(`SELECT id, delay, workflow_id, execution_id, created_at, params, context
705
- FROM scheduled_tasks
706
- WHERE (created_at + delay) < $currentTime`);
707
- return query.all({ $currentTime: Date.now() });
708
- }
709
- getTaskCount() {
710
- const query = this.db.query(`SELECT COUNT(*) as count
711
- FROM scheduled_tasks
712
- WHERE (created_at + delay) < $currentTime`);
713
- const count = query.get({ $currentTime: Date.now() });
714
- return count.count;
715
- }
716
- insertTimer(event, delay) {
717
- const query = this.db.query(`INSERT INTO scheduled_tasks (delay, workflow_id, execution_id, created_at, params, context)
718
- VALUES ($delay, $workflowId, $executionId, $createdAt, $params, $context)`);
719
- query.run({
720
- $delay: delay,
721
- $workflowId: event.workflowId,
722
- $executionId: event.executionId,
723
- $createdAt: Date.now(),
724
- $params: event.params ? JSON.stringify(event.params) : null,
725
- $context: JSON.stringify(Array.from(event.context.entries()))
726
- });
727
- }
728
- deleteTimerById(id) {
729
- const query = this.db.query(`DELETE FROM scheduled_tasks WHERE id = $id`);
730
- query.run({ $id: id });
731
- }
732
- }
733
-
734
- // src/sqlite-timers.ts
735
- class SqliteTimers {
736
- timersDao;
737
- taskQueue;
738
- constructor(params) {
739
- this.timersDao = new TimersDao(params.db);
740
- this.taskQueue = params.taskQueue;
741
- }
742
- processTimers() {
743
- const timers = this.timersDao.getExpiredTimers();
744
- for (const timer of timers) {
745
- this.timersDao.deleteTimerById(timer.id);
746
- this.taskQueue.add({
747
- workflowId: timer.workflow_id,
748
- executionId: timer.execution_id,
749
- params: timer.params ? JSON.parse(timer.params) : undefined,
750
- context: timer.context ? new Map(JSON.parse(timer.context)) : new Map
751
- });
752
- }
753
- }
754
- }
755
-
756
- class SqliteTimersClient {
757
- timersDao;
758
- constructor(db) {
759
- this.timersDao = new TimersDao(db);
760
- }
761
- createTimer(event, delay) {
762
- this.timersDao.insertTimer(event, delay);
763
- }
764
- }
765
-
766
- // src/sqlite-event-loop.ts
767
- class SqliteEventLoop {
768
- taskQueue;
769
- timers;
770
- isRunning = false;
771
- constructor(db) {
772
- this.taskQueue = new SqliteTaskQueue(db);
773
- this.timers = new SqliteTimers({ db, taskQueue: this.taskQueue });
774
- }
775
- start(params) {
776
- this.isRunning = true;
777
- this.loop(params.onNewEvent, params.logger);
778
- }
779
- stop() {
780
- this.isRunning = false;
781
- }
782
- async loop(processEvent, logger) {
783
- if (!this.isRunning)
784
- return;
785
- while (!this.taskQueue.isEmpty) {
786
- const task = this.taskQueue.process();
787
- if (task) {
788
- await processEvent(task.event, logger);
789
- this.taskQueue.remove(task.taskId);
790
- }
791
- }
792
- this.timers.processTimers();
793
- setTimeout(() => this.loop(processEvent, logger), 10);
794
- }
795
- }
796
- export { SqliteHeapClient, SqliteStoreClient, SqliteSchedulerClient, SqliteTaskQueueClient, SqliteTimersClient, SqliteEventLoop };