UNPKG

@spfn/core

Version:

SPFN Framework Core - File-based routing, transactions, repository pattern

371 lines (366 loc) 12.6 kB
import { NextResponse } from 'next/server'; import { cookies } from 'next/headers.js'; // src/client/nextjs/proxy.ts // src/client/nextjs/interceptor.ts function matchPath(path, pattern) { if (pattern === "*") { return true; } if (pattern instanceof RegExp) { return pattern.test(path); } const regexPattern = pattern.replace(/\*/g, ".*").replace(/:[^/]+/g, "[^/]+").replace(/\//g, "\\/"); const regex = new RegExp(`^${regexPattern}$`); return regex.test(path); } function matchMethod(method, pattern) { if (!pattern) { return true; } if (typeof pattern === "string") { return method.toUpperCase() === pattern.toUpperCase(); } return pattern.some((m) => m.toUpperCase() === method.toUpperCase()); } function filterMatchingInterceptors(rules, path, method) { return rules.filter((rule) => { return matchPath(path, rule.pathPattern) && matchMethod(method, rule.method); }); } async function executeRequestInterceptors(context, interceptors) { let index = 0; const next = async () => { if (index >= interceptors.length) { return; } const interceptor = interceptors[index]; index++; await interceptor(context, next); }; await next(); } async function executeResponseInterceptors(context, interceptors) { let index = 0; const next = async () => { if (index >= interceptors.length) { return; } const interceptor = interceptors[index]; index++; await interceptor(context, next); }; await next(); } // src/client/nextjs/registry.ts var InterceptorRegistry = class { interceptors = /* @__PURE__ */ new Map(); /** * Register interceptors for a package * * @param packageName - Unique package identifier (e.g., 'auth', 'storage') * @param interceptors - Array of interceptor rules * * @example * ```typescript * registerInterceptors('auth', [ * { * pathPattern: '/_auth/*', * request: async (ctx, next) => { ... } * } * ]); * ``` */ register(packageName, interceptors) { console.log(`[SPFN Registry] Registering ${interceptors.length} interceptors for package "${packageName}"`); if (this.interceptors.has(packageName)) { console.warn( `[SPFN Registry] Interceptors for "${packageName}" already registered. Overwriting.` ); } this.interceptors.set(packageName, interceptors); console.log(`[SPFN Registry] Successfully registered interceptors for "${packageName}". Total packages: ${this.getPackageNames().length}`); } /** * Get all registered interceptors * * @param exclude - Package names to exclude * @returns Flat array of all interceptor rules */ getAll(exclude = []) { const all = []; for (const [packageName, interceptors] of this.interceptors.entries()) { if (!exclude.includes(packageName)) { all.push(...interceptors); } } return all; } /** * Get interceptors for specific package * * @param packageName - Package identifier * @returns Interceptor rules or undefined */ get(packageName) { return this.interceptors.get(packageName); } /** * Get list of registered package names */ getPackageNames() { return Array.from(this.interceptors.keys()); } /** * Check if package has registered interceptors */ has(packageName) { return this.interceptors.has(packageName); } /** * Unregister interceptors for a package * * @param packageName - Package identifier */ unregister(packageName) { this.interceptors.delete(packageName); } /** * Clear all registered interceptors * * Useful for testing */ clear() { this.interceptors.clear(); } /** * Get total count of registered interceptors */ count() { let total = 0; for (const interceptors of this.interceptors.values()) { total += interceptors.length; } return total; } }; var interceptorRegistry = new InterceptorRegistry(); function registerInterceptors(packageName, interceptors) { interceptorRegistry.register(packageName, interceptors); } // src/client/nextjs/proxy.ts function getApiUrl(config) { return config?.apiUrl || process.env.SERVER_API_URL || process.env.SPFN_API_URL || "http://localhost:8790"; } async function handleProxy(request, context, method, config) { try { const params = "then" in context.params ? await context.params : context.params; const pathSegments = params.path; const path = `/${pathSegments.join("/")}`; const query = {}; request.nextUrl.searchParams.forEach((value, key) => { const existing = query[key]; if (existing) { query[key] = Array.isArray(existing) ? [...existing, value] : [existing, value]; } else { query[key] = value; } }); const cookieStore = await cookies(); const cookieMap = /* @__PURE__ */ new Map(); cookieStore.getAll().forEach((cookie) => { cookieMap.set(cookie.name, cookie.value); }); const headers = { "Content-Type": "application/json" }; let body = void 0; const contentType = request.headers.get("Content-Type"); if (method === "POST" || method === "PUT" || method === "PATCH") { if (contentType?.includes("application/json")) { const text = await request.text(); if (text) { try { body = JSON.parse(text); } catch (error) { body = text; } } } else if (contentType?.includes("multipart/form-data")) { body = await request.formData(); } else { body = await request.text(); } } const requestContext = { path, method, headers, body, query, cookies: cookieMap, request, metadata: {} }; const rules = config?.interceptors || []; console.log(`[SPFN Proxy] Handling ${method} ${path}`); console.log(`[SPFN Proxy] Total available interceptor rules: ${rules.length}`); const matchedRules = filterMatchingInterceptors(rules, path, method); console.log(`[SPFN Proxy] Matched ${matchedRules.length} interceptor rules for this request`); const requestInterceptors = matchedRules.map((rule) => rule.request).filter((interceptor) => !!interceptor); console.log(`[SPFN Proxy] Executing ${requestInterceptors.length} request interceptors`); await executeRequestInterceptors(requestContext, requestInterceptors); const apiUrl = getApiUrl(config); const queryString = Object.entries(query).flatMap( ([key, value]) => Array.isArray(value) ? value.map((v) => `${key}=${v}`) : [`${key}=${value}`] ).join("&"); const url = `${apiUrl}${path}${queryString ? `?${queryString}` : ""}`; const init = { method, headers: requestContext.headers }; if (requestContext.body !== void 0) { if (requestContext.body instanceof FormData) { init.body = requestContext.body; delete requestContext.headers["Content-Type"]; } else if (typeof requestContext.body === "string") { init.body = requestContext.body; } else { init.body = JSON.stringify(requestContext.body); } } if (config?.debug) { console.log(`[SPFN Proxy] Calling ${url}`); console.log(`[SPFN Proxy] Headers:`, requestContext.headers); } const response = await fetch(url, init); const responseText = await response.text(); let responseBody; try { responseBody = JSON.parse(responseText); } catch (error) { responseBody = responseText; } const responseContext = { path, method, request: { headers: requestContext.headers, body: requestContext.body }, response: { status: response.status, statusText: response.statusText, headers: response.headers, body: responseBody }, setCookies: [], metadata: requestContext.metadata // Pass metadata from request }; const responseInterceptors = matchedRules.map((rule) => rule.response).filter((interceptor) => !!interceptor); if (config?.debug) { console.log(`[SPFN Proxy] Response interceptors: ${responseInterceptors.length}`); } await executeResponseInterceptors(responseContext, responseInterceptors); const nextResponse = NextResponse.json(responseContext.response.body, { status: responseContext.response.status, statusText: responseContext.response.statusText }); for (const cookie of responseContext.setCookies) { const cookieString = [`${cookie.name}=${cookie.value}`]; if (cookie.options?.httpOnly) { cookieString.push("HttpOnly"); } if (cookie.options?.secure) { cookieString.push("Secure"); } if (cookie.options?.sameSite) { cookieString.push(`SameSite=${cookie.options.sameSite}`); } if (cookie.options?.maxAge !== void 0) { cookieString.push(`Max-Age=${cookie.options.maxAge}`); } if (cookie.options?.path) { cookieString.push(`Path=${cookie.options.path}`); } if (cookie.options?.domain) { cookieString.push(`Domain=${cookie.options.domain}`); } nextResponse.headers.append("Set-Cookie", cookieString.join("; ")); } const setCookieHeaders = response.headers.get("Set-Cookie"); if (setCookieHeaders) { nextResponse.headers.append("Set-Cookie", setCookieHeaders); } if (config?.debug) { console.log(`[SPFN Proxy] Response: ${responseContext.response.status}`); } return nextResponse; } catch (error) { console.error("[SPFN Proxy] Error:", error); return NextResponse.json( { success: false, error: { code: "PROXY_ERROR", message: error instanceof Error ? error.message : "Unknown proxy error" } }, { status: 500 } ); } } function createProxy(config) { const finalConfig = { autoDiscoverInterceptors: true, ...config }; let allInterceptors = []; console.log("[SPFN Proxy] Creating proxy with config:", { autoDiscoverInterceptors: finalConfig.autoDiscoverInterceptors, customInterceptors: finalConfig.interceptors?.length || 0, disableAutoInterceptors: finalConfig.disableAutoInterceptors || [] }); if (finalConfig.autoDiscoverInterceptors) { const registeredPackages = interceptorRegistry.getPackageNames(); console.log("[SPFN Proxy] Registered packages in registry:", registeredPackages); const autoInterceptors = interceptorRegistry.getAll( finalConfig.disableAutoInterceptors || [] ); allInterceptors.push(...autoInterceptors); console.log("[SPFN Proxy] Auto-discovered interceptors from packages:", registeredPackages); console.log(`[SPFN Proxy] Total auto-discovered interceptors: ${autoInterceptors.length}`); } if (finalConfig.interceptors) { allInterceptors.push(...finalConfig.interceptors); console.log(`[SPFN Proxy] Custom interceptors: ${finalConfig.interceptors.length}`); } const proxyConfig = { ...finalConfig, interceptors: allInterceptors }; console.log(`[SPFN Proxy] Total interceptors loaded: ${allInterceptors.length}`); return { GET: async (request, context) => handleProxy(request, context, "GET", proxyConfig), POST: async (request, context) => handleProxy(request, context, "POST", proxyConfig), PUT: async (request, context) => handleProxy(request, context, "PUT", proxyConfig), PATCH: async (request, context) => handleProxy(request, context, "PATCH", proxyConfig), DELETE: async (request, context) => handleProxy(request, context, "DELETE", proxyConfig) }; } var defaultProxy = null; function getDefaultProxy() { if (!defaultProxy) { console.log("[SPFN Proxy] Initializing default proxy with auto-discovery"); defaultProxy = createProxy(); } return defaultProxy; } var GET = async (request, context) => getDefaultProxy().GET(request, context); var POST = async (request, context) => getDefaultProxy().POST(request, context); var PUT = async (request, context) => getDefaultProxy().PUT(request, context); var PATCH = async (request, context) => getDefaultProxy().PATCH(request, context); var DELETE = async (request, context) => getDefaultProxy().DELETE(request, context); export { DELETE, GET, PATCH, POST, PUT, createProxy, executeRequestInterceptors, executeResponseInterceptors, filterMatchingInterceptors, interceptorRegistry, matchMethod, matchPath, registerInterceptors }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map