UNPKG

everything-dev

Version:

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

402 lines (400 loc) 17.5 kB
const require_runtime = require('./_virtual/_rolldown/runtime.cjs'); let node_fs = require("node:fs"); let node_path = require("node:path"); let node_crypto = require("node:crypto"); //#region src/api-contract.ts const REMOTE_FETCH_TIMEOUT_MS = 1e4; function sha256(input) { return (0, node_crypto.createHash)("sha256").update(input).digest("hex"); } function trimTrailingSlash(input) { return input.replace(/\/$/, ""); } function sanitizeIdentifier(input) { return input.replace(/[^A-Za-z0-9_]/g, "_").replace(/^[^A-Za-z_]+/, "_"); } function toImportPath(fromFile, targetFile) { const rel = (0, node_path.relative)((0, node_path.dirname)(fromFile), targetFile).replace(/\\/g, "/"); return rel.startsWith(".") ? rel : `./${rel}`; } function writeFileIfChanged(filePath, content) { try { if ((0, node_fs.readFileSync)(filePath, "utf8") === content) return false; } catch {} (0, node_fs.writeFileSync)(filePath, content); return true; } function getApiPluginManifestUrl(apiBaseUrl) { return `${trimTrailingSlash(apiBaseUrl)}/plugin.manifest.json`; } async function fetchWithTimeout(url) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REMOTE_FETCH_TIMEOUT_MS); try { return await fetch(url, { signal: controller.signal }); } catch (error) { if (error instanceof Error && error.name === "AbortError") throw new Error(`Timed out fetching ${url} after ${REMOTE_FETCH_TIMEOUT_MS}ms`); throw error; } finally { clearTimeout(timeout); } } async function fetchApiPluginManifest(apiBaseUrl) { const response = await fetchWithTimeout(getApiPluginManifestUrl(apiBaseUrl)); if (!response.ok) throw new Error(`Failed to fetch API plugin manifest: ${response.status} ${response.statusText}`); const manifest = await response.json(); if (manifest.schemaVersion !== 1 || manifest.kind !== "every-plugin/manifest") throw new Error("Unsupported API plugin manifest format"); return manifest; } function localApiContractSource(configDir) { return { key: "api", importName: "BaseApiContract", sourceFilePath: (0, node_path.join)(configDir, "api", "src", "contract.ts") }; } function localAuthContractSource(configDir) { return { key: "auth", importName: "authContract", sourceFilePath: (0, node_path.join)(configDir, "plugins", "auth", "src", "contract.ts") }; } async function remoteContractSource(opts) { const manifest = await fetchApiPluginManifest(opts.baseUrl); if (!manifest.contract) throw new Error(`Plugin manifest for ${manifest.plugin.name} does not advertise contract types`); const contractResponse = await fetchWithTimeout(`${trimTrailingSlash(opts.baseUrl)}/${manifest.contract.types.path.replace(/^\.\//, "")}`); if (!contractResponse.ok) throw new Error(`Failed to fetch contract types: ${contractResponse.status} ${contractResponse.statusText}`); const contractTypes = await contractResponse.text(); if (manifest.contract.types.sha256 && manifest.contract.types.sha256 !== sha256(contractTypes)) throw new Error("Fetched contract types failed checksum verification"); const generatedPath = (0, node_path.join)(opts.runtimeDir, opts.generatedSubdir, "contract.d.ts"); (0, node_fs.mkdirSync)((0, node_path.dirname)(generatedPath), { recursive: true }); writeFileIfChanged(generatedPath, contractTypes); return { key: opts.name, importName: `${sanitizeIdentifier(opts.name)}Contract`, sourceFilePath: generatedPath, generatedPath }; } async function fetchAuthExportTypes(opts) { if (!opts.manifest.additionalExports || opts.manifest.additionalExports.length === 0) return null; const authExportEntry = opts.manifest.additionalExports.find((entry) => entry.path.includes("auth-export") || entry.path.endsWith("auth-export.d.ts")); if (!authExportEntry) return null; const response = await fetchWithTimeout(`${trimTrailingSlash(opts.baseUrl)}/${authExportEntry.path.replace(/^\.\//, "")}`); if (!response.ok) { console.warn(`[API Contract] Failed to fetch auth export types: ${response.status}`); return null; } const content = await response.text(); if (authExportEntry.sha256 && authExportEntry.sha256 !== sha256(content)) { console.warn("[API Contract] Auth export types checksum mismatch"); return null; } const generatedPath = (0, node_path.join)(opts.runtimeDir, "auth", "auth-export.d.ts"); (0, node_fs.mkdirSync)((0, node_path.dirname)(generatedPath), { recursive: true }); writeFileIfChanged(generatedPath, content); return generatedPath; } function writeAuthTypesGen(targetPath, authExportPath) { const exportImportPath = toImportPath(targetPath, authExportPath); const content = [ `export type {`, ` Auth,`, ` AuthOrganizationContext,`, ` AuthOrganization,`, ` AuthOrganizationSummary,`, ` AuthOrganizationMember,`, ` AuthApiKey,`, ` AuthInvitation,`, ` GetActiveMemberInput,`, ` GetOrganizationInput,`, ` ListMembersInput,`, ` ListInvitationsInput,`, ` ListApiKeysInput,`, ` AuthServices,`, ` createAuthInstance,`, `} from "${exportImportPath}";`, `import type { InferOutput, ContractType as AuthContract } from "${toImportPath(targetPath, (0, node_path.join)((0, node_path.dirname)(authExportPath), "contract.d.ts"))}";`, `import type { Auth as BaseAuth } from "${exportImportPath}";`, "", "type RawAuthSession = InferOutput<\"getSession\">;", "type RawAuthRequestContext = InferOutput<\"getContext\">;", "type RawAuthActiveMember = InferOutput<\"getActiveMember\">;", "", "export type AuthSessionUser = NonNullable<RawAuthSession[\"user\"]> & {", " role?: string | null;", " isAnonymous?: boolean | null;", " walletAddress?: string | null;", " banned?: boolean | null;", "};", "export type AuthSessionData = NonNullable<RawAuthSession[\"session\"]> & {", " activeOrganizationId?: string | null;", "};", "export type AuthSession = {", " user: AuthSessionUser | null;", " session: AuthSessionData | null;", "};", "export type AuthRequestContext = RawAuthRequestContext;", "export type AuthActiveMember = RawAuthActiveMember;", "export type AuthBaseSession = BaseAuth[\"$Infer\"][\"Session\"];", "export type AuthContractType = AuthContract;", "" ].join("\n"); (0, node_fs.mkdirSync)((0, node_path.dirname)(targetPath), { recursive: true }); writeFileIfChanged(targetPath, content); } function writeFallbackAuthTypesGen(targetPath) { const content = [ "import type { Auth } from \"better-auth\";", "export type { Auth } from \"better-auth\";", "export type AuthSession = Auth[\"$Infer\"][\"Session\"];", "export type AuthSessionData = AuthSession;", "export type AuthSessionUser = NonNullable<AuthSession[\"user\"]>;", "export interface AuthOrganizationContext {", " activeOrganizationId: string | null;", " organization: { id: string; name: string; slug: string; logo?: string | null; metadata?: Record<string, unknown> } | null;", " member: { id: string; role: string } | null;", " isPersonal: boolean;", " hasOrganization: boolean;", "}", "export interface AuthRequestContext {", " user: AuthSessionUser | null;", " userId: string | null;", " isAuthenticated: boolean;", " authMethod: \"session\" | \"apiKey\" | \"anonymous\" | \"none\";", " near: {", " primaryAccountId: string | null;", " linkedAccounts: Array<{ accountId: string; network: string; publicKey: string; isPrimary: boolean }>;", " hasNearAccount: boolean;", " };", " organization: AuthOrganizationContext;", " organizations?: Array<{ id: string; role: string; name?: string; slug?: string }>;", "}", "export type AuthActiveMember = { id: string | null; role: string | null; organizationId: string | null };", "export type AuthOrganization = {", " id: string;", " name: string;", " slug: string;", " logo?: string | null;", " metadata?: Record<string, unknown> | null;", " createdAt: Date;", "};", "export type AuthOrganizationSummary = NonNullable<AuthOrganizationContext[\"organization\"]>;", "export type AuthOrganizationMember = NonNullable<AuthOrganizationContext[\"member\"]>;", "export type AuthApiKey = {", " id: string;", " name: string | null;", " prefix: string | null;", " start: string | null;", " expiresAt: Date | null;", " createdAt: Date;", " updatedAt: Date;", " metadata: unknown | null;", " permissions: Record<string, string[]> | null;", "};", "export type AuthInvitation = {", " id: string;", " organizationId: string;", " email: string;", " role: string | null;", " status: string;", " expiresAt: Date;", " inviterId: string;", "};", "export type GetActiveMemberInput = { organizationId?: string };", "export type GetOrganizationInput = { id: string };", "export type ListMembersInput = { organizationId: string };", "export type ListInvitationsInput = { organizationId: string };", "export type ListApiKeysInput = { organizationId?: string };", "export type createAuthInstance = never;", "export interface AuthServices {", " auth: Auth;", " db: unknown;", " driver: { close(): Promise<void> };", " handler: (req: Request) => Promise<Response>;", "}", "" ].join("\n"); (0, node_fs.mkdirSync)((0, node_path.dirname)(targetPath), { recursive: true }); writeFileIfChanged(targetPath, content); } async function resolveContractSource(opts) { if (opts.key === "api") { const localPath = opts.source && "localPath" in opts.source ? opts.source.localPath : void 0; if (localPath != null && localPath !== "") return { key: opts.key, importName: "BaseApiContract", sourceFilePath: (0, node_path.join)(localPath, "src", "contract.ts") }; if (!opts.baseUrl) return localApiContractSource(opts.configDir); } if (opts.key === "auth" && opts.localSourceFactory) { const localPath = opts.source && "localPath" in opts.source ? opts.source.localPath : void 0; if (localPath != null && localPath !== "") return { key: opts.key, importName: "authContract", sourceFilePath: (0, node_path.join)(localPath, "src", "contract.ts") }; if (!opts.baseUrl) return opts.localSourceFactory(opts.configDir); } if (opts.source && "localPath" in opts.source && opts.source.localPath != null && opts.source.localPath !== "") return { key: opts.key, importName: `${sanitizeIdentifier(opts.key)}Contract`, sourceFilePath: (0, node_path.join)(opts.source.localPath, "src", "contract.ts") }; return remoteContractSource({ configDir: opts.configDir, runtimeDir: opts.runtimeDir, name: opts.key, baseUrl: opts.baseUrl, generatedSubdir: opts.generatedSubdir }); } function writeGeneratedFiles(opts) { const baseSource = opts.sources.find((source) => source.key === "api"); const pluginSources = opts.pluginKeys.map((key) => opts.sources.find((entry) => entry.key === key)).filter((source) => Boolean(source)); if (!baseSource) throw new Error("API contract source is required to generate the aggregate contract"); const uiContractPath = (0, node_path.join)(opts.configDir, "ui", "src", "lib", "api-types.gen.ts"); const uiLines = []; for (const source of opts.sources) { const importPath = toImportPath(uiContractPath, source.sourceFilePath); uiLines.push(`import type { ContractType as ${source.importName} } from "${importPath}";`); } uiLines.push(""); const compositeParts = []; if (opts.authSource) compositeParts.push(`auth: ${opts.authSource.importName}`); for (const source of pluginSources) { const key = /^[$A-Z_][0-9A-Z_$]*$/i.test(source.key) ? source.key : JSON.stringify(source.key); compositeParts.push(`${key}: ${source.importName}`); } if (compositeParts.length === 0) uiLines.push(`export type ApiContract = ${baseSource.importName};`); else { uiLines.push(`export type ApiContract = ${baseSource.importName} & {`); for (const part of compositeParts) uiLines.push(` ${part};`); uiLines.push("};"); } (0, node_fs.mkdirSync)((0, node_path.dirname)(uiContractPath), { recursive: true }); writeFileIfChanged(uiContractPath, `${uiLines.join("\n")}\n`); const pluginsClientPath = (0, node_path.join)(opts.configDir, "api", "src", "lib", "plugins-types.gen.ts"); const pluginsClientLines = []; for (const source of pluginSources) { const importPath = toImportPath(pluginsClientPath, source.sourceFilePath); pluginsClientLines.push(`import type { ContractType as ${source.importName} } from "${importPath}";`); } if (opts.authSource) { const authImportPath = toImportPath(pluginsClientPath, opts.authSource.sourceFilePath); pluginsClientLines.push(`import type { ContractType as ${opts.authSource.importName} } from "${authImportPath}";`); } pluginsClientLines.push("import type { ContractRouterClient, AnyContractRouter } from \"@orpc/contract\";"); pluginsClientLines.push("type ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;"); pluginsClientLines.push(""); const allPluginSources = [...pluginSources]; if (opts.authSource) allPluginSources.push({ ...opts.authSource, key: "auth" }); if (allPluginSources.length === 0) pluginsClientLines.push("export type PluginsClient = Record<string, never>;"); else { pluginsClientLines.push("export type PluginsClient = {"); for (const source of allPluginSources) { const key = /^[$A-Z_][0-9A-Z_$]*$/i.test(source.key) ? source.key : JSON.stringify(source.key); pluginsClientLines.push(` ${key}: ClientFactory<${source.importName}>;`); } pluginsClientLines.push("};"); } (0, node_fs.mkdirSync)((0, node_path.dirname)(pluginsClientPath), { recursive: true }); writeFileIfChanged(pluginsClientPath, `${pluginsClientLines.join("\n")}\n`); const authTypeTargets = [(0, node_path.join)(opts.configDir, "ui", "src", "lib", "auth-types.gen.ts")]; const apiLibDir = (0, node_path.join)(opts.configDir, "api", "src", "lib"); if ((0, node_fs.existsSync)(apiLibDir)) authTypeTargets.push((0, node_path.join)(apiLibDir, "auth-types.gen.ts")); const hostLibDir = (0, node_path.join)(opts.configDir, "host", "src", "lib"); if ((0, node_fs.existsSync)((0, node_path.join)(opts.configDir, "host", "src"))) authTypeTargets.push((0, node_path.join)(hostLibDir, "auth-types.gen.ts")); if (opts.authExportPath) for (const authTypesPath of authTypeTargets) writeAuthTypesGen(authTypesPath, opts.authExportPath); else if (opts.authSource) for (const authTypesPath of authTypeTargets) writeFallbackAuthTypesGen(authTypesPath); return uiContractPath; } async function syncApiContractBridge(opts) { const runtimeDir = (0, node_path.join)(opts.configDir, ".bos", "generated"); const pluginEntries = Object.entries(opts.runtimeConfig.plugins ?? {}).sort(([a], [b]) => a.localeCompare(b)); const sources = []; let manifest = null; let generatedPath = null; let authSource = null; let authExportPath = null; const baseSource = await resolveContractSource({ configDir: opts.configDir, runtimeDir, key: "api", source: opts.runtimeConfig.api, baseUrl: opts.apiBaseUrl, generatedSubdir: "api" }); sources.push(baseSource); if (opts.runtimeConfig.auth) { authSource = await resolveContractSource({ configDir: opts.configDir, runtimeDir, key: "auth", source: opts.runtimeConfig.auth, baseUrl: opts.runtimeConfig.auth.url, generatedSubdir: "auth", localSourceFactory: localAuthContractSource }); sources.push(authSource); if (authSource.generatedPath) generatedPath = authSource.generatedPath; if (opts.runtimeConfig.auth.url && opts.runtimeConfig.auth.source !== "local") try { const authManifest = await fetchApiPluginManifest(opts.runtimeConfig.auth.url); const fetchedAuthExportPath = await fetchAuthExportTypes({ baseUrl: opts.runtimeConfig.auth.url, runtimeDir, manifest: authManifest }); if (fetchedAuthExportPath) authExportPath = fetchedAuthExportPath; } catch (error) { console.warn(`[API Contract] Failed to fetch auth additional exports: ${error instanceof Error ? error.message : String(error)}`); } if (!authExportPath) { const localAuthExport = (0, node_path.join)(opts.configDir, "plugins", "auth", "src", "auth-export.ts"); if ((0, node_fs.existsSync)(localAuthExport)) authExportPath = localAuthExport; else { const generatedAuthExport = (0, node_path.join)(runtimeDir, "auth", "auth-export.d.ts"); if ((0, node_fs.existsSync)(generatedAuthExport)) authExportPath = generatedAuthExport; } } } for (const [key, plugin] of pluginEntries) { if (!plugin.url && !plugin.localPath) { console.warn(`[API Contract] Skipping plugin "${key}" — no URL resolved (local path missing and no production URL configured)`); continue; } const source = await resolveContractSource({ configDir: opts.configDir, runtimeDir, key, source: plugin, baseUrl: plugin.url, generatedSubdir: `plugins/${key}` }); sources.push(source); if (source.generatedPath) generatedPath = source.generatedPath; } const allPluginKeys = pluginEntries.map(([key]) => key); writeGeneratedFiles({ configDir: opts.configDir, sources, pluginKeys: allPluginKeys, authSource, authExportPath }); if (opts.runtimeConfig.api.source !== "local") manifest = await fetchApiPluginManifest(opts.apiBaseUrl); return { bridgePath: (0, node_path.join)(opts.configDir, "ui", "src", "lib", "api-types.gen.ts"), generatedPath, manifest, source: opts.runtimeConfig.api.source }; } //#endregion exports.syncApiContractBridge = syncApiContractBridge; //# sourceMappingURL=api-contract.cjs.map