@walkeros/server-source-express 4.2.1-next-1781594053720 → 4.2.1-next-1781682752679
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -3
- package/dist/dev.d.mts +0 -1
- package/dist/dev.d.ts +0 -1
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.d.mts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +2 -7
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
# @walkeros/server-source-express
|
|
2
2
|
|
|
3
|
-
## 4.2.1-next-
|
|
3
|
+
## 4.2.1-next-1781682752679
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
+
- 0a8a08b: Add an optional `async` option to the source config
|
|
8
|
+
(`Source.Config.async`) for respond-first acknowledgement on
|
|
9
|
+
response-producing server sources. The express source now reads `config.async`
|
|
10
|
+
(default `true`): a 2xx response means the event was accepted, not yet
|
|
11
|
+
delivered.
|
|
7
12
|
- Updated dependencies [bd9188d]
|
|
13
|
+
- Updated dependencies [d8aebd1]
|
|
8
14
|
- Updated dependencies [5cbcd23]
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
- Updated dependencies [0a8a08b]
|
|
16
|
+
- Updated dependencies [8afb7cc]
|
|
17
|
+
- Updated dependencies [8afb7cc]
|
|
18
|
+
- @walkeros/collector@4.2.1-next-1781682752679
|
|
19
|
+
- @walkeros/core@4.2.1-next-1781682752679
|
|
11
20
|
|
|
12
21
|
## 4.2.0
|
|
13
22
|
|
package/dist/dev.d.mts
CHANGED
|
@@ -89,7 +89,6 @@ declare const SettingsSchema: z.ZodObject<{
|
|
|
89
89
|
credentials: z.ZodOptional<z.ZodBoolean>;
|
|
90
90
|
maxAge: z.ZodOptional<z.ZodNumber>;
|
|
91
91
|
}, z.core.$strip>]>>;
|
|
92
|
-
async: z.ZodDefault<z.ZodBoolean>;
|
|
93
92
|
}, z.core.$strip>;
|
|
94
93
|
type Settings = z.infer<typeof SettingsSchema>;
|
|
95
94
|
|
package/dist/dev.d.ts
CHANGED
|
@@ -89,7 +89,6 @@ declare const SettingsSchema: z.ZodObject<{
|
|
|
89
89
|
credentials: z.ZodOptional<z.ZodBoolean>;
|
|
90
90
|
maxAge: z.ZodOptional<z.ZodNumber>;
|
|
91
91
|
}, z.core.$strip>]>>;
|
|
92
|
-
async: z.ZodDefault<z.ZodBoolean>;
|
|
93
92
|
}, z.core.$strip>;
|
|
94
93
|
type Settings = z.infer<typeof SettingsSchema>;
|
|
95
94
|
|
package/dist/dev.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,o=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,
|
|
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:()=>b,CorsOrigin:()=>g,HttpMethod:()=>p,RouteConfigSchema:()=>h,RouteMethod:()=>u,SettingsSchema:()=>m,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("*")]),b=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()}),m=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(),b]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0)}),y=(0,l.zodToSchema)(m),f={};s(f,{createTrigger:()=>z,step:()=>w});var w={};s(w,{pixelGet:()=>v,postEvent:()=>T});var T={title:"POST event",description:"An Express POST to /collect with a JSON body becomes a single walker elb event.",trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:[["elb",{name:"page view",data:{title:"Home",url:"https://example.com/"}}]]},v={title:"Pixel GET",description:"An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.",trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:[["elb",{e:"page view",d:'{"title":"Home"}'}]]},O=require("@walkeros/collector");var z=async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const a=r.method||"POST";if(!t){const r=await(0,O.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 async: z\n .boolean()\n .describe(\n 'Respond-first delivery (default true). When true, POST responds 2xx (\"accepted\") immediately and pushes to the collector without blocking the response; a rejected push is logged (destination errors are DLQ\\'d inside the collector). When false, the response waits for the push to settle. The GET pixel always responds first regardless of this flag. A 2xx means accepted, not delivered.',\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 title: 'POST event',\n description:\n 'An Express POST to /collect with a JSON body becomes a single walker elb event.',\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 [\n 'elb',\n {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n ],\n ],\n};\n\nexport const pixelGet: Flow.StepExample = {\n title: 'Pixel GET',\n description:\n 'An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.',\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: [\n [\n 'elb',\n {\n e: 'page view',\n d: '{\"title\":\"Home\"}',\n },\n ],\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"]}
|
|
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 title: 'POST event',\n description:\n 'An Express POST to /collect with a JSON body becomes a single walker elb event.',\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 [\n 'elb',\n {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n ],\n ],\n};\n\nexport const pixelGet: Flow.StepExample = {\n title: 'Pixel GET',\n description:\n 'An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.',\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: [\n [\n 'elb',\n {\n e: 'page view',\n d: '{\"title\":\"Home\"}',\n },\n ],\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:()=>i,RouteConfigSchema:()=>l,RouteMethod:()=>n,SettingsSchema:()=>p,settings:()=>
|
|
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),h={};t(h,{createTrigger:()=>y,step:()=>m});var m={};t(m,{pixelGet:()=>b,postEvent:()=>g});var g={title:"POST event",description:"An Express POST to /collect with a JSON body becomes a single walker elb event.",trigger:{type:"POST"},in:{method:"POST",path:"/collect",body:{name:"page view",data:{title:"Home",url:"https://example.com/"}}},out:[["elb",{name:"page view",data:{title:"Home",url:"https://example.com/"}}]]},b={title:"Pixel GET",description:"An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.",trigger:{type:"GET"},in:{method:"GET",path:"/collect",query:{e:"page view",d:'{"title":"Home"}'}},out:[["elb",{e:"page view",d:'{"title":"Home"}'}]]};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{h 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 async: z\n .boolean()\n .describe(\n 'Respond-first delivery (default true). When true, POST responds 2xx (\"accepted\") immediately and pushes to the collector without blocking the response; a rejected push is logged (destination errors are DLQ\\'d inside the collector). When false, the response waits for the push to settle. The GET pixel always responds first regardless of this flag. A 2xx means accepted, not delivered.',\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 title: 'POST event',\n description:\n 'An Express POST to /collect with a JSON body becomes a single walker elb event.',\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 [\n 'elb',\n {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n ],\n ],\n};\n\nexport const pixelGet: Flow.StepExample = {\n title: 'Pixel GET',\n description:\n 'An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.',\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: [\n [\n 'elb',\n {\n e: 'page view',\n d: '{\"title\":\"Home\"}',\n },\n ],\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"]}
|
|
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 title: 'POST event',\n description:\n 'An Express POST to /collect with a JSON body becomes a single walker elb event.',\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 [\n 'elb',\n {\n name: 'page view',\n data: { title: 'Home', url: 'https://example.com/' },\n },\n ],\n ],\n};\n\nexport const pixelGet: Flow.StepExample = {\n title: 'Pixel GET',\n description:\n 'An Express GET to /collect with query parameters is parsed into an elb event payload for pixel tracking.',\n trigger: { type: 'GET' },\n in: {\n method: 'GET',\n path: '/collect',\n query: { e: 'page view', d: '{\"title\":\"Home\"}' },\n },\n out: [\n [\n 'elb',\n {\n e: 'page view',\n d: '{\"title\":\"Home\"}',\n },\n ],\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.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
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");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"),toError=value=>value instanceof Error?value:new Error(String(value)),sourceExpress=async context=>{const{config:config={},env:env}=context,expressLib=env.express??import_express.default,corsLib=env.cors??import_cors.default,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,
|
|
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");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"),toError=value=>value instanceof Error?value:new Error(String(value)),sourceExpress=async context=>{const{config:config={},env:env}=context,expressLib=env.express??import_express.default,corsLib=env.cors??import_cors.default,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},respondFirst=config.async??!0,app=expressLib();if(app.use(expressLib.json({limit:"1mb",type:["application/json","text/plain"]})),!1!==settings.cors){const corsOptions=!0===settings.cors?{}:settings.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings.cors),void res.status(204).send();const respond=(0,import_core.createRespond)(options=>{const status=options.status??200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status);const body=options.body;"string"==typeof body||Buffer.isBuffer(body)?res.send(body):body instanceof Uint8Array?res.send(Buffer.from(body)):res.json(body)});await context.withScope(req,respond,async env2=>{if("GET"===req.method){const parsedData=(0,import_core.requestToData)(req.url),respondGif=()=>respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}});return void(parsedData&&"object"==typeof parsedData?respondFirst?(respondGif(),env2.push(parsedData).catch(err=>{env2.logger.error(toError(err))})):(await env2.push(parsedData),respondGif()):respondGif())}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return void(respondFirst?(respond({body:{success:!0,timestamp:Date.now()}}),env2.push(eventData).catch(err=>{env2.logger.error(toError(err))})):(await env2.push(eventData),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=settings.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!==settings.port&&(server=app.listen(settings.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings},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/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 { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Normalize an unknown rejection reason into an Error for the logger.\n * A fire-and-forget push can reject with any value; the logger accepts\n * `string | Error`, so non-Error reasons are wrapped.\n */\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\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 // Apply defaults (no runtime validation — flow.json is developer-controlled).\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n // Respond-first by default: a 2xx means \"accepted\", not \"delivered\".\n async: userSettings.async ?? true,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Body parsing — JSON content-type plus text/plain so navigator.sendBeacon\n // payloads (which the browser forces to text/plain;charset=UTF-8) are also\n // parsed as JSON. 1mb default limit.\n app.use(\n expressLib.json({\n limit: '1mb',\n type: ['application/json', 'text/plain'],\n }),\n );\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 * Each inbound request gets its own `withScope` invocation. The per-scope\n * env carries this request's `ingest` and `respond` end to end, so\n * concurrent requests never crosstalk through source-factory state.\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight (no scope needed: no event, no ingest)\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\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 const body = options.body;\n if (typeof body === 'string' || Buffer.isBuffer(body)) {\n res.send(body);\n } else if (body instanceof Uint8Array) {\n // A decoded cache value surfaces binary as a plain Uint8Array,\n // not a Node Buffer; send it as bytes (res.json would corrupt it).\n res.send(Buffer.from(body));\n } else {\n res.json(body);\n }\n });\n\n await context.withScope(req, respond, async (env) => {\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 // Default GIF body (idempotent fallback; skipped if a step already\n // called respond, e.g. a cache/asset destination serving real bytes).\n const respondGif = () =>\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\n if (parsedData && typeof parsedData === 'object') {\n if (settings.async) {\n // Respond-first: the tracking pixel must return instantly and\n // never block on backend delivery. Fire the push without\n // awaiting; a rejected push is logged (destination errors are\n // DLQ'd inside the collector). A 2xx means \"accepted\", not\n // \"delivered\".\n respondGif();\n env.push(parsedData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous: await the push so a step (e.g. a cache/asset\n // destination) can respond with real content before the GIF\n // fallback applies.\n await env.push(parsedData);\n respondGif();\n }\n } else {\n respondGif();\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 if (settings.async) {\n // Respond-first (\"accepted\"), then deliver asynchronously. A\n // rejected push is logged, not surfaced to the client and not left\n // unhandled (destination errors are DLQ'd inside the collector).\n respond({ body: { success: true, timestamp: Date.now() } });\n env.push(eventData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous ack: wait for delivery to settle before responding.\n await env.push(eventData);\n respond({ body: { success: true, timestamp: Date.now() } });\n }\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 });\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 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":["express","cors","env"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.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 { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Normalize an unknown rejection reason into an Error for the logger.\n * A fire-and-forget push can reject with any value; the logger accepts\n * `string | Error`, so non-Error reasons are wrapped.\n */\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\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 // Apply defaults (no runtime validation — flow.json is developer-controlled).\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n\n // Respond-first by default: a 2xx means \"accepted\", not \"delivered\".\n // Standardized on the source config (Source.Config.async), not settings.\n const respondFirst = config.async ?? true;\n\n const app = expressLib();\n\n // Body parsing — JSON content-type plus text/plain so navigator.sendBeacon\n // payloads (which the browser forces to text/plain;charset=UTF-8) are also\n // parsed as JSON. 1mb default limit.\n app.use(\n expressLib.json({\n limit: '1mb',\n type: ['application/json', 'text/plain'],\n }),\n );\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 * Each inbound request gets its own `withScope` invocation. The per-scope\n * env carries this request's `ingest` and `respond` end to end, so\n * concurrent requests never crosstalk through source-factory state.\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight (no scope needed: no event, no ingest)\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\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 const body = options.body;\n if (typeof body === 'string' || Buffer.isBuffer(body)) {\n res.send(body);\n } else if (body instanceof Uint8Array) {\n // A decoded cache value surfaces binary as a plain Uint8Array,\n // not a Node Buffer; send it as bytes (res.json would corrupt it).\n res.send(Buffer.from(body));\n } else {\n res.json(body);\n }\n });\n\n await context.withScope(req, respond, async (env) => {\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 // Default GIF body (idempotent fallback; skipped if a step already\n // called respond, e.g. a cache/asset destination serving real bytes).\n const respondGif = () =>\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\n if (parsedData && typeof parsedData === 'object') {\n if (respondFirst) {\n // Respond-first: the tracking pixel must return instantly and\n // never block on backend delivery. Fire the push without\n // awaiting; a rejected push is logged (destination errors are\n // DLQ'd inside the collector). A 2xx means \"accepted\", not\n // \"delivered\".\n respondGif();\n env.push(parsedData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous: await the push so a step (e.g. a cache/asset\n // destination) can respond with real content before the GIF\n // fallback applies.\n await env.push(parsedData);\n respondGif();\n }\n } else {\n respondGif();\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 if (respondFirst) {\n // Respond-first (\"accepted\"), then deliver asynchronously. A\n // rejected push is logged, not surfaced to the client and not left\n // unhandled (destination errors are DLQ'd inside the collector).\n respond({ body: { success: true, timestamp: Date.now() } });\n env.push(eventData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous ack: wait for delivery to settle before responding.\n await env.push(eventData);\n respond({ body: { success: true, timestamp: Date.now() } });\n }\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 });\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 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":["express","cors","env"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import express from"express";import cors from"cors";import{requestToData,createRespond}from"@walkeros/core";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"),toError=value=>value instanceof Error?value:new Error(String(value)),sourceExpress=async context=>{const{config:config={},env:env}=context,expressLib=env.express??express,corsLib=env.cors??cors,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,
|
|
1
|
+
import express from"express";import cors from"cors";import{requestToData,createRespond}from"@walkeros/core";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"),toError=value=>value instanceof Error?value:new Error(String(value)),sourceExpress=async context=>{const{config:config={},env:env}=context,expressLib=env.express??express,corsLib=env.cors??cors,userSettings=config.settings||{},settings={...userSettings,cors:userSettings.cors??!0,paths:userSettings.paths??(userSettings.path?[userSettings.path]:["/collect"])},respondFirst=config.async??!0,app=expressLib();if(app.use(expressLib.json({limit:"1mb",type:["application/json","text/plain"]})),!1!==settings.cors){const corsOptions=!0===settings.cors?{}:settings.cors;app.use(corsLib(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings.cors),void res.status(204).send();const respond=createRespond(options=>{const status=options.status??200;if(options.headers)for(const[key,value]of Object.entries(options.headers))res.set(key,value);res.status(status);const body=options.body;"string"==typeof body||Buffer.isBuffer(body)?res.send(body):body instanceof Uint8Array?res.send(Buffer.from(body)):res.json(body)});await context.withScope(req,respond,async env2=>{if("GET"===req.method){const parsedData=requestToData(req.url),respondGif=()=>respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}});return void(parsedData&&"object"==typeof parsedData?respondFirst?(respondGif(),env2.push(parsedData).catch(err=>{env2.logger.error(toError(err))})):(await env2.push(parsedData),respondGif()):respondGif())}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return void(respondFirst?(respond({body:{success:!0,timestamp:Date.now()}}),env2.push(eventData).catch(err=>{env2.logger.error(toError(err))})):(await env2.push(eventData),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=settings.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!==settings.port&&(server=app.listen(settings.port,()=>{const routeLines=resolvedPaths.map(r=>` ${[...r.methods,"OPTIONS"].join(", ")} ${r.path}`).join("\n");env.logger.info(`Express source listening on port ${settings.port}\n`+routeLines)}));return{type:"express",config:{...config,settings:settings},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
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.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 { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Normalize an unknown rejection reason into an Error for the logger.\n * A fire-and-forget push can reject with any value; the logger accepts\n * `string | Error`, so non-Error reasons are wrapped.\n */\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\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 // Apply defaults (no runtime validation — flow.json is developer-controlled).\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n // Respond-first by default: a 2xx means \"accepted\", not \"delivered\".\n async: userSettings.async ?? true,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n\n const app = expressLib();\n\n // Body parsing — JSON content-type plus text/plain so navigator.sendBeacon\n // payloads (which the browser forces to text/plain;charset=UTF-8) are also\n // parsed as JSON. 1mb default limit.\n app.use(\n expressLib.json({\n limit: '1mb',\n type: ['application/json', 'text/plain'],\n }),\n );\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 * Each inbound request gets its own `withScope` invocation. The per-scope\n * env carries this request's `ingest` and `respond` end to end, so\n * concurrent requests never crosstalk through source-factory state.\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight (no scope needed: no event, no ingest)\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\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 const body = options.body;\n if (typeof body === 'string' || Buffer.isBuffer(body)) {\n res.send(body);\n } else if (body instanceof Uint8Array) {\n // A decoded cache value surfaces binary as a plain Uint8Array,\n // not a Node Buffer; send it as bytes (res.json would corrupt it).\n res.send(Buffer.from(body));\n } else {\n res.json(body);\n }\n });\n\n await context.withScope(req, respond, async (env) => {\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 // Default GIF body (idempotent fallback; skipped if a step already\n // called respond, e.g. a cache/asset destination serving real bytes).\n const respondGif = () =>\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\n if (parsedData && typeof parsedData === 'object') {\n if (settings.async) {\n // Respond-first: the tracking pixel must return instantly and\n // never block on backend delivery. Fire the push without\n // awaiting; a rejected push is logged (destination errors are\n // DLQ'd inside the collector). A 2xx means \"accepted\", not\n // \"delivered\".\n respondGif();\n env.push(parsedData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous: await the push so a step (e.g. a cache/asset\n // destination) can respond with real content before the GIF\n // fallback applies.\n await env.push(parsedData);\n respondGif();\n }\n } else {\n respondGif();\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 if (settings.async) {\n // Respond-first (\"accepted\"), then deliver asynchronously. A\n // rejected push is logged, not surfaced to the client and not left\n // unhandled (destination errors are DLQ'd inside the collector).\n respond({ body: { success: true, timestamp: Date.now() } });\n env.push(eventData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous ack: wait for delivery to settle before responding.\n await env.push(eventData);\n respond({ body: { success: true, timestamp: Date.now() } });\n }\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 });\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 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;;;ACOtC,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;;;AD1CA,IAAM,UAAU,CAAC,UACf,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAenD,IAAM,gBAAgB,OAC3B,YAC2B;AAC3B,QAAM,EAAE,SAAS,CAAC,GAAG,IAAI,IAAI;AAC7B,QAAM,aAAa,IAAI,WAAW;AAClC,QAAM,UAAU,IAAI,QAAQ;AAG5B,QAAM,eAAe,OAAO,YAAY,CAAC;AACzC,QAAM,WAAW;AAAA,IACf,GAAG;AAAA,IACH,MAAM,aAAa,QAAQ;AAAA;AAAA,IAE3B,OAAO,aAAa,SAAS;AAAA,IAC7B,OACE,aAAa,UACZ,aAAa,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU;AAAA,EAC1D;AAEA,QAAM,MAAM,WAAW;AAKvB,MAAI;AAAA,IACF,WAAW,KAAK;AAAA,MACd,OAAO;AAAA,MACP,MAAM,CAAC,oBAAoB,YAAY;AAAA,IACzC,CAAC;AAAA,EACH;AAGA,MAAI,SAAS,SAAS,OAAO;AAC3B,UAAM,cAAc,SAAS,SAAS,OAAO,CAAC,IAAI,SAAS;AAC3D,QAAI,IAAI,QAAQ,WAAW,CAAC;AAAA,EAC9B;AAUA,QAAM,OAAO,OAAO,KAAc,QAAiC;AACjE,QAAI;AAEF,UAAI,IAAI,WAAW,WAAW;AAC5B,uBAAe,KAAK,SAAS,IAAI;AACjC,YAAI,OAAO,GAAG,EAAE,KAAK;AACrB;AAAA,MACF;AAGA,YAAM,UAAU,cAAc,CAAC,YAAY;AACzC,cAAM,SAAS,QAAQ,UAAU;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,cAAM,OAAO,QAAQ;AACrB,YAAI,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,GAAG;AACrD,cAAI,KAAK,IAAI;AAAA,QACf,WAAW,gBAAgB,YAAY;AAGrC,cAAI,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA,QAC5B,OAAO;AACL,cAAI,KAAK,IAAI;AAAA,QACf;AAAA,MACF,CAAC;AAED,YAAM,QAAQ,UAAU,KAAK,SAAS,OAAOA,SAAQ;AAEnD,YAAI,IAAI,WAAW,OAAO;AAExB,gBAAM,aAAa,cAAc,IAAI,GAAG;AAIxC,gBAAM,aAAa,MACjB,QAAQ;AAAA,YACN,MAAM;AAAA,YACN,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,YACnB;AAAA,UACF,CAAC;AAEH,cAAI,cAAc,OAAO,eAAe,UAAU;AAChD,gBAAI,SAAS,OAAO;AAMlB,yBAAW;AACX,cAAAA,KAAI,KAAK,UAAU,EAAE,MAAM,CAAC,QAAiB;AAC3C,gBAAAA,KAAI,OAAO,MAAM,QAAQ,GAAG,CAAC;AAAA,cAC/B,CAAC;AAAA,YACH,OAAO;AAIL,oBAAMA,KAAI,KAAK,UAAU;AACzB,yBAAW;AAAA,YACb;AAAA,UACF,OAAO;AACL,uBAAW;AAAA,UACb;AACA;AAAA,QACF;AAGA,YAAI,IAAI,WAAW,QAAQ;AACzB,gBAAM,YACJ,IAAI,QAAQ,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,CAAC;AAEzD,cAAI,SAAS,OAAO;AAIlB,oBAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,WAAW,KAAK,IAAI,EAAE,EAAE,CAAC;AAC1D,YAAAA,KAAI,KAAK,SAAS,EAAE,MAAM,CAAC,QAAiB;AAC1C,cAAAA,KAAI,OAAO,MAAM,QAAQ,GAAG,CAAC;AAAA,YAC/B,CAAC;AAAA,UACH,OAAO;AAEL,kBAAMA,KAAI,KAAK,SAAS;AACxB,oBAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,WAAW,KAAK,IAAI,EAAE,EAAE,CAAC;AAAA,UAC5D;AACA;AAAA,QACF;AAGA,YAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UACnB,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH,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,gBAAgB,SAAS,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,MAAI,SAAS,SAAS,QAAW;AAC/B,aAAS,IAAI,OAAO,SAAS,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,oCAAoC,SAAS,IAAI;AAAA,IAAO;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,WAA0B;AAAA,IAC9B,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,GAAG;AAAA,MACH;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":["env"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.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 { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Normalize an unknown rejection reason into an Error for the logger.\n * A fire-and-forget push can reject with any value; the logger accepts\n * `string | Error`, so non-Error reasons are wrapped.\n */\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\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 // Apply defaults (no runtime validation — flow.json is developer-controlled).\n const userSettings = config.settings || {};\n const settings = {\n ...userSettings,\n cors: userSettings.cors ?? true,\n paths:\n userSettings.paths ??\n (userSettings.path ? [userSettings.path] : ['/collect']),\n };\n\n // Respond-first by default: a 2xx means \"accepted\", not \"delivered\".\n // Standardized on the source config (Source.Config.async), not settings.\n const respondFirst = config.async ?? true;\n\n const app = expressLib();\n\n // Body parsing — JSON content-type plus text/plain so navigator.sendBeacon\n // payloads (which the browser forces to text/plain;charset=UTF-8) are also\n // parsed as JSON. 1mb default limit.\n app.use(\n expressLib.json({\n limit: '1mb',\n type: ['application/json', 'text/plain'],\n }),\n );\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 * Each inbound request gets its own `withScope` invocation. The per-scope\n * env carries this request's `ingest` and `respond` end to end, so\n * concurrent requests never crosstalk through source-factory state.\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight (no scope needed: no event, no ingest)\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\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 const body = options.body;\n if (typeof body === 'string' || Buffer.isBuffer(body)) {\n res.send(body);\n } else if (body instanceof Uint8Array) {\n // A decoded cache value surfaces binary as a plain Uint8Array,\n // not a Node Buffer; send it as bytes (res.json would corrupt it).\n res.send(Buffer.from(body));\n } else {\n res.json(body);\n }\n });\n\n await context.withScope(req, respond, async (env) => {\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 // Default GIF body (idempotent fallback; skipped if a step already\n // called respond, e.g. a cache/asset destination serving real bytes).\n const respondGif = () =>\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\n if (parsedData && typeof parsedData === 'object') {\n if (respondFirst) {\n // Respond-first: the tracking pixel must return instantly and\n // never block on backend delivery. Fire the push without\n // awaiting; a rejected push is logged (destination errors are\n // DLQ'd inside the collector). A 2xx means \"accepted\", not\n // \"delivered\".\n respondGif();\n env.push(parsedData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous: await the push so a step (e.g. a cache/asset\n // destination) can respond with real content before the GIF\n // fallback applies.\n await env.push(parsedData);\n respondGif();\n }\n } else {\n respondGif();\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 if (respondFirst) {\n // Respond-first (\"accepted\"), then deliver asynchronously. A\n // rejected push is logged, not surfaced to the client and not left\n // unhandled (destination errors are DLQ'd inside the collector).\n respond({ body: { success: true, timestamp: Date.now() } });\n env.push(eventData).catch((err: unknown) => {\n env.logger.error(toError(err));\n });\n } else {\n // Synchronous ack: wait for delivery to settle before responding.\n await env.push(eventData);\n respond({ body: { success: true, timestamp: Date.now() } });\n }\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 });\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 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":["env"]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/server-source-express",
|
|
4
|
-
"version": "4.2.1-next-
|
|
4
|
+
"version": "4.2.1-next-1781682752679",
|
|
5
5
|
"type": "source",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server"
|
|
@@ -126,11 +126,6 @@
|
|
|
126
126
|
}
|
|
127
127
|
],
|
|
128
128
|
"description": "CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration"
|
|
129
|
-
},
|
|
130
|
-
"async": {
|
|
131
|
-
"default": true,
|
|
132
|
-
"type": "boolean",
|
|
133
|
-
"description": "Respond-first delivery (default true). When true, POST responds 2xx (\"accepted\") immediately and pushes to the collector without blocking the response; a rejected push is logged (destination errors are DLQ'd inside the collector). When false, the response waits for the push to settle. The GET pixel always responds first regardless of this flag. A 2xx means accepted, not delivered."
|
|
134
129
|
}
|
|
135
130
|
},
|
|
136
131
|
"required": [],
|
|
@@ -139,7 +134,7 @@
|
|
|
139
134
|
},
|
|
140
135
|
"examples": {
|
|
141
136
|
"createTrigger": {
|
|
142
|
-
"$code": "async e=>{let t,o;return{get flow(){return t},trigger:()=>async r=>{const
|
|
137
|
+
"$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}}}}"
|
|
143
138
|
},
|
|
144
139
|
"step": {
|
|
145
140
|
"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": "4.2.1-next-
|
|
4
|
+
"version": "4.2.1-next-1781682752679",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"update": "npx npm-check-updates -u && npm update"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@walkeros/collector": "4.2.1-next-
|
|
24
|
-
"@walkeros/core": "4.2.1-next-
|
|
23
|
+
"@walkeros/collector": "4.2.1-next-1781682752679",
|
|
24
|
+
"@walkeros/core": "4.2.1-next-1781682752679",
|
|
25
25
|
"express": "^5.2.1",
|
|
26
26
|
"cors": "^2.8.5"
|
|
27
27
|
},
|