UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

194 lines (192 loc) • 7.96 kB
import { bundleUrlToStagedPath } from "./bundle-path.mjs"; import { isAppDescriptorPath } from "./config.mjs"; import { bundleCachePath, bundleCacheRoot, readBundleCache, writeBundleCache } from "./bundle-cache.mjs"; import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import { Context, Effect, Layer, ManagedRuntime, Schema } from "effect"; import { readFile } from "node:fs/promises"; import { cacheControlOf } from "every-plugin/build/artifact-names"; //#region src/bundle-fs-resolve.ts var BundleReadError = class extends Schema.TaggedError()("BundleReadError", { path: Schema.String, cause: Schema.optional(Schema.Unknown) }) {}; const MIME_TYPES = { ".js": "text/javascript", ".mjs": "text/javascript", ".cjs": "text/javascript", ".css": "text/css", ".json": "application/json", ".map": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".avif": "image/avif", ".ico": "image/x-icon", ".html": "text/html", ".htm": "text/html", ".webmanifest": "application/manifest+json", ".md": "text/markdown", ".ts": "text/plain", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".otf": "font/otf", ".eot": "application/vnd.ms-fontobject", ".wasm": "application/wasm", ".txt": "text/plain", ".xml": "application/xml" }; /** * Own-namespace path for a bundle URL: the shared URL→staged-path mapping, * narrowed to the runtime identity — a URL whose encoded namespace differs * from `namespace.account`/`namespace.gateway` must fall to the network/cache * path, never serve a sibling namespace's staged bytes. */ function bundleUrlToLocalPath(url, namespace) { const filePath = bundleUrlToStagedPath(url, namespace.bundleDir); if (!filePath) return null; const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway); if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null; return filePath; } var BundleResolver = class BundleResolver extends Context.Service()("everything-dev/bundle-fs-resolve/BundleResolver") { static layer(namespace) { const respond = Effect.fn("BundleResolver.respond")(function* (url) { const filePath = bundleUrlToLocalPath(url, namespace); if (!filePath) return null; const bytes = yield* Effect.tryPromise({ try: () => readFile(filePath), catch: (cause) => new BundleReadError({ path: filePath, cause }) }).pipe(Effect.catchTag("BundleReadError", () => Effect.succeed(null))); if (bytes === null) return null; const name = path.basename(filePath); const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? "application/octet-stream"; return new Response(new Uint8Array(bytes), { headers: { "content-type": contentType, "cache-control": cacheControlOf(name), etag: `"${Buffer.from(bytes.subarray(0, 4096)).toString("base64").slice(0, 24)}"` } }); }); return Layer.effect(BundleResolver, Effect.succeed(BundleResolver.of({ lookup: (input) => { const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; return respond(url); } }))); } }; const staleResponse = (bytes, url) => { const name = url.split("?")[0].split("/").pop() ?? ""; const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? "application/octet-stream"; return new Response(bytes.slice().buffer, { status: 200, headers: { "content-type": contentType, "x-bundle-cache": "stale" } }); }; /** * Installs the resolver as a global fetch interceptor — the one seam every * boot-time consumer shares (config manifest discovery, contract-type * fetches, orchestrator host loading, MF remoteEntry/identity probes). * One layered resolution policy: staged disk → network → stale-if-error. * Own-namespace URLs resolve from the staged directory and fall through to * the original fetch on a miss (a partially-staged namespace boots over the * wire instead of failing); a miss never touches the write-through cache — * for the own namespace the staged disk IS the resilience artifact. Foreign * `/bundles/…` URLs get the network with the stale-if-error cache; every * other URL falls through to the original fetch untouched. Inert when * neither a staged namespace nor a cache is configured. */ function installGlobalBundleFetch(options) { const namespace = options.namespace; const cacheDir = options.cacheDir; const runtime = namespace ? ManagedRuntime.make(BundleResolver.layer(namespace)) : null; const original = globalThis.fetch; const patched = (async (input, init) => { const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; if (namespace && bundleUrlToLocalPath(url, namespace)) { if (!runtime) throw new Error("unreachable: resolver runtime missing"); const response = await runtime.runPromise(Effect.gen(function* () { return yield* (yield* BundleResolver).lookup(input); })); if (response !== null) return response; return original(input, init); } if (cacheDir !== void 0 && bundleCachePath(url, { cacheDir })) return (async () => { try { const response = await original(input, init); if (response.ok) try { const bytes = new Uint8Array(await response.clone().arrayBuffer()); await writeBundleCache(url, bytes, { cacheDir }); } catch {} else { const cached = await readBundleCache(url, { cacheDir }); if (cached) return staleResponse(cached, url); } return response; } catch (error) { const cached = await readBundleCache(url, { cacheDir }); if (cached) return staleResponse(cached, url); throw error; } })(); return original(input, init); }); const originalPreconnect = original.preconnect; if (originalPreconnect) patched.preconnect = originalPreconnect.bind(original); globalThis.fetch = patched; return { uninstall: async () => { if (globalThis.fetch === patched) globalThis.fetch = original; if (runtime) await runtime.dispose(); } }; } /** * Boot-time installer for the CLI: derives the own namespace from * `BOS_BUNDLE_DIR` plus the runtime identity (`BOS_ACCOUNT`/`BOS_GATEWAY` * registry env taking precedence over the bos.config.json fields), and the * foreign-namespace cache from `BOS_BUNDLE_CACHE_DIR`. Tier auto-detection * (ADR 0021): an identity whose namespace is not actually staged under the * bundle dir drops to the registry tier — plain network fetch — instead of * deterministically 404ing own-namespace URLs against a foreign image's * staged bytes. Never throws — a missing or malformed identity leaves the * default network fetch in place. */ function installBundleFetchFromEnv(input) { const bundleDir = process.env.BOS_BUNDLE_DIR; const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR; if (!bundleDir && cacheDir === void 0) return null; let configAccount; let configGateway; const configPath = input.configPath; if (configPath && !isAppDescriptorPath(configPath)) try { const parsed = JSON.parse(readFileSync(configPath, "utf8")); configAccount = parsed.account; configGateway = parsed.domain; } catch {} const account = process.env.BOS_ACCOUNT ?? configAccount; const gateway = process.env.BOS_GATEWAY ?? configGateway; if (bundleDir && (!account || !gateway)) return null; let namespace; if (bundleDir && account && gateway) { if (existsSync(path.join(bundleDir, account, gateway))) namespace = { bundleDir, account, gateway }; else console.warn(`[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`); } return installGlobalBundleFetch({ namespace, cacheDir: cacheDir ?? (namespace ? bundleCacheRoot() : void 0) }); } //#endregion export { BundleReadError, BundleResolver, bundleUrlToLocalPath, installBundleFetchFromEnv, installGlobalBundleFetch }; //# sourceMappingURL=bundle-fs-resolve.mjs.map