@walkeros/server-source-express 3.1.1 → 3.2.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/README.md CHANGED
@@ -176,6 +176,34 @@ curl -X POST http://localhost:8080/collect \
176
176
  }
177
177
  ```
178
178
 
179
+ ### POST - Non-JSON Bodies (Raw Input)
180
+
181
+ For non-JSON bodies (Base64, XML, form data), the source pushes an empty event
182
+ `{}` to the collector. Use `source.before` transformers to decode raw input
183
+ available via ingest:
184
+
185
+ ```json
186
+ {
187
+ "sources": {
188
+ "http": {
189
+ "package": "@walkeros/server-source-express",
190
+ "before": "base64Decoder",
191
+ "config": {
192
+ "ingest": {
193
+ "map": {
194
+ "rawBody": "body",
195
+ "contentType": "headers.content-type"
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ The `base64Decoder` transformer reads `context.ingest.rawBody` and returns the
205
+ decoded walkerOS event.
206
+
179
207
  ### GET - Pixel Tracking
180
208
 
181
209
  Send events as query parameters. Returns a 1x1 transparent GIF.
package/dist/dev.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,o=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,i=(e,o)=>{for(var r in o)t(e,r,{get:o[r],enumerable:!0})},s={};i(s,{examples:()=>y,schemas:()=>n}),module.exports=(e=s,((e,i,s,n)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let l of r(i))a.call(e,l)||l===s||t(e,l,{get:()=>i[l],enumerable:!(n=o(i,l))||n.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n={};i(n,{CorsOptionsSchema:()=>m,CorsOrigin:()=>h,HttpMethod:()=>p,RouteConfigSchema:()=>g,RouteMethod:()=>u,SettingsSchema:()=>b,settings:()=>f});var l=require("@walkeros/core/dev"),c=require("@walkeros/core/dev"),d=require("@walkeros/core/dev"),p=d.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),u=d.z.enum(["GET","POST"]),g=d.z.object({path:d.z.string().describe("Express route path (supports wildcards like /api/*)"),methods:d.z.array(u).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),h=d.z.union([d.z.string(),d.z.array(d.z.string()),d.z.literal("*")]),m=d.z.object({origin:h.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:d.z.array(p).describe("Allowed HTTP methods").optional(),headers:d.z.array(d.z.string()).describe("Allowed request headers").optional(),credentials:d.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:d.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),b=c.z.object({port:c.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:c.z.string().describe("Deprecated: use paths instead").optional(),paths:c.z.array(c.z.union([c.z.string(),g])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:c.z.union([c.z.boolean(),m]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)}),f=(0,l.zodToSchema)(b),y={};i(y,{createTrigger:()=>O,step:()=>w});var w={};i(w,{pixelGet:()=>z,postEvent:()=>v});var v={trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:{name:"page view",data:{title:"Home",url:"https://example.com/"},entity:"page",action:"view"}},z={trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:{name:"page view",data:{title:"Home"},entity:"page",action:"view"}},T=require("@walkeros/collector");var O=async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||"POST";if(!t){const r=await(0,T.startFlow)(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if("object"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error("Express source server not found — ensure port is configured in source settings");o=`http://localhost:${a}`}let i=`${o}${r.path}`;r.query&&(i+=`?${new URLSearchParams(r.query).toString()}`);const s={method:a,headers:{"Content-Type":"application/json",...r.headers}};"GET"!==a&&"HEAD"!==a&&void 0!==r.body&&(s.body=JSON.stringify(r.body));const n=await fetch(i,s),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get("content-type")||"").includes("application/json")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}};//# sourceMappingURL=dev.js.map
1
+ "use strict";var e,t=Object.defineProperty,o=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,s=(e,o)=>{for(var r in o)t(e,r,{get:o[r],enumerable:!0})},i={};s(i,{examples:()=>f,schemas:()=>n}),module.exports=(e=i,((e,s,i,n)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let l of r(s))a.call(e,l)||l===i||t(e,l,{get:()=>s[l],enumerable:!(n=o(s,l))||n.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n={};s(n,{CorsOptionsSchema:()=>m,CorsOrigin:()=>g,HttpMethod:()=>p,RouteConfigSchema:()=>h,RouteMethod:()=>u,SettingsSchema:()=>b,settings:()=>y});var l=require("@walkeros/core/dev"),c=require("@walkeros/core/dev"),d=require("@walkeros/core/dev"),p=d.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),u=d.z.enum(["GET","POST"]),h=d.z.object({path:d.z.string().describe("Express route path (supports wildcards like /api/*)"),methods:d.z.array(u).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),g=d.z.union([d.z.string(),d.z.array(d.z.string()),d.z.literal("*")]),m=d.z.object({origin:g.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:d.z.array(p).describe("Allowed HTTP methods").optional(),headers:d.z.array(d.z.string()).describe("Allowed request headers").optional(),credentials:d.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:d.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),b=c.z.object({port:c.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:c.z.string().describe("Deprecated: use paths instead").optional(),paths:c.z.array(c.z.union([c.z.string(),h])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:c.z.union([c.z.boolean(),m]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)}),y=(0,l.zodToSchema)(b),f={};s(f,{createTrigger:()=>O,step:()=>w});var w={};s(w,{pixelGet:()=>z,postEvent:()=>v});var v={trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:{name:"page view",data:{title:"Home",url:"https://example.com/"},entity:"page",action:"view"}},z={trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:{name:"page view",data:{title:"Home"},entity:"page",action:"view"}},T=require("@walkeros/collector");var O=async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||"POST";if(!t){const r=await(0,T.startFlow)(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if("object"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error("Express source server not found — ensure port is configured in source settings");o=`http://localhost:${a}`}let s=`${o}${r.path}`;r.query&&(s+=`?${new URLSearchParams(r.query).toString()}`);const i={method:a,headers:{...r.headers&&Object.keys(r.headers).some(e=>"content-type"===e.toLowerCase())?{}:{"Content-Type":"application/json"},...r.headers}};"GET"!==a&&"HEAD"!==a&&void 0!==r.body&&(i.body="string"==typeof r.body?r.body:JSON.stringify(r.body));const n=await fetch(s,i),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get("content-type")||"").includes("application/json")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}};//# sourceMappingURL=dev.js.map
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/examples/index.ts","../src/examples/step.ts","../src/examples/trigger.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","export * as step from './step';\nexport { createTrigger } from './trigger';\n","import type { Flow } from '@walkeros/core';\n\nexport const postEvent: Flow.StepExample = {\n trigger: { type: 'POST' },\n in: {\n method: 'POST',\n path: '/collect',\n body: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n },\n out: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n entity: 'page',\n action: 'view',\n },\n};\n\nexport const pixelGet: Flow.StepExample = {\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: {\n name: 'page view',\n data: { title: 'Home' },\n entity: 'page',\n action: 'view',\n },\n};\n","import type { Trigger, Collector } from '@walkeros/core';\nimport { startFlow } from '@walkeros/collector';\n\nexport interface Content {\n method: string;\n path: string;\n body?: unknown;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface Result {\n status: number;\n body: unknown;\n headers: Record<string, string>;\n}\n\n/**\n * Discover the port of a running HTTP server from the collector's sources.\n * Scans all registered sources for one with a `server` property (express pattern).\n */\nfunction discoverPort(collector: Collector.Instance): number | undefined {\n for (const source of Object.values(collector.sources || {})) {\n const s = source as { server?: { address(): { port: number } | string } };\n if (s.server) {\n const addr = s.server.address();\n if (typeof addr === 'object' && addr !== null) return addr.port;\n }\n }\n return undefined;\n}\n\n/**\n * Express source createTrigger.\n *\n * Boots a real express server via startFlow, then fires real HTTP requests.\n * Blackbox: no source instance access, no mocked req/res — just fetch().\n *\n * Pass `port: 0` in the express source settings to use a random available port.\n *\n * @example\n * const { trigger, flow } = await createTrigger(config);\n * const result = await trigger('POST')({ path: '/collect', body: { name: 'page view' } });\n * console.log(result.status, result.body);\n */\nconst createTrigger: Trigger.CreateFn<Content, Result> = async (\n config: Collector.InitConfig,\n) => {\n let flow: Trigger.FlowHandle | undefined;\n let baseUrl: string | undefined;\n\n const trigger: Trigger.Fn<Content, Result> =\n () =>\n async (content: Content): Promise<Result> => {\n const method = content.method || 'POST';\n\n // Lazy startFlow — first call boots the server\n if (!flow) {\n const result = await startFlow(config);\n flow = { collector: result.collector, elb: result.elb };\n\n const port = discoverPort(result.collector);\n if (!port)\n throw new Error(\n 'Express source server not found — ensure port is configured in source settings',\n );\n baseUrl = `http://localhost:${port}`;\n }\n\n // Build URL\n let url = `${baseUrl}${content.path}`;\n if (content.query) {\n url += `?${new URLSearchParams(content.query).toString()}`;\n }\n\n // Build fetch options\n const fetchOptions: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...content.headers,\n },\n };\n if (method !== 'GET' && method !== 'HEAD' && content.body !== undefined) {\n fetchOptions.body = JSON.stringify(content.body);\n }\n\n // Real HTTP request\n const response = await fetch(url, fetchOptions);\n\n // Capture response\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let body: unknown;\n const ct = response.headers.get('content-type') || '';\n if (ct.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n return {\n status: response.status,\n body,\n headers: responseHeaders,\n };\n };\n\n return {\n get flow() {\n return flow;\n },\n trigger,\n };\n};\n\nexport { createTrigger };\n"],"mappings":"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","names":["import_dev","import_dev"]}
1
+ {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/examples/index.ts","../src/examples/step.ts","../src/examples/trigger.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","export * as step from './step';\nexport { createTrigger } from './trigger';\n","import type { Flow } from '@walkeros/core';\n\nexport const postEvent: Flow.StepExample = {\n trigger: { type: 'POST' },\n in: {\n method: 'POST',\n path: '/collect',\n body: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n },\n out: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n entity: 'page',\n action: 'view',\n },\n};\n\nexport const pixelGet: Flow.StepExample = {\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: {\n name: 'page view',\n data: { title: 'Home' },\n entity: 'page',\n action: 'view',\n },\n};\n","import type { Trigger, Collector } from '@walkeros/core';\nimport { startFlow } from '@walkeros/collector';\n\nexport interface Content {\n method: string;\n path: string;\n body?: unknown;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface Result {\n status: number;\n body: unknown;\n headers: Record<string, string>;\n}\n\n/**\n * Discover the port of a running HTTP server from the collector's sources.\n * Scans all registered sources for one with a `server` property (express pattern).\n */\nfunction discoverPort(collector: Collector.Instance): number | undefined {\n for (const source of Object.values(collector.sources || {})) {\n const s = source as { server?: { address(): { port: number } | string } };\n if (s.server) {\n const addr = s.server.address();\n if (typeof addr === 'object' && addr !== null) return addr.port;\n }\n }\n return undefined;\n}\n\n/**\n * Express source createTrigger.\n *\n * Boots a real express server via startFlow, then fires real HTTP requests.\n * Blackbox: no source instance access, no mocked req/res — just fetch().\n *\n * Pass `port: 0` in the express source settings to use a random available port.\n *\n * @example\n * const { trigger, flow } = await createTrigger(config);\n * const result = await trigger('POST')({ path: '/collect', body: { name: 'page view' } });\n * console.log(result.status, result.body);\n */\nconst createTrigger: Trigger.CreateFn<Content, Result> = async (\n config: Collector.InitConfig,\n) => {\n let flow: Trigger.FlowHandle | undefined;\n let baseUrl: string | undefined;\n\n const trigger: Trigger.Fn<Content, Result> =\n () =>\n async (content: Content): Promise<Result> => {\n const method = content.method || 'POST';\n\n // Lazy startFlow — first call boots the server\n if (!flow) {\n const result = await startFlow(config);\n flow = { collector: result.collector, elb: result.elb };\n\n const port = discoverPort(result.collector);\n if (!port)\n throw new Error(\n 'Express source server not found — ensure port is configured in source settings',\n );\n baseUrl = `http://localhost:${port}`;\n }\n\n // Build URL\n let url = `${baseUrl}${content.path}`;\n if (content.query) {\n url += `?${new URLSearchParams(content.query).toString()}`;\n }\n\n // Build fetch options — only set default Content-Type if user doesn't provide one\n const hasContentType =\n content.headers &&\n Object.keys(content.headers).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const fetchOptions: RequestInit = {\n method,\n headers: {\n ...(hasContentType ? {} : { 'Content-Type': 'application/json' }),\n ...content.headers,\n },\n };\n if (method !== 'GET' && method !== 'HEAD' && content.body !== undefined) {\n // String bodies sent raw (e.g., base64 beacon with text/plain).\n // Object bodies JSON-serialized.\n fetchOptions.body =\n typeof content.body === 'string'\n ? content.body\n : JSON.stringify(content.body);\n }\n\n // Real HTTP request\n const response = await fetch(url, fetchOptions);\n\n // Capture response\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let body: unknown;\n const ct = response.headers.get('content-type') || '';\n if (ct.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n return {\n status: response.status,\n body,\n headers: responseHeaders,\n };\n };\n\n return {\n get flow() {\n return flow;\n },\n trigger,\n };\n};\n\nexport { createTrigger };\n"],"mappings":"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","names":["import_dev","import_dev"]}
package/dist/dev.mjs CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,o)=>{for(var r in o)e(t,r,{get:o[r],enumerable:!0})},o={};t(o,{CorsOptionsSchema:()=>d,CorsOrigin:()=>c,HttpMethod:()=>s,RouteConfigSchema:()=>l,RouteMethod:()=>n,SettingsSchema:()=>p,settings:()=>u});import{zodToSchema as r}from"@walkeros/core/dev";import{z as a}from"@walkeros/core/dev";import{z as i}from"@walkeros/core/dev";var s=i.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),n=i.enum(["GET","POST"]),l=i.object({path:i.string().describe("Express route path (supports wildcards like /api/*)"),methods:i.array(n).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),c=i.union([i.string(),i.array(i.string()),i.literal("*")]),d=i.object({origin:c.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:i.array(s).describe("Allowed HTTP methods").optional(),headers:i.array(i.string()).describe("Allowed request headers").optional(),credentials:i.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:i.number().int().positive().describe("Preflight cache duration in seconds").optional()}),p=a.object({port:a.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:a.string().describe("Deprecated: use paths instead").optional(),paths:a.array(a.union([a.string(),l])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:a.union([a.boolean(),d]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)}),u=r(p),m={};t(m,{createTrigger:()=>w,step:()=>h});var h={};t(h,{pixelGet:()=>f,postEvent:()=>g});var g={trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:{name:"page view",data:{title:"Home",url:"https://example.com/"},entity:"page",action:"view"}},f={trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:{name:"page view",data:{title:"Home"},entity:"page",action:"view"}};import{startFlow as b}from"@walkeros/collector";var w=async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||"POST";if(!t){const r=await b(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if("object"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error("Express source server not found — ensure port is configured in source settings");o=`http://localhost:${a}`}let i=`${o}${r.path}`;r.query&&(i+=`?${new URLSearchParams(r.query).toString()}`);const s={method:a,headers:{"Content-Type":"application/json",...r.headers}};"GET"!==a&&"HEAD"!==a&&void 0!==r.body&&(s.body=JSON.stringify(r.body));const n=await fetch(i,s),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get("content-type")||"").includes("application/json")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}};export{m as examples,o as schemas};//# sourceMappingURL=dev.mjs.map
1
+ var e=Object.defineProperty,t=(t,o)=>{for(var r in o)e(t,r,{get:o[r],enumerable:!0})},o={};t(o,{CorsOptionsSchema:()=>d,CorsOrigin:()=>c,HttpMethod:()=>i,RouteConfigSchema:()=>l,RouteMethod:()=>n,SettingsSchema:()=>p,settings:()=>u});import{zodToSchema as r}from"@walkeros/core/dev";import{z as a}from"@walkeros/core/dev";import{z as s}from"@walkeros/core/dev";var i=s.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),n=s.enum(["GET","POST"]),l=s.object({path:s.string().describe("Express route path (supports wildcards like /api/*)"),methods:s.array(n).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),c=s.union([s.string(),s.array(s.string()),s.literal("*")]),d=s.object({origin:c.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:s.array(i).describe("Allowed HTTP methods").optional(),headers:s.array(s.string()).describe("Allowed request headers").optional(),credentials:s.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:s.number().int().positive().describe("Preflight cache duration in seconds").optional()}),p=a.object({port:a.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:a.string().describe("Deprecated: use paths instead").optional(),paths:a.array(a.union([a.string(),l])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:a.union([a.boolean(),d]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)}),u=r(p),m={};t(m,{createTrigger:()=>y,step:()=>h});var h={};t(h,{pixelGet:()=>b,postEvent:()=>g});var g={trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:{name:"page view",data:{title:"Home",url:"https://example.com/"},entity:"page",action:"view"}},b={trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:{name:"page view",data:{title:"Home"},entity:"page",action:"view"}};import{startFlow as f}from"@walkeros/collector";var y=async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||"POST";if(!t){const r=await f(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if("object"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error("Express source server not found — ensure port is configured in source settings");o=`http://localhost:${a}`}let s=`${o}${r.path}`;r.query&&(s+=`?${new URLSearchParams(r.query).toString()}`);const i={method:a,headers:{...r.headers&&Object.keys(r.headers).some(e=>"content-type"===e.toLowerCase())?{}:{"Content-Type":"application/json"},...r.headers}};"GET"!==a&&"HEAD"!==a&&void 0!==r.body&&(i.body="string"==typeof r.body?r.body:JSON.stringify(r.body));const n=await fetch(s,i),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get("content-type")||"").includes("application/json")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}};export{m as examples,o as schemas};//# sourceMappingURL=dev.mjs.map
package/dist/dev.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/examples/index.ts","../src/examples/step.ts","../src/examples/trigger.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","export * as step from './step';\nexport { createTrigger } from './trigger';\n","import type { Flow } from '@walkeros/core';\n\nexport const postEvent: Flow.StepExample = {\n trigger: { type: 'POST' },\n in: {\n method: 'POST',\n path: '/collect',\n body: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n },\n out: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n entity: 'page',\n action: 'view',\n },\n};\n\nexport const pixelGet: Flow.StepExample = {\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: {\n name: 'page view',\n data: { title: 'Home' },\n entity: 'page',\n action: 'view',\n },\n};\n","import type { Trigger, Collector } from '@walkeros/core';\nimport { startFlow } from '@walkeros/collector';\n\nexport interface Content {\n method: string;\n path: string;\n body?: unknown;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface Result {\n status: number;\n body: unknown;\n headers: Record<string, string>;\n}\n\n/**\n * Discover the port of a running HTTP server from the collector's sources.\n * Scans all registered sources for one with a `server` property (express pattern).\n */\nfunction discoverPort(collector: Collector.Instance): number | undefined {\n for (const source of Object.values(collector.sources || {})) {\n const s = source as { server?: { address(): { port: number } | string } };\n if (s.server) {\n const addr = s.server.address();\n if (typeof addr === 'object' && addr !== null) return addr.port;\n }\n }\n return undefined;\n}\n\n/**\n * Express source createTrigger.\n *\n * Boots a real express server via startFlow, then fires real HTTP requests.\n * Blackbox: no source instance access, no mocked req/res — just fetch().\n *\n * Pass `port: 0` in the express source settings to use a random available port.\n *\n * @example\n * const { trigger, flow } = await createTrigger(config);\n * const result = await trigger('POST')({ path: '/collect', body: { name: 'page view' } });\n * console.log(result.status, result.body);\n */\nconst createTrigger: Trigger.CreateFn<Content, Result> = async (\n config: Collector.InitConfig,\n) => {\n let flow: Trigger.FlowHandle | undefined;\n let baseUrl: string | undefined;\n\n const trigger: Trigger.Fn<Content, Result> =\n () =>\n async (content: Content): Promise<Result> => {\n const method = content.method || 'POST';\n\n // Lazy startFlow — first call boots the server\n if (!flow) {\n const result = await startFlow(config);\n flow = { collector: result.collector, elb: result.elb };\n\n const port = discoverPort(result.collector);\n if (!port)\n throw new Error(\n 'Express source server not found — ensure port is configured in source settings',\n );\n baseUrl = `http://localhost:${port}`;\n }\n\n // Build URL\n let url = `${baseUrl}${content.path}`;\n if (content.query) {\n url += `?${new URLSearchParams(content.query).toString()}`;\n }\n\n // Build fetch options\n const fetchOptions: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...content.headers,\n },\n };\n if (method !== 'GET' && method !== 'HEAD' && content.body !== undefined) {\n fetchOptions.body = JSON.stringify(content.body);\n }\n\n // Real HTTP request\n const response = await fetch(url, fetchOptions);\n\n // Capture response\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let body: unknown;\n const ct = response.headers.get('content-type') || '';\n if (ct.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n return {\n status: response.status,\n body,\n headers: responseHeaders,\n };\n };\n\n return {\n get flow() {\n return flow;\n },\n trigger,\n };\n};\n\nexport { createTrigger };\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,KAAAA,UAAS;;;ACAlB,SAAS,SAAS;AAKX,IAAM,aAAa,EAAE,KAAK;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMM,IAAM,cAAc,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC;AAK1C,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,MAAM,EACH,OAAO,EACP,SAAS,qDAAqD;AAAA,EACjE,SAAS,EACN,MAAM,WAAW,EACjB,IAAI,CAAC,EACL,SAAS,6DAA6D,EACtE,SAAS;AACd,CAAC;AASM,IAAM,aAAa,EAAE,MAAM;AAAA,EAChC,EAAE,OAAO;AAAA,EACT,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAClB,EAAE,QAAQ,GAAG;AACf,CAAC;AAMM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,QAAQ,WAAW;AAAA,IACjB;AAAA,EACF,EAAE,SAAS;AAAA,EAEX,SAAS,EAAE,MAAM,UAAU,EAAE,SAAS,sBAAsB,EAAE,SAAS;AAAA,EAEvE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,yBAAyB,EAAE,SAAS;AAAA,EAE1E,aAAa,EACV,QAAQ,EACR,SAAS,oDAAoD,EAC7D,SAAS;AAAA,EAEZ,QAAQ,EACL,OAAO,EACP,IAAI,EACJ,SAAS,EACT,SAAS,qCAAqC,EAC9C,SAAS;AACd,CAAC;;;ADlEM,IAAM,iBAAiBC,GAAE,OAAO;AAAA,EACrC,MAAMA,GACH,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,KAAK,EACT;AAAA,IACC;AAAA,EACF,EACC,SAAS;AAAA;AAAA,EAGZ,MAAMA,GAAE,OAAO,EAAE,SAAS,+BAA+B,EAAE,SAAS;AAAA,EAEpE,OAAOA,GACJ,MAAMA,GAAE,MAAM,CAACA,GAAE,OAAO,GAAG,iBAAiB,CAAC,CAAC,EAC9C,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF,EACC,SAAS;AAAA,EAEZ,MAAMA,GACH,MAAM,CAACA,GAAE,QAAQ,GAAG,iBAAiB,CAAC,EACtC;AAAA,IACC;AAAA,EACF,EACC,QAAQ,IAAI;AACjB,CAAC;;;AD3BM,IAAM,WAAW,YAAY,cAAc;;;AGPlD;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,YAA8B;AAAA,EACzC,SAAS,EAAE,MAAM,OAAO;AAAA,EACxB,IAAI;AAAA,IACF,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,MAAM,EAAE,OAAO,QAAQ,KAAK,uBAAuB;AAAA,IACrD;AAAA,EACF;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM,EAAE,OAAO,QAAQ,KAAK,uBAAuB;AAAA,IACnD,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAEO,IAAM,WAA6B;AAAA,EACxC,SAAS,EAAE,MAAM,MAAM;AAAA,EACvB,IAAI;AAAA,IACF,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO,EAAE,GAAG,aAAa,GAAG,mBAAmB;AAAA,EACjD;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM,EAAE,OAAO,OAAO;AAAA,IACtB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;;;AChCA,SAAS,iBAAiB;AAoB1B,SAAS,aAAa,WAAmD;AACvE,aAAW,UAAU,OAAO,OAAO,UAAU,WAAW,CAAC,CAAC,GAAG;AAC3D,UAAM,IAAI;AACV,QAAI,EAAE,QAAQ;AACZ,YAAM,OAAO,EAAE,OAAO,QAAQ;AAC9B,UAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO,KAAK;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;AAeA,IAAM,gBAAmD,OACvD,WACG;AACH,MAAI;AACJ,MAAI;AAEJ,QAAM,UACJ,MACA,OAAO,YAAsC;AAC3C,UAAM,SAAS,QAAQ,UAAU;AAGjC,QAAI,CAAC,MAAM;AACT,YAAM,SAAS,MAAM,UAAU,MAAM;AACrC,aAAO,EAAE,WAAW,OAAO,WAAW,KAAK,OAAO,IAAI;AAEtD,YAAM,OAAO,aAAa,OAAO,SAAS;AAC1C,UAAI,CAAC;AACH,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AACF,gBAAU,oBAAoB,IAAI;AAAA,IACpC;AAGA,QAAI,MAAM,GAAG,OAAO,GAAG,QAAQ,IAAI;AACnC,QAAI,QAAQ,OAAO;AACjB,aAAO,IAAI,IAAI,gBAAgB,QAAQ,KAAK,EAAE,SAAS,CAAC;AAAA,IAC1D;AAGA,UAAM,eAA4B;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,QAAQ;AAAA,MACb;AAAA,IACF;AACA,QAAI,WAAW,SAAS,WAAW,UAAU,QAAQ,SAAS,QAAW;AACvE,mBAAa,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA,IACjD;AAGA,UAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAG9C,UAAM,kBAA0C,CAAC;AACjD,aAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,sBAAgB,GAAG,IAAI;AAAA,IACzB,CAAC;AAED,QAAI;AACJ,UAAM,KAAK,SAAS,QAAQ,IAAI,cAAc,KAAK;AACnD,QAAI,GAAG,SAAS,kBAAkB,GAAG;AACnC,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,OAAO;AACL,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B;AAEA,WAAO;AAAA,MACL,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,IACX;AAAA,EACF;AAEF,SAAO;AAAA,IACL,IAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;","names":["z","z"]}
1
+ {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/examples/index.ts","../src/examples/step.ts","../src/examples/trigger.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","export * as step from './step';\nexport { createTrigger } from './trigger';\n","import type { Flow } from '@walkeros/core';\n\nexport const postEvent: Flow.StepExample = {\n trigger: { type: 'POST' },\n in: {\n method: 'POST',\n path: '/collect',\n body: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n },\n out: {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n entity: 'page',\n action: 'view',\n },\n};\n\nexport const pixelGet: Flow.StepExample = {\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: {\n name: 'page view',\n data: { title: 'Home' },\n entity: 'page',\n action: 'view',\n },\n};\n","import type { Trigger, Collector } from '@walkeros/core';\nimport { startFlow } from '@walkeros/collector';\n\nexport interface Content {\n method: string;\n path: string;\n body?: unknown;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface Result {\n status: number;\n body: unknown;\n headers: Record<string, string>;\n}\n\n/**\n * Discover the port of a running HTTP server from the collector's sources.\n * Scans all registered sources for one with a `server` property (express pattern).\n */\nfunction discoverPort(collector: Collector.Instance): number | undefined {\n for (const source of Object.values(collector.sources || {})) {\n const s = source as { server?: { address(): { port: number } | string } };\n if (s.server) {\n const addr = s.server.address();\n if (typeof addr === 'object' && addr !== null) return addr.port;\n }\n }\n return undefined;\n}\n\n/**\n * Express source createTrigger.\n *\n * Boots a real express server via startFlow, then fires real HTTP requests.\n * Blackbox: no source instance access, no mocked req/res — just fetch().\n *\n * Pass `port: 0` in the express source settings to use a random available port.\n *\n * @example\n * const { trigger, flow } = await createTrigger(config);\n * const result = await trigger('POST')({ path: '/collect', body: { name: 'page view' } });\n * console.log(result.status, result.body);\n */\nconst createTrigger: Trigger.CreateFn<Content, Result> = async (\n config: Collector.InitConfig,\n) => {\n let flow: Trigger.FlowHandle | undefined;\n let baseUrl: string | undefined;\n\n const trigger: Trigger.Fn<Content, Result> =\n () =>\n async (content: Content): Promise<Result> => {\n const method = content.method || 'POST';\n\n // Lazy startFlow — first call boots the server\n if (!flow) {\n const result = await startFlow(config);\n flow = { collector: result.collector, elb: result.elb };\n\n const port = discoverPort(result.collector);\n if (!port)\n throw new Error(\n 'Express source server not found — ensure port is configured in source settings',\n );\n baseUrl = `http://localhost:${port}`;\n }\n\n // Build URL\n let url = `${baseUrl}${content.path}`;\n if (content.query) {\n url += `?${new URLSearchParams(content.query).toString()}`;\n }\n\n // Build fetch options — only set default Content-Type if user doesn't provide one\n const hasContentType =\n content.headers &&\n Object.keys(content.headers).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const fetchOptions: RequestInit = {\n method,\n headers: {\n ...(hasContentType ? {} : { 'Content-Type': 'application/json' }),\n ...content.headers,\n },\n };\n if (method !== 'GET' && method !== 'HEAD' && content.body !== undefined) {\n // String bodies sent raw (e.g., base64 beacon with text/plain).\n // Object bodies JSON-serialized.\n fetchOptions.body =\n typeof content.body === 'string'\n ? content.body\n : JSON.stringify(content.body);\n }\n\n // Real HTTP request\n const response = await fetch(url, fetchOptions);\n\n // Capture response\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n let body: unknown;\n const ct = response.headers.get('content-type') || '';\n if (ct.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n return {\n status: response.status,\n body,\n headers: responseHeaders,\n };\n };\n\n return {\n get flow() {\n return flow;\n },\n trigger,\n };\n};\n\nexport { createTrigger };\n"],"mappings":"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","names":["z","z"]}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var mod,__create=Object.create,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty,__copyProps=(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},__toESM=(mod,isNodeMode,target)=>(target=null!=mod?__create(__getProtoOf(mod)):{},__copyProps(!isNodeMode&&mod&&mod.__esModule?target:__defProp(target,"default",{value:mod,enumerable:!0}),mod)),index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{TRANSPARENT_GIF:()=>TRANSPARENT_GIF,default:()=>index_default,setCorsHeaders:()=>setCorsHeaders,sourceExpress:()=>sourceExpress}),module.exports=(mod=index_exports,__copyProps(__defProp({},"__esModule",{value:!0}),mod));var import_express=__toESM(require("express")),import_cors=__toESM(require("cors")),import_core=require("@walkeros/core"),import_dev3=require("@walkeros/core/dev"),import_dev2=require("@walkeros/core/dev"),import_dev=require("@walkeros/core/dev"),HttpMethod=import_dev.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),RouteMethod=import_dev.z.enum(["GET","POST"]),RouteConfigSchema=import_dev.z.object({path:import_dev.z.string().describe("Express route path (supports wildcards like /api/*)"),methods:import_dev.z.array(RouteMethod).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),CorsOrigin=import_dev.z.union([import_dev.z.string(),import_dev.z.array(import_dev.z.string()),import_dev.z.literal("*")]),CorsOptionsSchema=import_dev.z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:import_dev.z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:import_dev.z.array(import_dev.z.string()).describe("Allowed request headers").optional(),credentials:import_dev.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:import_dev.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),SettingsSchema=import_dev2.z.object({port:import_dev2.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:import_dev2.z.string().describe("Deprecated: use paths instead").optional(),paths:import_dev2.z.array(import_dev2.z.union([import_dev2.z.string(),RouteConfigSchema])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:import_dev2.z.union([import_dev2.z.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)});(0,import_dev3.zodToSchema)(SettingsSchema);function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{var _a,_b,_c;const{config:config={},env:env}=context,expressLib=null!=(_a=env.express)?_a:import_express.default,corsLib=null!=(_b=env.cors)?_b:import_cors.default,parsed=SettingsSchema.parse(config.settings||{}),settings2={...parsed,paths:null!=(_c=parsed.paths)?_c:parsed.path?[parsed.path]:["/collect"]},app=expressLib();if(app.use(expressLib.json({limit:"1mb"})),!1!==settings2.cors){const corsOptions=!0===settings2.cors?{}:settings2.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings2.cors),void res.status(204).send();await context.setIngest(req);const respond=(0,import_core.createRespond)(options=>{var _a2;const status=null!=(_a2=options.status)?_a2:200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});if(context.setRespond(respond),"GET"===req.method){const parsedData=(0,import_core.requestToData)(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body;return eventData&&"object"==typeof eventData?(await env.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})):void res.status(400).json({success:!1,error:"Invalid event: body must be an object"})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=settings2.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]});for(const route of resolvedPaths)route.methods.includes("POST")&&app.post(route.path,push),route.methods.includes("GET")&&app.get(route.path,push),app.options(route.path,push);let server;void 0!==settings2.port&&(server=app.listen(settings2.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings2.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings2},push:push,httpHandler:app,app:app,server:server,destroy:_context=>new Promise((resolve,reject)=>{if(!server)return resolve();server.close(err=>err?reject(err):resolve())})}},index_default=sourceExpress;//# sourceMappingURL=index.js.map
1
+ "use strict";var mod,__create=Object.create,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty,__copyProps=(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},__toESM=(mod,isNodeMode,target)=>(target=null!=mod?__create(__getProtoOf(mod)):{},__copyProps(!isNodeMode&&mod&&mod.__esModule?target:__defProp(target,"default",{value:mod,enumerable:!0}),mod)),index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{TRANSPARENT_GIF:()=>TRANSPARENT_GIF,default:()=>index_default,setCorsHeaders:()=>setCorsHeaders,sourceExpress:()=>sourceExpress}),module.exports=(mod=index_exports,__copyProps(__defProp({},"__esModule",{value:!0}),mod));var import_express=__toESM(require("express")),import_cors=__toESM(require("cors")),import_core=require("@walkeros/core"),import_dev3=require("@walkeros/core/dev"),import_dev2=require("@walkeros/core/dev"),import_dev=require("@walkeros/core/dev"),HttpMethod=import_dev.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),RouteMethod=import_dev.z.enum(["GET","POST"]),RouteConfigSchema=import_dev.z.object({path:import_dev.z.string().describe("Express route path (supports wildcards like /api/*)"),methods:import_dev.z.array(RouteMethod).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),CorsOrigin=import_dev.z.union([import_dev.z.string(),import_dev.z.array(import_dev.z.string()),import_dev.z.literal("*")]),CorsOptionsSchema=import_dev.z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:import_dev.z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:import_dev.z.array(import_dev.z.string()).describe("Allowed request headers").optional(),credentials:import_dev.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:import_dev.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),SettingsSchema=import_dev2.z.object({port:import_dev2.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:import_dev2.z.string().describe("Deprecated: use paths instead").optional(),paths:import_dev2.z.array(import_dev2.z.union([import_dev2.z.string(),RouteConfigSchema])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:import_dev2.z.union([import_dev2.z.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)});(0,import_dev3.zodToSchema)(SettingsSchema);function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{var _a,_b,_c;const{config:config={},env:env}=context,expressLib=null!=(_a=env.express)?_a:import_express.default,corsLib=null!=(_b=env.cors)?_b:import_cors.default,parsed=SettingsSchema.parse(config.settings||{}),settings2={...parsed,paths:null!=(_c=parsed.paths)?_c:parsed.path?[parsed.path]:["/collect"]},app=expressLib();if(app.use(expressLib.json({limit:"1mb"})),app.use(expressLib.text({limit:"1mb"})),!1!==settings2.cors){const corsOptions=!0===settings2.cors?{}:settings2.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings2.cors),void res.status(204).send();await context.setIngest(req);const respond=(0,import_core.createRespond)(options=>{var _a2;const status=null!=(_a2=options.status)?_a2:200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});if(context.setRespond(respond),"GET"===req.method){const parsedData=(0,import_core.requestToData)(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return await env.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=settings2.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]});for(const route of resolvedPaths)route.methods.includes("POST")&&app.post(route.path,push),route.methods.includes("GET")&&app.get(route.path,push),app.options(route.path,push);let server;void 0!==settings2.port&&(server=app.listen(settings2.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings2.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings2},push:push,httpHandler:app,app:app,server:server,destroy:_context=>new Promise((resolve,reject)=>{if(!server)return resolve();server.close(err=>err?reject(err):resolve())})}},index_default=sourceExpress;//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData, createRespond } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Provides destroy() for graceful shutdown (called by runner)\n *\n * @param context Source context with config, env, logger, id\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n context: Source.Context<Types>,\n): Promise<ExpressSource> => {\n const { config = {}, env } = context;\n const expressLib = env.express ?? express;\n const corsLib = env.cors ?? cors;\n\n // Validate and apply default settings\n const parsed = SettingsSchema.parse(config.settings || {});\n const settings = {\n ...parsed,\n paths: parsed.paths ?? (parsed.path ? [parsed.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(expressLib.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(corsLib(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n // Default: JSON success (skipped if a step already called respond)\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\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 for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["import_dev","import_dev","express","cors","settings","_a"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData, createRespond } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Provides destroy() for graceful shutdown (called by runner)\n *\n * @param context Source context with config, env, logger, id\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n context: Source.Context<Types>,\n): Promise<ExpressSource> => {\n const { config = {}, env } = context;\n const expressLib = env.express ?? express;\n const corsLib = env.cors ?? cors;\n\n // Validate and apply default settings\n const parsed = SettingsSchema.parse(config.settings || {});\n const settings = {\n ...parsed,\n paths: parsed.paths ?? (parsed.path ? [parsed.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(expressLib.json({ limit: '1mb' }));\n app.use(expressLib.text({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(corsLib(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\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 for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["import_dev","import_dev","express","cors","settings","_a"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import express from"express";import cors from"cors";import{requestToData,createRespond}from"@walkeros/core";import{zodToSchema}from"@walkeros/core/dev";import{z as z2}from"@walkeros/core/dev";import{z}from"@walkeros/core/dev";var HttpMethod=z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),RouteMethod=z.enum(["GET","POST"]),RouteConfigSchema=z.object({path:z.string().describe("Express route path (supports wildcards like /api/*)"),methods:z.array(RouteMethod).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),CorsOrigin=z.union([z.string(),z.array(z.string()),z.literal("*")]),CorsOptionsSchema=z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:z.array(z.string()).describe("Allowed request headers").optional(),credentials:z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),SettingsSchema=z2.object({port:z2.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:z2.string().describe("Deprecated: use paths instead").optional(),paths:z2.array(z2.union([z2.string(),RouteConfigSchema])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:z2.union([z2.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)});zodToSchema(SettingsSchema);function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{var _a,_b,_c;const{config:config={},env:env}=context,expressLib=null!=(_a=env.express)?_a:express,corsLib=null!=(_b=env.cors)?_b:cors,parsed=SettingsSchema.parse(config.settings||{}),settings2={...parsed,paths:null!=(_c=parsed.paths)?_c:parsed.path?[parsed.path]:["/collect"]},app=expressLib();if(app.use(expressLib.json({limit:"1mb"})),!1!==settings2.cors){const corsOptions=!0===settings2.cors?{}:settings2.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings2.cors),void res.status(204).send();await context.setIngest(req);const respond=createRespond(options=>{var _a2;const status=null!=(_a2=options.status)?_a2:200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});if(context.setRespond(respond),"GET"===req.method){const parsedData=requestToData(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body;return eventData&&"object"==typeof eventData?(await env.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})):void res.status(400).json({success:!1,error:"Invalid event: body must be an object"})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=settings2.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]});for(const route of resolvedPaths)route.methods.includes("POST")&&app.post(route.path,push),route.methods.includes("GET")&&app.get(route.path,push),app.options(route.path,push);let server;void 0!==settings2.port&&(server=app.listen(settings2.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings2.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings2},push:push,httpHandler:app,app:app,server:server,destroy:_context=>new Promise((resolve,reject)=>{if(!server)return resolve();server.close(err=>err?reject(err):resolve())})}},index_default=sourceExpress;export{TRANSPARENT_GIF,index_default as default,setCorsHeaders,sourceExpress};//# sourceMappingURL=index.mjs.map
1
+ import express from"express";import cors from"cors";import{requestToData,createRespond}from"@walkeros/core";import{zodToSchema}from"@walkeros/core/dev";import{z as z2}from"@walkeros/core/dev";import{z}from"@walkeros/core/dev";var HttpMethod=z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),RouteMethod=z.enum(["GET","POST"]),RouteConfigSchema=z.object({path:z.string().describe("Express route path (supports wildcards like /api/*)"),methods:z.array(RouteMethod).min(1).describe("HTTP methods to register. OPTIONS always included for CORS.").optional()}),CorsOrigin=z.union([z.string(),z.array(z.string()),z.literal("*")]),CorsOptionsSchema=z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:z.array(z.string()).describe("Allowed request headers").optional(),credentials:z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),SettingsSchema=z2.object({port:z2.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:z2.string().describe("Deprecated: use paths instead").optional(),paths:z2.array(z2.union([z2.string(),RouteConfigSchema])).min(1).describe("Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.").optional(),cors:z2.union([z2.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)});zodToSchema(SettingsSchema);function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{var _a,_b,_c;const{config:config={},env:env}=context,expressLib=null!=(_a=env.express)?_a:express,corsLib=null!=(_b=env.cors)?_b:cors,parsed=SettingsSchema.parse(config.settings||{}),settings2={...parsed,paths:null!=(_c=parsed.paths)?_c:parsed.path?[parsed.path]:["/collect"]},app=expressLib();if(app.use(expressLib.json({limit:"1mb"})),app.use(expressLib.text({limit:"1mb"})),!1!==settings2.cors){const corsOptions=!0===settings2.cors?{}:settings2.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings2.cors),void res.status(204).send();await context.setIngest(req);const respond=createRespond(options=>{var _a2;const status=null!=(_a2=options.status)?_a2:200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});if(context.setRespond(respond),"GET"===req.method){const parsedData=requestToData(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return await env.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=settings2.paths.map(entry=>"string"==typeof entry?{path:entry,methods:["GET","POST"]}:{path:entry.path,methods:entry.methods||["GET","POST"]});for(const route of resolvedPaths)route.methods.includes("POST")&&app.post(route.path,push),route.methods.includes("GET")&&app.get(route.path,push),app.options(route.path,push);let server;void 0!==settings2.port&&(server=app.listen(settings2.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings2.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings2},push:push,httpHandler:app,app:app,server:server,destroy:_context=>new Promise((resolve,reject)=>{if(!server)return resolve();server.close(err=>err?reject(err):resolve())})}},index_default=sourceExpress;export{TRANSPARENT_GIF,index_default as default,setCorsHeaders,sourceExpress};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData, createRespond } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Provides destroy() for graceful shutdown (called by runner)\n *\n * @param context Source context with config, env, logger, id\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n context: Source.Context<Types>,\n): Promise<ExpressSource> => {\n const { config = {}, env } = context;\n const expressLib = env.express ?? express;\n const corsLib = env.cors ?? cors;\n\n // Validate and apply default settings\n const parsed = SettingsSchema.parse(config.settings || {});\n const settings = {\n ...parsed,\n paths: parsed.paths ?? (parsed.path ? [parsed.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(expressLib.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(corsLib(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n // Default: JSON success (skipped if a step already called respond)\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\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 for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":";AAAA,OAAO,aAA8C;AACrD,OAAO,UAAU;AACjB,SAAS,eAAe,qBAAqB;;;ACF7C,SAAS,mBAAmB;;;ACA5B,SAAS,KAAAA,UAAS;;;ACAlB,SAAS,SAAS;AAKX,IAAM,aAAa,EAAE,KAAK;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMM,IAAM,cAAc,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC;AAK1C,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,MAAM,EACH,OAAO,EACP,SAAS,qDAAqD;AAAA,EACjE,SAAS,EACN,MAAM,WAAW,EACjB,IAAI,CAAC,EACL,SAAS,6DAA6D,EACtE,SAAS;AACd,CAAC;AASM,IAAM,aAAa,EAAE,MAAM;AAAA,EAChC,EAAE,OAAO;AAAA,EACT,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAClB,EAAE,QAAQ,GAAG;AACf,CAAC;AAMM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,QAAQ,WAAW;AAAA,IACjB;AAAA,EACF,EAAE,SAAS;AAAA,EAEX,SAAS,EAAE,MAAM,UAAU,EAAE,SAAS,sBAAsB,EAAE,SAAS;AAAA,EAEvE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,yBAAyB,EAAE,SAAS;AAAA,EAE1E,aAAa,EACV,QAAQ,EACR,SAAS,oDAAoD,EAC7D,SAAS;AAAA,EAEZ,QAAQ,EACL,OAAO,EACP,IAAI,EACJ,SAAS,EACT,SAAS,qCAAqC,EAC9C,SAAS;AACd,CAAC;;;ADlEM,IAAM,iBAAiBC,GAAE,OAAO;AAAA,EACrC,MAAMA,GACH,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,KAAK,EACT;AAAA,IACC;AAAA,EACF,EACC,SAAS;AAAA;AAAA,EAGZ,MAAMA,GAAE,OAAO,EAAE,SAAS,+BAA+B,EAAE,SAAS;AAAA,EAEpE,OAAOA,GACJ,MAAMA,GAAE,MAAM,CAACA,GAAE,OAAO,GAAG,iBAAiB,CAAC,CAAC,EAC9C,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF,EACC,SAAS;AAAA,EAEZ,MAAMA,GACH,MAAM,CAACA,GAAE,QAAQ,GAAG,iBAAiB,CAAC,EACtC;AAAA,IACC;AAAA,EACF,EACC,QAAQ,IAAI;AACjB,CAAC;;;AD3BM,IAAM,WAAW,YAAY,cAAc;;;AGE3C,SAAS,eACd,KACA,aAAoC,MAC9B;AACN,MAAI,eAAe,MAAO;AAE1B,MAAI,eAAe,MAAM;AAEvB,QAAI,IAAI,+BAA+B,GAAG;AAC1C,QAAI,IAAI,gCAAgC,oBAAoB;AAC5D,QAAI,IAAI,gCAAgC,cAAc;AAAA,EACxD,OAAO;AAEL,QAAI,WAAW,QAAQ;AACrB,YAAM,SAAS,MAAM,QAAQ,WAAW,MAAM,IAC1C,WAAW,OAAO,KAAK,IAAI,IAC3B,WAAW;AACf,UAAI,IAAI,+BAA+B,MAAM;AAAA,IAC/C;AAEA,QAAI,WAAW,SAAS;AACtB,UAAI,IAAI,gCAAgC,WAAW,QAAQ,KAAK,IAAI,CAAC;AAAA,IACvE;AAEA,QAAI,WAAW,SAAS;AACtB,UAAI,IAAI,gCAAgC,WAAW,QAAQ,KAAK,IAAI,CAAC;AAAA,IACvE;AAEA,QAAI,WAAW,aAAa;AAC1B,UAAI,IAAI,oCAAoC,MAAM;AAAA,IACpD;AAEA,QAAI,WAAW,QAAQ;AACrB,UAAI,IAAI,0BAA0B,OAAO,WAAW,MAAM,CAAC;AAAA,IAC7D;AAAA,EACF;AACF;AAMO,IAAM,kBAAkB,OAAO;AAAA,EACpC;AAAA,EACA;AACF;;;AJjCO,IAAM,gBAAgB,OAC3B,YAC2B;AAvB7B;AAwBE,QAAM,EAAE,SAAS,CAAC,GAAG,IAAI,IAAI;AAC7B,QAAM,cAAa,SAAI,YAAJ,YAAe;AAClC,QAAM,WAAU,SAAI,SAAJ,YAAY;AAG5B,QAAM,SAAS,eAAe,MAAM,OAAO,YAAY,CAAC,CAAC;AACzD,QAAMC,YAAW;AAAA,IACf,GAAG;AAAA,IACH,QAAO,YAAO,UAAP,YAAiB,OAAO,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU;AAAA,EACnE;AAEA,QAAM,MAAM,WAAW;AAGvB,MAAI,IAAI,WAAW,KAAK,EAAE,OAAO,MAAM,CAAC,CAAC;AAGzC,MAAIA,UAAS,SAAS,OAAO;AAC3B,UAAM,cAAcA,UAAS,SAAS,OAAO,CAAC,IAAIA,UAAS;AAC3D,QAAI,IAAI,QAAQ,WAAW,CAAC;AAAA,EAC9B;AAMA,QAAM,OAAO,OAAO,KAAc,QAAiC;AACjE,QAAI;AAEF,UAAI,IAAI,WAAW,WAAW;AAC5B,uBAAe,KAAKA,UAAS,IAAI;AACjC,YAAI,OAAO,GAAG,EAAE,KAAK;AACrB;AAAA,MACF;AAGA,YAAM,QAAQ,UAAU,GAAG;AAG3B,YAAM,UAAU,cAAc,CAAC,YAAY;AA/DjD,YAAAC;AAgEQ,cAAM,UAASA,MAAA,QAAQ,WAAR,OAAAA,MAAkB;AACjC,YAAI,QAAQ,SAAS;AACnB,qBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAC1D,gBAAI,IAAI,KAAK,KAAK;AAAA,UACpB;AAAA,QACF;AACA,YAAI,OAAO,MAAM;AACjB,YAAI,OAAO,QAAQ,SAAS,YAAY,OAAO,SAAS,QAAQ,IAAI,GAAG;AACrE,cAAI,KAAK,QAAQ,IAAI;AAAA,QACvB,OAAO;AACL,cAAI,KAAK,QAAQ,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AACD,cAAQ,WAAW,OAAO;AAG1B,UAAI,IAAI,WAAW,OAAO;AAExB,cAAM,aAAa,cAAc,IAAI,GAAG;AAGxC,YAAI,cAAc,OAAO,eAAe,UAAU;AAChD,gBAAM,IAAI,KAAK,UAAU;AAAA,QAC3B;AAGA,gBAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,UACnB;AAAA,QACF,CAAC;AACD;AAAA,MACF;AAGA,UAAI,IAAI,WAAW,QAAQ;AACzB,cAAM,YAAY,IAAI;AAEtB,YAAI,CAAC,aAAa,OAAO,cAAc,UAAU;AAC/C,cAAI,OAAO,GAAG,EAAE,KAAK;AAAA,YACnB,SAAS;AAAA,YACT,OAAO;AAAA,UACT,CAAC;AACD;AAAA,QACF;AAGA,cAAM,IAAI,KAAK,SAAS;AAGxB,gBAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,WAAW,KAAK,IAAI,EAAE,EAAE,CAAC;AAC1D;AAAA,MACF;AAGA,UAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QACnB,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QACnB,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAClD,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,gBAAgBD,UAAS,MAAM;AAAA,IAAI,CAAC,UACxC,OAAO,UAAU,WACb,EAAE,MAAM,OAAO,SAAS,CAAC,OAAO,MAAM,EAAW,IACjD;AAAA,MACE,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM,WAAY,CAAC,OAAO,MAAM;AAAA,IAC3C;AAAA,EACN;AAEA,aAAW,SAAS,eAAe;AACjC,QAAI,MAAM,QAAQ,SAAS,MAAM,EAAG,KAAI,KAAK,MAAM,MAAM,IAAI;AAC7D,QAAI,MAAM,QAAQ,SAAS,KAAK,EAAG,KAAI,IAAI,MAAM,MAAM,IAAI;AAC3D,QAAI,QAAQ,MAAM,MAAM,IAAI;AAAA,EAC9B;AAGA,MAAI;AAEJ,MAAIA,UAAS,SAAS,QAAW;AAC/B,aAAS,IAAI,OAAOA,UAAS,MAAM,MAAM;AACvC,YAAM,aAAa,cAChB,IAAI,CAAC,MAAM;AACV,cAAM,UAAU,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,KAAK,IAAI;AACnD,eAAO,MAAM,OAAO,IAAI,EAAE,IAAI;AAAA,MAChC,CAAC,EACA,KAAK,IAAI;AACZ,UAAI,OAAO;AAAA,QACT,oCAAoCA,UAAS,IAAI;AAAA,IAAO;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,WAA0B;AAAA,IAC9B,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,GAAG;AAAA,MACH,UAAAA;AAAA,IACF;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,SAAS,CAAC,aACR,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,UAAI,CAAC,OAAQ,QAAO,QAAQ;AAC5B,aAAO,MAAM,CAAC,QAAS,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAE;AAAA,IACvD,CAAC;AAAA,EACL;AAEA,SAAO;AACT;AAwBA,IAAO,gBAAQ;","names":["z","z","settings","_a"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/primitives.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData, createRespond } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Provides destroy() for graceful shutdown (called by runner)\n *\n * @param context Source context with config, env, logger, id\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n context: Source.Context<Types>,\n): Promise<ExpressSource> => {\n const { config = {}, env } = context;\n const expressLib = env.express ?? express;\n const corsLib = env.cors ?? cors;\n\n // Validate and apply default settings\n const parsed = SettingsSchema.parse(config.settings || {});\n const settings = {\n ...parsed,\n paths: parsed.paths ?? (parsed.path ? [parsed.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(expressLib.json({ limit: '1mb' }));\n app.use(expressLib.text({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(corsLib(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\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 for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport * from './primitives';\nexport { SettingsSchema, type Settings } from './settings';\n\n// JSON Schema\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema, RouteConfigSchema } from './primitives';\n\n/**\n * Express source settings schema.\n */\nexport const SettingsSchema = z.object({\n port: z\n .number()\n .int()\n .min(0)\n .max(65535)\n .describe(\n 'HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)',\n )\n .optional(),\n\n /** @deprecated Use `paths` instead */\n path: z.string().describe('Deprecated: use paths instead').optional(),\n\n paths: z\n .array(z.union([z.string(), RouteConfigSchema]))\n .min(1)\n .describe(\n 'Route paths to register. String shorthand registers GET+POST. RouteConfig allows per-route method control.',\n )\n .optional(),\n\n cors: z\n .union([z.boolean(), CorsOptionsSchema])\n .describe(\n 'CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration',\n )\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n/**\n * HTTP methods enum\n */\nexport const HttpMethod = z.enum([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'OPTIONS',\n 'HEAD',\n]);\n\n/**\n * HTTP methods supported for route configuration.\n * OPTIONS is always registered for CORS (not user-configurable per route).\n */\nexport const RouteMethod = z.enum(['GET', 'POST']);\n\n/**\n * Route configuration for multi-path support.\n */\nexport const RouteConfigSchema = z.object({\n path: z\n .string()\n .describe('Express route path (supports wildcards like /api/*)'),\n methods: z\n .array(RouteMethod)\n .min(1)\n .describe('HTTP methods to register. OPTIONS always included for CORS.')\n .optional(),\n});\n\n/**\n * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["z","z","settings","_a"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-source-express",
4
- "version": "3.1.0",
4
+ "version": "3.1.1",
5
5
  "type": "source",
6
6
  "platform": [
7
7
  "server"
@@ -136,7 +136,7 @@
136
136
  },
137
137
  "examples": {
138
138
  "createTrigger": {
139
- "$code": "async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||\"POST\";if(!t){const r=await b(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if(\"object\"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error(\"Express source server not found — ensure port is configured in source settings\");o=`http://localhost:${a}`}let i=`${o}${r.path}`;r.query&&(i+=`?${new URLSearchParams(r.query).toString()}`);const s={method:a,headers:{\"Content-Type\":\"application/json\",...r.headers}};\"GET\"!==a&&\"HEAD\"!==a&&void 0!==r.body&&(s.body=JSON.stringify(r.body));const n=await fetch(i,s),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get(\"content-type\")||\"\").includes(\"application/json\")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}}"
139
+ "$code": "async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||\"POST\";if(!t){const r=await f(e);t={collector:r.collector,elb:r.elb};const a=function(e){for(const t of Object.values(e.sources||{})){const e=t;if(e.server){const t=e.server.address();if(\"object\"==typeof t&&null!==t)return t.port}}}(r.collector);if(!a)throw new Error(\"Express source server not found — ensure port is configured in source settings\");o=`http://localhost:${a}`}let s=`${o}${r.path}`;r.query&&(s+=`?${new URLSearchParams(r.query).toString()}`);const i={method:a,headers:{...r.headers&&Object.keys(r.headers).some(e=>\"content-type\"===e.toLowerCase())?{}:{\"Content-Type\":\"application/json\"},...r.headers}};\"GET\"!==a&&\"HEAD\"!==a&&void 0!==r.body&&(i.body=\"string\"==typeof r.body?r.body:JSON.stringify(r.body));const n=await fetch(s,i),l={};let c;n.headers.forEach((e,t)=>{l[t]=e});return c=(n.headers.get(\"content-type\")||\"\").includes(\"application/json\")?await n.json():await n.text(),{status:n.status,body:c,headers:l}}}}"
140
140
  },
141
141
  "step": {
142
142
  "pixelGet": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-source-express",
3
3
  "description": "Express server source for walkerOS",
4
- "version": "3.1.1",
4
+ "version": "3.2.0",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -19,8 +19,8 @@
19
19
  "update": "npx npm-check-updates -u && npm update"
20
20
  },
21
21
  "dependencies": {
22
- "@walkeros/collector": "^3.1.1",
23
- "@walkeros/core": "^3.1.1",
22
+ "@walkeros/collector": "^3.2.0",
23
+ "@walkeros/core": "^3.2.0",
24
24
  "express": "^5.2.1",
25
25
  "cors": "^2.8.5"
26
26
  },