everything-dev 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/api-contract.cjs +55 -8
  2. package/dist/api-contract.cjs.map +1 -1
  3. package/dist/api-contract.mjs +55 -8
  4. package/dist/api-contract.mjs.map +1 -1
  5. package/dist/app.cjs +26 -2
  6. package/dist/app.cjs.map +1 -1
  7. package/dist/app.mjs +27 -3
  8. package/dist/app.mjs.map +1 -1
  9. package/dist/cli/init.cjs +4 -4
  10. package/dist/cli/init.cjs.map +1 -1
  11. package/dist/cli/init.mjs +4 -4
  12. package/dist/cli/init.mjs.map +1 -1
  13. package/dist/cli/sync.cjs +4 -3
  14. package/dist/cli/sync.cjs.map +1 -1
  15. package/dist/cli/sync.mjs +4 -3
  16. package/dist/cli/sync.mjs.map +1 -1
  17. package/dist/cli.cjs +0 -1
  18. package/dist/cli.cjs.map +1 -1
  19. package/dist/cli.mjs +0 -1
  20. package/dist/cli.mjs.map +1 -1
  21. package/dist/components/streaming-view.cjs +0 -18
  22. package/dist/components/streaming-view.cjs.map +1 -1
  23. package/dist/components/streaming-view.mjs +0 -18
  24. package/dist/components/streaming-view.mjs.map +1 -1
  25. package/dist/config.cjs +21 -5
  26. package/dist/config.cjs.map +1 -1
  27. package/dist/config.d.cts +2 -1
  28. package/dist/config.d.cts.map +1 -1
  29. package/dist/config.d.mts +2 -1
  30. package/dist/config.d.mts.map +1 -1
  31. package/dist/config.mjs +21 -6
  32. package/dist/config.mjs.map +1 -1
  33. package/dist/contract.cjs +8 -1
  34. package/dist/contract.cjs.map +1 -1
  35. package/dist/contract.d.cts +44 -8
  36. package/dist/contract.d.cts.map +1 -1
  37. package/dist/contract.d.mts +44 -8
  38. package/dist/contract.d.mts.map +1 -1
  39. package/dist/contract.meta.cjs +1 -1
  40. package/dist/contract.meta.cjs.map +1 -1
  41. package/dist/contract.meta.d.cts +1 -1
  42. package/dist/contract.meta.d.mts +1 -1
  43. package/dist/contract.meta.mjs +1 -1
  44. package/dist/contract.meta.mjs.map +1 -1
  45. package/dist/contract.mjs +8 -1
  46. package/dist/contract.mjs.map +1 -1
  47. package/dist/dev-session.cjs +51 -66
  48. package/dist/dev-session.cjs.map +1 -1
  49. package/dist/dev-session.mjs +52 -67
  50. package/dist/dev-session.mjs.map +1 -1
  51. package/dist/fastkv.cjs +56 -0
  52. package/dist/fastkv.cjs.map +1 -1
  53. package/dist/fastkv.d.cts +45 -1
  54. package/dist/fastkv.d.cts.map +1 -1
  55. package/dist/fastkv.d.mts +45 -1
  56. package/dist/fastkv.d.mts.map +1 -1
  57. package/dist/fastkv.mjs +54 -1
  58. package/dist/fastkv.mjs.map +1 -1
  59. package/dist/host.cjs +1 -1
  60. package/dist/host.cjs.map +1 -1
  61. package/dist/host.mjs +1 -1
  62. package/dist/host.mjs.map +1 -1
  63. package/dist/index.cjs +4 -0
  64. package/dist/index.d.cts +4 -4
  65. package/dist/index.d.mts +4 -4
  66. package/dist/index.mjs +3 -3
  67. package/dist/near-cli.cjs +1 -1
  68. package/dist/near-cli.mjs +1 -1
  69. package/dist/orchestrator.cjs +55 -20
  70. package/dist/orchestrator.cjs.map +1 -1
  71. package/dist/orchestrator.d.cts +5 -4
  72. package/dist/orchestrator.d.cts.map +1 -1
  73. package/dist/orchestrator.d.mts +5 -4
  74. package/dist/orchestrator.d.mts.map +1 -1
  75. package/dist/orchestrator.mjs +55 -20
  76. package/dist/orchestrator.mjs.map +1 -1
  77. package/dist/plugin.cjs +135 -9
  78. package/dist/plugin.cjs.map +1 -1
  79. package/dist/plugin.d.cts +50 -9
  80. package/dist/plugin.d.cts.map +1 -1
  81. package/dist/plugin.d.mts +50 -9
  82. package/dist/plugin.d.mts.map +1 -1
  83. package/dist/plugin.mjs +137 -11
  84. package/dist/plugin.mjs.map +1 -1
  85. package/dist/types.cjs +15 -5
  86. package/dist/types.cjs.map +1 -1
  87. package/dist/types.d.cts +62 -11
  88. package/dist/types.d.cts.map +1 -1
  89. package/dist/types.d.mts +62 -11
  90. package/dist/types.d.mts.map +1 -1
  91. package/dist/types.mjs +15 -5
  92. package/dist/types.mjs.map +1 -1
  93. package/package.json +2 -2
  94. package/src/api-contract.ts +88 -9
  95. package/src/app.ts +55 -7
  96. package/src/cli/init.ts +6 -6
  97. package/src/cli/sync.ts +11 -4
  98. package/src/cli.ts +0 -1
  99. package/src/components/streaming-view.ts +0 -20
  100. package/src/config.ts +39 -23
  101. package/src/contract.meta.ts +4 -1
  102. package/src/contract.ts +7 -0
  103. package/src/dev-session.ts +85 -83
  104. package/src/fastkv.ts +95 -0
  105. package/src/host.ts +1 -1
  106. package/src/orchestrator.ts +61 -31
  107. package/src/plugin.ts +202 -5
  108. package/src/types.ts +38 -4
