syncorejs 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{core/src/cli.d.ts → _vendor/cli/app.d.mts} +4 -2
- package/dist/_vendor/cli/app.d.mts.map +1 -0
- package/dist/_vendor/cli/app.mjs +997 -0
- package/dist/_vendor/cli/app.mjs.map +1 -0
- package/dist/_vendor/cli/context.mjs +180 -0
- package/dist/_vendor/cli/context.mjs.map +1 -0
- package/dist/_vendor/cli/dev-session.mjs +49 -0
- package/dist/_vendor/cli/dev-session.mjs.map +1 -0
- package/dist/_vendor/cli/doctor.mjs +80 -0
- package/dist/_vendor/cli/doctor.mjs.map +1 -0
- package/dist/_vendor/cli/errors.mjs +22 -0
- package/dist/_vendor/cli/errors.mjs.map +1 -0
- package/dist/_vendor/cli/help.mjs +26 -0
- package/dist/_vendor/cli/help.mjs.map +1 -0
- package/dist/_vendor/cli/index.d.mts +2 -0
- package/dist/_vendor/cli/index.mjs +23 -0
- package/dist/_vendor/cli/index.mjs.map +1 -0
- package/dist/_vendor/cli/messages.mjs +32 -0
- package/dist/_vendor/cli/messages.mjs.map +1 -0
- package/dist/_vendor/cli/preflight.mjs +35 -0
- package/dist/_vendor/cli/preflight.mjs.map +1 -0
- package/dist/_vendor/cli/project.mjs +583 -0
- package/dist/_vendor/cli/project.mjs.map +1 -0
- package/dist/_vendor/cli/render.mjs +133 -0
- package/dist/_vendor/cli/render.mjs.map +1 -0
- package/dist/_vendor/cli/targets.mjs +87 -0
- package/dist/_vendor/cli/targets.mjs.map +1 -0
- package/dist/_vendor/core/cli.d.mts +59 -1
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +528 -75
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/index.d.mts +12 -4
- package/dist/_vendor/core/index.d.mts.map +1 -0
- package/dist/_vendor/core/index.mjs +4 -3
- package/dist/_vendor/core/index.mjs.map +1 -1
- package/dist/_vendor/core/runtime/devtools.d.mts +32 -6
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +397 -182
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/runtime.d.mts +89 -7
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +303 -32
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/devtools-protocol/index.d.ts +189 -82
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js +39 -0
- package/dist/_vendor/devtools-protocol/index.js.map +1 -0
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +2 -5
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +15 -5
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +33 -3
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-node/index.d.mts +10 -5
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +145 -35
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +39 -0
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -0
- package/dist/_vendor/platform-web/external-change.js +61 -0
- package/dist/_vendor/platform-web/external-change.js.map +1 -0
- package/dist/_vendor/platform-web/index.d.ts +27 -5
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +310 -44
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +22 -2
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/browser-react.d.ts +1 -1
- package/dist/browser-react.js +1 -1
- package/dist/browser.d.ts +6 -7
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +4 -5
- package/dist/browser.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/expo-react.d.ts +1 -1
- package/dist/expo-react.js +1 -1
- package/dist/expo.d.ts +1 -2
- package/dist/expo.js +1 -2
- package/dist/index.d.ts +3 -7
- package/dist/index.js +3 -8
- package/dist/next-config.d.ts +1 -2
- package/dist/next-config.js +1 -2
- package/dist/next.d.ts +1 -3
- package/dist/next.js +1 -3
- package/dist/node-ipc-react.d.ts +1 -1
- package/dist/node-ipc-react.js +1 -1
- package/dist/node-ipc.d.ts +1 -2
- package/dist/node-ipc.js +1 -2
- package/dist/node.d.ts +1 -4
- package/dist/node.js +1 -3
- package/dist/react.d.ts +1 -2
- package/dist/react.js +1 -2
- package/dist/svelte.d.ts +1 -2
- package/dist/svelte.js +1 -2
- package/package.json +6 -3
- package/dist/core/src/cli.d.ts.map +0 -1
- package/dist/core/src/cli.js +0 -1196
- package/dist/core/src/cli.js.map +0 -1
- package/dist/core/src/index.js +0 -7
- package/dist/core/src/runtime/devtools.d.ts +0 -7
- package/dist/core/src/runtime/devtools.d.ts.map +0 -1
- package/dist/core/src/runtime/devtools.js +0 -300
- package/dist/core/src/runtime/devtools.js.map +0 -1
- package/dist/core/src/runtime/functions.d.ts +0 -123
- package/dist/core/src/runtime/functions.d.ts.map +0 -1
- package/dist/core/src/runtime/functions.js +0 -71
- package/dist/core/src/runtime/functions.js.map +0 -1
- package/dist/core/src/runtime/id.d.ts +0 -13
- package/dist/core/src/runtime/id.d.ts.map +0 -1
- package/dist/core/src/runtime/id.js +0 -28
- package/dist/core/src/runtime/id.js.map +0 -1
- package/dist/core/src/runtime/runtime.d.ts +0 -371
- package/dist/core/src/runtime/runtime.d.ts.map +0 -1
- package/dist/core/src/runtime/runtime.js +0 -1143
- package/dist/core/src/runtime/runtime.js.map +0 -1
- package/dist/devtools-protocol/src/index.d.ts +0 -201
- package/dist/devtools-protocol/src/index.d.ts.map +0 -1
- package/dist/next/src/config.d.ts +0 -17
- package/dist/next/src/config.d.ts.map +0 -1
- package/dist/next/src/config.js +0 -73
- package/dist/next/src/config.js.map +0 -1
- package/dist/next/src/index.d.ts +0 -80
- package/dist/next/src/index.d.ts.map +0 -1
- package/dist/next/src/index.js +0 -82
- package/dist/next/src/index.js.map +0 -1
- package/dist/platform-expo/src/index.d.ts +0 -96
- package/dist/platform-expo/src/index.d.ts.map +0 -1
- package/dist/platform-expo/src/index.js +0 -198
- package/dist/platform-expo/src/index.js.map +0 -1
- package/dist/platform-expo/src/react.d.ts +0 -26
- package/dist/platform-expo/src/react.d.ts.map +0 -1
- package/dist/platform-expo/src/react.js +0 -30
- package/dist/platform-expo/src/react.js.map +0 -1
- package/dist/platform-node/src/index.d.ts +0 -145
- package/dist/platform-node/src/index.d.ts.map +0 -1
- package/dist/platform-node/src/index.js +0 -407
- package/dist/platform-node/src/index.js.map +0 -1
- package/dist/platform-node/src/ipc-react.d.ts +0 -25
- package/dist/platform-node/src/ipc-react.d.ts.map +0 -1
- package/dist/platform-node/src/ipc-react.js +0 -21
- package/dist/platform-node/src/ipc-react.js.map +0 -1
- package/dist/platform-node/src/ipc.d.ts +0 -76
- package/dist/platform-node/src/ipc.d.ts.map +0 -1
- package/dist/platform-node/src/ipc.js +0 -344
- package/dist/platform-node/src/ipc.js.map +0 -1
- package/dist/platform-web/src/index.d.ts +0 -106
- package/dist/platform-web/src/index.d.ts.map +0 -1
- package/dist/platform-web/src/index.js +0 -311
- package/dist/platform-web/src/index.js.map +0 -1
- package/dist/platform-web/src/indexeddb.js +0 -125
- package/dist/platform-web/src/indexeddb.js.map +0 -1
- package/dist/platform-web/src/opfs.js +0 -146
- package/dist/platform-web/src/opfs.js.map +0 -1
- package/dist/platform-web/src/persistence.d.ts +0 -20
- package/dist/platform-web/src/persistence.d.ts.map +0 -1
- package/dist/platform-web/src/persistence.js +0 -23
- package/dist/platform-web/src/persistence.js.map +0 -1
- package/dist/platform-web/src/react.d.ts +0 -35
- package/dist/platform-web/src/react.d.ts.map +0 -1
- package/dist/platform-web/src/react.js +0 -42
- package/dist/platform-web/src/react.js.map +0 -1
- package/dist/platform-web/src/sqljs.js +0 -133
- package/dist/platform-web/src/sqljs.js.map +0 -1
- package/dist/platform-web/src/worker.d.ts +0 -79
- package/dist/platform-web/src/worker.d.ts.map +0 -1
- package/dist/platform-web/src/worker.js +0 -308
- package/dist/platform-web/src/worker.js.map +0 -1
- package/dist/react/src/index.d.ts +0 -59
- package/dist/react/src/index.d.ts.map +0 -1
- package/dist/react/src/index.js +0 -151
- package/dist/react/src/index.js.map +0 -1
- package/dist/schema/src/definition.d.ts +0 -98
- package/dist/schema/src/definition.d.ts.map +0 -1
- package/dist/schema/src/definition.js +0 -84
- package/dist/schema/src/definition.js.map +0 -1
- package/dist/schema/src/planner.d.ts +0 -42
- package/dist/schema/src/planner.d.ts.map +0 -1
- package/dist/schema/src/planner.js +0 -131
- package/dist/schema/src/planner.js.map +0 -1
- package/dist/schema/src/validators.d.ts +0 -194
- package/dist/schema/src/validators.d.ts.map +0 -1
- package/dist/schema/src/validators.js +0 -158
- package/dist/schema/src/validators.js.map +0 -1
- package/dist/svelte/src/index.d.ts +0 -44
- package/dist/svelte/src/index.d.ts.map +0 -1
- package/dist/svelte/src/index.js +0 -75
- package/dist/svelte/src/index.js.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { generateId } from "./runtime/id.mjs";
|
|
3
|
+
import { SyncoreRuntime } from "./runtime/runtime.mjs";
|
|
4
|
+
import { createDevtoolsCommandHandler, createDevtoolsSubscriptionHost } from "./runtime/devtools.mjs";
|
|
3
5
|
import { src_exports } from "./index.mjs";
|
|
4
|
-
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { appendFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
5
7
|
import { createServer } from "node:http";
|
|
6
8
|
import { connect } from "node:net";
|
|
7
9
|
import path from "node:path";
|
|
@@ -10,12 +12,13 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
10
12
|
import { Command } from "commander";
|
|
11
13
|
import { tsImport } from "tsx/esm/api";
|
|
12
14
|
import WebSocket, { WebSocketServer } from "ws";
|
|
15
|
+
import { createPublicRuntimeId, createPublicTargetId } from "../devtools-protocol/index.js";
|
|
13
16
|
//#region src/cli.ts
|
|
14
17
|
const COMBINED_DEV_COMMAND = "concurrently --kill-others-on-fail --names syncore,app --prefix-colors yellow,cyan \"bun run syncorejs:dev\" \"bun run dev:app\"";
|
|
15
18
|
const program = new Command();
|
|
16
19
|
const CORE_PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
-
const
|
|
18
|
-
const
|
|
20
|
+
const SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME = "_schema_snapshot.json";
|
|
21
|
+
const VALID_SYNCORE_TEMPLATES = [
|
|
19
22
|
"minimal",
|
|
20
23
|
"node",
|
|
21
24
|
"react-web",
|
|
@@ -25,8 +28,9 @@ const validTemplates = [
|
|
|
25
28
|
];
|
|
26
29
|
let pendingDevBootstrap;
|
|
27
30
|
let devBootstrapInFlight = false;
|
|
31
|
+
const PROJECT_TARGET_RUNTIME_ID = "syncore-project-target";
|
|
28
32
|
program.name("syncorejs").description("Syncore local-first toolkit CLI").version("0.1.0");
|
|
29
|
-
program.command("init").description("Scaffold Syncore in the current directory").option("--template <template>", `Template to scaffold (${
|
|
33
|
+
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) => {
|
|
30
34
|
const cwd = process.cwd();
|
|
31
35
|
const result = await scaffoldProject(cwd, {
|
|
32
36
|
template: await resolveRequestedTemplate(cwd, options.template),
|
|
@@ -105,7 +109,7 @@ program.command("migrate:apply").description("Apply SQL migrations from syncore/
|
|
|
105
109
|
const appliedCount = await applyProjectMigrations(process.cwd());
|
|
106
110
|
console.log(`Applied ${appliedCount} migration(s).`);
|
|
107
111
|
});
|
|
108
|
-
program.command("dev").description("Start the Syncore dev loop and devtools hub").option("--template <template>", `Template to scaffold when Syncore is missing (${
|
|
112
|
+
program.command("dev").description("Start the Syncore dev loop and devtools hub").option("--template <template>", `Template to scaffold when Syncore is missing (${VALID_SYNCORE_TEMPLATES.join(", ")}, or auto)`, "auto").action(async (options) => {
|
|
109
113
|
const cwd = process.cwd();
|
|
110
114
|
await startDevHub({
|
|
111
115
|
cwd,
|
|
@@ -337,11 +341,7 @@ function buildTemplateFiles(template) {
|
|
|
337
341
|
const files = [
|
|
338
342
|
{
|
|
339
343
|
path: "syncore.config.ts",
|
|
340
|
-
content:
|
|
341
|
-
databasePath: ".syncore/syncore.db",
|
|
342
|
-
storageDirectory: ".syncore/storage"
|
|
343
|
-
};
|
|
344
|
-
`
|
|
344
|
+
content: renderSyncoreConfigTemplate(template)
|
|
345
345
|
},
|
|
346
346
|
{
|
|
347
347
|
path: path.join("syncore", "schema.ts"),
|
|
@@ -513,6 +513,17 @@ export function createAppSyncoreRuntime() {
|
|
|
513
513
|
}
|
|
514
514
|
return files;
|
|
515
515
|
}
|
|
516
|
+
function renderSyncoreConfigTemplate(template) {
|
|
517
|
+
if (template === "node" || template === "electron") return `export default {
|
|
518
|
+
projectTarget: {
|
|
519
|
+
databasePath: ".syncore/syncore.db",
|
|
520
|
+
storageDirectory: ".syncore/storage"
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
`;
|
|
524
|
+
return `export default {};
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
516
527
|
function logScaffoldResult(result, heading) {
|
|
517
528
|
console.log(heading);
|
|
518
529
|
console.log(`Using template: ${result.template}`);
|
|
@@ -529,7 +540,7 @@ async function hasSyncoreProject(cwd) {
|
|
|
529
540
|
}
|
|
530
541
|
async function resolveRequestedTemplate(cwd, requestedTemplate) {
|
|
531
542
|
if (requestedTemplate !== "auto") {
|
|
532
|
-
if (!
|
|
543
|
+
if (!VALID_SYNCORE_TEMPLATES.includes(requestedTemplate)) throw new Error(`Unknown template ${JSON.stringify(requestedTemplate)}. Expected one of ${VALID_SYNCORE_TEMPLATES.join(", ")} or auto.`);
|
|
533
544
|
return requestedTemplate;
|
|
534
545
|
}
|
|
535
546
|
return detectProjectTemplate(cwd);
|
|
@@ -622,9 +633,9 @@ async function writeManagedFile(filePath, content, force = false) {
|
|
|
622
633
|
async function importJsonlIntoProject(cwd, tableName, sourcePath) {
|
|
623
634
|
const schema = await loadProjectSchema(cwd);
|
|
624
635
|
const table = schema.getTable(tableName);
|
|
625
|
-
const
|
|
626
|
-
const databasePath = path.resolve(cwd,
|
|
627
|
-
const storageDirectory = path.resolve(cwd,
|
|
636
|
+
const projectTarget = requireProjectTargetConfig(await loadProjectConfig(cwd));
|
|
637
|
+
const databasePath = path.resolve(cwd, projectTarget.databasePath);
|
|
638
|
+
const storageDirectory = path.resolve(cwd, projectTarget.storageDirectory);
|
|
628
639
|
const sourceFilePath = path.resolve(cwd, sourcePath);
|
|
629
640
|
await mkdir(path.dirname(databasePath), { recursive: true });
|
|
630
641
|
await mkdir(storageDirectory, { recursive: true });
|
|
@@ -798,10 +809,27 @@ function renderFunctionTypeImports(functionEntries, extension) {
|
|
|
798
809
|
function renderFunctionImportName(entry) {
|
|
799
810
|
return [...entry.pathParts.map((segment) => segment.replace(/[^a-zA-Z0-9_$]/g, "_")), entry.exportName].join("__");
|
|
800
811
|
}
|
|
812
|
+
function resolveProjectTargetConfig(config) {
|
|
813
|
+
if (config.projectTarget && typeof config.projectTarget === "object" && typeof config.projectTarget.databasePath === "string" && typeof config.projectTarget.storageDirectory === "string") return config.projectTarget;
|
|
814
|
+
if (typeof config.databasePath === "string" && typeof config.storageDirectory === "string") return {
|
|
815
|
+
databasePath: config.databasePath,
|
|
816
|
+
storageDirectory: config.storageDirectory
|
|
817
|
+
};
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
801
820
|
async function loadProjectConfig(cwd) {
|
|
802
821
|
const config = await loadDefaultExport(path.join(cwd, "syncore.config.ts"));
|
|
803
|
-
if (!config || typeof config !== "object"
|
|
804
|
-
|
|
822
|
+
if (!config || typeof config !== "object") throw new Error("syncore.config.ts must default export a Syncore config object.");
|
|
823
|
+
const projectTarget = resolveProjectTargetConfig(config);
|
|
824
|
+
return projectTarget ? {
|
|
825
|
+
...config,
|
|
826
|
+
projectTarget
|
|
827
|
+
} : config;
|
|
828
|
+
}
|
|
829
|
+
function requireProjectTargetConfig(config) {
|
|
830
|
+
const projectTarget = resolveProjectTargetConfig(config);
|
|
831
|
+
if (!projectTarget) throw new Error("This Syncore project does not define a projectTarget. Use a connected client target instead.");
|
|
832
|
+
return projectTarget;
|
|
805
833
|
}
|
|
806
834
|
async function loadProjectSchema(cwd) {
|
|
807
835
|
const schema = await loadDefaultExport(path.join(cwd, "syncore", "schema.ts"));
|
|
@@ -817,15 +845,267 @@ async function loadDefaultExport(filePath) {
|
|
|
817
845
|
if (resolvedDefault === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined.`);
|
|
818
846
|
return resolvedDefault;
|
|
819
847
|
}
|
|
848
|
+
async function loadNamedExport(filePath, exportName) {
|
|
849
|
+
if (!await fileExists(filePath)) throw new Error(`Missing file: ${path.relative(process.cwd(), filePath)}`);
|
|
850
|
+
const moduleUrl = pathToFileURL(filePath).href;
|
|
851
|
+
const loaded = await tsImport(moduleUrl, { parentURL: import.meta.url });
|
|
852
|
+
const defaultExport = loaded.default && typeof loaded.default === "object" && exportName in loaded.default ? loaded.default[exportName] : void 0;
|
|
853
|
+
if (!(exportName in loaded) && defaultExport === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} must export ${exportName}.`);
|
|
854
|
+
const resolvedValue = unwrapDefaultExport(loaded[exportName] ?? defaultExport);
|
|
855
|
+
if (resolvedValue === void 0) throw new Error(`File ${path.relative(process.cwd(), filePath)} exported undefined for ${exportName}.`);
|
|
856
|
+
return resolvedValue;
|
|
857
|
+
}
|
|
858
|
+
async function loadProjectFunctions(cwd) {
|
|
859
|
+
const functions = await loadNamedExport(path.join(cwd, "syncore", "_generated", "functions.ts"), "functions");
|
|
860
|
+
if (!functions || typeof functions !== "object") throw new Error("syncore/_generated/functions.ts must export a functions registry.");
|
|
861
|
+
return functions;
|
|
862
|
+
}
|
|
863
|
+
var HubSqliteDriver = class {
|
|
864
|
+
database;
|
|
865
|
+
transactionDepth = 0;
|
|
866
|
+
constructor(filename) {
|
|
867
|
+
this.database = new DatabaseSync(filename);
|
|
868
|
+
this.database.exec("PRAGMA foreign_keys = ON;");
|
|
869
|
+
this.database.exec("PRAGMA journal_mode = WAL;");
|
|
870
|
+
}
|
|
871
|
+
async exec(sql) {
|
|
872
|
+
this.database.exec(sql);
|
|
873
|
+
}
|
|
874
|
+
async run(sql, params = []) {
|
|
875
|
+
const result = this.database.prepare(sql).run(...toSqlParameters(params));
|
|
876
|
+
return {
|
|
877
|
+
changes: Number(result.changes ?? 0),
|
|
878
|
+
lastInsertRowid: typeof result.lastInsertRowid === "bigint" ? Number(result.lastInsertRowid) : result.lastInsertRowid
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
async get(sql, params = []) {
|
|
882
|
+
return this.database.prepare(sql).get(...toSqlParameters(params));
|
|
883
|
+
}
|
|
884
|
+
async all(sql, params = []) {
|
|
885
|
+
return this.database.prepare(sql).all(...toSqlParameters(params));
|
|
886
|
+
}
|
|
887
|
+
async withTransaction(callback) {
|
|
888
|
+
if (this.transactionDepth > 0) return this.withSavepoint(`nested_${this.transactionDepth}`, callback);
|
|
889
|
+
this.transactionDepth += 1;
|
|
890
|
+
this.database.exec("BEGIN");
|
|
891
|
+
try {
|
|
892
|
+
const result = await callback();
|
|
893
|
+
this.database.exec("COMMIT");
|
|
894
|
+
return result;
|
|
895
|
+
} catch (error) {
|
|
896
|
+
this.database.exec("ROLLBACK");
|
|
897
|
+
throw error;
|
|
898
|
+
} finally {
|
|
899
|
+
this.transactionDepth -= 1;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async withSavepoint(name, callback) {
|
|
903
|
+
const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
904
|
+
this.database.exec(`SAVEPOINT ${safeName}`);
|
|
905
|
+
try {
|
|
906
|
+
const result = await callback();
|
|
907
|
+
this.database.exec(`RELEASE SAVEPOINT ${safeName}`);
|
|
908
|
+
return result;
|
|
909
|
+
} catch (error) {
|
|
910
|
+
this.database.exec(`ROLLBACK TO SAVEPOINT ${safeName}`);
|
|
911
|
+
this.database.exec(`RELEASE SAVEPOINT ${safeName}`);
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
async close() {
|
|
916
|
+
this.database.close();
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
var HubFileStorageAdapter = class {
|
|
920
|
+
constructor(directory) {
|
|
921
|
+
this.directory = directory;
|
|
922
|
+
}
|
|
923
|
+
filePath(id) {
|
|
924
|
+
return path.join(this.directory, id);
|
|
925
|
+
}
|
|
926
|
+
async put(id, input) {
|
|
927
|
+
await mkdir(this.directory, { recursive: true });
|
|
928
|
+
const filePath = this.filePath(id);
|
|
929
|
+
const bytes = normalizeStorageInput(input.data);
|
|
930
|
+
await writeFile(filePath, bytes);
|
|
931
|
+
return {
|
|
932
|
+
id,
|
|
933
|
+
path: filePath,
|
|
934
|
+
size: bytes.byteLength,
|
|
935
|
+
contentType: input.contentType ?? null
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
async get(id) {
|
|
939
|
+
const filePath = this.filePath(id);
|
|
940
|
+
try {
|
|
941
|
+
return {
|
|
942
|
+
id,
|
|
943
|
+
path: filePath,
|
|
944
|
+
size: (await stat(filePath)).size,
|
|
945
|
+
contentType: null
|
|
946
|
+
};
|
|
947
|
+
} catch {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async read(id) {
|
|
952
|
+
try {
|
|
953
|
+
return await readFile(this.filePath(id));
|
|
954
|
+
} catch {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
async delete(id) {
|
|
959
|
+
await rm(this.filePath(id), { force: true });
|
|
960
|
+
}
|
|
961
|
+
async list() {
|
|
962
|
+
try {
|
|
963
|
+
const entries = await readdir(this.directory, { withFileTypes: true });
|
|
964
|
+
return Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
965
|
+
const filePath = this.filePath(entry.name);
|
|
966
|
+
const info = await stat(filePath);
|
|
967
|
+
return {
|
|
968
|
+
id: entry.name,
|
|
969
|
+
path: filePath,
|
|
970
|
+
size: info.size,
|
|
971
|
+
contentType: null
|
|
972
|
+
};
|
|
973
|
+
}));
|
|
974
|
+
} catch {
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
const hubDevtoolsSqlSupport = {
|
|
980
|
+
analyzeSqlStatement(query) {
|
|
981
|
+
const firstKeyword = query.trim().replace(/^\(+/, "").toUpperCase().split(/\s+/, 1)[0] ?? "";
|
|
982
|
+
if (firstKeyword === "SELECT" || firstKeyword === "WITH" || firstKeyword === "PRAGMA" || firstKeyword === "EXPLAIN") return {
|
|
983
|
+
mode: "read",
|
|
984
|
+
readTables: [],
|
|
985
|
+
writeTables: [],
|
|
986
|
+
schemaChanged: false,
|
|
987
|
+
observedScopes: ["all"]
|
|
988
|
+
};
|
|
989
|
+
if (firstKeyword === "INSERT" || firstKeyword === "UPDATE" || firstKeyword === "DELETE" || firstKeyword === "REPLACE") return {
|
|
990
|
+
mode: "write",
|
|
991
|
+
readTables: [],
|
|
992
|
+
writeTables: [],
|
|
993
|
+
schemaChanged: false,
|
|
994
|
+
observedScopes: ["all"]
|
|
995
|
+
};
|
|
996
|
+
if (firstKeyword === "CREATE" || firstKeyword === "DROP" || firstKeyword === "ALTER") return {
|
|
997
|
+
mode: "ddl",
|
|
998
|
+
readTables: [],
|
|
999
|
+
writeTables: [],
|
|
1000
|
+
schemaChanged: true,
|
|
1001
|
+
observedScopes: ["all", "schema.tables"]
|
|
1002
|
+
};
|
|
1003
|
+
throw new Error(`Unsupported SQL statement type: ${firstKeyword || "unknown"}`);
|
|
1004
|
+
},
|
|
1005
|
+
ensureSqlMode(analysis, expected) {
|
|
1006
|
+
if (expected === "watch") {
|
|
1007
|
+
if (analysis.mode !== "read") throw new Error("Live mode supports read-only SQL only.");
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (analysis.mode !== expected) {
|
|
1011
|
+
if (expected === "read") throw new Error("Use SQL Write for mutating statements.");
|
|
1012
|
+
throw new Error("Use SQL Read or SQL Live for read-only statements.");
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
runReadonlyQuery(databasePath, query) {
|
|
1016
|
+
const analysis = this.analyzeSqlStatement(query);
|
|
1017
|
+
this.ensureSqlMode(analysis, "read");
|
|
1018
|
+
const database = new DatabaseSync(databasePath, { readOnly: true });
|
|
1019
|
+
try {
|
|
1020
|
+
const statement = database.prepare(query);
|
|
1021
|
+
const rows = statement.all();
|
|
1022
|
+
const columns = statement.columns().map((column) => column.name);
|
|
1023
|
+
return {
|
|
1024
|
+
columns,
|
|
1025
|
+
rows: rows.map((row) => columns.map((column) => row[column])),
|
|
1026
|
+
observedTables: []
|
|
1027
|
+
};
|
|
1028
|
+
} finally {
|
|
1029
|
+
database.close();
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
async function createProjectTargetBackend(cwd) {
|
|
1034
|
+
const projectTarget = resolveProjectTargetConfig(await loadProjectConfig(cwd));
|
|
1035
|
+
if (!projectTarget) return null;
|
|
1036
|
+
const schema = await loadProjectSchema(cwd);
|
|
1037
|
+
const functions = await loadProjectFunctions(cwd);
|
|
1038
|
+
const databasePath = path.resolve(cwd, projectTarget.databasePath);
|
|
1039
|
+
const storageDirectory = path.resolve(cwd, projectTarget.storageDirectory);
|
|
1040
|
+
await mkdir(path.dirname(databasePath), { recursive: true });
|
|
1041
|
+
await mkdir(storageDirectory, { recursive: true });
|
|
1042
|
+
const driver = new HubSqliteDriver(databasePath);
|
|
1043
|
+
const runtime = new SyncoreRuntime({
|
|
1044
|
+
schema,
|
|
1045
|
+
functions,
|
|
1046
|
+
driver,
|
|
1047
|
+
storage: new HubFileStorageAdapter(storageDirectory),
|
|
1048
|
+
platform: "project"
|
|
1049
|
+
});
|
|
1050
|
+
await runtime.prepareForDirectAccess();
|
|
1051
|
+
const commandHandler = createDevtoolsCommandHandler({
|
|
1052
|
+
driver,
|
|
1053
|
+
schema,
|
|
1054
|
+
functions,
|
|
1055
|
+
runtime,
|
|
1056
|
+
sql: hubDevtoolsSqlSupport
|
|
1057
|
+
});
|
|
1058
|
+
const subscriptionHost = createDevtoolsSubscriptionHost({
|
|
1059
|
+
driver,
|
|
1060
|
+
schema,
|
|
1061
|
+
functions,
|
|
1062
|
+
runtime,
|
|
1063
|
+
sql: hubDevtoolsSqlSupport
|
|
1064
|
+
});
|
|
1065
|
+
return {
|
|
1066
|
+
hello: {
|
|
1067
|
+
type: "hello",
|
|
1068
|
+
runtimeId: PROJECT_TARGET_RUNTIME_ID,
|
|
1069
|
+
platform: "project",
|
|
1070
|
+
sessionLabel: "Project Target",
|
|
1071
|
+
targetKind: "project",
|
|
1072
|
+
storageProtocol: "file",
|
|
1073
|
+
databaseLabel: path.basename(databasePath),
|
|
1074
|
+
storageIdentity: `file::${databasePath}`
|
|
1075
|
+
},
|
|
1076
|
+
handleCommand: commandHandler,
|
|
1077
|
+
subscribe(subscriptionId, payload, listener) {
|
|
1078
|
+
return subscriptionHost.subscribe(subscriptionId, payload, listener);
|
|
1079
|
+
},
|
|
1080
|
+
unsubscribe(subscriptionId) {
|
|
1081
|
+
subscriptionHost.unsubscribe(subscriptionId);
|
|
1082
|
+
},
|
|
1083
|
+
async dispose() {
|
|
1084
|
+
subscriptionHost.dispose();
|
|
1085
|
+
await runtime.stop();
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function normalizeStorageInput(input) {
|
|
1090
|
+
if (typeof input === "string") return Buffer.from(input);
|
|
1091
|
+
if (input instanceof Uint8Array) return input;
|
|
1092
|
+
return new Uint8Array(input);
|
|
1093
|
+
}
|
|
1094
|
+
function toSqlParameters(params) {
|
|
1095
|
+
return params.map((value) => {
|
|
1096
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
1097
|
+
return value;
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
820
1100
|
async function readStoredSnapshot(cwd) {
|
|
821
|
-
const snapshotPath = path.join(cwd, "syncore", "migrations",
|
|
1101
|
+
const snapshotPath = path.join(cwd, "syncore", "migrations", SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME);
|
|
822
1102
|
if (!await fileExists(snapshotPath)) return null;
|
|
823
1103
|
return (0, src_exports.parseSchemaSnapshot)(await readFile(snapshotPath, "utf8"));
|
|
824
1104
|
}
|
|
825
1105
|
async function writeStoredSnapshot(cwd, snapshot) {
|
|
826
1106
|
const migrationsDirectory = path.join(cwd, "syncore", "migrations");
|
|
827
1107
|
await mkdir(migrationsDirectory, { recursive: true });
|
|
828
|
-
await writeFile(path.join(migrationsDirectory,
|
|
1108
|
+
await writeFile(path.join(migrationsDirectory, SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME), `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
829
1109
|
}
|
|
830
1110
|
async function getNextMigrationNumber(directory) {
|
|
831
1111
|
if (!await fileExists(directory)) return 1;
|
|
@@ -834,25 +1114,20 @@ async function getNextMigrationNumber(directory) {
|
|
|
834
1114
|
return Math.max(...migrationNumbers) + 1;
|
|
835
1115
|
}
|
|
836
1116
|
async function applyProjectMigrations(cwd) {
|
|
837
|
-
const
|
|
838
|
-
const databasePath = path.resolve(cwd,
|
|
839
|
-
const storageDirectory = path.resolve(cwd,
|
|
1117
|
+
const projectTarget = requireProjectTargetConfig(await loadProjectConfig(cwd));
|
|
1118
|
+
const databasePath = path.resolve(cwd, projectTarget.databasePath);
|
|
1119
|
+
const storageDirectory = path.resolve(cwd, projectTarget.storageDirectory);
|
|
840
1120
|
await mkdir(path.dirname(databasePath), { recursive: true });
|
|
841
1121
|
await mkdir(storageDirectory, { recursive: true });
|
|
842
1122
|
const database = new DatabaseSync(databasePath);
|
|
843
|
-
database
|
|
844
|
-
CREATE TABLE IF NOT EXISTS "_syncore_migrations" (
|
|
845
|
-
name TEXT PRIMARY KEY,
|
|
846
|
-
applied_at TEXT NOT NULL
|
|
847
|
-
);
|
|
848
|
-
`);
|
|
1123
|
+
ensureCliMigrationTrackingTable(database);
|
|
849
1124
|
const migrationsDirectory = path.join(cwd, "syncore", "migrations");
|
|
850
1125
|
if (!await fileExists(migrationsDirectory)) {
|
|
851
1126
|
database.close();
|
|
852
1127
|
return 0;
|
|
853
1128
|
}
|
|
854
|
-
const appliedRows = database.prepare(`SELECT
|
|
855
|
-
const appliedNames = new Set(appliedRows.map((row) => row.
|
|
1129
|
+
const appliedRows = database.prepare(`SELECT id FROM "_syncore_migrations" ORDER BY id ASC`).all();
|
|
1130
|
+
const appliedNames = new Set(appliedRows.map((row) => row.id));
|
|
856
1131
|
const migrationFiles = (await readdir(migrationsDirectory)).filter((name) => /\.sql$/i.test(name)).sort((left, right) => left.localeCompare(right));
|
|
857
1132
|
let appliedCount = 0;
|
|
858
1133
|
for (const fileName of migrationFiles) {
|
|
@@ -861,7 +1136,7 @@ async function applyProjectMigrations(cwd) {
|
|
|
861
1136
|
database.exec("BEGIN");
|
|
862
1137
|
try {
|
|
863
1138
|
applyMigrationSql(database, sql, fileName);
|
|
864
|
-
database.prepare(`INSERT INTO "_syncore_migrations" (
|
|
1139
|
+
database.prepare(`INSERT OR REPLACE INTO "_syncore_migrations" (id, applied_at, sql) VALUES (?, ?, ?)`).run(fileName, Date.now(), sql);
|
|
865
1140
|
database.exec("COMMIT");
|
|
866
1141
|
appliedCount += 1;
|
|
867
1142
|
} catch (error) {
|
|
@@ -873,6 +1148,41 @@ async function applyProjectMigrations(cwd) {
|
|
|
873
1148
|
database.close();
|
|
874
1149
|
return appliedCount;
|
|
875
1150
|
}
|
|
1151
|
+
function ensureCliMigrationTrackingTable(database) {
|
|
1152
|
+
if (!(database.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = '_syncore_migrations'`).get()?.name === "_syncore_migrations")) {
|
|
1153
|
+
database.exec(`
|
|
1154
|
+
CREATE TABLE "_syncore_migrations" (
|
|
1155
|
+
id TEXT PRIMARY KEY,
|
|
1156
|
+
applied_at INTEGER NOT NULL,
|
|
1157
|
+
sql TEXT NOT NULL
|
|
1158
|
+
);
|
|
1159
|
+
`);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const columns = database.prepare(`PRAGMA table_info("_syncore_migrations")`).all();
|
|
1163
|
+
const columnNames = new Set(columns.map((column) => column.name));
|
|
1164
|
+
if (columnNames.has("id") && columnNames.has("applied_at") && columnNames.has("sql")) return;
|
|
1165
|
+
database.exec(`
|
|
1166
|
+
ALTER TABLE "_syncore_migrations" RENAME TO "_syncore_migrations_legacy";
|
|
1167
|
+
CREATE TABLE "_syncore_migrations" (
|
|
1168
|
+
id TEXT PRIMARY KEY,
|
|
1169
|
+
applied_at INTEGER NOT NULL,
|
|
1170
|
+
sql TEXT NOT NULL
|
|
1171
|
+
);
|
|
1172
|
+
`);
|
|
1173
|
+
if (columnNames.has("name")) database.exec(`
|
|
1174
|
+
INSERT INTO "_syncore_migrations" (id, applied_at, sql)
|
|
1175
|
+
SELECT
|
|
1176
|
+
name,
|
|
1177
|
+
CASE
|
|
1178
|
+
WHEN typeof(applied_at) = 'integer' THEN applied_at
|
|
1179
|
+
ELSE CAST(strftime('%s', applied_at) AS INTEGER) * 1000
|
|
1180
|
+
END,
|
|
1181
|
+
''
|
|
1182
|
+
FROM "_syncore_migrations_legacy";
|
|
1183
|
+
`);
|
|
1184
|
+
database.exec(`DROP TABLE "_syncore_migrations_legacy";`);
|
|
1185
|
+
}
|
|
876
1186
|
async function fileExists(filePath) {
|
|
877
1187
|
try {
|
|
878
1188
|
await stat(filePath);
|
|
@@ -882,15 +1192,14 @@ async function fileExists(filePath) {
|
|
|
882
1192
|
}
|
|
883
1193
|
}
|
|
884
1194
|
async function resolveFunctionImportExtension(cwd) {
|
|
885
|
-
const
|
|
886
|
-
for (const
|
|
887
|
-
const
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
1195
|
+
const tsconfigFiles = (await readdir(cwd, { withFileTypes: true })).filter((entry) => entry.isFile() && /^tsconfig(\..+)?\.json$/u.test(entry.name)).map((entry) => path.join(cwd, entry.name)).sort();
|
|
1196
|
+
for (const configPath of tsconfigFiles) try {
|
|
1197
|
+
const source = await readFile(configPath, "utf8");
|
|
1198
|
+
const parsed = JSON.parse(source);
|
|
1199
|
+
const moduleResolution = parsed.compilerOptions?.moduleResolution?.toLowerCase();
|
|
1200
|
+
const moduleKind = parsed.compilerOptions?.module?.toLowerCase();
|
|
1201
|
+
if (moduleResolution === "nodenext" || moduleResolution === "node16" || moduleKind === "nodenext" || moduleKind === "node16") return ".js";
|
|
1202
|
+
} catch {}
|
|
894
1203
|
return "";
|
|
895
1204
|
}
|
|
896
1205
|
function formatError(error) {
|
|
@@ -930,12 +1239,22 @@ function applyMigrationSql(database, sql, fileName) {
|
|
|
930
1239
|
async function startDevHub(options) {
|
|
931
1240
|
const dashboardPort = resolvePortFromEnv("SYNCORE_DASHBOARD_PORT", 4310);
|
|
932
1241
|
const devtoolsPort = resolvePortFromEnv("SYNCORE_DEVTOOLS_PORT", 4311);
|
|
1242
|
+
const logsDirectory = path.join(options.cwd, ".syncore", "logs");
|
|
1243
|
+
const logFilePath = path.join(logsDirectory, "runtime.jsonl");
|
|
1244
|
+
await mkdir(logsDirectory, { recursive: true });
|
|
1245
|
+
await writeFile(logFilePath, "");
|
|
933
1246
|
await runDevProjectBootstrap(options.cwd, options.template);
|
|
934
1247
|
await setupDevProjectWatch(options.cwd, options.template);
|
|
935
1248
|
if (await isLocalPortInUse(devtoolsPort)) {
|
|
936
|
-
console.log(`Syncore devtools hub already running at ws://
|
|
1249
|
+
console.log(`Syncore devtools hub already running at ws://localhost:${devtoolsPort}. Reusing existing hub/dashboard.`);
|
|
937
1250
|
return;
|
|
938
1251
|
}
|
|
1252
|
+
let projectTargetBackend = null;
|
|
1253
|
+
try {
|
|
1254
|
+
projectTargetBackend = await createProjectTargetBackend(options.cwd);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
console.warn(`Project target fallback unavailable: ${formatError(error)}`);
|
|
1257
|
+
}
|
|
939
1258
|
const httpServer = createServer((_request, response) => {
|
|
940
1259
|
response.writeHead(200, { "content-type": "application/json" });
|
|
941
1260
|
response.end(JSON.stringify({
|
|
@@ -944,19 +1263,56 @@ async function startDevHub(options) {
|
|
|
944
1263
|
}));
|
|
945
1264
|
});
|
|
946
1265
|
const websocketServer = new WebSocketServer({ server: httpServer });
|
|
947
|
-
const latestSnapshots = /* @__PURE__ */ new Map();
|
|
948
1266
|
const runtimeSockets = /* @__PURE__ */ new Map();
|
|
1267
|
+
const runtimeHellos = /* @__PURE__ */ new Map();
|
|
1268
|
+
const runtimeEvents = /* @__PURE__ */ new Map();
|
|
949
1269
|
const socketRuntimeIds = /* @__PURE__ */ new Map();
|
|
950
1270
|
const dashboardSockets = /* @__PURE__ */ new Set();
|
|
1271
|
+
const dashboardSubscriptions = /* @__PURE__ */ new Map();
|
|
951
1272
|
const hello = {
|
|
952
1273
|
type: "hello",
|
|
953
1274
|
runtimeId: "syncore-dev-hub",
|
|
954
1275
|
platform: "dev"
|
|
955
1276
|
};
|
|
1277
|
+
if (projectTargetBackend) {
|
|
1278
|
+
runtimeHellos.set(PROJECT_TARGET_RUNTIME_ID, projectTargetBackend.hello);
|
|
1279
|
+
runtimeEvents.set(PROJECT_TARGET_RUNTIME_ID, []);
|
|
1280
|
+
}
|
|
1281
|
+
const appendHubLog = async (event) => {
|
|
1282
|
+
const runtimeHello = runtimeHellos.get(event.runtimeId);
|
|
1283
|
+
const clientRuntimeIds = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.runtimeId).sort();
|
|
1284
|
+
const clientTargetKeys = [...runtimeHellos.values()].filter((hello) => hello.runtimeId !== "syncore-dev-hub" && hello.targetKind !== "project").map((hello) => hello.storageIdentity ?? `runtime::${hello.runtimeId}`).sort();
|
|
1285
|
+
const targetIdentity = runtimeHello?.storageIdentity ?? `runtime::${event.runtimeId}`;
|
|
1286
|
+
const targetId = event.runtimeId === "syncore-dev-hub" ? "all" : runtimeHello?.targetKind === "project" ? "project" : createPublicTargetId(targetIdentity, clientTargetKeys);
|
|
1287
|
+
const publicRuntimeId = event.runtimeId === "syncore-dev-hub" ? void 0 : createPublicRuntimeId(event.runtimeId, clientRuntimeIds);
|
|
1288
|
+
const category = event.type === "query.executed" ? "query" : event.type === "mutation.committed" ? "mutation" : event.type === "action.completed" ? "action" : "system";
|
|
1289
|
+
const message = event.type === "log" ? event.message : event.type === "query.executed" || event.type === "mutation.committed" || event.type === "action.completed" ? event.functionName : event.type;
|
|
1290
|
+
await appendFile(logFilePath, `${JSON.stringify({
|
|
1291
|
+
version: 2,
|
|
1292
|
+
timestamp: event.timestamp,
|
|
1293
|
+
runtimeId: event.runtimeId,
|
|
1294
|
+
targetId,
|
|
1295
|
+
...publicRuntimeId ? { publicRuntimeId } : {},
|
|
1296
|
+
...runtimeHello?.platform ? { platform: runtimeHello.platform } : {},
|
|
1297
|
+
eventType: event.type,
|
|
1298
|
+
category,
|
|
1299
|
+
message,
|
|
1300
|
+
event
|
|
1301
|
+
})}\n`);
|
|
1302
|
+
};
|
|
956
1303
|
websocketServer.on("connection", (socket) => {
|
|
957
1304
|
dashboardSockets.add(socket);
|
|
958
1305
|
socket.send(JSON.stringify(hello));
|
|
959
|
-
for (const
|
|
1306
|
+
for (const runtimeHello of runtimeHellos.values()) socket.send(JSON.stringify(runtimeHello));
|
|
1307
|
+
for (const [runtimeId, history] of runtimeEvents) {
|
|
1308
|
+
if (!runtimeHellos.has(runtimeId)) continue;
|
|
1309
|
+
if (history.length === 0) continue;
|
|
1310
|
+
socket.send(JSON.stringify({
|
|
1311
|
+
type: "event.batch",
|
|
1312
|
+
runtimeId,
|
|
1313
|
+
events: [...history]
|
|
1314
|
+
}));
|
|
1315
|
+
}
|
|
960
1316
|
socket.on("message", (payload) => {
|
|
961
1317
|
const rawPayload = decodeWebSocketPayload(payload);
|
|
962
1318
|
if (rawPayload.length === 0) return;
|
|
@@ -965,32 +1321,110 @@ async function startDevHub(options) {
|
|
|
965
1321
|
socket.send(JSON.stringify({ type: "pong" }));
|
|
966
1322
|
return;
|
|
967
1323
|
}
|
|
968
|
-
if (message.type === "
|
|
1324
|
+
if (message.type === "command") {
|
|
1325
|
+
const targetRuntimeId = message.targetRuntimeId;
|
|
1326
|
+
if (!targetRuntimeId) return;
|
|
1327
|
+
if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
|
|
1328
|
+
(async () => {
|
|
1329
|
+
const payload = await projectTargetBackend.handleCommand(message.payload);
|
|
1330
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
1331
|
+
socket.send(JSON.stringify({
|
|
1332
|
+
type: "command.result",
|
|
1333
|
+
commandId: message.commandId,
|
|
1334
|
+
runtimeId: PROJECT_TARGET_RUNTIME_ID,
|
|
1335
|
+
payload
|
|
1336
|
+
}));
|
|
1337
|
+
})();
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
const target = runtimeSockets.get(targetRuntimeId);
|
|
1341
|
+
if (target && target.readyState === WebSocket.OPEN) target.send(JSON.stringify(message));
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (message.type === "subscribe") {
|
|
969
1345
|
const targetRuntimeId = message.targetRuntimeId;
|
|
970
1346
|
if (!targetRuntimeId) return;
|
|
1347
|
+
const subscriptions = dashboardSubscriptions.get(socket) ?? /* @__PURE__ */ new Map();
|
|
1348
|
+
subscriptions.set(message.subscriptionId, {
|
|
1349
|
+
runtimeId: targetRuntimeId,
|
|
1350
|
+
payload: message
|
|
1351
|
+
});
|
|
1352
|
+
dashboardSubscriptions.set(socket, subscriptions);
|
|
1353
|
+
if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
|
|
1354
|
+
projectTargetBackend.subscribe(message.subscriptionId, message.payload, (payload) => {
|
|
1355
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
1356
|
+
socket.send(JSON.stringify({
|
|
1357
|
+
type: "subscription.data",
|
|
1358
|
+
subscriptionId: message.subscriptionId,
|
|
1359
|
+
runtimeId: PROJECT_TARGET_RUNTIME_ID,
|
|
1360
|
+
payload
|
|
1361
|
+
}));
|
|
1362
|
+
}).catch((error) => {
|
|
1363
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
1364
|
+
socket.send(JSON.stringify({
|
|
1365
|
+
type: "subscription.error",
|
|
1366
|
+
subscriptionId: message.subscriptionId,
|
|
1367
|
+
runtimeId: PROJECT_TARGET_RUNTIME_ID,
|
|
1368
|
+
error: formatError(error)
|
|
1369
|
+
}));
|
|
1370
|
+
});
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
971
1373
|
const target = runtimeSockets.get(targetRuntimeId);
|
|
972
1374
|
if (target && target.readyState === WebSocket.OPEN) target.send(JSON.stringify(message));
|
|
973
1375
|
return;
|
|
974
1376
|
}
|
|
1377
|
+
if (message.type === "unsubscribe") {
|
|
1378
|
+
const subscriptions = dashboardSubscriptions.get(socket);
|
|
1379
|
+
const subscription = subscriptions?.get(message.subscriptionId);
|
|
1380
|
+
if (!subscription) return;
|
|
1381
|
+
if (subscription.runtimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
|
|
1382
|
+
projectTargetBackend.unsubscribe(message.subscriptionId);
|
|
1383
|
+
subscriptions?.delete(message.subscriptionId);
|
|
1384
|
+
if (subscriptions && subscriptions.size === 0) dashboardSubscriptions.delete(socket);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
const target = runtimeSockets.get(subscription.runtimeId);
|
|
1388
|
+
if (target && target.readyState === WebSocket.OPEN) {
|
|
1389
|
+
const runtimeMessage = {
|
|
1390
|
+
type: "unsubscribe",
|
|
1391
|
+
subscriptionId: message.subscriptionId,
|
|
1392
|
+
targetRuntimeId: subscription.runtimeId
|
|
1393
|
+
};
|
|
1394
|
+
target.send(JSON.stringify(runtimeMessage));
|
|
1395
|
+
}
|
|
1396
|
+
subscriptions?.delete(message.subscriptionId);
|
|
1397
|
+
if (subscriptions && subscriptions.size === 0) dashboardSubscriptions.delete(socket);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
975
1400
|
if (message.type === "hello") {
|
|
976
1401
|
dashboardSockets.delete(socket);
|
|
977
1402
|
runtimeSockets.set(message.runtimeId, socket);
|
|
1403
|
+
runtimeHellos.set(message.runtimeId, message);
|
|
1404
|
+
runtimeEvents.set(message.runtimeId, []);
|
|
978
1405
|
const runtimeIds = socketRuntimeIds.get(socket) ?? /* @__PURE__ */ new Set();
|
|
979
1406
|
runtimeIds.add(message.runtimeId);
|
|
980
1407
|
socketRuntimeIds.set(socket, runtimeIds);
|
|
1408
|
+
for (const [dashboardSocket, subscriptions] of dashboardSubscriptions) {
|
|
1409
|
+
if (dashboardSocket.readyState !== WebSocket.OPEN) continue;
|
|
1410
|
+
for (const subscription of subscriptions.values()) {
|
|
1411
|
+
if (subscription.runtimeId !== message.runtimeId) continue;
|
|
1412
|
+
socket.send(JSON.stringify(subscription.payload));
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
981
1415
|
for (const client of dashboardSockets) if (client.readyState === WebSocket.OPEN) client.send(JSON.stringify(message));
|
|
982
1416
|
return;
|
|
983
1417
|
}
|
|
984
|
-
if (message.type === "snapshot") {
|
|
985
|
-
latestSnapshots.set(message.snapshot.runtimeId, message);
|
|
986
|
-
dashboardSockets.delete(socket);
|
|
987
|
-
runtimeSockets.set(message.snapshot.runtimeId, socket);
|
|
988
|
-
const runtimeIds = socketRuntimeIds.get(socket) ?? /* @__PURE__ */ new Set();
|
|
989
|
-
runtimeIds.add(message.snapshot.runtimeId);
|
|
990
|
-
socketRuntimeIds.set(socket, runtimeIds);
|
|
991
|
-
}
|
|
992
1418
|
const encoded = JSON.stringify(message);
|
|
993
|
-
if (message.type === "
|
|
1419
|
+
if (message.type === "event" && message.event.type === "runtime.disconnected") runtimeHellos.delete(message.event.runtimeId);
|
|
1420
|
+
if (message.type === "event" && message.event.runtimeId !== "syncore-dev-hub") {
|
|
1421
|
+
const history = runtimeEvents.get(message.event.runtimeId) ?? [];
|
|
1422
|
+
history.unshift(message.event);
|
|
1423
|
+
runtimeEvents.set(message.event.runtimeId, history.slice(0, 200));
|
|
1424
|
+
if (message.event.type === "runtime.disconnected") runtimeEvents.delete(message.event.runtimeId);
|
|
1425
|
+
appendHubLog(message.event);
|
|
1426
|
+
} else if (message.type === "event") appendHubLog(message.event);
|
|
1427
|
+
if (message.type === "command.result" || message.type === "subscription.data" || message.type === "subscription.error") {
|
|
994
1428
|
for (const client of dashboardSockets) if (client.readyState === WebSocket.OPEN) client.send(encoded);
|
|
995
1429
|
return;
|
|
996
1430
|
}
|
|
@@ -1001,38 +1435,57 @@ async function startDevHub(options) {
|
|
|
1001
1435
|
});
|
|
1002
1436
|
socket.on("close", () => {
|
|
1003
1437
|
dashboardSockets.delete(socket);
|
|
1438
|
+
const subscriptions = dashboardSubscriptions.get(socket);
|
|
1439
|
+
if (subscriptions) {
|
|
1440
|
+
for (const [subscriptionId, subscription] of subscriptions) {
|
|
1441
|
+
if (subscription.runtimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
|
|
1442
|
+
projectTargetBackend.unsubscribe(subscriptionId);
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
const target = runtimeSockets.get(subscription.runtimeId);
|
|
1446
|
+
if (target && target.readyState === WebSocket.OPEN) {
|
|
1447
|
+
const message = {
|
|
1448
|
+
type: "unsubscribe",
|
|
1449
|
+
subscriptionId,
|
|
1450
|
+
targetRuntimeId: subscription.runtimeId
|
|
1451
|
+
};
|
|
1452
|
+
target.send(JSON.stringify(message));
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
dashboardSubscriptions.delete(socket);
|
|
1456
|
+
}
|
|
1004
1457
|
const runtimeIds = socketRuntimeIds.get(socket);
|
|
1005
1458
|
if (!runtimeIds) return;
|
|
1006
|
-
for (const runtimeId of runtimeIds) {
|
|
1007
|
-
|
|
1008
|
-
|
|
1459
|
+
for (const runtimeId of runtimeIds) if (runtimeSockets.get(runtimeId) === socket) {
|
|
1460
|
+
if (runtimeHellos.has(runtimeId)) {
|
|
1461
|
+
const disconnectedEvent = {
|
|
1462
|
+
type: "event",
|
|
1463
|
+
event: {
|
|
1464
|
+
type: "runtime.disconnected",
|
|
1465
|
+
runtimeId,
|
|
1466
|
+
timestamp: Date.now()
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
const payload = JSON.stringify(disconnectedEvent);
|
|
1470
|
+
appendHubLog(disconnectedEvent.event);
|
|
1471
|
+
for (const client of dashboardSockets) if (client.readyState === WebSocket.OPEN) client.send(payload);
|
|
1472
|
+
}
|
|
1473
|
+
runtimeSockets.delete(runtimeId);
|
|
1474
|
+
runtimeHellos.delete(runtimeId);
|
|
1475
|
+
runtimeEvents.delete(runtimeId);
|
|
1009
1476
|
}
|
|
1010
1477
|
socketRuntimeIds.delete(socket);
|
|
1011
1478
|
});
|
|
1012
1479
|
});
|
|
1013
|
-
const heartbeat = setInterval(() => {
|
|
1014
|
-
const event = {
|
|
1015
|
-
type: "event",
|
|
1016
|
-
event: {
|
|
1017
|
-
type: "log",
|
|
1018
|
-
runtimeId: "syncore-dev-hub",
|
|
1019
|
-
level: "info",
|
|
1020
|
-
message: "Syncore devtools hub is alive.",
|
|
1021
|
-
timestamp: Date.now()
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
const payload = JSON.stringify(event);
|
|
1025
|
-
for (const client of websocketServer.clients) client.send(payload);
|
|
1026
|
-
}, 4e3);
|
|
1027
1480
|
httpServer.on("error", (error) => {
|
|
1028
1481
|
console.error(`Syncore devtools hub failed: ${formatError(error)}`);
|
|
1029
1482
|
process.exit(1);
|
|
1030
1483
|
});
|
|
1031
1484
|
httpServer.listen(devtoolsPort, "127.0.0.1", () => {
|
|
1032
1485
|
(async () => {
|
|
1033
|
-
console.log(`Syncore devtools hub: ws://
|
|
1034
|
-
console.log(`Electron/Node runtimes: set devtoolsUrl to ws://
|
|
1035
|
-
console.log(`Web/Next apps: connect the dashboard or worker bridge to ws://
|
|
1486
|
+
console.log(`Syncore devtools hub: ws://localhost:${devtoolsPort}`);
|
|
1487
|
+
console.log(`Electron/Node runtimes: set devtoolsUrl to ws://localhost:${devtoolsPort}.`);
|
|
1488
|
+
console.log(`Web/Next apps: connect the dashboard or worker bridge to ws://localhost:${devtoolsPort}.`);
|
|
1036
1489
|
console.log("Expo apps: use the same hub URL through LAN or adb reverse while developing.");
|
|
1037
1490
|
const dashboardRoot = path.resolve(CORE_PACKAGE_ROOT, "..", "..", "apps", "dashboard");
|
|
1038
1491
|
if (await fileExists(path.join(dashboardRoot, "vite.config.ts"))) try {
|
|
@@ -1041,14 +1494,14 @@ async function startDevHub(options) {
|
|
|
1041
1494
|
root: dashboardRoot,
|
|
1042
1495
|
server: { port: dashboardPort }
|
|
1043
1496
|
})).listen();
|
|
1044
|
-
console.log(`Dashboard shell: http://
|
|
1497
|
+
console.log(`Dashboard shell: http://localhost:${dashboardPort}`);
|
|
1045
1498
|
} catch (error) {
|
|
1046
1499
|
console.log(`Dashboard source not started automatically: ${formatError(error)}`);
|
|
1047
1500
|
}
|
|
1048
1501
|
})();
|
|
1049
1502
|
});
|
|
1050
1503
|
const close = () => {
|
|
1051
|
-
|
|
1504
|
+
projectTargetBackend?.dispose();
|
|
1052
1505
|
websocketServer.close();
|
|
1053
1506
|
httpServer.close();
|
|
1054
1507
|
process.exit(0);
|
|
@@ -1191,6 +1644,6 @@ function toSearchValue(value) {
|
|
|
1191
1644
|
return stableStringify(value);
|
|
1192
1645
|
}
|
|
1193
1646
|
//#endregion
|
|
1194
|
-
export { runSyncoreCli };
|
|
1647
|
+
export { SYNCORE_MIGRATION_SNAPSHOT_FILE_NAME, VALID_SYNCORE_TEMPLATES, applyProjectMigrations, detectProjectTemplate, fileExists, formatError, getNextMigrationNumber, hasSyncoreProject, importJsonlIntoProject, isLocalPortInUse, loadProjectConfig, loadProjectFunctions, loadProjectSchema, logScaffoldResult, readPackageJson, readStoredSnapshot, resolveDefaultSeedFile, resolvePortFromEnv, resolveProjectTargetConfig, resolveRequestedTemplate, runCodegen, runDevProjectBootstrap, runSyncoreCli, scaffoldProject, slugify, startDevHub, writeStoredSnapshot };
|
|
1195
1648
|
|
|
1196
1649
|
//# sourceMappingURL=cli.mjs.map
|