syncorejs 0.2.2 → 0.2.4

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 (142) hide show
  1. package/dist/_vendor/cli/app.d.mts.map +1 -1
  2. package/dist/_vendor/cli/app.mjs +8 -5
  3. package/dist/_vendor/cli/app.mjs.map +1 -1
  4. package/dist/_vendor/cli/context.mjs.map +1 -1
  5. package/dist/_vendor/cli/dev-session.mjs.map +1 -1
  6. package/dist/_vendor/cli/doctor.mjs.map +1 -1
  7. package/dist/_vendor/cli/errors.mjs.map +1 -1
  8. package/dist/_vendor/cli/help.mjs.map +1 -1
  9. package/dist/_vendor/cli/index.mjs +9 -2
  10. package/dist/_vendor/cli/index.mjs.map +1 -1
  11. package/dist/_vendor/cli/messages.mjs.map +1 -1
  12. package/dist/_vendor/cli/preflight.mjs.map +1 -1
  13. package/dist/_vendor/cli/project.mjs +20 -20
  14. package/dist/_vendor/cli/project.mjs.map +1 -1
  15. package/dist/_vendor/cli/render.mjs.map +1 -1
  16. package/dist/_vendor/cli/targets.mjs.map +1 -1
  17. package/dist/_vendor/core/cli.d.mts +8 -2
  18. package/dist/_vendor/core/cli.d.mts.map +1 -1
  19. package/dist/_vendor/core/cli.mjs +510 -71
  20. package/dist/_vendor/core/cli.mjs.map +1 -1
  21. package/dist/_vendor/core/devtools-auth.mjs.map +1 -1
  22. package/dist/_vendor/core/index.d.mts +3 -3
  23. package/dist/_vendor/core/runtime/components.d.mts.map +1 -1
  24. package/dist/_vendor/core/runtime/components.mjs.map +1 -1
  25. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  26. package/dist/_vendor/core/runtime/devtools.mjs +261 -23
  27. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  28. package/dist/_vendor/core/runtime/functions.d.mts +388 -6
  29. package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
  30. package/dist/_vendor/core/runtime/functions.mjs +72 -1
  31. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  32. package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
  33. package/dist/_vendor/core/runtime/id.mjs.map +1 -1
  34. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +12 -6
  35. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
  36. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +123 -20
  37. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -1
  38. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +56 -8
  39. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
  40. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +49 -14
  41. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -1
  42. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +4 -7
  43. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -1
  44. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +81 -2
  45. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
  46. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +100 -13
  47. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
  48. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +42 -7
  49. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
  50. package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -1
  51. package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -1
  52. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +4 -0
  53. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -1
  54. package/dist/_vendor/core/runtime/runtime.d.mts +1100 -12
  55. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  56. package/dist/_vendor/core/runtime/runtime.mjs +63 -0
  57. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  58. package/dist/_vendor/core/transport.d.mts +2 -0
  59. package/dist/_vendor/core/transport.d.mts.map +1 -1
  60. package/dist/_vendor/core/transport.mjs +61 -27
  61. package/dist/_vendor/core/transport.mjs.map +1 -1
  62. package/dist/_vendor/devtools-protocol/index.d.ts +223 -4
  63. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  64. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  65. package/dist/_vendor/next/config.d.ts +3 -4
  66. package/dist/_vendor/next/config.d.ts.map +1 -1
  67. package/dist/_vendor/next/config.js +37 -19
  68. package/dist/_vendor/next/config.js.map +1 -1
  69. package/dist/_vendor/next/index.d.ts +109 -29
  70. package/dist/_vendor/next/index.d.ts.map +1 -1
  71. package/dist/_vendor/next/index.js +86 -18
  72. package/dist/_vendor/next/index.js.map +1 -1
  73. package/dist/_vendor/platform-expo/index.d.ts +146 -27
  74. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  75. package/dist/_vendor/platform-expo/index.js +81 -10
  76. package/dist/_vendor/platform-expo/index.js.map +1 -1
  77. package/dist/_vendor/platform-expo/react.js.map +1 -1
  78. package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
  79. package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
  80. package/dist/_vendor/platform-node/index.d.mts +174 -9
  81. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  82. package/dist/_vendor/platform-node/index.mjs +251 -95
  83. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  84. package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
  85. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  86. package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
  87. package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
  88. package/dist/_vendor/platform-web/external-change.d.ts +41 -0
  89. package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
  90. package/dist/_vendor/platform-web/external-change.js +30 -0
  91. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  92. package/dist/_vendor/platform-web/index.d.ts +312 -37
  93. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  94. package/dist/_vendor/platform-web/index.js +247 -25
  95. package/dist/_vendor/platform-web/index.js.map +1 -1
  96. package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
  97. package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
  98. package/dist/_vendor/platform-web/indexeddb.js +10 -0
  99. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  100. package/dist/_vendor/platform-web/opfs.d.ts +16 -1
  101. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  102. package/dist/_vendor/platform-web/opfs.js +41 -3
  103. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  104. package/dist/_vendor/platform-web/persistence.d.ts +85 -1
  105. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  106. package/dist/_vendor/platform-web/persistence.js +15 -0
  107. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  108. package/dist/_vendor/platform-web/react.d.ts +1 -2
  109. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  110. package/dist/_vendor/platform-web/react.js +11 -5
  111. package/dist/_vendor/platform-web/react.js.map +1 -1
  112. package/dist/_vendor/platform-web/sqljs.js +10 -1
  113. package/dist/_vendor/platform-web/sqljs.js.map +1 -1
  114. package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
  115. package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
  116. package/dist/_vendor/platform-web/worker.d.ts +60 -9
  117. package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
  118. package/dist/_vendor/platform-web/worker.js +37 -4
  119. package/dist/_vendor/platform-web/worker.js.map +1 -1
  120. package/dist/_vendor/react/index.d.ts +197 -13
  121. package/dist/_vendor/react/index.d.ts.map +1 -1
  122. package/dist/_vendor/react/index.js +209 -17
  123. package/dist/_vendor/react/index.js.map +1 -1
  124. package/dist/_vendor/schema/definition.d.ts +129 -0
  125. package/dist/_vendor/schema/definition.d.ts.map +1 -1
  126. package/dist/_vendor/schema/definition.js +99 -0
  127. package/dist/_vendor/schema/definition.js.map +1 -1
  128. package/dist/_vendor/schema/planner.d.ts.map +1 -1
  129. package/dist/_vendor/schema/planner.js.map +1 -1
  130. package/dist/_vendor/schema/validators.d.ts +180 -4
  131. package/dist/_vendor/schema/validators.d.ts.map +1 -1
  132. package/dist/_vendor/schema/validators.js +35 -1
  133. package/dist/_vendor/schema/validators.js.map +1 -1
  134. package/dist/_vendor/svelte/index.d.ts +207 -7
  135. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  136. package/dist/_vendor/svelte/index.js +201 -6
  137. package/dist/_vendor/svelte/index.js.map +1 -1
  138. package/dist/browser.d.ts.map +1 -1
  139. package/dist/cli.js +3 -1
  140. package/dist/cli.js.map +1 -1
  141. package/dist/index.d.ts +1 -1
  142. package/package.json +24 -21