@@ -54,7 +54,8 @@ function formatLogLine(entry) {
54
54
  const prefix = entry.isError ? "ERR" : "OUT";
55
55
  return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;
56
56
  }
57
- const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(function* () {
57
+ const scopedProcess = (pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry) => effect.Effect.acquireRelease(require_orchestrator.makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry), (handle) => handle.kill.pipe(effect.Effect.ignore));
58
+ const runDevSession = (orchestrator, onShutdownReady) => effect.Effect.gen(function* () {
58
59
  const configDir = require_config.getProjectRoot();
59
60
  const orderedPackages = sortByOrder(orchestrator.packages);
60
61
  const initialProcesses = orderedPackages.map((pkg) => {
@@ -70,41 +71,18 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
70
71
  const registry = yield* require_process_registry.makeProcessRegistry(configDir);
71
72
  yield* registry.killAll().pipe(effect.Effect.ignore);
72
73
  const logger = yield* effect.Effect.promise(() => require_dev_logs.createDevLogger(configDir, orchestrator.description));
73
- const handles = [];
74
+ const shutdown = yield* effect.Deferred.make();
75
+ onShutdownReady?.(() => {
76
+ effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0));
77
+ });
74
78
  const allLogs = [];
75
79
  let view = null;
76
- let shuttingDown = false;
77
- const killAll = async () => {
78
- const reversed = [...handles].reverse();
79
- for (const handle of reversed) try {
80
- await handle.kill();
81
- } catch {}
82
- await effect.Effect.runPromise(registry.killAll(true)).catch(() => {});
83
- };
84
- const exportLogs = async () => {
85
- console.log("\n");
86
- console.log("═".repeat(70));
87
- console.log(` SESSION LOGS: ${orchestrator.description}`);
88
- console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
89
- console.log(` Total entries: ${allLogs.length}`);
90
- console.log("═".repeat(70));
91
- console.log("");
92
- for (const entry of allLogs) console.log(formatLogLine(entry));
93
- console.log("");
94
- console.log("═".repeat(70));
95
- console.log(` Full logs saved to: ${logger.logFile}`);
96
- console.log("═".repeat(70));
97
- console.log("");
98
- };
99
- const cleanup = async (showLogs = false) => {
100
- if (shuttingDown) return;
101
- shuttingDown = true;
102
- view?.unmount();
103
- await killAll();
104
- if (showLogs) await exportLogs();
80
+ let shouldExportLogs = false;
81
+ const requestShutdownAndExport = () => {
82
+ shouldExportLogs = true;
83
+ effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0));
105
84
  };
106
- onCleanupReady?.(cleanup);
107
- view = orchestrator.interactive ?? isInteractiveSupported() ? require_dev_view.renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false), () => cleanup(true)) : require_streaming_view.renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false));
85
+ view = orchestrator.interactive ?? isInteractiveSupported() ? require_dev_view.renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => void effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0)), requestShutdownAndExport) : require_streaming_view.renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => void effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0)));
108
86
  const callbacks = {
109
87
  onStatus: (name, status, message) => {
110
88
  view?.updateProcess(name, status, message);
@@ -124,7 +102,7 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
124
102
  };
125
103
  const startProcess = (pkg) => {
126
104
  const portOverride = pkg === "host" ? orchestrator.port : void 0;
127
- return require_orchestrator.makeDevProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
105
+ return scopedProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
128
106
  };
129
107
  const startGroup = (packages) => effect.Effect.forEach(packages, startProcess, { concurrency: "unbounded" });
130
108
  const awaitReady = (pkg, handle) => effect.Effect.race(handle.waitForReady, effect.Effect.sleep("30 seconds").pipe(effect.Effect.andThen(effect.Effect.sync(() => {
@@ -133,61 +111,68 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
133
111
  const nonHostPackages = orderedPackages.filter((pkg) => pkg !== "host");
134
112
  const hostPackages = orderedPackages.filter((pkg) => pkg === "host");
135
113
  const nonHostHandles = yield* startGroup(nonHostPackages);
136
- handles.push(...nonHostHandles);
137
114
  yield* effect.Effect.forEach(nonHostHandles.map((handle, index) => ({
138
115
  handle,
139
116
  pkg: nonHostPackages[index] ?? handle.name
140
117
  })), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
141
118
  const hostHandles = yield* startGroup(hostPackages);
142
- handles.push(...hostHandles);
143
119
  yield* effect.Effect.forEach(hostHandles.map((handle, index) => ({
144
120
  handle,
145
121
  pkg: hostPackages[index] ?? handle.name
146
122
  })), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
147
- yield* effect.Effect.addFinalizer(() => effect.Effect.promise(() => cleanup(false)));
148
- yield* effect.Effect.never;
123
+ yield* effect.Effect.addFinalizer(() => effect.Effect.gen(function* () {
124
+ view?.unmount();
125
+ if (shouldExportLogs) {
126
+ console.log("\n");
127
+ console.log("═".repeat(70));
128
+ console.log(` SESSION LOGS: ${orchestrator.description}`);
129
+ console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
130
+ console.log(` Total entries: ${allLogs.length}`);
131
+ console.log("═".repeat(70));
132
+ console.log("");
133
+ for (const entry of allLogs) console.log(formatLogLine(entry));
134
+ console.log("");
135
+ console.log("═".repeat(70));
136
+ console.log(` Full logs saved to: ${logger.logFile}`);
137
+ console.log("═".repeat(70));
138
+ console.log("");
139
+ }
140
+ yield* registry.killAll(true).pipe(effect.Effect.ignore);
141
+ }));
142
+ yield* effect.Deferred.await(shutdown);
149
143
  });
150
144
  const startApp = (orchestrator) => {
151
- let activeCleanup = null;
152
- const program = effect.Effect.scoped(runDevSession(orchestrator, (cleanup) => {
153
- activeCleanup = cleanup;
145
+ let requestShutdown = null;
146
+ let signalCount = 0;
147
+ let forceExitTimer = null;
148
+ const forceExit = () => {
149
+ console.log("\n[Dev] Force exit");
150
+ process.exit(0);
151
+ };
152
+ const program = effect.Effect.scoped(runDevSession(orchestrator, (shutdown) => {
153
+ requestShutdown = shutdown;
154
154
  })).pipe(effect.Effect.catchAll((e) => effect.Effect.sync(() => {
155
155
  if (e instanceof Error) {
156
156
  console.error("App server error:", e.message);
157
157
  if (e.stack) console.error(e.stack);
158
158
  } else console.error("App server error:", e);
159
159
  })));
160
- const handleSignal = async () => {
161
- if (activeCleanup) await activeCleanup();
162
- };
163
- const forceExit = () => {
164
- console.log("\n[Dev] Force exit");
165
- process.exit(0);
166
- };
167
- let signalCount = 0;
168
- process.on("SIGINT", () => {
169
- signalCount++;
170
- if (signalCount > 1) {
171
- forceExit();
172
- return;
173
- }
174
- const timeout = setTimeout(forceExit, 5e3);
175
- handleSignal().finally(() => {
176
- clearTimeout(timeout);
177
- });
178
- });
179
- process.on("SIGTERM", () => {
160
+ const handleSignal = () => {
180
161
  signalCount++;
181
162
  if (signalCount > 1) {
182
163
  forceExit();
183
164
  return;
184
165
  }
185
- const timeout = setTimeout(forceExit, 5e3);
186
- handleSignal().finally(() => {
187
- clearTimeout(timeout);
188
- });
166
+ console.log("\n[Dev] Shutting down...");
167
+ forceExitTimer = setTimeout(forceExit, 8e3);
168
+ requestShutdown?.();
169
+ };
170
+ process.on("SIGINT", handleSignal);
171
+ process.on("SIGTERM", handleSignal);
172
+ effect.Effect.runPromiseExit(program).then((exit) => {
173
+ if (forceExitTimer) clearTimeout(forceExitTimer);
174
+ process.exit(effect.Exit.isSuccess(exit) ? 0 : 0);
189
175
  });
190
- effect.Effect.runPromise(program);
191
176
  };
192
177
 
193
178
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"dev-session.cjs","names":["Effect","getProjectRoot","getProcessConfig","makeProcessRegistry","createDevLogger","renderDevView","renderStreamingView","makeDevProcess"],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onCleanupReady?: (cleanup: () => Promise<void>) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n const handles: ProcessHandle[] = [];\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shuttingDown = false;\n\n const killAll = async () => {\n const reversed = [...handles].reverse();\n for (const handle of reversed) {\n try {\n await handle.kill();\n } catch {}\n }\n await Effect.runPromise(registry.killAll(true)).catch(() => {});\n };\n\n const exportLogs = async () => {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n };\n\n const cleanup = async (showLogs = false) => {\n if (shuttingDown) return;\n shuttingDown = true;\n view?.unmount();\n await killAll();\n if (showLogs) {\n await exportLogs();\n }\n };\n\n onCleanupReady?.(cleanup);\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => cleanup(false),\n () => cleanup(true),\n )\n : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () =>\n cleanup(false),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n handles.push(...nonHostHandles);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n handles.push(...hostHandles);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));\n yield* Effect.never;\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let activeCleanup: (() => Promise<void>) | null = null;\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (cleanup) => {\n activeCleanup = cleanup;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = async () => {\n if (activeCleanup) await activeCleanup();\n };\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n let signalCount = 0;\n process.on(\"SIGINT\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n process.on(\"SIGTERM\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n\n void Effect.runPromise(program);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev-session.cjs","names":["Effect","makeDevProcess","getProjectRoot","getProcessConfig","makeProcessRegistry","createDevLogger","Deferred","renderDevView","renderStreamingView","Exit"],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Deferred, Effect, Exit } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry, type ProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nconst scopedProcess = (\n pkg: string,\n env: Record<string, string> | undefined,\n callbacks: ProcessCallbacks,\n portOverride: number | undefined,\n bosConfig: BosConfig | undefined,\n runtimeConfig: RuntimeConfig | undefined,\n registry: ProcessRegistry | undefined,\n) =>\n Effect.acquireRelease(\n makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry),\n (handle) => handle.kill.pipe(Effect.ignore),\n );\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onShutdownReady?: (requestShutdown: () => void) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n onShutdownReady?.(() => {\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n });\n\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shouldExportLogs = false;\n\n const requestShutdownAndExport = () => {\n shouldExportLogs = true;\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n };\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n requestShutdownAndExport,\n )\n : renderStreamingView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return scopedProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n view?.unmount();\n\n if (shouldExportLogs) {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n }\n\n yield* registry.killAll(true).pipe(Effect.ignore);\n }),\n );\n\n yield* Deferred.await(shutdown);\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let requestShutdown: (() => void) | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (shutdown) => {\n requestShutdown = shutdown;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 8000);\n requestShutdown?.();\n };\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n Effect.runPromiseExit(program).then((exit) => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 0);\n });\n};\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { renderDevView } from "./components/dev-view.mjs";
4
4
  import { renderStreamingView } from "./components/streaming-view.mjs";
