@yieldstar/sqlite-runtime 0.5.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/bun.d.ts +13 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +43 -0
- package/dist/dao/step-response-dao.d.ts +19 -0
- package/dist/dao/step-response-dao.d.ts.map +1 -0
- package/dist/dao/task-queue-dao.d.ts +21 -0
- package/dist/dao/task-queue-dao.d.ts.map +1 -0
- package/dist/dao/timers-dao.d.ts +22 -0
- package/dist/dao/timers-dao.d.ts.map +1 -0
- package/dist/index-4vq70w0r.js +796 -0
- package/dist/index-h7bm9xg4.js +26 -0
- package/dist/index-s8wqgfwt.js +19 -0
- package/dist/index-x891hxwg.js +796 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/node-driver.d.ts +10 -0
- package/dist/node-driver.d.ts.map +1 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +49 -0
- package/dist/sqlite-db.d.ts +29 -0
- package/dist/sqlite-db.d.ts.map +1 -0
- package/dist/sqlite-driver.d.ts +21 -0
- package/dist/sqlite-driver.d.ts.map +1 -0
- package/dist/sqlite-event-loop.d.ts +18 -0
- package/dist/sqlite-event-loop.d.ts.map +1 -0
- package/dist/sqlite-heap.d.ts +26 -0
- package/dist/sqlite-heap.d.ts.map +1 -0
- package/dist/sqlite-scheduler.d.ts +13 -0
- package/dist/sqlite-scheduler.d.ts.map +1 -0
- package/dist/sqlite-store.d.ts +54 -0
- package/dist/sqlite-store.d.ts.map +1 -0
- package/dist/sqlite-store.test.d.ts +2 -0
- package/dist/sqlite-store.test.d.ts.map +1 -0
- package/dist/sqlite-task-queue.d.ts +26 -0
- package/dist/sqlite-task-queue.d.ts.map +1 -0
- package/dist/sqlite-timers.d.ts +18 -0
- package/dist/sqlite-timers.d.ts.map +1 -0
- package/package.json +43 -0
- package/src/bun.ts +32 -0
- package/src/dao/step-response-dao.ts +80 -0
- package/src/dao/task-queue-dao.ts +79 -0
- package/src/dao/timers-dao.ts +81 -0
- package/src/index.ts +14 -0
- package/src/node.ts +47 -0
- package/src/sqlite-db.ts +70 -0
- package/src/sqlite-driver.ts +30 -0
- package/src/sqlite-event-loop.ts +41 -0
- package/src/sqlite-heap.ts +54 -0
- package/src/sqlite-scheduler.ts +24 -0
- package/src/sqlite-store.ts +755 -0
- package/src/sqlite-task-queue.ts +64 -0
- package/src/sqlite-timers.ts +40 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { WorkflowEvent } from "@yieldstar/core";
|
|
2
|
+
import type { SqliteDriver } from "../sqlite-driver";
|
|
3
|
+
|
|
4
|
+
class TimerRow {
|
|
5
|
+
id!: string;
|
|
6
|
+
delay!: number;
|
|
7
|
+
workflow_id!: string;
|
|
8
|
+
execution_id!: string;
|
|
9
|
+
created_at!: number;
|
|
10
|
+
params?: string;
|
|
11
|
+
context?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class CountRow {
|
|
15
|
+
count!: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class TimersDao {
|
|
19
|
+
private db: SqliteDriver;
|
|
20
|
+
|
|
21
|
+
constructor(db: SqliteDriver) {
|
|
22
|
+
this.db = db;
|
|
23
|
+
this.setupDb();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private setupDb() {
|
|
27
|
+
this.db.run(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS scheduled_tasks (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
+
delay INTEGER NOT NULL,
|
|
31
|
+
workflow_id TEXT NOT NULL,
|
|
32
|
+
execution_id TEXT NOT NULL,
|
|
33
|
+
created_at INTEGER NOT NULL,
|
|
34
|
+
params TEXT,
|
|
35
|
+
context TEXT
|
|
36
|
+
);
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getExpiredTimers(): TimerRow[] {
|
|
41
|
+
const query = this.db.query<TimerRow>(
|
|
42
|
+
`SELECT id, delay, workflow_id, execution_id, created_at, params, context
|
|
43
|
+
FROM scheduled_tasks
|
|
44
|
+
WHERE (created_at + delay) < $currentTime`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return query.all({ $currentTime: Date.now() });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getTaskCount() {
|
|
51
|
+
const query = this.db.query<CountRow>(
|
|
52
|
+
`SELECT COUNT(*) as count
|
|
53
|
+
FROM scheduled_tasks
|
|
54
|
+
WHERE (created_at + delay) < $currentTime`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const count = query.get({ $currentTime: Date.now() })!;
|
|
58
|
+
return count.count;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
insertTimer(event: WorkflowEvent, delay: number) {
|
|
62
|
+
const query = this.db.query(
|
|
63
|
+
`INSERT INTO scheduled_tasks (delay, workflow_id, execution_id, created_at, params, context)
|
|
64
|
+
VALUES ($delay, $workflowId, $executionId, $createdAt, $params, $context)`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
query.run({
|
|
68
|
+
$delay: delay,
|
|
69
|
+
$workflowId: event.workflowId,
|
|
70
|
+
$executionId: event.executionId,
|
|
71
|
+
$createdAt: Date.now(),
|
|
72
|
+
$params: event.params ? JSON.stringify(event.params) : null,
|
|
73
|
+
$context: JSON.stringify(Array.from(event.context.entries())),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deleteTimerById(id: string) {
|
|
78
|
+
const query = this.db.query(`DELETE FROM scheduled_tasks WHERE id = $id`);
|
|
79
|
+
query.run({ $id: id });
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { SqliteHeapClient } from "./sqlite-heap";
|
|
2
|
+
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
|
+
export type {
|
|
8
|
+
SqliteDriver,
|
|
9
|
+
SqliteParameters,
|
|
10
|
+
SqliteParameterValue,
|
|
11
|
+
SqliteRunResult,
|
|
12
|
+
SqliteStatement,
|
|
13
|
+
} from "./sqlite-driver";
|
|
14
|
+
export type { SqliteDbOpts } from "./sqlite-db";
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DatabaseSync, type SQLInputValue } from "node:sqlite";
|
|
2
|
+
import {
|
|
3
|
+
createSqliteDriver,
|
|
4
|
+
wrapSqliteStatement,
|
|
5
|
+
type SqliteDbOpts,
|
|
6
|
+
} from "./sqlite-db";
|
|
7
|
+
import type {
|
|
8
|
+
SqliteDriver,
|
|
9
|
+
SqliteParameters,
|
|
10
|
+
SqliteStatement,
|
|
11
|
+
} from "./sqlite-driver";
|
|
12
|
+
|
|
13
|
+
export * from "./index";
|
|
14
|
+
|
|
15
|
+
export class NodeSqliteDriver implements SqliteDriver {
|
|
16
|
+
constructor(private readonly database: DatabaseSync) {}
|
|
17
|
+
|
|
18
|
+
run(sql: string) {
|
|
19
|
+
this.database.exec(sql);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
query<Row>(sql: string): SqliteStatement<Row> {
|
|
23
|
+
return wrapSqliteStatement(this.database.prepare(sql), normalizeParameters);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
close() {
|
|
27
|
+
this.database.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeParameters(
|
|
32
|
+
parameters: SqliteParameters
|
|
33
|
+
): Record<string, SQLInputValue> {
|
|
34
|
+
return Object.fromEntries(
|
|
35
|
+
Object.entries(parameters).map(([name, value]) => [
|
|
36
|
+
name,
|
|
37
|
+
typeof value === "boolean" ? Number(value) : value,
|
|
38
|
+
])
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createSqliteDb(opts?: SqliteDbOpts) {
|
|
43
|
+
return createSqliteDriver(
|
|
44
|
+
(path) => new NodeSqliteDriver(new DatabaseSync(path)),
|
|
45
|
+
opts
|
|
46
|
+
);
|
|
47
|
+
}
|
package/src/sqlite-db.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
SqliteDriver,
|
|
5
|
+
SqliteParameters,
|
|
6
|
+
SqliteRunResult,
|
|
7
|
+
SqliteStatement,
|
|
8
|
+
} from "./sqlite-driver";
|
|
9
|
+
|
|
10
|
+
export type SqliteDbOpts = {
|
|
11
|
+
/**
|
|
12
|
+
* Path to the SQLite database file
|
|
13
|
+
* @default "./.db/yieldstar.sqlite"
|
|
14
|
+
*/
|
|
15
|
+
path?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Enable Write-Ahead Logging (WAL) mode.
|
|
18
|
+
* Recommended for better performance and concurrency.
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
wal?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const defaultOpts: Required<SqliteDbOpts> = {
|
|
25
|
+
path: "./.db/yieldstar.sqlite",
|
|
26
|
+
wal: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function createSqliteDriver<Driver extends SqliteDriver>(
|
|
30
|
+
create: (path: string) => Driver,
|
|
31
|
+
opts?: SqliteDbOpts
|
|
32
|
+
) {
|
|
33
|
+
const mergedOpts = { ...defaultOpts, ...opts };
|
|
34
|
+
mkdirSync(dirname(mergedOpts.path), { recursive: true });
|
|
35
|
+
|
|
36
|
+
const db = create(mergedOpts.path);
|
|
37
|
+
if (mergedOpts.wal) {
|
|
38
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
39
|
+
}
|
|
40
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
41
|
+
return db;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The native prepared-statement shape connectors adapt to SqliteStatement. */
|
|
45
|
+
type NativeStatement = {
|
|
46
|
+
get(parameters?: any): unknown;
|
|
47
|
+
all(parameters?: any): unknown[];
|
|
48
|
+
run(parameters?: any): SqliteRunResult;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adapt a native prepared statement to the SqliteStatement contract: bind
|
|
53
|
+
* parameters are only passed when provided, with `bind` mapping them to the
|
|
54
|
+
* native driver's binding shape.
|
|
55
|
+
*/
|
|
56
|
+
export function wrapSqliteStatement<Row>(
|
|
57
|
+
statement: NativeStatement,
|
|
58
|
+
bind: (parameters: SqliteParameters) => any = (parameters) => parameters
|
|
59
|
+
): SqliteStatement<Row> {
|
|
60
|
+
return {
|
|
61
|
+
get: (parameters) =>
|
|
62
|
+
(parameters ? statement.get(bind(parameters)) : statement.get()) as
|
|
63
|
+
| Row
|
|
64
|
+
| undefined,
|
|
65
|
+
all: (parameters) =>
|
|
66
|
+
(parameters ? statement.all(bind(parameters)) : statement.all()) as Row[],
|
|
67
|
+
run: (parameters) =>
|
|
68
|
+
parameters ? statement.run(bind(parameters)) : statement.run(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type SqliteParameterValue =
|
|
2
|
+
| string
|
|
3
|
+
| number
|
|
4
|
+
| bigint
|
|
5
|
+
| boolean
|
|
6
|
+
| Uint8Array
|
|
7
|
+
| null;
|
|
8
|
+
|
|
9
|
+
export type SqliteParameters = Record<`$${string}`, SqliteParameterValue>;
|
|
10
|
+
|
|
11
|
+
export type SqliteRunResult = {
|
|
12
|
+
changes: number | bigint;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callers must supply exactly the named parameters used by the SQL, including
|
|
17
|
+
* each `$` prefix. Connectors do not normalize additional names because native
|
|
18
|
+
* drivers differ: some ignore them while others throw.
|
|
19
|
+
*/
|
|
20
|
+
export interface SqliteStatement<Row> {
|
|
21
|
+
get(parameters?: SqliteParameters): Row | undefined;
|
|
22
|
+
all(parameters?: SqliteParameters): Row[];
|
|
23
|
+
run(parameters?: SqliteParameters): SqliteRunResult;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SqliteDriver {
|
|
27
|
+
run(sql: string): void;
|
|
28
|
+
query<Row>(sql: string): SqliteStatement<Row>;
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SqliteTaskQueue } from "./sqlite-task-queue";
|
|
2
|
+
import { SqliteTimers } from "./sqlite-timers";
|
|
3
|
+
import type { EventProcessor } from "@yieldstar/core";
|
|
4
|
+
import type { Logger } from "pino";
|
|
5
|
+
import type { SqliteDriver } from "./sqlite-driver";
|
|
6
|
+
|
|
7
|
+
export class SqliteEventLoop {
|
|
8
|
+
taskQueue: SqliteTaskQueue;
|
|
9
|
+
timers: SqliteTimers;
|
|
10
|
+
private isRunning: boolean = false;
|
|
11
|
+
|
|
12
|
+
constructor(db: SqliteDriver) {
|
|
13
|
+
this.taskQueue = new SqliteTaskQueue(db);
|
|
14
|
+
this.timers = new SqliteTimers({ db, taskQueue: this.taskQueue });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
start(params: { onNewEvent: EventProcessor; logger: Logger }) {
|
|
18
|
+
this.isRunning = true;
|
|
19
|
+
this.loop(params.onNewEvent, params.logger);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
stop() {
|
|
23
|
+
this.isRunning = false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async loop(processEvent: EventProcessor, logger: Logger) {
|
|
27
|
+
if (!this.isRunning) return;
|
|
28
|
+
|
|
29
|
+
while (!this.taskQueue.isEmpty) {
|
|
30
|
+
const task = this.taskQueue.process();
|
|
31
|
+
if (task) {
|
|
32
|
+
await processEvent(task.event, logger);
|
|
33
|
+
this.taskQueue.remove(task.taskId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.timers.processTimers();
|
|
38
|
+
|
|
39
|
+
setTimeout(() => this.loop(processEvent, logger), 10);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { HeapClient } from "@yieldstar/core";
|
|
2
|
+
import { StepResponsesDao } from "./dao/step-response-dao";
|
|
3
|
+
import type { SqliteDriver } from "./sqlite-driver";
|
|
4
|
+
|
|
5
|
+
export class SqliteHeapClient implements HeapClient {
|
|
6
|
+
private stepResponsesDao: StepResponsesDao;
|
|
7
|
+
|
|
8
|
+
constructor(db: SqliteDriver) {
|
|
9
|
+
this.stepResponsesDao = new StepResponsesDao(db);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async getAllSteps() {
|
|
13
|
+
return this.stepResponsesDao.getAllSteps();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async deleteAll() {
|
|
17
|
+
this.stepResponsesDao.deleteAll();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async readStep(params: { executionId: string; stepKey: string }) {
|
|
21
|
+
const result = this.stepResponsesDao.getLatestStepResponse(
|
|
22
|
+
params.executionId,
|
|
23
|
+
params.stepKey
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!result?.step_response) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
stepResponseJson: result.step_response,
|
|
32
|
+
meta: {
|
|
33
|
+
attempt: result.step_attempt,
|
|
34
|
+
done: Boolean(result.step_done),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async writeStep(params: {
|
|
40
|
+
executionId: string;
|
|
41
|
+
stepKey: string;
|
|
42
|
+
stepAttempt: number;
|
|
43
|
+
stepDone: boolean;
|
|
44
|
+
stepResponseJson: string;
|
|
45
|
+
}) {
|
|
46
|
+
this.stepResponsesDao.insertStepResponse(
|
|
47
|
+
params.executionId,
|
|
48
|
+
params.stepKey,
|
|
49
|
+
params.stepAttempt,
|
|
50
|
+
params.stepDone,
|
|
51
|
+
params.stepResponseJson
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SchedulerClient, WorkflowEvent } from "@yieldstar/core";
|
|
2
|
+
import { SqliteTaskQueueClient } from "./sqlite-task-queue";
|
|
3
|
+
import { SqliteTimersClient } from "./sqlite-timers";
|
|
4
|
+
|
|
5
|
+
export class SqliteSchedulerClient implements SchedulerClient {
|
|
6
|
+
private taskQueue: SqliteTaskQueueClient;
|
|
7
|
+
private timersClient: SqliteTimersClient;
|
|
8
|
+
|
|
9
|
+
constructor(params: {
|
|
10
|
+
taskQueueClient: SqliteTaskQueueClient;
|
|
11
|
+
timersClient: SqliteTimersClient;
|
|
12
|
+
}) {
|
|
13
|
+
this.taskQueue = params.taskQueueClient;
|
|
14
|
+
this.timersClient = params.timersClient;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async requestWakeUp(event: WorkflowEvent, resumeIn?: number) {
|
|
18
|
+
if (resumeIn) {
|
|
19
|
+
this.timersClient.createTimer(event, resumeIn);
|
|
20
|
+
} else {
|
|
21
|
+
this.taskQueue.add(event);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|