syncorejs 0.2.1 → 0.2.3
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/README.md +2 -1
- package/dist/_vendor/cli/app.d.mts.map +1 -1
- package/dist/_vendor/cli/app.mjs +330 -46
- package/dist/_vendor/cli/app.mjs.map +1 -1
- package/dist/_vendor/cli/context.mjs +27 -9
- package/dist/_vendor/cli/context.mjs.map +1 -1
- package/dist/_vendor/cli/dev-session.mjs.map +1 -1
- package/dist/_vendor/cli/doctor.mjs +513 -46
- package/dist/_vendor/cli/doctor.mjs.map +1 -1
- package/dist/_vendor/cli/errors.mjs.map +1 -1
- package/dist/_vendor/cli/help.mjs.map +1 -1
- package/dist/_vendor/cli/index.mjs +9 -2
- package/dist/_vendor/cli/index.mjs.map +1 -1
- package/dist/_vendor/cli/messages.mjs +5 -4
- package/dist/_vendor/cli/messages.mjs.map +1 -1
- package/dist/_vendor/cli/preflight.mjs.map +1 -1
- package/dist/_vendor/cli/project.mjs +125 -27
- package/dist/_vendor/cli/project.mjs.map +1 -1
- package/dist/_vendor/cli/render.mjs +57 -9
- package/dist/_vendor/cli/render.mjs.map +1 -1
- package/dist/_vendor/cli/targets.mjs +4 -3
- package/dist/_vendor/cli/targets.mjs.map +1 -1
- package/dist/_vendor/core/cli.d.mts +20 -4
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +458 -133
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/devtools-auth.mjs +60 -0
- package/dist/_vendor/core/devtools-auth.mjs.map +1 -0
- package/dist/_vendor/core/index.d.mts +5 -3
- package/dist/_vendor/core/index.mjs +22 -2
- package/dist/_vendor/core/index.mjs.map +1 -1
- package/dist/_vendor/core/runtime/components.d.mts +111 -0
- package/dist/_vendor/core/runtime/components.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/components.mjs +186 -0
- package/dist/_vendor/core/runtime/components.mjs.map +1 -0
- package/dist/_vendor/core/runtime/devtools.d.mts +4 -4
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +178 -60
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +398 -16
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs +74 -3
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/id.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +83 -0
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +720 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +234 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +255 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +200 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +252 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +145 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +221 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs +32 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs +61 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +41 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -0
- package/dist/_vendor/core/runtime/runtime.d.mts +1187 -202
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +73 -1365
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts +113 -0
- package/dist/_vendor/core/transport.d.mts.map +1 -0
- package/dist/_vendor/core/transport.mjs +428 -0
- package/dist/_vendor/core/transport.mjs.map +1 -0
- package/dist/_vendor/devtools-protocol/index.d.ts +187 -4
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js +25 -9
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/config.d.ts +3 -4
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +37 -19
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/next/index.d.ts +109 -29
- package/dist/_vendor/next/index.d.ts.map +1 -1
- package/dist/_vendor/next/index.js +104 -26
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +156 -37
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +80 -12
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/react.js +11 -10
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-node/index.d.mts +192 -24
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +236 -97
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs +15 -2
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc.d.mts +11 -35
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc.mjs +3 -273
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +43 -1
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
- package/dist/_vendor/platform-web/external-change.js +32 -1
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +323 -51
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +233 -30
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js +10 -0
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.d.ts +13 -0
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
- package/dist/_vendor/platform-web/opfs.js +12 -0
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.d.ts +54 -0
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
- package/dist/_vendor/platform-web/persistence.js +15 -0
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts +1 -2
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +27 -13
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +10 -1
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
- package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-web/worker.d.ts +71 -44
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
- package/dist/_vendor/platform-web/worker.js +40 -271
- package/dist/_vendor/platform-web/worker.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +222 -23
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +476 -63
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/schema/definition.d.ts +151 -37
- package/dist/_vendor/schema/definition.d.ts.map +1 -1
- package/dist/_vendor/schema/definition.js +102 -20
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/index.d.ts +4 -4
- package/dist/_vendor/schema/index.js +2 -2
- package/dist/_vendor/schema/planner.d.ts +19 -2
- package/dist/_vendor/schema/planner.d.ts.map +1 -1
- package/dist/_vendor/schema/planner.js +79 -3
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.d.ts +279 -83
- package/dist/_vendor/schema/validators.d.ts.map +1 -1
- package/dist/_vendor/schema/validators.js +330 -38
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +245 -19
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +443 -20
- package/dist/_vendor/svelte/index.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/package.json +29 -21
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { generateId } from "../../id.mjs";
|
|
2
|
+
import { computeNextRun, createDevtoolsPreview, parseMisfirePolicy, shouldRunMissedJob, stableStringify } from "./shared.mjs";
|
|
3
|
+
//#region src/runtime/internal/engines/schedulerEngine.ts
|
|
4
|
+
var SchedulerEngine = class {
|
|
5
|
+
deps;
|
|
6
|
+
timer;
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.deps = deps;
|
|
9
|
+
}
|
|
10
|
+
async prepare() {
|
|
11
|
+
await this.deps.driver.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS "_scheduled_functions" (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
function_name TEXT NOT NULL,
|
|
15
|
+
function_kind TEXT NOT NULL,
|
|
16
|
+
args_json TEXT NOT NULL,
|
|
17
|
+
status TEXT NOT NULL,
|
|
18
|
+
run_at INTEGER NOT NULL,
|
|
19
|
+
created_at INTEGER NOT NULL,
|
|
20
|
+
updated_at INTEGER NOT NULL,
|
|
21
|
+
recurring_name TEXT,
|
|
22
|
+
schedule_json TEXT,
|
|
23
|
+
timezone TEXT,
|
|
24
|
+
misfire_policy TEXT NOT NULL,
|
|
25
|
+
last_run_at INTEGER,
|
|
26
|
+
window_ms INTEGER
|
|
27
|
+
);
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
startPolling() {
|
|
31
|
+
if (this.timer) return;
|
|
32
|
+
this.timer = setInterval(() => {
|
|
33
|
+
this.processDueJobs();
|
|
34
|
+
}, this.deps.pollIntervalMs);
|
|
35
|
+
}
|
|
36
|
+
stopPolling() {
|
|
37
|
+
if (!this.timer) return;
|
|
38
|
+
clearInterval(this.timer);
|
|
39
|
+
this.timer = void 0;
|
|
40
|
+
}
|
|
41
|
+
async scheduleJob(runAt, reference, args, misfirePolicy, namespacePrefix) {
|
|
42
|
+
const id = `${namespacePrefix ?? ""}${generateId()}`;
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
await this.deps.driver.run(`INSERT INTO "_scheduled_functions"
|
|
45
|
+
(id, function_name, function_kind, args_json, status, run_at, created_at, updated_at, recurring_name, schedule_json, timezone, misfire_policy, last_run_at, window_ms)
|
|
46
|
+
VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, NULL, NULL, NULL, ?, NULL, ?)`, [
|
|
47
|
+
id,
|
|
48
|
+
reference.name,
|
|
49
|
+
reference.kind,
|
|
50
|
+
stableStringify(args),
|
|
51
|
+
runAt,
|
|
52
|
+
now,
|
|
53
|
+
now,
|
|
54
|
+
misfirePolicy.type,
|
|
55
|
+
misfirePolicy.type === "windowed" ? misfirePolicy.windowMs : null
|
|
56
|
+
]);
|
|
57
|
+
this.notifySchedulerJobsChanged();
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
async cancelScheduledJob(id) {
|
|
61
|
+
if (((await this.deps.driver.run(`UPDATE "_scheduled_functions"
|
|
62
|
+
SET status = 'cancelled', updated_at = ?
|
|
63
|
+
WHERE id = ? AND status = 'scheduled'`, [Date.now(), id])).changes ?? 0) > 0) {
|
|
64
|
+
this.notifySchedulerJobsChanged();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
async updateScheduledJob(options) {
|
|
70
|
+
const existing = await this.deps.driver.get(`SELECT status, run_at, recurring_name, schedule_json, misfire_policy, window_ms
|
|
71
|
+
FROM "_scheduled_functions" WHERE id = ?`, [options.id]);
|
|
72
|
+
if (!existing || existing.status !== "scheduled") return false;
|
|
73
|
+
const existingSchedule = existing.schedule_json ? JSON.parse(existing.schedule_json) : void 0;
|
|
74
|
+
const schedule = options.schedule ?? existingSchedule;
|
|
75
|
+
const misfirePolicy = options.misfirePolicy ?? parseMisfirePolicy(existing.misfire_policy ?? "catch_up", existing.window_ms);
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const runAt = options.runAt ?? (schedule ? computeNextRun(schedule, now) : existing.run_at);
|
|
78
|
+
if (((await this.deps.driver.run(`UPDATE "_scheduled_functions"
|
|
79
|
+
SET args_json = ?, run_at = ?, updated_at = ?, schedule_json = ?, timezone = ?, misfire_policy = ?, window_ms = ?
|
|
80
|
+
WHERE id = ? AND status = 'scheduled'`, [
|
|
81
|
+
stableStringify(options.args),
|
|
82
|
+
runAt,
|
|
83
|
+
now,
|
|
84
|
+
schedule ? stableStringify(schedule) : null,
|
|
85
|
+
schedule && "timezone" in schedule ? schedule.timezone ?? null : null,
|
|
86
|
+
misfirePolicy.type,
|
|
87
|
+
misfirePolicy.type === "windowed" ? misfirePolicy.windowMs : null,
|
|
88
|
+
options.id
|
|
89
|
+
])).changes ?? 0) > 0) {
|
|
90
|
+
this.notifySchedulerJobsChanged();
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
async syncRecurringJobs() {
|
|
96
|
+
for (const job of this.deps.recurringJobs) {
|
|
97
|
+
const id = `recurring:${job.name}`;
|
|
98
|
+
if (await this.deps.driver.get(`SELECT * FROM "_scheduled_functions" WHERE id = ?`, [id])) continue;
|
|
99
|
+
const nextRunAt = computeNextRun(job.schedule, Date.now());
|
|
100
|
+
await this.deps.driver.run(`INSERT INTO "_scheduled_functions"
|
|
101
|
+
(id, function_name, function_kind, args_json, status, run_at, created_at, updated_at, recurring_name, schedule_json, timezone, misfire_policy, last_run_at, window_ms)
|
|
102
|
+
VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, ?, ?, ?, ?, NULL, ?)`, [
|
|
103
|
+
id,
|
|
104
|
+
job.function.name,
|
|
105
|
+
job.function.kind,
|
|
106
|
+
stableStringify(job.args),
|
|
107
|
+
nextRunAt,
|
|
108
|
+
Date.now(),
|
|
109
|
+
Date.now(),
|
|
110
|
+
job.name,
|
|
111
|
+
stableStringify(job.schedule),
|
|
112
|
+
"timezone" in job.schedule ? job.schedule.timezone ?? null : null,
|
|
113
|
+
job.misfirePolicy.type,
|
|
114
|
+
job.misfirePolicy.type === "windowed" ? job.misfirePolicy.windowMs : null
|
|
115
|
+
]);
|
|
116
|
+
this.notifySchedulerJobsChanged();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
notifySchedulerJobsChanged() {
|
|
120
|
+
this.deps.devtools.notifyScopes(["scheduler.jobs"]);
|
|
121
|
+
}
|
|
122
|
+
async processDueJobs() {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const dueJobs = await this.deps.driver.all(`SELECT * FROM "_scheduled_functions" WHERE status = 'scheduled' AND run_at <= ? ORDER BY run_at ASC`, [now]);
|
|
125
|
+
const executedJobIds = [];
|
|
126
|
+
const jobExecutions = [];
|
|
127
|
+
for (const job of dueJobs) {
|
|
128
|
+
const misfirePolicy = parseMisfirePolicy(job.misfire_policy, job.window_ms);
|
|
129
|
+
if (!shouldRunMissedJob(job.run_at, now, misfirePolicy)) {
|
|
130
|
+
await this.advanceOrFinalizeJob(job, "skipped", now);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const args = JSON.parse(job.args_json);
|
|
135
|
+
const executionId = generateId();
|
|
136
|
+
const startedAt = Date.now();
|
|
137
|
+
let result;
|
|
138
|
+
if (job.function_kind === "mutation") result = await this.deps.runMutation({
|
|
139
|
+
kind: "mutation",
|
|
140
|
+
name: job.function_name
|
|
141
|
+
}, args, {
|
|
142
|
+
executionId,
|
|
143
|
+
schedulerJobId: job.id,
|
|
144
|
+
schedulerRun: true
|
|
145
|
+
});
|
|
146
|
+
else result = await this.deps.runAction({
|
|
147
|
+
kind: "action",
|
|
148
|
+
name: job.function_name
|
|
149
|
+
}, args, {
|
|
150
|
+
executionId,
|
|
151
|
+
schedulerJobId: job.id,
|
|
152
|
+
schedulerRun: true
|
|
153
|
+
});
|
|
154
|
+
executedJobIds.push(job.id);
|
|
155
|
+
jobExecutions.push({
|
|
156
|
+
jobId: job.id,
|
|
157
|
+
executionId,
|
|
158
|
+
functionName: job.function_name,
|
|
159
|
+
functionType: job.function_kind === "mutation" ? "mutation" : "action",
|
|
160
|
+
argsPreview: createDevtoolsPreview(args),
|
|
161
|
+
resultPreview: createDevtoolsPreview(result),
|
|
162
|
+
durationMs: Date.now() - startedAt
|
|
163
|
+
});
|
|
164
|
+
await this.advanceOrFinalizeJob(job, "completed", now);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
jobExecutions.push({
|
|
167
|
+
jobId: job.id,
|
|
168
|
+
functionName: job.function_name,
|
|
169
|
+
functionType: job.function_kind === "mutation" ? "mutation" : "action",
|
|
170
|
+
error: error instanceof Error ? error.message : String(error)
|
|
171
|
+
});
|
|
172
|
+
await this.deps.driver.run(`UPDATE "_scheduled_functions" SET status = 'failed', updated_at = ? WHERE id = ?`, [Date.now(), job.id]);
|
|
173
|
+
this.notifySchedulerJobsChanged();
|
|
174
|
+
this.deps.devtools.emit({
|
|
175
|
+
type: "log",
|
|
176
|
+
runtimeId: this.deps.runtimeId,
|
|
177
|
+
level: "error",
|
|
178
|
+
message: `Scheduled job ${job.id} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
179
|
+
timestamp: Date.now()
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (jobExecutions.length > 0) {
|
|
184
|
+
this.deps.devtools.emit({
|
|
185
|
+
type: "scheduler.tick",
|
|
186
|
+
runtimeId: this.deps.runtimeId,
|
|
187
|
+
executionId: generateId(),
|
|
188
|
+
executedJobIds,
|
|
189
|
+
jobExecutions,
|
|
190
|
+
timestamp: Date.now()
|
|
191
|
+
});
|
|
192
|
+
this.notifySchedulerJobsChanged();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async advanceOrFinalizeJob(job, terminalStatus, executedAt) {
|
|
196
|
+
if (!job.recurring_name || !job.schedule_json) {
|
|
197
|
+
await this.deps.driver.run(`UPDATE "_scheduled_functions" SET status = ?, updated_at = ?, last_run_at = ? WHERE id = ?`, [
|
|
198
|
+
terminalStatus,
|
|
199
|
+
executedAt,
|
|
200
|
+
executedAt,
|
|
201
|
+
job.id
|
|
202
|
+
]);
|
|
203
|
+
this.notifySchedulerJobsChanged();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const schedule = readRecurringSchedule(job.schedule_json);
|
|
207
|
+
if (!schedule) {
|
|
208
|
+
await this.deps.driver.run(`UPDATE "_scheduled_functions" SET status = ?, updated_at = ?, last_run_at = ? WHERE id = ?`, [
|
|
209
|
+
terminalStatus,
|
|
210
|
+
executedAt,
|
|
211
|
+
executedAt,
|
|
212
|
+
job.id
|
|
213
|
+
]);
|
|
214
|
+
this.notifySchedulerJobsChanged();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const nextRunAt = computeNextRun(schedule, executedAt + 1);
|
|
218
|
+
await this.deps.driver.run(`UPDATE "_scheduled_functions"
|
|
219
|
+
SET status = 'scheduled', run_at = ?, updated_at = ?, last_run_at = ?
|
|
220
|
+
WHERE id = ?`, [
|
|
221
|
+
nextRunAt,
|
|
222
|
+
executedAt,
|
|
223
|
+
executedAt,
|
|
224
|
+
job.id
|
|
225
|
+
]);
|
|
226
|
+
this.notifySchedulerJobsChanged();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function readRecurringSchedule(scheduleJson) {
|
|
230
|
+
if (!scheduleJson) return;
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(scheduleJson);
|
|
233
|
+
if (!isRecurringSchedule(parsed)) return;
|
|
234
|
+
return parsed;
|
|
235
|
+
} catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function isRecurringSchedule(value) {
|
|
240
|
+
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
241
|
+
const schedule = value;
|
|
242
|
+
switch (schedule.type) {
|
|
243
|
+
case "interval": return (schedule.seconds === void 0 || typeof schedule.seconds === "number") && (schedule.minutes === void 0 || typeof schedule.minutes === "number") && (schedule.hours === void 0 || typeof schedule.hours === "number");
|
|
244
|
+
case "daily": return typeof schedule.hour === "number" && typeof schedule.minute === "number" && (schedule.timezone === void 0 || typeof schedule.timezone === "string");
|
|
245
|
+
case "weekly": return isDayOfWeek(schedule.dayOfWeek) && typeof schedule.hour === "number" && typeof schedule.minute === "number" && (schedule.timezone === void 0 || typeof schedule.timezone === "string");
|
|
246
|
+
default: return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function isDayOfWeek(value) {
|
|
250
|
+
return value === "sunday" || value === "monday" || value === "tuesday" || value === "wednesday" || value === "thursday" || value === "friday" || value === "saturday";
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
export { SchedulerEngine };
|
|
254
|
+
|
|
255
|
+
//# sourceMappingURL=schedulerEngine.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedulerEngine.mjs","names":[],"sources":["../../../../src/runtime/internal/engines/schedulerEngine.ts"],"sourcesContent":["import type {\n FunctionReference,\n MisfirePolicy,\n RecurringJobDefinition\n} from \"../../functions.js\";\nimport type {\n JsonObject,\n SyncoreSqlDriver,\n UpdateScheduledJobOptions\n} from \"../../runtime.js\";\nimport { type DevtoolsEngine } from \"./devtoolsEngine.js\";\nimport {\n computeNextRun,\n createDevtoolsPreview,\n parseMisfirePolicy,\n shouldRunMissedJob,\n stableStringify,\n type ScheduledJobRow\n} from \"./shared.js\";\nimport { generateId } from \"../../id.js\";\n\ntype SchedulerEngineDeps = {\n driver: SyncoreSqlDriver;\n runtimeId: string;\n devtools: DevtoolsEngine;\n recurringJobs: RecurringJobDefinition[];\n pollIntervalMs: number;\n runMutation: (\n reference: FunctionReference<\"mutation\", unknown, unknown>,\n args: JsonObject,\n meta?: { executionId?: string; schedulerJobId?: string; schedulerRun?: boolean }\n ) => Promise<unknown>;\n runAction: (\n reference: FunctionReference<\"action\", unknown, unknown>,\n args: JsonObject,\n meta?: { executionId?: string; schedulerJobId?: string; schedulerRun?: boolean }\n ) => Promise<unknown>;\n};\n\nexport class SchedulerEngine {\n private timer: ReturnType<typeof setInterval> | undefined;\n\n constructor(private readonly deps: SchedulerEngineDeps) {}\n\n async prepare(): Promise<void> {\n await this.deps.driver.exec(`\n CREATE TABLE IF NOT EXISTS \"_scheduled_functions\" (\n id TEXT PRIMARY KEY,\n function_name TEXT NOT NULL,\n function_kind TEXT NOT NULL,\n args_json TEXT NOT NULL,\n status TEXT NOT NULL,\n run_at INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n recurring_name TEXT,\n schedule_json TEXT,\n timezone TEXT,\n misfire_policy TEXT NOT NULL,\n last_run_at INTEGER,\n window_ms INTEGER\n );\n `);\n }\n\n startPolling(): void {\n if (this.timer) {\n return;\n }\n this.timer = setInterval(() => {\n void this.processDueJobs();\n }, this.deps.pollIntervalMs);\n }\n\n stopPolling(): void {\n if (!this.timer) {\n return;\n }\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n async scheduleJob(\n runAt: number,\n reference: FunctionReference<\"mutation\" | \"action\", unknown, unknown>,\n args: JsonObject,\n misfirePolicy: MisfirePolicy,\n namespacePrefix?: string\n ): Promise<string> {\n const id = `${namespacePrefix ?? \"\"}${generateId()}`;\n const now = Date.now();\n await this.deps.driver.run(\n `INSERT INTO \"_scheduled_functions\"\n (id, function_name, function_kind, args_json, status, run_at, created_at, updated_at, recurring_name, schedule_json, timezone, misfire_policy, last_run_at, window_ms)\n VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, NULL, NULL, NULL, ?, NULL, ?)`,\n [\n id,\n reference.name,\n reference.kind,\n stableStringify(args),\n runAt,\n now,\n now,\n misfirePolicy.type,\n misfirePolicy.type === \"windowed\" ? misfirePolicy.windowMs : null\n ]\n );\n this.notifySchedulerJobsChanged();\n return id;\n }\n\n async cancelScheduledJob(id: string): Promise<boolean> {\n const result = await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\"\n SET status = 'cancelled', updated_at = ?\n WHERE id = ? AND status = 'scheduled'`,\n [Date.now(), id]\n );\n if ((result.changes ?? 0) > 0) {\n this.notifySchedulerJobsChanged();\n return true;\n }\n return false;\n }\n\n async updateScheduledJob(options: UpdateScheduledJobOptions): Promise<boolean> {\n const existing = await this.deps.driver.get<{\n status: string;\n run_at: number;\n recurring_name: string | null;\n schedule_json: string | null;\n misfire_policy: string | null;\n window_ms: number | null;\n }>(\n `SELECT status, run_at, recurring_name, schedule_json, misfire_policy, window_ms\n FROM \"_scheduled_functions\" WHERE id = ?`,\n [options.id]\n );\n if (!existing || existing.status !== \"scheduled\") {\n return false;\n }\n const existingSchedule = existing.schedule_json\n ? (JSON.parse(existing.schedule_json) as RecurringJobDefinition[\"schedule\"])\n : undefined;\n const schedule = options.schedule ?? existingSchedule;\n const misfirePolicy =\n options.misfirePolicy ??\n parseMisfirePolicy(existing.misfire_policy ?? \"catch_up\", existing.window_ms);\n const now = Date.now();\n const runAt = options.runAt ?? (schedule ? computeNextRun(schedule, now) : existing.run_at);\n const result = await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\"\n SET args_json = ?, run_at = ?, updated_at = ?, schedule_json = ?, timezone = ?, misfire_policy = ?, window_ms = ?\n WHERE id = ? AND status = 'scheduled'`,\n [\n stableStringify(options.args),\n runAt,\n now,\n schedule ? stableStringify(schedule) : null,\n schedule && \"timezone\" in schedule ? (schedule.timezone ?? null) : null,\n misfirePolicy.type,\n misfirePolicy.type === \"windowed\" ? misfirePolicy.windowMs : null,\n options.id\n ]\n );\n if ((result.changes ?? 0) > 0) {\n this.notifySchedulerJobsChanged();\n return true;\n }\n return false;\n }\n\n async syncRecurringJobs(): Promise<void> {\n for (const job of this.deps.recurringJobs) {\n const id = `recurring:${job.name}`;\n const existing = await this.deps.driver.get<ScheduledJobRow>(\n `SELECT * FROM \"_scheduled_functions\" WHERE id = ?`,\n [id]\n );\n if (existing) {\n continue;\n }\n const nextRunAt = computeNextRun(job.schedule, Date.now());\n await this.deps.driver.run(\n `INSERT INTO \"_scheduled_functions\"\n (id, function_name, function_kind, args_json, status, run_at, created_at, updated_at, recurring_name, schedule_json, timezone, misfire_policy, last_run_at, window_ms)\n VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, ?, ?, ?, ?, NULL, ?)`,\n [\n id,\n job.function.name,\n job.function.kind,\n stableStringify(job.args),\n nextRunAt,\n Date.now(),\n Date.now(),\n job.name,\n stableStringify(job.schedule),\n \"timezone\" in job.schedule ? (job.schedule.timezone ?? null) : null,\n job.misfirePolicy.type,\n job.misfirePolicy.type === \"windowed\"\n ? job.misfirePolicy.windowMs\n : null\n ]\n );\n this.notifySchedulerJobsChanged();\n }\n }\n\n private notifySchedulerJobsChanged(): void {\n this.deps.devtools.notifyScopes([\"scheduler.jobs\"]);\n }\n\n private async processDueJobs(): Promise<void> {\n const now = Date.now();\n const dueJobs = await this.deps.driver.all<ScheduledJobRow>(\n `SELECT * FROM \"_scheduled_functions\" WHERE status = 'scheduled' AND run_at <= ? ORDER BY run_at ASC`,\n [now]\n );\n const executedJobIds: string[] = [];\n const jobExecutions: Array<{\n jobId: string;\n executionId?: string;\n functionName: string;\n functionType: \"mutation\" | \"action\";\n argsPreview?: ReturnType<typeof createDevtoolsPreview>;\n resultPreview?: ReturnType<typeof createDevtoolsPreview>;\n error?: string;\n durationMs?: number;\n }> = [];\n\n for (const job of dueJobs) {\n const misfirePolicy = parseMisfirePolicy(\n job.misfire_policy,\n job.window_ms\n );\n if (!shouldRunMissedJob(job.run_at, now, misfirePolicy)) {\n await this.advanceOrFinalizeJob(job, \"skipped\", now);\n continue;\n }\n\n try {\n const args = JSON.parse(job.args_json) as JsonObject;\n const executionId = generateId();\n const startedAt = Date.now();\n let result: unknown;\n if (job.function_kind === \"mutation\") {\n result = await this.deps.runMutation(\n { kind: \"mutation\", name: job.function_name },\n args,\n { executionId, schedulerJobId: job.id, schedulerRun: true }\n );\n } else {\n result = await this.deps.runAction(\n { kind: \"action\", name: job.function_name },\n args,\n { executionId, schedulerJobId: job.id, schedulerRun: true }\n );\n }\n executedJobIds.push(job.id);\n jobExecutions.push({\n jobId: job.id,\n executionId,\n functionName: job.function_name,\n functionType: job.function_kind === \"mutation\" ? \"mutation\" : \"action\",\n argsPreview: createDevtoolsPreview(args),\n resultPreview: createDevtoolsPreview(result),\n durationMs: Date.now() - startedAt\n });\n await this.advanceOrFinalizeJob(job, \"completed\", now);\n } catch (error) {\n jobExecutions.push({\n jobId: job.id,\n functionName: job.function_name,\n functionType: job.function_kind === \"mutation\" ? \"mutation\" : \"action\",\n error: error instanceof Error ? error.message : String(error)\n });\n await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\" SET status = 'failed', updated_at = ? WHERE id = ?`,\n [Date.now(), job.id]\n );\n this.notifySchedulerJobsChanged();\n this.deps.devtools.emit({\n type: \"log\",\n runtimeId: this.deps.runtimeId,\n level: \"error\",\n message: `Scheduled job ${job.id} failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n timestamp: Date.now()\n });\n }\n }\n\n if (jobExecutions.length > 0) {\n this.deps.devtools.emit({\n type: \"scheduler.tick\",\n runtimeId: this.deps.runtimeId,\n executionId: generateId(),\n executedJobIds,\n jobExecutions,\n timestamp: Date.now()\n });\n this.notifySchedulerJobsChanged();\n }\n }\n\n private async advanceOrFinalizeJob(\n job: ScheduledJobRow,\n terminalStatus: ScheduledJobRow[\"status\"],\n executedAt: number\n ): Promise<void> {\n if (!job.recurring_name || !job.schedule_json) {\n await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\" SET status = ?, updated_at = ?, last_run_at = ? WHERE id = ?`,\n [terminalStatus, executedAt, executedAt, job.id]\n );\n this.notifySchedulerJobsChanged();\n return;\n }\n\n const schedule = readRecurringSchedule(job.schedule_json);\n if (!schedule) {\n await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\" SET status = ?, updated_at = ?, last_run_at = ? WHERE id = ?`,\n [terminalStatus, executedAt, executedAt, job.id]\n );\n this.notifySchedulerJobsChanged();\n return;\n }\n const nextRunAt = computeNextRun(schedule, executedAt + 1);\n await this.deps.driver.run(\n `UPDATE \"_scheduled_functions\"\n SET status = 'scheduled', run_at = ?, updated_at = ?, last_run_at = ?\n WHERE id = ?`,\n [nextRunAt, executedAt, executedAt, job.id]\n );\n this.notifySchedulerJobsChanged();\n }\n}\n\nfunction readRecurringSchedule(\n scheduleJson: string | null\n): RecurringJobDefinition[\"schedule\"] | undefined {\n if (!scheduleJson) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(scheduleJson);\n if (!isRecurringSchedule(parsed)) {\n return undefined;\n }\n return parsed;\n } catch {\n return undefined;\n }\n}\n\nfunction isRecurringSchedule(\n value: unknown\n): value is RecurringJobDefinition[\"schedule\"] {\n if (!value || typeof value !== \"object\" || !(\"type\" in value)) {\n return false;\n }\n const schedule = value as Record<string, unknown>;\n switch (schedule.type) {\n case \"interval\":\n return (\n (schedule.seconds === undefined || typeof schedule.seconds === \"number\") &&\n (schedule.minutes === undefined || typeof schedule.minutes === \"number\") &&\n (schedule.hours === undefined || typeof schedule.hours === \"number\")\n );\n case \"daily\":\n return (\n typeof schedule.hour === \"number\" &&\n typeof schedule.minute === \"number\" &&\n (schedule.timezone === undefined || typeof schedule.timezone === \"string\")\n );\n case \"weekly\":\n return (\n isDayOfWeek(schedule.dayOfWeek) &&\n typeof schedule.hour === \"number\" &&\n typeof schedule.minute === \"number\" &&\n (schedule.timezone === undefined || typeof schedule.timezone === \"string\")\n );\n default:\n return false;\n }\n}\n\nfunction isDayOfWeek(\n value: unknown\n): value is Extract<\n RecurringJobDefinition[\"schedule\"],\n { type: \"weekly\" }\n>[\"dayOfWeek\"] {\n return (\n value === \"sunday\" ||\n value === \"monday\" ||\n value === \"tuesday\" ||\n value === \"wednesday\" ||\n value === \"thursday\" ||\n value === \"friday\" ||\n value === \"saturday\"\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { getTableDefinition, quoteIdentifier, resolveSearchIndexTableName, searchIndexKey, stableStringify, toSearchValue } from "./shared.mjs";
|
|
2
|
+
import { createSchemaSnapshot, describeValidator, deserializeValue, diffSchemaSnapshots, parseSchemaSnapshot, renderCreateSearchIndexStatement, renderMigrationSql, serializeValue } from "../../../../schema/index.js";
|
|
3
|
+
//#region src/runtime/internal/engines/schemaEngine.ts
|
|
4
|
+
var SchemaEngine = class {
|
|
5
|
+
deps;
|
|
6
|
+
disabledSearchIndexes = /* @__PURE__ */ new Set();
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.deps = deps;
|
|
9
|
+
}
|
|
10
|
+
async prepare() {
|
|
11
|
+
await this.deps.driver.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS "_syncore_migrations" (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
applied_at INTEGER NOT NULL,
|
|
15
|
+
sql TEXT NOT NULL
|
|
16
|
+
);
|
|
17
|
+
CREATE TABLE IF NOT EXISTS "_syncore_schema_state" (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
schema_hash TEXT NOT NULL,
|
|
20
|
+
schema_json TEXT NOT NULL,
|
|
21
|
+
updated_at INTEGER NOT NULL
|
|
22
|
+
);
|
|
23
|
+
`);
|
|
24
|
+
try {
|
|
25
|
+
await this.deps.driver.exec(`ALTER TABLE "_syncore_schema_state" ADD COLUMN schema_json TEXT NOT NULL DEFAULT '{}'`);
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
async applySchema() {
|
|
29
|
+
const nextSnapshot = createSchemaSnapshot(this.deps.schema);
|
|
30
|
+
const stateRow = await this.deps.driver.get(`SELECT schema_hash, schema_json FROM "_syncore_schema_state" WHERE id = 'current'`);
|
|
31
|
+
let previousSnapshot = null;
|
|
32
|
+
if (stateRow?.schema_json && stateRow.schema_json !== "{}") try {
|
|
33
|
+
previousSnapshot = parseSchemaSnapshot(stateRow.schema_json);
|
|
34
|
+
} catch {
|
|
35
|
+
previousSnapshot = null;
|
|
36
|
+
}
|
|
37
|
+
const plan = diffSchemaSnapshots(previousSnapshot, nextSnapshot);
|
|
38
|
+
if (plan.destructiveChanges.length > 0) throw new Error(`Syncore detected destructive schema changes that require a manual migration:\n${plan.destructiveChanges.join("\n")}`);
|
|
39
|
+
for (const warning of plan.warnings) this.deps.devtools.emit({
|
|
40
|
+
type: "log",
|
|
41
|
+
runtimeId: this.deps.runtimeId,
|
|
42
|
+
level: "warn",
|
|
43
|
+
message: warning,
|
|
44
|
+
timestamp: Date.now()
|
|
45
|
+
});
|
|
46
|
+
for (const statement of plan.statements) {
|
|
47
|
+
const searchKey = this.findSearchIndexKeyForStatement(statement);
|
|
48
|
+
try {
|
|
49
|
+
await this.deps.driver.exec(statement);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (searchKey) {
|
|
52
|
+
this.disabledSearchIndexes.add(searchKey);
|
|
53
|
+
this.deps.devtools.emit({
|
|
54
|
+
type: "log",
|
|
55
|
+
runtimeId: this.deps.runtimeId,
|
|
56
|
+
level: "warn",
|
|
57
|
+
message: `FTS5 unavailable for ${searchKey}; falling back to LIKE search.`,
|
|
58
|
+
timestamp: Date.now()
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (plan.statements.length > 0 || plan.warnings.length > 0) {
|
|
66
|
+
const migrationSql = renderMigrationSql(plan, { title: "Syncore automatic schema reconciliation" });
|
|
67
|
+
await this.deps.driver.run(`INSERT OR REPLACE INTO "_syncore_migrations" (id, applied_at, sql) VALUES (?, ?, ?)`, [
|
|
68
|
+
nextSnapshot.hash,
|
|
69
|
+
Date.now(),
|
|
70
|
+
migrationSql
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
await this.deps.driver.run(`INSERT INTO "_syncore_schema_state" (id, schema_hash, schema_json, updated_at)
|
|
74
|
+
VALUES ('current', ?, ?, ?)
|
|
75
|
+
ON CONFLICT(id) DO UPDATE SET schema_hash = excluded.schema_hash, schema_json = excluded.schema_json, updated_at = excluded.updated_at`, [
|
|
76
|
+
nextSnapshot.hash,
|
|
77
|
+
stableStringify(nextSnapshot),
|
|
78
|
+
Date.now()
|
|
79
|
+
]);
|
|
80
|
+
for (const tableName of this.deps.schema.tableNames()) {
|
|
81
|
+
const table = this.getTableDefinition(tableName);
|
|
82
|
+
for (const searchIndex of table.searchIndexes) {
|
|
83
|
+
const key = searchIndexKey(tableName, searchIndex.name);
|
|
84
|
+
try {
|
|
85
|
+
await this.deps.driver.exec(renderCreateSearchIndexStatement(tableName, searchIndex));
|
|
86
|
+
this.disabledSearchIndexes.delete(key);
|
|
87
|
+
} catch {
|
|
88
|
+
const alreadyDisabled = this.disabledSearchIndexes.has(key);
|
|
89
|
+
this.disabledSearchIndexes.add(key);
|
|
90
|
+
if (!alreadyDisabled) this.deps.devtools.emit({
|
|
91
|
+
type: "log",
|
|
92
|
+
runtimeId: this.deps.runtimeId,
|
|
93
|
+
level: "warn",
|
|
94
|
+
message: `FTS5 unavailable for ${key}; falling back to LIKE search.`,
|
|
95
|
+
timestamp: Date.now()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getTableDefinition(tableName) {
|
|
102
|
+
return getTableDefinition(this.deps.schema, tableName);
|
|
103
|
+
}
|
|
104
|
+
isSearchIndexDisabled(tableName, indexName) {
|
|
105
|
+
return this.disabledSearchIndexes.has(searchIndexKey(tableName, indexName));
|
|
106
|
+
}
|
|
107
|
+
validateDocument(tableName, value) {
|
|
108
|
+
const validator = this.getTableDefinition(tableName).validator;
|
|
109
|
+
const parsed = validator.parse(value);
|
|
110
|
+
return this.ensureRecordDocument(serializeValue(validator, parsed), "Validated Syncore document payload must serialize to a JSON object.");
|
|
111
|
+
}
|
|
112
|
+
deserializeDocument(tableName, row) {
|
|
113
|
+
const validator = this.getTableDefinition(tableName).validator;
|
|
114
|
+
const payload = this.parseStoredDocument(row._json);
|
|
115
|
+
return {
|
|
116
|
+
...this.ensureRecordDocument(deserializeValue(validator, payload), "Stored Syncore document payload must deserialize to a JSON object."),
|
|
117
|
+
_id: row._id,
|
|
118
|
+
_creationTime: row._creationTime
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async syncSearchIndexes(tableName, row) {
|
|
122
|
+
const table = this.getTableDefinition(tableName);
|
|
123
|
+
if (table.searchIndexes.length === 0) return;
|
|
124
|
+
const payload = this.parseStoredDocument(row._json);
|
|
125
|
+
for (const searchIndex of table.searchIndexes) {
|
|
126
|
+
if (this.isSearchIndexDisabled(tableName, searchIndex.name)) continue;
|
|
127
|
+
const searchTable = resolveSearchIndexTableName(tableName, searchIndex.name);
|
|
128
|
+
await this.deps.driver.run(`DELETE FROM ${quoteIdentifier(searchTable)} WHERE _id = ?`, [row._id]);
|
|
129
|
+
await this.deps.driver.run(`INSERT INTO ${quoteIdentifier(searchTable)} (_id, search_value) VALUES (?, ?)`, [row._id, toSearchValue(payload[searchIndex.searchField])]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
parseStoredDocument(json) {
|
|
133
|
+
const value = JSON.parse(json);
|
|
134
|
+
return this.ensureRecordDocument(value, "Stored Syncore document payload must be a JSON object.");
|
|
135
|
+
}
|
|
136
|
+
ensureRecordDocument(value, message) {
|
|
137
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(message);
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
async removeSearchIndexes(tableName, id) {
|
|
141
|
+
const table = this.getTableDefinition(tableName);
|
|
142
|
+
for (const searchIndex of table.searchIndexes) {
|
|
143
|
+
if (this.isSearchIndexDisabled(tableName, searchIndex.name)) continue;
|
|
144
|
+
await this.deps.driver.run(`DELETE FROM ${quoteIdentifier(resolveSearchIndexTableName(tableName, searchIndex.name))} WHERE _id = ?`, [id]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async getSchemaTablesForDevtools() {
|
|
148
|
+
const tables = [];
|
|
149
|
+
for (const name of this.deps.schema.tableNames()) {
|
|
150
|
+
const table = this.getTableDefinition(name);
|
|
151
|
+
const validator = table.validator;
|
|
152
|
+
const validatorDesc = describeValidator(validator);
|
|
153
|
+
const fields = validatorDesc.kind === "object" ? Object.entries(validatorDesc.shape).map(([fieldName, fieldDesc]) => {
|
|
154
|
+
const field = fieldDesc;
|
|
155
|
+
return {
|
|
156
|
+
name: fieldName,
|
|
157
|
+
type: field.validator.kind,
|
|
158
|
+
optional: field.optional,
|
|
159
|
+
...field.validator.kind === "id" && field.validator.tableName ? { referenceTable: field.validator.tableName } : {}
|
|
160
|
+
};
|
|
161
|
+
}) : [];
|
|
162
|
+
fields.unshift({
|
|
163
|
+
name: "_id",
|
|
164
|
+
type: "string",
|
|
165
|
+
optional: false
|
|
166
|
+
}, {
|
|
167
|
+
name: "_creationTime",
|
|
168
|
+
type: "number",
|
|
169
|
+
optional: false
|
|
170
|
+
});
|
|
171
|
+
const documentCount = await this.deps.driver.get(`SELECT COUNT(*) as count FROM ${quoteIdentifier(name)}`).then((countRow) => countRow?.count ?? 0).catch(() => 0);
|
|
172
|
+
tables.push({
|
|
173
|
+
name,
|
|
174
|
+
...table.options.tableName ? { displayName: table.options.tableName } : {},
|
|
175
|
+
owner: table.options.componentPath ? "component" : "root",
|
|
176
|
+
...table.options.componentPath ? { componentPath: table.options.componentPath } : {},
|
|
177
|
+
...table.options.componentName ? { componentName: table.options.componentName } : {},
|
|
178
|
+
fields,
|
|
179
|
+
indexes: table.indexes.map((index) => ({
|
|
180
|
+
name: index.name,
|
|
181
|
+
fields: index.fields,
|
|
182
|
+
unique: false
|
|
183
|
+
})),
|
|
184
|
+
documentCount
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return tables;
|
|
188
|
+
}
|
|
189
|
+
findSearchIndexKeyForStatement(statement) {
|
|
190
|
+
for (const tableName of this.deps.schema.tableNames()) {
|
|
191
|
+
const table = this.getTableDefinition(tableName);
|
|
192
|
+
for (const searchIndex of table.searchIndexes) if (statement === renderCreateSearchIndexStatement(tableName, searchIndex)) return searchIndexKey(tableName, searchIndex.name);
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
198
|
+
export { SchemaEngine };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=schemaEngine.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaEngine.mjs","names":[],"sources":["../../../../src/runtime/internal/engines/schemaEngine.ts"],"sourcesContent":["import {\n describeValidator,\n createSchemaSnapshot,\n deserializeValue,\n diffSchemaSnapshots,\n parseSchemaSnapshot,\n renderCreateSearchIndexStatement,\n renderMigrationSql,\n serializeValue,\n type Validator\n} from \"@syncore/schema\";\nimport type {\n TableDefinition\n} from \"@syncore/schema\";\nimport type {\n DevtoolsLiveQuerySnapshot,\n JsonObject,\n SyncoreDataModel,\n SyncoreSqlDriver\n} from \"../../runtime.js\";\nimport {\n getTableDefinition,\n quoteIdentifier,\n resolveSearchIndexTableName,\n searchIndexKey,\n stableStringify,\n toSearchValue,\n type DatabaseRow\n} from \"./shared.js\";\nimport { type DevtoolsEngine } from \"./devtoolsEngine.js\";\n\ntype RecordDocument = Record<string, unknown>;\ntype StructuredValidator = Validator<RecordDocument, RecordDocument, string>;\ntype StructuredTableDefinition = TableDefinition<StructuredValidator>;\ntype SystemDocumentFields = {\n _id: string;\n _creationTime: number;\n};\ntype StructuredRuntimeDocument = RecordDocument & SystemDocumentFields;\n\ntype SchemaEngineDeps<TSchema extends SyncoreDataModel> = {\n schema: TSchema;\n driver: SyncoreSqlDriver;\n runtimeId: string;\n devtools: DevtoolsEngine;\n};\n\nexport class SchemaEngine<\n TSchema extends SyncoreDataModel\n> {\n private readonly disabledSearchIndexes = new Set<string>();\n\n constructor(private readonly deps: SchemaEngineDeps<TSchema>) {}\n\n async prepare(): Promise<void> {\n await this.deps.driver.exec(`\n CREATE TABLE IF NOT EXISTS \"_syncore_migrations\" (\n id TEXT PRIMARY KEY,\n applied_at INTEGER NOT NULL,\n sql TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS \"_syncore_schema_state\" (\n id TEXT PRIMARY KEY,\n schema_hash TEXT NOT NULL,\n schema_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n );\n `);\n try {\n await this.deps.driver.exec(\n `ALTER TABLE \"_syncore_schema_state\" ADD COLUMN schema_json TEXT NOT NULL DEFAULT '{}'`\n );\n } catch {\n // Column already exists.\n }\n }\n\n async applySchema(): Promise<void> {\n const nextSnapshot = createSchemaSnapshot(this.deps.schema);\n const stateRow = await this.deps.driver.get<{\n schema_hash: string;\n schema_json: string;\n }>(\n `SELECT schema_hash, schema_json FROM \"_syncore_schema_state\" WHERE id = 'current'`\n );\n let previousSnapshot = null;\n if (stateRow?.schema_json && stateRow.schema_json !== \"{}\") {\n try {\n previousSnapshot = parseSchemaSnapshot(stateRow.schema_json);\n } catch {\n previousSnapshot = null;\n }\n }\n const plan = diffSchemaSnapshots(previousSnapshot, nextSnapshot);\n\n if (plan.destructiveChanges.length > 0) {\n throw new Error(\n `Syncore detected destructive schema changes that require a manual migration:\\n${plan.destructiveChanges.join(\n \"\\n\"\n )}`\n );\n }\n\n for (const warning of plan.warnings) {\n this.deps.devtools.emit({\n type: \"log\",\n runtimeId: this.deps.runtimeId,\n level: \"warn\",\n message: warning,\n timestamp: Date.now()\n });\n }\n\n for (const statement of plan.statements) {\n const searchKey = this.findSearchIndexKeyForStatement(statement);\n try {\n await this.deps.driver.exec(statement);\n } catch (error) {\n if (searchKey) {\n this.disabledSearchIndexes.add(searchKey);\n this.deps.devtools.emit({\n type: \"log\",\n runtimeId: this.deps.runtimeId,\n level: \"warn\",\n message: `FTS5 unavailable for ${searchKey}; falling back to LIKE search.`,\n timestamp: Date.now()\n });\n continue;\n }\n throw error;\n }\n }\n\n if (plan.statements.length > 0 || plan.warnings.length > 0) {\n const migrationSql = renderMigrationSql(plan, {\n title: \"Syncore automatic schema reconciliation\"\n });\n await this.deps.driver.run(\n `INSERT OR REPLACE INTO \"_syncore_migrations\" (id, applied_at, sql) VALUES (?, ?, ?)`,\n [nextSnapshot.hash, Date.now(), migrationSql]\n );\n }\n\n await this.deps.driver.run(\n `INSERT INTO \"_syncore_schema_state\" (id, schema_hash, schema_json, updated_at)\n VALUES ('current', ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET schema_hash = excluded.schema_hash, schema_json = excluded.schema_json, updated_at = excluded.updated_at`,\n [nextSnapshot.hash, stableStringify(nextSnapshot), Date.now()]\n );\n\n for (const tableName of this.deps.schema.tableNames()) {\n const table = this.getTableDefinition(tableName);\n for (const searchIndex of table.searchIndexes) {\n const key = searchIndexKey(tableName, searchIndex.name);\n try {\n await this.deps.driver.exec(\n renderCreateSearchIndexStatement(tableName, searchIndex)\n );\n this.disabledSearchIndexes.delete(key);\n } catch {\n const alreadyDisabled = this.disabledSearchIndexes.has(key);\n this.disabledSearchIndexes.add(key);\n if (!alreadyDisabled) {\n this.deps.devtools.emit({\n type: \"log\",\n runtimeId: this.deps.runtimeId,\n level: \"warn\",\n message: `FTS5 unavailable for ${key}; falling back to LIKE search.`,\n timestamp: Date.now()\n });\n }\n }\n }\n }\n }\n\n getTableDefinition(\n tableName: string\n ): StructuredTableDefinition {\n return getTableDefinition(this.deps.schema, tableName);\n }\n\n isSearchIndexDisabled(tableName: string, indexName: string): boolean {\n return this.disabledSearchIndexes.has(searchIndexKey(tableName, indexName));\n }\n\n validateDocument(tableName: string, value: JsonObject): JsonObject {\n const table = this.getTableDefinition(tableName);\n const validator: StructuredValidator = table.validator;\n const parsed = validator.parse(value);\n return this.ensureRecordDocument(\n serializeValue(validator, parsed),\n \"Validated Syncore document payload must serialize to a JSON object.\"\n );\n }\n\n deserializeDocument<TDocument>(tableName: string, row: DatabaseRow): TDocument {\n const table = this.getTableDefinition(tableName);\n const validator: StructuredValidator = table.validator;\n const payload = this.parseStoredDocument(row._json);\n const deserialized = this.ensureRecordDocument(\n deserializeValue(validator, payload),\n \"Stored Syncore document payload must deserialize to a JSON object.\"\n );\n const document: StructuredRuntimeDocument = {\n ...deserialized,\n _id: row._id,\n _creationTime: row._creationTime\n };\n return document as TDocument;\n }\n\n async syncSearchIndexes(\n tableName: string,\n row: DatabaseRow\n ): Promise<void> {\n const table = this.getTableDefinition(tableName);\n if (table.searchIndexes.length === 0) {\n return;\n }\n const payload = this.parseStoredDocument(row._json);\n for (const searchIndex of table.searchIndexes) {\n if (this.isSearchIndexDisabled(tableName, searchIndex.name)) {\n continue;\n }\n const searchTable = resolveSearchIndexTableName(tableName, searchIndex.name);\n await this.deps.driver.run(\n `DELETE FROM ${quoteIdentifier(searchTable)} WHERE _id = ?`,\n [row._id]\n );\n await this.deps.driver.run(\n `INSERT INTO ${quoteIdentifier(searchTable)} (_id, search_value) VALUES (?, ?)`,\n [row._id, toSearchValue(payload[searchIndex.searchField])]\n );\n }\n }\n\n private parseStoredDocument(json: string): RecordDocument {\n const value = JSON.parse(json) as unknown;\n return this.ensureRecordDocument(\n value,\n \"Stored Syncore document payload must be a JSON object.\"\n );\n }\n\n private ensureRecordDocument(\n value: unknown,\n message: string\n ): RecordDocument {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(message);\n }\n return value as RecordDocument;\n }\n\n async removeSearchIndexes(tableName: string, id: string): Promise<void> {\n const table = this.getTableDefinition(tableName);\n for (const searchIndex of table.searchIndexes) {\n if (this.isSearchIndexDisabled(tableName, searchIndex.name)) {\n continue;\n }\n await this.deps.driver.run(\n `DELETE FROM ${quoteIdentifier(\n resolveSearchIndexTableName(tableName, searchIndex.name)\n )} WHERE _id = ?`,\n [id]\n );\n }\n }\n\n async getSchemaTablesForDevtools(): Promise<\n DevtoolsLiveQuerySnapshot[\"schemaTables\"]\n > {\n const tables = [] as DevtoolsLiveQuerySnapshot[\"schemaTables\"];\n\n for (const name of this.deps.schema.tableNames()) {\n const table = this.getTableDefinition(name);\n const validator: StructuredValidator = table.validator;\n const validatorDesc = describeValidator(validator);\n const fields =\n validatorDesc.kind === \"object\"\n ? Object.entries(validatorDesc.shape).map(\n ([fieldName, fieldDesc]) => {\n const field = fieldDesc as {\n validator: { kind: string; tableName?: string };\n optional: boolean;\n };\n return {\n name: fieldName,\n type: field.validator.kind,\n optional: field.optional,\n ...(field.validator.kind === \"id\" && field.validator.tableName\n ? { referenceTable: field.validator.tableName }\n : {})\n };\n }\n )\n : [];\n\n fields.unshift(\n { name: \"_id\", type: \"string\", optional: false },\n { name: \"_creationTime\", type: \"number\", optional: false }\n );\n\n const documentCount = await this.deps.driver\n .get<{ count: number }>(\n `SELECT COUNT(*) as count FROM ${quoteIdentifier(name)}`\n )\n .then((countRow) => countRow?.count ?? 0)\n .catch(() => 0);\n\n tables.push({\n name,\n ...(table.options.tableName ? { displayName: table.options.tableName } : {}),\n owner: table.options.componentPath ? \"component\" : \"root\",\n ...(table.options.componentPath\n ? { componentPath: table.options.componentPath }\n : {}),\n ...(table.options.componentName\n ? { componentName: table.options.componentName }\n : {}),\n fields,\n indexes: table.indexes.map((index) => ({\n name: index.name,\n fields: index.fields,\n unique: false\n })),\n documentCount\n });\n }\n\n return tables;\n }\n\n private findSearchIndexKeyForStatement(statement: string): string | null {\n for (const tableName of this.deps.schema.tableNames()) {\n const table = this.getTableDefinition(tableName);\n for (const searchIndex of table.searchIndexes) {\n if (\n statement === renderCreateSearchIndexStatement(tableName, searchIndex)\n ) {\n return searchIndexKey(tableName, searchIndex.name);\n }\n }\n }\n return null;\n }\n}\n"],"mappings":"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"}
|