everything-dev 2.0.0-rc.5 → 2.0.0-rc.7
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/README.md +2 -2
- package/dist/build.cjs +1 -1
- package/dist/build.mjs +1 -1
- package/dist/bundle-cache.cjs +1 -1
- package/dist/bundle-cache.cjs.map +1 -1
- package/dist/bundle-cache.mjs +1 -1
- package/dist/bundle-cache.mjs.map +1 -1
- package/dist/cli/infra.cjs +4 -2
- package/dist/cli/infra.cjs.map +1 -1
- package/dist/cli/infra.mjs +4 -2
- package/dist/cli/infra.mjs.map +1 -1
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/deploy.cjs +111 -1
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +112 -2
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/smoke.cjs +41 -0
- package/dist/commands/smoke.cjs.map +1 -0
- package/dist/commands/smoke.mjs +41 -0
- package/dist/commands/smoke.mjs.map +1 -0
- package/dist/commands/upgrade.cjs +2 -2
- package/dist/commands/upgrade.cjs.map +1 -1
- package/dist/commands/upgrade.mjs +1 -1
- package/dist/commands/upgrade.mjs.map +1 -1
- package/dist/contract.cjs +27 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +65 -1
- package/dist/contract.d.mts +65 -1
- package/dist/contract.meta.cjs +13 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +23 -1
- package/dist/contract.meta.d.mts +23 -1
- package/dist/contract.meta.mjs +13 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +26 -2
- package/dist/contract.mjs.map +1 -1
- package/dist/dev-log-pipeline.cjs +6 -0
- package/dist/dev-log-pipeline.cjs.map +1 -1
- package/dist/dev-log-pipeline.mjs +6 -0
- package/dist/dev-log-pipeline.mjs.map +1 -1
- package/dist/dev-program.cjs +21 -5
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +21 -6
- package/dist/dev-program.mjs.map +1 -1
- package/dist/dev-session.cjs +1 -0
- package/dist/dev-session.cjs.map +1 -1
- package/dist/dev-session.mjs +1 -0
- package/dist/dev-session.mjs.map +1 -1
- package/dist/fastkv.cjs +21 -3
- package/dist/fastkv.cjs.map +1 -1
- package/dist/fastkv.d.cts +14 -1
- package/dist/fastkv.d.mts +14 -1
- package/dist/fastkv.mjs +21 -5
- package/dist/fastkv.mjs.map +1 -1
- package/dist/{mf.cjs → federation.cjs} +6 -13
- package/dist/federation.cjs.map +1 -0
- package/dist/{mf.d.cts → federation.d.cts} +2 -2
- package/dist/{mf.d.mts → federation.d.mts} +2 -2
- package/dist/{mf.mjs → federation.mjs} +6 -13
- package/dist/federation.mjs.map +1 -0
- package/dist/http-client.cjs +70 -53
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.mjs +70 -54
- package/dist/http-client.mjs.map +1 -1
- package/dist/image-deploy.cjs +9 -0
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +9 -1
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/orchestrator.cjs +60 -52
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.mjs +60 -52
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +24 -0
- package/dist/plugin.d.mts +24 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/process-registry.cjs +1 -1
- package/dist/process-registry.mjs +1 -1
- package/dist/publish.cjs +3 -3
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +4 -4
- package/dist/publish.mjs.map +1 -1
- package/dist/resolver.cjs +293 -0
- package/dist/resolver.cjs.map +1 -0
- package/dist/resolver.d.cts +99 -0
- package/dist/resolver.d.mts +99 -0
- package/dist/resolver.mjs +284 -0
- package/dist/resolver.mjs.map +1 -0
- package/dist/smoke.cjs +262 -0
- package/dist/smoke.cjs.map +1 -0
- package/dist/smoke.mjs +260 -0
- package/dist/smoke.mjs.map +1 -0
- package/dist/version-manifest-resolve.cjs +3 -3
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.mjs +1 -1
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/dist/workspace.cjs +1 -1
- package/dist/workspace.mjs +1 -1
- package/package.json +9 -9
- package/dist/bundle-fs-resolve.cjs +0 -201
- package/dist/bundle-fs-resolve.cjs.map +0 -1
- package/dist/bundle-fs-resolve.d.cts +0 -82
- package/dist/bundle-fs-resolve.d.mts +0 -82
- package/dist/bundle-fs-resolve.mjs +0 -194
- package/dist/bundle-fs-resolve.mjs.map +0 -1
- package/dist/bundle-path.cjs +0 -48
- package/dist/bundle-path.cjs.map +0 -1
- package/dist/bundle-path.mjs +0 -45
- package/dist/bundle-path.mjs.map +0 -1
- package/dist/mf.cjs.map +0 -1
- package/dist/mf.mjs.map +0 -1
package/dist/dev-session.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Clock, DateTime, Deferred, Effect, Exit, Layer } from \"effect\";\nimport { probePortBindable } from \"./app\";\nimport {\n createDevRenderer,\n type DevProcessState,\n type DevRendererHandle,\n} from \"./components/dev-render\";\nimport { createLogPipeline, isDebug, type LogEvent, resolveLogLevel } from \"./dev-log-pipeline\";\nimport { createDevLogger, formatLogLine } from \"./dev-logs\";\nimport { getProcessEnv } from \"./env/process-env\";\nimport { ShellEnvLive } from \"./env/project-env\";\nimport { ownerOfPort } from \"./infra/port-ownership\";\nimport {\n getProcessStates,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { reapGroup } from \"./process-kill\";\nimport {\n isPidAlive,\n readRegistry,\n registerStandalone,\n unregisterPid,\n updateChildPids,\n writeRegistry,\n} from \"./process-registry\";\nimport {\n type AppOrchestrator,\n DevGeneratedEnvLive,\n DevRuntimeConfig,\n DevRuntimeConfigLive,\n isAuthMirrorPluginEntry,\n type ServiceDescriptor,\n ServiceDescriptorMap,\n ServiceDescriptorMapLive,\n} from \"./service-descriptor\";\nimport type { RuntimeConfig } from \"./types\";\n\nconst adoptOrphanedChildren = (configDir: string): void => {\n try {\n const entries = readRegistry().filter(\n (entry) => entry.pid > 1 && entry.configDir === configDir && !isPidAlive(entry.pid),\n );\n if (entries.length === 0) return;\n const reaped: number[] = [];\n for (const entry of entries) {\n for (const childPid of entry.childPids ?? []) {\n if (childPid === process.pid) continue;\n if (isPidAlive(childPid)) {\n reapGroup(childPid);\n reaped.push(childPid);\n }\n }\n }\n if (reaped.length > 0) {\n console.error(\n `[Dev] Reaped ${reaped.length} orphaned child process(es) from dead session(s): ${reaped.join(\", \")}`,\n );\n }\n const deadPids = new Set(entries.map((entry) => entry.pid));\n writeRegistry(readRegistry().filter((entry) => !deadPids.has(entry.pid)));\n } catch {\n // best-effort; registry hygiene is non-critical for the running session\n }\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui\", \"auth\", \"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\nexport interface DevSessionControls {\n requestShutdown: () => void;\n emergencyKill: () => void;\n requestShutdownEscalating: () => void;\n forceExit: () => void;\n restoreView: () => void;\n rearmForceExitTimer: () => void;\n}\n\nexport const runDevSession = (\n configDir: string,\n orchestrator: AppOrchestrator,\n onShutdownReady?: (controls: DevSessionControls) => void,\n) =>\n Effect.gen(function* () {\n adoptOrphanedChildren(configDir);\n const services = yield* ServiceDescriptorMap;\n const runtimeConfig = yield* DevRuntimeConfig;\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: DevProcessState[] = getProcessStates(\n orderedPackages,\n services,\n orchestrator.port,\n );\n\n if (isDebug(getProcessEnv(\"DEBUG\"))) {\n yield* Effect.logError(\n `[DEBUG session] orchestrator.packages: ${orchestrator.packages.join(\", \")}`,\n );\n yield* Effect.logError(`[DEBUG session] orderedPackages: ${orderedPackages.join(\", \")}`);\n yield* Effect.logError(`[DEBUG session] services keys: ${[...services.keys()].join(\", \")}`);\n yield* Effect.logError(\n `[DEBUG session] initialProcesses: ${initialProcesses.map((p) => `${p.name}=${p.source}`).join(\", \")}`,\n );\n }\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n const effectContext = yield* Effect.context();\n const controls: DevSessionControls = {\n requestShutdown: () => {\n void Effect.runPromiseWith(effectContext)(Deferred.succeed(shutdown, undefined));\n },\n emergencyKill: () => {},\n requestShutdownEscalating: () => {},\n forceExit: () => {},\n restoreView: () => {},\n rearmForceExitTimer: () => {},\n };\n\n onShutdownReady?.(controls);\n\n const isWorkspaceChild = getProcessEnv(\"BOS_WORKSPACE_CHILD\") === \"1\";\n let ownedPorts: number[] = [];\n if (!isWorkspaceChild) {\n const regPorts: Record<string, number> = {};\n const addPort = (key: string, value: number | undefined | null) => {\n if (value != null) regPorts[key] = value;\n };\n addPort(\"host\", runtimeConfig.host.port);\n addPort(\"api\", runtimeConfig.api.port);\n addPort(\"ui\", runtimeConfig.ui.port);\n addPort(\"auth\", runtimeConfig.auth?.port);\n if (runtimeConfig.plugins) {\n for (const [id, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (!isAuthMirrorPluginEntry(runtimeConfig.auth, id, plugin)) {\n addPort(`plugin:${id}`, plugin.port);\n }\n addPort(`plugin-ui:${id}`, plugin.ui?.port);\n }\n }\n registerStandalone({\n pid: process.pid,\n configDir,\n ports: regPorts,\n startedAt: yield* Clock.currentTimeMillis,\n description: orchestrator.description,\n });\n ownedPorts = Object.values(regPorts);\n }\n\n let view: DevRendererHandle | null = null;\n let shouldExportLogs = false;\n\n const logLevel = resolveLogLevel();\n const allLogs: LogEvent[] = [];\n const pipeline = createLogPipeline({\n level: logLevel,\n sinks: {\n display: (event) => {\n view?.addLog(event.source, event.line, event.level === \"error\");\n },\n file: (event) => {\n if (orchestrator.noLogs) return;\n void logger.write(event);\n },\n export: (event) => {\n allLogs.push(event);\n },\n },\n });\n\n const useInteractive = isInteractiveSupported() && orchestrator.interactive !== false;\n view = createDevRenderer(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => controls.requestShutdownEscalating(),\n () => {\n shouldExportLogs = true;\n controls.requestShutdownEscalating();\n },\n { interactive: useInteractive, onForceExit: () => controls.forceExit() },\n );\n controls.restoreView = () => {\n if (useInteractive) view?.unmount();\n };\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n pipeline.ingest({ source: name, line, isError });\n },\n };\n\n const spawned: ProcessHandle[] = [];\n\n controls.emergencyKill = () => {\n for (const handle of spawned) {\n if (handle.pid === undefined) continue;\n reapGroup(handle.pid);\n }\n };\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n controls.rearmForceExitTimer();\n\n yield* Effect.forEach(spawned, (h) => h.kill.pipe(Effect.ignore), {\n concurrency: \"unbounded\",\n });\n\n yield* Effect.sleep(\"200 millis\");\n\n for (const port of ownedPorts) {\n const bindable = yield* probePortBindable(port);\n if (!bindable) {\n const owner = yield* ownerOfPort(port);\n yield* Effect.logError(\n `[Dev] Port ${port} still bound after teardown${\n owner\n ? ` — pid ${owner.pid} (${owner.command})`\n : \" — owner unknown (lsof unavailable)\"\n }`,\n );\n }\n }\n\n if (!isWorkspaceChild) {\n yield* Effect.sync(() => {\n try {\n unregisterPid(process.pid);\n } catch {\n // best-effort; pruneDead cleans stale entries on next ps/kill\n }\n });\n }\n\n pipeline.flush();\n\n view?.unmount();\n\n if (shouldExportLogs) {\n const now = yield* Clock.currentTimeMillis;\n const startedMs = allLogs[0]?.timestamp || now;\n yield* Effect.log(\"\");\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(` SESSION LOGS: ${orchestrator.description}`);\n yield* Effect.log(` Started: ${DateTime.formatIso(DateTime.makeUnsafe(startedMs))}`);\n yield* Effect.log(` Filtered entries: ${allLogs.length} (level: ${logLevel})`);\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(\"\");\n for (const event of allLogs) {\n yield* Effect.log(formatLogLine(event));\n }\n yield* Effect.log(\"\");\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(` Full logs saved to: ${logger.logFile}`);\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(\"\");\n }\n }),\n );\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(pkg, callbacks, portOverride).pipe(\n Effect.tap((handle) => Effect.sync(() => spawned.push(handle))),\n Effect.flatMap((handle) =>\n Effect.acquireRelease(Effect.succeed(handle), () => handle.kill.pipe(Effect.ignore)),\n ),\n Effect.tapError((err) =>\n Effect.sync(() => {\n callbacks.onLog(pkg, `Failed to start: ${String(err)}`, true);\n callbacks.onStatus(pkg, \"error\");\n }),\n ),\n Effect.orElseSucceed(\n () =>\n ({\n name: pkg,\n pid: undefined,\n kill: Effect.void,\n waitForReady: Effect.void,\n waitForExit: Effect.never,\n }) satisfies ProcessHandle,\n ),\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n handle.waitForReady.pipe(\n Effect.timeout(\"120 seconds\"),\n Effect.catch((err) =>\n Effect.sync(() => {\n callbacks.onLog(\n pkg,\n `Timed out or failed: ${err instanceof Error ? err.message : String(err)}`,\n 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 startupPhase = Effect.gen(function* () {\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) => ({\n handle,\n pkg: hostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n return { nonHostHandles, hostHandles };\n });\n\n const startup = yield* Effect.raceFirst(\n startupPhase,\n Deferred.await(shutdown).pipe(Effect.as(null)),\n );\n\n if (startup === null) return;\n\n const allHandles = [...startup.nonHostHandles, ...startup.hostHandles];\n\n const childPids = allHandles\n .map((handle) => Number(handle.pid))\n .filter((pid) => Number.isFinite(pid) && pid > 1 && pid !== process.pid);\n\n if (!isWorkspaceChild && childPids.length > 0) {\n yield* Effect.sync(() => {\n try {\n updateChildPids(process.pid, childPids);\n } catch {\n // best-effort; registry hygiene is non-critical for the running session\n }\n });\n }\n\n yield* Deferred.await(shutdown);\n });\n\nconst runApp = (\n configDir: string,\n orchestrator: AppOrchestrator,\n services: Map<string, ServiceDescriptor>,\n runtimeConfig: RuntimeConfig,\n envGenerated: Record<string, string>,\n shellEnv: Record<string, string>,\n) => {\n let controls: DevSessionControls | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n controls?.restoreView();\n console.log(\"\\n[Dev] Force exit\");\n controls?.emergencyKill();\n process.exit(0);\n };\n\n // Orphan watch: when the wrapper chain above this process dies (playwright\n // tree-kills its webServer with SIGKILL — no signal handler runs, detached\n // group escapes), this orchestrator is reparented. Detect that and reap the\n // whole service tree — the alternatives are zombie port squatters that\n // poison the next run. Covers shell aborts (Ctrl+C killing only the\n // wrapper) the same way.\n const initialPpid = process.ppid;\n const orphanWatch = setInterval(() => {\n if (process.ppid !== initialPpid) {\n console.error(\"\\n[Dev] Parent process died — force exiting (orphaned service tree)\");\n forceExit();\n }\n }, 200);\n orphanWatch.unref?.();\n\n const requestShutdownEscalating = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n controls?.restoreView();\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 5000);\n forceExitTimer.unref?.();\n controls?.requestShutdown();\n };\n const handleSignal = requestShutdownEscalating;\n\n const program = Effect.scoped(\n runDevSession(configDir, orchestrator, (sessionControls) => {\n controls = sessionControls;\n sessionControls.requestShutdownEscalating = requestShutdownEscalating;\n sessionControls.rearmForceExitTimer = () => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n forceExitTimer = setTimeout(forceExit, 5000);\n forceExitTimer.unref?.();\n };\n sessionControls.forceExit = forceExit;\n }),\n ).pipe(\n Effect.provide(\n Layer.mergeAll(\n ServiceDescriptorMapLive(services),\n DevRuntimeConfigLive(runtimeConfig),\n DevGeneratedEnvLive(envGenerated),\n ShellEnvLive(shellEnv),\n ),\n ),\n Effect.catchDefect((defect) =>\n Effect.logError(\"[Dev] Unhandled defect in orchestrator:\", defect).pipe(\n Effect.andThen(Effect.die(defect)),\n ),\n ),\n );\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n void Effect.runPromiseExit(program)\n .then((exit) => {\n clearInterval(orphanWatch);\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 1);\n })\n .catch((error: unknown) => {\n clearInterval(orphanWatch);\n if (forceExitTimer) clearTimeout(forceExitTimer);\n console.error(\"[Dev] Failed to observe application exit:\", error);\n process.exit(1);\n });\n};\n\nexport const devApp = runApp;\n\nexport const startApp = runApp;\n"],"mappings":";;;;;;;;;;;;;;AAuCA,MAAM,yBAAyB,cAA4B;CACzD,IAAI;EACF,MAAM,UAAU,aAAa,CAAC,CAAC,QAC5B,UAAU,MAAM,MAAM,KAAK,MAAM,cAAc,aAAa,CAAC,WAAW,MAAM,GAAG,CACpF;EACA,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,SAAmB,CAAC;EAC1B,KAAK,MAAM,SAAS,SAClB,KAAK,MAAM,YAAY,MAAM,aAAa,CAAC,GAAG;GAC5C,IAAI,aAAa,QAAQ,KAAK;GAC9B,IAAI,WAAW,QAAQ,GAAG;IACxB,UAAU,QAAQ;IAClB,OAAO,KAAK,QAAQ;GACtB;EACF;EAEF,IAAI,OAAO,SAAS,GAClB,QAAQ,MACN,gBAAgB,OAAO,OAAO,oDAAoD,OAAO,KAAK,IAAI,GACpG;EAEF,MAAM,WAAW,IAAI,IAAI,QAAQ,KAAK,UAAU,MAAM,GAAG,CAAC;EAC1D,cAAc,aAAa,CAAC,CAAC,QAAQ,UAAU,CAAC,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC;CAC1E,QAAQ,CAER;AACF;AAEA,MAAM,+BAAwC;CAC5C,OAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ,OAAO,UAAU;AAClE;AAEA,MAAM,gBAAgB;CAAC;CAAM;CAAQ;CAAO;CAAU;CAAc;AAAM;AAE1E,MAAM,eAAe,aAAiC;CACpD,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM;EAClC,MAAM,OAAO,EAAE,WAAW,SAAS,IAC/B,cAAc,QAAQ,QAAQ,IAC9B,cAAc,QAAQ,CAAC;EAC3B,MAAM,OAAO,EAAE,WAAW,SAAS,IAC/B,cAAc,QAAQ,QAAQ,IAC9B,cAAc,QAAQ,CAAC;EAC3B,IAAI,SAAS,MAAM,SAAS,IAAI,OAAO;EACvC,IAAI,SAAS,IAAI,OAAO;EACxB,IAAI,SAAS,IAAI,OAAO;EACxB,OAAO,OAAO;CAChB,CAAC;AACH;AAWA,MAAa,iBACX,WACA,cACA,oBAEA,OAAO,IAAI,aAAa;CACtB,sBAAsB,SAAS;CAC/B,MAAM,WAAW,OAAO;CACxB,MAAM,gBAAgB,OAAO;CAC7B,MAAM,kBAAkB,YAAY,aAAa,QAAQ;CACzD,MAAM,mBAAsC,iBAC1C,iBACA,UACA,aAAa,IACf;CAEA,IAAI,QAAQ,cAAc,OAAO,CAAC,GAAG;EACnC,OAAO,OAAO,SACZ,0CAA0C,aAAa,SAAS,KAAK,IAAI,GAC3E;EACA,OAAO,OAAO,SAAS,oCAAoC,gBAAgB,KAAK,IAAI,GAAG;EACvF,OAAO,OAAO,SAAS,kCAAkC,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG;EAC1F,OAAO,OAAO,SACZ,qCAAqC,iBAAiB,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,IAAI,GACrG;CACF;CAEA,MAAM,SAAS,OAAO,OAAO,cAC3B,gBAAgB,WAAW,aAAa,WAAW,CACrD;CAEA,MAAM,WAAW,OAAO,SAAS,KAAW;CAE5C,MAAM,gBAAgB,OAAO,OAAO,QAAQ;CAC5C,MAAM,WAA+B;EACnC,uBAAuB;GACrB,AAAK,OAAO,eAAe,aAAa,CAAC,CAAC,SAAS,QAAQ,UAAU,MAAS,CAAC;EACjF;EACA,qBAAqB,CAAC;EACtB,iCAAiC,CAAC;EAClC,iBAAiB,CAAC;EAClB,mBAAmB,CAAC;EACpB,2BAA2B,CAAC;CAC9B;CAEA,kBAAkB,QAAQ;CAE1B,MAAM,mBAAmB,cAAc,qBAAqB,MAAM;CAClE,IAAI,aAAuB,CAAC;CAC5B,IAAI,CAAC,kBAAkB;EACrB,MAAM,WAAmC,CAAC;EAC1C,MAAM,WAAW,KAAa,UAAqC;GACjE,IAAI,SAAS,MAAM,SAAS,OAAO;EACrC;EACA,QAAQ,QAAQ,cAAc,KAAK,IAAI;EACvC,QAAQ,OAAO,cAAc,IAAI,IAAI;EACrC,QAAQ,MAAM,cAAc,GAAG,IAAI;EACnC,QAAQ,QAAQ,cAAc,MAAM,IAAI;EACxC,IAAI,cAAc,SAChB,KAAK,MAAM,CAAC,IAAI,WAAW,OAAO,QAAQ,cAAc,OAAO,GAAG;GAChE,IAAI,CAAC,wBAAwB,cAAc,MAAM,IAAI,MAAM,GACzD,QAAQ,UAAU,MAAM,OAAO,IAAI;GAErC,QAAQ,aAAa,MAAM,OAAO,IAAI,IAAI;EAC5C;EAEF,mBAAmB;GACjB,KAAK,QAAQ;GACb;GACA,OAAO;GACP,WAAW,OAAO,MAAM;GACxB,aAAa,aAAa;EAC5B,CAAC;EACD,aAAa,OAAO,OAAO,QAAQ;CACrC;CAEA,IAAI,OAAiC;CACrC,IAAI,mBAAmB;CAEvB,MAAM,WAAW,gBAAgB;CACjC,MAAM,UAAsB,CAAC;CAC7B,MAAM,WAAW,kBAAkB;EACjC,OAAO;EACP,OAAO;GACL,UAAU,UAAU;IAClB,MAAM,OAAO,MAAM,QAAQ,MAAM,MAAM,MAAM,UAAU,OAAO;GAChE;GACA,OAAO,UAAU;IACf,IAAI,aAAa,QAAQ;IACzB,AAAK,OAAO,MAAM,KAAK;GACzB;GACA,SAAS,UAAU;IACjB,QAAQ,KAAK,KAAK;GACpB;EACF;CACF,CAAC;CAED,MAAM,iBAAiB,uBAAuB,KAAK,aAAa,gBAAgB;CAChF,OAAO,kBACL,kBACA,aAAa,aACb,aAAa,WACP,SAAS,0BAA0B,SACnC;EACJ,mBAAmB;EACnB,SAAS,0BAA0B;CACrC,GACA;EAAE,aAAa;EAAgB,mBAAmB,SAAS,UAAU;CAAE,CACzE;CACA,SAAS,oBAAoB;EAC3B,IAAI,gBAAgB,MAAM,QAAQ;CACpC;CAEA,MAAM,YAA8B;EAClC,WAAW,MAAM,QAAQ,YAAY;GACnC,MAAM,cAAc,MAAM,QAAQ,OAAO;EAC3C;EACA,QAAQ,MAAM,MAAM,YAAY;GAC9B,SAAS,OAAO;IAAE,QAAQ;IAAM;IAAM;GAAQ,CAAC;EACjD;CACF;CAEA,MAAM,UAA2B,CAAC;CAElC,SAAS,sBAAsB;EAC7B,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,OAAO,QAAQ,QAAW;GAC9B,UAAU,OAAO,GAAG;EACtB;CACF;CAEA,OAAO,OAAO,mBACZ,OAAO,IAAI,aAAa;EACtB,SAAS,oBAAoB;EAE7B,OAAO,OAAO,QAAQ,UAAU,MAAM,EAAE,KAAK,KAAK,OAAO,MAAM,GAAG,EAChE,aAAa,YACf,CAAC;EAED,OAAO,OAAO,MAAM,YAAY;EAEhC,KAAK,MAAM,QAAQ,YAEjB,IAAI,EAAC,OADmB,kBAAkB,IAAI,IAC/B;GACb,MAAM,QAAQ,OAAO,YAAY,IAAI;GACrC,OAAO,OAAO,SACZ,cAAc,KAAK,6BACjB,QACI,UAAU,MAAM,IAAI,IAAI,MAAM,QAAQ,KACtC,uCAER;EACF;EAGF,IAAI,CAAC,kBACH,OAAO,OAAO,WAAW;GACvB,IAAI;IACF,cAAc,QAAQ,GAAG;GAC3B,QAAQ,CAER;EACF,CAAC;EAGH,SAAS,MAAM;EAEf,MAAM,QAAQ;EAEd,IAAI,kBAAkB;GACpB,MAAM,MAAM,OAAO,MAAM;GACzB,MAAM,YAAY,QAAQ,EAAE,EAAE,aAAa;GAC3C,OAAO,OAAO,IAAI,EAAE;GACpB,OAAO,OAAO,IAAI,IAAI,OAAO,EAAE,CAAC;GAChC,OAAO,OAAO,IAAI,mBAAmB,aAAa,aAAa;GAC/D,OAAO,OAAO,IAAI,cAAc,SAAS,UAAU,SAAS,WAAW,SAAS,CAAC,GAAG;GACpF,OAAO,OAAO,IAAI,uBAAuB,QAAQ,OAAO,WAAW,SAAS,EAAE;GAC9E,OAAO,OAAO,IAAI,IAAI,OAAO,EAAE,CAAC;GAChC,OAAO,OAAO,IAAI,EAAE;GACpB,KAAK,MAAM,SAAS,SAClB,OAAO,OAAO,IAAI,cAAc,KAAK,CAAC;GAExC,OAAO,OAAO,IAAI,EAAE;GACpB,OAAO,OAAO,IAAI,IAAI,OAAO,EAAE,CAAC;GAChC,OAAO,OAAO,IAAI,yBAAyB,OAAO,SAAS;GAC3D,OAAO,OAAO,IAAI,IAAI,OAAO,EAAE,CAAC;GAChC,OAAO,OAAO,IAAI,EAAE;EACtB;CACF,CAAC,CACH;CAEA,MAAM,gBAAgB,QAAgB;EACpC,MAAM,eAAe,QAAQ,SAAS,aAAa,OAAO;EAC1D,OAAO,eAAe,KAAK,WAAW,YAAY,CAAC,CAAC,KAClD,OAAO,KAAK,WAAW,OAAO,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC,GAC9D,OAAO,SAAS,WACd,OAAO,eAAe,OAAO,QAAQ,MAAM,SAAS,OAAO,KAAK,KAAK,OAAO,MAAM,CAAC,CACrF,GACA,OAAO,UAAU,QACf,OAAO,WAAW;GAChB,UAAU,MAAM,KAAK,oBAAoB,OAAO,GAAG,KAAK,IAAI;GAC5D,UAAU,SAAS,KAAK,OAAO;EACjC,CAAC,CACH,GACA,OAAO,qBAEF;GACC,MAAM;GACN,KAAK;GACL,MAAM,OAAO;GACb,cAAc,OAAO;GACrB,aAAa,OAAO;EACtB,EACJ,CACF;CACF;CAEA,MAAM,cAAc,aAClB,OAAO,QAAQ,UAAU,cAAc,EAAE,aAAa,YAAY,CAAC;CAErE,MAAM,cAAc,KAAa,WAC/B,OAAO,aAAa,KAClB,OAAO,QAAQ,aAAa,GAC5B,OAAO,OAAO,QACZ,OAAO,WAAW;EAChB,UAAU,MACR,KACA,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACvE,IACF;CACF,CAAC,CACH,CACF;CAEF,MAAM,kBAAkB,gBAAgB,QAAQ,QAAQ,QAAQ,MAAM;CACtE,MAAM,eAAe,gBAAgB,QAAQ,QAAQ,QAAQ,MAAM;CAEnE,MAAM,eAAe,OAAO,IAAI,aAAa;EAC3C,MAAM,iBAAiB,OAAO,WAAW,eAAe;EAExD,OAAO,OAAO,QACZ,eAAe,KAAK,QAAQ,WAAW;GACrC;GACA,KAAK,gBAAgB,UAAU,OAAO;EACxC,EAAE,IACD,EAAE,QAAQ,UAAU,WAAW,KAAK,MAAM,GAC3C,EAAE,aAAa,YAAY,CAC7B;EAEA,MAAM,cAAc,OAAO,WAAW,YAAY;EAElD,OAAO,OAAO,QACZ,YAAY,KAAK,QAAQ,WAAW;GAClC;GACA,KAAK,aAAa,UAAU,OAAO;EACrC,EAAE,IACD,EAAE,QAAQ,UAAU,WAAW,KAAK,MAAM,GAC3C,EAAE,aAAa,YAAY,CAC7B;EAEA,OAAO;GAAE;GAAgB;EAAY;CACvC,CAAC;CAED,MAAM,UAAU,OAAO,OAAO,UAC5B,cACA,SAAS,MAAM,QAAQ,CAAC,CAAC,KAAK,OAAO,GAAG,IAAI,CAAC,CAC/C;CAEA,IAAI,YAAY,MAAM;CAItB,MAAM,YAAY,CAFE,GAAG,QAAQ,gBAAgB,GAAG,QAAQ,WAE/B,CAAC,CACzB,KAAK,WAAW,OAAO,OAAO,GAAG,CAAC,CAAC,CACnC,QAAQ,QAAQ,OAAO,SAAS,GAAG,KAAK,MAAM,KAAK,QAAQ,QAAQ,GAAG;CAEzE,IAAI,CAAC,oBAAoB,UAAU,SAAS,GAC1C,OAAO,OAAO,WAAW;EACvB,IAAI;GACF,gBAAgB,QAAQ,KAAK,SAAS;EACxC,QAAQ,CAER;CACF,CAAC;CAGH,OAAO,SAAS,MAAM,QAAQ;AAChC,CAAC;AAEH,MAAM,UACJ,WACA,cACA,UACA,eACA,cACA,aACG;CACH,IAAI,WAAsC;CAC1C,IAAI,cAAc;CAClB,IAAI,iBAAuD;CAE3D,MAAM,kBAAkB;EACtB,UAAU,YAAY;EACtB,QAAQ,IAAI,oBAAoB;EAChC,UAAU,cAAc;EACxB,QAAQ,KAAK,CAAC;CAChB;CAQA,MAAM,cAAc,QAAQ;CAC5B,MAAM,cAAc,kBAAkB;EACpC,IAAI,QAAQ,SAAS,aAAa;GAChC,QAAQ,MAAM,qEAAqE;GACnF,UAAU;EACZ;CACF,GAAG,GAAG;CACN,YAAY,QAAQ;CAEpB,MAAM,kCAAkC;EACtC;EACA,IAAI,cAAc,GAAG;GACnB,UAAU;GACV;EACF;EACA,UAAU,YAAY;EACtB,QAAQ,IAAI,0BAA0B;EACtC,iBAAiB,WAAW,WAAW,GAAI;EAC3C,eAAe,QAAQ;EACvB,UAAU,gBAAgB;CAC5B;CACA,MAAM,eAAe;CAErB,MAAM,UAAU,OAAO,OACrB,cAAc,WAAW,eAAe,oBAAoB;EAC1D,WAAW;EACX,gBAAgB,4BAA4B;EAC5C,gBAAgB,4BAA4B;GAC1C,IAAI,gBAAgB,aAAa,cAAc;GAC/C,iBAAiB,WAAW,WAAW,GAAI;GAC3C,eAAe,QAAQ;EACzB;EACA,gBAAgB,YAAY;CAC9B,CAAC,CACH,CAAC,CAAC,KACA,OAAO,QACL,MAAM,SACJ,yBAAyB,QAAQ,GACjC,qBAAqB,aAAa,GAClC,oBAAoB,YAAY,GAChC,aAAa,QAAQ,CACvB,CACF,GACA,OAAO,aAAa,WAClB,OAAO,SAAS,2CAA2C,MAAM,CAAC,CAAC,KACjE,OAAO,QAAQ,OAAO,IAAI,MAAM,CAAC,CACnC,CACF,CACF;CAEA,QAAQ,GAAG,UAAU,YAAY;CACjC,QAAQ,GAAG,WAAW,YAAY;CAElC,AAAK,OAAO,eAAe,OAAO,CAAC,CAChC,MAAM,SAAS;EACd,cAAc,WAAW;EACzB,IAAI,gBAAgB,aAAa,cAAc;EAC/C,QAAQ,KAAK,KAAK,UAAU,IAAI,IAAI,IAAI,CAAC;CAC3C,CAAC,CAAC,CACD,OAAO,UAAmB;EACzB,cAAc,WAAW;EACzB,IAAI,gBAAgB,aAAa,cAAc;EAC/C,QAAQ,MAAM,6CAA6C,KAAK;EAChE,QAAQ,KAAK,CAAC;CAChB,CAAC;AACL;AAEA,MAAa,SAAS;AAEtB,MAAa,WAAW"}
|
|
1
|
+
{"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Clock, DateTime, Deferred, Effect, Exit, Layer } from \"effect\";\nimport { probePortBindable } from \"./app\";\nimport {\n createDevRenderer,\n type DevProcessState,\n type DevRendererHandle,\n} from \"./components/dev-render\";\nimport { createLogPipeline, isDebug, type LogEvent, resolveLogLevel } from \"./dev-log-pipeline\";\nimport { createDevLogger, formatLogLine } from \"./dev-logs\";\nimport { getProcessEnv } from \"./env/process-env\";\nimport { ShellEnvLive } from \"./env/project-env\";\nimport { ownerOfPort } from \"./infra/port-ownership\";\nimport {\n getProcessStates,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { reapGroup } from \"./process-kill\";\nimport {\n isPidAlive,\n readRegistry,\n registerStandalone,\n unregisterPid,\n updateChildPids,\n writeRegistry,\n} from \"./process-registry\";\nimport {\n type AppOrchestrator,\n DevGeneratedEnvLive,\n DevRuntimeConfig,\n DevRuntimeConfigLive,\n isAuthMirrorPluginEntry,\n type ServiceDescriptor,\n ServiceDescriptorMap,\n ServiceDescriptorMapLive,\n} from \"./service-descriptor\";\nimport type { RuntimeConfig } from \"./types\";\n\nconst adoptOrphanedChildren = (configDir: string): void => {\n try {\n const entries = readRegistry().filter(\n (entry) => entry.pid > 1 && entry.configDir === configDir && !isPidAlive(entry.pid),\n );\n if (entries.length === 0) return;\n const reaped: number[] = [];\n for (const entry of entries) {\n for (const childPid of entry.childPids ?? []) {\n if (childPid === process.pid) continue;\n if (isPidAlive(childPid)) {\n reapGroup(childPid);\n reaped.push(childPid);\n }\n }\n }\n if (reaped.length > 0) {\n console.error(\n `[Dev] Reaped ${reaped.length} orphaned child process(es) from dead session(s): ${reaped.join(\", \")}`,\n );\n }\n const deadPids = new Set(entries.map((entry) => entry.pid));\n writeRegistry(readRegistry().filter((entry) => !deadPids.has(entry.pid)));\n } catch {\n // best-effort; registry hygiene is non-critical for the running session\n }\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui\", \"auth\", \"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\nexport interface DevSessionControls {\n requestShutdown: () => void;\n emergencyKill: () => void;\n requestShutdownEscalating: () => void;\n forceExit: () => void;\n restoreView: () => void;\n rearmForceExitTimer: () => void;\n}\n\nexport const runDevSession = (\n configDir: string,\n orchestrator: AppOrchestrator,\n onShutdownReady?: (controls: DevSessionControls) => void,\n) =>\n Effect.gen(function* () {\n adoptOrphanedChildren(configDir);\n const services = yield* ServiceDescriptorMap;\n const runtimeConfig = yield* DevRuntimeConfig;\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: DevProcessState[] = getProcessStates(\n orderedPackages,\n services,\n orchestrator.port,\n );\n\n if (isDebug(getProcessEnv(\"DEBUG\"))) {\n yield* Effect.logError(\n `[DEBUG session] orchestrator.packages: ${orchestrator.packages.join(\", \")}`,\n );\n yield* Effect.logError(`[DEBUG session] orderedPackages: ${orderedPackages.join(\", \")}`);\n yield* Effect.logError(`[DEBUG session] services keys: ${[...services.keys()].join(\", \")}`);\n yield* Effect.logError(\n `[DEBUG session] initialProcesses: ${initialProcesses.map((p) => `${p.name}=${p.source}`).join(\", \")}`,\n );\n }\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n const effectContext = yield* Effect.context();\n const controls: DevSessionControls = {\n requestShutdown: () => {\n void Effect.runPromiseWith(effectContext)(Deferred.succeed(shutdown, undefined));\n },\n emergencyKill: () => {},\n requestShutdownEscalating: () => {},\n forceExit: () => {},\n restoreView: () => {},\n rearmForceExitTimer: () => {},\n };\n\n onShutdownReady?.(controls);\n\n const isWorkspaceChild = getProcessEnv(\"BOS_WORKSPACE_CHILD\") === \"1\";\n let ownedPorts: number[] = [];\n if (!isWorkspaceChild) {\n const regPorts: Record<string, number> = {};\n const addPort = (key: string, value: number | undefined | null) => {\n if (value != null) regPorts[key] = value;\n };\n addPort(\"host\", runtimeConfig.host.port);\n addPort(\"api\", runtimeConfig.api.port);\n addPort(\"ui\", runtimeConfig.ui.port);\n addPort(\"auth\", runtimeConfig.auth?.port);\n if (runtimeConfig.plugins) {\n for (const [id, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (!isAuthMirrorPluginEntry(runtimeConfig.auth, id, plugin)) {\n addPort(`plugin:${id}`, plugin.port);\n }\n addPort(`plugin-ui:${id}`, plugin.ui?.port);\n }\n }\n registerStandalone({\n pid: process.pid,\n configDir,\n ports: regPorts,\n startedAt: yield* Clock.currentTimeMillis,\n description: orchestrator.description,\n });\n ownedPorts = Object.values(regPorts);\n }\n\n let view: DevRendererHandle | null = null;\n let shouldExportLogs = false;\n\n const logLevel = resolveLogLevel();\n const allLogs: LogEvent[] = [];\n const pipeline = createLogPipeline({\n level: logLevel,\n sinks: {\n display: (event) => {\n view?.addLog(event.source, event.line, event.level === \"error\");\n },\n file: (event) => {\n if (orchestrator.noLogs) return;\n void logger.write(event);\n },\n export: (event) => {\n allLogs.push(event);\n },\n },\n });\n\n const useInteractive = isInteractiveSupported() && orchestrator.interactive !== false;\n view = createDevRenderer(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => controls.requestShutdownEscalating(),\n () => {\n shouldExportLogs = true;\n controls.requestShutdownEscalating();\n },\n { interactive: useInteractive, onForceExit: () => controls.forceExit() },\n );\n controls.restoreView = () => {\n if (useInteractive) view?.unmount();\n };\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n // An error-status flip is the failure boundary — the failure message\n // logged just before it is still held in the pipeline's pending\n // multi-line buffer (events complete one line late). Drain it now so\n // the cause reaches the display tail and the log file even if the\n // process is SIGKILLed before the session finalizer can flush.\n if (status === \"error\") pipeline.flushSource(name);\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n pipeline.ingest({ source: name, line, isError });\n },\n };\n\n const spawned: ProcessHandle[] = [];\n\n controls.emergencyKill = () => {\n for (const handle of spawned) {\n if (handle.pid === undefined) continue;\n reapGroup(handle.pid);\n }\n };\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n controls.rearmForceExitTimer();\n\n yield* Effect.forEach(spawned, (h) => h.kill.pipe(Effect.ignore), {\n concurrency: \"unbounded\",\n });\n\n yield* Effect.sleep(\"200 millis\");\n\n for (const port of ownedPorts) {\n const bindable = yield* probePortBindable(port);\n if (!bindable) {\n const owner = yield* ownerOfPort(port);\n yield* Effect.logError(\n `[Dev] Port ${port} still bound after teardown${\n owner\n ? ` — pid ${owner.pid} (${owner.command})`\n : \" — owner unknown (lsof unavailable)\"\n }`,\n );\n }\n }\n\n if (!isWorkspaceChild) {\n yield* Effect.sync(() => {\n try {\n unregisterPid(process.pid);\n } catch {\n // best-effort; pruneDead cleans stale entries on next ps/kill\n }\n });\n }\n\n pipeline.flush();\n\n view?.unmount();\n\n if (shouldExportLogs) {\n const now = yield* Clock.currentTimeMillis;\n const startedMs = allLogs[0]?.timestamp || now;\n yield* Effect.log(\"\");\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(` SESSION LOGS: ${orchestrator.description}`);\n yield* Effect.log(` Started: ${DateTime.formatIso(DateTime.makeUnsafe(startedMs))}`);\n yield* Effect.log(` Filtered entries: ${allLogs.length} (level: ${logLevel})`);\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(\"\");\n for (const event of allLogs) {\n yield* Effect.log(formatLogLine(event));\n }\n yield* Effect.log(\"\");\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(` Full logs saved to: ${logger.logFile}`);\n yield* Effect.log(\"═\".repeat(70));\n yield* Effect.log(\"\");\n }\n }),\n );\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(pkg, callbacks, portOverride).pipe(\n Effect.tap((handle) => Effect.sync(() => spawned.push(handle))),\n Effect.flatMap((handle) =>\n Effect.acquireRelease(Effect.succeed(handle), () => handle.kill.pipe(Effect.ignore)),\n ),\n Effect.tapError((err) =>\n Effect.sync(() => {\n callbacks.onLog(pkg, `Failed to start: ${String(err)}`, true);\n callbacks.onStatus(pkg, \"error\");\n }),\n ),\n Effect.orElseSucceed(\n () =>\n ({\n name: pkg,\n pid: undefined,\n kill: Effect.void,\n waitForReady: Effect.void,\n waitForExit: Effect.never,\n }) satisfies ProcessHandle,\n ),\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n handle.waitForReady.pipe(\n Effect.timeout(\"120 seconds\"),\n Effect.catch((err) =>\n Effect.sync(() => {\n callbacks.onLog(\n pkg,\n `Timed out or failed: ${err instanceof Error ? err.message : String(err)}`,\n 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 startupPhase = Effect.gen(function* () {\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) => ({\n handle,\n pkg: hostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n return { nonHostHandles, hostHandles };\n });\n\n const startup = yield* Effect.raceFirst(\n startupPhase,\n Deferred.await(shutdown).pipe(Effect.as(null)),\n );\n\n if (startup === null) return;\n\n const allHandles = [...startup.nonHostHandles, ...startup.hostHandles];\n\n const childPids = allHandles\n .map((handle) => Number(handle.pid))\n .filter((pid) => Number.isFinite(pid) && pid > 1 && pid !== process.pid);\n\n if (!isWorkspaceChild && childPids.length > 0) {\n yield* Effect.sync(() => {\n try {\n updateChildPids(process.pid, childPids);\n } catch {\n // best-effort; registry hygiene is non-critical for the running session\n }\n });\n }\n\n yield* Deferred.await(shutdown);\n });\n\nconst runApp = (\n configDir: string,\n orchestrator: AppOrchestrator,\n services: Map<string, ServiceDescriptor>,\n runtimeConfig: RuntimeConfig,\n envGenerated: Record<string, string>,\n shellEnv: Record<string, string>,\n) => {\n let controls: DevSessionControls | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n controls?.restoreView();\n console.log(\"\\n[Dev] Force exit\");\n controls?.emergencyKill();\n process.exit(0);\n };\n\n // Orphan watch: when the wrapper chain above this process dies (playwright\n // tree-kills its webServer with SIGKILL — no signal handler runs, detached\n // group escapes), this orchestrator is reparented. Detect that and reap the\n // whole service tree — the alternatives are zombie port squatters that\n // poison the next run. Covers shell aborts (Ctrl+C killing only the\n // wrapper) the same way.\n const initialPpid = process.ppid;\n const orphanWatch = setInterval(() => {\n if (process.ppid !== initialPpid) {\n console.error(\"\\n[Dev] Parent process died — force exiting (orphaned service tree)\");\n forceExit();\n }\n }, 200);\n orphanWatch.unref?.();\n\n const requestShutdownEscalating = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n controls?.restoreView();\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 5000);\n forceExitTimer.unref?.();\n controls?.requestShutdown();\n };\n const handleSignal = requestShutdownEscalating;\n\n const program = Effect.scoped(\n runDevSession(configDir, orchestrator, (sessionControls) => {\n controls = sessionControls;\n sessionControls.requestShutdownEscalating = requestShutdownEscalating;\n sessionControls.rearmForceExitTimer = () => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n forceExitTimer = setTimeout(forceExit, 5000);\n forceExitTimer.unref?.();\n };\n sessionControls.forceExit = forceExit;\n }),\n ).pipe(\n Effect.provide(\n Layer.mergeAll(\n ServiceDescriptorMapLive(services),\n DevRuntimeConfigLive(runtimeConfig),\n DevGeneratedEnvLive(envGenerated),\n ShellEnvLive(shellEnv),\n ),\n ),\n Effect.catchDefect((defect) =>\n Effect.logError(\"[Dev] Unhandled defect in orchestrator:\", defect).pipe(\n Effect.andThen(Effect.die(defect)),\n ),\n ),\n );\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n void Effect.runPromiseExit(program)\n .then((exit) => {\n clearInterval(orphanWatch);\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 1);\n })\n .catch((error: unknown) => {\n clearInterval(orphanWatch);\n if (forceExitTimer) clearTimeout(forceExitTimer);\n console.error(\"[Dev] Failed to observe application exit:\", error);\n process.exit(1);\n });\n};\n\nexport const devApp = runApp;\n\nexport const startApp = runApp;\n"],"mappings":"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"}
|
package/dist/fastkv.cjs
CHANGED
|
@@ -53,10 +53,26 @@ function parseBosUrl(bosUrl) {
|
|
|
53
53
|
async function fetchBosConfigFromFastKv(bosUrl, registry) {
|
|
54
54
|
const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);
|
|
55
55
|
const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
return parseFastKvConfigValue(await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`), bosUrl);
|
|
57
|
+
}
|
|
58
|
+
/** Read a published config from an already-built registry URL
|
|
59
|
+
* (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}
|
|
60
|
+
* this takes the URL directly, so the read key is exactly the one the publish
|
|
61
|
+
* writes, and a network failure is reported as such instead of collapsing
|
|
62
|
+
* into "No config found" (fetchJsonOrNull swallows errors into null). */
|
|
63
|
+
async function fetchPublishedConfigFromFastKv(url) {
|
|
64
|
+
const response = await require_http_client.fetchRaw(url, { headers: { accept: "application/json" } });
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
if (response.status === 404) throw new Error(`No config found for ${url}`);
|
|
67
|
+
throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
68
|
+
}
|
|
69
|
+
return parseFastKvConfigValue(await response.json(), url);
|
|
70
|
+
}
|
|
71
|
+
function parseFastKvConfigValue(payload, label) {
|
|
72
|
+
const value = payload?.entries?.find(Boolean)?.value;
|
|
73
|
+
if (!value) throw new Error(`No config found for ${label}`);
|
|
58
74
|
if (typeof value === "string") return JSON.parse(value);
|
|
59
|
-
if (typeof value !== "object") throw new Error(`Invalid config value for ${
|
|
75
|
+
if (typeof value !== "object") throw new Error(`Invalid config value for ${label}`);
|
|
60
76
|
return value;
|
|
61
77
|
}
|
|
62
78
|
const FASTKV_HISTORY_MAX_LIMIT = 200;
|
|
@@ -122,9 +138,11 @@ exports.buildRegistryConfigUrlForNetwork = buildRegistryConfigUrlForNetwork;
|
|
|
122
138
|
exports.fetchBosConfigFromFastKv = fetchBosConfigFromFastKv;
|
|
123
139
|
exports.fetchConfigHistory = fetchConfigHistory;
|
|
124
140
|
exports.fetchDeployManifests = fetchDeployManifests;
|
|
141
|
+
exports.fetchPublishedConfigFromFastKv = fetchPublishedConfigFromFastKv;
|
|
125
142
|
exports.fetchRemotePluginManifest = fetchRemotePluginManifest;
|
|
126
143
|
exports.getFastKvBaseUrlForNetwork = getFastKvBaseUrlForNetwork;
|
|
127
144
|
exports.getRegistryNamespaceForAccount = getRegistryNamespaceForAccount;
|
|
128
145
|
exports.getRegistryNamespaceForNetwork = getRegistryNamespaceForNetwork;
|
|
129
146
|
exports.parseBosUrl = parseBosUrl;
|
|
147
|
+
exports.parseFastKvConfigValue = parseFastKvConfigValue;
|
|
130
148
|
//# sourceMappingURL=fastkv.cjs.map
|
package/dist/fastkv.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastkv.cjs","names":["fetchJsonOrNull"],"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\nimport { fetchJsonOrNull } from \"./http-client\";\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nconst FASTKV_TESTNET_URL = \"https://kv.test.fastnear.com\";\nconst FASTKV_MAINNET_URL = \"https://kv.main.fastnear.com\";\nconst REGISTRY_NAMESPACE_TESTNET = \"dev.allthethings.testnet\";\nconst REGISTRY_NAMESPACE_MAINNET = \"dev.everything.near\";\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\" ? FASTKV_TESTNET_URL : FASTKV_MAINNET_URL;\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(\n accountId: string,\n gatewayId: string,\n registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId, registry);\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 registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network, registry);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string, registry?: string): string {\n return registry ?? getRegistryNamespaceForNetwork(getNetworkIdForAccount(accountId));\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId, registry?: string): string {\n if (registry) return registry;\n return network === \"testnet\" ? REGISTRY_NAMESPACE_TESTNET : REGISTRY_NAMESPACE_MAINNET;\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\nexport function parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const strippedUrl = bosUrl.split(\"#\")[0]!;\n const match = strippedUrl.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, registry?: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`,\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 ConfigHistoryEntry {\n blockHeight: number;\n blockTimestampNs: string;\n txHash?: string;\n value: unknown;\n}\n\ninterface FastKvHistoryEntry {\n block_height?: number;\n block_timestamp?: string | number;\n tx_hash?: string;\n value?: unknown;\n}\n\ninterface FastKvHistoryResponse {\n entries?: Array<FastKvHistoryEntry | null>;\n}\n\nconst FASTKV_HISTORY_MAX_LIMIT = 200;\n\n/**\n * Publish history for the registry config key (atomic-deploys 11): the\n * exact-key variant of FastData's history endpoint, newest-first. The API\n * caps a page at 200 rows; a config key's write history fits well inside\n * that, so a single request is the whole story (the limit param is clamped).\n */\nexport async function fetchConfigHistory(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<ConfigHistoryEntry[]> {\n const key = encodeURIComponent(getRegistryConfigKey(opts.accountId, opts.gatewayId));\n const limit = Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT));\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}/${key}?limit=${limit}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url);\n const entries = (payload?.entries ?? []).filter(Boolean) as FastKvHistoryEntry[];\n\n return entries\n .map((entry) => ({\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n ...(entry.tx_hash ? { txHash: entry.tx_hash } : {}),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\n}\n\nexport interface DeployManifestEntry {\n key: string;\n blockHeight: number;\n blockTimestampNs: string;\n value: unknown;\n}\n\n/**\n * The per-deploy audit trail (atomic-deploys 03/12): every publish writes\n * `apps/<account>/<gateway>/manifests/<ts>.json` atomically with the pointer\n * swap — this lists that key family newest-first via the history-by-prefix\n * endpoint.\n */\nexport async function fetchDeployManifests(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<DeployManifestEntry[]> {\n const keyPrefix = encodeURIComponent(\n `${getRegistryConfigKey(opts.accountId, opts.gatewayId).replace(/bos\\.config\\.json$/, \"\")}manifests/`,\n );\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url, {\n method: \"POST\",\n body: JSON.stringify({\n key_prefix: decodeURIComponent(keyPrefix),\n asc: false,\n limit: Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)),\n }),\n });\n const entries = (payload?.entries ?? []).filter(Boolean) as Array<\n FastKvHistoryEntry & { key?: string }\n >;\n\n return entries\n .map((entry) => ({\n key: entry.key ?? \"\",\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\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 async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n return fetchJsonOrNull<PluginManifest>(`${baseUrl}/plugin.manifest.json`, { retries: 2 });\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers as Record<string, string> | undefined),\n };\n return fetchJsonOrNull<T>(url, {\n method: init?.method,\n headers,\n body: init?.body ?? undefined,\n retries: 3,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fastkv.cjs","names":["fetchRaw","fetchJsonOrNull"],"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\nimport { fetchJsonOrNull, fetchRaw } from \"./http-client\";\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nconst FASTKV_TESTNET_URL = \"https://kv.test.fastnear.com\";\nconst FASTKV_MAINNET_URL = \"https://kv.main.fastnear.com\";\nconst REGISTRY_NAMESPACE_TESTNET = \"dev.allthethings.testnet\";\nconst REGISTRY_NAMESPACE_MAINNET = \"dev.everything.near\";\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\" ? FASTKV_TESTNET_URL : FASTKV_MAINNET_URL;\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(\n accountId: string,\n gatewayId: string,\n registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId, registry);\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 registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network, registry);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string, registry?: string): string {\n return registry ?? getRegistryNamespaceForNetwork(getNetworkIdForAccount(accountId));\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId, registry?: string): string {\n if (registry) return registry;\n return network === \"testnet\" ? REGISTRY_NAMESPACE_TESTNET : REGISTRY_NAMESPACE_MAINNET;\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\nexport function parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const strippedUrl = bosUrl.split(\"#\")[0]!;\n const match = strippedUrl.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, registry?: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`,\n );\n return parseFastKvConfigValue<T>(payload, bosUrl);\n}\n\n/** Read a published config from an already-built registry URL\n * (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}\n * this takes the URL directly, so the read key is exactly the one the publish\n * writes, and a network failure is reported as such instead of collapsing\n * into \"No config found\" (fetchJsonOrNull swallows errors into null). */\nexport async function fetchPublishedConfigFromFastKv<T>(url: string): Promise<T> {\n const response = await fetchRaw(url, { headers: { accept: \"application/json\" } });\n if (!response.ok) {\n if (response.status === 404) {\n throw new Error(`No config found for ${url}`);\n }\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n const payload = (await response.json()) as FastKvListResponse | null;\n return parseFastKvConfigValue<T>(payload, url);\n}\n\nexport function parseFastKvConfigValue<T>(payload: FastKvListResponse | null, label: string): T {\n const value = payload?.entries?.find(Boolean)?.value;\n if (!value) {\n throw new Error(`No config found for ${label}`);\n }\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${label}`);\n }\n return value as T;\n}\n\nexport interface ConfigHistoryEntry {\n blockHeight: number;\n blockTimestampNs: string;\n txHash?: string;\n value: unknown;\n}\n\ninterface FastKvHistoryEntry {\n block_height?: number;\n block_timestamp?: string | number;\n tx_hash?: string;\n value?: unknown;\n}\n\ninterface FastKvHistoryResponse {\n entries?: Array<FastKvHistoryEntry | null>;\n}\n\nconst FASTKV_HISTORY_MAX_LIMIT = 200;\n\n/**\n * Publish history for the registry config key (atomic-deploys 11): the\n * exact-key variant of FastData's history endpoint, newest-first. The API\n * caps a page at 200 rows; a config key's write history fits well inside\n * that, so a single request is the whole story (the limit param is clamped).\n */\nexport async function fetchConfigHistory(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<ConfigHistoryEntry[]> {\n const key = encodeURIComponent(getRegistryConfigKey(opts.accountId, opts.gatewayId));\n const limit = Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT));\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}/${key}?limit=${limit}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url);\n const entries = (payload?.entries ?? []).filter(Boolean) as FastKvHistoryEntry[];\n\n return entries\n .map((entry) => ({\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n ...(entry.tx_hash ? { txHash: entry.tx_hash } : {}),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\n}\n\nexport interface DeployManifestEntry {\n key: string;\n blockHeight: number;\n blockTimestampNs: string;\n value: unknown;\n}\n\n/**\n * The per-deploy audit trail (atomic-deploys 03/12): every publish writes\n * `apps/<account>/<gateway>/manifests/<ts>.json` atomically with the pointer\n * swap — this lists that key family newest-first via the history-by-prefix\n * endpoint.\n */\nexport async function fetchDeployManifests(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<DeployManifestEntry[]> {\n const keyPrefix = encodeURIComponent(\n `${getRegistryConfigKey(opts.accountId, opts.gatewayId).replace(/bos\\.config\\.json$/, \"\")}manifests/`,\n );\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url, {\n method: \"POST\",\n body: JSON.stringify({\n key_prefix: decodeURIComponent(keyPrefix),\n asc: false,\n limit: Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)),\n }),\n });\n const entries = (payload?.entries ?? []).filter(Boolean) as Array<\n FastKvHistoryEntry & { key?: string }\n >;\n\n return entries\n .map((entry) => ({\n key: entry.key ?? \"\",\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\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 async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n return fetchJsonOrNull<PluginManifest>(`${baseUrl}/plugin.manifest.json`, { retries: 2 });\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers as Record<string, string> | undefined),\n };\n return fetchJsonOrNull<T>(url, {\n method: init?.method,\n headers,\n body: init?.body ?? undefined,\n retries: 3,\n });\n}\n"],"mappings":";;;;AAYA,SAAS,uBAAuB,WAA8B;CAC5D,OAAO,UAAU,SAAS,UAAU,IAAI,YAAY;AACtD;AAEA,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AAEnC,SAAgB,2BAA2B,SAA4B;CACrE,OAAO,YAAY,YAAY,qBAAqB;AACtD;AAEA,SAAS,2BAA2B,WAA2B;CAC7D,OAAO,uBAAuB,SAAS,MAAM,YACzC,2BAA2B,SAAS,IACpC,2BAA2B,SAAS;AAC1C;AAEA,SAAgB,uBACd,WACA,WACA,UACQ;CACR,MAAM,UAAU,2BAA2B,SAAS;CACpD,MAAM,YAAY,+BAA+B,WAAW,QAAQ;CACpE,MAAM,MAAM,mBAAmB,qBAAqB,WAAW,SAAS,CAAC;CACzE,OAAO,GAAG,QAAQ,aAAa,mBAAmB,SAAS,EAAE,GAAG,mBAAmB,SAAS,EAAE,GAAG;AACnG;AAEA,SAAgB,iCACd,SACA,WACA,WACA,UACQ;CACR,MAAM,UAAU,2BAA2B,OAAO;CAClD,MAAM,YAAY,+BAA+B,SAAS,QAAQ;CAClE,MAAM,MAAM,mBAAmB,qBAAqB,WAAW,SAAS,CAAC;CACzE,OAAO,GAAG,QAAQ,aAAa,mBAAmB,SAAS,EAAE,GAAG,mBAAmB,SAAS,EAAE,GAAG;AACnG;AAEA,SAAgB,+BAA+B,WAAmB,UAA2B;CAC3F,OAAO,YAAY,+BAA+B,uBAAuB,SAAS,CAAC;AACrF;AAEA,SAAgB,+BAA+B,SAAoB,UAA2B;CAC5F,IAAI,UAAU,OAAO;CACrB,OAAO,YAAY,YAAY,6BAA6B;AAC9D;AAEA,SAAS,qBACP,WACA,WACA,eAAyB,CAAC,GAClB;CAKR,OAAO,QAAQ,UAAU,GAAG,YAH1B,aAAa,SAAS,IAClB,IAAI,aAAa,KAAK,YAAY,mBAAmB,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,MACvE,GACyC;AACjD;AAEA,SAAgB,YAAY,QAI1B;CAEA,MAAM,QADc,OAAO,MAAM,GAAG,CAAC,CAAC,EACb,CAAC,MAAM,yBAAyB;CACzD,IAAI,CAAC,QAAQ,MAAM,CAAC,MAAM,IACxB,MAAM,IAAI,MAAM,oBAAoB,QAAQ;CAG9C,MAAM,eAAe,MAAM,EAAE,CAC1B,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,KAAK,YAAY,mBAAmB,OAAO,CAAC;CAC/C,IAAI,aAAa,WAAW,GAC1B,MAAM,IAAI,MAAM,oBAAoB,QAAQ;CAG9C,MAAM,CAAC,WAAW,GAAG,oBAAoB;CACzC,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,oBAAoB,QAAQ;CAG9C,OAAO;EACL,WAAW,MAAM;EACjB;EACA,cAAc;CAChB;AACF;AAEA,eAAsB,yBAA4B,QAAgB,UAA+B;CAC/F,MAAM,EAAE,WAAW,WAAW,iBAAiB,YAAY,MAAM;CACjE,MAAM,MAAM,mBAAmB,qBAAqB,WAAW,WAAW,YAAY,CAAC;CAIvF,OAAO,uBAA0B,MAHX,UACpB,GAAG,2BAA2B,SAAS,EAAE,aAAa,mBAAmB,+BAA+B,WAAW,QAAQ,CAAC,EAAE,GAAG,mBAAmB,SAAS,EAAE,GAAG,KACpK,GAC0C,MAAM;AAClD;;;;;;AAOA,eAAsB,+BAAkC,KAAyB;CAC/E,MAAM,WAAW,MAAMA,6BAAS,KAAK,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;CAChF,IAAI,CAAC,SAAS,IAAI;EAChB,IAAI,SAAS,WAAW,KACtB,MAAM,IAAI,MAAM,uBAAuB,KAAK;EAE9C,MAAM,IAAI,MAAM,QAAQ,SAAS,OAAO,YAAY,KAAK;CAC3D;CAEA,OAAO,uBAA0B,MADV,SAAS,KAAK,GACK,GAAG;AAC/C;AAEA,SAAgB,uBAA0B,SAAoC,OAAkB;CAC9F,MAAM,QAAQ,SAAS,SAAS,KAAK,OAAO,CAAC,EAAE;CAC/C,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,uBAAuB,OAAO;CAEhD,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK,MAAM,KAAK;CAEzB,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,MAAM,4BAA4B,OAAO;CAErD,OAAO;AACT;AAoBA,MAAM,2BAA2B;;;;;;;AAQjC,eAAsB,mBAAmB,MAKP;CAChC,MAAM,MAAM,mBAAmB,qBAAqB,KAAK,WAAW,KAAK,SAAS,CAAC;CACnF,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,IAAI,wBAAwB,CAAC;CAK9E,SAFiB,MADK,UAAiC,GADxC,2BAA2B,KAAK,SAAS,EAAE,cAAc,mBAAmB,+BAA+B,KAAK,WAAW,KAAK,QAAQ,CAAC,EAAE,GAAG,mBAAmB,KAAK,SAAS,EAAE,GAAG,IAAI,SAAS,OACtJ,EAClC,EAAE,WAAW,CAAC,EAAC,CAAE,OAAO,OAEnC,CAAC,CACX,KAAK,WAAW;EACf,aAAa,MAAM,gBAAgB;EACnC,kBAAkB,OAAO,MAAM,mBAAmB,EAAE;EACpD,GAAI,MAAM,UAAU,EAAE,QAAQ,MAAM,QAAQ,IAAI,CAAC;EACjD,OACE,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,SAAS,IACnD,KAAK,MAAM,MAAM,KAAK,IACvB,MAAM;CACd,EAAE,CAAC,CACF,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW;AACjD;;;;;;;AAeA,eAAsB,qBAAqB,MAKR;CACjC,MAAM,YAAY,mBAChB,GAAG,qBAAqB,KAAK,WAAW,KAAK,SAAS,CAAC,CAAC,QAAQ,sBAAsB,EAAE,EAAE,WAC5F;CAcA,SAJiB,MARK,UAAiC,GADxC,2BAA2B,KAAK,SAAS,EAAE,cAAc,mBAAmB,+BAA+B,KAAK,WAAW,KAAK,QAAQ,CAAC,EAAE,GAAG,mBAAmB,KAAK,SAAS,KAClI;EAC1D,QAAQ;EACR,MAAM,KAAK,UAAU;GACnB,YAAY,mBAAmB,SAAS;GACxC,KAAK;GACL,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,IAAI,wBAAwB,CAAC;EACzE,CAAC;CACH,CAAC,EACuB,EAAE,WAAW,CAAC,EAAC,CAAE,OAAO,OAInC,CAAC,CACX,KAAK,WAAW;EACf,KAAK,MAAM,OAAO;EAClB,aAAa,MAAM,gBAAgB;EACnC,kBAAkB,OAAO,MAAM,mBAAmB,EAAE;EACpD,OACE,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,SAAS,IACnD,KAAK,MAAM,MAAM,KAAK,IACvB,MAAM;CACd,EAAE,CAAC,CACF,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW;AACjD;AAcA,eAAsB,0BAA0B,QAAgD;CAC9F,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;CACxC,OAAOC,oCAAgC,GAAG,QAAQ,wBAAwB,EAAE,SAAS,EAAE,CAAC;AAC1F;AAEA,eAAe,UAAa,KAAa,MAAuC;CAC9E,MAAM,UAAkC;EACtC,QAAQ;EACR,gBAAgB;EAChB,GAAI,MAAM;CACZ;CACA,OAAOA,oCAAmB,KAAK;EAC7B,QAAQ,MAAM;EACd;EACA,MAAM,MAAM,QAAQ;EACpB,SAAS;CACX,CAAC;AACH"}
|
package/dist/fastkv.d.cts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
//#region src/fastkv.d.ts
|
|
2
2
|
type NetworkId = "mainnet" | "testnet";
|
|
3
|
+
interface FastKvEntry {
|
|
4
|
+
value: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface FastKvListResponse {
|
|
7
|
+
entries?: Array<FastKvEntry | null>;
|
|
8
|
+
}
|
|
3
9
|
declare function getFastKvBaseUrlForNetwork(network: NetworkId): string;
|
|
4
10
|
declare function buildRegistryConfigUrl(accountId: string, gatewayId: string, registry?: string): string;
|
|
5
11
|
declare function buildRegistryConfigUrlForNetwork(network: NetworkId, accountId: string, gatewayId: string, registry?: string): string;
|
|
@@ -11,6 +17,13 @@ declare function parseBosUrl(bosUrl: string): {
|
|
|
11
17
|
pathSegments: string[];
|
|
12
18
|
};
|
|
13
19
|
declare function fetchBosConfigFromFastKv<T>(bosUrl: string, registry?: string): Promise<T>;
|
|
20
|
+
/** Read a published config from an already-built registry URL
|
|
21
|
+
* (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}
|
|
22
|
+
* this takes the URL directly, so the read key is exactly the one the publish
|
|
23
|
+
* writes, and a network failure is reported as such instead of collapsing
|
|
24
|
+
* into "No config found" (fetchJsonOrNull swallows errors into null). */
|
|
25
|
+
declare function fetchPublishedConfigFromFastKv<T>(url: string): Promise<T>;
|
|
26
|
+
declare function parseFastKvConfigValue<T>(payload: FastKvListResponse | null, label: string): T;
|
|
14
27
|
interface ConfigHistoryEntry {
|
|
15
28
|
blockHeight: number;
|
|
16
29
|
blockTimestampNs: string;
|
|
@@ -74,5 +87,5 @@ interface PluginManifest {
|
|
|
74
87
|
}
|
|
75
88
|
declare function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null>;
|
|
76
89
|
//#endregion
|
|
77
|
-
export { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl };
|
|
90
|
+
export { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchPublishedConfigFromFastKv, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl, parseFastKvConfigValue };
|
|
78
91
|
//# sourceMappingURL=fastkv.d.cts.map
|
package/dist/fastkv.d.mts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
//#region src/fastkv.d.ts
|
|
2
2
|
type NetworkId = "mainnet" | "testnet";
|
|
3
|
+
interface FastKvEntry {
|
|
4
|
+
value: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface FastKvListResponse {
|
|
7
|
+
entries?: Array<FastKvEntry | null>;
|
|
8
|
+
}
|
|
3
9
|
declare function getFastKvBaseUrlForNetwork(network: NetworkId): string;
|
|
4
10
|
declare function buildRegistryConfigUrl(accountId: string, gatewayId: string, registry?: string): string;
|
|
5
11
|
declare function buildRegistryConfigUrlForNetwork(network: NetworkId, accountId: string, gatewayId: string, registry?: string): string;
|
|
@@ -11,6 +17,13 @@ declare function parseBosUrl(bosUrl: string): {
|
|
|
11
17
|
pathSegments: string[];
|
|
12
18
|
};
|
|
13
19
|
declare function fetchBosConfigFromFastKv<T>(bosUrl: string, registry?: string): Promise<T>;
|
|
20
|
+
/** Read a published config from an already-built registry URL
|
|
21
|
+
* (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}
|
|
22
|
+
* this takes the URL directly, so the read key is exactly the one the publish
|
|
23
|
+
* writes, and a network failure is reported as such instead of collapsing
|
|
24
|
+
* into "No config found" (fetchJsonOrNull swallows errors into null). */
|
|
25
|
+
declare function fetchPublishedConfigFromFastKv<T>(url: string): Promise<T>;
|
|
26
|
+
declare function parseFastKvConfigValue<T>(payload: FastKvListResponse | null, label: string): T;
|
|
14
27
|
interface ConfigHistoryEntry {
|
|
15
28
|
blockHeight: number;
|
|
16
29
|
blockTimestampNs: string;
|
|
@@ -74,5 +87,5 @@ interface PluginManifest {
|
|
|
74
87
|
}
|
|
75
88
|
declare function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null>;
|
|
76
89
|
//#endregion
|
|
77
|
-
export { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl };
|
|
90
|
+
export { ConfigHistoryEntry, DeployManifestEntry, NetworkId, PluginManifest, buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchPublishedConfigFromFastKv, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl, parseFastKvConfigValue };
|
|
78
91
|
//# sourceMappingURL=fastkv.d.mts.map
|
package/dist/fastkv.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchJsonOrNull } from "./http-client.mjs";
|
|
1
|
+
import { fetchJsonOrNull, fetchRaw } from "./http-client.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/fastkv.ts
|
|
4
4
|
function getNetworkIdForAccount(accountId) {
|
|
@@ -52,10 +52,26 @@ function parseBosUrl(bosUrl) {
|
|
|
52
52
|
async function fetchBosConfigFromFastKv(bosUrl, registry) {
|
|
53
53
|
const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);
|
|
54
54
|
const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
return parseFastKvConfigValue(await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`), bosUrl);
|
|
56
|
+
}
|
|
57
|
+
/** Read a published config from an already-built registry URL
|
|
58
|
+
* (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}
|
|
59
|
+
* this takes the URL directly, so the read key is exactly the one the publish
|
|
60
|
+
* writes, and a network failure is reported as such instead of collapsing
|
|
61
|
+
* into "No config found" (fetchJsonOrNull swallows errors into null). */
|
|
62
|
+
async function fetchPublishedConfigFromFastKv(url) {
|
|
63
|
+
const response = await fetchRaw(url, { headers: { accept: "application/json" } });
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
if (response.status === 404) throw new Error(`No config found for ${url}`);
|
|
66
|
+
throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
67
|
+
}
|
|
68
|
+
return parseFastKvConfigValue(await response.json(), url);
|
|
69
|
+
}
|
|
70
|
+
function parseFastKvConfigValue(payload, label) {
|
|
71
|
+
const value = payload?.entries?.find(Boolean)?.value;
|
|
72
|
+
if (!value) throw new Error(`No config found for ${label}`);
|
|
57
73
|
if (typeof value === "string") return JSON.parse(value);
|
|
58
|
-
if (typeof value !== "object") throw new Error(`Invalid config value for ${
|
|
74
|
+
if (typeof value !== "object") throw new Error(`Invalid config value for ${label}`);
|
|
59
75
|
return value;
|
|
60
76
|
}
|
|
61
77
|
const FASTKV_HISTORY_MAX_LIMIT = 200;
|
|
@@ -116,5 +132,5 @@ async function fetchJson(url, init) {
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
//#endregion
|
|
119
|
-
export { buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl };
|
|
135
|
+
export { buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests, fetchPublishedConfigFromFastKv, fetchRemotePluginManifest, getFastKvBaseUrlForNetwork, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork, parseBosUrl, parseFastKvConfigValue };
|
|
120
136
|
//# sourceMappingURL=fastkv.mjs.map
|
package/dist/fastkv.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastkv.mjs","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\nimport { fetchJsonOrNull } from \"./http-client\";\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nconst FASTKV_TESTNET_URL = \"https://kv.test.fastnear.com\";\nconst FASTKV_MAINNET_URL = \"https://kv.main.fastnear.com\";\nconst REGISTRY_NAMESPACE_TESTNET = \"dev.allthethings.testnet\";\nconst REGISTRY_NAMESPACE_MAINNET = \"dev.everything.near\";\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\" ? FASTKV_TESTNET_URL : FASTKV_MAINNET_URL;\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(\n accountId: string,\n gatewayId: string,\n registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId, registry);\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 registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network, registry);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string, registry?: string): string {\n return registry ?? getRegistryNamespaceForNetwork(getNetworkIdForAccount(accountId));\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId, registry?: string): string {\n if (registry) return registry;\n return network === \"testnet\" ? REGISTRY_NAMESPACE_TESTNET : REGISTRY_NAMESPACE_MAINNET;\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\nexport function parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const strippedUrl = bosUrl.split(\"#\")[0]!;\n const match = strippedUrl.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, registry?: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`,\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 ConfigHistoryEntry {\n blockHeight: number;\n blockTimestampNs: string;\n txHash?: string;\n value: unknown;\n}\n\ninterface FastKvHistoryEntry {\n block_height?: number;\n block_timestamp?: string | number;\n tx_hash?: string;\n value?: unknown;\n}\n\ninterface FastKvHistoryResponse {\n entries?: Array<FastKvHistoryEntry | null>;\n}\n\nconst FASTKV_HISTORY_MAX_LIMIT = 200;\n\n/**\n * Publish history for the registry config key (atomic-deploys 11): the\n * exact-key variant of FastData's history endpoint, newest-first. The API\n * caps a page at 200 rows; a config key's write history fits well inside\n * that, so a single request is the whole story (the limit param is clamped).\n */\nexport async function fetchConfigHistory(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<ConfigHistoryEntry[]> {\n const key = encodeURIComponent(getRegistryConfigKey(opts.accountId, opts.gatewayId));\n const limit = Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT));\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}/${key}?limit=${limit}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url);\n const entries = (payload?.entries ?? []).filter(Boolean) as FastKvHistoryEntry[];\n\n return entries\n .map((entry) => ({\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n ...(entry.tx_hash ? { txHash: entry.tx_hash } : {}),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\n}\n\nexport interface DeployManifestEntry {\n key: string;\n blockHeight: number;\n blockTimestampNs: string;\n value: unknown;\n}\n\n/**\n * The per-deploy audit trail (atomic-deploys 03/12): every publish writes\n * `apps/<account>/<gateway>/manifests/<ts>.json` atomically with the pointer\n * swap — this lists that key family newest-first via the history-by-prefix\n * endpoint.\n */\nexport async function fetchDeployManifests(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<DeployManifestEntry[]> {\n const keyPrefix = encodeURIComponent(\n `${getRegistryConfigKey(opts.accountId, opts.gatewayId).replace(/bos\\.config\\.json$/, \"\")}manifests/`,\n );\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url, {\n method: \"POST\",\n body: JSON.stringify({\n key_prefix: decodeURIComponent(keyPrefix),\n asc: false,\n limit: Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)),\n }),\n });\n const entries = (payload?.entries ?? []).filter(Boolean) as Array<\n FastKvHistoryEntry & { key?: string }\n >;\n\n return entries\n .map((entry) => ({\n key: entry.key ?? \"\",\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\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 async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n return fetchJsonOrNull<PluginManifest>(`${baseUrl}/plugin.manifest.json`, { retries: 2 });\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers as Record<string, string> | undefined),\n };\n return fetchJsonOrNull<T>(url, {\n method: init?.method,\n headers,\n body: init?.body ?? undefined,\n retries: 3,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fastkv.mjs","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\nimport { fetchJsonOrNull, fetchRaw } from \"./http-client\";\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nconst FASTKV_TESTNET_URL = \"https://kv.test.fastnear.com\";\nconst FASTKV_MAINNET_URL = \"https://kv.main.fastnear.com\";\nconst REGISTRY_NAMESPACE_TESTNET = \"dev.allthethings.testnet\";\nconst REGISTRY_NAMESPACE_MAINNET = \"dev.everything.near\";\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\" ? FASTKV_TESTNET_URL : FASTKV_MAINNET_URL;\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(\n accountId: string,\n gatewayId: string,\n registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId, registry);\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 registry?: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network, registry);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string, registry?: string): string {\n return registry ?? getRegistryNamespaceForNetwork(getNetworkIdForAccount(accountId));\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId, registry?: string): string {\n if (registry) return registry;\n return network === \"testnet\" ? REGISTRY_NAMESPACE_TESTNET : REGISTRY_NAMESPACE_MAINNET;\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\nexport function parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const strippedUrl = bosUrl.split(\"#\")[0]!;\n const match = strippedUrl.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, registry?: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments));\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`,\n );\n return parseFastKvConfigValue<T>(payload, bosUrl);\n}\n\n/** Read a published config from an already-built registry URL\n * (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv}\n * this takes the URL directly, so the read key is exactly the one the publish\n * writes, and a network failure is reported as such instead of collapsing\n * into \"No config found\" (fetchJsonOrNull swallows errors into null). */\nexport async function fetchPublishedConfigFromFastKv<T>(url: string): Promise<T> {\n const response = await fetchRaw(url, { headers: { accept: \"application/json\" } });\n if (!response.ok) {\n if (response.status === 404) {\n throw new Error(`No config found for ${url}`);\n }\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n const payload = (await response.json()) as FastKvListResponse | null;\n return parseFastKvConfigValue<T>(payload, url);\n}\n\nexport function parseFastKvConfigValue<T>(payload: FastKvListResponse | null, label: string): T {\n const value = payload?.entries?.find(Boolean)?.value;\n if (!value) {\n throw new Error(`No config found for ${label}`);\n }\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${label}`);\n }\n return value as T;\n}\n\nexport interface ConfigHistoryEntry {\n blockHeight: number;\n blockTimestampNs: string;\n txHash?: string;\n value: unknown;\n}\n\ninterface FastKvHistoryEntry {\n block_height?: number;\n block_timestamp?: string | number;\n tx_hash?: string;\n value?: unknown;\n}\n\ninterface FastKvHistoryResponse {\n entries?: Array<FastKvHistoryEntry | null>;\n}\n\nconst FASTKV_HISTORY_MAX_LIMIT = 200;\n\n/**\n * Publish history for the registry config key (atomic-deploys 11): the\n * exact-key variant of FastData's history endpoint, newest-first. The API\n * caps a page at 200 rows; a config key's write history fits well inside\n * that, so a single request is the whole story (the limit param is clamped).\n */\nexport async function fetchConfigHistory(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<ConfigHistoryEntry[]> {\n const key = encodeURIComponent(getRegistryConfigKey(opts.accountId, opts.gatewayId));\n const limit = Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT));\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}/${key}?limit=${limit}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url);\n const entries = (payload?.entries ?? []).filter(Boolean) as FastKvHistoryEntry[];\n\n return entries\n .map((entry) => ({\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n ...(entry.tx_hash ? { txHash: entry.tx_hash } : {}),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\n}\n\nexport interface DeployManifestEntry {\n key: string;\n blockHeight: number;\n blockTimestampNs: string;\n value: unknown;\n}\n\n/**\n * The per-deploy audit trail (atomic-deploys 03/12): every publish writes\n * `apps/<account>/<gateway>/manifests/<ts>.json` atomically with the pointer\n * swap — this lists that key family newest-first via the history-by-prefix\n * endpoint.\n */\nexport async function fetchDeployManifests(opts: {\n accountId: string;\n gatewayId: string;\n registry?: string;\n limit?: number;\n}): Promise<DeployManifestEntry[]> {\n const keyPrefix = encodeURIComponent(\n `${getRegistryConfigKey(opts.accountId, opts.gatewayId).replace(/bos\\.config\\.json$/, \"\")}manifests/`,\n );\n const url = `${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}`;\n const payload = await fetchJson<FastKvHistoryResponse>(url, {\n method: \"POST\",\n body: JSON.stringify({\n key_prefix: decodeURIComponent(keyPrefix),\n asc: false,\n limit: Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)),\n }),\n });\n const entries = (payload?.entries ?? []).filter(Boolean) as Array<\n FastKvHistoryEntry & { key?: string }\n >;\n\n return entries\n .map((entry) => ({\n key: entry.key ?? \"\",\n blockHeight: entry.block_height ?? 0,\n blockTimestampNs: String(entry.block_timestamp ?? \"\"),\n value:\n typeof entry.value === \"string\" && entry.value.length > 0\n ? (JSON.parse(entry.value) as unknown)\n : entry.value,\n }))\n .sort((a, b) => b.blockHeight - a.blockHeight);\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 async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n return fetchJsonOrNull<PluginManifest>(`${baseUrl}/plugin.manifest.json`, { retries: 2 });\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers as Record<string, string> | undefined),\n };\n return fetchJsonOrNull<T>(url, {\n method: init?.method,\n headers,\n body: init?.body ?? undefined,\n retries: 3,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_http_client = require('./http-client.cjs');
|
|
2
3
|
const require_bundle_cache = require('./bundle-cache.cjs');
|
|
3
4
|
const require_integrity = require('./integrity.cjs');
|
|
4
5
|
let node_fs = require("node:fs");
|
|
@@ -6,23 +7,15 @@ let _module_federation_enhanced_runtime = require("@module-federation/enhanced/r
|
|
|
6
7
|
let _module_federation_runtime_core = require("@module-federation/runtime-core");
|
|
7
8
|
let every_plugin_build_rspack = require("every-plugin/build/rspack");
|
|
8
9
|
|
|
9
|
-
//#region src/
|
|
10
|
+
//#region src/federation.ts
|
|
10
11
|
let mfInstance = null;
|
|
11
|
-
function fetchWithTimeout(url, init, timeoutMs) {
|
|
12
|
-
const controller = new AbortController();
|
|
13
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
14
|
-
return fetch(url, {
|
|
15
|
-
...init,
|
|
16
|
-
signal: controller.signal
|
|
17
|
-
}).finally(() => clearTimeout(timer));
|
|
18
|
-
}
|
|
19
12
|
function patchManifestFetchForSsrPublicPath(mf) {
|
|
20
13
|
if (!mf || !mf.loaderHook?.lifecycle?.fetch?.on) return;
|
|
21
14
|
if (mf.__everythingDevPatchedManifestFetch === true) return;
|
|
22
15
|
mf.__everythingDevPatchedManifestFetch = true;
|
|
23
16
|
mf.loaderHook.lifecycle.fetch.on((url, init) => {
|
|
24
17
|
if (typeof url !== "string" || !url.endsWith("/mf-manifest.json")) return;
|
|
25
|
-
return
|
|
18
|
+
return require_http_client.fetchRaw(url, init, "15 seconds").then((res) => res.json()).then((json) => {
|
|
26
19
|
json.metaData = json.metaData ?? {};
|
|
27
20
|
json.metaData.ssrPublicPath = json.metaData.ssrPublicPath ?? url.replace(/\/mf-manifest\.json$/, "/");
|
|
28
21
|
if (json.metaData.publicPath === "auto" || json.metaData.publicPath === "") json.metaData.publicPath = "/";
|
|
@@ -41,7 +34,7 @@ function installIntegrityFetchHook(mf, registry, options) {
|
|
|
41
34
|
if (typeof url !== "string") return;
|
|
42
35
|
const expectedHash = registry.get(url);
|
|
43
36
|
if (!expectedHash) return;
|
|
44
|
-
return
|
|
37
|
+
return require_http_client.fetchRaw(url, init, "15 seconds").then(async (res) => {
|
|
45
38
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
46
39
|
const computed = require_integrity.computeSriHash(buffer);
|
|
47
40
|
if (computed !== expectedHash) {
|
|
@@ -166,7 +159,7 @@ function manifestUrl(remoteUrl) {
|
|
|
166
159
|
return remoteUrl.endsWith("/") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;
|
|
167
160
|
}
|
|
168
161
|
async function fetchManifest(url, timeoutMs) {
|
|
169
|
-
const res = await
|
|
162
|
+
const res = await require_http_client.fetchRaw(manifestUrl(url), {}, timeoutMs);
|
|
170
163
|
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
171
164
|
return res.json();
|
|
172
165
|
}
|
|
@@ -301,4 +294,4 @@ exports.loadBosConfigForMfCheck = loadBosConfigForMfCheck;
|
|
|
301
294
|
exports.loadRemoteModule = loadRemoteModule;
|
|
302
295
|
exports.patchManifestFetchForSsrPublicPath = patchManifestFetchForSsrPublicPath;
|
|
303
296
|
exports.registerRemote = registerRemote;
|
|
304
|
-
//# sourceMappingURL=
|
|
297
|
+
//# sourceMappingURL=federation.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation.cjs","names":["fetchRaw","computeSriHash","readBundleCache","writeBundleCache","getInstance","createInstance","isEffectCriticalSharedDep","readFileSync"],"sources":["../src/federation.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { isEffectCriticalSharedDep } from \"every-plugin/build/rspack\";\nimport { readBundleCache, writeBundleCache } from \"./bundle-cache\";\nimport { fetchRaw } from \"./http-client\";\nimport { computeSriHash, type IntegrityRegistry } from \"./integrity\";\nimport type { BosConfig, BosPluginRef } from \"./types\";\n\ntype FederationInstance = ReturnType<typeof createInstance>;\n\nlet mfInstance: FederationInstance | null = null;\n\nexport function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) return;\n if ((mf as any).__everythingDevPatchedManifestFetch === true) return;\n (mf as any).__everythingDevPatchedManifestFetch = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\" || !url.endsWith(\"/mf-manifest.json\")) {\n return;\n }\n return fetchRaw(url as string, init as RequestInit, \"15 seconds\")\n .then((res) => res.json())\n .then((json: any) => {\n json.metaData = json.metaData ?? {};\n json.metaData.ssrPublicPath =\n json.metaData.ssrPublicPath ?? url.replace(/\\/mf-manifest\\.json$/, \"/\");\n if (json.metaData.publicPath === \"auto\" || json.metaData.publicPath === \"\") {\n json.metaData.publicPath = \"/\";\n }\n return new Response(JSON.stringify(json), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n });\n}\n\nexport function installIntegrityFetchHook(\n mf: FederationInstance,\n registry: IntegrityRegistry,\n options?: { cacheDir?: string },\n): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) {\n console.warn(\"[SRI] MF lifecycle fetch hook not available, skipping integrity-in-pipeline\");\n return;\n }\n if ((mf as any).__everythingDevIntegrityHook === true) return;\n (mf as any).__everythingDevIntegrityHook = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\") return;\n\n const expectedHash = registry.get(url);\n if (!expectedHash) return;\n\n return fetchRaw(url as string, init as RequestInit, \"15 seconds\").then(async (res) => {\n const buffer = Buffer.from(await res.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedHash) {\n // ticket 09 (atomic-deploys): an SRI mismatch is treated like an\n // origin failure — the last-known-good bytes (which satisfied the\n // same pin when cached) serve instead, and the corrupted bytes are\n // never written into the cache.\n const cached = await readBundleCache(url as string, { cacheDir: options?.cacheDir });\n if (cached && computeSriHash(Buffer.from(cached)) === expectedHash) {\n console.warn(\n `[SRI] Origin bytes failed verification for ${url} — serving last-known-good from the bundle cache`,\n );\n return new Response(cached.slice().buffer, {\n status: 200,\n headers: { \"x-bundle-cache\": \"stale\" },\n });\n }\n console.error(\n `[SRI] Integrity check failed in MF fetch pipeline for ${url}\\n Expected: ${expectedHash}\\n Computed: ${computed}`,\n );\n return new Response(`Integrity check failed for ${url}`, {\n status: 500,\n statusText: \"Integrity Check Failed\",\n });\n }\n\n // verified: pin-checked write-through\n await writeBundleCache(url as string, new Uint8Array(buffer), {\n cacheDir: options?.cacheDir,\n }).catch(() => {\n // write-through is best-effort\n });\n\n return new Response(buffer, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n });\n });\n}\n\nexport function getFederationInstance(): FederationInstance {\n if (mfInstance) return mfInstance;\n\n const existing = getInstance();\n if (existing) {\n mfInstance = existing as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n }\n\n mfInstance = createInstance({\n name: \"host\",\n remotes: [],\n }) as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n}\n\nexport async function registerRemote(opts: {\n name: string;\n entry: string;\n type?: \"manifest\" | \"script\";\n}): Promise<void> {\n const instance = getFederationInstance();\n\n const inferType = (): \"manifest\" | \"script\" => {\n if (opts.type) return opts.type;\n if (opts.entry.endsWith(\"/mf-manifest.json\")) return \"manifest\";\n if (/\\/remoteEntry(\\.[a-f0-9]{8,})?\\.js$/.test(opts.entry)) return \"script\";\n return typeof window === \"undefined\" ? \"script\" : \"manifest\";\n };\n\n const remoteType = inferType();\n\n instance.registerRemotes([\n {\n name: opts.name,\n entry: opts.entry,\n type: remoteType,\n },\n ]);\n}\n\nexport async function loadRemoteModule<T>(\n specifier: string,\n options?: { loadFactory?: boolean; from?: \"build\" | \"runtime\" },\n): Promise<T> {\n const instance = getFederationInstance();\n\n const isServer = typeof window === \"undefined\";\n if (isServer) {\n await (instance as any).initializeSharing?.(\"default\");\n }\n\n const mod = await instance.loadRemote<T>(specifier, options as any);\n if (!mod) {\n throw new Error(`Failed to load remote module: ${specifier}`);\n }\n return mod;\n}\n\nexport async function ensureNodeRuntimePlugin(): Promise<void> {\n const instance = getFederationInstance();\n if (typeof window !== \"undefined\") return;\n if ((instance as any).__nodeRuntimePluginLoaded) return;\n\n const mod: any = await import(\"@module-federation/node/runtimePlugin\");\n const factory = mod?.default ?? mod;\n const plugin = typeof factory === \"function\" ? factory() : null;\n if (plugin) {\n instance.registerPlugins([plugin]);\n }\n (instance as any).__nodeRuntimePluginLoaded = true;\n}\n\nexport interface FederationCompatRemote {\n role: string;\n url: string;\n reachable: boolean;\n pluginVersion: string | null;\n ok: boolean;\n reason?: string;\n}\n\nexport interface FederationCompatReport {\n ok: boolean;\n hostVersion: string | null;\n hostReachable: boolean;\n hostReason?: string;\n remotes: FederationCompatRemote[];\n}\n\nfunction parseSemver(v: unknown): { major: number; minor: number; patch: number } | null {\n const m = typeof v === \"string\" ? /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(v) : null;\n return m ? { major: +m[1]!, minor: +m[2]!, patch: +m[3]! } : null;\n}\n\nfunction satisfiesConstraint(version: string, constraint: string): boolean {\n const trimmed = constraint.trim();\n if (!trimmed) return true;\n if (![\"^\", \"~\", \">\", \"<\", \"=\", \"x\"].some((c) => trimmed.startsWith(c))) {\n return version === trimmed;\n }\n const m = /^\\s*\\^\\s*(\\d+)\\.(\\d+)\\.(\\d+)/.exec(trimmed);\n if (!m) return true;\n const v = parseSemver(version);\n if (!v) return false;\n if (v.major !== +m[1]!) return false;\n if (v.minor < +m[2]! || (v.minor === +m[2]! && v.patch < +m[3]!)) return false;\n const constraintPrerelease = trimmed.match(/-([0-9A-Za-z.-]+)/);\n const versionPrerelease = /-([0-9A-Za-z.-]+)\\s*$/.exec(version);\n if (constraintPrerelease) {\n if (!versionPrerelease) return false;\n const cPre = constraintPrerelease[1]!.split(\".\");\n const vPre = versionPrerelease[1]!.split(\".\");\n for (let i = 0; i < Math.max(cPre.length, vPre.length); i += 1) {\n const c = cPre[i] ?? \"0\";\n const p = vPre[i] ?? \"0\";\n if (c === p) continue;\n const cNum = /^\\d+$/.test(c) ? +c : null;\n const pNum = /^\\d+$/.test(p) ? +p : null;\n if (cNum !== null && pNum !== null) return cNum < pNum;\n return cNum === null ? c.localeCompare(p) < 0 : false;\n }\n return true;\n }\n if (versionPrerelease) return false;\n return true;\n}\n\nfunction manifestUrl(remoteUrl: string): string {\n return remoteUrl.endsWith(\"/\") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;\n}\n\nasync function fetchManifest(url: string, timeoutMs: number): Promise<unknown> {\n const res = await fetchRaw(manifestUrl(url), {}, timeoutMs);\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);\n return res.json();\n}\n\nfunction listFederationRemotes(bosConfig: BosConfig): Array<{ role: string; url: string }> {\n const out: Array<{ role: string; url: string }> = [];\n for (const role of [\"api\", \"auth\"] as const) {\n const url = readProductionUrl((bosConfig?.app as Record<string, unknown> | undefined)?.[role]);\n if (url) out.push({ role, url });\n }\n for (const [id, slot] of Object.entries(bosConfig?.plugins ?? {})) {\n const url = readProductionUrl(slot);\n if (url) out.push({ role: id, url });\n }\n return out;\n}\n\nfunction readProductionUrl(entry: unknown): string | null {\n if (typeof entry === \"string\") return entry.length > 0 ? entry : null;\n if (entry && typeof (entry as BosPluginRef).production === \"string\") {\n const url = (entry as BosPluginRef).production!;\n return url.length > 0 ? url : null;\n }\n return null;\n}\n\ninterface RemoteManifest {\n metaData?: { pluginVersion?: string };\n shared?: Array<{ name: string; version?: string; requiredVersion?: string | null }>;\n}\n\nexport async function checkFederationCompat(\n bosConfig: BosConfig | null,\n opts: { timeoutMs?: number } = {},\n): Promise<FederationCompatReport> {\n const timeoutMs = opts.timeoutMs ?? 15_000;\n\n if (!bosConfig?.app?.host?.production) {\n return { ok: true, hostVersion: null, hostReachable: false, remotes: [] };\n }\n\n const hostUrl = bosConfig.app.host.production;\n let hostManifest: RemoteManifest | null = null;\n let hostReason: string | undefined;\n try {\n hostManifest = (await fetchManifest(hostUrl, timeoutMs)) as RemoteManifest;\n } catch (e) {\n hostReason = e instanceof Error ? e.message : String(e);\n }\n\n const hostVersion = hostManifest?.metaData?.pluginVersion ?? null;\n const hostShared = (hostManifest?.shared ?? []).filter(\n (s) => typeof s.requiredVersion === \"string\",\n );\n\n const remotes = listFederationRemotes(bosConfig);\n const settled = await Promise.allSettled(\n remotes.map(async ({ role, url }) => {\n let manifest: RemoteManifest | null = null;\n let reason: string | undefined;\n try {\n manifest = (await fetchManifest(url, timeoutMs)) as RemoteManifest;\n } catch (e) {\n reason = e instanceof Error ? e.message : String(e);\n }\n\n const version = manifest?.metaData?.pluginVersion ?? null;\n const versionOk = version !== null && version === hostVersion;\n\n if (!manifest) {\n return {\n role,\n url,\n reachable: false,\n pluginVersion: null,\n ok: false,\n reason: reason ?? \"unreachable\",\n } satisfies FederationCompatRemote;\n }\n\n if (!versionOk) {\n return {\n role,\n url,\n reachable: true,\n pluginVersion: version,\n ok: false,\n reason: hostVersion\n ? `expected pluginVersion=${hostVersion} (host), got ${version ?? \"missing\"} — redeploy this plugin`\n : `host manifest missing pluginVersion`,\n } satisfies FederationCompatRemote;\n }\n\n const missingShared: string[] = [];\n const mismatchedShared: string[] = [];\n for (const hostDep of hostShared) {\n const remDep = manifest.shared?.find((s) => s.name === hostDep.name);\n if (!remDep) {\n missingShared.push(`${hostDep.name}@${hostDep.requiredVersion}`);\n continue;\n }\n if (!satisfiesConstraint(remDep.version ?? \"\", hostDep.requiredVersion!)) {\n mismatchedShared.push(\n `${hostDep.name}: remote=${remDep.version ?? \"?\"} hostReq=${hostDep.requiredVersion}`,\n );\n }\n if (isEffectCriticalSharedDep(hostDep.name)) {\n const hostVersion = hostDep.version ?? \"\";\n const remoteVersion = remDep.version ?? \"\";\n if (remoteVersion && remoteVersion !== hostVersion) {\n mismatchedShared.push(\n `${hostDep.name}: exact-version skew — remote=${remoteVersion} host=${hostVersion} — bundle was built against a different prerelease build; redeploy host and plugin together`,\n );\n }\n const remoteRequired =\n typeof remDep.requiredVersion === \"string\" ? remDep.requiredVersion.trim() : \"\";\n if (\n remoteRequired.length > 0 &&\n remoteRequired[0] !== \"^\" &&\n !satisfiesConstraint(hostVersion, remoteRequired)\n ) {\n mismatchedShared.push(\n `${hostDep.name}: train skew — host=${hostVersion} vs remote requiredVersion=${remoteRequired}`,\n );\n }\n }\n }\n\n const reasons = [\n ...missingShared.map((d) => `missing shared \"${d}\" required by host`),\n ...mismatchedShared,\n ];\n return {\n role,\n url,\n reachable: true,\n pluginVersion: version,\n ok: reasons.length === 0,\n reason: reasons[0],\n } satisfies FederationCompatRemote;\n }),\n );\n\n const remoteReports = settled.map((s, i) => {\n const placeholder = {\n role: remotes[i]!.role,\n url: remotes[i]!.url,\n reachable: false,\n pluginVersion: null,\n ok: false,\n };\n return s.status === \"fulfilled\" ? s.value : { ...placeholder, reason: String(s.reason) };\n });\n\n const ok =\n hostManifest !== null &&\n remoteReports.every((r) => r.ok && r.reachable && r.pluginVersion !== null);\n\n return {\n ok,\n hostVersion,\n hostReachable: hostManifest !== null,\n hostReason,\n remotes: remoteReports,\n };\n}\n\nexport function loadBosConfigForMfCheck(configPath: string): BosConfig {\n return JSON.parse(readFileSync(configPath, \"utf8\")) as BosConfig;\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BosConfig } from "./types.cjs";
|
|
2
2
|
import { IntegrityRegistry } from "./integrity.cjs";
|
|
3
3
|
import { createInstance } from "@module-federation/enhanced/runtime";
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/federation.d.ts
|
|
5
5
|
type FederationInstance = ReturnType<typeof createInstance>;
|
|
6
6
|
declare function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void;
|
|
7
7
|
declare function installIntegrityFetchHook(mf: FederationInstance, registry: IntegrityRegistry, options?: {
|
|
@@ -39,4 +39,4 @@ declare function checkFederationCompat(bosConfig: BosConfig | null, opts?: {
|
|
|
39
39
|
declare function loadBosConfigForMfCheck(configPath: string): BosConfig;
|
|
40
40
|
//#endregion
|
|
41
41
|
export { FederationCompatRemote, FederationCompatReport, checkFederationCompat, ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadBosConfigForMfCheck, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
|
|
42
|
-
//# sourceMappingURL=
|
|
42
|
+
//# sourceMappingURL=federation.d.cts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BosConfig } from "./types.mjs";
|
|
2
2
|
import { IntegrityRegistry } from "./integrity.mjs";
|
|
3
3
|
import { createInstance } from "@module-federation/enhanced/runtime";
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/federation.d.ts
|
|
5
5
|
type FederationInstance = ReturnType<typeof createInstance>;
|
|
6
6
|
declare function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void;
|
|
7
7
|
declare function installIntegrityFetchHook(mf: FederationInstance, registry: IntegrityRegistry, options?: {
|
|
@@ -39,4 +39,4 @@ declare function checkFederationCompat(bosConfig: BosConfig | null, opts?: {
|
|
|
39
39
|
declare function loadBosConfigForMfCheck(configPath: string): BosConfig;
|
|
40
40
|
//#endregion
|
|
41
41
|
export { FederationCompatRemote, FederationCompatReport, checkFederationCompat, ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadBosConfigForMfCheck, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
|
|
42
|
-
//# sourceMappingURL=
|
|
42
|
+
//# sourceMappingURL=federation.d.mts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fetchRaw } from "./http-client.mjs";
|
|
1
2
|
import { readBundleCache, writeBundleCache } from "./bundle-cache.mjs";
|
|
2
3
|
import { computeSriHash } from "./integrity.mjs";
|
|
3
4
|
import { readFileSync } from "node:fs";
|
|
@@ -5,23 +6,15 @@ import { createInstance, getInstance } from "@module-federation/enhanced/runtime
|
|
|
5
6
|
import { setGlobalFederationInstance } from "@module-federation/runtime-core";
|
|
6
7
|
import { isEffectCriticalSharedDep } from "every-plugin/build/rspack";
|
|
7
8
|
|
|
8
|
-
//#region src/
|
|
9
|
+
//#region src/federation.ts
|
|
9
10
|
let mfInstance = null;
|
|
10
|
-
function fetchWithTimeout(url, init, timeoutMs) {
|
|
11
|
-
const controller = new AbortController();
|
|
12
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
13
|
-
return fetch(url, {
|
|
14
|
-
...init,
|
|
15
|
-
signal: controller.signal
|
|
16
|
-
}).finally(() => clearTimeout(timer));
|
|
17
|
-
}
|
|
18
11
|
function patchManifestFetchForSsrPublicPath(mf) {
|
|
19
12
|
if (!mf || !mf.loaderHook?.lifecycle?.fetch?.on) return;
|
|
20
13
|
if (mf.__everythingDevPatchedManifestFetch === true) return;
|
|
21
14
|
mf.__everythingDevPatchedManifestFetch = true;
|
|
22
15
|
mf.loaderHook.lifecycle.fetch.on((url, init) => {
|
|
23
16
|
if (typeof url !== "string" || !url.endsWith("/mf-manifest.json")) return;
|
|
24
|
-
return
|
|
17
|
+
return fetchRaw(url, init, "15 seconds").then((res) => res.json()).then((json) => {
|
|
25
18
|
json.metaData = json.metaData ?? {};
|
|
26
19
|
json.metaData.ssrPublicPath = json.metaData.ssrPublicPath ?? url.replace(/\/mf-manifest\.json$/, "/");
|
|
27
20
|
if (json.metaData.publicPath === "auto" || json.metaData.publicPath === "") json.metaData.publicPath = "/";
|
|
@@ -40,7 +33,7 @@ function installIntegrityFetchHook(mf, registry, options) {
|
|
|
40
33
|
if (typeof url !== "string") return;
|
|
41
34
|
const expectedHash = registry.get(url);
|
|
42
35
|
if (!expectedHash) return;
|
|
43
|
-
return
|
|
36
|
+
return fetchRaw(url, init, "15 seconds").then(async (res) => {
|
|
44
37
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
45
38
|
const computed = computeSriHash(buffer);
|
|
46
39
|
if (computed !== expectedHash) {
|
|
@@ -165,7 +158,7 @@ function manifestUrl(remoteUrl) {
|
|
|
165
158
|
return remoteUrl.endsWith("/") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;
|
|
166
159
|
}
|
|
167
160
|
async function fetchManifest(url, timeoutMs) {
|
|
168
|
-
const res = await
|
|
161
|
+
const res = await fetchRaw(manifestUrl(url), {}, timeoutMs);
|
|
169
162
|
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
170
163
|
return res.json();
|
|
171
164
|
}
|
|
@@ -293,4 +286,4 @@ function loadBosConfigForMfCheck(configPath) {
|
|
|
293
286
|
|
|
294
287
|
//#endregion
|
|
295
288
|
export { checkFederationCompat, ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadBosConfigForMfCheck, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
|
|
296
|
-
//# sourceMappingURL=
|
|
289
|
+
//# sourceMappingURL=federation.mjs.map
|