import { assemble } from "../utils/assemble.js"; import { responder } from "../../responder/index.js"; import { render } from "../../render/index.js"; import { functionContextClient } from "../../fn/functionContextClient.js"; import { functionContextFs } from "../../fn/functionContextFs.js"; import { functionContextNoop } from "../../fn/functionContextNoop.js"; import { readConfig } from "../utils/config.js"; import { createPrefixLog } from "../utils/prefixLog.js"; import log from "../utils/log.js"; /** * Middleware that serves documents by finding and assembling them on-demand. * * @param getDiscovery - Getter function for the current discovery result * @param ctx - CLI context for authentication * @param fnMode - Function context mode: 'api' (default) uses remote server, 'fs' uses local filesystem */ export const serveDocument = (getDiscovery, ctx, fnMode = 'api') => { // Create function context once (lazily initialized) let fn = null; let fnInitialized = false; const getFn = async () => { if (!fnInitialized) { fnInitialized = true; if (fnMode === 'fs') { fn = functionContextFs(getDiscovery); log.info(`\nšŸ“‚ Using filesystem function context (--fn=fs)`); } else { try { const cmdLog = createPrefixLog(ctx.cliName, 'serve'); const config = await readConfig(ctx, cmdLog); fn = functionContextClient(config.serverUrl, { username: config.username, password: config.password, }, { cache: true }); log.info(`\nšŸ” Connected to: ${config.serverUrl}`); log.info(`šŸ’¾ Cache enabled: ./cache`); } catch { log.info('\nāš ļø Not logged in - server functions (fn.getPage, etc.) will not be available'); fn = functionContextNoop(ctx.cliName); } } } return fn; }; return async (c, next) => { const discovery = getDiscovery(); const requestPath = c.req.path; // Find the document by trying to match the path const docInfo = findDocumentForPath(discovery, requestPath); if (!docInfo) { return next(); } try { // Create a resolver that looks up paths from the discovery graph const resolveDocumentPath = async (path) => { const doc = discovery.documents.get(path); return doc?.fsPath ?? null; }; // Assemble document on-demand from its filesystem location const document = await assemble(docInfo.fsPath, { resolveDocumentPath }); // Get function context (lazy init) const fn = await getFn(); return await responder({ path: requestPath, getDocument: async () => document, getRender: async (doc) => render(doc, { fn }), }); } catch (error) { log.error(`āŒ Error loading ${requestPath}:`, error); return new Response(`Error loading document: ${error.message}`, { status: 500, headers: { 'Content-Type': 'text/plain' }, }); } }; }; /** * Find the document info for a given request path. * Handles parsing asset suffixes and finding the matching document. */ function findDocumentForPath(discovery, requestPath) { // Try direct match first let docInfo = discovery.documents.get(requestPath); if (docInfo) { return { fsPath: docInfo.fsPath, path: requestPath }; } // Try matching by stripping potential asset suffixes // Assets are things like /data.json, /style.css, etc. // We'll try progressively shorter paths const parts = requestPath.split('/'); for (let i = parts.length - 1; i >= 0; i--) { const testPath = parts.slice(0, i + 1).join('/') || '/'; docInfo = discovery.documents.get(testPath); if (docInfo) { return { fsPath: docInfo.fsPath, path: testPath }; } } return null; } //# sourceMappingURL=serveDocument.js.map