syncorejs 0.1.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/README.md +30 -0
- package/dist/_vendor/core/_virtual/_rolldown/runtime.mjs +27 -0
- package/dist/_vendor/core/cli.d.mts +5 -0
- package/dist/_vendor/core/cli.d.mts.map +1 -0
- package/dist/_vendor/core/cli.mjs +1196 -0
- package/dist/_vendor/core/cli.mjs.map +1 -0
- package/dist/_vendor/core/index.d.mts +7 -0
- package/dist/_vendor/core/index.mjs +25 -0
- package/dist/_vendor/core/index.mjs.map +1 -0
- package/dist/_vendor/core/runtime/devtools.d.mts +15 -0
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/devtools.mjs +300 -0
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -0
- package/dist/_vendor/core/runtime/functions.d.mts +123 -0
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/functions.mjs +71 -0
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -0
- package/dist/_vendor/core/runtime/id.d.mts +13 -0
- package/dist/_vendor/core/runtime/id.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/id.mjs +28 -0
- package/dist/_vendor/core/runtime/id.mjs.map +1 -0
- package/dist/_vendor/core/runtime/runtime.d.mts +370 -0
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/runtime.mjs +1143 -0
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -0
- package/dist/_vendor/devtools-protocol/index.d.ts +230 -0
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -0
- package/dist/_vendor/devtools-protocol/index.js +0 -0
- package/dist/_vendor/next/config.d.ts +17 -0
- package/dist/_vendor/next/config.d.ts.map +1 -0
- package/dist/_vendor/next/config.js +73 -0
- package/dist/_vendor/next/config.js.map +1 -0
- package/dist/_vendor/next/index.d.ts +80 -0
- package/dist/_vendor/next/index.d.ts.map +1 -0
- package/dist/_vendor/next/index.js +81 -0
- package/dist/_vendor/next/index.js.map +1 -0
- package/dist/_vendor/platform-expo/index.d.ts +97 -0
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -0
- package/dist/_vendor/platform-expo/index.js +197 -0
- package/dist/_vendor/platform-expo/index.js.map +1 -0
- package/dist/_vendor/platform-expo/react.d.ts +26 -0
- package/dist/_vendor/platform-expo/react.d.ts.map +1 -0
- package/dist/_vendor/platform-expo/react.js +30 -0
- package/dist/_vendor/platform-expo/react.js.map +1 -0
- package/dist/_vendor/platform-node/index.d.mts +145 -0
- package/dist/_vendor/platform-node/index.d.mts.map +1 -0
- package/dist/_vendor/platform-node/index.mjs +405 -0
- package/dist/_vendor/platform-node/index.mjs.map +1 -0
- package/dist/_vendor/platform-node/ipc-react.d.mts +25 -0
- package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -0
- package/dist/_vendor/platform-node/ipc-react.mjs +21 -0
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -0
- package/dist/_vendor/platform-node/ipc.d.mts +75 -0
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -0
- package/dist/_vendor/platform-node/ipc.mjs +343 -0
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -0
- package/dist/_vendor/platform-web/index.d.ts +123 -0
- package/dist/_vendor/platform-web/index.d.ts.map +1 -0
- package/dist/_vendor/platform-web/index.js +309 -0
- package/dist/_vendor/platform-web/index.js.map +1 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts +25 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -0
- package/dist/_vendor/platform-web/indexeddb.js +125 -0
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -0
- package/dist/_vendor/platform-web/opfs.d.ts +27 -0
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -0
- package/dist/_vendor/platform-web/opfs.js +146 -0
- package/dist/_vendor/platform-web/opfs.js.map +1 -0
- package/dist/_vendor/platform-web/persistence.d.ts +27 -0
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -0
- package/dist/_vendor/platform-web/persistence.js +23 -0
- package/dist/_vendor/platform-web/persistence.js.map +1 -0
- package/dist/_vendor/platform-web/react.d.ts +35 -0
- package/dist/_vendor/platform-web/react.d.ts.map +1 -0
- package/dist/_vendor/platform-web/react.js +42 -0
- package/dist/_vendor/platform-web/react.js.map +1 -0
- package/dist/_vendor/platform-web/sqljs.js +133 -0
- package/dist/_vendor/platform-web/sqljs.js.map +1 -0
- package/dist/_vendor/platform-web/worker.d.ts +78 -0
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -0
- package/dist/_vendor/platform-web/worker.js +307 -0
- package/dist/_vendor/platform-web/worker.js.map +1 -0
- package/dist/_vendor/react/index.d.ts +58 -0
- package/dist/_vendor/react/index.d.ts.map +1 -0
- package/dist/_vendor/react/index.js +151 -0
- package/dist/_vendor/react/index.js.map +1 -0
- package/dist/_vendor/schema/definition.d.ts +98 -0
- package/dist/_vendor/schema/definition.d.ts.map +1 -0
- package/dist/_vendor/schema/definition.js +84 -0
- package/dist/_vendor/schema/definition.js.map +1 -0
- package/dist/_vendor/schema/index.d.ts +4 -0
- package/dist/_vendor/schema/index.js +4 -0
- package/dist/_vendor/schema/planner.d.ts +42 -0
- package/dist/_vendor/schema/planner.d.ts.map +1 -0
- package/dist/_vendor/schema/planner.js +131 -0
- package/dist/_vendor/schema/planner.js.map +1 -0
- package/dist/_vendor/schema/validators.d.ts +194 -0
- package/dist/_vendor/schema/validators.d.ts.map +1 -0
- package/dist/_vendor/schema/validators.js +158 -0
- package/dist/_vendor/schema/validators.js.map +1 -0
- package/dist/_vendor/svelte/index.d.ts +43 -0
- package/dist/_vendor/svelte/index.d.ts.map +1 -0
- package/dist/_vendor/svelte/index.js +75 -0
- package/dist/_vendor/svelte/index.js.map +1 -0
- package/dist/browser-react.d.ts +2 -0
- package/dist/browser-react.js +2 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +10 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/src/cli.d.ts +5 -0
- package/dist/core/src/cli.d.ts.map +1 -0
- package/dist/core/src/cli.js +1196 -0
- package/dist/core/src/cli.js.map +1 -0
- package/dist/core/src/index.js +7 -0
- package/dist/core/src/runtime/devtools.d.ts +7 -0
- package/dist/core/src/runtime/devtools.d.ts.map +1 -0
- package/dist/core/src/runtime/devtools.js +300 -0
- package/dist/core/src/runtime/devtools.js.map +1 -0
- package/dist/core/src/runtime/functions.d.ts +123 -0
- package/dist/core/src/runtime/functions.d.ts.map +1 -0
- package/dist/core/src/runtime/functions.js +71 -0
- package/dist/core/src/runtime/functions.js.map +1 -0
- package/dist/core/src/runtime/id.d.ts +13 -0
- package/dist/core/src/runtime/id.d.ts.map +1 -0
- package/dist/core/src/runtime/id.js +28 -0
- package/dist/core/src/runtime/id.js.map +1 -0
- package/dist/core/src/runtime/runtime.d.ts +371 -0
- package/dist/core/src/runtime/runtime.d.ts.map +1 -0
- package/dist/core/src/runtime/runtime.js +1143 -0
- package/dist/core/src/runtime/runtime.js.map +1 -0
- package/dist/devtools-protocol/src/index.d.ts +201 -0
- package/dist/devtools-protocol/src/index.d.ts.map +1 -0
- package/dist/expo-react.d.ts +2 -0
- package/dist/expo-react.js +2 -0
- package/dist/expo.d.ts +2 -0
- package/dist/expo.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/next/src/config.d.ts +17 -0
- package/dist/next/src/config.d.ts.map +1 -0
- package/dist/next/src/config.js +73 -0
- package/dist/next/src/config.js.map +1 -0
- package/dist/next/src/index.d.ts +80 -0
- package/dist/next/src/index.d.ts.map +1 -0
- package/dist/next/src/index.js +82 -0
- package/dist/next/src/index.js.map +1 -0
- package/dist/next-config.d.ts +2 -0
- package/dist/next-config.js +2 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +3 -0
- package/dist/node-ipc-react.d.ts +2 -0
- package/dist/node-ipc-react.js +2 -0
- package/dist/node-ipc.d.ts +2 -0
- package/dist/node-ipc.js +2 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.js +3 -0
- package/dist/platform-expo/src/index.d.ts +96 -0
- package/dist/platform-expo/src/index.d.ts.map +1 -0
- package/dist/platform-expo/src/index.js +198 -0
- package/dist/platform-expo/src/index.js.map +1 -0
- package/dist/platform-expo/src/react.d.ts +26 -0
- package/dist/platform-expo/src/react.d.ts.map +1 -0
- package/dist/platform-expo/src/react.js +30 -0
- package/dist/platform-expo/src/react.js.map +1 -0
- package/dist/platform-node/src/index.d.ts +145 -0
- package/dist/platform-node/src/index.d.ts.map +1 -0
- package/dist/platform-node/src/index.js +407 -0
- package/dist/platform-node/src/index.js.map +1 -0
- package/dist/platform-node/src/ipc-react.d.ts +25 -0
- package/dist/platform-node/src/ipc-react.d.ts.map +1 -0
- package/dist/platform-node/src/ipc-react.js +21 -0
- package/dist/platform-node/src/ipc-react.js.map +1 -0
- package/dist/platform-node/src/ipc.d.ts +76 -0
- package/dist/platform-node/src/ipc.d.ts.map +1 -0
- package/dist/platform-node/src/ipc.js +344 -0
- package/dist/platform-node/src/ipc.js.map +1 -0
- package/dist/platform-web/src/index.d.ts +106 -0
- package/dist/platform-web/src/index.d.ts.map +1 -0
- package/dist/platform-web/src/index.js +311 -0
- package/dist/platform-web/src/index.js.map +1 -0
- package/dist/platform-web/src/indexeddb.js +125 -0
- package/dist/platform-web/src/indexeddb.js.map +1 -0
- package/dist/platform-web/src/opfs.js +146 -0
- package/dist/platform-web/src/opfs.js.map +1 -0
- package/dist/platform-web/src/persistence.d.ts +20 -0
- package/dist/platform-web/src/persistence.d.ts.map +1 -0
- package/dist/platform-web/src/persistence.js +23 -0
- package/dist/platform-web/src/persistence.js.map +1 -0
- package/dist/platform-web/src/react.d.ts +35 -0
- package/dist/platform-web/src/react.d.ts.map +1 -0
- package/dist/platform-web/src/react.js +42 -0
- package/dist/platform-web/src/react.js.map +1 -0
- package/dist/platform-web/src/sqljs.js +133 -0
- package/dist/platform-web/src/sqljs.js.map +1 -0
- package/dist/platform-web/src/worker.d.ts +79 -0
- package/dist/platform-web/src/worker.d.ts.map +1 -0
- package/dist/platform-web/src/worker.js +308 -0
- package/dist/platform-web/src/worker.js.map +1 -0
- package/dist/react/src/index.d.ts +59 -0
- package/dist/react/src/index.d.ts.map +1 -0
- package/dist/react/src/index.js +151 -0
- package/dist/react/src/index.js.map +1 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +2 -0
- package/dist/schema/src/definition.d.ts +98 -0
- package/dist/schema/src/definition.d.ts.map +1 -0
- package/dist/schema/src/definition.js +84 -0
- package/dist/schema/src/definition.js.map +1 -0
- package/dist/schema/src/planner.d.ts +42 -0
- package/dist/schema/src/planner.d.ts.map +1 -0
- package/dist/schema/src/planner.js +131 -0
- package/dist/schema/src/planner.js.map +1 -0
- package/dist/schema/src/validators.d.ts +194 -0
- package/dist/schema/src/validators.d.ts.map +1 -0
- package/dist/schema/src/validators.js +158 -0
- package/dist/schema/src/validators.js.map +1 -0
- package/dist/svelte/src/index.d.ts +44 -0
- package/dist/svelte/src/index.d.ts.map +1 -0
- package/dist/svelte/src/index.js +75 -0
- package/dist/svelte/src/index.js.map +1 -0
- package/dist/svelte.d.ts +2 -0
- package/dist/svelte.js +2 -0
- package/package.json +152 -0
|
@@ -0,0 +1,1143 @@
|
|
|
1
|
+
import { generateId } from "./id.mjs";
|
|
2
|
+
import { createSchemaSnapshot, diffSchemaSnapshots, parseSchemaSnapshot, renderCreateSearchIndexStatement, renderMigrationSql, searchIndexTableName } from "../../schema/index.js";
|
|
3
|
+
import { fromZonedTime, toZonedTime } from "date-fns-tz";
|
|
4
|
+
//#region src/runtime/runtime.ts
|
|
5
|
+
const DEFAULT_MISFIRE_POLICY = { type: "catch_up" };
|
|
6
|
+
var RuntimeFilterBuilder = class {
|
|
7
|
+
eq(field, value) {
|
|
8
|
+
return {
|
|
9
|
+
type: "condition",
|
|
10
|
+
condition: {
|
|
11
|
+
field,
|
|
12
|
+
operator: "=",
|
|
13
|
+
value
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
gt(field, value) {
|
|
18
|
+
return {
|
|
19
|
+
type: "condition",
|
|
20
|
+
condition: {
|
|
21
|
+
field,
|
|
22
|
+
operator: ">",
|
|
23
|
+
value
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
gte(field, value) {
|
|
28
|
+
return {
|
|
29
|
+
type: "condition",
|
|
30
|
+
condition: {
|
|
31
|
+
field,
|
|
32
|
+
operator: ">=",
|
|
33
|
+
value
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
lt(field, value) {
|
|
38
|
+
return {
|
|
39
|
+
type: "condition",
|
|
40
|
+
condition: {
|
|
41
|
+
field,
|
|
42
|
+
operator: "<",
|
|
43
|
+
value
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
lte(field, value) {
|
|
48
|
+
return {
|
|
49
|
+
type: "condition",
|
|
50
|
+
condition: {
|
|
51
|
+
field,
|
|
52
|
+
operator: "<=",
|
|
53
|
+
value
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
and(...expressions) {
|
|
58
|
+
return {
|
|
59
|
+
type: "and",
|
|
60
|
+
expressions
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
or(...expressions) {
|
|
64
|
+
return {
|
|
65
|
+
type: "or",
|
|
66
|
+
expressions
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var RuntimeIndexRangeBuilder = class {
|
|
71
|
+
conditions = [];
|
|
72
|
+
eq(field, value) {
|
|
73
|
+
this.conditions.push({
|
|
74
|
+
field,
|
|
75
|
+
operator: "=",
|
|
76
|
+
value
|
|
77
|
+
});
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
gt(field, value) {
|
|
81
|
+
this.conditions.push({
|
|
82
|
+
field,
|
|
83
|
+
operator: ">",
|
|
84
|
+
value
|
|
85
|
+
});
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
gte(field, value) {
|
|
89
|
+
this.conditions.push({
|
|
90
|
+
field,
|
|
91
|
+
operator: ">=",
|
|
92
|
+
value
|
|
93
|
+
});
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
lt(field, value) {
|
|
97
|
+
this.conditions.push({
|
|
98
|
+
field,
|
|
99
|
+
operator: "<",
|
|
100
|
+
value
|
|
101
|
+
});
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
lte(field, value) {
|
|
105
|
+
this.conditions.push({
|
|
106
|
+
field,
|
|
107
|
+
operator: "<=",
|
|
108
|
+
value
|
|
109
|
+
});
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
build() {
|
|
113
|
+
return [...this.conditions];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var RuntimeSearchIndexBuilder = class {
|
|
117
|
+
searchField;
|
|
118
|
+
searchText;
|
|
119
|
+
filters = [];
|
|
120
|
+
search(field, value) {
|
|
121
|
+
this.searchField = field;
|
|
122
|
+
this.searchText = value;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
eq(field, value) {
|
|
126
|
+
this.filters.push({
|
|
127
|
+
field,
|
|
128
|
+
operator: "=",
|
|
129
|
+
value
|
|
130
|
+
});
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
build() {
|
|
134
|
+
if (!this.searchField || !this.searchText) throw new Error("Search queries require a search field and search text.");
|
|
135
|
+
return {
|
|
136
|
+
searchField: this.searchField,
|
|
137
|
+
searchText: this.searchText,
|
|
138
|
+
filters: [...this.filters]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var RuntimeQueryBuilder = class {
|
|
143
|
+
orderDirection = "asc";
|
|
144
|
+
source = { type: "table" };
|
|
145
|
+
filterExpression;
|
|
146
|
+
constructor(executeQuery, tableName, dependencyCollector) {
|
|
147
|
+
this.executeQuery = executeQuery;
|
|
148
|
+
this.tableName = tableName;
|
|
149
|
+
this.dependencyCollector = dependencyCollector;
|
|
150
|
+
}
|
|
151
|
+
withIndex(indexName, builder) {
|
|
152
|
+
this.source = {
|
|
153
|
+
type: "index",
|
|
154
|
+
name: indexName,
|
|
155
|
+
range: builder?.(new RuntimeIndexRangeBuilder()).build() ?? []
|
|
156
|
+
};
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
withSearchIndex(indexName, builder) {
|
|
160
|
+
this.source = {
|
|
161
|
+
type: "search",
|
|
162
|
+
name: indexName,
|
|
163
|
+
query: builder(new RuntimeSearchIndexBuilder()).build()
|
|
164
|
+
};
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
order(order) {
|
|
168
|
+
this.orderDirection = order;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
filter(builder) {
|
|
172
|
+
this.filterExpression = builder(new RuntimeFilterBuilder());
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
async collect() {
|
|
176
|
+
return this.execute();
|
|
177
|
+
}
|
|
178
|
+
async take(count) {
|
|
179
|
+
return this.execute({ limit: count });
|
|
180
|
+
}
|
|
181
|
+
async first() {
|
|
182
|
+
return (await this.execute({ limit: 1 }))[0] ?? null;
|
|
183
|
+
}
|
|
184
|
+
async unique() {
|
|
185
|
+
const results = await this.execute({ limit: 2 });
|
|
186
|
+
if (results.length > 1) throw new Error("Expected a unique result but found multiple rows.");
|
|
187
|
+
return results[0] ?? null;
|
|
188
|
+
}
|
|
189
|
+
async paginate(options) {
|
|
190
|
+
const offset = options.cursor ? Number.parseInt(options.cursor, 10) : 0;
|
|
191
|
+
const page = await this.execute({
|
|
192
|
+
limit: options.numItems,
|
|
193
|
+
offset
|
|
194
|
+
});
|
|
195
|
+
const nextCursor = page.length < options.numItems ? null : String(offset + page.length);
|
|
196
|
+
return {
|
|
197
|
+
page,
|
|
198
|
+
cursor: nextCursor,
|
|
199
|
+
isDone: nextCursor === null
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async execute(options) {
|
|
203
|
+
this.dependencyCollector?.add(`table:${this.tableName}`);
|
|
204
|
+
const queryOptions = {
|
|
205
|
+
tableName: this.tableName,
|
|
206
|
+
source: this.source,
|
|
207
|
+
filterExpression: this.filterExpression,
|
|
208
|
+
orderDirection: this.orderDirection
|
|
209
|
+
};
|
|
210
|
+
if (this.dependencyCollector) queryOptions.dependencyCollector = this.dependencyCollector;
|
|
211
|
+
if (options?.limit !== void 0) queryOptions.limit = options.limit;
|
|
212
|
+
if (options?.offset !== void 0) queryOptions.offset = options.offset;
|
|
213
|
+
return this.executeQuery(queryOptions);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* The local Syncore runtime that owns the database, storage, scheduler, and function execution.
|
|
218
|
+
*/
|
|
219
|
+
var SyncoreRuntime = class {
|
|
220
|
+
runtimeId = generateId();
|
|
221
|
+
platform;
|
|
222
|
+
capabilities;
|
|
223
|
+
experimentalPlugins;
|
|
224
|
+
activeQueries = /* @__PURE__ */ new Map();
|
|
225
|
+
disabledSearchIndexes = /* @__PURE__ */ new Set();
|
|
226
|
+
recentEvents = [];
|
|
227
|
+
schedulerTimer;
|
|
228
|
+
recurringJobs;
|
|
229
|
+
schedulerPollIntervalMs;
|
|
230
|
+
started = false;
|
|
231
|
+
constructor(options) {
|
|
232
|
+
this.options = options;
|
|
233
|
+
this.platform = options.platform ?? "node";
|
|
234
|
+
this.experimentalPlugins = options.experimentalPlugins ?? [];
|
|
235
|
+
this.recurringJobs = options.scheduler?.recurringJobs ?? [];
|
|
236
|
+
this.schedulerPollIntervalMs = options.scheduler?.pollIntervalMs ?? 1e3;
|
|
237
|
+
this.capabilities = Object.freeze(this.buildCapabilities());
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Start the local Syncore runtime.
|
|
241
|
+
*/
|
|
242
|
+
async start() {
|
|
243
|
+
if (this.started) return;
|
|
244
|
+
await this.ensureSystemTables();
|
|
245
|
+
await this.reconcileStorageState();
|
|
246
|
+
await this.applySchema();
|
|
247
|
+
await this.syncRecurringJobs();
|
|
248
|
+
await this.runPluginHook("onStart");
|
|
249
|
+
this.schedulerTimer = setInterval(() => {
|
|
250
|
+
this.processDueJobs();
|
|
251
|
+
}, this.schedulerPollIntervalMs);
|
|
252
|
+
this.started = true;
|
|
253
|
+
this.emitDevtools({
|
|
254
|
+
type: "runtime.connected",
|
|
255
|
+
runtimeId: this.runtimeId,
|
|
256
|
+
platform: this.platform,
|
|
257
|
+
timestamp: Date.now()
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stop the local Syncore runtime and release any open resources.
|
|
262
|
+
*/
|
|
263
|
+
async stop() {
|
|
264
|
+
if (this.schedulerTimer) {
|
|
265
|
+
clearInterval(this.schedulerTimer);
|
|
266
|
+
this.schedulerTimer = void 0;
|
|
267
|
+
}
|
|
268
|
+
if (this.started) await this.runPluginHook("onStop");
|
|
269
|
+
await this.options.driver.close?.();
|
|
270
|
+
if (this.started) this.emitDevtools({
|
|
271
|
+
type: "runtime.disconnected",
|
|
272
|
+
runtimeId: this.runtimeId,
|
|
273
|
+
timestamp: Date.now()
|
|
274
|
+
});
|
|
275
|
+
this.started = false;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Create a typed client for calling this runtime from the same process.
|
|
279
|
+
*/
|
|
280
|
+
createClient() {
|
|
281
|
+
return {
|
|
282
|
+
query: (reference, ...args) => this.runQuery(reference, normalizeOptionalArgs(args)),
|
|
283
|
+
mutation: (reference, ...args) => this.runMutation(reference, normalizeOptionalArgs(args)),
|
|
284
|
+
action: (reference, ...args) => this.runAction(reference, normalizeOptionalArgs(args)),
|
|
285
|
+
watchQuery: (reference, ...args) => this.watchQuery(reference, normalizeOptionalArgs(args))
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
getDevtoolsSnapshot() {
|
|
289
|
+
return {
|
|
290
|
+
runtimeId: this.runtimeId,
|
|
291
|
+
platform: this.platform,
|
|
292
|
+
connectedAt: Date.now(),
|
|
293
|
+
activeQueries: [...this.activeQueries.values()].map((query) => ({
|
|
294
|
+
id: query.id,
|
|
295
|
+
functionName: query.functionName,
|
|
296
|
+
dependencyKeys: [...query.dependencyKeys],
|
|
297
|
+
lastRunAt: query.lastRunAt
|
|
298
|
+
})),
|
|
299
|
+
pendingJobs: [],
|
|
300
|
+
recentEvents: [...this.recentEvents]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
getRuntimeId() {
|
|
304
|
+
return this.runtimeId;
|
|
305
|
+
}
|
|
306
|
+
async runQuery(reference, args = {}) {
|
|
307
|
+
const definition = this.resolveFunction(reference, "query");
|
|
308
|
+
const dependencyCollector = /* @__PURE__ */ new Set();
|
|
309
|
+
const startedAt = Date.now();
|
|
310
|
+
const result = await this.invokeFunction(definition, args, {
|
|
311
|
+
mutationDepth: 0,
|
|
312
|
+
changedTables: /* @__PURE__ */ new Set(),
|
|
313
|
+
dependencyCollector
|
|
314
|
+
});
|
|
315
|
+
this.emitDevtools({
|
|
316
|
+
type: "query.executed",
|
|
317
|
+
runtimeId: this.runtimeId,
|
|
318
|
+
queryId: reference.name,
|
|
319
|
+
functionName: reference.name,
|
|
320
|
+
dependencies: [...dependencyCollector],
|
|
321
|
+
durationMs: Date.now() - startedAt,
|
|
322
|
+
timestamp: Date.now()
|
|
323
|
+
});
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
async runMutation(reference, args = {}) {
|
|
327
|
+
const definition = this.resolveFunction(reference, "mutation");
|
|
328
|
+
const mutationId = generateId();
|
|
329
|
+
const startedAt = Date.now();
|
|
330
|
+
const changedTables = /* @__PURE__ */ new Set();
|
|
331
|
+
const result = await this.options.driver.withTransaction(async () => this.invokeFunction(definition, args, {
|
|
332
|
+
mutationDepth: 1,
|
|
333
|
+
changedTables
|
|
334
|
+
}));
|
|
335
|
+
await this.refreshInvalidatedQueries(changedTables, mutationId);
|
|
336
|
+
this.emitDevtools({
|
|
337
|
+
type: "mutation.committed",
|
|
338
|
+
runtimeId: this.runtimeId,
|
|
339
|
+
mutationId,
|
|
340
|
+
functionName: reference.name,
|
|
341
|
+
changedTables: [...changedTables],
|
|
342
|
+
durationMs: Date.now() - startedAt,
|
|
343
|
+
timestamp: Date.now()
|
|
344
|
+
});
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
async runAction(reference, args = {}) {
|
|
348
|
+
const definition = this.resolveFunction(reference, "action");
|
|
349
|
+
const actionId = generateId();
|
|
350
|
+
const startedAt = Date.now();
|
|
351
|
+
try {
|
|
352
|
+
const result = await this.invokeFunction(definition, args, {
|
|
353
|
+
mutationDepth: 0,
|
|
354
|
+
changedTables: /* @__PURE__ */ new Set()
|
|
355
|
+
});
|
|
356
|
+
this.emitDevtools({
|
|
357
|
+
type: "action.completed",
|
|
358
|
+
runtimeId: this.runtimeId,
|
|
359
|
+
actionId,
|
|
360
|
+
functionName: reference.name,
|
|
361
|
+
durationMs: Date.now() - startedAt,
|
|
362
|
+
timestamp: Date.now()
|
|
363
|
+
});
|
|
364
|
+
return result;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
this.emitDevtools({
|
|
367
|
+
type: "action.completed",
|
|
368
|
+
runtimeId: this.runtimeId,
|
|
369
|
+
actionId,
|
|
370
|
+
functionName: reference.name,
|
|
371
|
+
durationMs: Date.now() - startedAt,
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
error: error instanceof Error ? error.message : String(error)
|
|
374
|
+
});
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
watchQuery(reference, args = {}) {
|
|
379
|
+
const key = this.createActiveQueryKey(reference.name, args);
|
|
380
|
+
let record = this.activeQueries.get(key);
|
|
381
|
+
if (!record) {
|
|
382
|
+
record = {
|
|
383
|
+
id: key,
|
|
384
|
+
functionName: reference.name,
|
|
385
|
+
args,
|
|
386
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
387
|
+
consumers: 0,
|
|
388
|
+
dependencyKeys: /* @__PURE__ */ new Set(),
|
|
389
|
+
lastResult: void 0,
|
|
390
|
+
lastError: void 0,
|
|
391
|
+
lastRunAt: 0
|
|
392
|
+
};
|
|
393
|
+
this.activeQueries.set(key, record);
|
|
394
|
+
this.rerunActiveQuery(record);
|
|
395
|
+
}
|
|
396
|
+
const activeRecord = record;
|
|
397
|
+
activeRecord.consumers += 1;
|
|
398
|
+
let disposed = false;
|
|
399
|
+
const ownedListeners = /* @__PURE__ */ new Set();
|
|
400
|
+
return {
|
|
401
|
+
onUpdate: (callback) => {
|
|
402
|
+
activeRecord.listeners.add(callback);
|
|
403
|
+
ownedListeners.add(callback);
|
|
404
|
+
queueMicrotask(callback);
|
|
405
|
+
return () => {
|
|
406
|
+
activeRecord.listeners.delete(callback);
|
|
407
|
+
ownedListeners.delete(callback);
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
localQueryResult: () => activeRecord.lastResult,
|
|
411
|
+
localQueryError: () => activeRecord.lastError,
|
|
412
|
+
dispose: () => {
|
|
413
|
+
if (disposed) return;
|
|
414
|
+
disposed = true;
|
|
415
|
+
for (const callback of ownedListeners) activeRecord.listeners.delete(callback);
|
|
416
|
+
ownedListeners.clear();
|
|
417
|
+
activeRecord.consumers = Math.max(0, activeRecord.consumers - 1);
|
|
418
|
+
if (activeRecord.consumers === 0) this.activeQueries.delete(key);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
async executeQueryBuilder(options) {
|
|
423
|
+
const table = this.options.schema.getTable(options.tableName);
|
|
424
|
+
const params = [];
|
|
425
|
+
const whereClauses = [];
|
|
426
|
+
const orderClauses = [];
|
|
427
|
+
let joinClause = "";
|
|
428
|
+
const source = options.source;
|
|
429
|
+
if (source.type === "index") {
|
|
430
|
+
const index = table.indexes.find((candidate) => candidate.name === source.name);
|
|
431
|
+
if (!index) throw new Error(`Unknown index "${source.name}" on table "${options.tableName}".`);
|
|
432
|
+
for (const condition of source.range) whereClauses.push(this.renderCondition("t", condition, params));
|
|
433
|
+
const primaryField = index.fields[0];
|
|
434
|
+
if (primaryField) orderClauses.push(`${fieldExpression("t", primaryField)} ${options.orderDirection.toUpperCase()}`);
|
|
435
|
+
}
|
|
436
|
+
if (source.type === "search") {
|
|
437
|
+
const searchIndex = table.searchIndexes.find((candidate) => candidate.name === source.name);
|
|
438
|
+
if (!searchIndex) throw new Error(`Unknown search index "${source.name}" on table "${options.tableName}".`);
|
|
439
|
+
if (searchIndex.searchField !== source.query.searchField) throw new Error(`Search index "${searchIndex.name}" expects field "${searchIndex.searchField}".`);
|
|
440
|
+
const searchIndexKey = `${options.tableName}:${searchIndex.name}`;
|
|
441
|
+
if (this.disabledSearchIndexes.has(searchIndexKey)) {
|
|
442
|
+
whereClauses.push(`${fieldExpression("t", searchIndex.searchField)} LIKE ?`);
|
|
443
|
+
params.push(`%${source.query.searchText}%`);
|
|
444
|
+
} else {
|
|
445
|
+
joinClause = `JOIN ${quoteIdentifier(searchIndexTableName(options.tableName, searchIndex.name))} s ON s._id = t._id`;
|
|
446
|
+
whereClauses.push(`s.search_value MATCH ?`);
|
|
447
|
+
params.push(source.query.searchText);
|
|
448
|
+
}
|
|
449
|
+
for (const condition of source.query.filters) whereClauses.push(this.renderCondition("t", condition, params));
|
|
450
|
+
}
|
|
451
|
+
if (options.filterExpression) whereClauses.push(this.renderExpression("t", options.filterExpression, params));
|
|
452
|
+
if (orderClauses.length === 0) orderClauses.push(`t._creationTime ${options.orderDirection.toUpperCase()}`);
|
|
453
|
+
orderClauses.push(`t._id ${options.orderDirection.toUpperCase()}`);
|
|
454
|
+
const sql = [
|
|
455
|
+
`SELECT t._id, t._creationTime, t._json FROM ${quoteIdentifier(options.tableName)} t`,
|
|
456
|
+
joinClause,
|
|
457
|
+
whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "",
|
|
458
|
+
`ORDER BY ${orderClauses.join(", ")}`,
|
|
459
|
+
options.limit !== void 0 ? `LIMIT ${options.limit}` : "",
|
|
460
|
+
options.offset !== void 0 ? `OFFSET ${options.offset}` : ""
|
|
461
|
+
].filter(Boolean).join(" ");
|
|
462
|
+
return (await this.options.driver.all(sql, params)).map((row) => this.deserializeDocument(options.tableName, row));
|
|
463
|
+
}
|
|
464
|
+
async invokeFunction(definition, rawArgs, state) {
|
|
465
|
+
const args = definition.argsValidator.parse(rawArgs);
|
|
466
|
+
const ctx = this.createContext(definition.kind, state);
|
|
467
|
+
const result = await definition.handler(ctx, args);
|
|
468
|
+
if (definition.returnsValidator) return definition.returnsValidator.parse(result);
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
createContext(kind, state) {
|
|
472
|
+
const db = kind === "mutation" ? this.createDatabaseWriter(state) : this.createDatabaseReader(state);
|
|
473
|
+
const storage = this.createStorageApi();
|
|
474
|
+
const scheduler = this.createSchedulerApi();
|
|
475
|
+
return {
|
|
476
|
+
db,
|
|
477
|
+
storage,
|
|
478
|
+
capabilities: this.capabilities,
|
|
479
|
+
scheduler,
|
|
480
|
+
runQuery: (reference, ...args) => this.runQuery(reference, normalizeOptionalArgs(args)),
|
|
481
|
+
runMutation: (reference, ...args) => {
|
|
482
|
+
const normalizedArgs = normalizeOptionalArgs(args);
|
|
483
|
+
if (kind === "mutation") return this.options.driver.withSavepoint(`sp_${generateId().replace(/-/g, "_")}`, () => this.invokeFunction(this.resolveFunction(reference, "mutation"), normalizedArgs, {
|
|
484
|
+
mutationDepth: state.mutationDepth + 1,
|
|
485
|
+
changedTables: state.changedTables
|
|
486
|
+
}));
|
|
487
|
+
return this.runMutation(reference, normalizedArgs);
|
|
488
|
+
},
|
|
489
|
+
runAction: (reference, ...args) => this.runAction(reference, normalizeOptionalArgs(args))
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
createDatabaseReader(state) {
|
|
493
|
+
return {
|
|
494
|
+
get: async (tableName, id) => {
|
|
495
|
+
state.dependencyCollector?.add(`table:${tableName}`);
|
|
496
|
+
state.dependencyCollector?.add(`row:${tableName}:${id}`);
|
|
497
|
+
const row = await this.options.driver.get(`SELECT _id, _creationTime, _json FROM ${quoteIdentifier(tableName)} WHERE _id = ?`, [id]);
|
|
498
|
+
return row ? this.deserializeDocument(tableName, row) : null;
|
|
499
|
+
},
|
|
500
|
+
query: (tableName) => new RuntimeQueryBuilder((options) => this.executeQueryBuilder(options), tableName, state.dependencyCollector),
|
|
501
|
+
raw: (sql, params) => this.options.driver.all(sql, params)
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
createDatabaseWriter(state) {
|
|
505
|
+
const reader = this.createDatabaseReader(state);
|
|
506
|
+
return {
|
|
507
|
+
...reader,
|
|
508
|
+
insert: async (tableName, value) => {
|
|
509
|
+
const validated = this.validateDocument(tableName, value);
|
|
510
|
+
const id = generateId();
|
|
511
|
+
const creationTime = Date.now();
|
|
512
|
+
const json = stableStringify(validated);
|
|
513
|
+
await this.options.driver.run(`INSERT INTO ${quoteIdentifier(tableName)} (_id, _creationTime, _json) VALUES (?, ?, ?)`, [
|
|
514
|
+
id,
|
|
515
|
+
creationTime,
|
|
516
|
+
json
|
|
517
|
+
]);
|
|
518
|
+
await this.syncSearchIndexes(tableName, {
|
|
519
|
+
_id: id,
|
|
520
|
+
_creationTime: creationTime,
|
|
521
|
+
_json: json
|
|
522
|
+
});
|
|
523
|
+
state.changedTables.add(tableName);
|
|
524
|
+
return id;
|
|
525
|
+
},
|
|
526
|
+
patch: async (tableName, id, value) => {
|
|
527
|
+
const current = await reader.get(tableName, id);
|
|
528
|
+
if (!current) throw new Error(`Document "${id}" does not exist in "${tableName}".`);
|
|
529
|
+
const merged = {
|
|
530
|
+
...omitSystemFields(current),
|
|
531
|
+
...value
|
|
532
|
+
};
|
|
533
|
+
for (const key of Object.keys(merged)) if (merged[key] === void 0) delete merged[key];
|
|
534
|
+
const validated = this.validateDocument(tableName, merged);
|
|
535
|
+
await this.options.driver.run(`UPDATE ${quoteIdentifier(tableName)} SET _json = ? WHERE _id = ?`, [stableStringify(validated), id]);
|
|
536
|
+
const row = await this.options.driver.get(`SELECT _id, _creationTime, _json FROM ${quoteIdentifier(tableName)} WHERE _id = ?`, [id]);
|
|
537
|
+
if (row) await this.syncSearchIndexes(tableName, row);
|
|
538
|
+
state.changedTables.add(tableName);
|
|
539
|
+
},
|
|
540
|
+
replace: async (tableName, id, value) => {
|
|
541
|
+
const validated = this.validateDocument(tableName, value);
|
|
542
|
+
await this.options.driver.run(`UPDATE ${quoteIdentifier(tableName)} SET _json = ? WHERE _id = ?`, [stableStringify(validated), id]);
|
|
543
|
+
const row = await this.options.driver.get(`SELECT _id, _creationTime, _json FROM ${quoteIdentifier(tableName)} WHERE _id = ?`, [id]);
|
|
544
|
+
if (!row) throw new Error(`Document "${id}" does not exist in "${tableName}".`);
|
|
545
|
+
await this.syncSearchIndexes(tableName, row);
|
|
546
|
+
state.changedTables.add(tableName);
|
|
547
|
+
},
|
|
548
|
+
delete: async (tableName, id) => {
|
|
549
|
+
await this.options.driver.run(`DELETE FROM ${quoteIdentifier(tableName)} WHERE _id = ?`, [id]);
|
|
550
|
+
await this.removeSearchIndexes(tableName, id);
|
|
551
|
+
state.changedTables.add(tableName);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
createStorageApi() {
|
|
556
|
+
return {
|
|
557
|
+
put: async (input) => {
|
|
558
|
+
const id = generateId();
|
|
559
|
+
const createdAt = Date.now();
|
|
560
|
+
await this.options.driver.run(`INSERT OR REPLACE INTO "_storage_pending" (_id, _creationTime, file_name, content_type) VALUES (?, ?, ?, ?)`, [
|
|
561
|
+
id,
|
|
562
|
+
createdAt,
|
|
563
|
+
input.fileName ?? null,
|
|
564
|
+
input.contentType ?? null
|
|
565
|
+
]);
|
|
566
|
+
const object = await this.options.storage.put(id, input);
|
|
567
|
+
await this.options.driver.withTransaction(async () => {
|
|
568
|
+
await this.options.driver.run(`INSERT OR REPLACE INTO "_storage" (_id, _creationTime, file_name, content_type, size, path) VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
569
|
+
id,
|
|
570
|
+
createdAt,
|
|
571
|
+
input.fileName ?? null,
|
|
572
|
+
object.contentType,
|
|
573
|
+
object.size,
|
|
574
|
+
object.path
|
|
575
|
+
]);
|
|
576
|
+
await this.options.driver.run(`DELETE FROM "_storage_pending" WHERE _id = ?`, [id]);
|
|
577
|
+
});
|
|
578
|
+
this.emitDevtools({
|
|
579
|
+
type: "storage.updated",
|
|
580
|
+
runtimeId: this.runtimeId,
|
|
581
|
+
storageId: id,
|
|
582
|
+
operation: "put",
|
|
583
|
+
timestamp: Date.now()
|
|
584
|
+
});
|
|
585
|
+
return id;
|
|
586
|
+
},
|
|
587
|
+
get: async (id) => {
|
|
588
|
+
const row = await this.options.driver.get(`SELECT _id, _creationTime, file_name, content_type, size, path FROM "_storage" WHERE _id = ?`, [id]);
|
|
589
|
+
if (!row) return null;
|
|
590
|
+
return {
|
|
591
|
+
id: row._id,
|
|
592
|
+
path: row.path,
|
|
593
|
+
size: row.size,
|
|
594
|
+
contentType: row.content_type
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
read: async (id) => {
|
|
598
|
+
if (!await this.options.driver.get(`SELECT _id FROM "_storage" WHERE _id = ?`, [id])) return null;
|
|
599
|
+
return this.options.storage.read(id);
|
|
600
|
+
},
|
|
601
|
+
delete: async (id) => {
|
|
602
|
+
await this.options.storage.delete(id);
|
|
603
|
+
await this.options.driver.withTransaction(async () => {
|
|
604
|
+
await this.options.driver.run(`DELETE FROM "_storage" WHERE _id = ?`, [id]);
|
|
605
|
+
await this.options.driver.run(`DELETE FROM "_storage_pending" WHERE _id = ?`, [id]);
|
|
606
|
+
});
|
|
607
|
+
this.emitDevtools({
|
|
608
|
+
type: "storage.updated",
|
|
609
|
+
runtimeId: this.runtimeId,
|
|
610
|
+
storageId: id,
|
|
611
|
+
operation: "delete",
|
|
612
|
+
timestamp: Date.now()
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
createSchedulerApi() {
|
|
618
|
+
return {
|
|
619
|
+
runAfter: async (delayMs, reference, ...args) => {
|
|
620
|
+
const schedulerArgs = splitSchedulerArgs(args);
|
|
621
|
+
const functionArgs = schedulerArgs[0];
|
|
622
|
+
const misfirePolicy = schedulerArgs[1] ?? DEFAULT_MISFIRE_POLICY;
|
|
623
|
+
return this.scheduleJob(Date.now() + delayMs, reference, functionArgs, misfirePolicy);
|
|
624
|
+
},
|
|
625
|
+
runAt: async (timestamp, reference, ...args) => {
|
|
626
|
+
const schedulerArgs = splitSchedulerArgs(args);
|
|
627
|
+
const functionArgs = schedulerArgs[0];
|
|
628
|
+
const misfirePolicy = schedulerArgs[1] ?? DEFAULT_MISFIRE_POLICY;
|
|
629
|
+
const value = timestamp instanceof Date ? timestamp.getTime() : timestamp;
|
|
630
|
+
return this.scheduleJob(value, reference, functionArgs, misfirePolicy);
|
|
631
|
+
},
|
|
632
|
+
cancel: async (id) => {
|
|
633
|
+
await this.options.driver.run(`UPDATE "_scheduled_functions" SET status = 'cancelled', updated_at = ? WHERE id = ?`, [Date.now(), id]);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async ensureSystemTables() {
|
|
638
|
+
await this.options.driver.exec(`
|
|
639
|
+
CREATE TABLE IF NOT EXISTS "_syncore_migrations" (
|
|
640
|
+
id TEXT PRIMARY KEY,
|
|
641
|
+
applied_at INTEGER NOT NULL,
|
|
642
|
+
sql TEXT NOT NULL
|
|
643
|
+
);
|
|
644
|
+
CREATE TABLE IF NOT EXISTS "_syncore_schema_state" (
|
|
645
|
+
id TEXT PRIMARY KEY,
|
|
646
|
+
schema_hash TEXT NOT NULL,
|
|
647
|
+
schema_json TEXT NOT NULL,
|
|
648
|
+
updated_at INTEGER NOT NULL
|
|
649
|
+
);
|
|
650
|
+
CREATE TABLE IF NOT EXISTS "_storage" (
|
|
651
|
+
_id TEXT PRIMARY KEY,
|
|
652
|
+
_creationTime INTEGER NOT NULL,
|
|
653
|
+
file_name TEXT,
|
|
654
|
+
content_type TEXT,
|
|
655
|
+
size INTEGER NOT NULL,
|
|
656
|
+
path TEXT NOT NULL
|
|
657
|
+
);
|
|
658
|
+
CREATE TABLE IF NOT EXISTS "_storage_pending" (
|
|
659
|
+
_id TEXT PRIMARY KEY,
|
|
660
|
+
_creationTime INTEGER NOT NULL,
|
|
661
|
+
file_name TEXT,
|
|
662
|
+
content_type TEXT
|
|
663
|
+
);
|
|
664
|
+
CREATE TABLE IF NOT EXISTS "_scheduled_functions" (
|
|
665
|
+
id TEXT PRIMARY KEY,
|
|
666
|
+
function_name TEXT NOT NULL,
|
|
667
|
+
function_kind TEXT NOT NULL,
|
|
668
|
+
args_json TEXT NOT NULL,
|
|
669
|
+
status TEXT NOT NULL,
|
|
670
|
+
run_at INTEGER NOT NULL,
|
|
671
|
+
created_at INTEGER NOT NULL,
|
|
672
|
+
updated_at INTEGER NOT NULL,
|
|
673
|
+
recurring_name TEXT,
|
|
674
|
+
schedule_json TEXT,
|
|
675
|
+
timezone TEXT,
|
|
676
|
+
misfire_policy TEXT NOT NULL,
|
|
677
|
+
last_run_at INTEGER,
|
|
678
|
+
window_ms INTEGER
|
|
679
|
+
);
|
|
680
|
+
`);
|
|
681
|
+
try {
|
|
682
|
+
await this.options.driver.exec(`ALTER TABLE "_syncore_schema_state" ADD COLUMN schema_json TEXT NOT NULL DEFAULT '{}'`);
|
|
683
|
+
} catch {}
|
|
684
|
+
}
|
|
685
|
+
async reconcileStorageState() {
|
|
686
|
+
const pendingRows = await this.options.driver.all(`SELECT _id, _creationTime, file_name, content_type FROM "_storage_pending"`);
|
|
687
|
+
for (const pendingRow of pendingRows) {
|
|
688
|
+
if (!await this.options.driver.get(`SELECT _id FROM "_storage" WHERE _id = ?`, [pendingRow._id])) {
|
|
689
|
+
await this.options.storage.delete(pendingRow._id);
|
|
690
|
+
this.emitDevtools({
|
|
691
|
+
type: "log",
|
|
692
|
+
runtimeId: this.runtimeId,
|
|
693
|
+
level: "warn",
|
|
694
|
+
message: `Recovered interrupted storage write ${pendingRow._id}.`,
|
|
695
|
+
timestamp: Date.now()
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
await this.options.driver.run(`DELETE FROM "_storage_pending" WHERE _id = ?`, [pendingRow._id]);
|
|
699
|
+
}
|
|
700
|
+
if (!this.options.storage.list) return;
|
|
701
|
+
const storedRows = await this.options.driver.all(`SELECT _id FROM "_storage"`);
|
|
702
|
+
const knownIds = new Set(storedRows.map((row) => row._id));
|
|
703
|
+
const physicalObjects = await this.options.storage.list();
|
|
704
|
+
for (const object of physicalObjects) {
|
|
705
|
+
if (knownIds.has(object.id)) continue;
|
|
706
|
+
await this.options.storage.delete(object.id);
|
|
707
|
+
this.emitDevtools({
|
|
708
|
+
type: "log",
|
|
709
|
+
runtimeId: this.runtimeId,
|
|
710
|
+
level: "warn",
|
|
711
|
+
message: `Removed orphaned storage object ${object.id}.`,
|
|
712
|
+
timestamp: Date.now()
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async applySchema() {
|
|
717
|
+
const nextSnapshot = createSchemaSnapshot(this.options.schema);
|
|
718
|
+
const stateRow = await this.options.driver.get(`SELECT schema_hash, schema_json FROM "_syncore_schema_state" WHERE id = 'current'`);
|
|
719
|
+
let previousSnapshot = null;
|
|
720
|
+
if (stateRow?.schema_json && stateRow.schema_json !== "{}") try {
|
|
721
|
+
previousSnapshot = parseSchemaSnapshot(stateRow.schema_json);
|
|
722
|
+
} catch {
|
|
723
|
+
previousSnapshot = null;
|
|
724
|
+
}
|
|
725
|
+
const plan = diffSchemaSnapshots(previousSnapshot, nextSnapshot);
|
|
726
|
+
if (plan.destructiveChanges.length > 0) throw new Error(`Syncore detected destructive schema changes that require a manual migration:\n${plan.destructiveChanges.join("\n")}`);
|
|
727
|
+
for (const warning of plan.warnings) this.emitDevtools({
|
|
728
|
+
type: "log",
|
|
729
|
+
runtimeId: this.runtimeId,
|
|
730
|
+
level: "warn",
|
|
731
|
+
message: warning,
|
|
732
|
+
timestamp: Date.now()
|
|
733
|
+
});
|
|
734
|
+
for (const statement of plan.statements) {
|
|
735
|
+
const searchKey = this.findSearchIndexKeyForStatement(statement);
|
|
736
|
+
try {
|
|
737
|
+
await this.options.driver.exec(statement);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (searchKey) {
|
|
740
|
+
this.disabledSearchIndexes.add(searchKey);
|
|
741
|
+
this.emitDevtools({
|
|
742
|
+
type: "log",
|
|
743
|
+
runtimeId: this.runtimeId,
|
|
744
|
+
level: "warn",
|
|
745
|
+
message: `FTS5 unavailable for ${searchKey}; falling back to LIKE search.`,
|
|
746
|
+
timestamp: Date.now()
|
|
747
|
+
});
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
throw error;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (plan.statements.length > 0 || plan.warnings.length > 0) {
|
|
754
|
+
const migrationSql = renderMigrationSql(plan, { title: "Syncore automatic schema reconciliation" });
|
|
755
|
+
await this.options.driver.run(`INSERT OR REPLACE INTO "_syncore_migrations" (id, applied_at, sql) VALUES (?, ?, ?)`, [
|
|
756
|
+
nextSnapshot.hash,
|
|
757
|
+
Date.now(),
|
|
758
|
+
migrationSql
|
|
759
|
+
]);
|
|
760
|
+
}
|
|
761
|
+
await this.options.driver.run(`INSERT INTO "_syncore_schema_state" (id, schema_hash, schema_json, updated_at)
|
|
762
|
+
VALUES ('current', ?, ?, ?)
|
|
763
|
+
ON CONFLICT(id) DO UPDATE SET schema_hash = excluded.schema_hash, schema_json = excluded.schema_json, updated_at = excluded.updated_at`, [
|
|
764
|
+
nextSnapshot.hash,
|
|
765
|
+
stableStringify(nextSnapshot),
|
|
766
|
+
Date.now()
|
|
767
|
+
]);
|
|
768
|
+
for (const tableName of this.options.schema.tableNames()) {
|
|
769
|
+
const table = this.getTableDefinition(tableName);
|
|
770
|
+
for (const searchIndex of table.searchIndexes) {
|
|
771
|
+
const searchKey = `${tableName}:${searchIndex.name}`;
|
|
772
|
+
try {
|
|
773
|
+
await this.options.driver.exec(renderCreateSearchIndexStatement(tableName, searchIndex));
|
|
774
|
+
this.disabledSearchIndexes.delete(searchKey);
|
|
775
|
+
} catch {
|
|
776
|
+
const alreadyDisabled = this.disabledSearchIndexes.has(searchKey);
|
|
777
|
+
this.disabledSearchIndexes.add(searchKey);
|
|
778
|
+
if (!alreadyDisabled) this.emitDevtools({
|
|
779
|
+
type: "log",
|
|
780
|
+
runtimeId: this.runtimeId,
|
|
781
|
+
level: "warn",
|
|
782
|
+
message: `FTS5 unavailable for ${searchKey}; falling back to LIKE search.`,
|
|
783
|
+
timestamp: Date.now()
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async scheduleJob(runAt, reference, args, misfirePolicy) {
|
|
790
|
+
const id = generateId();
|
|
791
|
+
const now = Date.now();
|
|
792
|
+
await this.options.driver.run(`INSERT INTO "_scheduled_functions"
|
|
793
|
+
(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)
|
|
794
|
+
VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, NULL, NULL, NULL, ?, NULL, ?)`, [
|
|
795
|
+
id,
|
|
796
|
+
reference.name,
|
|
797
|
+
reference.kind,
|
|
798
|
+
stableStringify(args),
|
|
799
|
+
runAt,
|
|
800
|
+
now,
|
|
801
|
+
now,
|
|
802
|
+
misfirePolicy.type,
|
|
803
|
+
misfirePolicy.type === "windowed" ? misfirePolicy.windowMs : null
|
|
804
|
+
]);
|
|
805
|
+
return id;
|
|
806
|
+
}
|
|
807
|
+
async syncRecurringJobs() {
|
|
808
|
+
for (const job of this.recurringJobs) {
|
|
809
|
+
const id = `recurring:${job.name}`;
|
|
810
|
+
if (await this.options.driver.get(`SELECT * FROM "_scheduled_functions" WHERE id = ?`, [id])) continue;
|
|
811
|
+
const nextRunAt = computeNextRun(job.schedule, Date.now());
|
|
812
|
+
await this.options.driver.run(`INSERT INTO "_scheduled_functions"
|
|
813
|
+
(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)
|
|
814
|
+
VALUES (?, ?, ?, ?, 'scheduled', ?, ?, ?, ?, ?, ?, ?, NULL, ?)`, [
|
|
815
|
+
id,
|
|
816
|
+
job.function.name,
|
|
817
|
+
job.function.kind,
|
|
818
|
+
stableStringify(job.args),
|
|
819
|
+
nextRunAt,
|
|
820
|
+
Date.now(),
|
|
821
|
+
Date.now(),
|
|
822
|
+
job.name,
|
|
823
|
+
stableStringify(job.schedule),
|
|
824
|
+
"timezone" in job.schedule ? job.schedule.timezone ?? null : null,
|
|
825
|
+
job.misfirePolicy.type,
|
|
826
|
+
job.misfirePolicy.type === "windowed" ? job.misfirePolicy.windowMs : null
|
|
827
|
+
]);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async processDueJobs() {
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const dueJobs = await this.options.driver.all(`SELECT * FROM "_scheduled_functions" WHERE status = 'scheduled' AND run_at <= ? ORDER BY run_at ASC`, [now]);
|
|
833
|
+
const executedJobIds = [];
|
|
834
|
+
for (const job of dueJobs) {
|
|
835
|
+
const misfirePolicy = parseMisfirePolicy(job.misfire_policy, job.window_ms);
|
|
836
|
+
if (!shouldRunMissedJob(job.run_at, now, misfirePolicy)) {
|
|
837
|
+
await this.advanceOrFinalizeJob(job, "skipped", now);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
if (job.function_kind === "mutation") await this.runMutation({
|
|
842
|
+
kind: "mutation",
|
|
843
|
+
name: job.function_name
|
|
844
|
+
}, JSON.parse(job.args_json));
|
|
845
|
+
else await this.runAction({
|
|
846
|
+
kind: "action",
|
|
847
|
+
name: job.function_name
|
|
848
|
+
}, JSON.parse(job.args_json));
|
|
849
|
+
executedJobIds.push(job.id);
|
|
850
|
+
await this.advanceOrFinalizeJob(job, "completed", now);
|
|
851
|
+
} catch (error) {
|
|
852
|
+
await this.options.driver.run(`UPDATE "_scheduled_functions" SET status = 'failed', updated_at = ? WHERE id = ?`, [Date.now(), job.id]);
|
|
853
|
+
this.emitDevtools({
|
|
854
|
+
type: "log",
|
|
855
|
+
runtimeId: this.runtimeId,
|
|
856
|
+
level: "error",
|
|
857
|
+
message: `Scheduled job ${job.id} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
858
|
+
timestamp: Date.now()
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (executedJobIds.length > 0) this.emitDevtools({
|
|
863
|
+
type: "scheduler.tick",
|
|
864
|
+
runtimeId: this.runtimeId,
|
|
865
|
+
executedJobIds,
|
|
866
|
+
timestamp: Date.now()
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
async advanceOrFinalizeJob(job, terminalStatus, executedAt) {
|
|
870
|
+
if (!job.recurring_name || !job.schedule_json) {
|
|
871
|
+
await this.options.driver.run(`UPDATE "_scheduled_functions" SET status = ?, updated_at = ?, last_run_at = ? WHERE id = ?`, [
|
|
872
|
+
terminalStatus,
|
|
873
|
+
executedAt,
|
|
874
|
+
executedAt,
|
|
875
|
+
job.id
|
|
876
|
+
]);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const nextRunAt = computeNextRun(JSON.parse(job.schedule_json), executedAt + 1);
|
|
880
|
+
await this.options.driver.run(`UPDATE "_scheduled_functions"
|
|
881
|
+
SET status = 'scheduled', run_at = ?, updated_at = ?, last_run_at = ?
|
|
882
|
+
WHERE id = ?`, [
|
|
883
|
+
nextRunAt,
|
|
884
|
+
executedAt,
|
|
885
|
+
executedAt,
|
|
886
|
+
job.id
|
|
887
|
+
]);
|
|
888
|
+
}
|
|
889
|
+
async refreshInvalidatedQueries(changedTables, mutationId) {
|
|
890
|
+
for (const query of this.activeQueries.values()) {
|
|
891
|
+
if (![...changedTables].some((tableName) => query.dependencyKeys.has(`table:${tableName}`))) continue;
|
|
892
|
+
this.emitDevtools({
|
|
893
|
+
type: "query.invalidated",
|
|
894
|
+
runtimeId: this.runtimeId,
|
|
895
|
+
queryId: query.id,
|
|
896
|
+
reason: `Mutation ${mutationId} changed ${[...changedTables].join(", ")}`,
|
|
897
|
+
timestamp: Date.now()
|
|
898
|
+
});
|
|
899
|
+
await this.rerunActiveQuery(query);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async rerunActiveQuery(record) {
|
|
903
|
+
record.dependencyKeys.clear();
|
|
904
|
+
try {
|
|
905
|
+
record.lastResult = await this.runQuery({
|
|
906
|
+
kind: "query",
|
|
907
|
+
name: record.functionName
|
|
908
|
+
}, record.args);
|
|
909
|
+
record.lastError = void 0;
|
|
910
|
+
record.lastRunAt = Date.now();
|
|
911
|
+
record.dependencyKeys = await this.collectQueryDependencies(record.functionName, record.args);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
record.lastError = error;
|
|
914
|
+
}
|
|
915
|
+
for (const listener of record.listeners) listener();
|
|
916
|
+
}
|
|
917
|
+
async collectQueryDependencies(functionName, args) {
|
|
918
|
+
const definition = this.resolveFunction({
|
|
919
|
+
kind: "query",
|
|
920
|
+
name: functionName
|
|
921
|
+
}, "query");
|
|
922
|
+
const dependencyCollector = /* @__PURE__ */ new Set();
|
|
923
|
+
await this.invokeFunction(definition, args, {
|
|
924
|
+
mutationDepth: 0,
|
|
925
|
+
changedTables: /* @__PURE__ */ new Set(),
|
|
926
|
+
dependencyCollector
|
|
927
|
+
});
|
|
928
|
+
return dependencyCollector;
|
|
929
|
+
}
|
|
930
|
+
resolveFunction(reference, expectedKind) {
|
|
931
|
+
const definition = this.options.functions[reference.name];
|
|
932
|
+
if (!definition) throw new Error(`Unknown function "${reference.name}".`);
|
|
933
|
+
if (definition.kind !== expectedKind) throw new Error(`Function "${reference.name}" is a ${definition.kind}, expected ${expectedKind}.`);
|
|
934
|
+
return definition;
|
|
935
|
+
}
|
|
936
|
+
validateDocument(tableName, value) {
|
|
937
|
+
return this.getTableDefinition(tableName).validator.parse(value);
|
|
938
|
+
}
|
|
939
|
+
deserializeDocument(tableName, row) {
|
|
940
|
+
const payload = JSON.parse(row._json);
|
|
941
|
+
const document = {
|
|
942
|
+
...payload,
|
|
943
|
+
_id: row._id,
|
|
944
|
+
_creationTime: row._creationTime
|
|
945
|
+
};
|
|
946
|
+
this.getTableDefinition(tableName).validator.parse(payload);
|
|
947
|
+
return document;
|
|
948
|
+
}
|
|
949
|
+
async syncSearchIndexes(tableName, row) {
|
|
950
|
+
const table = this.getTableDefinition(tableName);
|
|
951
|
+
if (table.searchIndexes.length === 0) return;
|
|
952
|
+
const payload = JSON.parse(row._json);
|
|
953
|
+
for (const searchIndex of table.searchIndexes) {
|
|
954
|
+
if (this.disabledSearchIndexes.has(`${tableName}:${searchIndex.name}`)) continue;
|
|
955
|
+
await this.options.driver.run(`DELETE FROM ${quoteIdentifier(searchIndexTableName(tableName, searchIndex.name))} WHERE _id = ?`, [row._id]);
|
|
956
|
+
await this.options.driver.run(`INSERT INTO ${quoteIdentifier(searchIndexTableName(tableName, searchIndex.name))} (_id, search_value) VALUES (?, ?)`, [row._id, toSearchValue(payload[searchIndex.searchField])]);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async removeSearchIndexes(tableName, id) {
|
|
960
|
+
const table = this.getTableDefinition(tableName);
|
|
961
|
+
for (const searchIndex of table.searchIndexes) {
|
|
962
|
+
if (this.disabledSearchIndexes.has(`${tableName}:${searchIndex.name}`)) continue;
|
|
963
|
+
await this.options.driver.run(`DELETE FROM ${quoteIdentifier(searchIndexTableName(tableName, searchIndex.name))} WHERE _id = ?`, [id]);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
renderExpression(tableAlias, expression, params) {
|
|
967
|
+
if (expression.type === "condition") return this.renderCondition(tableAlias, expression.condition, params);
|
|
968
|
+
const separator = expression.type === "and" ? " AND " : " OR ";
|
|
969
|
+
return `(${expression.expressions.map((child) => this.renderExpression(tableAlias, child, params)).join(separator)})`;
|
|
970
|
+
}
|
|
971
|
+
renderCondition(tableAlias, condition, params) {
|
|
972
|
+
params.push(condition.value);
|
|
973
|
+
return `${fieldExpression(tableAlias, condition.field)} ${condition.operator} ?`;
|
|
974
|
+
}
|
|
975
|
+
createActiveQueryKey(name, args) {
|
|
976
|
+
return `${name}:${stableStringify(args)}`;
|
|
977
|
+
}
|
|
978
|
+
emitDevtools(event) {
|
|
979
|
+
this.recentEvents.unshift(event);
|
|
980
|
+
this.recentEvents.splice(24);
|
|
981
|
+
this.options.devtools?.emit(event);
|
|
982
|
+
}
|
|
983
|
+
createPluginContext() {
|
|
984
|
+
return {
|
|
985
|
+
runtimeId: this.runtimeId,
|
|
986
|
+
platform: this.platform,
|
|
987
|
+
schema: this.options.schema,
|
|
988
|
+
driver: this.options.driver,
|
|
989
|
+
storage: this.options.storage,
|
|
990
|
+
...this.options.scheduler ? { scheduler: this.options.scheduler } : {},
|
|
991
|
+
...this.options.devtools ? { devtools: this.options.devtools } : {},
|
|
992
|
+
emitDevtools: (event) => {
|
|
993
|
+
this.emitDevtools(event);
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
buildCapabilities() {
|
|
998
|
+
const capabilities = { ...this.options.capabilities ?? {} };
|
|
999
|
+
for (const plugin of this.experimentalPlugins) {
|
|
1000
|
+
if (!plugin.capabilities) continue;
|
|
1001
|
+
const contributed = typeof plugin.capabilities === "function" ? plugin.capabilities(this.createPluginContext()) : plugin.capabilities;
|
|
1002
|
+
if (!contributed) continue;
|
|
1003
|
+
Object.assign(capabilities, contributed);
|
|
1004
|
+
}
|
|
1005
|
+
return capabilities;
|
|
1006
|
+
}
|
|
1007
|
+
async runPluginHook(hook) {
|
|
1008
|
+
const context = this.createPluginContext();
|
|
1009
|
+
for (const plugin of this.experimentalPlugins) {
|
|
1010
|
+
const handler = plugin[hook];
|
|
1011
|
+
if (!handler) continue;
|
|
1012
|
+
await handler(context);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
findSearchIndexKeyForStatement(statement) {
|
|
1016
|
+
for (const tableName of this.options.schema.tableNames()) {
|
|
1017
|
+
const table = this.getTableDefinition(tableName);
|
|
1018
|
+
for (const searchIndex of table.searchIndexes) if (statement === renderCreateSearchIndexStatement(tableName, searchIndex)) return `${tableName}:${searchIndex.name}`;
|
|
1019
|
+
}
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
getTableDefinition(tableName) {
|
|
1023
|
+
return this.options.schema.getTable(tableName);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
function fieldExpression(tableAlias, field) {
|
|
1027
|
+
return `json_extract(${tableAlias ? `${tableAlias}.` : ""}_json, '$.${field}')`;
|
|
1028
|
+
}
|
|
1029
|
+
function quoteIdentifier(identifier) {
|
|
1030
|
+
return `"${identifier.replaceAll("\"", "\"\"")}"`;
|
|
1031
|
+
}
|
|
1032
|
+
function stableStringify(value) {
|
|
1033
|
+
return JSON.stringify(sortValue(value));
|
|
1034
|
+
}
|
|
1035
|
+
function sortValue(value) {
|
|
1036
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
1037
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortValue(nested)]));
|
|
1038
|
+
return value;
|
|
1039
|
+
}
|
|
1040
|
+
function omitSystemFields(document) {
|
|
1041
|
+
const clone = { ...document };
|
|
1042
|
+
delete clone._id;
|
|
1043
|
+
delete clone._creationTime;
|
|
1044
|
+
return clone;
|
|
1045
|
+
}
|
|
1046
|
+
function toSearchValue(value) {
|
|
1047
|
+
if (typeof value === "string") return value;
|
|
1048
|
+
if (value === null || value === void 0) return "";
|
|
1049
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
1050
|
+
return stableStringify(value);
|
|
1051
|
+
}
|
|
1052
|
+
function parseMisfirePolicy(type, windowMs) {
|
|
1053
|
+
if (type === "windowed") return {
|
|
1054
|
+
type,
|
|
1055
|
+
windowMs: windowMs ?? 0
|
|
1056
|
+
};
|
|
1057
|
+
if (type === "skip" || type === "run_once_if_missed") return { type };
|
|
1058
|
+
return { type: "catch_up" };
|
|
1059
|
+
}
|
|
1060
|
+
function shouldRunMissedJob(scheduledAt, now, policy) {
|
|
1061
|
+
if (scheduledAt >= now) return true;
|
|
1062
|
+
switch (policy.type) {
|
|
1063
|
+
case "catch_up": return true;
|
|
1064
|
+
case "run_once_if_missed": return true;
|
|
1065
|
+
case "skip": return false;
|
|
1066
|
+
case "windowed": return now - scheduledAt <= policy.windowMs;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function computeNextRun(schedule, fromTimestamp) {
|
|
1070
|
+
switch (schedule.type) {
|
|
1071
|
+
case "interval": return fromTimestamp + intervalToMs(schedule);
|
|
1072
|
+
case "daily": return nextDailyOccurrence(fromTimestamp, schedule);
|
|
1073
|
+
case "weekly": return nextWeeklyOccurrence(fromTimestamp, schedule);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function intervalToMs(schedule) {
|
|
1077
|
+
if (schedule.seconds) return schedule.seconds * 1e3;
|
|
1078
|
+
if (schedule.minutes) return schedule.minutes * 60 * 1e3;
|
|
1079
|
+
return (schedule.hours ?? 1) * 60 * 60 * 1e3;
|
|
1080
|
+
}
|
|
1081
|
+
function nextDailyOccurrence(fromTimestamp, schedule) {
|
|
1082
|
+
const timezone = schedule.timezone ?? "UTC";
|
|
1083
|
+
const zonedNow = toZonedTime(new Date(fromTimestamp), timezone);
|
|
1084
|
+
const zoned = new Date(zonedNow.getTime());
|
|
1085
|
+
zoned.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
1086
|
+
if (zoned.getTime() <= zonedNow.getTime()) zoned.setDate(zoned.getDate() + 1);
|
|
1087
|
+
return fromZonedTime(zoned, timezone).getTime();
|
|
1088
|
+
}
|
|
1089
|
+
function nextWeeklyOccurrence(fromTimestamp, schedule) {
|
|
1090
|
+
const timezone = schedule.timezone ?? "UTC";
|
|
1091
|
+
const zonedNow = toZonedTime(new Date(fromTimestamp), timezone);
|
|
1092
|
+
const targetDay = [
|
|
1093
|
+
"sunday",
|
|
1094
|
+
"monday",
|
|
1095
|
+
"tuesday",
|
|
1096
|
+
"wednesday",
|
|
1097
|
+
"thursday",
|
|
1098
|
+
"friday",
|
|
1099
|
+
"saturday"
|
|
1100
|
+
].indexOf(schedule.dayOfWeek);
|
|
1101
|
+
const zoned = new Date(zonedNow.getTime());
|
|
1102
|
+
const delta = (targetDay - zonedNow.getDay() + 7) % 7;
|
|
1103
|
+
zoned.setDate(zoned.getDate() + delta);
|
|
1104
|
+
zoned.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
1105
|
+
if (zoned.getTime() <= zonedNow.getTime()) zoned.setDate(zoned.getDate() + 7);
|
|
1106
|
+
return fromZonedTime(zoned, timezone).getTime();
|
|
1107
|
+
}
|
|
1108
|
+
function createFunctionReference(kind, name) {
|
|
1109
|
+
return {
|
|
1110
|
+
kind,
|
|
1111
|
+
name
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Create a typed function reference from a concrete Syncore function definition.
|
|
1116
|
+
*
|
|
1117
|
+
* Generated code uses this helper to preserve function arg and result inference.
|
|
1118
|
+
*/
|
|
1119
|
+
function createFunctionReferenceFor(kind, name) {
|
|
1120
|
+
return {
|
|
1121
|
+
kind,
|
|
1122
|
+
name
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
function normalizeOptionalArgs(args) {
|
|
1126
|
+
return args[0] ?? {};
|
|
1127
|
+
}
|
|
1128
|
+
function splitSchedulerArgs(args) {
|
|
1129
|
+
if (args.length === 0) return [{}, void 0];
|
|
1130
|
+
if (args.length === 1) {
|
|
1131
|
+
const [first] = args;
|
|
1132
|
+
if (isMisfirePolicy(first)) return [{}, first];
|
|
1133
|
+
return [first ?? {}, void 0];
|
|
1134
|
+
}
|
|
1135
|
+
return [args[0] ?? {}, args[1]];
|
|
1136
|
+
}
|
|
1137
|
+
function isMisfirePolicy(value) {
|
|
1138
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
1139
|
+
}
|
|
1140
|
+
//#endregion
|
|
1141
|
+
export { SyncoreRuntime, createFunctionReference, createFunctionReferenceFor };
|
|
1142
|
+
|
|
1143
|
+
//# sourceMappingURL=runtime.mjs.map
|