@walkeros/server-source-express 0.5.1-next.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -70
- package/dist/index.d.mts +2 -3
- package/dist/index.d.ts +2 -3
- 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/package.json +2 -2
package/README.md
CHANGED
|
@@ -306,6 +306,64 @@ await startFlow({
|
|
|
306
306
|
// OPTIONS /api/v1/events
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
+
### Ingest Metadata
|
|
310
|
+
|
|
311
|
+
Extract request metadata (IP, headers) and forward it to processors and
|
|
312
|
+
destinations:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
await startFlow({
|
|
316
|
+
sources: {
|
|
317
|
+
express: {
|
|
318
|
+
code: sourceExpress,
|
|
319
|
+
config: {
|
|
320
|
+
settings: { port: 8080 },
|
|
321
|
+
ingest: {
|
|
322
|
+
ip: 'ip',
|
|
323
|
+
ua: 'headers.user-agent',
|
|
324
|
+
origin: 'headers.origin',
|
|
325
|
+
referer: 'headers.referer',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Available ingest paths:**
|
|
334
|
+
|
|
335
|
+
| Path | Description |
|
|
336
|
+
| ----------- | ------------------------------------------------ |
|
|
337
|
+
| `ip` | Client IP address |
|
|
338
|
+
| `headers.*` | HTTP headers (user-agent, origin, referer, etc.) |
|
|
339
|
+
| `protocol` | Request protocol (http/https) |
|
|
340
|
+
| `method` | HTTP method (GET, POST, etc.) |
|
|
341
|
+
| `hostname` | Request hostname |
|
|
342
|
+
| `url` | Full request URL |
|
|
343
|
+
|
|
344
|
+
**Advanced mapping:**
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
ingest: {
|
|
348
|
+
// Custom function for geo lookup
|
|
349
|
+
country: { fn: (req) => geoip.lookup(req.ip)?.country },
|
|
350
|
+
|
|
351
|
+
// Conditional extraction
|
|
352
|
+
devMode: {
|
|
353
|
+
key: 'headers.x-debug',
|
|
354
|
+
condition: (req) => req.hostname === 'localhost',
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
// Nested structure
|
|
358
|
+
request: {
|
|
359
|
+
map: {
|
|
360
|
+
ua: 'headers.user-agent',
|
|
361
|
+
origin: 'headers.origin',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
309
367
|
### Extend Express App
|
|
310
368
|
|
|
311
369
|
```typescript
|
|
@@ -432,80 +490,21 @@ This design enables:
|
|
|
432
490
|
|
|
433
491
|
## Deployment
|
|
434
492
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
```dockerfile
|
|
438
|
-
FROM node:18-alpine
|
|
439
|
-
|
|
440
|
-
WORKDIR /app
|
|
493
|
+
Use the [walkerOS Docker image](https://hub.docker.com/r/walkeros/flow) for
|
|
494
|
+
deployment:
|
|
441
495
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
COPY . .
|
|
446
|
-
|
|
447
|
-
ENV PORT=8080
|
|
448
|
-
EXPOSE 8080
|
|
496
|
+
```bash
|
|
497
|
+
# Bundle your flow with a Dockerfile
|
|
498
|
+
walkeros bundle flow.json --dockerfile
|
|
449
499
|
|
|
450
|
-
|
|
500
|
+
# Build and run
|
|
501
|
+
cd dist
|
|
502
|
+
docker build -t my-flow .
|
|
503
|
+
docker run -p 8080:8080 my-flow
|
|
451
504
|
```
|
|
452
505
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
```javascript
|
|
456
|
-
import { startFlow } from '@walkeros/collector';
|
|
457
|
-
import { sourceExpress } from '@walkeros/server-source-express';
|
|
458
|
-
|
|
459
|
-
await startFlow({
|
|
460
|
-
sources: {
|
|
461
|
-
express: {
|
|
462
|
-
code: sourceExpress,
|
|
463
|
-
config: {
|
|
464
|
-
settings: {
|
|
465
|
-
port: process.env.PORT || 8080,
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
// Your destinations...
|
|
471
|
-
});
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### Kubernetes
|
|
475
|
-
|
|
476
|
-
```yaml
|
|
477
|
-
apiVersion: apps/v1
|
|
478
|
-
kind: Deployment
|
|
479
|
-
metadata:
|
|
480
|
-
name: walkeros-collector
|
|
481
|
-
spec:
|
|
482
|
-
replicas: 3
|
|
483
|
-
selector:
|
|
484
|
-
matchLabels:
|
|
485
|
-
app: walkeros-collector
|
|
486
|
-
template:
|
|
487
|
-
metadata:
|
|
488
|
-
labels:
|
|
489
|
-
app: walkeros-collector
|
|
490
|
-
spec:
|
|
491
|
-
containers:
|
|
492
|
-
- name: collector
|
|
493
|
-
image: your-registry/walkeros-collector:latest
|
|
494
|
-
ports:
|
|
495
|
-
- containerPort: 8080
|
|
496
|
-
livenessProbe:
|
|
497
|
-
httpGet:
|
|
498
|
-
path: /health
|
|
499
|
-
port: 8080
|
|
500
|
-
initialDelaySeconds: 10
|
|
501
|
-
periodSeconds: 5
|
|
502
|
-
readinessProbe:
|
|
503
|
-
httpGet:
|
|
504
|
-
path: /ready
|
|
505
|
-
port: 8080
|
|
506
|
-
initialDelaySeconds: 5
|
|
507
|
-
periodSeconds: 3
|
|
508
|
-
```
|
|
506
|
+
See the [Docker documentation](https://www.walkeros.io/docs/apps/docker) for
|
|
507
|
+
Cloud Run, Kubernetes, and other deployment options.
|
|
509
508
|
|
|
510
509
|
## Testing
|
|
511
510
|
|
package/dist/index.d.mts
CHANGED
|
@@ -104,10 +104,9 @@ declare const TRANSPARENT_GIF: Buffer<ArrayBuffer>;
|
|
|
104
104
|
* - Starts HTTP server (if port configured)
|
|
105
105
|
* - Handles graceful shutdown
|
|
106
106
|
*
|
|
107
|
-
* @param config
|
|
108
|
-
* @param env Source environment with push, command, elb functions
|
|
107
|
+
* @param context Source context with config, env, logger, id
|
|
109
108
|
* @returns Express source instance with app and push handler
|
|
110
109
|
*/
|
|
111
|
-
declare const sourceExpress: (
|
|
110
|
+
declare const sourceExpress: (context: Source.Context<Types>) => Promise<ExpressSource>;
|
|
112
111
|
|
|
113
112
|
export { type Config, type Env, type EventRequest, type EventResponse, type ExpressSource, type InitSettings, type Mapping, type PartialConfig, type Push, type RequestBody, type ResponseBody, type Settings, TRANSPARENT_GIF, type Types, setCorsHeaders, sourceExpress };
|
package/dist/index.d.ts
CHANGED
|
@@ -104,10 +104,9 @@ declare const TRANSPARENT_GIF: Buffer<ArrayBuffer>;
|
|
|
104
104
|
* - Starts HTTP server (if port configured)
|
|
105
105
|
* - Handles graceful shutdown
|
|
106
106
|
*
|
|
107
|
-
* @param config
|
|
108
|
-
* @param env Source environment with push, command, elb functions
|
|
107
|
+
* @param context Source context with config, env, logger, id
|
|
109
108
|
* @returns Express source instance with app and push handler
|
|
110
109
|
*/
|
|
111
|
-
declare const sourceExpress: (
|
|
110
|
+
declare const sourceExpress: (context: Source.Context<Types>) => Promise<ExpressSource>;
|
|
112
111
|
|
|
113
112
|
export { type Config, type Env, type EventRequest, type EventResponse, type ExpressSource, type InitSettings, type Mapping, type PartialConfig, type Push, type RequestBody, type ResponseBody, type Settings, TRANSPARENT_GIF, type Types, setCorsHeaders, sourceExpress };
|
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,setCorsHeaders:()=>setCorsHeaders,sourceExpress:()=>sourceExpress}),module.exports=(mod=index_exports,__copyProps(__defProp({},"__esModule",{value:!0}),mod));var import_express=__toESM(require("express")),import_cors=__toESM(require("cors")),import_core=require("@walkeros/core"),import_dev=require("@walkeros/core/dev"),HttpMethod=import_dev.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),CorsOrigin=import_dev.z.union([import_dev.z.string(),import_dev.z.array(import_dev.z.string()),import_dev.z.literal("*")]),CorsOptionsSchema=import_dev.z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:import_dev.z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:import_dev.z.array(import_dev.z.string()).describe("Allowed request headers").optional(),credentials:import_dev.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:import_dev.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),import_dev2=require("@walkeros/core/dev"),SettingsSchema=import_dev2.z.object({port:import_dev2.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:import_dev2.z.string().describe("Event collection endpoint path").default("/collect"),cors:import_dev2.z.union([import_dev2.z.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0),status:import_dev2.z.boolean().describe("Enable health check endpoints (/health, /ready)").default(!0)});function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async
|
|
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,setCorsHeaders:()=>setCorsHeaders,sourceExpress:()=>sourceExpress}),module.exports=(mod=index_exports,__copyProps(__defProp({},"__esModule",{value:!0}),mod));var import_express=__toESM(require("express")),import_cors=__toESM(require("cors")),import_core=require("@walkeros/core"),import_dev=require("@walkeros/core/dev"),HttpMethod=import_dev.z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),CorsOrigin=import_dev.z.union([import_dev.z.string(),import_dev.z.array(import_dev.z.string()),import_dev.z.literal("*")]),CorsOptionsSchema=import_dev.z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:import_dev.z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:import_dev.z.array(import_dev.z.string()).describe("Allowed request headers").optional(),credentials:import_dev.z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:import_dev.z.number().int().positive().describe("Preflight cache duration in seconds").optional()}),import_dev2=require("@walkeros/core/dev"),SettingsSchema=import_dev2.z.object({port:import_dev2.z.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:import_dev2.z.string().describe("Event collection endpoint path").default("/collect"),cors:import_dev2.z.union([import_dev2.z.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0),status:import_dev2.z.boolean().describe("Enable health check endpoints (/health, /ready)").default(!0)});function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{const{config:config={},env:env}=context,settings=SettingsSchema.parse(config.settings||{}),app=(0,import_express.default)();if(app.use(import_express.default.json({limit:"1mb"})),!1!==settings.cors){const corsOptions=!0===settings.cors?{}:settings.cors;app.use((0,import_cors.default)(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings.cors),void res.status(204).send();if(await context.setIngest(req),"GET"===req.method){const parsedData=(0,import_core.requestToData)(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),res.set("Content-Type","image/gif"),res.set("Cache-Control","no-cache, no-store, must-revalidate"),void res.send(TRANSPARENT_GIF)}if("POST"===req.method){const eventData=req.body;return eventData&&"object"==typeof eventData?(await env.push(eventData),void res.json({success:!0,timestamp:Date.now()})):void res.status(400).json({success:!1,error:"Invalid event: body must be an object"})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}};let server;if(app.post(settings.path,push),app.get(settings.path,push),app.options(settings.path,push),settings.status&&(app.get("/health",(req,res)=>{res.json({status:"ok",timestamp:Date.now(),source:"express"})}),app.get("/ready",(req,res)=>{res.json({status:"ready",timestamp:Date.now(),source:"express"})})),void 0!==settings.port){server=app.listen(settings.port,()=>{const statusRoutes=settings.status?"\n GET /health - Health check\n GET /ready - Readiness check":"";env.logger.info(`Express source listening on port ${settings.port}\n POST ${settings.path} - Event collection (JSON body)\n GET ${settings.path} - Pixel tracking (query params)\n OPTIONS ${settings.path} - CORS preflight`+statusRoutes)});const shutdownHandler=()=>{server&&server.close()};process.on("SIGTERM",shutdownHandler),process.on("SIGINT",shutdownHandler)}return{type:"express",config:{...config,settings:settings},push:push,app:app,server:server}};//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData } from '@walkeros/core';\nimport type {\n ExpressSource,\n PartialConfig,\n Types,\n EventRequest,\n} from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Handles graceful shutdown\n *\n * @param config Partial source configuration\n * @param env Source environment with push, command, elb functions\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n config: PartialConfig,\n env: Types['env'],\n): Promise<ExpressSource> => {\n // Validate and apply default settings\n const settings = SettingsSchema.parse(config.settings || {});\n\n const app = express();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(express.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(cors(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Return 1x1 transparent GIF for pixel tracking\n res.set('Content-Type', 'image/gif');\n res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n res.send(TRANSPARENT_GIF);\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n res.json({\n success: true,\n 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 } 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 event collection endpoint (handles POST, GET, OPTIONS)\n app.post(settings.path, push);\n app.get(settings.path, push);\n app.options(settings.path, push);\n\n // Health check endpoints (if enabled)\n if (settings.status) {\n app.get('/health', (req, res) => {\n res.json({\n status: 'ok',\n timestamp: Date.now(),\n source: 'express',\n });\n });\n\n app.get('/ready', (req, res) => {\n res.json({\n status: 'ready',\n timestamp: Date.now(),\n source: 'express',\n });\n });\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 statusRoutes = settings.status\n ? `\\n GET /health - Health check\\n GET /ready - Readiness check`\n : '';\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` +\n ` POST ${settings.path} - Event collection (JSON body)\\n` +\n ` GET ${settings.path} - Pixel tracking (query params)\\n` +\n ` OPTIONS ${settings.path} - CORS preflight` +\n statusRoutes,\n );\n });\n\n // Graceful shutdown handlers\n const shutdownHandler = () => {\n if (server) {\n server.close();\n }\n };\n\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n app, // Expose app for advanced usage\n server, // Expose server (if started)\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} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\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 * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema } 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) // 0 = random available port\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 path: z\n .string()\n .describe('Event collection endpoint path')\n .default('/collect'),\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 status: z\n .boolean()\n .describe('Enable health check endpoints (/health, /ready)')\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["import_dev","express","cors"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Handles graceful shutdown\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\n // Validate and apply default settings\n const settings = SettingsSchema.parse(config.settings || {});\n\n const app = express();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(express.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(cors(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Return 1x1 transparent GIF for pixel tracking\n res.set('Content-Type', 'image/gif');\n res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n res.send(TRANSPARENT_GIF);\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n res.json({\n success: true,\n 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 } 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 event collection endpoint (handles POST, GET, OPTIONS)\n app.post(settings.path, push);\n app.get(settings.path, push);\n app.options(settings.path, push);\n\n // Health check endpoints (if enabled)\n if (settings.status) {\n app.get('/health', (req, res) => {\n res.json({\n status: 'ok',\n timestamp: Date.now(),\n source: 'express',\n });\n });\n\n app.get('/ready', (req, res) => {\n res.json({\n status: 'ready',\n timestamp: Date.now(),\n source: 'express',\n });\n });\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 statusRoutes = settings.status\n ? `\\n GET /health - Health check\\n GET /ready - Readiness check`\n : '';\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` +\n ` POST ${settings.path} - Event collection (JSON body)\\n` +\n ` GET ${settings.path} - Pixel tracking (query params)\\n` +\n ` OPTIONS ${settings.path} - CORS preflight` +\n statusRoutes,\n );\n });\n\n // Graceful shutdown handlers\n const shutdownHandler = () => {\n if (server) {\n server.close();\n }\n };\n\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n app, // Expose app for advanced usage\n server, // Expose server (if started)\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} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\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 * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema } 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) // 0 = random available port\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 path: z\n .string()\n .describe('Event collection endpoint path')\n .default('/collect'),\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 status: z\n .boolean()\n .describe('Enable health check endpoints (/health, /ready)')\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["import_dev","express","cors"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import express from"express";import cors from"cors";import{requestToData}from"@walkeros/core";import{z}from"@walkeros/core/dev";var HttpMethod=z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),CorsOrigin=z.union([z.string(),z.array(z.string()),z.literal("*")]),CorsOptionsSchema=z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:z.array(z.string()).describe("Allowed request headers").optional(),credentials:z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:z.number().int().positive().describe("Preflight cache duration in seconds").optional()});import{z as z2}from"@walkeros/core/dev";var SettingsSchema=z2.object({port:z2.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:z2.string().describe("Event collection endpoint path").default("/collect"),cors:z2.union([z2.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0),status:z2.boolean().describe("Enable health check endpoints (/health, /ready)").default(!0)});function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async
|
|
1
|
+
import express from"express";import cors from"cors";import{requestToData}from"@walkeros/core";import{z}from"@walkeros/core/dev";var HttpMethod=z.enum(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]),CorsOrigin=z.union([z.string(),z.array(z.string()),z.literal("*")]),CorsOptionsSchema=z.object({origin:CorsOrigin.describe("Allowed origins (* for all, URL string, or array of URLs)").optional(),methods:z.array(HttpMethod).describe("Allowed HTTP methods").optional(),headers:z.array(z.string()).describe("Allowed request headers").optional(),credentials:z.boolean().describe("Allow credentials (cookies, authorization headers)").optional(),maxAge:z.number().int().positive().describe("Preflight cache duration in seconds").optional()});import{z as z2}from"@walkeros/core/dev";var SettingsSchema=z2.object({port:z2.number().int().min(0).max(65535).describe("HTTP server port to listen on. Use 0 for random available port. If not provided, server will not start (app only mode)").optional(),path:z2.string().describe("Event collection endpoint path").default("/collect"),cors:z2.union([z2.boolean(),CorsOptionsSchema]).describe("CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration").default(!0),status:z2.boolean().describe("Enable health check endpoints (/health, /ready)").default(!0)});function setCorsHeaders(res,corsConfig=!0){if(!1!==corsConfig)if(!0===corsConfig)res.set("Access-Control-Allow-Origin","*"),res.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),res.set("Access-Control-Allow-Headers","Content-Type");else{if(corsConfig.origin){const origin=Array.isArray(corsConfig.origin)?corsConfig.origin.join(", "):corsConfig.origin;res.set("Access-Control-Allow-Origin",origin)}corsConfig.methods&&res.set("Access-Control-Allow-Methods",corsConfig.methods.join(", ")),corsConfig.headers&&res.set("Access-Control-Allow-Headers",corsConfig.headers.join(", ")),corsConfig.credentials&&res.set("Access-Control-Allow-Credentials","true"),corsConfig.maxAge&&res.set("Access-Control-Max-Age",String(corsConfig.maxAge))}}var TRANSPARENT_GIF=Buffer.from("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","base64"),sourceExpress=async context=>{const{config:config={},env:env}=context,settings=SettingsSchema.parse(config.settings||{}),app=express();if(app.use(express.json({limit:"1mb"})),!1!==settings.cors){const corsOptions=!0===settings.cors?{}:settings.cors;app.use(cors(corsOptions))}const push=async(req,res)=>{try{if("OPTIONS"===req.method)return setCorsHeaders(res,settings.cors),void res.status(204).send();if(await context.setIngest(req),"GET"===req.method){const parsedData=requestToData(req.url);return parsedData&&"object"==typeof parsedData&&await env.push(parsedData),res.set("Content-Type","image/gif"),res.set("Cache-Control","no-cache, no-store, must-revalidate"),void res.send(TRANSPARENT_GIF)}if("POST"===req.method){const eventData=req.body;return eventData&&"object"==typeof eventData?(await env.push(eventData),void res.json({success:!0,timestamp:Date.now()})):void res.status(400).json({success:!1,error:"Invalid event: body must be an object"})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}};let server;if(app.post(settings.path,push),app.get(settings.path,push),app.options(settings.path,push),settings.status&&(app.get("/health",(req,res)=>{res.json({status:"ok",timestamp:Date.now(),source:"express"})}),app.get("/ready",(req,res)=>{res.json({status:"ready",timestamp:Date.now(),source:"express"})})),void 0!==settings.port){server=app.listen(settings.port,()=>{const statusRoutes=settings.status?"\n GET /health - Health check\n GET /ready - Readiness check":"";env.logger.info(`Express source listening on port ${settings.port}\n POST ${settings.path} - Event collection (JSON body)\n GET ${settings.path} - Pixel tracking (query params)\n OPTIONS ${settings.path} - CORS preflight`+statusRoutes)});const shutdownHandler=()=>{server&&server.close()};process.on("SIGTERM",shutdownHandler),process.on("SIGINT",shutdownHandler)}return{type:"express",config:{...config,settings:settings},push:push,app:app,server:server}};export{TRANSPARENT_GIF,setCorsHeaders,sourceExpress};//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData } from '@walkeros/core';\nimport type {\n ExpressSource,\n PartialConfig,\n Types,\n EventRequest,\n} from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Handles graceful shutdown\n *\n * @param config Partial source configuration\n * @param env Source environment with push, command, elb functions\n * @returns Express source instance with app and push handler\n */\nexport const sourceExpress = async (\n config: PartialConfig,\n env: Types['env'],\n): Promise<ExpressSource> => {\n // Validate and apply default settings\n const settings = SettingsSchema.parse(config.settings || {});\n\n const app = express();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(express.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(cors(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Return 1x1 transparent GIF for pixel tracking\n res.set('Content-Type', 'image/gif');\n res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n res.send(TRANSPARENT_GIF);\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n res.json({\n success: true,\n 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 } 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 event collection endpoint (handles POST, GET, OPTIONS)\n app.post(settings.path, push);\n app.get(settings.path, push);\n app.options(settings.path, push);\n\n // Health check endpoints (if enabled)\n if (settings.status) {\n app.get('/health', (req, res) => {\n res.json({\n status: 'ok',\n timestamp: Date.now(),\n source: 'express',\n });\n });\n\n app.get('/ready', (req, res) => {\n res.json({\n status: 'ready',\n timestamp: Date.now(),\n source: 'express',\n });\n });\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 statusRoutes = settings.status\n ? `\\n GET /health - Health check\\n GET /ready - Readiness check`\n : '';\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` +\n ` POST ${settings.path} - Event collection (JSON body)\\n` +\n ` GET ${settings.path} - Pixel tracking (query params)\\n` +\n ` OPTIONS ${settings.path} - CORS preflight` +\n statusRoutes,\n );\n });\n\n // Graceful shutdown handlers\n const shutdownHandler = () => {\n if (server) {\n server.close();\n }\n };\n\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n app, // Expose app for advanced usage\n server, // Expose server (if started)\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} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\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 * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema } 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) // 0 = random available port\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 path: z\n .string()\n .describe('Event collection endpoint path')\n .default('/collect'),\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 status: z\n .boolean()\n .describe('Enable health check endpoints (/health, /ready)')\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["z","z"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/utils.ts"],"sourcesContent":["import express, { type Request, type Response } from 'express';\nimport cors from 'cors';\nimport { requestToData } from '@walkeros/core';\nimport type { Source } from '@walkeros/core';\nimport type { ExpressSource, Types, EventRequest } from './types';\nimport { SettingsSchema } from './schemas';\nimport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\n/**\n * Express source initialization\n *\n * This source OWNS its HTTP server infrastructure:\n * - Creates Express application\n * - Sets up middleware (JSON parsing, CORS)\n * - Registers event collection endpoints (POST, GET, OPTIONS)\n * - Starts HTTP server (if port configured)\n * - Handles graceful shutdown\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\n // Validate and apply default settings\n const settings = SettingsSchema.parse(config.settings || {});\n\n const app = express();\n\n // Middleware setup - JSON body parsing with 10mb default limit\n app.use(express.json({ limit: '1mb' }));\n\n // CORS middleware (enabled by default)\n if (settings.cors !== false) {\n const corsOptions = settings.cors === true ? {} : settings.cors;\n app.use(cors(corsOptions));\n }\n\n /**\n * Request handler - transforms HTTP requests into walker events\n * Supports POST (JSON body), GET (query params), and OPTIONS (CORS preflight)\n */\n const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Return 1x1 transparent GIF for pixel tracking\n res.set('Content-Type', 'image/gif');\n res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n res.send(TRANSPARENT_GIF);\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData = req.body;\n\n if (!eventData || typeof eventData !== 'object') {\n res.status(400).json({\n success: false,\n error: 'Invalid event: body must be an object',\n });\n return;\n }\n\n // Send event to collector\n await env.push(eventData);\n\n res.json({\n success: true,\n 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 } 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 event collection endpoint (handles POST, GET, OPTIONS)\n app.post(settings.path, push);\n app.get(settings.path, push);\n app.options(settings.path, push);\n\n // Health check endpoints (if enabled)\n if (settings.status) {\n app.get('/health', (req, res) => {\n res.json({\n status: 'ok',\n timestamp: Date.now(),\n source: 'express',\n });\n });\n\n app.get('/ready', (req, res) => {\n res.json({\n status: 'ready',\n timestamp: Date.now(),\n source: 'express',\n });\n });\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 statusRoutes = settings.status\n ? `\\n GET /health - Health check\\n GET /ready - Readiness check`\n : '';\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` +\n ` POST ${settings.path} - Event collection (JSON body)\\n` +\n ` GET ${settings.path} - Pixel tracking (query params)\\n` +\n ` OPTIONS ${settings.path} - CORS preflight` +\n statusRoutes,\n );\n });\n\n // Graceful shutdown handlers\n const shutdownHandler = () => {\n if (server) {\n server.close();\n }\n };\n\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n app, // Expose app for advanced usage\n server, // Expose server (if started)\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} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\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 * CORS origin configuration\n * Accepts:\n * - '*' for all origins\n * - Single URL string\n * - Array of URL strings\n */\nexport const CorsOrigin = z.union([\n z.string(),\n z.array(z.string()),\n z.literal('*'),\n]);\n\n/**\n * CORS options schema\n * Configuration for Cross-Origin Resource Sharing\n */\nexport const CorsOptionsSchema = z.object({\n origin: CorsOrigin.describe(\n 'Allowed origins (* for all, URL string, or array of URLs)',\n ).optional(),\n\n methods: z.array(HttpMethod).describe('Allowed HTTP methods').optional(),\n\n headers: z.array(z.string()).describe('Allowed request headers').optional(),\n\n credentials: z\n .boolean()\n .describe('Allow credentials (cookies, authorization headers)')\n .optional(),\n\n maxAge: z\n .number()\n .int()\n .positive()\n .describe('Preflight cache duration in seconds')\n .optional(),\n});\n\nexport type CorsOptions = z.infer<typeof CorsOptionsSchema>;\n","import { z } from '@walkeros/core/dev';\nimport { CorsOptionsSchema } 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) // 0 = random available port\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 path: z\n .string()\n .describe('Event collection endpoint path')\n .default('/collect'),\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 status: z\n .boolean()\n .describe('Enable health check endpoints (/health, /ready)')\n .default(true),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Response } from 'express';\nimport type { CorsOptions } from './schemas';\n\n/**\n * Set CORS headers on response\n *\n * @param res Express response object\n * @param corsConfig CORS configuration (false = disabled, true = allow all, object = custom)\n */\nexport function setCorsHeaders(\n res: Response,\n corsConfig: boolean | CorsOptions = true,\n): void {\n if (corsConfig === false) return;\n\n if (corsConfig === true) {\n // Simple CORS - allow all\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type');\n } else {\n // Custom CORS configuration\n if (corsConfig.origin) {\n const origin = Array.isArray(corsConfig.origin)\n ? corsConfig.origin.join(', ')\n : corsConfig.origin;\n res.set('Access-Control-Allow-Origin', origin);\n }\n\n if (corsConfig.methods) {\n res.set('Access-Control-Allow-Methods', corsConfig.methods.join(', '));\n }\n\n if (corsConfig.headers) {\n res.set('Access-Control-Allow-Headers', corsConfig.headers.join(', '));\n }\n\n if (corsConfig.credentials) {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (corsConfig.maxAge) {\n res.set('Access-Control-Max-Age', String(corsConfig.maxAge));\n }\n }\n}\n\n/**\n * 1x1 transparent GIF for pixel tracking\n * Base64-encoded GIF (43 bytes)\n */\nexport const TRANSPARENT_GIF = Buffer.from(\n 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n 'base64',\n);\n"],"mappings":"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","names":["z","z"]}
|
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": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"update": "npx npm-check-updates -u && npm update"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@walkeros/core": "0.
|
|
21
|
+
"@walkeros/core": "0.7.0",
|
|
22
22
|
"express": "^5.2.1",
|
|
23
23
|
"cors": "^2.8.5"
|
|
24
24
|
},
|