UNPKG

@withstudiocms/effect

Version:

Effect-TS Utilities for Astro

139 lines (138 loc) 4.51 kB
import { runEffect } from "../index.js"; import { getCorsHeaders, validateRequest } from "./utils/api-route.js"; const defineAPIRoute = (context) => async (fn) => await runEffect(fn(context)); const createEffectAPIRoute = (fn) => { return async (context) => { return await runEffect(fn(context)); }; }; const withEffectAPI = (fn, options = {}) => { return async (context) => { try { const corsHeaders = getCorsHeaders(context, options.cors); if (context.request.method === "OPTIONS") { return new Response(null, { status: 204, headers: corsHeaders }); } let processedContext = context; if (options.onBeforeEffect) { processedContext = await options.onBeforeEffect(context); } if (options.validate) { const validationError = await validateRequest(processedContext, options.validate); if (validationError) { const errorResponse = new Response( JSON.stringify({ error: "Validation failed", details: validationError }), { status: 400, headers: { "Content-Type": "application/json", ...corsHeaders } } ); return errorResponse; } } let effectPromise = runEffect(fn(processedContext)); if (options.timeout) { effectPromise = Promise.race([ effectPromise, new Promise( (_, reject) => setTimeout(() => reject(new Error("Request timeout")), options.timeout) ) ]); } const response = await effectPromise; if (corsHeaders && Object.keys(corsHeaders).length > 0) { Object.entries(corsHeaders).forEach(([key, value]) => { response.headers.set(key, value); }); } return options.onSuccess ? await options.onSuccess(response, processedContext) : response; } catch (error) { const corsHeaders = getCorsHeaders(context, options.cors); if (options.onError) { const errorResponse = await options.onError(error, context); Object.entries(corsHeaders).forEach(([key, value]) => { errorResponse.headers.set(key, value); }); return errorResponse; } return new Response( JSON.stringify({ error: error instanceof Error ? error.message : "Internal Server Error" }), { status: error instanceof Error && error.message === "Request timeout" ? 408 : 500, headers: { "Content-Type": "application/json", ...corsHeaders } } ); } }; }; const createEffectAPIRoutes = (handlers, config = {}) => { const routes = {}; for (const [method, handler] of Object.entries(handlers)) { if (handler) { const methodKey = method; const methodConfig = { ...config, ...config.methods?.[methodKey] || {} }; const { methods: _droppedMethods, ...finalConfig } = methodConfig; routes[methodKey] = withEffectAPI(handler, finalConfig); } } return routes; }; class EffectAPIRouteBuilder { handlers; config = {}; constructor(handlers = {}) { this.handlers = handlers; } // Set global configuration withGlobalConfig(config) { this.config = { ...this.config, ...config }; return this; } addHandler(method, handler, options) { const newHandlers = { ...this.handlers, [method]: handler }; const newBuilder = new EffectAPIRouteBuilder(newHandlers); newBuilder.config = { ...this.config }; if (options) { newBuilder.config.methods = { ...this.config.methods, [method]: options }; } return newBuilder; } // Add handlers with fluent interface - each method returns a new type that includes the added method get(handler, options) { return this.addHandler("GET", handler, options); } post(handler, options) { return this.addHandler("POST", handler, options); } put(handler, options) { return this.addHandler("PUT", handler, options); } delete(handler, options) { return this.addHandler("DELETE", handler, options); } patch(handler, options) { return this.addHandler("PATCH", handler, options); } all(handler, options) { return this.addHandler("ALL", handler, options); } // Build the final routes with precise typing build() { return createEffectAPIRoutes(this.handlers, this.config); } } export { EffectAPIRouteBuilder, createEffectAPIRoute, createEffectAPIRoutes, defineAPIRoute, withEffectAPI };