@@ -4,7 +4,8 @@ import { SyncoreRuntime } from "./runtime/runtime.mjs";
4
4
  import { createDevtoolsCommandHandler, createDevtoolsSubscriptionHost } from "./runtime/devtools.mjs";
5
5
  import { src_exports } from "./index.mjs";
6
6
  import { generateDevtoolsToken, isAllowedDashboardOrigin, isAuthorizedDashboardRequest, sanitizeDevtoolsToken } from "./devtools-auth.mjs";
7
- import { appendFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
+ import { appendFile, mkdir, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
8
+ import { randomUUID } from "node:crypto";
8
9
  import { createServer } from "node:http";
9
10
  import { connect } from "node:net";
10
11
  import path from "node:path";
@@ -16,9 +17,10 @@ import WebSocket, { WebSocketServer } from "ws";
16
17
  import { SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION, SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION, SYNCORE_DEVTOOLS_PROTOCOL_VERSION, createPublicRuntimeId, createPublicTargetId } from "../devtools-protocol/index.js";
17
18
  //#region src/cli.ts
18
19
  function templateUsesConnectedClients(template) {
19
- return template === "react-web" || template === "expo" || template === "next";
20
+ return template === "react-web" || template === "svelte" || template === "expo" || template === "next";
20
21
  }
21
- const COMBINED_DEV_COMMAND = "concurrently --kill-others-on-fail --names syncore,app --prefix-colors yellow,cyan \"bun run syncorejs:dev\" \"bun run dev:app\"";
22
+ const loadedTypeScriptModules = /* @__PURE__ */ new Map();
23
+ const COMBINED_DEV_COMMAND = "concurrently --kill-others-on-fail --names syncore,app --prefix-colors yellow,cyan \"npm run syncorejs:dev\" \"npm run dev:app\"";
22
24
  const program = new Command();
23
25
  const CORE_PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
24
26
  const DEVTOOLS_SESSION_FILE = path.join(".syncore", "devtools-session.json");
@@ -27,6 +29,7 @@ const VALID_SYNCORE_TEMPLATES = [
27
29
  "minimal",
28
30
  "node",
29
31
  "react-web",
32
+ "svelte",
30
33
  "expo",
31
34
  "electron",
32
35
  "next"
@@ -34,6 +37,9 @@ const VALID_SYNCORE_TEMPLATES = [
34
37
  let pendingDevBootstrap;
35
38
  let devBootstrapInFlight = false;
36
39
  const PROJECT_TARGET_RUNTIME_ID = "syncore-project-target";
40
+ const STORAGE_ACCESS_TICKET_TTL_MS = 300 * 1e3;
41
+ const STORAGE_ACCESS_CHUNK_BYTES = 1024 * 1024;
42
+ const STORAGE_ACCESS_MAX_PREVIEW_BYTES = 8e4;
37
43
  program.name("syncorejs").description("Syncore local-first toolkit CLI").version("0.1.0");
38
44
  program.command("init").description("Scaffold Syncore in the current directory").option("--template <template>", `Template to scaffold (${VALID_SYNCORE_TEMPLATES.join(", ")}, or auto)`, "auto").option("--force", "Overwrite Syncore-managed files when they already exist").action(async (options) => {
39
45
  const cwd = process.cwd();
@@ -131,7 +137,7 @@ async function runCodegen(cwd) {
131
137
  const componentsManifestPath = path.join(cwd, "syncore", "components.ts");
132
138
  await mkdir(generatedDir, { recursive: true });
133
139
  const functionImportExtension = await resolveFunctionImportExtension(cwd);
134
- const hasComponentsManifest = await fileExists(componentsManifestPath);
140
+ const hasComponentsManifest = await hasNonEmptyComponentsManifest(componentsManifestPath);
135
141
  const files = await listTypeScriptFiles(functionsDir);
136
142
  const functionEntries = [];
137
143
  for (const file of files) {
@@ -246,6 +252,24 @@ async function runCodegen(cwd) {
246
252
  `export default resolvedComponents;`,
247
253
  ``
248
254
  ].join("\n");
255
+ const runtimeSource = [
256
+ `/**`,
257
+ ` * Generated local runtime bundle for Syncore CLI and Node adapters.`,
258
+ ` *`,
259
+ ` * THIS CODE IS AUTOMATICALLY GENERATED.`,
260
+ ` *`,
261
+ ` * To regenerate, run \`npx syncorejs dev\` or \`npx syncorejs codegen\`.`,
262
+ ` * @module`,
263
+ ` */`,
264
+ ``,
265
+ `import schema from "./schema${functionImportExtension}";`,
266
+ `import { functions } from "./functions${functionImportExtension}";`,
267
+ ...hasComponentsManifest ? [`import { resolvedComponents } from "./components${functionImportExtension}";`] : [`const resolvedComponents = [] as const;`],
268
+ ``,
269
+ `export { functions, schema };`,
270
+ `export const components = resolvedComponents;`,
271
+ ``
272
+ ].join("\n");
249
273
  const serverSource = [
250
274
  `/**`,
251
275
  ` * Generated utilities for implementing Syncore query, mutation, and action functions.`,
@@ -341,11 +365,18 @@ async function runCodegen(cwd) {
341
365
  `};`,
342
366
  ``
343
367
  ].join("\n");
344
- await writeFile(path.join(generatedDir, "api.ts"), apiSource);
345
- await writeFile(path.join(generatedDir, "components.ts"), componentsSource);
346
- await writeFile(path.join(generatedDir, "functions.ts"), functionsSource);
347
- await writeFile(path.join(generatedDir, "schema.ts"), schemaSource);
348
- await writeFile(path.join(generatedDir, "server.ts"), serverSource);
368
+ await writeGeneratedFile(path.join(generatedDir, "api.ts"), apiSource);
369
+ await writeGeneratedFile(path.join(generatedDir, "components.ts"), componentsSource);
370
+ await writeGeneratedFile(path.join(generatedDir, "functions.ts"), functionsSource);
371
+ await writeGeneratedFile(path.join(generatedDir, "runtime.ts"), runtimeSource);
372
+ await writeGeneratedFile(path.join(generatedDir, "schema.ts"), schemaSource);
373
+ await writeGeneratedFile(path.join(generatedDir, "server.ts"), serverSource);
374
+ }
375
+ async function writeGeneratedFile(filePath, source) {
376
+ try {
377
+ if (await readFile(filePath, "utf8") === source) return;
378
+ } catch {}
379
+ await writeFile(filePath, source);
349
380
  }
350
381
  async function scaffoldProject(cwd, options) {
351
382
  const files = buildTemplateFiles(options.template);
@@ -423,16 +454,12 @@ export const create = mutation({
423
454
 
424
455
  import { createBrowserWorkerRuntime } from "syncorejs/browser";
425
456
  import schema from "../syncore/_generated/schema";
426
- import { resolvedComponents } from "../syncore/_generated/components";
427
457
  import { functions } from "../syncore/_generated/functions";
428
458
 
429
459
  void createBrowserWorkerRuntime({
430
460
  endpoint: self,
431
461
  schema,
432
- functions,
433
- components: resolvedComponents,
434
- databaseName: "syncore-app",
435
- persistenceMode: "opfs"
462
+ functions
436
463
  });
437
464
  `
438
465
  }, {
@@ -455,15 +482,11 @@ export function AppSyncoreProvider({ children }: { children: ReactNode }) {
455
482
  path: path.join("lib", "syncore.ts"),
456
483
  content: `import { createExpoSyncoreBootstrap } from "syncorejs/expo";
457
484
  import schema from "../syncore/_generated/schema";
458
- import { resolvedComponents } from "../syncore/_generated/components";
459
485
  import { functions } from "../syncore/_generated/functions";
460
486
 
461
487
  export const syncore = createExpoSyncoreBootstrap({
462
488
  schema,
463
- functions,
464
- components: resolvedComponents,
465
- databaseName: "syncore-app.db",
466
- storageDirectoryName: "syncore-app-storage"
489
+ functions
467
490
  });
468
491
  `
469
492
  });
@@ -475,18 +498,12 @@ export const syncore = createExpoSyncoreBootstrap({
475
498
 
476
499
  import { createBrowserWorkerRuntime } from "syncorejs/browser";
477
500
  import schema from "../syncore/_generated/schema";
478
- import { resolvedComponents } from "../syncore/_generated/components";
479
501
  import { functions } from "../syncore/_generated/functions";
480
502
 
481
503
  void createBrowserWorkerRuntime({
482
504
  endpoint: self,
483
505
  schema,
484
- functions,
485
- components: resolvedComponents,
486
- databaseName: "syncore-app",
487
- persistenceDatabaseName: "syncore-app",
488
- locateFile: () => "/sql-wasm.wasm",
489
- platform: "browser-worker"
506
+ functions
490
507
  });
491
508
  `
492
509
  }, {
@@ -518,7 +535,6 @@ export function AppSyncoreProvider({ children }: { children: ReactNode }) {
518
535
  import { withNodeSyncoreClient } from "syncorejs/node";
519
536
  import { api } from "./syncore/_generated/api.ts";
520
537
  import schema from "./syncore/_generated/schema.ts";
521
- import { resolvedComponents } from "./syncore/_generated/components.ts";
522
538
  import { functions } from "./syncore/_generated/functions.ts";
523
539
 
524
540
  await withNodeSyncoreClient(
@@ -526,8 +542,7 @@ await withNodeSyncoreClient(
526
542
  databasePath: path.join(process.cwd(), ".syncore", "syncore.db"),
527
543
  storageDirectory: path.join(process.cwd(), ".syncore", "storage"),
528
544
  schema,
529
- functions,
530
- components: resolvedComponents
545
+ functions
531
546
  },
532
547
  async (client) => {
533
548
  await client.mutation(api.tasks.create, { text: "Run locally" });
@@ -544,7 +559,6 @@ await withNodeSyncoreClient(
544
559
  import { app } from "electron";
545
560
  import { createNodeSyncoreRuntime } from "syncorejs/node";
546
561
  import schema from "../syncore/_generated/schema.js";
547
- import { resolvedComponents } from "../syncore/_generated/components.js";
548
562
  import { functions } from "../syncore/_generated/functions.js";
549
563
 
550
564
  export function createAppSyncoreRuntime() {
@@ -554,10 +568,53 @@ export function createAppSyncoreRuntime() {
554
568
  storageDirectory: path.join(userDataDirectory, "storage"),
555
569
  schema,
556
570
  functions,
557
- components: resolvedComponents,
558
571
  platform: "electron-main"
559
572
  });
560
573
  }
574
+ `
575
+ });
576
+ break;
577
+ case "svelte":
578
+ files.push({
579
+ path: path.join("src", "syncore.worker.ts"),
580
+ content: `/// <reference lib="webworker" />
581
+
582
+ import { createBrowserWorkerRuntime } from "syncorejs/browser";
583
+ import schema from "../syncore/_generated/schema";
584
+ import { functions } from "../syncore/_generated/functions";
585
+
586
+ void createBrowserWorkerRuntime({
587
+ endpoint: self,
588
+ schema,
589
+ functions
590
+ });
591
+ `
592
+ }, {
593
+ path: path.join("src", "SyncoreProvider.svelte"),
594
+ content: `<script lang="ts">
595
+ import { onDestroy } from "svelte";
596
+ import type { Snippet } from "svelte";
597
+ import { createBrowserWorkerClient } from "syncorejs/browser";
598
+ import { setSyncoreClient } from "syncorejs/svelte";
599
+
600
+ interface Props {
601
+ children?: Snippet;
602
+ }
603
+
604
+ const { children }: Props = $props();
605
+
606
+ const managed = createBrowserWorkerClient({
607
+ workerUrl: new URL("./syncore.worker.ts", import.meta.url)
608
+ });
609
+
610
+ setSyncoreClient(managed.client);
611
+
612
+ onDestroy(() => {
613
+ void managed.dispose();
614
+ });
615
+ <\/script>
616
+
617
+ {@render children?.()}
561
618
  `
562
619
  });
563
620
  break;
@@ -606,7 +663,8 @@ async function detectProjectTemplate(cwd) {
606
663
  if ("expo" in dependencies || "react-native" in dependencies) return "expo";
607
664
  if ("electron" in dependencies) return "electron";
608
665
  if ("next" in dependencies) return "next";
609
- if ("vite" in dependencies || "@vitejs/plugin-react" in dependencies || await fileExists(path.join(cwd, "src", "main.tsx")) && "react" in dependencies) return "react-web";
666
+ if ("svelte" in dependencies || "@sveltejs/vite-plugin-svelte" in dependencies || "@sveltejs/kit" in dependencies || await fileExists(path.join(cwd, "src", "SyncoreProvider.svelte"))) return "svelte";
667
+ 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";
610
668
  if (packageJson) return "node";
611
669
  return "minimal";
612
670
  }
@@ -621,7 +679,16 @@ async function readPackageJson(cwd) {
621
679
  }
622
680
  async function ensurePackageScripts(cwd, template) {
623
681
  const packageJsonPath = path.join(cwd, "package.json");
624
- if (!await fileExists(packageJsonPath)) return;
682
+ if (!await fileExists(packageJsonPath)) {
683
+ await writeFile(packageJsonPath, `${JSON.stringify({
684
+ type: "module",
685
+ scripts: {
686
+ "syncorejs:dev": "syncorejs dev",
687
+ "syncorejs:codegen": "syncorejs codegen"
688
+ }
689
+ }, null, 2)}\n`);
690
+ return;
691
+ }
625
692
  const packageJson = await readPackageJson(cwd);
626
693
  if (!packageJson) return;
627
694
  const nextPackageJson = {
@@ -654,7 +721,7 @@ function maybeAddManagedDevScripts(packageJson, template) {
654
721
  scripts.dev = combinedDevCommand();
655
722
  }
656
723
  function supportsManagedCombinedDev(template) {
657
- return template === "next" || template === "react-web" || template === "electron";
724
+ return template === "next" || template === "react-web" || template === "svelte" || template === "electron";
658
725
  }
659
726
  function combinedDevCommand() {
660
727
  return COMBINED_DEV_COMMAND;
@@ -703,7 +770,7 @@ async function importJsonlIntoProject(cwd, tableName, sourcePath) {
703
770
  try {
704
771
  parsed = JSON.parse(line);
705
772
  } catch (error) {
706
- throw new Error(`Invalid JSON on line ${lineNumber} of ${sourcePath}: ${formatError(error)}`);
773
+ throw new Error(`Invalid JSON on line ${lineNumber} of ${sourcePath}: ${formatError(error)}`, { cause: error });
707
774
  }
708
775
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Line ${lineNumber} of ${sourcePath} must contain a JSON object.`);
709
776
  const payload = { ...parsed };
@@ -911,8 +978,7 @@ async function loadProjectSchema(cwd) {
911
978
  }
912
979
  async function loadDefaultExport(filePath) {
913
980
  if (!await fileExists(filePath)) throw new Error(`Missing file: ${path.relative(process.cwd(), filePath)}`);
914
- const moduleUrl = pathToFileURL(filePath).href;
915
- const loaded = await tsImport(moduleUrl, { parentURL: import.meta.url });
981
+ const loaded = await loadTypeScriptModule(filePath);
916
982
  if (!("default" in loaded)) throw new Error(`File ${path.relative(process.cwd(), filePath)} must have a default export.`);
917
983
  const resolvedDefault = unwrapDefaultExport(loaded.default);
918
984
  if (resolvedDefault === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined.`);
@@ -920,14 +986,30 @@ async function loadDefaultExport(filePath) {
920
986
  }
921
987
  async function loadNamedExport(filePath, exportName) {
922
988
  if (!await fileExists(filePath)) throw new Error(`Missing file: ${path.relative(process.cwd(), filePath)}`);
923
- const moduleUrl = pathToFileURL(filePath).href;
924
- const loaded = await tsImport(moduleUrl, { parentURL: import.meta.url });
989
+ const loaded = await loadTypeScriptModule(filePath);
925
990
  const defaultExport = loaded.default && typeof loaded.default === "object" && exportName in loaded.default ? loaded.default[exportName] : void 0;
926
991
  if (!(exportName in loaded) && defaultExport === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} must export ${exportName}.`);
927
992
  const resolvedValue = unwrapDefaultExport(loaded[exportName] ?? defaultExport);
928
993
  if (resolvedValue === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined for ${exportName}.`);
929
994
  return resolvedValue;
930
995
  }
996
+ async function hasNonEmptyComponentsManifest(filePath) {
997
+ if (!await fileExists(filePath)) return false;
998
+ const source = await readFile(filePath, "utf8");
999
+ return !/defineComponents\s*\(\s*\{\s*\}\s*\)/.test(source);
1000
+ }
1001
+ async function loadTypeScriptModule(filePath) {
1002
+ const fileStat = await stat(filePath);
1003
+ const cached = loadedTypeScriptModules.get(filePath);
1004
+ if (cached && cached.mtimeMs === fileStat.mtimeMs) return await cached.loaded;
1005
+ const moduleUrl = pathToFileURL(filePath).href;
1006
+ const loaded = tsImport(moduleUrl, { parentURL: import.meta.url });
1007
+ loadedTypeScriptModules.set(filePath, {
1008
+ mtimeMs: fileStat.mtimeMs,
1009
+ loaded
1010
+ });
1011
+ return await loaded;
1012
+ }
931
1013
  async function loadProjectFunctions(cwd) {
932
1014
  const filePath = path.join(cwd, "syncore", "_generated", "functions.ts");
933
1015
  if (!await fileExists(filePath)) await runCodegen(cwd);
@@ -936,17 +1018,62 @@ async function loadProjectFunctions(cwd) {
936
1018
  return functions;
937
1019
  }
938
1020
  async function loadProjectResolvedComponents(cwd) {
1021
+ const componentsManifestPath = path.join(cwd, "syncore", "components.ts");
1022
+ if (await fileExists(componentsManifestPath)) {
1023
+ const source = await readFile(componentsManifestPath, "utf8");
1024
+ if (/defineComponents\s*\(\s*\{\s*\}\s*\)/.test(source)) return [];
1025
+ }
939
1026
  const filePath = path.join(cwd, "syncore", "_generated", "components.ts");
940
1027
  if (!await fileExists(filePath)) await runCodegen(cwd);
941
1028
  const components = await loadNamedExport(filePath, "resolvedComponents");
942
1029
  if (!Array.isArray(components)) throw new Error("syncore/_generated/components.ts must export resolvedComponents.");
943
1030
  return components;
944
1031
  }
1032
+ async function loadProjectRuntime(cwd) {
1033
+ const filePath = path.join(cwd, "syncore", "_generated", "runtime.ts");
1034
+ if (!await fileExists(filePath)) await runCodegen(cwd);
1035
+ const loaded = await loadTypeScriptModule(filePath);
1036
+ const schema = unwrapDefaultExport(loaded.schema);
1037
+ const functions = unwrapDefaultExport(loaded.functions);
1038
+ const components = unwrapDefaultExport(loaded.components);
1039
+ if (!schema || typeof schema !== "object" || typeof schema.tableNames !== "function") throw new Error("syncore/_generated/runtime.ts must export a composed Syncore schema.");
1040
+ if (!functions || typeof functions !== "object") throw new Error("syncore/_generated/runtime.ts must export a functions registry.");
1041
+ if (!Array.isArray(components)) throw new Error("syncore/_generated/runtime.ts must export resolved components.");
1042
+ return {
1043
+ schema,
1044
+ functions,
1045
+ components
1046
+ };
1047
+ }
1048
+ var HubExternalChangeSignal = class {
1049
+ publishToHub;
1050
+ listeners = /* @__PURE__ */ new Set();
1051
+ constructor(publishToHub) {
1052
+ this.publishToHub = publishToHub;
1053
+ }
1054
+ subscribe(listener) {
1055
+ this.listeners.add(listener);
1056
+ return () => {
1057
+ this.listeners.delete(listener);
1058
+ };
1059
+ }
1060
+ publish(event) {
1061
+ return this.publishToHub(event);
1062
+ }
1063
+ receive(event) {
1064
+ for (const listener of this.listeners) listener(event);
1065
+ }
1066
+ close() {
1067
+ this.listeners.clear();
1068
+ }
1069
+ };
945
1070
  var HubSqliteDriver = class {
1071
+ databasePath;
946
1072
  database;
947
1073
  transactionDepth = 0;
948
- constructor(filename) {
949
- this.database = new DatabaseSync(filename);
1074
+ constructor(databasePath) {
1075
+ this.databasePath = databasePath;
1076
+ this.database = new DatabaseSync(databasePath);
950
1077
  this.database.exec("PRAGMA foreign_keys = ON;");
951
1078
  this.database.exec("PRAGMA journal_mode = WAL;");
952
1079
  }
@@ -999,6 +1126,7 @@ var HubSqliteDriver = class {
999
1126
  }
1000
1127
  };
1001
1128
  var HubFileStorageAdapter = class {
1129
+ directory;
1002
1130
  constructor(directory) {
1003
1131
  this.directory = directory;
1004
1132
  }
@@ -1037,6 +1165,19 @@ var HubFileStorageAdapter = class {
1037
1165
  return null;
1038
1166
  }
1039
1167
  }
1168
+ async readRange(id, offset, length) {
1169
+ let handle;
1170
+ try {
1171
+ handle = await open(this.filePath(id), "r");
1172
+ const buffer = Buffer.alloc(Math.max(length, 0));
1173
+ const result = await handle.read(buffer, 0, buffer.byteLength, Math.max(offset, 0));
1174
+ return buffer.subarray(0, result.bytesRead);
1175
+ } catch {
1176
+ return null;
1177
+ } finally {
1178
+ await handle?.close();
1179
+ }
1180
+ }
1040
1181
  async delete(id) {
1041
1182
  await rm(this.filePath(id), { force: true });
1042
1183
  }
@@ -1112,14 +1253,10 @@ const hubDevtoolsSqlSupport = {
1112
1253
  }
1113
1254
  }
1114
1255
  };
1115
- async function createProjectTargetBackend(cwd) {
1256
+ async function createProjectTargetBackend(cwd, externalChangeSignal) {
1116
1257
  const projectTarget = resolveProjectTargetConfig(await loadProjectConfig(cwd));
1117
1258
  if (!projectTarget) return null;
1118
- const [schema, functions, components] = await Promise.all([
1119
- loadProjectSchema(cwd),
1120
- loadProjectFunctions(cwd),
1121
- loadProjectResolvedComponents(cwd)
1122
- ]);
1259
+ const { schema, functions, components } = await loadProjectRuntime(cwd);
1123
1260
  const databasePath = path.resolve(cwd, projectTarget.databasePath);
1124
1261
  const storageDirectory = path.resolve(cwd, projectTarget.storageDirectory);
1125
1262
  await mkdir(path.dirname(databasePath), { recursive: true });
@@ -1131,9 +1268,15 @@ async function createProjectTargetBackend(cwd) {
1131
1268
  components,
1132
1269
  driver,
1133
1270
  storage: new HubFileStorageAdapter(storageDirectory),
1134
- platform: "project"
1271
+ platform: "project",
1272
+ runtimeCapabilities: { storage: {
1273
+ available: true,
1274
+ protocol: "file",
1275
+ supportsRange: true
1276
+ } },
1277
+ ...externalChangeSignal ? { externalChangeSignal } : {}
1135
1278
  });
1136
- await runtime.prepareForDirectAccess();
1279
+ await runtime.start();
1137
1280
  const commandHandler = createDevtoolsCommandHandler({
1138
1281
  driver,
1139
1282
  schema,
@@ -1158,9 +1301,11 @@ async function createProjectTargetBackend(cwd) {
1158
1301
  platform: "project",
1159
1302
  sessionLabel: "Project Target",
1160
1303
  targetKind: "project",
1304
+ runtimeRole: "project-target",
1161
1305
  storageProtocol: "file",
1162
1306
  databaseLabel: path.basename(databasePath),
1163
- storageIdentity: `file::${databasePath}`
1307
+ storageIdentity: `file::${databasePath}`,
1308
+ capabilities: createProjectDevtoolsCapabilities()
1164
1309
  },
1165
1310
  handleCommand: commandHandler,
1166
1311
  subscribe(subscriptionId, payload, listener) {
@@ -1175,6 +1320,31 @@ async function createProjectTargetBackend(cwd) {
1175
1320
  }
1176
1321
  };
1177
1322
  }
1323
+ function createProjectDevtoolsCapabilities() {
1324
+ return {
1325
+ sql: {
1326
+ read: true,
1327
+ write: true,
1328
+ live: true
1329
+ },
1330
+ data: {
1331
+ browse: true,
1332
+ mutate: true,
1333
+ importExport: true
1334
+ },
1335
+ storage: {
1336
+ browse: true,
1337
+ download: true,
1338
+ readRange: true,
1339
+ delete: true,
1340
+ maxPreviewBytes: 8e4
1341
+ },
1342
+ scheduler: {
1343
+ read: true,
1344
+ edit: true
1345
+ }
1346
+ };
1347
+ }
1178
1348
  function normalizeStorageInput(input) {
1179
1349
  if (typeof input === "string") return Buffer.from(input);
1180
1350
  if (input instanceof Uint8Array) return input;
@@ -1297,16 +1467,24 @@ function formatError(error) {
1297
1467
  }
1298
1468
  async function isLocalPortInUse(port) {
1299
1469
  return await new Promise((resolve) => {
1470
+ let settled = false;
1300
1471
  const socket = connect({
1301
1472
  host: "127.0.0.1",
1302
1473
  port
1303
1474
  });
1475
+ const finish = (inUse) => {
1476
+ if (settled) return;
1477
+ settled = true;
1478
+ clearTimeout(timeout);
1479
+ socket.destroy();
1480
+ resolve(inUse);
1481
+ };
1482
+ const timeout = setTimeout(() => finish(false), 100);
1304
1483
  socket.once("connect", () => {
1305
- socket.end();
1306
- resolve(true);
1484
+ finish(true);
1307
1485
  });
1308
1486
  socket.once("error", () => {
1309
- resolve(false);
1487
+ finish(false);
1310
1488
  });
1311
1489
  });
1312
1490
  }
@@ -1348,26 +1526,29 @@ async function startDevHub(options) {
1348
1526
  return await readDevtoolsSessionState(options.cwd) ?? sessionState;
1349
1527
  }
1350
1528
  await writeDevtoolsSessionState(options.cwd, sessionState);
1351
- let projectTargetBackend = null;
1352
- try {
1353
- projectTargetBackend = await createProjectTargetBackend(options.cwd);
1354
- } catch (error) {
1355
- console.warn(`Project target fallback unavailable: ${formatError(error)}`);
1356
- }
1357
- const httpServer = createServer((_request, response) => {
1358
- response.writeHead(200, { "content-type": "application/json" });
1359
- response.end(JSON.stringify({
1360
- ok: true,
1361
- wsPort: devtoolsPort
1362
- }));
1529
+ const httpServer = createServer((request, response) => {
1530
+ handleStorageAccessHttpRequest(request, response).catch((error) => {
1531
+ if (!response.headersSent) writeJsonResponse(response, 500, { error: formatError(error) });
1532
+ else response.destroy(error instanceof Error ? error : void 0);
1533
+ });
1363
1534
  });
1364
1535
  const websocketServer = new WebSocketServer({ server: httpServer });
1536
+ const storageAccessTickets = /* @__PURE__ */ new Map();
1537
+ const pendingHubCommands = /* @__PURE__ */ new Map();
1365
1538
  const runtimeSockets = /* @__PURE__ */ new Map();
1366
1539
  const runtimeHellos = /* @__PURE__ */ new Map();
1367
1540
  const runtimeEvents = /* @__PURE__ */ new Map();
1368
1541
  const socketRuntimeIds = /* @__PURE__ */ new Map();
1369
1542
  const dashboardSockets = /* @__PURE__ */ new Set();
1370
1543
  const dashboardSubscriptions = /* @__PURE__ */ new Map();
1544
+ let relayExternalChange = () => {};
1545
+ const projectTargetExternalChangeSignal = new HubExternalChangeSignal((event) => relayExternalChange(PROJECT_TARGET_RUNTIME_ID, runtimeHellos.get(PROJECT_TARGET_RUNTIME_ID)?.storageIdentity ?? "", event));
1546
+ let projectTargetBackend = null;
1547
+ try {
1548
+ projectTargetBackend = await createProjectTargetBackend(options.cwd, projectTargetExternalChangeSignal);
1549
+ } catch (error) {
1550
+ console.warn(`Project target fallback unavailable: ${formatError(error)}`);
1551
+ }
1371
1552
  const hello = {
1372
1553
  type: "hello",
1373
1554
  protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,
@@ -1380,12 +1561,30 @@ async function startDevHub(options) {
1380
1561
  runtimeHellos.set(PROJECT_TARGET_RUNTIME_ID, projectTargetBackend.hello);
1381
1562
  runtimeEvents.set(PROJECT_TARGET_RUNTIME_ID, []);
1382
1563
  }
1564
+ relayExternalChange = (sourceRuntimeId, storageIdentity, event) => {
1565
+ if (!storageIdentity) return;
1566
+ const message = {
1567
+ type: "external.change",
1568
+ runtimeId: sourceRuntimeId,
1569
+ storageIdentity,
1570
+ event
1571
+ };
1572
+ for (const [runtimeId, runtimeHello] of runtimeHellos) {
1573
+ if (runtimeId === sourceRuntimeId || runtimeHello.storageIdentity !== storageIdentity) continue;
1574
+ if (runtimeId === PROJECT_TARGET_RUNTIME_ID) {
1575
+ projectTargetExternalChangeSignal.receive(event);
1576
+ continue;
1577
+ }
1578
+ const targetSocket = runtimeSockets.get(runtimeId);
1579
+ if (targetSocket?.readyState === WebSocket.OPEN) targetSocket.send(JSON.stringify(message));
1580
+ }
1581
+ };
1383
1582
  const appendHubLog = async (event) => {
1384
1583
  const runtimeHello = runtimeHellos.get(event.runtimeId);
1385
- const clientRuntimeIds = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.runtimeId).sort();
1386
- const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.storageIdentity ?? `runtime::${hello.runtimeId}`).sort();
1584
+ const clientRuntimeIds = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub").map((hello) => hello.runtimeId).sort();
1585
+ const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub").map((hello) => hello.storageIdentity ?? `runtime::${hello.runtimeId}`).sort();
1387
1586
  const targetIdentity = runtimeHello?.storageIdentity ?? `runtime::${event.runtimeId}`;
1388
- const targetId = event.runtimeId === "syncore-dev-hub" ? "all" : runtimeHello?.targetKind === "project" ? "project" : createPublicTargetId(targetIdentity, clientTargetKeys);
1587
+ const targetId = event.runtimeId === "syncore-dev-hub" ? "all" : createPublicTargetId(targetIdentity, clientTargetKeys);
1389
1588
  const publicRuntimeId = event.runtimeId === "syncore-dev-hub" ? void 0 : createPublicRuntimeId(event.runtimeId, clientRuntimeIds);
1390
1589
  const category = event.type === "query.executed" ? "query" : event.type === "mutation.committed" ? "mutation" : event.type === "action.completed" ? "action" : "system";
1391
1590
  const message = event.type === "log" ? event.message : event.type === "query.executed" || event.type === "mutation.committed" || event.type === "action.completed" ? event.functionName : event.type;
@@ -1402,6 +1601,135 @@ async function startDevHub(options) {
1402
1601
  event
1403
1602
  })}\n`);
1404
1603
  };
1604
+ const requestRuntimeCommand = async (targetRuntimeId, payload, timeoutMs = 3e4) => {
1605
+ if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) return projectTargetBackend.handleCommand(payload);
1606
+ const target = runtimeSockets.get(targetRuntimeId);
1607
+ if (!target || target.readyState !== WebSocket.OPEN) throw new Error(`Runtime ${targetRuntimeId} is not connected.`);
1608
+ const commandId = `hub:${randomUUID()}`;
1609
+ return await new Promise((resolve, reject) => {
1610
+ const timeout = setTimeout(() => {
1611
+ pendingHubCommands.delete(commandId);
1612
+ reject(/* @__PURE__ */ new Error(`Runtime command ${payload.kind} timed out.`));
1613
+ }, timeoutMs);
1614
+ pendingHubCommands.set(commandId, {
1615
+ resolve,
1616
+ reject,
1617
+ timeout
1618
+ });
1619
+ target.send(JSON.stringify({
1620
+ type: "command",
1621
+ commandId,
1622
+ targetRuntimeId,
1623
+ payload
1624
+ }));
1625
+ });
1626
+ };
1627
+ const createStorageAccessTicket = async (targetRuntimeId, id, purpose) => {
1628
+ const metadata = await requestRuntimeCommand(targetRuntimeId, {
1629
+ kind: "storage.readRange",
1630
+ id,
1631
+ offset: 0,
1632
+ length: 0
1633
+ });
1634
+ if (metadata.kind !== "storage.readRange.result") return {
1635
+ kind: "storage.access.create.result",
1636
+ error: "Runtime returned an unexpected storage access response."
1637
+ };
1638
+ if (metadata.error || !metadata.entry) return {
1639
+ kind: "storage.access.create.result",
1640
+ error: metadata.error ?? "Storage object could not be accessed."
1641
+ };
1642
+ const ticket = randomUUID();
1643
+ const expiresAt = Date.now() + STORAGE_ACCESS_TICKET_TTL_MS;
1644
+ storageAccessTickets.set(ticket, {
1645
+ id: ticket,
1646
+ runtimeId: targetRuntimeId,
1647
+ storageId: id,
1648
+ purpose,
1649
+ entry: metadata.entry,
1650
+ supportsRange: metadata.supportsRange,
1651
+ expiresAt
1652
+ });
1653
+ cleanupExpiredStorageTickets();
1654
+ return {
1655
+ kind: "storage.access.create.result",
1656
+ entry: metadata.entry,
1657
+ url: `http://127.0.0.1:${devtoolsPort}/storage/access/${ticket}`,
1658
+ expiresAt,
1659
+ supportsRange: metadata.supportsRange,
1660
+ maxPreviewBytes: STORAGE_ACCESS_MAX_PREVIEW_BYTES
1661
+ };
1662
+ };
1663
+ const cleanupExpiredStorageTickets = () => {
1664
+ const now = Date.now();
1665
+ for (const [ticket, access] of storageAccessTickets) if (access.expiresAt <= now) storageAccessTickets.delete(ticket);
1666
+ };
1667
+ const handleStorageAccessHttpRequest = async (request, response) => {
1668
+ setStorageCorsHeaders(response);
1669
+ if (request.method === "OPTIONS") {
1670
+ response.writeHead(204);
1671
+ response.end();
1672
+ return;
1673
+ }
1674
+ const requestUrl = new URL(request.url ?? "/", `http://127.0.0.1:${devtoolsPort}`);
1675
+ const match = /^\/storage\/access\/([^/]+)$/.exec(requestUrl.pathname);
1676
+ if (!match) {
1677
+ writeJsonResponse(response, 200, {
1678
+ ok: true,
1679
+ wsPort: devtoolsPort
1680
+ });
1681
+ return;
1682
+ }
1683
+ if (request.method !== "GET" && request.method !== "HEAD") {
1684
+ writeTextResponse(response, 405, "Method not allowed.");
1685
+ return;
1686
+ }
1687
+ cleanupExpiredStorageTickets();
1688
+ const ticket = storageAccessTickets.get(match[1]);
1689
+ if (!ticket || ticket.expiresAt <= Date.now()) {
1690
+ writeTextResponse(response, 401, "Storage access ticket is invalid or expired.");
1691
+ return;
1692
+ }
1693
+ const range = parseStorageRangeHeader(request.headers.range, ticket.entry.size);
1694
+ if ("error" in range) {
1695
+ writeTextResponse(response, range.status, range.error);
1696
+ return;
1697
+ }
1698
+ const start = range.start;
1699
+ const end = range.end;
1700
+ const byteLength = end >= start ? end - start + 1 : 0;
1701
+ if (!ticket.supportsRange && ticket.entry.size > STORAGE_ACCESS_CHUNK_BYTES) {
1702
+ writeTextResponse(response, 409, "This storage backend does not support streaming large files.");
1703
+ return;
1704
+ }
1705
+ writeStorageAccessHeaders(response, ticket, {
1706
+ status: range.partial ? 206 : 200,
1707
+ start,
1708
+ end,
1709
+ byteLength
1710
+ });
1711
+ if (request.method === "HEAD") {
1712
+ response.end();
1713
+ return;
1714
+ }
1715
+ let offset = start;
1716
+ while (offset <= end) {
1717
+ const chunkLength = Math.min(STORAGE_ACCESS_CHUNK_BYTES, end - offset + 1);
1718
+ const chunk = await requestRuntimeCommand(ticket.runtimeId, {
1719
+ kind: "storage.readRange",
1720
+ id: ticket.storageId,
1721
+ offset,
1722
+ length: chunkLength
1723
+ }, 6e4);
1724
+ if (chunk.kind !== "storage.readRange.result") throw new Error("Runtime returned an unexpected storage chunk response.");
1725
+ if (chunk.error || !chunk.base64) throw new Error(chunk.error ?? "Storage chunk could not be read.");
1726
+ const bytes = Buffer.from(chunk.base64, "base64");
1727
+ if (bytes.byteLength === 0) break;
1728
+ await writeResponseChunk(response, bytes);
1729
+ offset += bytes.byteLength;
1730
+ }
1731
+ response.end();
1732
+ };
1405
1733
  websocketServer.on("connection", (socket, request) => {
1406
1734
  const isBrowserDashboardClient = isAllowedDashboardOrigin(request.headers.origin, dashboardPort);
1407
1735
  const isAuthorizedDashboardClient = !isBrowserDashboardClient || isAuthorizedDashboardRequest({
@@ -1438,10 +1766,38 @@ async function startDevHub(options) {
1438
1766
  socket.send(JSON.stringify({ type: "pong" }));
1439
1767
  return;
1440
1768
  }
1769
+ if (message.type === "external.change") {
1770
+ const storageIdentity = runtimeHellos.get(message.runtimeId)?.storageIdentity ?? message.storageIdentity;
1771
+ if (storageIdentity && storageIdentity === message.storageIdentity) relayExternalChange(message.runtimeId, storageIdentity, message.event);
1772
+ return;
1773
+ }
1441
1774
  if (message.type === "command") {
1442
1775
  if (!isAuthorizedDashboardClient) return;
1443
1776
  const targetRuntimeId = message.targetRuntimeId;
1444
1777
  if (!targetRuntimeId) return;
1778
+ if (message.payload.kind === "storage.access.create") {
1779
+ createStorageAccessTicket(targetRuntimeId, message.payload.id, message.payload.purpose).then((payload) => {
1780
+ if (socket.readyState !== WebSocket.OPEN) return;
1781
+ socket.send(JSON.stringify({
1782
+ type: "command.result",
1783
+ commandId: message.commandId,
1784
+ runtimeId: targetRuntimeId,
1785
+ payload
1786
+ }));
1787
+ }).catch((error) => {
1788
+ if (socket.readyState !== WebSocket.OPEN) return;
1789
+ socket.send(JSON.stringify({
1790
+ type: "command.result",
1791
+ commandId: message.commandId,
1792
+ runtimeId: targetRuntimeId,
1793
+ payload: {
1794
+ kind: "storage.access.create.result",
1795
+ error: formatError(error)
1796
+ }
1797
+ }));
1798
+ });
1799
+ return;
1800
+ }
1445
1801
  if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
1446
1802
  (async () => {
1447
1803
  const payload = await projectTargetBackend.handleCommand(message.payload);
@@ -1539,12 +1895,21 @@ async function startDevHub(options) {
1539
1895
  if (message.type === "event" && message.event.type === "runtime.disconnected") runtimeHellos.delete(message.event.runtimeId);
1540
1896
  if (message.type === "event" && message.event.runtimeId !== "syncore-dev-hub") {
1541
1897
  const history = runtimeEvents.get(message.event.runtimeId) ?? [];
1542
- history.unshift(message.event);
1543
- runtimeEvents.set(message.event.runtimeId, history.slice(0, 200));
1898
+ history.push(message.event);
1899
+ runtimeEvents.set(message.event.runtimeId, history.slice(-200));
1544
1900
  if (message.event.type === "runtime.disconnected") runtimeEvents.delete(message.event.runtimeId);
1545
1901
  appendHubLog(message.event);
1546
1902
  } else if (message.type === "event") appendHubLog(message.event);
1547
1903
  if (message.type === "command.result" || message.type === "subscription.data" || message.type === "subscription.error") {
1904
+ if (message.type === "command.result") {
1905
+ const pending = pendingHubCommands.get(message.commandId);
1906
+ if (pending) {
1907
+ clearTimeout(pending.timeout);
1908
+ pendingHubCommands.delete(message.commandId);
1909
+ pending.resolve(message.payload);
1910
+ return;
1911
+ }
1912
+ }
1548
1913
  for (const client of dashboardSockets) if (client.readyState === WebSocket.OPEN) client.send(encoded);
1549
1914
  return;
1550
1915
  }
@@ -1634,6 +1999,80 @@ async function startDevHub(options) {
1634
1999
  process.on("SIGTERM", close);
1635
2000
  return sessionState;
1636
2001
  }
2002
+ function setStorageCorsHeaders(response) {
2003
+ response.setHeader("Access-Control-Allow-Origin", "*");
2004
+ response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
2005
+ response.setHeader("Access-Control-Allow-Headers", "Range");
2006
+ response.setHeader("Access-Control-Expose-Headers", "Accept-Ranges, Content-Disposition, Content-Length, Content-Range, Content-Type");
2007
+ }
2008
+ function writeJsonResponse(response, status, payload) {
2009
+ response.writeHead(status, { "content-type": "application/json" });
2010
+ response.end(JSON.stringify(payload));
2011
+ }
2012
+ function writeTextResponse(response, status, message) {
2013
+ response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
2014
+ response.end(message);
2015
+ }
2016
+ function parseStorageRangeHeader(header, size) {
2017
+ if (size === 0) {
2018
+ if (!header) return {
2019
+ start: 0,
2020
+ end: -1,
2021
+ partial: false
2022
+ };
2023
+ return {
2024
+ error: "Requested range is not satisfiable.",
2025
+ status: 416
2026
+ };
2027
+ }
2028
+ if (!header) return {
2029
+ start: 0,
2030
+ end: size - 1,
2031
+ partial: false
2032
+ };
2033
+ if (header.includes(",")) return {
2034
+ error: "Multi-range requests are not supported.",
2035
+ status: 416
2036
+ };
2037
+ const match = /^bytes=(\d+)-(\d*)$/.exec(header);
2038
+ if (!match) return {
2039
+ error: "Only simple byte ranges are supported.",
2040
+ status: 416
2041
+ };
2042
+ const start = Number(match[1]);
2043
+ const end = match[2] ? Number(match[2]) : size - 1;
2044
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || start >= size) return {
2045
+ error: "Requested range is not satisfiable.",
2046
+ status: 416
2047
+ };
2048
+ return {
2049
+ start,
2050
+ end: Math.min(end, size - 1),
2051
+ partial: true
2052
+ };
2053
+ }
2054
+ function writeStorageAccessHeaders(response, ticket, range) {
2055
+ const headers = {
2056
+ "content-type": ticket.entry.contentType ?? "application/octet-stream",
2057
+ "content-length": range.byteLength,
2058
+ "accept-ranges": ticket.supportsRange ? "bytes" : "none",
2059
+ "content-disposition": renderStorageContentDisposition(ticket)
2060
+ };
2061
+ if (range.status === 206) headers["content-range"] = `bytes ${range.start}-${range.end}/${ticket.entry.size}`;
2062
+ response.writeHead(range.status, headers);
2063
+ }
2064
+ function renderStorageContentDisposition(ticket) {
2065
+ return `${ticket.purpose === "download" ? "attachment" : "inline"}; filename="${sanitizeHeaderFilename(ticket.entry.fileName ?? ticket.entry.id)}"; filename*=UTF-8''${encodeURIComponent(ticket.entry.fileName ?? `${ticket.entry.id}.bin`)}`;
2066
+ }
2067
+ function sanitizeHeaderFilename(value) {
2068
+ return value.replaceAll(/["\\\r\n]/g, "_");
2069
+ }
2070
+ async function writeResponseChunk(response, chunk) {
2071
+ if (response.write(chunk)) return;
2072
+ await new Promise((resolve) => {
2073
+ response.once("drain", resolve);
2074
+ });
2075
+ }
1637
2076
  async function writeDevtoolsSessionState(cwd, state) {
1638
2077
  const sessionPath = path.join(cwd, DEVTOOLS_SESSION_FILE);
1639
2078
  await mkdir(path.dirname(sessionPath), { recursive: true });
@@ -1795,6 +2234,6 @@ function toSearchValue(value) {
1795
2234
  return stableStringify(value);
1796
2235
  }
1797
2236
  //#endregion
1798
- export { SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME, VALID_SYNCORE_TEMPLATES, applyProjectMigrations, detectProjectTemplate, fileExists, formatError, getNextMigrationNumber, hasSyncoreProject, importJsonlIntoProject, isLocalPortInUse, loadProjectComponentsManifest, loadProjectConfig, loadProjectFunctions, loadProjectResolvedComponents, loadProjectRootSchema, loadProjectSchema, logScaffoldResult, readPackageJson, readStoredSnapshot, resolveDefaultSeedFile, resolvePortFromEnv, resolveProjectTargetConfig, resolveRequestedTemplate, runCodegen, runDevProjectBootstrap, runSyncoreCli, scaffoldProject, slugify, startDevHub, templateUsesConnectedClients, writeStoredSnapshot };
2237
+ 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 };
1799
2238
 
1800
2239
  //# sourceMappingURL=cli.mjs.map