@walkeros/server-source-fetch 4.4.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @walkeros/server-source-fetch
2
2
 
3
+ ## 4.5.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 79cdcb0: Server sources answer rejected client input with 4xx JSON instead of
8
+ unhandled errors or 500s: unparseable bodies return 400 at the HTTP boundary,
9
+ and invalid events return 400 with the reason wherever the response is still
10
+ open (synchronous handling; in the express default respond-first mode the 200
11
+ ack has already been sent, so the rejection surfaces as a warn and a counter
12
+ instead) (`push` resolves `{ok: false, invalid: true, error}`). Genuine
13
+ pipeline failures return 500 instead of `success: true`. Invalid input now
14
+ counts on `collector.status.sources.<id>.rejected` instead of inflating
15
+ `status.failed`. The express source no longer sends `X-Powered-By` and now
16
+ sets `X-Content-Type-Options: nosniff` on every response.
17
+ - 756b571: Server sources now share one request scope and one event envelope, so
18
+ `config.ingest` paths and POST body forms behave identically on Express,
19
+ Fetch, Lambda and Cloud Functions. Batches and bare arrays are accepted
20
+ everywhere, and a destination before-chain fan-out no longer drops all but the
21
+ first event. Breaking: AWS `requestContext.*` moves under `raw.*`, Express
22
+ drops `protocol` and `hostname`, and Fetch `{ fn }` header mappings become
23
+ `{ key: 'headers.*' }`. See the migration guide.
24
+ - Updated dependencies [63845bb]
25
+ - Updated dependencies [79cdcb0]
26
+ - Updated dependencies [756b571]
27
+ - @walkeros/core@4.5.0
28
+ - @walkeros/collector@4.5.0
29
+
3
30
  ## 4.4.0
4
31
 
