{"version":3,"file":"bundle-fs-resolve.cjs","names":["Schema","bundleUrlToStagedPath","path","Context","Effect","readFile","cacheControlOf","Layer","ManagedRuntime","bundleCachePath","writeBundleCache","readBundleCache","isAppDescriptorPath","readFileSync","existsSync","bundleCacheRoot"],"sources":["../src/bundle-fs-resolve.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Context, Effect, Layer, ManagedRuntime, Schema } from \"effect\";\nimport { cacheControlOf } from \"every-plugin/build/artifact-names\";\nimport {\n  bundleCachePath,\n  bundleCacheRoot,\n  readBundleCache,\n  writeBundleCache,\n} from \"./bundle-cache\";\nimport { bundleUrlToStagedPath } from \"./bundle-path\";\nimport { isAppDescriptorPath } from \"./config\";\n\n/**\n * Local-first bundle resolution (ADR 0011 amendment; unified policy, ADR 0021\n * amendment): a self-contained runtime image stages its own namespace under\n * `BOS_BUNDLE_DIR` as `bundles/<account>/<gateway>/<workspace>/…` and consumes\n * those bytes directly from disk; a miss falls through to the network, so a\n * partially-staged namespace boots over the wire instead of failing. The\n * registry tier (children, `BOS_BUNDLE_DIR` unset) keeps the network fetch —\n * the namespace guard below only ever matches the runtime's own\n * account/gateway, never another runtime's layout.\n */\n\nexport interface BundleNamespace {\n  readonly bundleDir: string;\n  readonly account: string;\n  readonly gateway: string;\n}\n\nexport class BundleReadError extends Schema.TaggedError<BundleReadError>()(\"BundleReadError\", {\n  path: Schema.String,\n  cause: Schema.optional(Schema.Unknown),\n}) {}\n\nconst MIME_TYPES: Record<string, string> = {\n  \".js\": \"text/javascript\",\n  \".mjs\": \"text/javascript\",\n  \".cjs\": \"text/javascript\",\n  \".css\": \"text/css\",\n  \".json\": \"application/json\",\n  \".map\": \"application/json\",\n  \".svg\": \"image/svg+xml\",\n  \".png\": \"image/png\",\n  \".jpg\": \"image/jpeg\",\n  \".jpeg\": \"image/jpeg\",\n  \".gif\": \"image/gif\",\n  \".webp\": \"image/webp\",\n  \".avif\": \"image/avif\",\n  \".ico\": \"image/x-icon\",\n  \".html\": \"text/html\",\n  \".htm\": \"text/html\",\n  \".webmanifest\": \"application/manifest+json\",\n  \".md\": \"text/markdown\",\n  \".ts\": \"text/plain\",\n  \".woff\": \"font/woff\",\n  \".woff2\": \"font/woff2\",\n  \".ttf\": \"font/ttf\",\n  \".otf\": \"font/otf\",\n  \".eot\": \"application/vnd.ms-fontobject\",\n  \".wasm\": \"application/wasm\",\n  \".txt\": \"text/plain\",\n  \".xml\": \"application/xml\",\n};\n\n/**\n * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,\n * narrowed to the runtime identity — a URL whose encoded namespace differs\n * from `namespace.account`/`namespace.gateway` must fall to the network/cache\n * path, never serve a sibling namespace's staged bytes.\n */\nexport function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null {\n  const filePath = bundleUrlToStagedPath(url, namespace.bundleDir);\n  if (!filePath) return null;\n\n  const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway);\n  if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null;\n\n  return filePath;\n}\n\nexport class BundleResolver extends Context.Service<\n  BundleResolver,\n  {\n    /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */\n    readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;\n  }\n>()(\"everything-dev/bundle-fs-resolve/BundleResolver\") {\n  static layer(namespace: BundleNamespace): Layer.Layer<BundleResolver> {\n    const respond = Effect.fn(\"BundleResolver.respond\")(function* (\n      url: string,\n    ): Effect.fn.Return<Response | null, never> {\n      const filePath = bundleUrlToLocalPath(url, namespace);\n      if (!filePath) return null;\n      const bytes = yield* Effect.tryPromise({\n        try: () => readFile(filePath),\n        catch: (cause) => new BundleReadError({ path: filePath, cause }),\n      }).pipe(Effect.catchTag(\"BundleReadError\", () => Effect.succeed(null)));\n      if (bytes === null) return null;\n      const name = path.basename(filePath);\n      const contentType =\n        MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n      return new Response(new Uint8Array(bytes), {\n        headers: {\n          \"content-type\": contentType,\n          \"cache-control\": cacheControlOf(name),\n          etag: `\"${Buffer.from(bytes.subarray(0, 4096)).toString(\"base64\").slice(0, 24)}\"`,\n        },\n      });\n    });\n\n    return Layer.effect(\n      BundleResolver,\n      Effect.succeed(\n        BundleResolver.of({\n          lookup: (input) => {\n            const url =\n              typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n            return respond(url);\n          },\n        }),\n      ),\n    );\n  }\n}\n\nexport interface BundleFetchHandle {\n  readonly uninstall: () => Promise<void>;\n}\n\nexport interface BundleFetchOptions {\n  /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */\n  namespace?: BundleNamespace;\n  /**\n   * Foreign-namespace stale-if-error cache (child tier): `/bundles/…` URLs\n   * outside the own namespace write through to disk on success and serve\n   * the last-known-good bytes when the origin fails. Enabled implicitly\n   * when `namespace` is present (deployment contexts), or explicitly via\n   * `BOS_BUNDLE_CACHE_DIR`.\n   */\n  cacheDir?: string;\n}\n\ntype FetchImpl = typeof globalThis.fetch;\n\nconst staleResponse = (bytes: Uint8Array, url: string): Response => {\n  const name = url.split(\"?\")[0].split(\"/\").pop() ?? \"\";\n  const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n  return new Response(bytes.slice().buffer, {\n    status: 200,\n    headers: { \"content-type\": contentType, \"x-bundle-cache\": \"stale\" },\n  });\n};\n\n/**\n * Installs the resolver as a global fetch interceptor — the one seam every\n * boot-time consumer shares (config manifest discovery, contract-type\n * fetches, orchestrator host loading, MF remoteEntry/identity probes).\n * One layered resolution policy: staged disk → network → stale-if-error.\n * Own-namespace URLs resolve from the staged directory and fall through to\n * the original fetch on a miss (a partially-staged namespace boots over the\n * wire instead of failing); a miss never touches the write-through cache —\n * for the own namespace the staged disk IS the resilience artifact. Foreign\n * `/bundles/…` URLs get the network with the stale-if-error cache; every\n * other URL falls through to the original fetch untouched. Inert when\n * neither a staged namespace nor a cache is configured.\n */\nexport function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle {\n  const namespace = options.namespace;\n  const cacheDir = options.cacheDir;\n  const runtime = namespace ? ManagedRuntime.make(BundleResolver.layer(namespace)) : null;\n  const original: FetchImpl = globalThis.fetch;\n  const patched = (async (input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]) => {\n    const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n    if (namespace && bundleUrlToLocalPath(url, namespace)) {\n      if (!runtime) throw new Error(\"unreachable: resolver runtime missing\");\n      const response = await runtime.runPromise(\n        Effect.gen(function* () {\n          const resolver = yield* BundleResolver;\n          return yield* resolver.lookup(input);\n        }),\n      );\n      if (response !== null) return response;\n      return original(input, init);\n    }\n\n    if (cacheDir !== undefined && bundleCachePath(url, { cacheDir })) {\n      return (async () => {\n        try {\n          const response = await original(input, init);\n          if (response.ok) {\n            try {\n              const bytes = new Uint8Array(await response.clone().arrayBuffer());\n              await writeBundleCache(url, bytes, { cacheDir });\n            } catch {\n              // write-through is best-effort\n            }\n          } else {\n            const cached = await readBundleCache(url, { cacheDir });\n            if (cached) return staleResponse(cached, url);\n          }\n          return response;\n        } catch (error) {\n          const cached = await readBundleCache(url, { cacheDir });\n          if (cached) return staleResponse(cached, url);\n          throw error;\n        }\n      })();\n    }\n\n    return original(input, init);\n  }) as FetchImpl;\n  const originalPreconnect = (original as Partial<FetchImpl>).preconnect;\n  if (originalPreconnect) {\n    patched.preconnect = originalPreconnect.bind(original);\n  }\n  globalThis.fetch = patched;\n  return {\n    uninstall: async () => {\n      if (globalThis.fetch === patched) {\n        globalThis.fetch = original;\n      }\n      if (runtime) await runtime.dispose();\n    },\n  };\n}\n\n/**\n * Boot-time installer for the CLI: derives the own namespace from\n * `BOS_BUNDLE_DIR` plus the runtime identity (`BOS_ACCOUNT`/`BOS_GATEWAY`\n * registry env taking precedence over the bos.config.json fields), and the\n * foreign-namespace cache from `BOS_BUNDLE_CACHE_DIR`. Tier auto-detection\n * (ADR 0021): an identity whose namespace is not actually staged under the\n * bundle dir drops to the registry tier — plain network fetch — instead of\n * deterministically 404ing own-namespace URLs against a foreign image's\n * staged bytes. Never throws — a missing or malformed identity leaves the\n * default network fetch in place.\n */\nexport function installBundleFetchFromEnv(input: {\n  configPath?: string | null;\n}): BundleFetchHandle | null {\n  const bundleDir = process.env.BOS_BUNDLE_DIR;\n  const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR;\n  if (!bundleDir && cacheDir === undefined) return null;\n\n  let configAccount: string | undefined;\n  let configGateway: string | undefined;\n  const configPath = input.configPath;\n  if (configPath && !isAppDescriptorPath(configPath)) {\n    try {\n      const parsed = JSON.parse(readFileSync(configPath, \"utf8\")) as {\n        account?: string;\n        domain?: string;\n      };\n      configAccount = parsed.account;\n      configGateway = parsed.domain;\n    } catch {\n      // unreadable config — identity may still come from the registry env\n    }\n  }\n\n  const account = process.env.BOS_ACCOUNT ?? configAccount;\n  const gateway = process.env.BOS_GATEWAY ?? configGateway;\n  if (bundleDir && (!account || !gateway)) return null;\n\n  let namespace: BundleNamespace | undefined;\n  if (bundleDir && account && gateway) {\n    if (existsSync(path.join(bundleDir, account, gateway))) {\n      namespace = { bundleDir, account, gateway };\n    } else {\n      console.warn(\n        `[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`,\n      );\n    }\n  }\n  return installGlobalBundleFetch({\n    namespace,\n    cacheDir: cacheDir ?? (namespace ? bundleCacheRoot() : undefined),\n  });\n}\n"],"mappings":"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"}