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.
Files changed (227) hide show
  1. package/README.md +30 -0
  2. package/dist/_vendor/core/_virtual/_rolldown/runtime.mjs +27 -0
  3. package/dist/_vendor/core/cli.d.mts +5 -0
  4. package/dist/_vendor/core/cli.d.mts.map +1 -0
  5. package/dist/_vendor/core/cli.mjs +1196 -0
  6. package/dist/_vendor/core/cli.mjs.map +1 -0
  7. package/dist/_vendor/core/index.d.mts +7 -0
  8. package/dist/_vendor/core/index.mjs +25 -0
  9. package/dist/_vendor/core/index.mjs.map +1 -0
  10. package/dist/_vendor/core/runtime/devtools.d.mts +15 -0
  11. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -0
  12. package/dist/_vendor/core/runtime/devtools.mjs +300 -0
  13. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -0
  14. package/dist/_vendor/core/runtime/functions.d.mts +123 -0
  15. package/dist/_vendor/core/runtime/functions.d.mts.map +1 -0
  16. package/dist/_vendor/core/runtime/functions.mjs +71 -0
  17. package/dist/_vendor/core/runtime/functions.mjs.map +1 -0
  18. package/dist/_vendor/core/runtime/id.d.mts +13 -0
  19. package/dist/_vendor/core/runtime/id.d.mts.map +1 -0
  20. package/dist/_vendor/core/runtime/id.mjs +28 -0
  21. package/dist/_vendor/core/runtime/id.mjs.map +1 -0
  22. package/dist/_vendor/core/runtime/runtime.d.mts +370 -0
  23. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -0
  24. package/dist/_vendor/core/runtime/runtime.mjs +1143 -0
  25. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -0
  26. package/dist/_vendor/devtools-protocol/index.d.ts +230 -0
  27. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -0
  28. package/dist/_vendor/devtools-protocol/index.js +0 -0
  29. package/dist/_vendor/next/config.d.ts +17 -0
  30. package/dist/_vendor/next/config.d.ts.map +1 -0
  31. package/dist/_vendor/next/config.js +73 -0
  32. package/dist/_vendor/next/config.js.map +1 -0
  33. package/dist/_vendor/next/index.d.ts +80 -0
  34. package/dist/_vendor/next/index.d.ts.map +1 -0
  35. package/dist/_vendor/next/index.js +81 -0
  36. package/dist/_vendor/next/index.js.map +1 -0
  37. package/dist/_vendor/platform-expo/index.d.ts +97 -0
  38. package/dist/_vendor/platform-expo/index.d.ts.map +1 -0
  39. package/dist/_vendor/platform-expo/index.js +197 -0
  40. package/dist/_vendor/platform-expo/index.js.map +1 -0
  41. package/dist/_vendor/platform-expo/react.d.ts +26 -0
  42. package/dist/_vendor/platform-expo/react.d.ts.map +1 -0
  43. package/dist/_vendor/platform-expo/react.js +30 -0
  44. package/dist/_vendor/platform-expo/react.js.map +1 -0
  45. package/dist/_vendor/platform-node/index.d.mts +145 -0
  46. package/dist/_vendor/platform-node/index.d.mts.map +1 -0
  47. package/dist/_vendor/platform-node/index.mjs +405 -0
  48. package/dist/_vendor/platform-node/index.mjs.map +1 -0
  49. package/dist/_vendor/platform-node/ipc-react.d.mts +25 -0
  50. package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -0
  51. package/dist/_vendor/platform-node/ipc-react.mjs +21 -0
  52. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -0
  53. package/dist/_vendor/platform-node/ipc.d.mts +75 -0
  54. package/dist/_vendor/platform-node/ipc.d.mts.map +1 -0
  55. package/dist/_vendor/platform-node/ipc.mjs +343 -0
  56. package/dist/_vendor/platform-node/ipc.mjs.map +1 -0
  57. package/dist/_vendor/platform-web/index.d.ts +123 -0
  58. package/dist/_vendor/platform-web/index.d.ts.map +1 -0
  59. package/dist/_vendor/platform-web/index.js +309 -0
  60. package/dist/_vendor/platform-web/index.js.map +1 -0
  61. package/dist/_vendor/platform-web/indexeddb.d.ts +25 -0
  62. package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -0
  63. package/dist/_vendor/platform-web/indexeddb.js +125 -0
  64. package/dist/_vendor/platform-web/indexeddb.js.map +1 -0
  65. package/dist/_vendor/platform-web/opfs.d.ts +27 -0
  66. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -0
  67. package/dist/_vendor/platform-web/opfs.js +146 -0
  68. package/dist/_vendor/platform-web/opfs.js.map +1 -0
  69. package/dist/_vendor/platform-web/persistence.d.ts +27 -0
  70. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -0
  71. package/dist/_vendor/platform-web/persistence.js +23 -0
  72. package/dist/_vendor/platform-web/persistence.js.map +1 -0
  73. package/dist/_vendor/platform-web/react.d.ts +35 -0
  74. package/dist/_vendor/platform-web/react.d.ts.map +1 -0
  75. package/dist/_vendor/platform-web/react.js +42 -0
  76. package/dist/_vendor/platform-web/react.js.map +1 -0
  77. package/dist/_vendor/platform-web/sqljs.js +133 -0
  78. package/dist/_vendor/platform-web/sqljs.js.map +1 -0
  79. package/dist/_vendor/platform-web/worker.d.ts +78 -0
  80. package/dist/_vendor/platform-web/worker.d.ts.map +1 -0
  81. package/dist/_vendor/platform-web/worker.js +307 -0
  82. package/dist/_vendor/platform-web/worker.js.map +1 -0
  83. package/dist/_vendor/react/index.d.ts +58 -0
  84. package/dist/_vendor/react/index.d.ts.map +1 -0
  85. package/dist/_vendor/react/index.js +151 -0
  86. package/dist/_vendor/react/index.js.map +1 -0
  87. package/dist/_vendor/schema/definition.d.ts +98 -0
  88. package/dist/_vendor/schema/definition.d.ts.map +1 -0
  89. package/dist/_vendor/schema/definition.js +84 -0
  90. package/dist/_vendor/schema/definition.js.map +1 -0
  91. package/dist/_vendor/schema/index.d.ts +4 -0
  92. package/dist/_vendor/schema/index.js +4 -0
  93. package/dist/_vendor/schema/planner.d.ts +42 -0
  94. package/dist/_vendor/schema/planner.d.ts.map +1 -0
  95. package/dist/_vendor/schema/planner.js +131 -0
  96. package/dist/_vendor/schema/planner.js.map +1 -0
  97. package/dist/_vendor/schema/validators.d.ts +194 -0
  98. package/dist/_vendor/schema/validators.d.ts.map +1 -0
  99. package/dist/_vendor/schema/validators.js +158 -0
  100. package/dist/_vendor/schema/validators.js.map +1 -0
  101. package/dist/_vendor/svelte/index.d.ts +43 -0
  102. package/dist/_vendor/svelte/index.d.ts.map +1 -0
  103. package/dist/_vendor/svelte/index.js +75 -0
  104. package/dist/_vendor/svelte/index.js.map +1 -0
  105. package/dist/browser-react.d.ts +2 -0
  106. package/dist/browser-react.js +2 -0
  107. package/dist/browser.d.ts +12 -0
  108. package/dist/browser.d.ts.map +1 -0
  109. package/dist/browser.js +10 -0
  110. package/dist/browser.js.map +1 -0
  111. package/dist/cli.d.ts +2 -0
  112. package/dist/cli.js +11 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/core/src/cli.d.ts +5 -0
  115. package/dist/core/src/cli.d.ts.map +1 -0
  116. package/dist/core/src/cli.js +1196 -0
  117. package/dist/core/src/cli.js.map +1 -0
  118. package/dist/core/src/index.js +7 -0
  119. package/dist/core/src/runtime/devtools.d.ts +7 -0
  120. package/dist/core/src/runtime/devtools.d.ts.map +1 -0
  121. package/dist/core/src/runtime/devtools.js +300 -0
  122. package/dist/core/src/runtime/devtools.js.map +1 -0
  123. package/dist/core/src/runtime/functions.d.ts +123 -0
  124. package/dist/core/src/runtime/functions.d.ts.map +1 -0
  125. package/dist/core/src/runtime/functions.js +71 -0
  126. package/dist/core/src/runtime/functions.js.map +1 -0
  127. package/dist/core/src/runtime/id.d.ts +13 -0
  128. package/dist/core/src/runtime/id.d.ts.map +1 -0
  129. package/dist/core/src/runtime/id.js +28 -0
  130. package/dist/core/src/runtime/id.js.map +1 -0
  131. package/dist/core/src/runtime/runtime.d.ts +371 -0
  132. package/dist/core/src/runtime/runtime.d.ts.map +1 -0
  133. package/dist/core/src/runtime/runtime.js +1143 -0
  134. package/dist/core/src/runtime/runtime.js.map +1 -0
  135. package/dist/devtools-protocol/src/index.d.ts +201 -0
  136. package/dist/devtools-protocol/src/index.d.ts.map +1 -0
  137. package/dist/expo-react.d.ts +2 -0
  138. package/dist/expo-react.js +2 -0
  139. package/dist/expo.d.ts +2 -0
  140. package/dist/expo.js +2 -0
  141. package/dist/index.d.ts +7 -0
  142. package/dist/index.js +8 -0
  143. package/dist/next/src/config.d.ts +17 -0
  144. package/dist/next/src/config.d.ts.map +1 -0
  145. package/dist/next/src/config.js +73 -0
  146. package/dist/next/src/config.js.map +1 -0
  147. package/dist/next/src/index.d.ts +80 -0
  148. package/dist/next/src/index.d.ts.map +1 -0
  149. package/dist/next/src/index.js +82 -0
  150. package/dist/next/src/index.js.map +1 -0
  151. package/dist/next-config.d.ts +2 -0
  152. package/dist/next-config.js +2 -0
  153. package/dist/next.d.ts +3 -0
  154. package/dist/next.js +3 -0
  155. package/dist/node-ipc-react.d.ts +2 -0
  156. package/dist/node-ipc-react.js +2 -0
  157. package/dist/node-ipc.d.ts +2 -0
  158. package/dist/node-ipc.js +2 -0
  159. package/dist/node.d.ts +4 -0
  160. package/dist/node.js +3 -0
  161. package/dist/platform-expo/src/index.d.ts +96 -0
  162. package/dist/platform-expo/src/index.d.ts.map +1 -0
  163. package/dist/platform-expo/src/index.js +198 -0
  164. package/dist/platform-expo/src/index.js.map +1 -0
  165. package/dist/platform-expo/src/react.d.ts +26 -0
  166. package/dist/platform-expo/src/react.d.ts.map +1 -0
  167. package/dist/platform-expo/src/react.js +30 -0
  168. package/dist/platform-expo/src/react.js.map +1 -0
  169. package/dist/platform-node/src/index.d.ts +145 -0
  170. package/dist/platform-node/src/index.d.ts.map +1 -0
  171. package/dist/platform-node/src/index.js +407 -0
  172. package/dist/platform-node/src/index.js.map +1 -0
  173. package/dist/platform-node/src/ipc-react.d.ts +25 -0
  174. package/dist/platform-node/src/ipc-react.d.ts.map +1 -0
  175. package/dist/platform-node/src/ipc-react.js +21 -0
  176. package/dist/platform-node/src/ipc-react.js.map +1 -0
  177. package/dist/platform-node/src/ipc.d.ts +76 -0
  178. package/dist/platform-node/src/ipc.d.ts.map +1 -0
  179. package/dist/platform-node/src/ipc.js +344 -0
  180. package/dist/platform-node/src/ipc.js.map +1 -0
  181. package/dist/platform-web/src/index.d.ts +106 -0
  182. package/dist/platform-web/src/index.d.ts.map +1 -0
  183. package/dist/platform-web/src/index.js +311 -0
  184. package/dist/platform-web/src/index.js.map +1 -0
  185. package/dist/platform-web/src/indexeddb.js +125 -0
  186. package/dist/platform-web/src/indexeddb.js.map +1 -0
  187. package/dist/platform-web/src/opfs.js +146 -0
  188. package/dist/platform-web/src/opfs.js.map +1 -0
  189. package/dist/platform-web/src/persistence.d.ts +20 -0
  190. package/dist/platform-web/src/persistence.d.ts.map +1 -0
  191. package/dist/platform-web/src/persistence.js +23 -0
  192. package/dist/platform-web/src/persistence.js.map +1 -0
  193. package/dist/platform-web/src/react.d.ts +35 -0
  194. package/dist/platform-web/src/react.d.ts.map +1 -0
  195. package/dist/platform-web/src/react.js +42 -0
  196. package/dist/platform-web/src/react.js.map +1 -0
  197. package/dist/platform-web/src/sqljs.js +133 -0
  198. package/dist/platform-web/src/sqljs.js.map +1 -0
  199. package/dist/platform-web/src/worker.d.ts +79 -0
  200. package/dist/platform-web/src/worker.d.ts.map +1 -0
  201. package/dist/platform-web/src/worker.js +308 -0
  202. package/dist/platform-web/src/worker.js.map +1 -0
  203. package/dist/react/src/index.d.ts +59 -0
  204. package/dist/react/src/index.d.ts.map +1 -0
  205. package/dist/react/src/index.js +151 -0
  206. package/dist/react/src/index.js.map +1 -0
  207. package/dist/react.d.ts +2 -0
  208. package/dist/react.js +2 -0
  209. package/dist/schema/src/definition.d.ts +98 -0
  210. package/dist/schema/src/definition.d.ts.map +1 -0
  211. package/dist/schema/src/definition.js +84 -0
  212. package/dist/schema/src/definition.js.map +1 -0
  213. package/dist/schema/src/planner.d.ts +42 -0
  214. package/dist/schema/src/planner.d.ts.map +1 -0
  215. package/dist/schema/src/planner.js +131 -0
  216. package/dist/schema/src/planner.js.map +1 -0
  217. package/dist/schema/src/validators.d.ts +194 -0
  218. package/dist/schema/src/validators.d.ts.map +1 -0
  219. package/dist/schema/src/validators.js +158 -0
  220. package/dist/schema/src/validators.js.map +1 -0
  221. package/dist/svelte/src/index.d.ts +44 -0
  222. package/dist/svelte/src/index.d.ts.map +1 -0
  223. package/dist/svelte/src/index.js +75 -0
  224. package/dist/svelte/src/index.js.map +1 -0
  225. package/dist/svelte.d.ts +2 -0
  226. package/dist/svelte.js +2 -0
  227. package/package.json +152 -0
@@ -0,0 +1,1143 @@
1
+ import { createSchemaSnapshot, diffSchemaSnapshots, parseSchemaSnapshot, renderCreateSearchIndexStatement, renderMigrationSql, searchIndexTableName } from "../../../schema/src/planner.js";
2
+ import { generateId } from "./id.js";
3
+ import { fromZonedTime, toZonedTime } from "date-fns-tz";
4
+ //#region ../core/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.js.map