5
32
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{SourceFetch:()=>types_exports,TRANSPARENT_GIF_BASE64:()=>TRANSPARENT_GIF_BASE64,createCorsHeaders:()=>createCorsHeaders,createJsonResponse:()=>createJsonResponse,createPixelResponse:()=>createPixelResponse,default:()=>index_default,matchPath:()=>matchPath,sourceFetch:()=>sourceFetch}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core=require("@walkeros/core");function createCorsHeaders(corsConfig=!0,requestOrigin){const headers=new Headers;if(!1===corsConfig)return headers;if(!0===corsConfig)headers.set("Access-Control-Allow-Origin","*"),headers.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),headers.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){let origin;origin=Array.isArray(corsConfig.origin)?requestOrigin&&corsConfig.origin.includes(requestOrigin)?requestOrigin:corsConfig.origin[0]:corsConfig.origin,headers.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&headers.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&headers.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&headers.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&headers.set("Access-Control-Max-Age",String(corsConfig.maxAge))}return headers}var TRANSPARENT_GIF_BASE64="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function createPixelResponse(corsHeaders){const binaryString=atob(TRANSPARENT_GIF_BASE64),bytes=new Uint8Array(binaryString.length);for(let i=0;i<binaryString.length;i++)bytes[i]=binaryString.charCodeAt(i);const headers=new Headers(corsHeaders);return headers.set("Content-Type","image/gif"),headers.set("Cache-Control","no-cache, no-store, must-revalidate"),new Response(bytes,{status:200,headers:headers})}function createJsonResponse(body,status=200,corsHeaders){const headers=new Headers(corsHeaders);return headers.set("Content-Type","application/json"),new Response(JSON.stringify(body),{status:status,headers:headers})}function matchPath(requestPath,pattern){if(pattern.endsWith("/*")){const prefix=pattern.slice(0,-2);return requestPath===prefix||requestPath.startsWith(prefix+"/")}return requestPath===pattern}var types_exports={},sourceFetch=async context=>{const{config:config={},env:env}=context,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,maxRequestSize:userSettings.maxRequestSize??102400,maxBatchSize:userSettings.maxBatchSize??100,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},{logger:logger}=env;return{type:"fetch",config:{...config,settings:settings},push:async request=>{Date.now();try{const url=new URL(request.url),method=request.method.toUpperCase(),origin=request.headers.get("Origin"),corsHeaders=createCorsHeaders(settings.cors,origin),matchedRoute=settings.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]}).find(route=>matchPath(url.pathname,route.path));return matchedRoute?"OPTIONS"===method?new Response(null,{status:204,headers:corsHeaders}):matchedRoute.methods.includes(method)?await context.withScope(request,void 0,async scopeEnv=>{const envPush=scopeEnv.push;if("GET"===method){const parsedData=(0,import_core.requestToData)(url.search);return parsedData&&(0,import_core.isObject)(parsedData)&&await envPush(parsedData),createPixelResponse(corsHeaders)}if("POST"===method){const contentLength=request.headers.get("Content-Length");if(contentLength){const size=parseInt(contentLength,10);if(size>settings.maxRequestSize)return logger.error("Request too large",{size:size,limit:settings.maxRequestSize}),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders)}let eventData,bodyText,rawBody=!1;try{if(bodyText=await request.text(),bodyText.length>settings.maxRequestSize)return logger.error("Request body too large",{size:bodyText.length,limit:settings.maxRequestSize}),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders);eventData=JSON.parse(bodyText)}catch{eventData={},rawBody=!0}if((0,import_core.isDefined)(eventData)&&(0,import_core.isObject)(eventData)||(eventData={},rawBody=!0),rawBody){const result2=await processEvent(eventData,envPush);return result2.error?(logger.error("Event processing failed",{error:result2.error}),createJsonResponse({success:!1,error:result2.error},400,corsHeaders)):createJsonResponse({success:!0,id:result2.id,timestamp:Date.now()},200,corsHeaders)}const validData=eventData;if("batch"in validData&&Array.isArray(validData.batch)){const batch=validData.batch;if(batch.length>settings.maxBatchSize)return logger.error("Batch too large",{size:batch.length,limit:settings.maxBatchSize}),createJsonResponse({success:!1,error:`Batch too large. Maximum size: ${settings.maxBatchSize} events`},400,corsHeaders);const results=await async function(events,push,logger){const results={successful:0,failed:0,ids:[],errors:[]};for(let i=0;i<events.length;i++){const event=events[i];try{const result=await push(event);result?.event?.id&&results.ids.push(result.event.id),results.successful++}catch(error){results.failed++,results.errors.push({index:i,error:error instanceof Error?error.message:"Unknown error"}),logger.error(`Batch event ${i} processing failed`,error)}}return results}(batch,envPush,logger);return results.failed>0?createJsonResponse({success:!1,processed:results.successful,failed:results.failed,errors:results.errors},207,corsHeaders):createJsonResponse({success:!0,processed:results.successful,ids:results.ids},200,corsHeaders)}const result=await processEvent(eventData,envPush);return result.error?(logger.error("Event processing failed",{error:result.error}),createJsonResponse({success:!1,error:result.error},400,corsHeaders)):createJsonResponse({success:!0,id:result.id,timestamp:Date.now()},200,corsHeaders)}return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders)}):createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders):createJsonResponse({success:!1,error:"Not found"},404,corsHeaders)}catch(error){logger.error("Internal server error",error);const corsHeaders=createCorsHeaders(settings.cors);return createJsonResponse({success:!1,error:error instanceof Error?error.message:"Internal server error"},500,corsHeaders)}}}};async function processEvent(event,push){try{const result=await push(event);return{id:result?.event?.id}}catch(error){return{error:error instanceof Error?error.message:"Unknown error"}}}var index_default=sourceFetch;//# sourceMappingURL=index.js.map
1
+ "use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{SourceFetch:()=>types_exports,TRANSPARENT_GIF_BASE64:()=>TRANSPARENT_GIF_BASE64,createCorsHeaders:()=>createCorsHeaders,createJsonResponse:()=>createJsonResponse,createPixelResponse:()=>createPixelResponse,default:()=>index_default,matchPath:()=>matchPath,sourceFetch:()=>sourceFetch}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core2=require("@walkeros/core");function createCorsHeaders(corsConfig=!0,requestOrigin){const headers=new Headers;if(!1===corsConfig)return headers;if(!0===corsConfig)headers.set("Access-Control-Allow-Origin","*"),headers.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),headers.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){let origin;origin=Array.isArray(corsConfig.origin)?requestOrigin&&corsConfig.origin.includes(requestOrigin)?requestOrigin:corsConfig.origin[0]:corsConfig.origin,headers.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&headers.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&headers.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&headers.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&headers.set("Access-Control-Max-Age",String(corsConfig.maxAge))}return headers}var TRANSPARENT_GIF_BASE64="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function createPixelResponse(corsHeaders){const binaryString=atob(TRANSPARENT_GIF_BASE64),bytes=new Uint8Array(binaryString.length);for(let i=0;i<binaryString.length;i++)bytes[i]=binaryString.charCodeAt(i);const headers=new Headers(corsHeaders);return headers.set("Content-Type","image/gif"),headers.set("Cache-Control","no-cache, no-store, must-revalidate"),new Response(bytes,{status:200,headers:headers})}function createJsonResponse(body,status=200,corsHeaders){const headers=new Headers(corsHeaders);return headers.set("Content-Type","application/json"),new Response(JSON.stringify(body),{status:status,headers:headers})}function matchPath(requestPath,pattern){if(pattern.endsWith("/*")){const prefix=pattern.slice(0,-2);return requestPath===prefix||requestPath.startsWith(prefix+"/")}return requestPath===pattern}var import_core=require("@walkeros/core");var types_exports={},sourceFetch=async context=>{const{config:config={},env:env}=context,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,maxRequestSize:userSettings.maxRequestSize??102400,maxBatchSize:userSettings.maxBatchSize??100,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},{logger:logger}=env,countRejected=()=>{const sources=context.collector.status?.sources;if(!sources)return;sources[context.id]||(sources[context.id]={count:0,duration:0});const sourceStatus=sources[context.id];sourceStatus.rejected=(sourceStatus.rejected??0)+1};return{type:"fetch",config:{...config,settings:settings},push:async request=>{Date.now();try{const url=new URL(request.url),method=request.method.toUpperCase(),origin=request.headers.get("Origin"),corsHeaders=createCorsHeaders(settings.cors,origin),matchedRoute=settings.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]}).find(route=>matchPath(url.pathname,route.path));if(!matchedRoute)return createJsonResponse({success:!1,error:"Not found"},404,corsHeaders);if("OPTIONS"===method)return new Response(null,{status:204,headers:corsHeaders});if(!matchedRoute.methods.includes(method))return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders);const tooLarge=()=>(countRejected(),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders));let parsedBody;if("POST"===method){const contentLength=request.headers.get("Content-Length");if(contentLength){const size=parseInt(contentLength,10);if(size>settings.maxRequestSize)return logger.debug("Request too large",{size:size,limit:settings.maxRequestSize}),tooLarge()}const bodyText=await request.text(),bodySize=(new TextEncoder).encode(bodyText).byteLength;if(bodySize>settings.maxRequestSize)return logger.debug("Request body too large",{size:bodySize,limit:settings.maxRequestSize}),tooLarge();parsedBody=(0,import_core2.normalizeBody)(bodyText)}const scope=function(request,body){const url=new URL(request.url);return{method:request.method.toUpperCase(),url:request.url,path:url.pathname,query:(0,import_core.normalizeQuery)(url.search),headers:(0,import_core.normalizeHeaders)(request.headers),body:body,raw:request}}(request,"POST"===method?parsedBody:void 0);return await context.withScope(scope,void 0,async scopeEnv=>{const envPush=scopeEnv.push;if("GET"===method){const parsedData=(0,import_core2.requestToData)(url.search);return parsedData&&(0,import_core2.isObject)(parsedData)&&await envPush(parsedData),createPixelResponse(corsHeaders)}if("POST"===method){const events=(0,import_core2.toEventList)(parsedBody);if((0,import_core2.isBatchBody)(parsedBody)){const batch=events;if(batch.length>settings.maxBatchSize)return countRejected(),logger.debug("Batch too large",{size:batch.length,limit:settings.maxBatchSize}),createJsonResponse({success:!1,error:`Batch too large. Maximum size: ${settings.maxBatchSize} events`},400,corsHeaders);const outcomes=await async function(events,push,logger){const outcomes=[];for(let i=0;i<events.length;i++){const event=events[i];try{const result=await push(event),outcome=(0,import_core2.pushResultToOutcome)(result??{});outcome.error&&logger.warn(`Batch event ${i} not processed`),outcomes.push(outcome)}catch(error){outcomes.push({error:error instanceof Error?error.message:"Unknown error"}),logger.warn(`Batch event ${i} processing failed`,error)}}return outcomes}(batch,envPush,logger),{status:status,body:body}=(0,import_core2.batchResponse)(outcomes);return createJsonResponse(body,status,corsHeaders)}const result=await async function(event,push,logger){try{const result=await push(event);return!1===result?.ok?!0===result.invalid?{error:result.error??"Invalid event",status:400}:{error:result.error??"Event was not processed",status:500}:{id:result?.event?.id}}catch(error){const message=error instanceof Error?error.message:"Unknown error";return logger.error("Event processing failed",{error:message}),{error:message,status:500}}}(events[0]??{},envPush,logger);return result.error?(logger.warn("Event processing failed",{error:result.error}),createJsonResponse({success:!1,error:result.error},result.status??500,corsHeaders)):createJsonResponse({success:!0,id:result.id,timestamp:Date.now()},200,corsHeaders)}return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders)})}catch(error){logger.error("Internal server error",error);const corsHeaders=createCorsHeaders(settings.cors);return createJsonResponse({success:!1,error:error instanceof Error?error.message:"Internal server error"},500,corsHeaders)}}}};var index_default=sourceFetch;//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/types.ts"],"sourcesContent":["import { requestToData, isObject, isDefined } from '@walkeros/core';\nimport type { WalkerOS, Collector, Source } from '@walkeros/core';\nimport type { FetchSource, Types } from './types';\nimport {\n createCorsHeaders,\n createPixelResponse,\n createJsonResponse,\n matchPath,\n} from './utils';\n\nexport const sourceFetch: Source.Init<Types> = async (context) => {\n const { config = {}, env } = context;\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n maxRequestSize: userSettings.maxRequestSize ?? 102400,\n maxBatchSize: userSettings.maxBatchSize ?? 100,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n const { logger } = env;\n\n const push = async (request: Request): Promise<Response> => {\n const startTime = Date.now();\n void startTime;\n\n try {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const origin = request.headers.get('Origin');\n const corsHeaders = createCorsHeaders(settings.cors, origin);\n\n // Resolve route configs\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n // Match request path against configured routes\n const matchedRoute = resolvedPaths.find((route) =>\n matchPath(url.pathname, route.path),\n );\n\n if (!matchedRoute) {\n return createJsonResponse(\n { success: false, error: 'Not found' },\n 404,\n corsHeaders,\n );\n }\n\n // OPTIONS (CORS preflight - no logging, routine)\n if (method === 'OPTIONS') {\n return new Response(null, { status: 204, headers: corsHeaders });\n }\n\n // Check method is allowed for this route\n if (!matchedRoute.methods.includes(method as 'GET' | 'POST')) {\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n }\n\n // Per-request scope: each fetch invocation gets its own ingest.\n // Fetch sources return a Response directly, not via async respond.\n return await context.withScope(request, undefined, async (scopeEnv) => {\n const envPush = scopeEnv.push;\n\n // GET (pixel tracking - no logging, routine)\n if (method === 'GET') {\n const parsedData = requestToData(url.search);\n if (parsedData && isObject(parsedData)) {\n await envPush(parsedData);\n }\n return createPixelResponse(corsHeaders);\n }\n\n // POST\n if (method === 'POST') {\n // Check request size\n const contentLength = request.headers.get('Content-Length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > settings.maxRequestSize) {\n logger.error('Request too large', {\n size,\n limit: settings.maxRequestSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n }\n }\n\n let eventData: unknown;\n let bodyText: string;\n let rawBody = false;\n\n try {\n bodyText = await request.text();\n\n // Check actual body size\n if (bodyText.length > settings.maxRequestSize) {\n logger.error('Request body too large', {\n size: bodyText.length,\n limit: settings.maxRequestSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n }\n\n eventData = JSON.parse(bodyText);\n } catch {\n // Non-JSON body: push empty event for source.before transformers\n eventData = {};\n rawBody = true;\n }\n\n if (!isDefined(eventData) || !isObject(eventData)) {\n // Non-object body: push empty event for source.before transformers\n eventData = {};\n rawBody = true;\n }\n\n // Raw body: push empty event directly, skip validation\n if (rawBody) {\n const result = await processEvent(\n eventData as WalkerOS.DeepPartialEvent,\n envPush,\n );\n if (result.error) {\n logger.error('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n 400,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n // Check for batch (eventData is a validated object at this point)\n const validData = eventData as Record<string, unknown>;\n const isBatch =\n 'batch' in validData && Array.isArray(validData.batch);\n\n if (isBatch) {\n const batch = validData.batch as unknown[];\n\n if (batch.length > settings.maxBatchSize) {\n logger.error('Batch too large', {\n size: batch.length,\n limit: settings.maxBatchSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Batch too large. Maximum size: ${settings.maxBatchSize} events`,\n },\n 400,\n corsHeaders,\n );\n }\n\n const results = await processBatch(batch, envPush, logger);\n\n if (results.failed > 0) {\n return createJsonResponse(\n {\n success: false,\n processed: results.successful,\n failed: results.failed,\n errors: results.errors,\n },\n 207,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n {\n success: true,\n processed: results.successful,\n ids: results.ids,\n },\n 200,\n corsHeaders,\n );\n }\n\n // Forward event directly — validation is not the source's responsibility.\n const result = await processEvent(\n eventData as WalkerOS.DeepPartialEvent,\n envPush,\n );\n if (result.error) {\n logger.error('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n 400,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n });\n } catch (error) {\n logger.error('Internal server error', error);\n const corsHeaders = createCorsHeaders(settings.cors);\n return createJsonResponse(\n {\n success: false,\n error:\n error instanceof Error ? error.message : 'Internal server error',\n },\n 500,\n corsHeaders,\n );\n }\n };\n\n return { type: 'fetch', config: { ...config, settings }, push };\n};\n\nasync function processEvent(\n event: WalkerOS.DeepPartialEvent,\n push: Collector.PushFn,\n): Promise<{ id?: string; error?: string }> {\n try {\n const result = await push(event);\n return { id: result?.event?.id };\n } catch (error) {\n return { error: error instanceof Error ? error.message : 'Unknown error' };\n }\n}\n\nasync function processBatch(\n events: unknown[],\n push: Collector.PushFn,\n logger: Types['env']['logger'],\n): Promise<{\n successful: number;\n failed: number;\n ids: string[];\n errors: Array<{ index: number; error: string }>;\n}> {\n const results = {\n successful: 0,\n failed: 0,\n ids: [] as string[],\n errors: [] as Array<{ index: number; error: string }>,\n };\n\n for (let i = 0; i < events.length; i++) {\n const event = events[i];\n\n try {\n const result = await push(event as WalkerOS.DeepPartialEvent);\n if (result?.event?.id) {\n results.ids.push(result.event.id);\n }\n results.successful++;\n } catch (error) {\n results.failed++;\n results.errors.push({\n index: i,\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n logger.error(`Batch event ${i} processing failed`, error);\n }\n }\n\n return results;\n}\n\nexport type * from './types';\nexport * as SourceFetch from './types';\nexport * from './utils';\n\nexport default sourceFetch;\n","import type { CorsOptions } from './schemas';\n\nexport function createCorsHeaders(\n corsConfig: boolean | CorsOptions = true,\n requestOrigin?: string | null,\n): Headers {\n const headers = new Headers();\n\n if (corsConfig === false) return headers;\n\n if (corsConfig === true) {\n headers.set('Access-Control-Allow-Origin', '*');\n headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n headers.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n if (corsConfig.origin) {\n let origin: string;\n if (Array.isArray(corsConfig.origin)) {\n origin =\n requestOrigin && corsConfig.origin.includes(requestOrigin)\n ? requestOrigin\n : corsConfig.origin[0];\n } else {\n origin = corsConfig.origin;\n }\n headers.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n headers.set(\n 'Access-Control-Allow-Methods',\n corsConfig.methods.join(', '),\n );\n }\n\n if (corsConfig.headers) {\n headers.set(\n 'Access-Control-Allow-Headers',\n corsConfig.headers.join(', '),\n );\n }\n\n if (corsConfig.credentials) {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n headers.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n\n return headers;\n}\n\nexport const TRANSPARENT_GIF_BASE64 =\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\nexport function createPixelResponse(corsHeaders?: Headers): Response {\n const binaryString = atob(TRANSPARENT_GIF_BASE64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'image/gif');\n headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n return new Response(bytes, { status: 200, headers });\n}\n\nexport function createJsonResponse(\n body: unknown,\n status = 200,\n corsHeaders?: Headers,\n): Response {\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'application/json');\n\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * Match a request pathname against a route pattern.\n * Supports exact matches and wildcard patterns (e.g., /api/*).\n */\nexport function matchPath(requestPath: string, pattern: string): boolean {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n return requestPath === prefix || requestPath.startsWith(prefix + '/');\n }\n return requestPath === pattern;\n}\n","import type { WalkerOS, Source as CoreSource } from '@walkeros/core';\nimport type {\n SettingsSchema,\n CorsOptionsSchema,\n RouteConfigSchema,\n} from './schemas';\nimport type { z } from '@walkeros/core/dev';\n\ndeclare module '@walkeros/core' {\n interface SourceMap {\n fetch: { type: 'fetch'; platform: 'server' };\n }\n}\n\nexport type Settings = z.infer<typeof SettingsSchema>;\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\nexport type RouteConfig = z.infer<typeof RouteConfigSchema>;\nexport type RouteMethod = 'GET' | 'POST';\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {}\n\nexport type Push = (request: Request) => Response | Promise<Response>;\n\nexport interface Env extends CoreSource.Env {\n request?: Request;\n}\n\nexport type Types = CoreSource.Types<\n Settings,\n Mapping,\n Push,\n Env,\n InitSettings\n>;\nexport type Config = CoreSource.Config<Types>;\nexport type PartialConfig = CoreSource.PartialConfig<Types>;\n\nexport interface FetchSource extends Omit<CoreSource.Instance<Types>, 'push'> {\n push: Push;\n}\n\nexport interface EventResponse {\n success: boolean;\n id?: string;\n timestamp?: number;\n error?: string;\n}\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/scope.ts","../src/types.ts"],"sourcesContent":["import {\n normalizeBody,\n requestToData,\n isObject,\n toEventList,\n isBatchBody,\n batchResponse,\n pushResultToOutcome,\n} from '@walkeros/core';\nimport type {\n WalkerOS,\n Collector,\n Logger,\n Source,\n EventOutcome,\n} from '@walkeros/core';\nimport type { FetchSource, Types } from './types';\nimport {\n createCorsHeaders,\n createPixelResponse,\n createJsonResponse,\n matchPath,\n} from './utils';\nimport { buildScope } from './scope';\n\nexport const sourceFetch: Source.Init<Types> = async (context) => {\n const { config = {}, env } = context;\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n maxRequestSize: userSettings.maxRequestSize ?? 102400,\n maxBatchSize: userSettings.maxBatchSize ?? 100,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n const { logger } = env;\n\n // Rejection volume belongs in collector.status, not in per-request logs.\n // Lazy entry creation mirrors the collector's own sources bookkeeping.\n // Guarded: unit harnesses may run the handler with a bare collector stub.\n const countRejected = (): void => {\n const sources = context.collector.status?.sources;\n if (!sources) return;\n if (!sources[context.id]) {\n sources[context.id] = { count: 0, duration: 0 };\n }\n const sourceStatus = sources[context.id];\n sourceStatus.rejected = (sourceStatus.rejected ?? 0) + 1;\n };\n\n const push = async (request: Request): Promise<Response> => {\n const startTime = Date.now();\n void startTime;\n\n try {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const origin = request.headers.get('Origin');\n const corsHeaders = createCorsHeaders(settings.cors, origin);\n\n // Resolve route configs\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n // Match request path against configured routes\n const matchedRoute = resolvedPaths.find((route) =>\n matchPath(url.pathname, route.path),\n );\n\n if (!matchedRoute) {\n return createJsonResponse(\n { success: false, error: 'Not found' },\n 404,\n corsHeaders,\n );\n }\n\n // OPTIONS (CORS preflight - no logging, routine)\n if (method === 'OPTIONS') {\n return new Response(null, { status: 204, headers: corsHeaders });\n }\n\n // Check method is allowed for this route\n if (!matchedRoute.methods.includes(method as 'GET' | 'POST')) {\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n }\n\n // Per-request scope: each fetch invocation gets its own ingest.\n // Fetch sources return a Response directly, not via async respond.\n // Size guards run before the scope is built, and in this order: the\n // declared length is checked first so an oversized request is rejected\n // without ever reading its body.\n const tooLarge = () => {\n countRejected();\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n };\n\n let parsedBody: unknown;\n\n if (method === 'POST') {\n const contentLength = request.headers.get('Content-Length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > settings.maxRequestSize) {\n logger.debug('Request too large', {\n size,\n limit: settings.maxRequestSize,\n });\n return tooLarge();\n }\n }\n\n const bodyText = await request.text();\n // The limit is in bytes, and String.length counts UTF-16 code units,\n // so a non-ASCII body would otherwise pass a limit it exceeds.\n const bodySize = new TextEncoder().encode(bodyText).byteLength;\n\n if (bodySize > settings.maxRequestSize) {\n logger.debug('Request body too large', {\n size: bodySize,\n limit: settings.maxRequestSize,\n });\n return tooLarge();\n }\n\n // A body that does not parse to an object is raw input: toEventList\n // yields a single empty event and ingest.body carries the value for a\n // source.before chain to decode.\n parsedBody = normalizeBody(bodyText);\n }\n\n const scope = buildScope(\n request,\n method === 'POST' ? parsedBody : undefined,\n );\n\n return await context.withScope(scope, undefined, async (scopeEnv) => {\n const envPush = scopeEnv.push;\n\n // GET (pixel tracking - no logging, routine)\n if (method === 'GET') {\n const parsedData = requestToData(url.search);\n if (parsedData && isObject(parsedData)) {\n await envPush(parsedData);\n }\n return createPixelResponse(corsHeaders);\n }\n\n // POST\n if (method === 'POST') {\n const events = toEventList(parsedBody);\n // Dispatch on the envelope FORM, not the event count: a one element\n // batch is still a batch and keeps the batch response shape.\n const batched = isBatchBody(parsedBody);\n\n if (batched) {\n const batch = events;\n\n if (batch.length > settings.maxBatchSize) {\n countRejected();\n logger.debug('Batch too large', {\n size: batch.length,\n limit: settings.maxBatchSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Batch too large. Maximum size: ${settings.maxBatchSize} events`,\n },\n 400,\n corsHeaders,\n );\n }\n\n const outcomes = await processBatch(batch, envPush, logger);\n const { status, body } = batchResponse(outcomes);\n\n return createJsonResponse(body, status, corsHeaders);\n }\n\n // Forward event directly — validation is not the source's responsibility.\n const result = await processEvent(events[0] ?? {}, envPush, logger);\n if (result.error) {\n // A thrown push is already error-logged inside processEvent;\n // this covers settled declines only.\n logger.warn('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n result.status ?? 500,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n });\n } catch (error) {\n logger.error('Internal server error', error);\n const corsHeaders = createCorsHeaders(settings.cors);\n return createJsonResponse(\n {\n success: false,\n error:\n error instanceof Error ? error.message : 'Internal server error',\n },\n 500,\n corsHeaders,\n );\n }\n };\n\n return { type: 'fetch', config: { ...config, settings }, push };\n};\n\nasync function processEvent(\n event: WalkerOS.DeepPartialEvent,\n push: Collector.PushFn,\n logger: Logger.Instance,\n): Promise<{ id?: string; error?: string; status?: number }> {\n try {\n const result = await push(event);\n if (result?.ok === false) {\n if (result.invalid === true) {\n return { error: result.error ?? 'Invalid event', status: 400 };\n }\n return {\n error: result.error ?? 'Event was not processed',\n status: 500,\n };\n }\n return { id: result?.event?.id };\n } catch (error) {\n // A rejected push promise is a server fault: the collector resolves\n // pipeline failures, so a rejection is exceptional by construction.\n // Logged here at error because the call site cannot distinguish a\n // settled ok:false from a rejection, and a rejected push has to stay\n // in the error channel.\n const message = error instanceof Error ? error.message : 'Unknown error';\n logger.error('Event processing failed', { error: message });\n return { error: message, status: 500 };\n }\n}\n\nasync function processBatch(\n events: unknown[],\n push: Collector.PushFn,\n logger: Types['env']['logger'],\n): Promise<EventOutcome[]> {\n const outcomes: EventOutcome[] = [];\n\n for (let i = 0; i < events.length; i++) {\n const event = events[i];\n\n try {\n const result = await push(event as WalkerOS.DeepPartialEvent);\n const outcome = pushResultToOutcome(result ?? {});\n if (outcome.error) logger.warn(`Batch event ${i} not processed`);\n outcomes.push(outcome);\n } catch (error) {\n outcomes.push({\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n logger.warn(`Batch event ${i} processing failed`, error);\n }\n }\n\n return outcomes;\n}\n\nexport type * from './types';\nexport * as SourceFetch from './types';\nexport * from './utils';\n\nexport default sourceFetch;\n","import type { CorsOptions } from './schemas';\n\nexport function createCorsHeaders(\n corsConfig: boolean | CorsOptions = true,\n requestOrigin?: string | null,\n): Headers {\n const headers = new Headers();\n\n if (corsConfig === false) return headers;\n\n if (corsConfig === true) {\n headers.set('Access-Control-Allow-Origin', '*');\n headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n headers.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n if (corsConfig.origin) {\n let origin: string;\n if (Array.isArray(corsConfig.origin)) {\n origin =\n requestOrigin && corsConfig.origin.includes(requestOrigin)\n ? requestOrigin\n : corsConfig.origin[0];\n } else {\n origin = corsConfig.origin;\n }\n headers.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n headers.set(\n 'Access-Control-Allow-Methods',\n corsConfig.methods.join(', '),\n );\n }\n\n if (corsConfig.headers) {\n headers.set(\n 'Access-Control-Allow-Headers',\n corsConfig.headers.join(', '),\n );\n }\n\n if (corsConfig.credentials) {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n headers.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n\n return headers;\n}\n\nexport const TRANSPARENT_GIF_BASE64 =\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\nexport function createPixelResponse(corsHeaders?: Headers): Response {\n const binaryString = atob(TRANSPARENT_GIF_BASE64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'image/gif');\n headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n return new Response(bytes, { status: 200, headers });\n}\n\nexport function createJsonResponse(\n body: unknown,\n status = 200,\n corsHeaders?: Headers,\n): Response {\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'application/json');\n\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * Match a request pathname against a route pattern.\n * Supports exact matches and wildcard patterns (e.g., /api/*).\n */\nexport function matchPath(requestPath: string, pattern: string): boolean {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n return requestPath === prefix || requestPath.startsWith(prefix + '/');\n }\n return requestPath === pattern;\n}\n","import { normalizeHeaders, normalizeQuery } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\n\n/**\n * Builds the normalized scope from a WHATWG Request.\n *\n * A `Request` and its `Headers` are class instances, so a mapping path can\n * never descend into them directly. Flattening them here is what makes\n * `config.ingest` resolve on this source at all.\n *\n * The body is read by the handler, which owns the size guards, and handed in,\n * so this stays synchronous and pure.\n *\n * @param request The incoming request.\n * @param body The already read and normalized body, or undefined.\n * @returns The normalized scope.\n */\nexport function buildScope(request: Request, body: unknown): Source.Scope {\n const url = new URL(request.url);\n\n return {\n method: request.method.toUpperCase(),\n url: request.url,\n path: url.pathname,\n query: normalizeQuery(url.search),\n headers: normalizeHeaders(request.headers),\n body,\n // No ip: a bare WHATWG Request reports none, and deriving one from\n // x-forwarded-for would be a platform guess. The header is in `headers`.\n raw: request,\n };\n}\n","import type { WalkerOS, Source as CoreSource } from '@walkeros/core';\nimport type {\n SettingsSchema,\n CorsOptionsSchema,\n RouteConfigSchema,\n} from './schemas';\nimport type { z } from '@walkeros/core/dev';\n\ndeclare module '@walkeros/core' {\n interface SourceMap {\n fetch: { type: 'fetch'; platform: 'server' };\n }\n}\n\nexport type Settings = z.infer<typeof SettingsSchema>;\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\nexport type RouteConfig = z.infer<typeof RouteConfigSchema>;\nexport type RouteMethod = 'GET' | 'POST';\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {}\n\nexport type Push = (request: Request) => Response | Promise<Response>;\n\nexport interface Env extends CoreSource.Env {\n request?: Request;\n}\n\nexport type Types = CoreSource.Types<\n Settings,\n Mapping,\n Push,\n Env,\n InitSettings\n>;\nexport type Config = CoreSource.Config<Types>;\nexport type PartialConfig = CoreSource.PartialConfig<Types>;\n\nexport interface FetchSource extends Omit<CoreSource.Instance<Types>, 'push'> {\n push: Push;\n}\n\nexport interface EventResponse {\n success: boolean;\n id?: string;\n timestamp?: number;\n error?: string;\n}\n"],"mappings":"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","names":["import_core"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{requestToData,isObject,isDefined}from"@walkeros/core";function createCorsHeaders(corsConfig=!0,requestOrigin){const headers=new Headers;if(!1===corsConfig)return headers;if(!0===corsConfig)headers.set("Access-Control-Allow-Origin","*"),headers.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),headers.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){let origin;origin=Array.isArray(corsConfig.origin)?requestOrigin&&corsConfig.origin.includes(requestOrigin)?requestOrigin:corsConfig.origin[0]:corsConfig.origin,headers.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&headers.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&headers.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&headers.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&headers.set("Access-Control-Max-Age",String(corsConfig.maxAge))}return headers}var TRANSPARENT_GIF_BASE64="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function createPixelResponse(corsHeaders){const binaryString=atob("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"),bytes=new Uint8Array(binaryString.length);for(let i=0;i<binaryString.length;i++)bytes[i]=binaryString.charCodeAt(i);const headers=new Headers(corsHeaders);return headers.set("Content-Type","image/gif"),headers.set("Cache-Control","no-cache, no-store, must-revalidate"),new Response(bytes,{status:200,headers:headers})}function createJsonResponse(body,status=200,corsHeaders){const headers=new Headers(corsHeaders);return headers.set("Content-Type","application/json"),new Response(JSON.stringify(body),{status:status,headers:headers})}function matchPath(requestPath,pattern){if(pattern.endsWith("/*")){const prefix=pattern.slice(0,-2);return requestPath===prefix||requestPath.startsWith(prefix+"/")}return requestPath===pattern}var types_exports={},sourceFetch=async context=>{const{config:config={},env:env}=context,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,maxRequestSize:userSettings.maxRequestSize??102400,maxBatchSize:userSettings.maxBatchSize??100,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},{logger:logger}=env;return{type:"fetch",config:{...config,settings:settings},push:async request=>{Date.now();try{const url=new URL(request.url),method=request.method.toUpperCase(),origin=request.headers.get("Origin"),corsHeaders=createCorsHeaders(settings.cors,origin),matchedRoute=settings.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]}).find(route=>matchPath(url.pathname,route.path));return matchedRoute?"OPTIONS"===method?new Response(null,{status:204,headers:corsHeaders}):matchedRoute.methods.includes(method)?await context.withScope(request,void 0,async scopeEnv=>{const envPush=scopeEnv.push;if("GET"===method){const parsedData=requestToData(url.search);return parsedData&&isObject(parsedData)&&await envPush(parsedData),createPixelResponse(corsHeaders)}if("POST"===method){const contentLength=request.headers.get("Content-Length");if(contentLength){const size=parseInt(contentLength,10);if(size>settings.maxRequestSize)return logger.error("Request too large",{size:size,limit:settings.maxRequestSize}),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders)}let eventData,bodyText,rawBody=!1;try{if(bodyText=await request.text(),bodyText.length>settings.maxRequestSize)return logger.error("Request body too large",{size:bodyText.length,limit:settings.maxRequestSize}),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders);eventData=JSON.parse(bodyText)}catch{eventData={},rawBody=!0}if(isDefined(eventData)&&isObject(eventData)||(eventData={},rawBody=!0),rawBody){const result2=await processEvent(eventData,envPush);return result2.error?(logger.error("Event processing failed",{error:result2.error}),createJsonResponse({success:!1,error:result2.error},400,corsHeaders)):createJsonResponse({success:!0,id:result2.id,timestamp:Date.now()},200,corsHeaders)}const validData=eventData;if("batch"in validData&&Array.isArray(validData.batch)){const batch=validData.batch;if(batch.length>settings.maxBatchSize)return logger.error("Batch too large",{size:batch.length,limit:settings.maxBatchSize}),createJsonResponse({success:!1,error:`Batch too large. Maximum size: ${settings.maxBatchSize} events`},400,corsHeaders);const results=await async function(events,push,logger){const results={successful:0,failed:0,ids:[],errors:[]};for(let i=0;i<events.length;i++){const event=events[i];try{const result=await push(event);result?.event?.id&&results.ids.push(result.event.id),results.successful++}catch(error){results.failed++,results.errors.push({index:i,error:error instanceof Error?error.message:"Unknown error"}),logger.error(`Batch event ${i} processing failed`,error)}}return results}(batch,envPush,logger);return results.failed>0?createJsonResponse({success:!1,processed:results.successful,failed:results.failed,errors:results.errors},207,corsHeaders):createJsonResponse({success:!0,processed:results.successful,ids:results.ids},200,corsHeaders)}const result=await processEvent(eventData,envPush);return result.error?(logger.error("Event processing failed",{error:result.error}),createJsonResponse({success:!1,error:result.error},400,corsHeaders)):createJsonResponse({success:!0,id:result.id,timestamp:Date.now()},200,corsHeaders)}return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders)}):createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders):createJsonResponse({success:!1,error:"Not found"},404,corsHeaders)}catch(error){logger.error("Internal server error",error);const corsHeaders=createCorsHeaders(settings.cors);return createJsonResponse({success:!1,error:error instanceof Error?error.message:"Internal server error"},500,corsHeaders)}}}};async function processEvent(event,push){try{const result=await push(event);return{id:result?.event?.id}}catch(error){return{error:error instanceof Error?error.message:"Unknown error"}}}var index_default=sourceFetch;export{types_exports as SourceFetch,TRANSPARENT_GIF_BASE64,createCorsHeaders,createJsonResponse,createPixelResponse,index_default as default,matchPath,sourceFetch};//# sourceMappingURL=index.mjs.map
1
+ import{normalizeBody,requestToData,isObject,toEventList,isBatchBody,batchResponse,pushResultToOutcome}from"@walkeros/core";function createCorsHeaders(corsConfig=!0,requestOrigin){const headers=new Headers;if(!1===corsConfig)return headers;if(!0===corsConfig)headers.set("Access-Control-Allow-Origin","*"),headers.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),headers.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){let origin;origin=Array.isArray(corsConfig.origin)?requestOrigin&&corsConfig.origin.includes(requestOrigin)?requestOrigin:corsConfig.origin[0]:corsConfig.origin,headers.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&headers.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&headers.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&headers.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&headers.set("Access-Control-Max-Age",String(corsConfig.maxAge))}return headers}var TRANSPARENT_GIF_BASE64="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function createPixelResponse(corsHeaders){const binaryString=atob("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"),bytes=new Uint8Array(binaryString.length);for(let i=0;i<binaryString.length;i++)bytes[i]=binaryString.charCodeAt(i);const headers=new Headers(corsHeaders);return headers.set("Content-Type","image/gif"),headers.set("Cache-Control","no-cache, no-store, must-revalidate"),new Response(bytes,{status:200,headers:headers})}function createJsonResponse(body,status=200,corsHeaders){const headers=new Headers(corsHeaders);return headers.set("Content-Type","application/json"),new Response(JSON.stringify(body),{status:status,headers:headers})}function matchPath(requestPath,pattern){if(pattern.endsWith("/*")){const prefix=pattern.slice(0,-2);return requestPath===prefix||requestPath.startsWith(prefix+"/")}return requestPath===pattern}import{normalizeHeaders,normalizeQuery}from"@walkeros/core";var types_exports={},sourceFetch=async context=>{const{config:config={},env:env}=context,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,maxRequestSize:userSettings.maxRequestSize??102400,maxBatchSize:userSettings.maxBatchSize??100,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},{logger:logger}=env,countRejected=()=>{const sources=context.collector.status?.sources;if(!sources)return;sources[context.id]||(sources[context.id]={count:0,duration:0});const sourceStatus=sources[context.id];sourceStatus.rejected=(sourceStatus.rejected??0)+1};return{type:"fetch",config:{...config,settings:settings},push:async request=>{Date.now();try{const url=new URL(request.url),method=request.method.toUpperCase(),origin=request.headers.get("Origin"),corsHeaders=createCorsHeaders(settings.cors,origin),matchedRoute=settings.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]}).find(route=>matchPath(url.pathname,route.path));if(!matchedRoute)return createJsonResponse({success:!1,error:"Not found"},404,corsHeaders);if("OPTIONS"===method)return new Response(null,{status:204,headers:corsHeaders});if(!matchedRoute.methods.includes(method))return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders);const tooLarge=()=>(countRejected(),createJsonResponse({success:!1,error:`Request too large. Maximum size: ${settings.maxRequestSize} bytes`},413,corsHeaders));let parsedBody;if("POST"===method){const contentLength=request.headers.get("Content-Length");if(contentLength){const size=parseInt(contentLength,10);if(size>settings.maxRequestSize)return logger.debug("Request too large",{size:size,limit:settings.maxRequestSize}),tooLarge()}const bodyText=await request.text(),bodySize=(new TextEncoder).encode(bodyText).byteLength;if(bodySize>settings.maxRequestSize)return logger.debug("Request body too large",{size:bodySize,limit:settings.maxRequestSize}),tooLarge();parsedBody=normalizeBody(bodyText)}const scope=function(request,body){const url=new URL(request.url);return{method:request.method.toUpperCase(),url:request.url,path:url.pathname,query:normalizeQuery(url.search),headers:normalizeHeaders(request.headers),body:body,raw:request}}(request,"POST"===method?parsedBody:void 0);return await context.withScope(scope,void 0,async scopeEnv=>{const envPush=scopeEnv.push;if("GET"===method){const parsedData=requestToData(url.search);return parsedData&&isObject(parsedData)&&await envPush(parsedData),createPixelResponse(corsHeaders)}if("POST"===method){const events=toEventList(parsedBody);if(isBatchBody(parsedBody)){const batch=events;if(batch.length>settings.maxBatchSize)return countRejected(),logger.debug("Batch too large",{size:batch.length,limit:settings.maxBatchSize}),createJsonResponse({success:!1,error:`Batch too large. Maximum size: ${settings.maxBatchSize} events`},400,corsHeaders);const outcomes=await async function(events,push,logger){const outcomes=[];for(let i=0;i<events.length;i++){const event=events[i];try{const result=await push(event),outcome=pushResultToOutcome(result??{});outcome.error&&logger.warn(`Batch event ${i} not processed`),outcomes.push(outcome)}catch(error){outcomes.push({error:error instanceof Error?error.message:"Unknown error"}),logger.warn(`Batch event ${i} processing failed`,error)}}return outcomes}(batch,envPush,logger),{status:status,body:body}=batchResponse(outcomes);return createJsonResponse(body,status,corsHeaders)}const result=await async function(event,push,logger){try{const result=await push(event);return!1===result?.ok?!0===result.invalid?{error:result.error??"Invalid event",status:400}:{error:result.error??"Event was not processed",status:500}:{id:result?.event?.id}}catch(error){const message=error instanceof Error?error.message:"Unknown error";return logger.error("Event processing failed",{error:message}),{error:message,status:500}}}(events[0]??{},envPush,logger);return result.error?(logger.warn("Event processing failed",{error:result.error}),createJsonResponse({success:!1,error:result.error},result.status??500,corsHeaders)):createJsonResponse({success:!0,id:result.id,timestamp:Date.now()},200,corsHeaders)}return createJsonResponse({success:!1,error:"Method not allowed"},405,corsHeaders)})}catch(error){logger.error("Internal server error",error);const corsHeaders=createCorsHeaders(settings.cors);return createJsonResponse({success:!1,error:error instanceof Error?error.message:"Internal server error"},500,corsHeaders)}}}};var index_default=sourceFetch;export{types_exports as SourceFetch,TRANSPARENT_GIF_BASE64,createCorsHeaders,createJsonResponse,createPixelResponse,index_default as default,matchPath,sourceFetch};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/types.ts"],"sourcesContent":["import { requestToData, isObject, isDefined } from '@walkeros/core';\nimport type { WalkerOS, Collector, Source } from '@walkeros/core';\nimport type { FetchSource, Types } from './types';\nimport {\n createCorsHeaders,\n createPixelResponse,\n createJsonResponse,\n matchPath,\n} from './utils';\n\nexport const sourceFetch: Source.Init<Types> = async (context) => {\n const { config = {}, env } = context;\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n maxRequestSize: userSettings.maxRequestSize ?? 102400,\n maxBatchSize: userSettings.maxBatchSize ?? 100,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n const { logger } = env;\n\n const push = async (request: Request): Promise<Response> => {\n const startTime = Date.now();\n void startTime;\n\n try {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const origin = request.headers.get('Origin');\n const corsHeaders = createCorsHeaders(settings.cors, origin);\n\n // Resolve route configs\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n // Match request path against configured routes\n const matchedRoute = resolvedPaths.find((route) =>\n matchPath(url.pathname, route.path),\n );\n\n if (!matchedRoute) {\n return createJsonResponse(\n { success: false, error: 'Not found' },\n 404,\n corsHeaders,\n );\n }\n\n // OPTIONS (CORS preflight - no logging, routine)\n if (method === 'OPTIONS') {\n return new Response(null, { status: 204, headers: corsHeaders });\n }\n\n // Check method is allowed for this route\n if (!matchedRoute.methods.includes(method as 'GET' | 'POST')) {\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n }\n\n // Per-request scope: each fetch invocation gets its own ingest.\n // Fetch sources return a Response directly, not via async respond.\n return await context.withScope(request, undefined, async (scopeEnv) => {\n const envPush = scopeEnv.push;\n\n // GET (pixel tracking - no logging, routine)\n if (method === 'GET') {\n const parsedData = requestToData(url.search);\n if (parsedData && isObject(parsedData)) {\n await envPush(parsedData);\n }\n return createPixelResponse(corsHeaders);\n }\n\n // POST\n if (method === 'POST') {\n // Check request size\n const contentLength = request.headers.get('Content-Length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > settings.maxRequestSize) {\n logger.error('Request too large', {\n size,\n limit: settings.maxRequestSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n }\n }\n\n let eventData: unknown;\n let bodyText: string;\n let rawBody = false;\n\n try {\n bodyText = await request.text();\n\n // Check actual body size\n if (bodyText.length > settings.maxRequestSize) {\n logger.error('Request body too large', {\n size: bodyText.length,\n limit: settings.maxRequestSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n }\n\n eventData = JSON.parse(bodyText);\n } catch {\n // Non-JSON body: push empty event for source.before transformers\n eventData = {};\n rawBody = true;\n }\n\n if (!isDefined(eventData) || !isObject(eventData)) {\n // Non-object body: push empty event for source.before transformers\n eventData = {};\n rawBody = true;\n }\n\n // Raw body: push empty event directly, skip validation\n if (rawBody) {\n const result = await processEvent(\n eventData as WalkerOS.DeepPartialEvent,\n envPush,\n );\n if (result.error) {\n logger.error('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n 400,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n // Check for batch (eventData is a validated object at this point)\n const validData = eventData as Record<string, unknown>;\n const isBatch =\n 'batch' in validData && Array.isArray(validData.batch);\n\n if (isBatch) {\n const batch = validData.batch as unknown[];\n\n if (batch.length > settings.maxBatchSize) {\n logger.error('Batch too large', {\n size: batch.length,\n limit: settings.maxBatchSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Batch too large. Maximum size: ${settings.maxBatchSize} events`,\n },\n 400,\n corsHeaders,\n );\n }\n\n const results = await processBatch(batch, envPush, logger);\n\n if (results.failed > 0) {\n return createJsonResponse(\n {\n success: false,\n processed: results.successful,\n failed: results.failed,\n errors: results.errors,\n },\n 207,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n {\n success: true,\n processed: results.successful,\n ids: results.ids,\n },\n 200,\n corsHeaders,\n );\n }\n\n // Forward event directly — validation is not the source's responsibility.\n const result = await processEvent(\n eventData as WalkerOS.DeepPartialEvent,\n envPush,\n );\n if (result.error) {\n logger.error('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n 400,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n });\n } catch (error) {\n logger.error('Internal server error', error);\n const corsHeaders = createCorsHeaders(settings.cors);\n return createJsonResponse(\n {\n success: false,\n error:\n error instanceof Error ? error.message : 'Internal server error',\n },\n 500,\n corsHeaders,\n );\n }\n };\n\n return { type: 'fetch', config: { ...config, settings }, push };\n};\n\nasync function processEvent(\n event: WalkerOS.DeepPartialEvent,\n push: Collector.PushFn,\n): Promise<{ id?: string; error?: string }> {\n try {\n const result = await push(event);\n return { id: result?.event?.id };\n } catch (error) {\n return { error: error instanceof Error ? error.message : 'Unknown error' };\n }\n}\n\nasync function processBatch(\n events: unknown[],\n push: Collector.PushFn,\n logger: Types['env']['logger'],\n): Promise<{\n successful: number;\n failed: number;\n ids: string[];\n errors: Array<{ index: number; error: string }>;\n}> {\n const results = {\n successful: 0,\n failed: 0,\n ids: [] as string[],\n errors: [] as Array<{ index: number; error: string }>,\n };\n\n for (let i = 0; i < events.length; i++) {\n const event = events[i];\n\n try {\n const result = await push(event as WalkerOS.DeepPartialEvent);\n if (result?.event?.id) {\n results.ids.push(result.event.id);\n }\n results.successful++;\n } catch (error) {\n results.failed++;\n results.errors.push({\n index: i,\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n logger.error(`Batch event ${i} processing failed`, error);\n }\n }\n\n return results;\n}\n\nexport type * from './types';\nexport * as SourceFetch from './types';\nexport * from './utils';\n\nexport default sourceFetch;\n","import type { CorsOptions } from './schemas';\n\nexport function createCorsHeaders(\n corsConfig: boolean | CorsOptions = true,\n requestOrigin?: string | null,\n): Headers {\n const headers = new Headers();\n\n if (corsConfig === false) return headers;\n\n if (corsConfig === true) {\n headers.set('Access-Control-Allow-Origin', '*');\n headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n headers.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n if (corsConfig.origin) {\n let origin: string;\n if (Array.isArray(corsConfig.origin)) {\n origin =\n requestOrigin && corsConfig.origin.includes(requestOrigin)\n ? requestOrigin\n : corsConfig.origin[0];\n } else {\n origin = corsConfig.origin;\n }\n headers.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n headers.set(\n 'Access-Control-Allow-Methods',\n corsConfig.methods.join(', '),\n );\n }\n\n if (corsConfig.headers) {\n headers.set(\n 'Access-Control-Allow-Headers',\n corsConfig.headers.join(', '),\n );\n }\n\n if (corsConfig.credentials) {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n headers.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n\n return headers;\n}\n\nexport const TRANSPARENT_GIF_BASE64 =\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\nexport function createPixelResponse(corsHeaders?: Headers): Response {\n const binaryString = atob(TRANSPARENT_GIF_BASE64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'image/gif');\n headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n return new Response(bytes, { status: 200, headers });\n}\n\nexport function createJsonResponse(\n body: unknown,\n status = 200,\n corsHeaders?: Headers,\n): Response {\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'application/json');\n\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * Match a request pathname against a route pattern.\n * Supports exact matches and wildcard patterns (e.g., /api/*).\n */\nexport function matchPath(requestPath: string, pattern: string): boolean {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n return requestPath === prefix || requestPath.startsWith(prefix + '/');\n }\n return requestPath === pattern;\n}\n","import type { WalkerOS, Source as CoreSource } from '@walkeros/core';\nimport type {\n SettingsSchema,\n CorsOptionsSchema,\n RouteConfigSchema,\n} from './schemas';\nimport type { z } from '@walkeros/core/dev';\n\ndeclare module '@walkeros/core' {\n interface SourceMap {\n fetch: { type: 'fetch'; platform: 'server' };\n }\n}\n\nexport type Settings = z.infer<typeof SettingsSchema>;\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\nexport type RouteConfig = z.infer<typeof RouteConfigSchema>;\nexport type RouteMethod = 'GET' | 'POST';\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {}\n\nexport type Push = (request: Request) => Response | Promise<Response>;\n\nexport interface Env extends CoreSource.Env {\n request?: Request;\n}\n\nexport type Types = CoreSource.Types<\n Settings,\n Mapping,\n Push,\n Env,\n InitSettings\n>;\nexport type Config = CoreSource.Config<Types>;\nexport type PartialConfig = CoreSource.PartialConfig<Types>;\n\nexport interface FetchSource extends Omit<CoreSource.Instance<Types>, 'push'> {\n push: Push;\n}\n\nexport interface EventResponse {\n success: boolean;\n id?: string;\n timestamp?: number;\n error?: string;\n}\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/scope.ts","../src/types.ts"],"sourcesContent":["import {\n normalizeBody,\n requestToData,\n isObject,\n toEventList,\n isBatchBody,\n batchResponse,\n pushResultToOutcome,\n} from '@walkeros/core';\nimport type {\n WalkerOS,\n Collector,\n Logger,\n Source,\n EventOutcome,\n} from '@walkeros/core';\nimport type { FetchSource, Types } from './types';\nimport {\n createCorsHeaders,\n createPixelResponse,\n createJsonResponse,\n matchPath,\n} from './utils';\nimport { buildScope } from './scope';\n\nexport const sourceFetch: Source.Init<Types> = async (context) => {\n const { config = {}, env } = context;\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n maxRequestSize: userSettings.maxRequestSize ?? 102400,\n maxBatchSize: userSettings.maxBatchSize ?? 100,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n const { logger } = env;\n\n // Rejection volume belongs in collector.status, not in per-request logs.\n // Lazy entry creation mirrors the collector's own sources bookkeeping.\n // Guarded: unit harnesses may run the handler with a bare collector stub.\n const countRejected = (): void => {\n const sources = context.collector.status?.sources;\n if (!sources) return;\n if (!sources[context.id]) {\n sources[context.id] = { count: 0, duration: 0 };\n }\n const sourceStatus = sources[context.id];\n sourceStatus.rejected = (sourceStatus.rejected ?? 0) + 1;\n };\n\n const push = async (request: Request): Promise<Response> => {\n const startTime = Date.now();\n void startTime;\n\n try {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const origin = request.headers.get('Origin');\n const corsHeaders = createCorsHeaders(settings.cors, origin);\n\n // Resolve route configs\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n // Match request path against configured routes\n const matchedRoute = resolvedPaths.find((route) =>\n matchPath(url.pathname, route.path),\n );\n\n if (!matchedRoute) {\n return createJsonResponse(\n { success: false, error: 'Not found' },\n 404,\n corsHeaders,\n );\n }\n\n // OPTIONS (CORS preflight - no logging, routine)\n if (method === 'OPTIONS') {\n return new Response(null, { status: 204, headers: corsHeaders });\n }\n\n // Check method is allowed for this route\n if (!matchedRoute.methods.includes(method as 'GET' | 'POST')) {\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n }\n\n // Per-request scope: each fetch invocation gets its own ingest.\n // Fetch sources return a Response directly, not via async respond.\n // Size guards run before the scope is built, and in this order: the\n // declared length is checked first so an oversized request is rejected\n // without ever reading its body.\n const tooLarge = () => {\n countRejected();\n return createJsonResponse(\n {\n success: false,\n error: `Request too large. Maximum size: ${settings.maxRequestSize} bytes`,\n },\n 413,\n corsHeaders,\n );\n };\n\n let parsedBody: unknown;\n\n if (method === 'POST') {\n const contentLength = request.headers.get('Content-Length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > settings.maxRequestSize) {\n logger.debug('Request too large', {\n size,\n limit: settings.maxRequestSize,\n });\n return tooLarge();\n }\n }\n\n const bodyText = await request.text();\n // The limit is in bytes, and String.length counts UTF-16 code units,\n // so a non-ASCII body would otherwise pass a limit it exceeds.\n const bodySize = new TextEncoder().encode(bodyText).byteLength;\n\n if (bodySize > settings.maxRequestSize) {\n logger.debug('Request body too large', {\n size: bodySize,\n limit: settings.maxRequestSize,\n });\n return tooLarge();\n }\n\n // A body that does not parse to an object is raw input: toEventList\n // yields a single empty event and ingest.body carries the value for a\n // source.before chain to decode.\n parsedBody = normalizeBody(bodyText);\n }\n\n const scope = buildScope(\n request,\n method === 'POST' ? parsedBody : undefined,\n );\n\n return await context.withScope(scope, undefined, async (scopeEnv) => {\n const envPush = scopeEnv.push;\n\n // GET (pixel tracking - no logging, routine)\n if (method === 'GET') {\n const parsedData = requestToData(url.search);\n if (parsedData && isObject(parsedData)) {\n await envPush(parsedData);\n }\n return createPixelResponse(corsHeaders);\n }\n\n // POST\n if (method === 'POST') {\n const events = toEventList(parsedBody);\n // Dispatch on the envelope FORM, not the event count: a one element\n // batch is still a batch and keeps the batch response shape.\n const batched = isBatchBody(parsedBody);\n\n if (batched) {\n const batch = events;\n\n if (batch.length > settings.maxBatchSize) {\n countRejected();\n logger.debug('Batch too large', {\n size: batch.length,\n limit: settings.maxBatchSize,\n });\n return createJsonResponse(\n {\n success: false,\n error: `Batch too large. Maximum size: ${settings.maxBatchSize} events`,\n },\n 400,\n corsHeaders,\n );\n }\n\n const outcomes = await processBatch(batch, envPush, logger);\n const { status, body } = batchResponse(outcomes);\n\n return createJsonResponse(body, status, corsHeaders);\n }\n\n // Forward event directly — validation is not the source's responsibility.\n const result = await processEvent(events[0] ?? {}, envPush, logger);\n if (result.error) {\n // A thrown push is already error-logged inside processEvent;\n // this covers settled declines only.\n logger.warn('Event processing failed', { error: result.error });\n return createJsonResponse(\n { success: false, error: result.error },\n result.status ?? 500,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: true, id: result.id, timestamp: Date.now() },\n 200,\n corsHeaders,\n );\n }\n\n return createJsonResponse(\n { success: false, error: 'Method not allowed' },\n 405,\n corsHeaders,\n );\n });\n } catch (error) {\n logger.error('Internal server error', error);\n const corsHeaders = createCorsHeaders(settings.cors);\n return createJsonResponse(\n {\n success: false,\n error:\n error instanceof Error ? error.message : 'Internal server error',\n },\n 500,\n corsHeaders,\n );\n }\n };\n\n return { type: 'fetch', config: { ...config, settings }, push };\n};\n\nasync function processEvent(\n event: WalkerOS.DeepPartialEvent,\n push: Collector.PushFn,\n logger: Logger.Instance,\n): Promise<{ id?: string; error?: string; status?: number }> {\n try {\n const result = await push(event);\n if (result?.ok === false) {\n if (result.invalid === true) {\n return { error: result.error ?? 'Invalid event', status: 400 };\n }\n return {\n error: result.error ?? 'Event was not processed',\n status: 500,\n };\n }\n return { id: result?.event?.id };\n } catch (error) {\n // A rejected push promise is a server fault: the collector resolves\n // pipeline failures, so a rejection is exceptional by construction.\n // Logged here at error because the call site cannot distinguish a\n // settled ok:false from a rejection, and a rejected push has to stay\n // in the error channel.\n const message = error instanceof Error ? error.message : 'Unknown error';\n logger.error('Event processing failed', { error: message });\n return { error: message, status: 500 };\n }\n}\n\nasync function processBatch(\n events: unknown[],\n push: Collector.PushFn,\n logger: Types['env']['logger'],\n): Promise<EventOutcome[]> {\n const outcomes: EventOutcome[] = [];\n\n for (let i = 0; i < events.length; i++) {\n const event = events[i];\n\n try {\n const result = await push(event as WalkerOS.DeepPartialEvent);\n const outcome = pushResultToOutcome(result ?? {});\n if (outcome.error) logger.warn(`Batch event ${i} not processed`);\n outcomes.push(outcome);\n } catch (error) {\n outcomes.push({\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n logger.warn(`Batch event ${i} processing failed`, error);\n }\n }\n\n return outcomes;\n}\n\nexport type * from './types';\nexport * as SourceFetch from './types';\nexport * from './utils';\n\nexport default sourceFetch;\n","import type { CorsOptions } from './schemas';\n\nexport function createCorsHeaders(\n corsConfig: boolean | CorsOptions = true,\n requestOrigin?: string | null,\n): Headers {\n const headers = new Headers();\n\n if (corsConfig === false) return headers;\n\n if (corsConfig === true) {\n headers.set('Access-Control-Allow-Origin', '*');\n headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n headers.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n if (corsConfig.origin) {\n let origin: string;\n if (Array.isArray(corsConfig.origin)) {\n origin =\n requestOrigin && corsConfig.origin.includes(requestOrigin)\n ? requestOrigin\n : corsConfig.origin[0];\n } else {\n origin = corsConfig.origin;\n }\n headers.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n headers.set(\n 'Access-Control-Allow-Methods',\n corsConfig.methods.join(', '),\n );\n }\n\n if (corsConfig.headers) {\n headers.set(\n 'Access-Control-Allow-Headers',\n corsConfig.headers.join(', '),\n );\n }\n\n if (corsConfig.credentials) {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n headers.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n\n return headers;\n}\n\nexport const TRANSPARENT_GIF_BASE64 =\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\nexport function createPixelResponse(corsHeaders?: Headers): Response {\n const binaryString = atob(TRANSPARENT_GIF_BASE64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'image/gif');\n headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n return new Response(bytes, { status: 200, headers });\n}\n\nexport function createJsonResponse(\n body: unknown,\n status = 200,\n corsHeaders?: Headers,\n): Response {\n const headers = new Headers(corsHeaders);\n headers.set('Content-Type', 'application/json');\n\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * Match a request pathname against a route pattern.\n * Supports exact matches and wildcard patterns (e.g., /api/*).\n */\nexport function matchPath(requestPath: string, pattern: string): boolean {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n return requestPath === prefix || requestPath.startsWith(prefix + '/');\n }\n return requestPath === pattern;\n}\n","import { normalizeHeaders, normalizeQuery } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\n\n/**\n * Builds the normalized scope from a WHATWG Request.\n *\n * A `Request` and its `Headers` are class instances, so a mapping path can\n * never descend into them directly. Flattening them here is what makes\n * `config.ingest` resolve on this source at all.\n *\n * The body is read by the handler, which owns the size guards, and handed in,\n * so this stays synchronous and pure.\n *\n * @param request The incoming request.\n * @param body The already read and normalized body, or undefined.\n * @returns The normalized scope.\n */\nexport function buildScope(request: Request, body: unknown): Source.Scope {\n const url = new URL(request.url);\n\n return {\n method: request.method.toUpperCase(),\n url: request.url,\n path: url.pathname,\n query: normalizeQuery(url.search),\n headers: normalizeHeaders(request.headers),\n body,\n // No ip: a bare WHATWG Request reports none, and deriving one from\n // x-forwarded-for would be a platform guess. The header is in `headers`.\n raw: request,\n };\n}\n","import type { WalkerOS, Source as CoreSource } from '@walkeros/core';\nimport type {\n SettingsSchema,\n CorsOptionsSchema,\n RouteConfigSchema,\n} from './schemas';\nimport type { z } from '@walkeros/core/dev';\n\ndeclare module '@walkeros/core' {\n interface SourceMap {\n fetch: { type: 'fetch'; platform: 'server' };\n }\n}\n\nexport type Settings = z.infer<typeof SettingsSchema>;\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\nexport type RouteConfig = z.infer<typeof RouteConfigSchema>;\nexport type RouteMethod = 'GET' | 'POST';\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {}\n\nexport type Push = (request: Request) => Response | Promise<Response>;\n\nexport interface Env extends CoreSource.Env {\n request?: Request;\n}\n\nexport type Types = CoreSource.Types<\n Settings,\n Mapping,\n Push,\n Env,\n InitSettings\n>;\nexport type Config = CoreSource.Config<Types>;\nexport type PartialConfig = CoreSource.PartialConfig<Types>;\n\nexport interface FetchSource extends Omit<CoreSource.Instance<Types>, 'push'> {\n push: Push;\n}\n\nexport interface EventResponse {\n success: boolean;\n id?: string;\n timestamp?: number;\n error?: string;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-source-fetch",
4
- "version": "4.4.0",
4
+ "version": "4.5.0",
5
5
  "type": "source",
6
6
  "platform": [
7
7
  "server"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-source-fetch",
3
3
  "description": "Web Standard Fetch API source for walkerOS (Cloudflare Workers, Vercel Edge, Deno, Bun)",
4
- "version": "4.4.0",
4
+ "version": "4.5.0",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -20,8 +20,8 @@
20
20
  "update": "npx npm-check-updates -u && npm update"
21
21
  },
22
22
  "dependencies": {
23
- "@walkeros/collector": "4.4.0",
24
- "@walkeros/core": "4.4.0"
23
+ "@walkeros/collector": "4.5.0",
24
+ "@walkeros/core": "4.5.0"
25
25
  },
26
26
  "devDependencies": {},
27
27
  "repository": {