@yieldstar/sqlite-runtime 0.5.0 → 0.6.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/dist/bun.js +15 -17
- package/dist/dao/effect-waiters-dao.d.ts +19 -0
- package/dist/dao/effect-waiters-dao.d.ts.map +1 -0
- package/dist/dao/effects-dao.d.ts +17 -0
- package/dist/dao/effects-dao.d.ts.map +1 -0
- package/dist/dao/executions-dao.d.ts +24 -0
- package/dist/dao/executions-dao.d.ts.map +1 -0
- package/dist/dao/start-requests-dao.d.ts +15 -0
- package/dist/dao/start-requests-dao.d.ts.map +1 -0
- package/dist/dao/step-response-dao.d.ts +1 -0
- package/dist/dao/step-response-dao.d.ts.map +1 -1
- package/dist/dao/store-waiters-dao.d.ts +26 -0
- package/dist/dao/store-waiters-dao.d.ts.map +1 -0
- package/dist/index-083bmj5k.js +800 -0
- package/dist/index-4kybaxga.js +798 -0
- package/dist/index-a8thbaee.js +718 -0
- package/dist/index-ag93sv33.js +664 -0
- package/dist/index-eabcgmaa.js +996 -0
- package/dist/index-ekggxe8v.js +987 -0
- package/dist/index-gjfs8rn6.js +997 -0
- package/dist/index-h3h7r4b6.js +981 -0
- package/dist/index-ma984fze.js +718 -0
- package/dist/index-ne28djfn.js +718 -0
- package/dist/index-t60e4kvz.js +988 -0
- package/dist/index-v6rn0pr6.js +664 -0
- package/dist/index-wx6vk994.js +669 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -13
- package/dist/node.js +15 -17
- package/dist/sqlite-effect.d.ts +30 -0
- package/dist/sqlite-effect.d.ts.map +1 -0
- package/dist/sqlite-execution.d.ts +55 -0
- package/dist/sqlite-execution.d.ts.map +1 -0
- package/dist/sqlite-heap.d.ts +12 -4
- package/dist/sqlite-heap.d.ts.map +1 -1
- package/dist/sqlite-runtime.d.ts +22 -0
- package/dist/sqlite-runtime.d.ts.map +1 -0
- package/dist/sqlite-store.d.ts +31 -25
- package/dist/sqlite-store.d.ts.map +1 -1
- package/dist/sqlite-transaction.d.ts +9 -0
- package/dist/sqlite-transaction.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/dao/effect-waiters-dao.ts +63 -0
- package/src/dao/effects-dao.ts +123 -0
- package/src/dao/executions-dao.ts +199 -0
- package/src/dao/start-requests-dao.ts +72 -0
- package/src/dao/step-response-dao.ts +16 -1
- package/src/dao/store-waiters-dao.ts +92 -0
- package/src/index.ts +6 -4
- package/src/sqlite-effect.ts +99 -0
- package/src/sqlite-execution.ts +242 -0
- package/src/sqlite-heap.ts +64 -12
- package/src/sqlite-runtime.ts +36 -0
- package/src/sqlite-store.ts +184 -520
- package/src/sqlite-transaction.ts +19 -0
- package/dist/dao/task-queue-dao.d.ts +0 -21
- package/dist/dao/task-queue-dao.d.ts.map +0 -1
- package/dist/dao/timers-dao.d.ts +0 -22
- package/dist/dao/timers-dao.d.ts.map +0 -1
- package/dist/index-4vq70w0r.js +0 -796
- package/dist/index-s8wqgfwt.js +0 -19
- package/dist/index-x891hxwg.js +0 -796
- package/dist/node-driver.d.ts +0 -10
- package/dist/node-driver.d.ts.map +0 -1
- package/dist/sqlite-event-loop.d.ts +0 -18
- package/dist/sqlite-event-loop.d.ts.map +0 -1
- package/dist/sqlite-scheduler.d.ts +0 -13
- package/dist/sqlite-scheduler.d.ts.map +0 -1
- package/dist/sqlite-store.test.d.ts +0 -2
- package/dist/sqlite-store.test.d.ts.map +0 -1
- package/dist/sqlite-task-queue.d.ts +0 -26
- package/dist/sqlite-task-queue.d.ts.map +0 -1
- package/dist/sqlite-timers.d.ts +0 -18
- package/dist/sqlite-timers.d.ts.map +0 -1
- package/src/dao/task-queue-dao.ts +0 -79
- package/src/dao/timers-dao.ts +0 -81
- package/src/sqlite-event-loop.ts +0 -41
- package/src/sqlite-scheduler.ts +0 -24
- package/src/sqlite-task-queue.ts +0 -64
- package/src/sqlite-timers.ts +0 -40
- package/tsconfig.tsbuildinfo +0 -1
- /package/dist/{index-h7bm9xg4.js → index-dapqh99v.js} +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { ExecutionState } from "@yieldstar/core";
|
|
2
|
+
import { nextClaimAt } from "@yieldstar/core";
|
|
3
|
+
import type { SqliteDriver } from "../sqlite-driver";
|
|
4
|
+
|
|
5
|
+
class ExecutionRow {
|
|
6
|
+
execution_id!: string;
|
|
7
|
+
workflow_id!: string;
|
|
8
|
+
params!: string;
|
|
9
|
+
context!: string;
|
|
10
|
+
status!: ExecutionState["status"];
|
|
11
|
+
outcome!: string | null;
|
|
12
|
+
admitted_at!: number;
|
|
13
|
+
updated_at!: number;
|
|
14
|
+
due_at!: number | null;
|
|
15
|
+
claimed_due_at!: number | null;
|
|
16
|
+
lease_token!: number;
|
|
17
|
+
lease_expires_at!: number | null;
|
|
18
|
+
unacknowledged_delivery_count!: number;
|
|
19
|
+
redeliver_at!: number | null;
|
|
20
|
+
next_claim_at!: number | null;
|
|
21
|
+
parent_execution_id!: string | null;
|
|
22
|
+
parent_step_key!: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class ExecutionIdRow {
|
|
26
|
+
execution_id!: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Persists every ExecutionState field, plus `next_claim_at` (derived with
|
|
31
|
+
* nextClaimAt) so claimDue can find due executions through an index.
|
|
32
|
+
* Callers own the surrounding transaction.
|
|
33
|
+
*/
|
|
34
|
+
export class ExecutionsDao {
|
|
35
|
+
private db: SqliteDriver;
|
|
36
|
+
|
|
37
|
+
constructor(db: SqliteDriver) {
|
|
38
|
+
this.db = db;
|
|
39
|
+
this.setupDb();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setupDb() {
|
|
43
|
+
this.db.run(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS executions (
|
|
45
|
+
execution_id TEXT PRIMARY KEY,
|
|
46
|
+
workflow_id TEXT NOT NULL,
|
|
47
|
+
params TEXT NOT NULL,
|
|
48
|
+
context TEXT NOT NULL,
|
|
49
|
+
status TEXT NOT NULL,
|
|
50
|
+
outcome TEXT,
|
|
51
|
+
admitted_at INTEGER NOT NULL,
|
|
52
|
+
updated_at INTEGER NOT NULL,
|
|
53
|
+
due_at INTEGER,
|
|
54
|
+
claimed_due_at INTEGER,
|
|
55
|
+
lease_token INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
lease_expires_at INTEGER,
|
|
57
|
+
unacknowledged_delivery_count INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
redeliver_at INTEGER,
|
|
59
|
+
next_claim_at INTEGER,
|
|
60
|
+
parent_execution_id TEXT,
|
|
61
|
+
parent_step_key TEXT
|
|
62
|
+
);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_executions_next_claim_at
|
|
64
|
+
ON executions(next_claim_at) WHERE next_claim_at IS NOT NULL;
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getState(executionId: string): ExecutionState | undefined {
|
|
69
|
+
const row = this.db
|
|
70
|
+
.query<ExecutionRow>(
|
|
71
|
+
`SELECT * FROM executions WHERE execution_id = $executionId`
|
|
72
|
+
)
|
|
73
|
+
.get({ $executionId: executionId });
|
|
74
|
+
return row ? toState(row) : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Up to `limit` claimable execution IDs, earliest due first. `now` is a
|
|
79
|
+
* millisecond Unix timestamp from the runtime clock. When `executionIds` is
|
|
80
|
+
* given, only those executions are considered.
|
|
81
|
+
*/
|
|
82
|
+
getClaimableIds(
|
|
83
|
+
now: number,
|
|
84
|
+
limit: number,
|
|
85
|
+
executionIds?: readonly string[]
|
|
86
|
+
) {
|
|
87
|
+
return this.db
|
|
88
|
+
.query<ExecutionIdRow>(
|
|
89
|
+
`SELECT execution_id FROM executions
|
|
90
|
+
WHERE next_claim_at IS NOT NULL AND next_claim_at <= $now
|
|
91
|
+
AND ($executionIds IS NULL
|
|
92
|
+
OR execution_id IN (SELECT value FROM json_each($executionIds)))
|
|
93
|
+
ORDER BY next_claim_at, execution_id
|
|
94
|
+
LIMIT $limit`
|
|
95
|
+
)
|
|
96
|
+
.all({
|
|
97
|
+
$now: now,
|
|
98
|
+
$limit: limit,
|
|
99
|
+
$executionIds: executionIds ? JSON.stringify(executionIds) : null,
|
|
100
|
+
})
|
|
101
|
+
.map((row) => row.execution_id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Returns false when the execution ID is already in use. */
|
|
105
|
+
insertState(state: ExecutionState): boolean {
|
|
106
|
+
const result = this.db
|
|
107
|
+
.query(
|
|
108
|
+
`INSERT INTO executions (
|
|
109
|
+
execution_id, workflow_id, params, context, status, outcome,
|
|
110
|
+
admitted_at, updated_at, due_at, claimed_due_at, lease_token,
|
|
111
|
+
lease_expires_at, unacknowledged_delivery_count, redeliver_at,
|
|
112
|
+
next_claim_at, parent_execution_id, parent_step_key
|
|
113
|
+
) VALUES (
|
|
114
|
+
$executionId, $workflowId, $params, $context, $status, $outcome,
|
|
115
|
+
$admittedAt, $updatedAt, $dueAt, $claimedDueAt, $leaseToken,
|
|
116
|
+
$leaseExpiresAt, $unacknowledgedDeliveryCount, $redeliverAt,
|
|
117
|
+
$nextClaimAt, $parentExecutionId, $parentStepKey
|
|
118
|
+
)
|
|
119
|
+
ON CONFLICT(execution_id) DO NOTHING`
|
|
120
|
+
)
|
|
121
|
+
.run({
|
|
122
|
+
...toBindings(state),
|
|
123
|
+
$parentExecutionId: state.parent?.executionId ?? null,
|
|
124
|
+
$parentStepKey: state.parent?.stepKey ?? null,
|
|
125
|
+
});
|
|
126
|
+
return Number(result.changes) === 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Leaves the parent link as inserted: it is immutable. */
|
|
130
|
+
updateState(state: ExecutionState) {
|
|
131
|
+
this.db
|
|
132
|
+
.query(
|
|
133
|
+
`UPDATE executions SET
|
|
134
|
+
workflow_id = $workflowId,
|
|
135
|
+
params = $params,
|
|
136
|
+
context = $context,
|
|
137
|
+
status = $status,
|
|
138
|
+
outcome = $outcome,
|
|
139
|
+
admitted_at = $admittedAt,
|
|
140
|
+
updated_at = $updatedAt,
|
|
141
|
+
due_at = $dueAt,
|
|
142
|
+
claimed_due_at = $claimedDueAt,
|
|
143
|
+
lease_token = $leaseToken,
|
|
144
|
+
lease_expires_at = $leaseExpiresAt,
|
|
145
|
+
unacknowledged_delivery_count = $unacknowledgedDeliveryCount,
|
|
146
|
+
redeliver_at = $redeliverAt,
|
|
147
|
+
next_claim_at = $nextClaimAt
|
|
148
|
+
WHERE execution_id = $executionId`
|
|
149
|
+
)
|
|
150
|
+
.run(toBindings(state));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toState(row: ExecutionRow): ExecutionState {
|
|
155
|
+
return {
|
|
156
|
+
executionId: row.execution_id,
|
|
157
|
+
workflowId: row.workflow_id,
|
|
158
|
+
params: row.params,
|
|
159
|
+
context: row.context,
|
|
160
|
+
status: row.status,
|
|
161
|
+
outcome: row.outcome,
|
|
162
|
+
admittedAt: Number(row.admitted_at),
|
|
163
|
+
updatedAt: Number(row.updated_at),
|
|
164
|
+
dueAt: toNumberOrNull(row.due_at),
|
|
165
|
+
claimedDueAt: toNumberOrNull(row.claimed_due_at),
|
|
166
|
+
leaseToken: Number(row.lease_token),
|
|
167
|
+
leaseExpiresAt: toNumberOrNull(row.lease_expires_at),
|
|
168
|
+
unacknowledgedDeliveryCount: Number(row.unacknowledged_delivery_count),
|
|
169
|
+
redeliverAt: toNumberOrNull(row.redeliver_at),
|
|
170
|
+
parent:
|
|
171
|
+
row.parent_execution_id !== null && row.parent_step_key !== null
|
|
172
|
+
? { executionId: row.parent_execution_id, stepKey: row.parent_step_key }
|
|
173
|
+
: null,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function toBindings(state: ExecutionState) {
|
|
178
|
+
return {
|
|
179
|
+
$executionId: state.executionId,
|
|
180
|
+
$workflowId: state.workflowId,
|
|
181
|
+
$params: state.params,
|
|
182
|
+
$context: state.context,
|
|
183
|
+
$status: state.status,
|
|
184
|
+
$outcome: state.outcome,
|
|
185
|
+
$admittedAt: state.admittedAt,
|
|
186
|
+
$updatedAt: state.updatedAt,
|
|
187
|
+
$dueAt: state.dueAt,
|
|
188
|
+
$claimedDueAt: state.claimedDueAt,
|
|
189
|
+
$leaseToken: state.leaseToken,
|
|
190
|
+
$leaseExpiresAt: state.leaseExpiresAt,
|
|
191
|
+
$unacknowledgedDeliveryCount: state.unacknowledgedDeliveryCount,
|
|
192
|
+
$redeliverAt: state.redeliverAt,
|
|
193
|
+
$nextClaimAt: nextClaimAt(state),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toNumberOrNull(value: number | null) {
|
|
198
|
+
return value === null ? null : Number(value);
|
|
199
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { StartRequestState } from "@yieldstar/core";
|
|
2
|
+
import type { SqliteDriver } from "../sqlite-driver";
|
|
3
|
+
|
|
4
|
+
class StartRequestRow {
|
|
5
|
+
caller_execution_id!: string;
|
|
6
|
+
step_key!: string;
|
|
7
|
+
workflow_id!: string;
|
|
8
|
+
params!: string | null;
|
|
9
|
+
execution_id!: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Start requests recorded by `ExecutionClient.start`, keyed by the caller's
|
|
14
|
+
* execution ID and step key. Callers own the surrounding transaction, which
|
|
15
|
+
* must also insert the target execution.
|
|
16
|
+
*/
|
|
17
|
+
export class StartRequestsDao {
|
|
18
|
+
private db: SqliteDriver;
|
|
19
|
+
|
|
20
|
+
constructor(db: SqliteDriver) {
|
|
21
|
+
this.db = db;
|
|
22
|
+
this.setupDb();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setupDb() {
|
|
26
|
+
this.db.run(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS start_requests (
|
|
28
|
+
caller_execution_id TEXT NOT NULL,
|
|
29
|
+
step_key TEXT NOT NULL,
|
|
30
|
+
workflow_id TEXT NOT NULL,
|
|
31
|
+
params TEXT,
|
|
32
|
+
execution_id TEXT NOT NULL,
|
|
33
|
+
PRIMARY KEY (caller_execution_id, step_key)
|
|
34
|
+
);
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get(callerExecutionId: string, stepKey: string): StartRequestState | undefined {
|
|
39
|
+
const row = this.db
|
|
40
|
+
.query<StartRequestRow>(
|
|
41
|
+
`SELECT * FROM start_requests
|
|
42
|
+
WHERE caller_execution_id = $callerExecutionId AND step_key = $stepKey`
|
|
43
|
+
)
|
|
44
|
+
.get({ $callerExecutionId: callerExecutionId, $stepKey: stepKey });
|
|
45
|
+
return row
|
|
46
|
+
? {
|
|
47
|
+
callerExecutionId: row.caller_execution_id,
|
|
48
|
+
stepKey: row.step_key,
|
|
49
|
+
workflowId: row.workflow_id,
|
|
50
|
+
params: row.params,
|
|
51
|
+
executionId: row.execution_id,
|
|
52
|
+
}
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
insert(request: StartRequestState) {
|
|
57
|
+
this.db
|
|
58
|
+
.query(
|
|
59
|
+
`INSERT INTO start_requests
|
|
60
|
+
(caller_execution_id, step_key, workflow_id, params, execution_id)
|
|
61
|
+
VALUES
|
|
62
|
+
($callerExecutionId, $stepKey, $workflowId, $params, $executionId)`
|
|
63
|
+
)
|
|
64
|
+
.run({
|
|
65
|
+
$callerExecutionId: request.callerExecutionId,
|
|
66
|
+
$stepKey: request.stepKey,
|
|
67
|
+
$workflowId: request.workflowId,
|
|
68
|
+
$params: request.params,
|
|
69
|
+
$executionId: request.executionId,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -23,7 +23,7 @@ export class StepResponsesDao {
|
|
|
23
23
|
step_key TEXT NOT NULL,
|
|
24
24
|
step_attempt INTEGER NOT NULL,
|
|
25
25
|
step_done BOOL NOT NULL,
|
|
26
|
-
step_response
|
|
26
|
+
step_response TEXT NOT NULL,
|
|
27
27
|
PRIMARY KEY (execution_id, step_key, step_attempt)
|
|
28
28
|
);
|
|
29
29
|
CREATE INDEX IF NOT EXISTS idx_execution_step_attempt
|
|
@@ -57,6 +57,21 @@ export class StepResponsesDao {
|
|
|
57
57
|
return result;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
getStepResponse(executionId: string, stepKey: string, stepAttempt: number) {
|
|
61
|
+
return this.db
|
|
62
|
+
.query<StepReponseRow>(
|
|
63
|
+
`SELECT * FROM step_responses
|
|
64
|
+
WHERE execution_id = $executionId
|
|
65
|
+
AND step_key = $stepKey
|
|
66
|
+
AND step_attempt = $stepAttempt`
|
|
67
|
+
)
|
|
68
|
+
.get({
|
|
69
|
+
$executionId: executionId,
|
|
70
|
+
$stepKey: stepKey,
|
|
71
|
+
$stepAttempt: stepAttempt,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
insertStepResponse(
|
|
61
76
|
executionId: string,
|
|
62
77
|
stepKey: string,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { StoreWaiter } from "@yieldstar/core";
|
|
2
|
+
import type { SqliteDriver } from "../sqlite-driver";
|
|
3
|
+
|
|
4
|
+
export class StoreWaiterRow {
|
|
5
|
+
workflow_id!: string;
|
|
6
|
+
execution_id!: string;
|
|
7
|
+
step_key!: string;
|
|
8
|
+
store_name!: string;
|
|
9
|
+
store_id!: string;
|
|
10
|
+
since_version!: number;
|
|
11
|
+
read_paths!: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Store waiters live in the same database as step records, so the heap can
|
|
16
|
+
* delete a waiter in the same transaction that records its step's result.
|
|
17
|
+
* Callers own the surrounding transaction.
|
|
18
|
+
*/
|
|
19
|
+
export class StoreWaitersDao {
|
|
20
|
+
private db: SqliteDriver;
|
|
21
|
+
|
|
22
|
+
constructor(db: SqliteDriver) {
|
|
23
|
+
this.db = db;
|
|
24
|
+
this.setupDb();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setupDb() {
|
|
28
|
+
this.db.run(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS store_waiters (
|
|
30
|
+
workflow_id TEXT NOT NULL,
|
|
31
|
+
execution_id TEXT NOT NULL,
|
|
32
|
+
step_key TEXT NOT NULL,
|
|
33
|
+
store_name TEXT NOT NULL,
|
|
34
|
+
store_id TEXT NOT NULL,
|
|
35
|
+
since_version INTEGER NOT NULL,
|
|
36
|
+
read_paths TEXT NOT NULL,
|
|
37
|
+
PRIMARY KEY (store_name, store_id, execution_id, step_key)
|
|
38
|
+
);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_store_waiters_step
|
|
40
|
+
ON store_waiters(execution_id, step_key);
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
upsertWaiter(waiter: StoreWaiter) {
|
|
45
|
+
this.db
|
|
46
|
+
.query(
|
|
47
|
+
`INSERT INTO store_waiters
|
|
48
|
+
(workflow_id, execution_id, step_key, store_name, store_id, since_version, read_paths)
|
|
49
|
+
VALUES
|
|
50
|
+
($workflowId, $executionId, $stepKey, $storeName, $storeId, $sinceVersion, $readPaths)
|
|
51
|
+
ON CONFLICT(store_name, store_id, execution_id, step_key)
|
|
52
|
+
DO UPDATE SET
|
|
53
|
+
workflow_id = excluded.workflow_id,
|
|
54
|
+
since_version = excluded.since_version,
|
|
55
|
+
read_paths = excluded.read_paths`
|
|
56
|
+
)
|
|
57
|
+
.run({
|
|
58
|
+
$workflowId: waiter.workflowId,
|
|
59
|
+
$executionId: waiter.executionId,
|
|
60
|
+
$stepKey: waiter.stepKey,
|
|
61
|
+
$storeName: waiter.storeName,
|
|
62
|
+
$storeId: waiter.storeId,
|
|
63
|
+
$sinceVersion: waiter.sinceVersion,
|
|
64
|
+
$readPaths: JSON.stringify(waiter.readPaths),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getStoreWaiters(storeName: string, storeId: string) {
|
|
69
|
+
return this.db
|
|
70
|
+
.query<StoreWaiterRow>(
|
|
71
|
+
`SELECT * FROM store_waiters
|
|
72
|
+
WHERE store_name = $storeName AND store_id = $storeId`
|
|
73
|
+
)
|
|
74
|
+
.all({ $storeName: storeName, $storeId: storeId });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deleteStoreWaiters(storeName: string, storeId: string) {
|
|
78
|
+
this.db
|
|
79
|
+
.query(
|
|
80
|
+
`DELETE FROM store_waiters WHERE store_name = $storeName AND store_id = $storeId`
|
|
81
|
+
)
|
|
82
|
+
.run({ $storeName: storeName, $storeId: storeId });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
deleteStepWaiters(executionId: string, stepKey: string) {
|
|
86
|
+
this.db
|
|
87
|
+
.query(
|
|
88
|
+
`DELETE FROM store_waiters WHERE execution_id = $executionId AND step_key = $stepKey`
|
|
89
|
+
)
|
|
90
|
+
.run({ $executionId: executionId, $stepKey: stepKey });
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
export { SqliteRuntime, type SqliteRuntimeOptions } from "./sqlite-runtime";
|
|
2
|
+
export {
|
|
3
|
+
SqliteExecutionClient,
|
|
4
|
+
type SqliteExecutionClientOptions,
|
|
5
|
+
} from "./sqlite-execution";
|
|
6
|
+
export { SqliteEffectClient } from "./sqlite-effect";
|
|
1
7
|
export { SqliteHeapClient } from "./sqlite-heap";
|
|
2
8
|
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
9
|
export type {
|
|
8
10
|
SqliteDriver,
|
|
9
11
|
SqliteParameters,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEffectState,
|
|
3
|
+
effectNotFound,
|
|
4
|
+
matchEffectBegin,
|
|
5
|
+
prepareEffectBegin,
|
|
6
|
+
prepareEffectSettle,
|
|
7
|
+
prepareEffectWatch,
|
|
8
|
+
settleEffect,
|
|
9
|
+
toEffectRecord,
|
|
10
|
+
type EffectBeginRequest,
|
|
11
|
+
type EffectBeginResult,
|
|
12
|
+
type EffectClient,
|
|
13
|
+
type EffectRecord,
|
|
14
|
+
type EffectSettleRequest,
|
|
15
|
+
type EffectSettleResult,
|
|
16
|
+
type EffectWatchRequest,
|
|
17
|
+
} from "@yieldstar/core";
|
|
18
|
+
import { EffectWaitersDao } from "./dao/effect-waiters-dao";
|
|
19
|
+
import { EffectsDao } from "./dao/effects-dao";
|
|
20
|
+
import type { SqliteDriver } from "./sqlite-driver";
|
|
21
|
+
import { SqliteExecutionClient } from "./sqlite-execution";
|
|
22
|
+
import { runTransaction } from "./sqlite-transaction";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Effect records and waiters in the same database as the execution records,
|
|
26
|
+
* so a settlement and the wakes of its waiters commit in one IMMEDIATE
|
|
27
|
+
* transaction.
|
|
28
|
+
*/
|
|
29
|
+
export class SqliteEffectClient implements EffectClient {
|
|
30
|
+
private db: SqliteDriver;
|
|
31
|
+
private executions: SqliteExecutionClient;
|
|
32
|
+
private effectsDao: EffectsDao;
|
|
33
|
+
private effectWaitersDao: EffectWaitersDao;
|
|
34
|
+
private clock: () => number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param params.executions The execution client whose records waiters wake.
|
|
38
|
+
* Defaults to one over `db` with the default clock; SqliteRuntime passes
|
|
39
|
+
* its own so both share the runtime clock.
|
|
40
|
+
*/
|
|
41
|
+
constructor(params: {
|
|
42
|
+
db: SqliteDriver;
|
|
43
|
+
executions?: SqliteExecutionClient;
|
|
44
|
+
clock?: () => number;
|
|
45
|
+
}) {
|
|
46
|
+
this.db = params.db;
|
|
47
|
+
// Read Date.now lazily so fake timers installed later still apply.
|
|
48
|
+
this.clock = params.clock ?? (() => Date.now());
|
|
49
|
+
this.executions =
|
|
50
|
+
params.executions ?? new SqliteExecutionClient(params.db, { clock: this.clock });
|
|
51
|
+
this.effectsDao = new EffectsDao(params.db);
|
|
52
|
+
this.effectWaitersDao = new EffectWaitersDao(params.db);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async begin(request: EffectBeginRequest): Promise<EffectBeginResult> {
|
|
56
|
+
const begin = prepareEffectBegin(request);
|
|
57
|
+
return runTransaction(this.db, () => {
|
|
58
|
+
const state = this.effectsDao.getState(begin.effectId);
|
|
59
|
+
if (state) return matchEffectBegin(state, begin);
|
|
60
|
+
this.effectsDao.insertState(createEffectState(begin));
|
|
61
|
+
return { status: "new" as const };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async settle(request: EffectSettleRequest): Promise<EffectSettleResult> {
|
|
66
|
+
const settle = prepareEffectSettle(request);
|
|
67
|
+
return runTransaction(this.db, () => {
|
|
68
|
+
const state = this.effectsDao.getState(settle.effectId);
|
|
69
|
+
if (!state) throw effectNotFound(settle.effectId);
|
|
70
|
+
const settled = settleEffect(state, settle, this.clock());
|
|
71
|
+
if (settled.state !== state) this.effectsDao.updateState(state, settled.state);
|
|
72
|
+
if (settled.wake) {
|
|
73
|
+
for (const executionId of this.effectWaitersDao.getWaitingExecutionIds(
|
|
74
|
+
settle.effectId
|
|
75
|
+
)) {
|
|
76
|
+
this.executions.wakeInTransaction(executionId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return settled.result;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async watch(request: EffectWatchRequest): Promise<void> {
|
|
84
|
+
const watch = prepareEffectWatch(request);
|
|
85
|
+
runTransaction(this.db, () => {
|
|
86
|
+
const state = this.effectsDao.getState(watch.effectId);
|
|
87
|
+
if (!state) throw effectNotFound(watch.effectId);
|
|
88
|
+
this.effectWaitersDao.upsertWaiter(watch);
|
|
89
|
+
if (state.status === "settled") {
|
|
90
|
+
this.executions.wakeInTransaction(watch.executionId);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getEffect(effectId: string): Promise<EffectRecord | undefined> {
|
|
96
|
+
const state = this.effectsDao.getState(effectId);
|
|
97
|
+
return state ? toEffectRecord(state) : undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|