syncorejs 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +2 -1
  2. package/dist/_vendor/cli/app.d.mts.map +1 -1
  3. package/dist/_vendor/cli/app.mjs +330 -46
  4. package/dist/_vendor/cli/app.mjs.map +1 -1
  5. package/dist/_vendor/cli/context.mjs +27 -9
  6. package/dist/_vendor/cli/context.mjs.map +1 -1
  7. package/dist/_vendor/cli/dev-session.mjs.map +1 -1
  8. package/dist/_vendor/cli/doctor.mjs +513 -46
  9. package/dist/_vendor/cli/doctor.mjs.map +1 -1
  10. package/dist/_vendor/cli/errors.mjs.map +1 -1
  11. package/dist/_vendor/cli/help.mjs.map +1 -1
  12. package/dist/_vendor/cli/index.mjs +9 -2
  13. package/dist/_vendor/cli/index.mjs.map +1 -1
  14. package/dist/_vendor/cli/messages.mjs +5 -4
  15. package/dist/_vendor/cli/messages.mjs.map +1 -1
  16. package/dist/_vendor/cli/preflight.mjs.map +1 -1
  17. package/dist/_vendor/cli/project.mjs +125 -27
  18. package/dist/_vendor/cli/project.mjs.map +1 -1
  19. package/dist/_vendor/cli/render.mjs +57 -9
  20. package/dist/_vendor/cli/render.mjs.map +1 -1
  21. package/dist/_vendor/cli/targets.mjs +4 -3
  22. package/dist/_vendor/cli/targets.mjs.map +1 -1
  23. package/dist/_vendor/core/cli.d.mts +20 -4
  24. package/dist/_vendor/core/cli.d.mts.map +1 -1
  25. package/dist/_vendor/core/cli.mjs +458 -133
  26. package/dist/_vendor/core/cli.mjs.map +1 -1
  27. package/dist/_vendor/core/devtools-auth.mjs +60 -0
  28. package/dist/_vendor/core/devtools-auth.mjs.map +1 -0
  29. package/dist/_vendor/core/index.d.mts +5 -3
  30. package/dist/_vendor/core/index.mjs +22 -2
  31. package/dist/_vendor/core/index.mjs.map +1 -1
  32. package/dist/_vendor/core/runtime/components.d.mts +111 -0
  33. package/dist/_vendor/core/runtime/components.d.mts.map +1 -0
  34. package/dist/_vendor/core/runtime/components.mjs +186 -0
  35. package/dist/_vendor/core/runtime/components.mjs.map +1 -0
  36. package/dist/_vendor/core/runtime/devtools.d.mts +4 -4
  37. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  38. package/dist/_vendor/core/runtime/devtools.mjs +178 -60
  39. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  40. package/dist/_vendor/core/runtime/functions.d.mts +398 -16
  41. package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
  42. package/dist/_vendor/core/runtime/functions.mjs +74 -3
  43. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  44. package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
  45. package/dist/_vendor/core/runtime/id.mjs.map +1 -1
  46. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +83 -0
  47. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -0
  48. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +720 -0
  49. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -0
  50. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +234 -0
  51. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -0
  52. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +255 -0
  53. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -0
  54. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +200 -0
  55. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -0
  56. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +252 -0
  57. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -0
  58. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +145 -0
  59. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -0
  60. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +221 -0
  61. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -0
  62. package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs +32 -0
  63. package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -0
  64. package/dist/_vendor/core/runtime/internal/systemMeta.mjs +61 -0
  65. package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -0
  66. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +41 -0
  67. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -0
  68. package/dist/_vendor/core/runtime/runtime.d.mts +1187 -202
  69. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  70. package/dist/_vendor/core/runtime/runtime.mjs +73 -1365
  71. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  72. package/dist/_vendor/core/transport.d.mts +113 -0
  73. package/dist/_vendor/core/transport.d.mts.map +1 -0
  74. package/dist/_vendor/core/transport.mjs +428 -0
  75. package/dist/_vendor/core/transport.mjs.map +1 -0
  76. package/dist/_vendor/devtools-protocol/index.d.ts +187 -4
  77. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  78. package/dist/_vendor/devtools-protocol/index.js +25 -9
  79. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  80. package/dist/_vendor/next/config.d.ts +3 -4
  81. package/dist/_vendor/next/config.d.ts.map +1 -1
  82. package/dist/_vendor/next/config.js +37 -19
  83. package/dist/_vendor/next/config.js.map +1 -1
  84. package/dist/_vendor/next/index.d.ts +109 -29
  85. package/dist/_vendor/next/index.d.ts.map +1 -1
  86. package/dist/_vendor/next/index.js +104 -26
  87. package/dist/_vendor/next/index.js.map +1 -1
  88. package/dist/_vendor/platform-expo/index.d.ts +156 -37
  89. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  90. package/dist/_vendor/platform-expo/index.js +80 -12
  91. package/dist/_vendor/platform-expo/index.js.map +1 -1
  92. package/dist/_vendor/platform-expo/react.d.ts.map +1 -1
  93. package/dist/_vendor/platform-expo/react.js +11 -10
  94. package/dist/_vendor/platform-expo/react.js.map +1 -1
  95. package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
  96. package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
  97. package/dist/_vendor/platform-node/index.d.mts +192 -24
  98. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  99. package/dist/_vendor/platform-node/index.mjs +236 -97
  100. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  101. package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -1
  102. package/dist/_vendor/platform-node/ipc-react.mjs +15 -2
  103. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  104. package/dist/_vendor/platform-node/ipc.d.mts +11 -35
  105. package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
  106. package/dist/_vendor/platform-node/ipc.mjs +3 -273
  107. package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
  108. package/dist/_vendor/platform-web/external-change.d.ts +43 -1
  109. package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
  110. package/dist/_vendor/platform-web/external-change.js +32 -1
  111. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  112. package/dist/_vendor/platform-web/index.d.ts +323 -51
  113. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  114. package/dist/_vendor/platform-web/index.js +233 -30
  115. package/dist/_vendor/platform-web/index.js.map +1 -1
  116. package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
  117. package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
  118. package/dist/_vendor/platform-web/indexeddb.js +10 -0
  119. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  120. package/dist/_vendor/platform-web/opfs.d.ts +13 -0
  121. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  122. package/dist/_vendor/platform-web/opfs.js +12 -0
  123. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  124. package/dist/_vendor/platform-web/persistence.d.ts +54 -0
  125. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  126. package/dist/_vendor/platform-web/persistence.js +15 -0
  127. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  128. package/dist/_vendor/platform-web/react.d.ts +1 -2
  129. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  130. package/dist/_vendor/platform-web/react.js +27 -13
  131. package/dist/_vendor/platform-web/react.js.map +1 -1
  132. package/dist/_vendor/platform-web/sqljs.js +10 -1
  133. package/dist/_vendor/platform-web/sqljs.js.map +1 -1
  134. package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
  135. package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
  136. package/dist/_vendor/platform-web/worker.d.ts +71 -44
  137. package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
  138. package/dist/_vendor/platform-web/worker.js +40 -271
  139. package/dist/_vendor/platform-web/worker.js.map +1 -1
  140. package/dist/_vendor/react/index.d.ts +222 -23
  141. package/dist/_vendor/react/index.d.ts.map +1 -1
  142. package/dist/_vendor/react/index.js +476 -63
  143. package/dist/_vendor/react/index.js.map +1 -1
  144. package/dist/_vendor/schema/definition.d.ts +151 -37
  145. package/dist/_vendor/schema/definition.d.ts.map +1 -1
  146. package/dist/_vendor/schema/definition.js +102 -20
  147. package/dist/_vendor/schema/definition.js.map +1 -1
  148. package/dist/_vendor/schema/index.d.ts +4 -4
  149. package/dist/_vendor/schema/index.js +2 -2
  150. package/dist/_vendor/schema/planner.d.ts +19 -2
  151. package/dist/_vendor/schema/planner.d.ts.map +1 -1
  152. package/dist/_vendor/schema/planner.js +79 -3
  153. package/dist/_vendor/schema/planner.js.map +1 -1
  154. package/dist/_vendor/schema/validators.d.ts +279 -83
  155. package/dist/_vendor/schema/validators.d.ts.map +1 -1
  156. package/dist/_vendor/schema/validators.js +330 -38
  157. package/dist/_vendor/schema/validators.js.map +1 -1
  158. package/dist/_vendor/svelte/index.d.ts +245 -19
  159. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  160. package/dist/_vendor/svelte/index.js +443 -20
  161. package/dist/_vendor/svelte/index.js.map +1 -1
  162. package/dist/browser.d.ts.map +1 -1
  163. package/dist/cli.js +3 -1
  164. package/dist/cli.js.map +1 -1
  165. package/dist/components.d.ts +2 -0
  166. package/dist/components.js +2 -0
  167. package/dist/index.d.ts +3 -2
  168. package/dist/index.js +2 -1
  169. package/package.json +29 -21