5
5
  import { createDevLogger } from "./dev-logs.mjs";
6
6
  import { makeProcessRegistry } from "./process-registry.mjs";
7
- import { Effect } from "effect";
7
+ import { Deferred, Effect, Exit } from "effect";
8
8
 
9
9
  //#region src/dev-session.ts
10
10
  const LOG_NOISE_PATTERNS = [
@@ -53,7 +53,8 @@ function formatLogLine(entry) {
53
53
  const prefix = entry.isError ? "ERR" : "OUT";
54
54
  return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;
55
55
  }
56
- const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* () {
56
+ const scopedProcess = (pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry) => Effect.acquireRelease(makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry), (handle) => handle.kill.pipe(Effect.ignore));
57
+ const runDevSession = (orchestrator, onShutdownReady) => Effect.gen(function* () {
57
58
  const configDir = getProjectRoot();
58
59
  const orderedPackages = sortByOrder(orchestrator.packages);
59
60
  const initialProcesses = orderedPackages.map((pkg) => {
@@ -69,41 +70,18 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
69
70
  const registry = yield* makeProcessRegistry(configDir);
70
71
  yield* registry.killAll().pipe(Effect.ignore);
71
72
  const logger = yield* Effect.promise(() => createDevLogger(configDir, orchestrator.description));
72
- const handles = [];
73
+ const shutdown = yield* Deferred.make();
74
+ onShutdownReady?.(() => {
75
+ Effect.runPromise(Deferred.succeed(shutdown, void 0));
76
+ });
73
77
  const allLogs = [];
74
78
  let view = null;
75
- let shuttingDown = false;
76
- const killAll = async () => {
77
- const reversed = [...handles].reverse();
78
- for (const handle of reversed) try {
79
- await handle.kill();
80
- } catch {}
81
- await Effect.runPromise(registry.killAll(true)).catch(() => {});
82
- };
83
- const exportLogs = async () => {
84
- console.log("\n");
85
- console.log("═".repeat(70));
86
- console.log(` SESSION LOGS: ${orchestrator.description}`);
87
- console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
88
- console.log(` Total entries: ${allLogs.length}`);
89
- console.log("═".repeat(70));
90
- console.log("");
91
- for (const entry of allLogs) console.log(formatLogLine(entry));
92
- console.log("");
93
- console.log("═".repeat(70));
94
- console.log(` Full logs saved to: ${logger.logFile}`);
95
- console.log("═".repeat(70));
96
- console.log("");
97
- };
98
- const cleanup = async (showLogs = false) => {
99
- if (shuttingDown) return;
100
- shuttingDown = true;
101
- view?.unmount();
102
- await killAll();
103
- if (showLogs) await exportLogs();
79
+ let shouldExportLogs = false;
80
+ const requestShutdownAndExport = () => {
81
+ shouldExportLogs = true;
82
+ Effect.runPromise(Deferred.succeed(shutdown, void 0));
104
83
  };
105
- onCleanupReady?.(cleanup);
106
- view = orchestrator.interactive ?? isInteractiveSupported() ? renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false), () => cleanup(true)) : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false));
84
+ view = orchestrator.interactive ?? isInteractiveSupported() ? renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => void Effect.runPromise(Deferred.succeed(shutdown, void 0)), requestShutdownAndExport) : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => void Effect.runPromise(Deferred.succeed(shutdown, void 0)));
107
85
  const callbacks = {
108
86
  onStatus: (name, status, message) => {
109
87
  view?.updateProcess(name, status, message);
@@ -123,7 +101,7 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
123
101
  };
124
102
  const startProcess = (pkg) => {
125
103
  const portOverride = pkg === "host" ? orchestrator.port : void 0;
126
- return makeDevProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
104
+ return scopedProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
127
105
  };
128
106
  const startGroup = (packages) => Effect.forEach(packages, startProcess, { concurrency: "unbounded" });
129
107
  const awaitReady = (pkg, handle) => Effect.race(handle.waitForReady, Effect.sleep("30 seconds").pipe(Effect.andThen(Effect.sync(() => {
@@ -132,61 +110,68 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
132
110
  const nonHostPackages = orderedPackages.filter((pkg) => pkg !== "host");
133
111
  const hostPackages = orderedPackages.filter((pkg) => pkg === "host");
134
112
  const nonHostHandles = yield* startGroup(nonHostPackages);
135
- handles.push(...nonHostHandles);
136
113
  yield* Effect.forEach(nonHostHandles.map((handle, index) => ({
137
114
  handle,
138
115
  pkg: nonHostPackages[index] ?? handle.name
139
116
  })), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
140
117
  const hostHandles = yield* startGroup(hostPackages);
141
- handles.push(...hostHandles);
142
118
  yield* Effect.forEach(hostHandles.map((handle, index) => ({
143
119
  handle,
144
120
  pkg: hostPackages[index] ?? handle.name
145
121
  })), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
146
- yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));
147
- yield* Effect.never;
122
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
123
+ view?.unmount();
124
+ if (shouldExportLogs) {
125
+ console.log("\n");
126
+ console.log("═".repeat(70));
127
+ console.log(` SESSION LOGS: ${orchestrator.description}`);
128
+ console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
129
+ console.log(` Total entries: ${allLogs.length}`);
130
+ console.log("═".repeat(70));
131
+ console.log("");
132
+ for (const entry of allLogs) console.log(formatLogLine(entry));
133
+ console.log("");
134
+ console.log("═".repeat(70));
135
+ console.log(` Full logs saved to: ${logger.logFile}`);
136
+ console.log("═".repeat(70));
137
+ console.log("");
138
+ }
139
+ yield* registry.killAll(true).pipe(Effect.ignore);
140
+ }));
141
+ yield* Deferred.await(shutdown);
148
142
  });
149
143
  const startApp = (orchestrator) => {
150
- let activeCleanup = null;
151
- const program = Effect.scoped(runDevSession(orchestrator, (cleanup) => {
152
- activeCleanup = cleanup;
144
+ let requestShutdown = null;
145
+ let signalCount = 0;
146
+ let forceExitTimer = null;
147
+ const forceExit = () => {
148
+ console.log("\n[Dev] Force exit");
149
+ process.exit(0);
150
+ };
151
+ const program = Effect.scoped(runDevSession(orchestrator, (shutdown) => {
152
+ requestShutdown = shutdown;
153
153
  })).pipe(Effect.catchAll((e) => Effect.sync(() => {
154
154
  if (e instanceof Error) {
155
155
  console.error("App server error:", e.message);
156
156
  if (e.stack) console.error(e.stack);
157
157
  } else console.error("App server error:", e);
158
158
  })));
159
- const handleSignal = async () => {
160
- if (activeCleanup) await activeCleanup();
161
- };
162
- const forceExit = () => {
163
- console.log("\n[Dev] Force exit");
164
- process.exit(0);
165
- };
166
- let signalCount = 0;
167
- process.on("SIGINT", () => {
168
- signalCount++;
169
- if (signalCount > 1) {
170
- forceExit();
171
- return;
172
- }
173
- const timeout = setTimeout(forceExit, 5e3);
174
- handleSignal().finally(() => {
175
- clearTimeout(timeout);
176
- });
177
- });
178
- process.on("SIGTERM", () => {
159
+ const handleSignal = () => {
179
160
  signalCount++;
180
161
  if (signalCount > 1) {
181
162
  forceExit();
182
163
  return;
183
164
  }
184
- const timeout = setTimeout(forceExit, 5e3);
185
- handleSignal().finally(() => {
186
- clearTimeout(timeout);
187
- });
165
+ console.log("\n[Dev] Shutting down...");
166
+ forceExitTimer = setTimeout(forceExit, 8e3);
167
+ requestShutdown?.();
168
+ };
169
+ process.on("SIGINT", handleSignal);
170
+ process.on("SIGTERM", handleSignal);
171
+ Effect.runPromiseExit(program).then((exit) => {
172
+ if (forceExitTimer) clearTimeout(forceExitTimer);
173
+ process.exit(Exit.isSuccess(exit) ? 0 : 0);
188
174
  });
189
- Effect.runPromise(program);
190
175
  };
191
176
 
192
177
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onCleanupReady?: (cleanup: () => Promise<void>) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n const handles: ProcessHandle[] = [];\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shuttingDown = false;\n\n const killAll = async () => {\n const reversed = [...handles].reverse();\n for (const handle of reversed) {\n try {\n await handle.kill();\n } catch {}\n }\n await Effect.runPromise(registry.killAll(true)).catch(() => {});\n };\n\n const exportLogs = async () => {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n };\n\n const cleanup = async (showLogs = false) => {\n if (shuttingDown) return;\n shuttingDown = true;\n view?.unmount();\n await killAll();\n if (showLogs) {\n await exportLogs();\n }\n };\n\n onCleanupReady?.(cleanup);\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => cleanup(false),\n () => cleanup(true),\n )\n : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () =>\n cleanup(false),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n handles.push(...nonHostHandles);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n handles.push(...hostHandles);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));\n yield* Effect.never;\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let activeCleanup: (() => Promise<void>) | null = null;\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (cleanup) => {\n activeCleanup = cleanup;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = async () => {\n if (activeCleanup) await activeCleanup();\n };\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n let signalCount = 0;\n process.on(\"SIGINT\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n process.on(\"SIGTERM\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n\n void Effect.runPromise(program);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Deferred, Effect, Exit } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry, type ProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nconst scopedProcess = (\n pkg: string,\n env: Record<string, string> | undefined,\n callbacks: ProcessCallbacks,\n portOverride: number | undefined,\n bosConfig: BosConfig | undefined,\n runtimeConfig: RuntimeConfig | undefined,\n registry: ProcessRegistry | undefined,\n) =>\n Effect.acquireRelease(\n makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry),\n (handle) => handle.kill.pipe(Effect.ignore),\n );\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onShutdownReady?: (requestShutdown: () => void) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n onShutdownReady?.(() => {\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n });\n\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shouldExportLogs = false;\n\n const requestShutdownAndExport = () => {\n shouldExportLogs = true;\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n };\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n requestShutdownAndExport,\n )\n : renderStreamingView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return scopedProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n view?.unmount();\n\n if (shouldExportLogs) {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n }\n\n yield* registry.killAll(true).pipe(Effect.ignore);\n }),\n );\n\n yield* Deferred.await(shutdown);\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let requestShutdown: (() => void) | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (shutdown) => {\n requestShutdown = shutdown;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 8000);\n requestShutdown?.();\n };\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n Effect.runPromiseExit(program).then((exit) => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 0);\n });\n};\n"],"mappings":"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"}
package/dist/fastkv.cjs CHANGED
@@ -59,6 +59,59 @@ async function fetchBosConfigFromFastKv(bosUrl) {
59
59
  if (typeof value !== "object") throw new Error(`Invalid config value for ${bosUrl}`);
60
60
  return value;
61
61
  }
62
+ function parsePluginBosUrl(source) {
63
+ if (!source.startsWith("bos://")) return null;
64
+ const match = source.match(/^bos:\/\/([^/]+)\/plugins\/([^/]+)$/);
65
+ if (!match?.[1] || !match[2]) return null;
66
+ return {
67
+ accountId: match[1],
68
+ pluginName: match[2]
69
+ };
70
+ }
71
+ async function fetchKvValue(accountId, key) {
72
+ const value = (await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`, {
73
+ method: "POST",
74
+ body: JSON.stringify({
75
+ key,
76
+ limit: 1
77
+ })
78
+ }))?.entries?.find(Boolean)?.value;
79
+ if (value == null) return null;
80
+ if (typeof value === "string") try {
81
+ return JSON.parse(value);
82
+ } catch {
83
+ return null;
84
+ }
85
+ return value;
86
+ }
87
+ async function fetchPluginFromRegistry(accountId, pluginName) {
88
+ const manifestKey = `plugins/${accountId}/${pluginName}/manifest.json`;
89
+ const metadataKey = `plugins/${accountId}/${pluginName}/metadata`;
90
+ const [rawManifest, rawMetadata] = await Promise.all([fetchKvValue(accountId, manifestKey), fetchKvValue(accountId, metadataKey)]);
91
+ if (!rawManifest || typeof rawManifest !== "object") return null;
92
+ return {
93
+ manifest: rawManifest,
94
+ metadata: rawMetadata ?? {
95
+ title: null,
96
+ description: null,
97
+ repoUrl: null,
98
+ version: "",
99
+ publishedAt: "",
100
+ cdnUrl: "",
101
+ integrity: null
102
+ }
103
+ };
104
+ }
105
+ async function fetchRemotePluginManifest(cdnUrl) {
106
+ try {
107
+ const baseUrl = cdnUrl.replace(/\/$/, "");
108
+ const response = await fetch(`${baseUrl}/plugin.manifest.json`);
109
+ if (!response.ok) return null;
110
+ return await response.json();
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
62
115
  async function fetchJson(url, init) {
63
116
  const controller = new AbortController();
64
117
  const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);
@@ -83,7 +136,10 @@ async function fetchJson(url, init) {
83
136
  exports.buildRegistryConfigUrl = buildRegistryConfigUrl;
84
137
  exports.buildRegistryConfigUrlForNetwork = buildRegistryConfigUrlForNetwork;
85
138
  exports.fetchBosConfigFromFastKv = fetchBosConfigFromFastKv;
139
+ exports.fetchPluginFromRegistry = fetchPluginFromRegistry;
140
+ exports.fetchRemotePluginManifest = fetchRemotePluginManifest;
86
141
  exports.getFastKvBaseUrlForNetwork = getFastKvBaseUrlForNetwork;
87
142
  exports.getRegistryNamespaceForAccount = getRegistryNamespaceForAccount;
88
143
  exports.getRegistryNamespaceForNetwork = getRegistryNamespaceForNetwork;
144
+ exports.parsePluginBosUrl = parsePluginBosUrl;
89
145
  //# sourceMappingURL=fastkv.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"fastkv.cjs","names":[],"sources":["../src/fastkv.ts"],"sourcesContent":["export type NetworkId = \"mainnet\" | \"testnet\";\n\ninterface FastKvEntry {\n value: unknown;\n}\n\ninterface FastKvListResponse {\n entries?: Array<FastKvEntry | null>;\n}\n\nconst FASTKV_TIMEOUT_MS = 10_000;\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_URL || \"https://kv.test.fastnear.com\"\n : process.env.REGISTRY_FASTKV_MAINNET_URL || \"https://kv.main.fastnear.com\";\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(accountId: string, gatewayId: string): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function buildRegistryConfigUrlForNetwork(\n network: NetworkId,\n accountId: string,\n gatewayId: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string): string {\n return accountId.endsWith(\".testnet\")\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || accountId;\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || \"dev.everything.near\";\n}\n\nfunction getRegistryConfigKey(\n accountId: string,\n gatewayId: string,\n pathSegments: string[] = [],\n): string {\n const suffix =\n pathSegments.length > 0\n ? `/${pathSegments.map((segment) => encodeURIComponent(segment)).join(\"/\")}`\n : \"\";\n return `apps/${accountId}/${gatewayId}${suffix}/bos.config.json`;\n}\n\nfunction parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const match = bosUrl.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!match?.[1] || !match[2]) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const pathSegments = match[2]\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => decodeURIComponent(segment));\n if (pathSegments.length === 0) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const [gatewayId, ...pathSegmentsTail] = pathSegments;\n if (!gatewayId) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n return {\n accountId: match[1],\n gatewayId,\n pathSegments: pathSegmentsTail,\n };\n}\n\nexport async function fetchBosConfigFromFastKv<T>(bosUrl: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n key: getRegistryConfigKey(accountId, gatewayId, pathSegments),\n limit: 1,\n }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n\n if (!value) {\n throw new Error(`No config found for ${bosUrl}`);\n }\n\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${bosUrl}`);\n }\n\n return value as T;\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers ?? {}),\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fastkv.cjs","names":[],"sources":["../src/fastkv.ts"],"sourcesContent":["export type NetworkId = \"mainnet\" | \"testnet\";\n\ninterface FastKvEntry {\n value: unknown;\n}\n\ninterface FastKvListResponse {\n entries?: Array<FastKvEntry | null>;\n}\n\nconst FASTKV_TIMEOUT_MS = 10_000;\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_URL || \"https://kv.test.fastnear.com\"\n : process.env.REGISTRY_FASTKV_MAINNET_URL || \"https://kv.main.fastnear.com\";\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(accountId: string, gatewayId: string): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function buildRegistryConfigUrlForNetwork(\n network: NetworkId,\n accountId: string,\n gatewayId: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string): string {\n return accountId.endsWith(\".testnet\")\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || accountId;\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || \"dev.everything.near\";\n}\n\nfunction getRegistryConfigKey(\n accountId: string,\n gatewayId: string,\n pathSegments: string[] = [],\n): string {\n const suffix =\n pathSegments.length > 0\n ? `/${pathSegments.map((segment) => encodeURIComponent(segment)).join(\"/\")}`\n : \"\";\n return `apps/${accountId}/${gatewayId}${suffix}/bos.config.json`;\n}\n\nfunction parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const match = bosUrl.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!match?.[1] || !match[2]) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const pathSegments = match[2]\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => decodeURIComponent(segment));\n if (pathSegments.length === 0) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const [gatewayId, ...pathSegmentsTail] = pathSegments;\n if (!gatewayId) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n return {\n accountId: match[1],\n gatewayId,\n pathSegments: pathSegmentsTail,\n };\n}\n\nexport async function fetchBosConfigFromFastKv<T>(bosUrl: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n key: getRegistryConfigKey(accountId, gatewayId, pathSegments),\n limit: 1,\n }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n\n if (!value) {\n throw new Error(`No config found for ${bosUrl}`);\n }\n\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${bosUrl}`);\n }\n\n return value as T;\n}\n\nexport interface PluginManifest {\n schemaVersion: number;\n kind: string;\n plugin: { name: string; version: string };\n runtime: { remoteEntry: string };\n contract: {\n kind: string;\n types: { path: string; exportName: string; typeName: string; sha256: string };\n };\n additionalExports?: Array<{ path: string; exports: string[]; sha256: string }>;\n}\n\nexport interface PluginMetadata {\n title: string | null;\n description: string | null;\n repoUrl: string | null;\n version: string;\n publishedAt: string;\n cdnUrl: string;\n integrity: string | null;\n}\n\nexport interface PluginRegistryEntry {\n manifest: PluginManifest;\n metadata: PluginMetadata;\n}\n\nexport function parsePluginBosUrl(\n source: string,\n): { accountId: string; pluginName: string } | null {\n if (!source.startsWith(\"bos://\")) return null;\n const match = source.match(/^bos:\\/\\/([^/]+)\\/plugins\\/([^/]+)$/);\n if (!match?.[1] || !match[2]) return null;\n return { accountId: match[1], pluginName: match[2] };\n}\n\nasync function fetchKvValue(accountId: string, key: string): Promise<unknown | null> {\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({ key, limit: 1 }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n if (value == null) return null;\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n return value;\n}\n\nexport async function fetchPluginFromRegistry(\n accountId: string,\n pluginName: string,\n): Promise<PluginRegistryEntry | null> {\n const manifestKey = `plugins/${accountId}/${pluginName}/manifest.json`;\n const metadataKey = `plugins/${accountId}/${pluginName}/metadata`;\n\n const [rawManifest, rawMetadata] = await Promise.all([\n fetchKvValue(accountId, manifestKey),\n fetchKvValue(accountId, metadataKey),\n ]);\n\n if (!rawManifest || typeof rawManifest !== \"object\") return null;\n\n return {\n manifest: rawManifest as PluginManifest,\n metadata: (rawMetadata ?? {\n title: null,\n description: null,\n repoUrl: null,\n version: \"\",\n publishedAt: \"\",\n cdnUrl: \"\",\n integrity: null,\n }) as PluginMetadata,\n };\n}\n\nexport async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n try {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n const response = await fetch(`${baseUrl}/plugin.manifest.json`);\n if (!response.ok) return null;\n return (await response.json()) as PluginManifest;\n } catch {\n return null;\n }\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers ?? {}),\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}