everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
194 lines (192 loc) • 7.96 kB
JavaScript
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