@@ -3,6 +3,7 @@ import { generateId } from "./runtime/id.mjs";
3
3
  import { SyncoreRuntime } from "./runtime/runtime.mjs";
4
4
  import { createDevtoolsCommandHandler, createDevtoolsSubscriptionHost } from "./runtime/devtools.mjs";
5
5
  import { src_exports } from "./index.mjs";
6
+ import { generateDevtoolsToken, isAllowedDashboardOrigin, isAuthorizedDashboardRequest, sanitizeDevtoolsToken } from "./devtools-auth.mjs";
6
7
  import { appendFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
8
  import { createServer } from "node:http";
8
9
  import { connect } from "node:net";
@@ -12,16 +13,22 @@ import { DatabaseSync } from "node:sqlite";
12
13
  import { Command } from "commander";
13
14
  import { tsImport } from "tsx/esm/api";
14
15
  import WebSocket, { WebSocketServer } from "ws";
15
- import { createPublicRuntimeId, createPublicTargetId } from "../devtools-protocol/index.js";
16
+ import { SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION, SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION, SYNCORE_DEVTOOLS_PROTOCOL_VERSION, createPublicRuntimeId, createPublicTargetId } from "../devtools-protocol/index.js";
16
17
  //#region src/cli.ts
17
- const COMBINED_DEV_COMMAND = "concurrently --kill-others-on-fail --names syncore,app --prefix-colors yellow,cyan \"bun run syncorejs:dev\" \"bun run dev:app\"";
18
+ function templateUsesConnectedClients(template) {
19
+ return template === "react-web" || template === "svelte" || template === "expo" || template === "next";
20
+ }
21
+ const loadedTypeScriptModules = /* @__PURE__ */ new Map();
22
+ const COMBINED_DEV_COMMAND = "concurrently --kill-others-on-fail --names syncore,app --prefix-colors yellow,cyan \"npm run syncorejs:dev\" \"npm run dev:app\"";
18
23
  const program = new Command();
19
24
  const CORE_PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
25
+ const DEVTOOLS_SESSION_FILE = path.join(".syncore", "devtools-session.json");
20
26
  const SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME = "_schema_snapshot.json";
21
27
  const VALID_SYNCORE_TEMPLATES = [
22
28
  "minimal",
23
29
  "node",
24
30
  "react-web",
31
+ "svelte",
25
32
  "expo",
26
33
  "electron",
27
34
  "next"
@@ -123,8 +130,10 @@ if (isCliEntryPoint()) await runSyncoreCli();
123
130
  async function runCodegen(cwd) {
124
131
  const functionsDir = path.join(cwd, "syncore", "functions");
125
132
  const generatedDir = path.join(cwd, "syncore", "_generated");
133
+ const componentsManifestPath = path.join(cwd, "syncore", "components.ts");
126
134
  await mkdir(generatedDir, { recursive: true });
127
135
  const functionImportExtension = await resolveFunctionImportExtension(cwd);
136
+ const hasComponentsManifest = await hasNonEmptyComponentsManifest(componentsManifestPath);
128
137
  const files = await listTypeScriptFiles(functionsDir);
129
138
  const functionEntries = [];
130
139
  for (const file of files) {
@@ -148,6 +157,8 @@ async function runCodegen(cwd) {
148
157
  ``,
149
158
  `import { createFunctionReferenceFor } from "syncorejs";`,
150
159
  `import type { FunctionReferenceFor } from "syncorejs";`,
160
+ `export { components } from "./components${functionImportExtension}";`,
161
+ ``,
151
162
  ...renderFunctionTypeImports(functionEntries, functionImportExtension),
152
163
  ``,
153
164
  ...renderGeneratedApiInterfaces(functionEntries),
@@ -174,9 +185,12 @@ async function runCodegen(cwd) {
174
185
  ` */`,
175
186
  ``,
176
187
  `import type { SyncoreFunctionRegistry } from "syncorejs";`,
177
- ``,
188
+ `import { composeProjectFunctionRegistry } from "syncorejs";`,
189
+ ...renderGeneratedManifestImportLines(hasComponentsManifest, functionImportExtension),
178
190
  ...renderFunctionImports(functionEntries, functionImportExtension),
179
191
  ``,
192
+ ...renderGeneratedManifestDeclarationLines(hasComponentsManifest),
193
+ ``,
180
194
  ...renderGeneratedFunctionsInterface(functionEntries),
181
195
  ``,
182
196
  `/**`,
@@ -184,9 +198,72 @@ async function runCodegen(cwd) {
184
198
  ` *`,
185
199
  ` * Most application code should import from \`./api\` instead of using this map directly.`,
186
200
  ` */`,
187
- `export const functions: SyncoreFunctionsRegistry = {`,
201
+ `const rootFunctions: SyncoreRootFunctionsRegistry = {`,
188
202
  ...functionEntries.map((entry) => ` ${JSON.stringify(`${entry.pathParts.join("/")}/${entry.exportName}`)}: ${renderFunctionImportName(entry)},`),
189
203
  `} as const;`,
204
+ ``,
205
+ `export const functions: SyncoreFunctionRegistry = composeProjectFunctionRegistry(rootFunctions, componentsManifest);`,
206
+ ``
207
+ ].join("\n");
208
+ const schemaSource = [
209
+ `/**`,
210
+ ` * Generated composed Syncore schema including installed components.`,
211
+ ` *`,
212
+ ` * THIS CODE IS AUTOMATICALLY GENERATED.`,
213
+ ` *`,
214
+ ` * To regenerate, run \`npx syncorejs dev\` or \`npx syncorejs codegen\`.`,
215
+ ` * @module`,
216
+ ` */`,
217
+ ``,
218
+ `import { composeProjectSchema } from "syncorejs";`,
219
+ `import rootSchema from "../schema${functionImportExtension}";`,
220
+ ...renderGeneratedManifestImportLines(hasComponentsManifest, functionImportExtension),
221
+ ``,
222
+ ...renderGeneratedManifestDeclarationLines(hasComponentsManifest),
223
+ ``,
224
+ `const schema = composeProjectSchema(rootSchema, componentsManifest);`,
225
+ ``,
226
+ `export default schema;`,
227
+ ``
228
+ ].join("\n");
229
+ const componentsSource = [
230
+ `/**`,
231
+ ` * Generated installed-component helpers for this Syncore app.`,
232
+ ` *`,
233
+ ` * THIS CODE IS AUTOMATICALLY GENERATED.`,
234
+ ` *`,
235
+ ` * To regenerate, run \`npx syncorejs dev\` or \`npx syncorejs codegen\`.`,
236
+ ` * @module`,
237
+ ` */`,
238
+ ``,
239
+ `import { createInstalledComponentsApi, resolveComponentsManifest } from "syncorejs";`,
240
+ ...renderGeneratedManifestImportLines(hasComponentsManifest, functionImportExtension),
241
+ ``,
242
+ ...renderGeneratedManifestDeclarationLines(hasComponentsManifest),
243
+ ``,
244
+ `export const components = createInstalledComponentsApi(componentsManifest);`,
245
+ ``,
246
+ `export const resolvedComponents = resolveComponentsManifest(componentsManifest);`,
247
+ ``,
248
+ `export default resolvedComponents;`,
249
+ ``
250
+ ].join("\n");
251
+ const runtimeSource = [
252
+ `/**`,
253
+ ` * Generated local runtime bundle for Syncore CLI and Node adapters.`,
254
+ ` *`,
255
+ ` * THIS CODE IS AUTOMATICALLY GENERATED.`,
256
+ ` *`,
257
+ ` * To regenerate, run \`npx syncorejs dev\` or \`npx syncorejs codegen\`.`,
258
+ ` * @module`,
259
+ ` */`,
260
+ ``,
261
+ `import schema from "./schema${functionImportExtension}";`,
262
+ `import { functions } from "./functions${functionImportExtension}";`,
263
+ ...hasComponentsManifest ? [`import { resolvedComponents } from "./components${functionImportExtension}";`] : [`const resolvedComponents = [] as const;`],
264
+ ``,
265
+ `export { functions, schema };`,
266
+ `export const components = resolvedComponents;`,
190
267
  ``
191
268
  ].join("\n");
192
269
  const serverSource = [
@@ -213,7 +290,7 @@ async function runCodegen(cwd) {
213
290
  ` ValidatorMap`,
214
291
  `} from "syncorejs";`,
215
292
  ``,
216
- `export { createFunctionReference, createFunctionReferenceFor, v } from "syncorejs";`,
293
+ `export { createFunctionReference, createFunctionReferenceFor, s } from "syncorejs";`,
217
294
  ``,
218
295
  `/**`,
219
296
  ` * The context object available inside Syncore query handlers in this app.`,
@@ -240,22 +317,14 @@ async function runCodegen(cwd) {
240
317
  ` * @param config - The query definition, including args and a handler.`,
241
318
  ` * @returns The wrapped query. Export it from \`syncore/functions\` to add it to the generated API.`,
242
319
  ` */`,
243
- `export function query<TValidator extends Validator<unknown>, TResult>(`,
244
- ` config: FunctionConfig<QueryCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
245
- `): SyncoreFunctionDefinition<"query", QueryCtx, Infer<TValidator>, TResult>;`,
246
- `export function query<TArgsShape extends ValidatorMap, TResult>(`,
247
- ` config: FunctionConfig<QueryCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
248
- `): SyncoreFunctionDefinition<"query", QueryCtx, InferArgs<TArgsShape>, TResult>;`,
249
- `export function query<TArgsShape extends Validator<unknown> | ValidatorMap, TResult>(`,
250
- ` config: FunctionConfig<QueryCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
251
- `) {`,
252
- ` return baseQuery(config as never) as SyncoreFunctionDefinition<`,
253
- ` "query",`,
254
- ` QueryCtx,`,
255
- ` InferArgs<TArgsShape>,`,
256
- ` TResult`,
257
- ` >;`,
258
- `}`,
320
+ `export const query = baseQuery as {`,
321
+ ` <TValidator extends Validator<unknown, unknown, string>, TResult>(`,
322
+ ` config: FunctionConfig<QueryCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
323
+ ` ): SyncoreFunctionDefinition<"query", QueryCtx, Infer<TValidator>, TResult>;`,
324
+ ` <TArgsShape extends ValidatorMap, TResult>(`,
325
+ ` config: FunctionConfig<QueryCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
326
+ ` ): SyncoreFunctionDefinition<"query", QueryCtx, InferArgs<TArgsShape>, TResult>;`,
327
+ `};`,
259
328
  ``,
260
329
  `/**`,
261
330
  ` * Define a mutation in this Syncore app's public API.`,
@@ -265,22 +334,14 @@ async function runCodegen(cwd) {
265
334
  ` * @param config - The mutation definition, including args and a handler.`,
266
335
  ` * @returns The wrapped mutation. Export it from \`syncore/functions\` to add it to the generated API.`,
267
336
  ` */`,
268
- `export function mutation<TValidator extends Validator<unknown>, TResult>(`,
269
- ` config: FunctionConfig<MutationCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
270
- `): SyncoreFunctionDefinition<"mutation", MutationCtx, Infer<TValidator>, TResult>;`,
271
- `export function mutation<TArgsShape extends ValidatorMap, TResult>(`,
272
- ` config: FunctionConfig<MutationCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
273
- `): SyncoreFunctionDefinition<"mutation", MutationCtx, InferArgs<TArgsShape>, TResult>;`,
274
- `export function mutation<TArgsShape extends Validator<unknown> | ValidatorMap, TResult>(`,
275
- ` config: FunctionConfig<MutationCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
276
- `) {`,
277
- ` return baseMutation(config as never) as SyncoreFunctionDefinition<`,
278
- ` "mutation",`,
279
- ` MutationCtx,`,
280
- ` InferArgs<TArgsShape>,`,
281
- ` TResult`,
282
- ` >;`,
283
- `}`,
337
+ `export const mutation = baseMutation as {`,
338
+ ` <TValidator extends Validator<unknown, unknown, string>, TResult>(`,
339
+ ` config: FunctionConfig<MutationCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
340
+ ` ): SyncoreFunctionDefinition<"mutation", MutationCtx, Infer<TValidator>, TResult>;`,
341
+ ` <TArgsShape extends ValidatorMap, TResult>(`,
342
+ ` config: FunctionConfig<MutationCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
343
+ ` ): SyncoreFunctionDefinition<"mutation", MutationCtx, InferArgs<TArgsShape>, TResult>;`,
344
+ `};`,
284
345
  ``,
285
346
  `/**`,
286
347
  ` * Define an action in this Syncore app's public API.`,
@@ -290,27 +351,28 @@ async function runCodegen(cwd) {
290
351
  ` * @param config - The action definition, including args and a handler.`,
291
352
  ` * @returns The wrapped action. Export it from \`syncore/functions\` to add it to the generated API.`,
292
353
  ` */`,
293
- `export function action<TValidator extends Validator<unknown>, TResult>(`,
294
- ` config: FunctionConfig<ActionCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
295
- `): SyncoreFunctionDefinition<"action", ActionCtx, Infer<TValidator>, TResult>;`,
296
- `export function action<TArgsShape extends ValidatorMap, TResult>(`,
297
- ` config: FunctionConfig<ActionCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
298
- `): SyncoreFunctionDefinition<"action", ActionCtx, InferArgs<TArgsShape>, TResult>;`,
299
- `export function action<TArgsShape extends Validator<unknown> | ValidatorMap, TResult>(`,
300
- ` config: FunctionConfig<ActionCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
301
- `) {`,
302
- ` return baseAction(config as never) as SyncoreFunctionDefinition<`,
303
- ` "action",`,
304
- ` ActionCtx,`,
305
- ` InferArgs<TArgsShape>,`,
306
- ` TResult`,
307
- ` >;`,
308
- `}`,
354
+ `export const action = baseAction as {`,
355
+ ` <TValidator extends Validator<unknown, unknown, string>, TResult>(`,
356
+ ` config: FunctionConfig<ActionCtx, Infer<TValidator>, TResult> & { args: TValidator }`,
357
+ ` ): SyncoreFunctionDefinition<"action", ActionCtx, Infer<TValidator>, TResult>;`,
358
+ ` <TArgsShape extends ValidatorMap, TResult>(`,
359
+ ` config: FunctionConfig<ActionCtx, InferArgs<TArgsShape>, TResult> & { args: TArgsShape }`,
360
+ ` ): SyncoreFunctionDefinition<"action", ActionCtx, InferArgs<TArgsShape>, TResult>;`,
361
+ `};`,
309
362
  ``
310
363
  ].join("\n");
311
- await writeFile(path.join(generatedDir, "api.ts"), apiSource);
312
- await writeFile(path.join(generatedDir, "functions.ts"), functionsSource);
313
- await writeFile(path.join(generatedDir, "server.ts"), serverSource);
364
+ await writeGeneratedFile(path.join(generatedDir, "api.ts"), apiSource);
365
+ await writeGeneratedFile(path.join(generatedDir, "components.ts"), componentsSource);
366
+ await writeGeneratedFile(path.join(generatedDir, "functions.ts"), functionsSource);
367
+ await writeGeneratedFile(path.join(generatedDir, "runtime.ts"), runtimeSource);
368
+ await writeGeneratedFile(path.join(generatedDir, "schema.ts"), schemaSource);
369
+ await writeGeneratedFile(path.join(generatedDir, "server.ts"), serverSource);
370
+ }
371
+ async function writeGeneratedFile(filePath, source) {
372
+ try {
373
+ if (await readFile(filePath, "utf8") === source) return;
374
+ } catch {}
375
+ await writeFile(filePath, source);
314
376
  }
315
377
  async function scaffoldProject(cwd, options) {
316
378
  const files = buildTemplateFiles(options.template);
@@ -345,19 +407,26 @@ function buildTemplateFiles(template) {
345
407
  },
346
408
  {
347
409
  path: path.join("syncore", "schema.ts"),
348
- content: `import { defineSchema, defineTable, v } from "syncorejs";
410
+ content: `import { defineSchema, defineTable, s } from "syncorejs";
349
411
 
350
412
  export default defineSchema({
351
413
  tasks: defineTable({
352
- text: v.string(),
353
- done: v.boolean()
414
+ text: s.string(),
415
+ done: s.boolean()
354
416
  }).index("by_done", ["done"])
355
417
  });
418
+ `
419
+ },
420
+ {
421
+ path: path.join("syncore", "components.ts"),
422
+ content: `import { defineComponents } from "syncorejs";
423
+
424
+ export default defineComponents({});
356
425
  `
357
426
  },
358
427
  {
359
428
  path: path.join("syncore", "functions", "tasks.ts"),
360
- content: `import { mutation, query, v } from "../_generated/server";
429
+ content: `import { mutation, query, s } from "../_generated/server";
361
430
 
362
431
  export const list = query({
363
432
  args: {},
@@ -366,7 +435,7 @@ export const list = query({
366
435
  });
367
436
 
368
437
  export const create = mutation({
369
- args: { text: v.string() },
438
+ args: { text: s.string() },
370
439
  handler: async (ctx, args) =>
371
440
  ctx.db.insert("tasks", { text: args.text, done: false })
372
441
  });
@@ -380,15 +449,13 @@ export const create = mutation({
380
449
  content: `/// <reference lib="webworker" />
381
450
 
382
451
  import { createBrowserWorkerRuntime } from "syncorejs/browser";
383
- import schema from "../syncore/schema";
452
+ import schema from "../syncore/_generated/schema";
384
453
  import { functions } from "../syncore/_generated/functions";
385
454
 
386
455
  void createBrowserWorkerRuntime({
387
456
  endpoint: self,
388
457
  schema,
389
- functions,
390
- databaseName: "syncore-app",
391
- persistenceMode: "opfs"
458
+ functions
392
459
  });
393
460
  `
394
461
  }, {
@@ -410,14 +477,12 @@ export function AppSyncoreProvider({ children }: { children: ReactNode }) {
410
477
  files.push({
411
478
  path: path.join("lib", "syncore.ts"),
412
479
  content: `import { createExpoSyncoreBootstrap } from "syncorejs/expo";
413
- import schema from "../syncore/schema";
480
+ import schema from "../syncore/_generated/schema";
414
481
  import { functions } from "../syncore/_generated/functions";
415
482
 
416
483
  export const syncore = createExpoSyncoreBootstrap({
417
484
  schema,
418
- functions,
419
- databaseName: "syncore-app.db",
420
- storageDirectoryName: "syncore-app-storage"
485
+ functions
421
486
  });
422
487
  `
423
488
  });
@@ -428,17 +493,13 @@ export const syncore = createExpoSyncoreBootstrap({
428
493
  content: `/* eslint-disable */
429
494
 
430
495
  import { createBrowserWorkerRuntime } from "syncorejs/browser";
431
- import schema from "../syncore/schema";
496
+ import schema from "../syncore/_generated/schema";
432
497
  import { functions } from "../syncore/_generated/functions";
433
498
 
434
499
  void createBrowserWorkerRuntime({
435
500
  endpoint: self,
436
501
  schema,
437
- functions,
438
- databaseName: "syncore-app",
439
- persistenceDatabaseName: "syncore-app",
440
- locateFile: () => "/sql-wasm.wasm",
441
- platform: "browser-worker"
502
+ functions
442
503
  });
443
504
  `
444
505
  }, {
@@ -469,7 +530,7 @@ export function AppSyncoreProvider({ children }: { children: ReactNode }) {
469
530
  content: `import path from "node:path";
470
531
  import { withNodeSyncoreClient } from "syncorejs/node";
471
532
  import { api } from "./syncore/_generated/api.ts";
472
- import schema from "./syncore/schema.ts";
533
+ import schema from "./syncore/_generated/schema.ts";
473
534
  import { functions } from "./syncore/_generated/functions.ts";
474
535
 
475
536
  await withNodeSyncoreClient(
@@ -493,7 +554,7 @@ await withNodeSyncoreClient(
493
554
  content: `import path from "node:path";
494
555
  import { app } from "electron";
495
556
  import { createNodeSyncoreRuntime } from "syncorejs/node";
496
- import schema from "../syncore/schema.js";
557
+ import schema from "../syncore/_generated/schema.js";
497
558
  import { functions } from "../syncore/_generated/functions.js";
498
559
 
499
560
  export function createAppSyncoreRuntime() {
@@ -506,6 +567,50 @@ export function createAppSyncoreRuntime() {
506
567
  platform: "electron-main"
507
568
  });
508
569
  }
570
+ `
571
+ });
572
+ break;
573
+ case "svelte":
574
+ files.push({
575
+ path: path.join("src", "syncore.worker.ts"),
576
+ content: `/// <reference lib="webworker" />
577
+
578
+ import { createBrowserWorkerRuntime } from "syncorejs/browser";
579
+ import schema from "../syncore/_generated/schema";
580
+ import { functions } from "../syncore/_generated/functions";
581
+
582
+ void createBrowserWorkerRuntime({
583
+ endpoint: self,
584
+ schema,
585
+ functions
586
+ });
587
+ `
588
+ }, {
589
+ path: path.join("src", "SyncoreProvider.svelte"),
590
+ content: `<script lang="ts">
591
+ import { onDestroy } from "svelte";
592
+ import type { Snippet } from "svelte";
593
+ import { createBrowserWorkerClient } from "syncorejs/browser";
594
+ import { setSyncoreClient } from "syncorejs/svelte";
595
+
596
+ interface Props {
597
+ children?: Snippet;
598
+ }
599
+
600
+ const { children }: Props = $props();
601
+
602
+ const managed = createBrowserWorkerClient({
603
+ workerUrl: new URL("./syncore.worker.ts", import.meta.url)
604
+ });
605
+
606
+ setSyncoreClient(managed.client);
607
+
608
+ onDestroy(() => {
609
+ void managed.dispose();
610
+ });
611
+ <\/script>
612
+
613
+ {@render children?.()}
509
614
  `
510
615
  });
511
616
  break;
@@ -554,7 +659,8 @@ async function detectProjectTemplate(cwd) {
554
659
  if ("expo" in dependencies || "react-native" in dependencies) return "expo";
555
660
  if ("electron" in dependencies) return "electron";
556
661
  if ("next" in dependencies) return "next";
557
- if ("vite" in dependencies || "@vitejs/plugin-react" in dependencies || await fileExists(path.join(cwd, "src", "main.tsx")) && "react" in dependencies) return "react-web";
662
+ if ("svelte" in dependencies || "@sveltejs/vite-plugin-svelte" in dependencies || "@sveltejs/kit" in dependencies || await fileExists(path.join(cwd, "src", "SyncoreProvider.svelte"))) return "svelte";
663
+ if ("vite" in dependencies || "@vitejs/plugin-react" in dependencies || await fileExists(path.join(cwd, "src", "syncore.worker.ts")) || await fileExists(path.join(cwd, "src", "syncore-provider.tsx")) || await fileExists(path.join(cwd, "src", "main.tsx")) && "react" in dependencies) return "react-web";
558
664
  if (packageJson) return "node";
559
665
  return "minimal";
560
666
  }
@@ -569,7 +675,16 @@ async function readPackageJson(cwd) {
569
675
  }
570
676
  async function ensurePackageScripts(cwd, template) {
571
677
  const packageJsonPath = path.join(cwd, "package.json");
572
- if (!await fileExists(packageJsonPath)) return;
678
+ if (!await fileExists(packageJsonPath)) {
679
+ await writeFile(packageJsonPath, `${JSON.stringify({
680
+ type: "module",
681
+ scripts: {
682
+ "syncorejs:dev": "syncorejs dev",
683
+ "syncorejs:codegen": "syncorejs codegen"
684
+ }
685
+ }, null, 2)}\n`);
686
+ return;
687
+ }
573
688
  const packageJson = await readPackageJson(cwd);
574
689
  if (!packageJson) return;
575
690
  const nextPackageJson = {
@@ -602,7 +717,7 @@ function maybeAddManagedDevScripts(packageJson, template) {
602
717
  scripts.dev = combinedDevCommand();
603
718
  }
604
719
  function supportsManagedCombinedDev(template) {
605
- return template === "next" || template === "react-web" || template === "electron";
720
+ return template === "next" || template === "react-web" || template === "svelte" || template === "electron";
606
721
  }
607
722
  function combinedDevCommand() {
608
723
  return COMBINED_DEV_COMMAND;
@@ -651,7 +766,7 @@ async function importJsonlIntoProject(cwd, tableName, sourcePath) {
651
766
  try {
652
767
  parsed = JSON.parse(line);
653
768
  } catch (error) {
654
- throw new Error(`Invalid JSON on line ${lineNumber} of ${sourcePath}: ${formatError(error)}`);
769
+ throw new Error(`Invalid JSON on line ${lineNumber} of ${sourcePath}: ${formatError(error)}`, { cause: error });
655
770
  }
656
771
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Line ${lineNumber} of ${sourcePath} must contain a JSON object.`);
657
772
  const payload = { ...parsed };
@@ -768,12 +883,19 @@ function renderGeneratedFunctionsInterface(functionEntries) {
768
883
  `/**`,
769
884
  ` * Type-safe runtime definitions for every function exported from \`syncore/functions\`.`,
770
885
  ` */`,
771
- `export interface SyncoreFunctionsRegistry extends SyncoreFunctionRegistry {`
886
+ `export interface SyncoreRootFunctionsRegistry extends SyncoreFunctionRegistry {`
772
887
  ];
773
888
  for (const entry of functionEntries.slice().sort((left, right) => `${left.pathParts.join("/")}/${left.exportName}`.localeCompare(`${right.pathParts.join("/")}/${right.exportName}`))) lines.push(` /**`, ` * Runtime definition for the public Syncore ${entry.kind} \`${entry.pathParts.join("/")}/${entry.exportName}\`.`, ` */`, ` readonly ${JSON.stringify(`${entry.pathParts.join("/")}/${entry.exportName}`)}: typeof ${renderFunctionImportName(entry)};`);
774
889
  lines.push(`}`);
775
890
  return [lines.join("\n")];
776
891
  }
892
+ function renderGeneratedManifestImportLines(hasComponentsManifest, extension) {
893
+ if (!hasComponentsManifest) return [];
894
+ return [`import componentsManifest from "../components${extension}";`];
895
+ }
896
+ function renderGeneratedManifestDeclarationLines(hasComponentsManifest) {
897
+ return hasComponentsManifest ? [] : [`const componentsManifest = {} as const;`];
898
+ }
777
899
  function renderApiInterfaceName(node) {
778
900
  if (node.pathParts.length === 0) return "SyncoreApi";
779
901
  return `SyncoreApi__${node.pathParts.map(toTypeNamePart).join("__")}`;
@@ -831,15 +953,28 @@ function requireProjectTargetConfig(config) {
831
953
  if (!projectTarget) throw new Error("This Syncore project does not define a projectTarget. Use a connected client target instead.");
832
954
  return projectTarget;
833
955
  }
834
- async function loadProjectSchema(cwd) {
956
+ async function loadProjectRootSchema(cwd) {
835
957
  const schema = await loadDefaultExport(path.join(cwd, "syncore", "schema.ts"));
836
958
  if (!schema || typeof schema !== "object" || typeof schema.tableNames !== "function") throw new Error("syncore/schema.ts must default export defineSchema(...).");
837
959
  return schema;
838
960
  }
961
+ async function loadProjectComponentsManifest(cwd) {
962
+ const filePath = path.join(cwd, "syncore", "components.ts");
963
+ if (!await fileExists(filePath)) return {};
964
+ const manifest = await loadDefaultExport(filePath);
965
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) throw new Error("syncore/components.ts must default export defineComponents({...}).");
966
+ return manifest;
967
+ }
968
+ async function loadProjectSchema(cwd) {
969
+ const filePath = path.join(cwd, "syncore", "_generated", "schema.ts");
970
+ if (!await fileExists(filePath)) await runCodegen(cwd);
971
+ const schema = await loadDefaultExport(filePath);
972
+ if (!schema || typeof schema !== "object" || typeof schema.tableNames !== "function") throw new Error("syncore/_generated/schema.ts must default export a composed Syncore schema.");
973
+ return schema;
974
+ }
839
975
  async function loadDefaultExport(filePath) {
840
976
  if (!await fileExists(filePath)) throw new Error(`Missing file: ${path.relative(process.cwd(), filePath)}`);
841
- const moduleUrl = pathToFileURL(filePath).href;
842
- const loaded = await tsImport(moduleUrl, { parentURL: import.meta.url });
977
+ const loaded = await loadTypeScriptModule(filePath);
843
978
  if (!("default" in loaded)) throw new Error(`File ${path.relative(process.cwd(), filePath)} must have a default export.`);
844
979
  const resolvedDefault = unwrapDefaultExport(loaded.default);
845
980
  if (resolvedDefault === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined.`);
@@ -847,24 +982,94 @@ async function loadDefaultExport(filePath) {
847
982
  }
848
983
  async function loadNamedExport(filePath, exportName) {
849
984
  if (!await fileExists(filePath)) throw new Error(`Missing file: ${path.relative(process.cwd(), filePath)}`);
850
- const moduleUrl = pathToFileURL(filePath).href;
851
- const loaded = await tsImport(moduleUrl, { parentURL: import.meta.url });
985
+ const loaded = await loadTypeScriptModule(filePath);
852
986
  const defaultExport = loaded.default && typeof loaded.default === "object" && exportName in loaded.default ? loaded.default[exportName] : void 0;
853
987
  if (!(exportName in loaded) && defaultExport === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} must export ${exportName}.`);
854
988
  const resolvedValue = unwrapDefaultExport(loaded[exportName] ?? defaultExport);
855
989
  if (resolvedValue === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined for ${exportName}.`);
856
990
  return resolvedValue;
857
991
  }
992
+ async function hasNonEmptyComponentsManifest(filePath) {
993
+ if (!await fileExists(filePath)) return false;
994
+ const source = await readFile(filePath, "utf8");
995
+ return !/defineComponents\s*\(\s*\{\s*\}\s*\)/.test(source);
996
+ }
997
+ async function loadTypeScriptModule(filePath) {
998
+ const fileStat = await stat(filePath);
999
+ const cached = loadedTypeScriptModules.get(filePath);
1000
+ if (cached && cached.mtimeMs === fileStat.mtimeMs) return await cached.loaded;
1001
+ const moduleUrl = pathToFileURL(filePath).href;
1002
+ const loaded = tsImport(moduleUrl, { parentURL: import.meta.url });
1003
+ loadedTypeScriptModules.set(filePath, {
1004
+ mtimeMs: fileStat.mtimeMs,
1005
+ loaded
1006
+ });
1007
+ return await loaded;
1008
+ }
858
1009
  async function loadProjectFunctions(cwd) {
859
- const functions = await loadNamedExport(path.join(cwd, "syncore", "_generated", "functions.ts"), "functions");
1010
+ const filePath = path.join(cwd, "syncore", "_generated", "functions.ts");
1011
+ if (!await fileExists(filePath)) await runCodegen(cwd);
1012
+ const functions = await loadNamedExport(filePath, "functions");
860
1013
  if (!functions || typeof functions !== "object") throw new Error("syncore/_generated/functions.ts must export a functions registry.");
861
1014
  return functions;
862
1015
  }
1016
+ async function loadProjectResolvedComponents(cwd) {
1017
+ const componentsManifestPath = path.join(cwd, "syncore", "components.ts");
1018
+ if (await fileExists(componentsManifestPath)) {
1019
+ const source = await readFile(componentsManifestPath, "utf8");
1020
+ if (/defineComponents\s*\(\s*\{\s*\}\s*\)/.test(source)) return [];
1021
+ }
1022
+ const filePath = path.join(cwd, "syncore", "_generated", "components.ts");
1023
+ if (!await fileExists(filePath)) await runCodegen(cwd);
1024
+ const components = await loadNamedExport(filePath, "resolvedComponents");
1025
+ if (!Array.isArray(components)) throw new Error("syncore/_generated/components.ts must export resolvedComponents.");
1026
+ return components;
1027
+ }
1028
+ async function loadProjectRuntime(cwd) {
1029
+ const filePath = path.join(cwd, "syncore", "_generated", "runtime.ts");
1030
+ if (!await fileExists(filePath)) await runCodegen(cwd);
1031
+ const loaded = await loadTypeScriptModule(filePath);
1032
+ const schema = unwrapDefaultExport(loaded.schema);
1033
+ const functions = unwrapDefaultExport(loaded.functions);
1034
+ const components = unwrapDefaultExport(loaded.components);
1035
+ if (!schema || typeof schema !== "object" || typeof schema.tableNames !== "function") throw new Error("syncore/_generated/runtime.ts must export a composed Syncore schema.");
1036
+ if (!functions || typeof functions !== "object") throw new Error("syncore/_generated/runtime.ts must export a functions registry.");
1037
+ if (!Array.isArray(components)) throw new Error("syncore/_generated/runtime.ts must export resolved components.");
1038
+ return {
1039
+ schema,
1040
+ functions,
1041
+ components
1042
+ };
1043
+ }
1044
+ var HubExternalChangeSignal = class {
1045
+ publishToHub;
1046
+ listeners = /* @__PURE__ */ new Set();
1047
+ constructor(publishToHub) {
1048
+ this.publishToHub = publishToHub;
1049
+ }
1050
+ subscribe(listener) {
1051
+ this.listeners.add(listener);
1052
+ return () => {
1053
+ this.listeners.delete(listener);
1054
+ };
1055
+ }
1056
+ publish(event) {
1057
+ return this.publishToHub(event);
1058
+ }
1059
+ receive(event) {
1060
+ for (const listener of this.listeners) listener(event);
1061
+ }
1062
+ close() {
1063
+ this.listeners.clear();
1064
+ }
1065
+ };
863
1066
  var HubSqliteDriver = class {
1067
+ databasePath;
864
1068
  database;
865
1069
  transactionDepth = 0;
866
- constructor(filename) {
867
- this.database = new DatabaseSync(filename);
1070
+ constructor(databasePath) {
1071
+ this.databasePath = databasePath;
1072
+ this.database = new DatabaseSync(databasePath);
868
1073
  this.database.exec("PRAGMA foreign_keys = ON;");
869
1074
  this.database.exec("PRAGMA journal_mode = WAL;");
870
1075
  }
@@ -917,6 +1122,7 @@ var HubSqliteDriver = class {
917
1122
  }
918
1123
  };
919
1124
  var HubFileStorageAdapter = class {
1125
+ directory;
920
1126
  constructor(directory) {
921
1127
  this.directory = directory;
922
1128
  }
@@ -1030,11 +1236,10 @@ const hubDevtoolsSqlSupport = {
1030
1236
  }
1031
1237
  }
1032
1238
  };
1033
- async function createProjectTargetBackend(cwd) {
1239
+ async function createProjectTargetBackend(cwd, externalChangeSignal) {
1034
1240
  const projectTarget = resolveProjectTargetConfig(await loadProjectConfig(cwd));
1035
1241
  if (!projectTarget) return null;
1036
- const schema = await loadProjectSchema(cwd);
1037
- const functions = await loadProjectFunctions(cwd);
1242
+ const { schema, functions, components } = await loadProjectRuntime(cwd);
1038
1243
  const databasePath = path.resolve(cwd, projectTarget.databasePath);
1039
1244
  const storageDirectory = path.resolve(cwd, projectTarget.storageDirectory);
1040
1245
  await mkdir(path.dirname(databasePath), { recursive: true });
@@ -1043,35 +1248,42 @@ async function createProjectTargetBackend(cwd) {
1043
1248
  const runtime = new SyncoreRuntime({
1044
1249
  schema,
1045
1250
  functions,
1251
+ components,
1046
1252
  driver,
1047
1253
  storage: new HubFileStorageAdapter(storageDirectory),
1048
- platform: "project"
1254
+ platform: "project",
1255
+ ...externalChangeSignal ? { externalChangeSignal } : {}
1049
1256
  });
1050
- await runtime.prepareForDirectAccess();
1257
+ await runtime.start();
1051
1258
  const commandHandler = createDevtoolsCommandHandler({
1052
1259
  driver,
1053
1260
  schema,
1054
1261
  functions,
1055
- runtime,
1262
+ admin: runtime.getAdmin(),
1056
1263
  sql: hubDevtoolsSqlSupport
1057
1264
  });
1058
1265
  const subscriptionHost = createDevtoolsSubscriptionHost({
1059
1266
  driver,
1060
1267
  schema,
1061
1268
  functions,
1062
- runtime,
1269
+ admin: runtime.getAdmin(),
1063
1270
  sql: hubDevtoolsSqlSupport
1064
1271
  });
1065
1272
  return {
1066
1273
  hello: {
1067
1274
  type: "hello",
1275
+ protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,
1276
+ minSupportedProtocolVersion: SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,
1277
+ maxSupportedProtocolVersion: SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,
1068
1278
  runtimeId: PROJECT_TARGET_RUNTIME_ID,
1069
1279
  platform: "project",
1070
1280
  sessionLabel: "Project Target",
1071
1281
  targetKind: "project",
1282
+ runtimeRole: "project-target",
1072
1283
  storageProtocol: "file",
1073
1284
  databaseLabel: path.basename(databasePath),
1074
- storageIdentity: `file::${databasePath}`
1285
+ storageIdentity: `file::${databasePath}`,
1286
+ capabilities: createProjectDevtoolsCapabilities()
1075
1287
  },
1076
1288
  handleCommand: commandHandler,
1077
1289
  subscribe(subscriptionId, payload, listener) {
@@ -1086,6 +1298,24 @@ async function createProjectTargetBackend(cwd) {
1086
1298
  }
1087
1299
  };
1088
1300
  }
1301
+ function createProjectDevtoolsCapabilities() {
1302
+ return {
1303
+ sql: {
1304
+ read: true,
1305
+ write: true,
1306
+ live: true
1307
+ },
1308
+ data: {
1309
+ browse: true,
1310
+ mutate: true,
1311
+ importExport: true
1312
+ },
1313
+ scheduler: {
1314
+ read: true,
1315
+ edit: true
1316
+ }
1317
+ };
1318
+ }
1089
1319
  function normalizeStorageInput(input) {
1090
1320
  if (typeof input === "string") return Buffer.from(input);
1091
1321
  if (input instanceof Uint8Array) return input;
@@ -1208,16 +1438,24 @@ function formatError(error) {
1208
1438
  }
1209
1439
  async function isLocalPortInUse(port) {
1210
1440
  return await new Promise((resolve) => {
1441
+ let settled = false;
1211
1442
  const socket = connect({
1212
1443
  host: "127.0.0.1",
1213
1444
  port
1214
1445
  });
1446
+ const finish = (inUse) => {
1447
+ if (settled) return;
1448
+ settled = true;
1449
+ clearTimeout(timeout);
1450
+ socket.destroy();
1451
+ resolve(inUse);
1452
+ };
1453
+ const timeout = setTimeout(() => finish(false), 100);
1215
1454
  socket.once("connect", () => {
1216
- socket.end();
1217
- resolve(true);
1455
+ finish(true);
1218
1456
  });
1219
1457
  socket.once("error", () => {
1220
- resolve(false);
1458
+ finish(false);
1221
1459
  });
1222
1460
  });
1223
1461
  }
@@ -1239,22 +1477,26 @@ function applyMigrationSql(database, sql, fileName) {
1239
1477
  async function startDevHub(options) {
1240
1478
  const dashboardPort = resolvePortFromEnv("SYNCORE_DASHBOARD_PORT", 4310);
1241
1479
  const devtoolsPort = resolvePortFromEnv("SYNCORE_DEVTOOLS_PORT", 4311);
1480
+ const dashboardUrl = `http://localhost:${dashboardPort}`;
1481
+ const devtoolsUrl = `ws://127.0.0.1:${devtoolsPort}`;
1242
1482
  const logsDirectory = path.join(options.cwd, ".syncore", "logs");
1243
1483
  const logFilePath = path.join(logsDirectory, "runtime.jsonl");
1484
+ const hubAccessToken = sanitizeDevtoolsToken(process.env.SYNCORE_DEVTOOLS_TOKEN) ?? generateDevtoolsToken();
1485
+ const sessionState = {
1486
+ dashboardUrl,
1487
+ authenticatedDashboardUrl: `${dashboardUrl}/?token=${hubAccessToken}`,
1488
+ devtoolsUrl,
1489
+ token: hubAccessToken
1490
+ };
1244
1491
  await mkdir(logsDirectory, { recursive: true });
1245
1492
  await writeFile(logFilePath, "");
1246
1493
  await runDevProjectBootstrap(options.cwd, options.template);
1247
1494
  await setupDevProjectWatch(options.cwd, options.template);
1248
1495
  if (await isLocalPortInUse(devtoolsPort)) {
1249
1496
  console.log(`Syncore devtools hub already running at ws://localhost:${devtoolsPort}. Reusing existing hub/dashboard.`);
1250
- return;
1251
- }
1252
- let projectTargetBackend = null;
1253
- try {
1254
- projectTargetBackend = await createProjectTargetBackend(options.cwd);
1255
- } catch (error) {
1256
- console.warn(`Project target fallback unavailable: ${formatError(error)}`);
1497
+ return await readDevtoolsSessionState(options.cwd) ?? sessionState;
1257
1498
  }
1499
+ await writeDevtoolsSessionState(options.cwd, sessionState);
1258
1500
  const httpServer = createServer((_request, response) => {
1259
1501
  response.writeHead(200, { "content-type": "application/json" });
1260
1502
  response.end(JSON.stringify({
@@ -1269,8 +1511,19 @@ async function startDevHub(options) {
1269
1511
  const socketRuntimeIds = /* @__PURE__ */ new Map();
1270
1512
  const dashboardSockets = /* @__PURE__ */ new Set();
1271
1513
  const dashboardSubscriptions = /* @__PURE__ */ new Map();
1514
+ let relayExternalChange = () => {};
1515
+ const projectTargetExternalChangeSignal = new HubExternalChangeSignal((event) => relayExternalChange(PROJECT_TARGET_RUNTIME_ID, runtimeHellos.get(PROJECT_TARGET_RUNTIME_ID)?.storageIdentity ?? "", event));
1516
+ let projectTargetBackend = null;
1517
+ try {
1518
+ projectTargetBackend = await createProjectTargetBackend(options.cwd, projectTargetExternalChangeSignal);
1519
+ } catch (error) {
1520
+ console.warn(`Project target fallback unavailable: ${formatError(error)}`);
1521
+ }
1272
1522
  const hello = {
1273
1523
  type: "hello",
1524
+ protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,
1525
+ minSupportedProtocolVersion: SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,
1526
+ maxSupportedProtocolVersion: SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,
1274
1527
  runtimeId: "syncore-dev-hub",
1275
1528
  platform: "dev"
1276
1529
  };
@@ -1278,12 +1531,30 @@ async function startDevHub(options) {
1278
1531
  runtimeHellos.set(PROJECT_TARGET_RUNTIME_ID, projectTargetBackend.hello);
1279
1532
  runtimeEvents.set(PROJECT_TARGET_RUNTIME_ID, []);
1280
1533
  }
1534
+ relayExternalChange = (sourceRuntimeId, storageIdentity, event) => {
1535
+ if (!storageIdentity) return;
1536
+ const message = {
1537
+ type: "external.change",
1538
+ runtimeId: sourceRuntimeId,
1539
+ storageIdentity,
1540
+ event
1541
+ };
1542
+ for (const [runtimeId, runtimeHello] of runtimeHellos) {
1543
+ if (runtimeId === sourceRuntimeId || runtimeHello.storageIdentity !== storageIdentity) continue;
1544
+ if (runtimeId === PROJECT_TARGET_RUNTIME_ID) {
1545
+ projectTargetExternalChangeSignal.receive(event);
1546
+ continue;
1547
+ }
1548
+ const targetSocket = runtimeSockets.get(runtimeId);
1549
+ if (targetSocket?.readyState === WebSocket.OPEN) targetSocket.send(JSON.stringify(message));
1550
+ }
1551
+ };
1281
1552
  const appendHubLog = async (event) => {
1282
1553
  const runtimeHello = runtimeHellos.get(event.runtimeId);
1283
- const clientRuntimeIds = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.runtimeId).sort();
1284
- const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.storageIdentity ?? `runtime::${hello.runtimeId}`).sort();
1554
+ const clientRuntimeIds = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub").map((hello) => hello.runtimeId).sort();
1555
+ const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub").map((hello) => hello.storageIdentity ?? `runtime::${hello.runtimeId}`).sort();
1285
1556
  const targetIdentity = runtimeHello?.storageIdentity ?? `runtime::${event.runtimeId}`;
1286
- const targetId = event.runtimeId === "syncore-dev-hub" ? "all" : runtimeHello?.targetKind === "project" ? "project" : createPublicTargetId(targetIdentity, clientTargetKeys);
1557
+ const targetId = event.runtimeId === "syncore-dev-hub" ? "all" : createPublicTargetId(targetIdentity, clientTargetKeys);
1287
1558
  const publicRuntimeId = event.runtimeId === "syncore-dev-hub" ? void 0 : createPublicRuntimeId(event.runtimeId, clientRuntimeIds);
1288
1559
  const category = event.type === "query.executed" ? "query" : event.type === "mutation.committed" ? "mutation" : event.type === "action.completed" ? "action" : "system";
1289
1560
  const message = event.type === "log" ? event.message : event.type === "query.executed" || event.type === "mutation.committed" || event.type === "action.completed" ? event.functionName : event.type;
@@ -1300,7 +1571,18 @@ async function startDevHub(options) {
1300
1571
  event
1301
1572
  })}\n`);
1302
1573
  };
1303
- websocketServer.on("connection", (socket) => {
1574
+ websocketServer.on("connection", (socket, request) => {
1575
+ const isBrowserDashboardClient = isAllowedDashboardOrigin(request.headers.origin, dashboardPort);
1576
+ const isAuthorizedDashboardClient = !isBrowserDashboardClient || isAuthorizedDashboardRequest({
1577
+ requestUrl: request.url,
1578
+ originHeader: request.headers.origin,
1579
+ dashboardPort,
1580
+ expectedToken: hubAccessToken
1581
+ });
1582
+ if (isBrowserDashboardClient && !isAuthorizedDashboardClient) {
1583
+ socket.close(1008, "Unauthorized devtools client");
1584
+ return;
1585
+ }
1304
1586
  dashboardSockets.add(socket);
1305
1587
  socket.send(JSON.stringify(hello));
1306
1588
  for (const runtimeHello of runtimeHellos.values()) socket.send(JSON.stringify(runtimeHello));
@@ -1318,10 +1600,20 @@ async function startDevHub(options) {
1318
1600
  if (rawPayload.length === 0) return;
1319
1601
  const message = JSON.parse(rawPayload);
1320
1602
  if (message.type === "ping") {
1603
+ if (!isAuthorizedDashboardClient) {
1604
+ socket.close(1008, "Unauthorized devtools client");
1605
+ return;
1606
+ }
1321
1607
  socket.send(JSON.stringify({ type: "pong" }));
1322
1608
  return;
1323
1609
  }
1610
+ if (message.type === "external.change") {
1611
+ const storageIdentity = runtimeHellos.get(message.runtimeId)?.storageIdentity ?? message.storageIdentity;
1612
+ if (storageIdentity && storageIdentity === message.storageIdentity) relayExternalChange(message.runtimeId, storageIdentity, message.event);
1613
+ return;
1614
+ }
1324
1615
  if (message.type === "command") {
1616
+ if (!isAuthorizedDashboardClient) return;
1325
1617
  const targetRuntimeId = message.targetRuntimeId;
1326
1618
  if (!targetRuntimeId) return;
1327
1619
  if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
@@ -1342,6 +1634,7 @@ async function startDevHub(options) {
1342
1634
  return;
1343
1635
  }
1344
1636
  if (message.type === "subscribe") {
1637
+ if (!isAuthorizedDashboardClient) return;
1345
1638
  const targetRuntimeId = message.targetRuntimeId;
1346
1639
  if (!targetRuntimeId) return;
1347
1640
  const subscriptions = dashboardSubscriptions.get(socket) ?? /* @__PURE__ */ new Map();
@@ -1375,6 +1668,7 @@ async function startDevHub(options) {
1375
1668
  return;
1376
1669
  }
1377
1670
  if (message.type === "unsubscribe") {
1671
+ if (!isAuthorizedDashboardClient) return;
1378
1672
  const subscriptions = dashboardSubscriptions.get(socket);
1379
1673
  const subscription = subscriptions?.get(message.subscriptionId);
1380
1674
  if (!subscription) return;
@@ -1419,8 +1713,8 @@ async function startDevHub(options) {
1419
1713
  if (message.type === "event" && message.event.type === "runtime.disconnected") runtimeHellos.delete(message.event.runtimeId);
1420
1714
  if (message.type === "event" && message.event.runtimeId !== "syncore-dev-hub") {
1421
1715
  const history = runtimeEvents.get(message.event.runtimeId) ?? [];
1422
- history.unshift(message.event);
1423
- runtimeEvents.set(message.event.runtimeId, history.slice(0, 200));
1716
+ history.push(message.event);
1717
+ runtimeEvents.set(message.event.runtimeId, history.slice(-200));
1424
1718
  if (message.event.type === "runtime.disconnected") runtimeEvents.delete(message.event.runtimeId);
1425
1719
  appendHubLog(message.event);
1426
1720
  } else if (message.type === "event") appendHubLog(message.event);
@@ -1481,25 +1775,29 @@ async function startDevHub(options) {
1481
1775
  console.error(`Syncore devtools hub failed: ${formatError(error)}`);
1482
1776
  process.exit(1);
1483
1777
  });
1484
- httpServer.listen(devtoolsPort, "127.0.0.1", () => {
1485
- (async () => {
1486
- console.log(`Syncore devtools hub: ws://localhost:${devtoolsPort}`);
1487
- console.log(`Electron/Node runtimes: set devtoolsUrl to ws://localhost:${devtoolsPort}.`);
1488
- console.log(`Web/Next apps: connect the dashboard or worker bridge to ws://localhost:${devtoolsPort}.`);
1489
- console.log("Expo apps: use the same hub URL through LAN or adb reverse while developing.");
1490
- const dashboardRoot = path.resolve(CORE_PACKAGE_ROOT, "..", "..", "apps", "dashboard");
1491
- if (await fileExists(path.join(dashboardRoot, "vite.config.ts"))) try {
1492
- await (await (await import("vite")).createServer({
1493
- configFile: path.join(dashboardRoot, "vite.config.ts"),
1494
- root: dashboardRoot,
1495
- server: { port: dashboardPort }
1496
- })).listen();
1497
- console.log(`Dashboard shell: http://localhost:${dashboardPort}`);
1498
- } catch (error) {
1499
- console.log(`Dashboard source not started automatically: ${formatError(error)}`);
1500
- }
1501
- })();
1778
+ await new Promise((resolve, reject) => {
1779
+ httpServer.once("error", reject);
1780
+ httpServer.listen(devtoolsPort, "127.0.0.1", () => {
1781
+ httpServer.off("error", reject);
1782
+ resolve();
1783
+ });
1502
1784
  });
1785
+ console.log(`Syncore devtools hub: ws://localhost:${devtoolsPort}`);
1786
+ console.log(`Devtools dashboard token: ${hubAccessToken}`);
1787
+ console.log(`Electron/Node runtimes: set devtoolsUrl to ws://localhost:${devtoolsPort}.`);
1788
+ console.log(`Web/Next apps: connect the dashboard or worker bridge to ws://localhost:${devtoolsPort}.`);
1789
+ console.log("Expo apps: use the same hub URL through LAN or adb reverse while developing.");
1790
+ const dashboardRoot = path.resolve(CORE_PACKAGE_ROOT, "..", "..", "apps", "dashboard");
1791
+ if (await fileExists(path.join(dashboardRoot, "vite.config.ts"))) try {
1792
+ await (await (await import("vite")).createServer({
1793
+ configFile: path.join(dashboardRoot, "vite.config.ts"),
1794
+ root: dashboardRoot,
1795
+ server: { port: dashboardPort }
1796
+ })).listen();
1797
+ console.log(`Dashboard shell: ${sessionState.authenticatedDashboardUrl}`);
1798
+ } catch (error) {
1799
+ console.log(`Dashboard source not started automatically: ${formatError(error)}`);
1800
+ }
1503
1801
  const close = () => {
1504
1802
  projectTargetBackend?.dispose();
1505
1803
  websocketServer.close();
@@ -1508,6 +1806,29 @@ async function startDevHub(options) {
1508
1806
  };
1509
1807
  process.on("SIGINT", close);
1510
1808
  process.on("SIGTERM", close);
1809
+ return sessionState;
1810
+ }
1811
+ async function writeDevtoolsSessionState(cwd, state) {
1812
+ const sessionPath = path.join(cwd, DEVTOOLS_SESSION_FILE);
1813
+ await mkdir(path.dirname(sessionPath), { recursive: true });
1814
+ await writeFile(sessionPath, `${JSON.stringify(state, null, 2)}\n`);
1815
+ }
1816
+ async function readDevtoolsSessionState(cwd) {
1817
+ const sessionPath = path.join(cwd, DEVTOOLS_SESSION_FILE);
1818
+ if (!await fileExists(sessionPath)) return null;
1819
+ try {
1820
+ const source = await readFile(sessionPath, "utf8");
1821
+ const parsed = JSON.parse(source);
1822
+ if (typeof parsed.dashboardUrl !== "string" || typeof parsed.authenticatedDashboardUrl !== "string" || typeof parsed.devtoolsUrl !== "string" || typeof parsed.token !== "string") return null;
1823
+ return {
1824
+ dashboardUrl: parsed.dashboardUrl,
1825
+ authenticatedDashboardUrl: parsed.authenticatedDashboardUrl,
1826
+ devtoolsUrl: parsed.devtoolsUrl,
1827
+ token: parsed.token
1828
+ };
1829
+ } catch {
1830
+ return null;
1831
+ }
1511
1832
  }
1512
1833
  async function setupDevProjectWatch(cwd, template) {
1513
1834
  const snapshot = await createDevWatchSnapshot(cwd);
@@ -1558,6 +1879,10 @@ async function runDevProjectBootstrap(cwd, template) {
1558
1879
  else console.log("Schema snapshot updated.");
1559
1880
  }
1560
1881
  for (const warning of plan.warnings) console.warn(`Syncore dev warning: ${warning}`);
1882
+ if (templateUsesConnectedClients(template)) {
1883
+ console.log("Syncore dev is ready. Codegen refreshed; waiting for a connected client runtime to apply schema locally.");
1884
+ return;
1885
+ }
1561
1886
  const appliedCount = await applyProjectMigrations(cwd);
1562
1887
  console.log(`Syncore dev is ready. Codegen refreshed; ${appliedCount} migration(s) applied.`);
1563
1888
  } catch (error) {
@@ -1644,6 +1969,6 @@ function toSearchValue(value) {
1644
1969
  return stableStringify(value);
1645
1970
  }
1646
1971
  //#endregion
1647
- export { SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME, VALID_SYNCORE_TEMPLATES, applyProjectMigrations, detectProjectTemplate, fileExists, formatError, getNextMigrationNumber, hasSyncoreProject, importJsonlIntoProject, isLocalPortInUse, loadProjectConfig, loadProjectFunctions, loadProjectSchema, logScaffoldResult, readPackageJson, readStoredSnapshot, resolveDefaultSeedFile, resolvePortFromEnv, resolveProjectTargetConfig, resolveRequestedTemplate, runCodegen, runDevProjectBootstrap, runSyncoreCli, scaffoldProject, slugify, startDevHub, writeStoredSnapshot };
1972
+ export { SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME, VALID_SYNCORE_TEMPLATES, applyProjectMigrations, detectProjectTemplate, fileExists, formatError, getNextMigrationNumber, hasSyncoreProject, importJsonlIntoProject, isLocalPortInUse, loadProjectComponentsManifest, loadProjectConfig, loadProjectFunctions, loadProjectResolvedComponents, loadProjectRootSchema, loadProjectRuntime, loadProjectSchema, logScaffoldResult, readPackageJson, readStoredSnapshot, resolveDefaultSeedFile, resolvePortFromEnv, resolveProjectTargetConfig, resolveRequestedTemplate, runCodegen, runDevProjectBootstrap, runSyncoreCli, scaffoldProject, slugify, startDevHub, templateUsesConnectedClients, writeStoredSnapshot };
1648
1973
 
1649
1974
  //# sourceMappingURL=cli.mjs.map