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.
- package/dist/_vendor/cli/app.d.mts.map +1 -1
- package/dist/_vendor/cli/app.mjs +8 -5
- package/dist/_vendor/cli/app.mjs.map +1 -1
- package/dist/_vendor/cli/context.mjs.map +1 -1
- package/dist/_vendor/cli/dev-session.mjs.map +1 -1
- package/dist/_vendor/cli/doctor.mjs.map +1 -1
- package/dist/_vendor/cli/errors.mjs.map +1 -1
- package/dist/_vendor/cli/help.mjs.map +1 -1
- package/dist/_vendor/cli/index.mjs +9 -2
- package/dist/_vendor/cli/index.mjs.map +1 -1
- package/dist/_vendor/cli/messages.mjs.map +1 -1
- package/dist/_vendor/cli/preflight.mjs.map +1 -1
- package/dist/_vendor/cli/project.mjs +20 -20
- package/dist/_vendor/cli/project.mjs.map +1 -1
- package/dist/_vendor/cli/render.mjs.map +1 -1
- package/dist/_vendor/cli/targets.mjs.map +1 -1
- package/dist/_vendor/core/cli.d.mts +8 -2
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +510 -71
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/devtools-auth.mjs.map +1 -1
- package/dist/_vendor/core/index.d.mts +3 -3
- package/dist/_vendor/core/runtime/components.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/components.mjs.map +1 -1
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +261 -23
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +388 -6
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs +72 -1
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/id.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +12 -6
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +123 -20
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +56 -8
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +49 -14
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +4 -7
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +81 -2
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +100 -13
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +42 -7
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +4 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -1
- package/dist/_vendor/core/runtime/runtime.d.mts +1100 -12
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +63 -0
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts +2 -0
- package/dist/_vendor/core/transport.d.mts.map +1 -1
- package/dist/_vendor/core/transport.mjs +61 -27
- package/dist/_vendor/core/transport.mjs.map +1 -1
- package/dist/_vendor/devtools-protocol/index.d.ts +223 -4
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/config.d.ts +3 -4
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +37 -19
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/next/index.d.ts +109 -29
- package/dist/_vendor/next/index.d.ts.map +1 -1
- package/dist/_vendor/next/index.js +86 -18
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +146 -27
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +81 -10
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-node/index.d.mts +174 -9
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +251 -95
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +41 -0
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
- package/dist/_vendor/platform-web/external-change.js +30 -0
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +312 -37
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +247 -25
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js +10 -0
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.d.ts +16 -1
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
- package/dist/_vendor/platform-web/opfs.js +41 -3
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.d.ts +85 -1
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
- package/dist/_vendor/platform-web/persistence.js +15 -0
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts +1 -2
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +11 -5
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +10 -1
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
- package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-web/worker.d.ts +60 -9
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
- package/dist/_vendor/platform-web/worker.js +37 -4
- package/dist/_vendor/platform-web/worker.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +197 -13
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +209 -17
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/schema/definition.d.ts +129 -0
- package/dist/_vendor/schema/definition.d.ts.map +1 -1
- package/dist/_vendor/schema/definition.js +99 -0
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/planner.d.ts.map +1 -1
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.d.ts +180 -4
- package/dist/_vendor/schema/validators.d.ts.map +1 -1
- package/dist/_vendor/schema/validators.js +35 -1
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +207 -7
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +201 -6
- package/dist/_vendor/svelte/index.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- 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
|
|
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
|
|
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
|
|
345
|
-
await
|
|
346
|
-
await
|
|
347
|
-
await
|
|
348
|
-
await
|
|
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 ("
|
|
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))
|
|
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
|
|
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
|
|
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(
|
|
949
|
-
this.
|
|
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
|
|
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.
|
|
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
|
-
|
|
1306
|
-
resolve(true);
|
|
1484
|
+
finish(true);
|
|
1307
1485
|
});
|
|
1308
1486
|
socket.once("error", () => {
|
|
1309
|
-
|
|
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
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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"
|
|
1386
|
-
const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub"
|
|
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" :
|
|
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.
|
|
1543
|
-
runtimeEvents.set(message.event.runtimeId, history.slice(
|
|
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
|