@walkeros/server-source-express 4.1.0-next-1778668930820 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +352 -0
- package/README.md +31 -599
- 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 -4
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# @walkeros/server-source-express
|
|
2
|
+
|
|
3
|
+
## 4.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 13aaeaa: `Source.Context` no longer exposes `setIngest` or `setRespond`.
|
|
8
|
+
Server sources handling concurrent inbound requests must call
|
|
9
|
+
`context.withScope(rawScope, respond, body)` to bind per-request ingest and
|
|
10
|
+
respond. Browser and other single-scope sources keep working without changes.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [e155ff8]
|
|
15
|
+
- Updated dependencies [e800974]
|
|
16
|
+
- Updated dependencies [e155ff8]
|
|
17
|
+
- Updated dependencies [1a8f2d7]
|
|
18
|
+
- Updated dependencies [1a8f2d7]
|
|
19
|
+
- Updated dependencies [b276173]
|
|
20
|
+
- Updated dependencies [dd9f5ad]
|
|
21
|
+
- Updated dependencies [c60ef35]
|
|
22
|
+
- Updated dependencies [adeebea]
|
|
23
|
+
- Updated dependencies [13aaeaa]
|
|
24
|
+
- Updated dependencies [e800974]
|
|
25
|
+
- Updated dependencies [adeebea]
|
|
26
|
+
- Updated dependencies [6cdc362]
|
|
27
|
+
- Updated dependencies [e800974]
|
|
28
|
+
- Updated dependencies [e800974]
|
|
29
|
+
- Updated dependencies [058f7ed]
|
|
30
|
+
- Updated dependencies [28a8ac2]
|
|
31
|
+
- Updated dependencies [fd6076e]
|
|
32
|
+
- @walkeros/core@4.1.0
|
|
33
|
+
- @walkeros/collector@4.1.0
|
|
34
|
+
|
|
35
|
+
## 4.0.2
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies [a6a0ea7]
|
|
40
|
+
- @walkeros/core@4.0.2
|
|
41
|
+
- @walkeros/collector@4.0.2
|
|
42
|
+
|
|
43
|
+
## 4.0.1
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- 70e4d89: Fix events being silently dropped when posted via
|
|
48
|
+
`navigator.sendBeacon`. The browser forces
|
|
49
|
+
`Content-Type: text/plain;charset=UTF-8` for beacon requests even when the
|
|
50
|
+
payload is JSON, which previously caused the express middleware to skip body
|
|
51
|
+
parsing and the GCP Cloud Functions handler to treat the body as an opaque
|
|
52
|
+
string, both falling through to an empty-event push. Express now accepts
|
|
53
|
+
`text/plain` bodies through `express.json()`, and the Cloud Functions handler
|
|
54
|
+
attempts `JSON.parse` on string bodies before classifying the request.
|
|
55
|
+
- Updated dependencies [cb265eb]
|
|
56
|
+
- Updated dependencies [381dfe7]
|
|
57
|
+
- Updated dependencies [1524275]
|
|
58
|
+
- Updated dependencies [03d7055]
|
|
59
|
+
- @walkeros/collector@4.0.1
|
|
60
|
+
- @walkeros/core@4.0.1
|
|
61
|
+
|
|
62
|
+
## 4.0.0
|
|
63
|
+
|
|
64
|
+
### Major Changes
|
|
65
|
+
|
|
66
|
+
- 93ea9c4: Event model v4: breaking changes to the `Event`, `Source`, and
|
|
67
|
+
`Entity` shapes.
|
|
68
|
+
- `event.id` is now a W3C span_id (16 lowercase hex chars), generated by the
|
|
69
|
+
collector. Reference: W3C Trace Context (W3C Recommendation, January 2020).
|
|
70
|
+
- `event.version`, `event.group`, `event.count` are removed.
|
|
71
|
+
- `source.type` is now the source kind (e.g. `browser`, `gtag`, `mcp`, `cli`).
|
|
72
|
+
New `source.platform` holds the runtime (`web` | `server` | `app` | ...).
|
|
73
|
+
- `source.id` and `source.previous_id` are removed.
|
|
74
|
+
- Browser source now sets `source.url` and `source.referrer`.
|
|
75
|
+
- MCP source sets `source.tool` per emission. CLI source sets
|
|
76
|
+
`source.command`.
|
|
77
|
+
- `Entity.nested` and `Entity.context` are now optional. Root `event.nested`
|
|
78
|
+
and `event.context` remain required.
|
|
79
|
+
- Each source self-registers via TypeScript module augmentation of `SourceMap`
|
|
80
|
+
in `@walkeros/core`.
|
|
81
|
+
- App-side coordination (`/workspaces/developer/app`) is a follow-up plan, not
|
|
82
|
+
part of this release. Telemetry from v4 CLI/MCP will not validate against
|
|
83
|
+
the existing app schema until that follow-up ships.
|
|
84
|
+
- `Mapping.Rule.skip` is renamed to `Mapping.Rule.silent`. Customer flow.json
|
|
85
|
+
configs using `skip: true` in mapping rules must rename to `silent: true`.
|
|
86
|
+
Hard cut: no legacy alias, the field is gone.
|
|
87
|
+
|
|
88
|
+
### Patch Changes
|
|
89
|
+
|
|
90
|
+
- Updated dependencies [93ea9c4]
|
|
91
|
+
- Updated dependencies [465775c]
|
|
92
|
+
- Updated dependencies [942a7fe]
|
|
93
|
+
- Updated dependencies [cfc7469]
|
|
94
|
+
- Updated dependencies [8e06b1f]
|
|
95
|
+
- Updated dependencies [3d50dd6]
|
|
96
|
+
- Updated dependencies [1ef33d9]
|
|
97
|
+
- @walkeros/core@4.0.0
|
|
98
|
+
- @walkeros/collector@4.0.0
|
|
99
|
+
|
|
100
|
+
## 3.4.2
|
|
101
|
+
|
|
102
|
+
### Patch Changes
|
|
103
|
+
|
|
104
|
+
- @walkeros/collector@3.4.2
|
|
105
|
+
- @walkeros/core@3.4.2
|
|
106
|
+
|
|
107
|
+
## 3.4.1
|
|
108
|
+
|
|
109
|
+
### Patch Changes
|
|
110
|
+
|
|
111
|
+
- Updated dependencies [12adf24]
|
|
112
|
+
- Updated dependencies [75aa26b]
|
|
113
|
+
- @walkeros/core@3.4.1
|
|
114
|
+
- @walkeros/collector@3.4.1
|
|
115
|
+
|
|
116
|
+
## 3.4.0
|
|
117
|
+
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- 724f97e: Migrate every step example in every walkerOS package to the
|
|
121
|
+
standardized `[callable, ...args][]` shape introduced in `@walkeros/core`.
|
|
122
|
+
Every step example's `out` is now an array of effect tuples whose first
|
|
123
|
+
element is the callable's public SDK name (`'gtag'`, `'analytics.track'`,
|
|
124
|
+
`'fbq'`, `'dataLayer.push'`, `'sendServer'`, `'fetch'`, `'trackClient.track'`,
|
|
125
|
+
`'amplitude.track'`, `'fs.writeFile'`, `'producer.send'`, `'client.xadd'`,
|
|
126
|
+
`'client.send'`, `'dataset.table.insert'`, etc.). Source examples use `'elb'`
|
|
127
|
+
as the callable; transformer examples use the reserved `'return'` keyword;
|
|
128
|
+
store examples use store-operation callables (`'get'`, `'set'`). Tests capture
|
|
129
|
+
real calls on each component's spy and assert against `example.out` directly —
|
|
130
|
+
the hardcoded `PACKAGE_CALLS` registry in the app is no longer consulted
|
|
131
|
+
(emptied; plan #3 removes it structurally).
|
|
132
|
+
|
|
133
|
+
### Patch Changes
|
|
134
|
+
|
|
135
|
+
- Updated dependencies [74940cc]
|
|
136
|
+
- Updated dependencies [525f5d9]
|
|
137
|
+
- @walkeros/core@3.4.0
|
|
138
|
+
- @walkeros/collector@3.4.0
|
|
139
|
+
|
|
140
|
+
## 3.3.1
|
|
141
|
+
|
|
142
|
+
### Patch Changes
|
|
143
|
+
|
|
144
|
+
- Updated dependencies [b10144a]
|
|
145
|
+
- Updated dependencies [206185a]
|
|
146
|
+
- Updated dependencies [50e5d09]
|
|
147
|
+
- Updated dependencies [32ff626]
|
|
148
|
+
- @walkeros/collector@3.3.1
|
|
149
|
+
- @walkeros/core@3.3.1
|
|
150
|
+
|
|
151
|
+
## 3.3.0
|
|
152
|
+
|
|
153
|
+
### Patch Changes
|
|
154
|
+
|
|
155
|
+
- Updated dependencies [2849acb]
|
|
156
|
+
- Updated dependencies [08c365a]
|
|
157
|
+
- Updated dependencies [08c365a]
|
|
158
|
+
- Updated dependencies [08c365a]
|
|
159
|
+
- Updated dependencies [08c365a]
|
|
160
|
+
- @walkeros/core@3.3.0
|
|
161
|
+
- @walkeros/collector@3.3.0
|
|
162
|
+
|
|
163
|
+
## 3.2.0
|
|
164
|
+
|
|
165
|
+
### Minor Changes
|
|
166
|
+
|
|
167
|
+
- f47d251: Accept non-JSON POST bodies in all server sources
|
|
168
|
+
|
|
169
|
+
Server sources no longer reject non-JSON bodies with HTTP 400. Instead, they
|
|
170
|
+
push an empty event `{}` to the collector, enabling `source.before`
|
|
171
|
+
transformers to process raw input via ingest. Raw body is available through
|
|
172
|
+
ingest mapping (e.g., `"rawBody": "body"`).
|
|
173
|
+
|
|
174
|
+
### Patch Changes
|
|
175
|
+
|
|
176
|
+
- Updated dependencies [eb865e1]
|
|
177
|
+
- Updated dependencies [c0a53f9]
|
|
178
|
+
- Updated dependencies [8cdc0bb]
|
|
179
|
+
- Updated dependencies [f007c9f]
|
|
180
|
+
- Updated dependencies [bf2dc5b]
|
|
181
|
+
- Updated dependencies [da0b640]
|
|
182
|
+
- Updated dependencies [a5d25bc]
|
|
183
|
+
- Updated dependencies [9a99298]
|
|
184
|
+
- Updated dependencies [884527d]
|
|
185
|
+
- @walkeros/core@3.2.0
|
|
186
|
+
- @walkeros/collector@3.2.0
|
|
187
|
+
|
|
188
|
+
## 3.1.1
|
|
189
|
+
|
|
190
|
+
### Patch Changes
|
|
191
|
+
|
|
192
|
+
- @walkeros/core@3.1.1
|
|
193
|
+
- @walkeros/collector@3.1.1
|
|
194
|
+
|
|
195
|
+
## 3.1.0
|
|
196
|
+
|
|
197
|
+
### Minor Changes
|
|
198
|
+
|
|
199
|
+
- ff58828: Add env.express and env.cors to Env interface for dependency
|
|
200
|
+
injection. Tests and simulations can now replace the HTTP layer without
|
|
201
|
+
touching module imports.
|
|
202
|
+
- a9149e4: Rewrite createTrigger to use real HTTP requests via fetch() instead
|
|
203
|
+
of mocked req/res. Follows unified Trigger.CreateFn interface. Step examples
|
|
204
|
+
updated with trigger metadata field.
|
|
205
|
+
|
|
206
|
+
### Patch Changes
|
|
207
|
+
|
|
208
|
+
- Updated dependencies [a9149e4]
|
|
209
|
+
- Updated dependencies [dfc6738]
|
|
210
|
+
- Updated dependencies [966342b]
|
|
211
|
+
- Updated dependencies [bee8ba7]
|
|
212
|
+
- Updated dependencies [966342b]
|
|
213
|
+
- Updated dependencies [df990d4]
|
|
214
|
+
- @walkeros/collector@3.1.0
|
|
215
|
+
- @walkeros/core@3.1.0
|
|
216
|
+
|
|
217
|
+
## 3.0.2
|
|
218
|
+
|
|
219
|
+
### Patch Changes
|
|
220
|
+
|
|
221
|
+
- @walkeros/core@3.0.2
|
|
222
|
+
|
|
223
|
+
## 3.0.1
|
|
224
|
+
|
|
225
|
+
### Patch Changes
|
|
226
|
+
|
|
227
|
+
- @walkeros/core@3.0.1
|
|
228
|
+
|
|
229
|
+
## 3.0.0
|
|
230
|
+
|
|
231
|
+
### Patch Changes
|
|
232
|
+
|
|
233
|
+
- 499e27a: Add sideEffects declarations to all packages for bundler tree-shaking
|
|
234
|
+
support.
|
|
235
|
+
- Updated dependencies [2b259b6]
|
|
236
|
+
- Updated dependencies [2614014]
|
|
237
|
+
- Updated dependencies [6ae0ee3]
|
|
238
|
+
- Updated dependencies [37299a9]
|
|
239
|
+
- Updated dependencies [499e27a]
|
|
240
|
+
- Updated dependencies [0e5eede]
|
|
241
|
+
- Updated dependencies [d11f574]
|
|
242
|
+
- Updated dependencies [d11f574]
|
|
243
|
+
- Updated dependencies [1fe337a]
|
|
244
|
+
- Updated dependencies [5cb84c1]
|
|
245
|
+
- Updated dependencies [23f218a]
|
|
246
|
+
- Updated dependencies [499e27a]
|
|
247
|
+
- Updated dependencies [c83d909]
|
|
248
|
+
- Updated dependencies [b6c8fa8]
|
|
249
|
+
- @walkeros/core@3.0.0
|
|
250
|
+
|
|
251
|
+
## 2.1.1
|
|
252
|
+
|
|
253
|
+
### Patch Changes
|
|
254
|
+
|
|
255
|
+
- Updated dependencies [fab477d]
|
|
256
|
+
- @walkeros/core@2.1.1
|
|
257
|
+
|
|
258
|
+
## 2.1.0
|
|
259
|
+
|
|
260
|
+
### Minor Changes
|
|
261
|
+
|
|
262
|
+
- 3eb6416: Add unified `env.respond` capability. Any step (transformer,
|
|
263
|
+
destination) can now customize HTTP responses via
|
|
264
|
+
`env.respond({ body, status?, headers? })`. Sources configure the response
|
|
265
|
+
handler — Express source uses createRespond for idempotent first-call-wins
|
|
266
|
+
semantics. CLI serve mode removed (superseded by response-capable flows).
|
|
267
|
+
- 66aaf2d: Runner-owned health server: The runner now provides /health and
|
|
268
|
+
/ready endpoints independently of flow sources. Express source's `status`
|
|
269
|
+
setting and fetch source's `healthPath` setting have been removed — health
|
|
270
|
+
endpoints are no longer source responsibilities.
|
|
271
|
+
- 97df0b2: Step examples: upgrade all packages to blueprint pattern with inline
|
|
272
|
+
mapping, no intermediate variables, no `all` export
|
|
273
|
+
|
|
274
|
+
### Patch Changes
|
|
275
|
+
|
|
276
|
+
- Updated dependencies [7fc4cee]
|
|
277
|
+
- Updated dependencies [7fc4cee]
|
|
278
|
+
- Updated dependencies [cb2da05]
|
|
279
|
+
- Updated dependencies [2bbe8c8]
|
|
280
|
+
- Updated dependencies [3eb6416]
|
|
281
|
+
- Updated dependencies [02a7958]
|
|
282
|
+
- Updated dependencies [97df0b2]
|
|
283
|
+
- Updated dependencies [97df0b2]
|
|
284
|
+
- Updated dependencies [026c412]
|
|
285
|
+
- Updated dependencies [7d38d9d]
|
|
286
|
+
- @walkeros/core@2.1.0
|
|
287
|
+
|
|
288
|
+
## 2.0.1
|
|
289
|
+
|
|
290
|
+
## 1.1.0
|
|
291
|
+
|
|
292
|
+
### Minor Changes
|
|
293
|
+
|
|
294
|
+
- bb0ab04: Add multi-path support with per-route method control. The `path`
|
|
295
|
+
setting is deprecated in favor of `paths` array.
|
|
296
|
+
|
|
297
|
+
### Patch Changes
|
|
298
|
+
|
|
299
|
+
- Updated dependencies [7b2d750]
|
|
300
|
+
- @walkeros/core@1.4.0
|
|
301
|
+
|
|
302
|
+
## 1.0.5
|
|
303
|
+
|
|
304
|
+
### Patch Changes
|
|
305
|
+
|
|
306
|
+
- Updated dependencies [a4cc1ea]
|
|
307
|
+
- @walkeros/core@1.3.0
|
|
308
|
+
|
|
309
|
+
## 1.0.4
|
|
310
|
+
|
|
311
|
+
### Patch Changes
|
|
312
|
+
|
|
313
|
+
- Updated dependencies [7ad6cfb]
|
|
314
|
+
- @walkeros/core@1.2.2
|
|
315
|
+
|
|
316
|
+
## 1.0.3
|
|
317
|
+
|
|
318
|
+
### Patch Changes
|
|
319
|
+
|
|
320
|
+
- Updated dependencies [6256c12]
|
|
321
|
+
- @walkeros/core@1.2.1
|
|
322
|
+
|
|
323
|
+
## 1.0.2
|
|
324
|
+
|
|
325
|
+
### Patch Changes
|
|
326
|
+
|
|
327
|
+
- 6778ab2: Add default exports for simpler CLI flow.json configuration
|
|
328
|
+
- Updated dependencies [f39d9fb]
|
|
329
|
+
- Updated dependencies [888bbdf]
|
|
330
|
+
- @walkeros/core@1.2.0
|
|
331
|
+
|
|
332
|
+
## 1.0.1
|
|
333
|
+
|
|
334
|
+
### Patch Changes
|
|
335
|
+
|
|
336
|
+
- Updated dependencies [b65b773]
|
|
337
|
+
- Updated dependencies [20eca6e]
|
|
338
|
+
- @walkeros/core@1.1.0
|
|
339
|
+
|
|
340
|
+
## 1.0.0
|
|
341
|
+
|
|
342
|
+
### Major Changes
|
|
343
|
+
|
|
344
|
+
- 67c9e1d: Hello World! walkerOS v1.0.0
|
|
345
|
+
|
|
346
|
+
Open-source event data collection. Collect event data for digital analytics in
|
|
347
|
+
a unified and privacy-centric way.
|
|
348
|
+
|
|
349
|
+
### Patch Changes
|
|
350
|
+
|
|
351
|
+
- Updated dependencies [67c9e1d]
|
|
352
|
+
- @walkeros/core@1.0.0
|
package/README.md
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
<p align="left">
|
|
2
|
+
<a href="https://www.walkeros.io">
|
|
3
|
+
<img alt="walkerOS" title="walkerOS" src="https://www.walkeros.io/img/walkerOS_logo.svg" width="256px"/>
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
1
7
|
# @walkeros/server-source-express
|
|
2
8
|
|
|
3
|
-
|
|
4
|
-
Express
|
|
9
|
+
Turn-key HTTP event collection server with Express.js. Runs standalone or embeds
|
|
10
|
+
inside an existing Express app, handles JSON POST events, pixel tracking via
|
|
11
|
+
GET, and configurable CORS.
|
|
12
|
+
|
|
13
|
+
[Documentation](https://www.walkeros.io/docs/sources/server/express) •
|
|
14
|
+
[NPM Package](https://www.npmjs.com/package/@walkeros/server-source-express)
|
|
15
|
+
•
|
|
16
|
+
[Source Code](https://github.com/elbwalker/walkerOS/tree/main/packages/server/sources/express)
|
|
5
17
|
|
|
6
18
|
## Installation
|
|
7
19
|
|
|
@@ -9,191 +21,18 @@ Express.js.
|
|
|
9
21
|
npm install @walkeros/server-source-express
|
|
10
22
|
```
|
|
11
23
|
|
|
12
|
-
## Quick
|
|
13
|
-
|
|
14
|
-
### Standalone Server (Docker-style)
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
import { startFlow } from '@walkeros/collector';
|
|
18
|
-
import { sourceExpress } from '@walkeros/server-source-express';
|
|
19
|
-
|
|
20
|
-
const { collector } = await startFlow({
|
|
21
|
-
sources: {
|
|
22
|
-
express: {
|
|
23
|
-
code: sourceExpress,
|
|
24
|
-
config: {
|
|
25
|
-
settings: {
|
|
26
|
-
port: 8080, // Start server on port 8080
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
destinations: {
|
|
32
|
-
// Your destinations here
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Server is now running!
|
|
37
|
-
// POST http://localhost:8080/collect - JSON event ingestion
|
|
38
|
-
// GET http://localhost:8080/collect - Pixel tracking
|
|
39
|
-
// GET http://localhost:8080/health - Health check
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### App-Only Mode (Custom Integration)
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
const { collector } = await startFlow({
|
|
46
|
-
sources: {
|
|
47
|
-
express: {
|
|
48
|
-
code: sourceExpress,
|
|
49
|
-
config: {
|
|
50
|
-
settings: {
|
|
51
|
-
// No port = app only, no server started
|
|
52
|
-
paths: ['/events'],
|
|
53
|
-
cors: false, // Handle CORS with your own middleware
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Access the Express app
|
|
61
|
-
const expressSource = collector.sources.express;
|
|
62
|
-
const app = expressSource.app;
|
|
63
|
-
|
|
64
|
-
// Add custom middleware
|
|
65
|
-
app.use(yourAuthMiddleware);
|
|
66
|
-
|
|
67
|
-
// Add custom routes
|
|
68
|
-
app.get('/custom', customHandler);
|
|
69
|
-
|
|
70
|
-
// Start server manually
|
|
71
|
-
app.listen(3000);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Configuration
|
|
75
|
-
|
|
76
|
-
### Settings
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
interface Settings {
|
|
80
|
-
/**
|
|
81
|
-
* HTTP server port to listen on
|
|
82
|
-
* If not provided, server will not start (app-only mode)
|
|
83
|
-
* @optional
|
|
84
|
-
*/
|
|
85
|
-
port?: number;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Route paths to register
|
|
89
|
-
* String shorthand registers GET+POST. RouteConfig allows per-route method control.
|
|
90
|
-
* @default ['/collect']
|
|
91
|
-
*/
|
|
92
|
-
paths?: Array<string | RouteConfig>;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* @deprecated Use `paths` instead. Will be removed in next major.
|
|
96
|
-
* Converted to `paths: [path]` internally.
|
|
97
|
-
*/
|
|
98
|
-
path?: string;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* CORS configuration
|
|
102
|
-
* - false: Disabled
|
|
103
|
-
* - true: Allow all origins (default)
|
|
104
|
-
* - object: Custom CORS options
|
|
105
|
-
* @default true
|
|
106
|
-
*/
|
|
107
|
-
cors?: boolean | CorsOptions;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Enable health check endpoints
|
|
111
|
-
* - GET /health (liveness check)
|
|
112
|
-
* - GET /ready (readiness check)
|
|
113
|
-
* @default true
|
|
114
|
-
*/
|
|
115
|
-
status?: boolean;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface RouteConfig {
|
|
119
|
-
/** Express route path (supports wildcards like /api/*) */
|
|
120
|
-
path: string;
|
|
121
|
-
/** HTTP methods to register. OPTIONS always included for CORS. */
|
|
122
|
-
methods?: ('GET' | 'POST')[];
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### CORS Options
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
interface CorsOptions {
|
|
130
|
-
/** Allowed origins (string, array, or '*') */
|
|
131
|
-
origin?: string | string[] | '*';
|
|
132
|
-
|
|
133
|
-
/** Allowed HTTP methods */
|
|
134
|
-
methods?: string[];
|
|
135
|
-
|
|
136
|
-
/** Allowed request headers */
|
|
137
|
-
headers?: string[];
|
|
138
|
-
|
|
139
|
-
/** Allow credentials (cookies, authorization) */
|
|
140
|
-
credentials?: boolean;
|
|
141
|
-
|
|
142
|
-
/** Preflight cache duration in seconds */
|
|
143
|
-
maxAge?: number;
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## HTTP Methods
|
|
148
|
-
|
|
149
|
-
### POST - Standard Event Ingestion
|
|
150
|
-
|
|
151
|
-
Send events as JSON in the request body.
|
|
152
|
-
|
|
153
|
-
**Request:**
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
curl -X POST http://localhost:8080/collect \
|
|
157
|
-
-H "Content-Type: application/json" \
|
|
158
|
-
-d '{
|
|
159
|
-
"event": "page view",
|
|
160
|
-
"data": {
|
|
161
|
-
"title": "Home Page",
|
|
162
|
-
"path": "/"
|
|
163
|
-
},
|
|
164
|
-
"user": {
|
|
165
|
-
"id": "user123"
|
|
166
|
-
}
|
|
167
|
-
}'
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**Response:**
|
|
24
|
+
## Quick start
|
|
171
25
|
|
|
172
26
|
```json
|
|
173
27
|
{
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
`{}` to the collector. Use `source.before` transformers to decode raw input
|
|
183
|
-
available via ingest:
|
|
184
|
-
|
|
185
|
-
```json
|
|
186
|
-
{
|
|
187
|
-
"sources": {
|
|
188
|
-
"http": {
|
|
189
|
-
"package": "@walkeros/server-source-express",
|
|
190
|
-
"before": "base64Decoder",
|
|
191
|
-
"config": {
|
|
192
|
-
"ingest": {
|
|
193
|
-
"map": {
|
|
194
|
-
"rawBody": "body",
|
|
195
|
-
"contentType": "headers.content-type"
|
|
196
|
-
}
|
|
28
|
+
"version": 4,
|
|
29
|
+
"flows": {
|
|
30
|
+
"default": {
|
|
31
|
+
"config": { "platform": "server" },
|
|
32
|
+
"sources": {
|
|
33
|
+
"express": {
|
|
34
|
+
"package": "@walkeros/server-source-express",
|
|
35
|
+
"config": {}
|
|
197
36
|
}
|
|
198
37
|
}
|
|
199
38
|
}
|
|
@@ -201,425 +40,18 @@ available via ingest:
|
|
|
201
40
|
}
|
|
202
41
|
```
|
|
203
42
|
|
|
204
|
-
|
|
205
|
-
decoded walkerOS event.
|
|
206
|
-
|
|
207
|
-
### GET - Pixel Tracking
|
|
43
|
+
## Documentation
|
|
208
44
|
|
|
209
|
-
|
|
45
|
+
Full configuration, mapping, and examples live in the docs:
|
|
46
|
+
**https://www.walkeros.io/docs/sources/server/express**
|
|
210
47
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
```html
|
|
214
|
-
<!-- In your HTML -->
|
|
215
|
-
<img
|
|
216
|
-
src="http://localhost:8080/collect?event=page%20view&data[title]=Home&user[id]=user123"
|
|
217
|
-
width="1"
|
|
218
|
-
height="1"
|
|
219
|
-
alt=""
|
|
220
|
-
/>
|
|
221
|
-
```
|
|
48
|
+
## Contribute
|
|
222
49
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Cache-Control: no-cache, no-store, must-revalidate
|
|
228
|
-
|
|
229
|
-
[1x1 transparent GIF binary]
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### OPTIONS - CORS Preflight
|
|
233
|
-
|
|
234
|
-
Automatically handled for cross-origin requests.
|
|
235
|
-
|
|
236
|
-
**Request:**
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
curl -X OPTIONS http://localhost:8080/collect \
|
|
240
|
-
-H "Origin: https://example.com"
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
**Response:**
|
|
244
|
-
|
|
245
|
-
```
|
|
246
|
-
Access-Control-Allow-Origin: *
|
|
247
|
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
|
248
|
-
Access-Control-Allow-Headers: Content-Type
|
|
249
|
-
|
|
250
|
-
204 No Content
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Health Checks
|
|
254
|
-
|
|
255
|
-
### GET /health - Liveness Check
|
|
256
|
-
|
|
257
|
-
Returns server status (always returns 200 if server is running).
|
|
258
|
-
|
|
259
|
-
**Response:**
|
|
260
|
-
|
|
261
|
-
```json
|
|
262
|
-
{
|
|
263
|
-
"status": "ok",
|
|
264
|
-
"timestamp": 1647261462000,
|
|
265
|
-
"source": "express"
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### GET /ready - Readiness Check
|
|
270
|
-
|
|
271
|
-
Returns readiness status (same as health for Express source).
|
|
272
|
-
|
|
273
|
-
**Response:**
|
|
274
|
-
|
|
275
|
-
```json
|
|
276
|
-
{
|
|
277
|
-
"status": "ready",
|
|
278
|
-
"timestamp": 1647261462000,
|
|
279
|
-
"source": "express"
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Advanced Examples
|
|
284
|
-
|
|
285
|
-
### Custom CORS Configuration
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
await startFlow({
|
|
289
|
-
sources: {
|
|
290
|
-
express: {
|
|
291
|
-
code: sourceExpress,
|
|
292
|
-
config: {
|
|
293
|
-
settings: {
|
|
294
|
-
port: 8080,
|
|
295
|
-
cors: {
|
|
296
|
-
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
297
|
-
credentials: true,
|
|
298
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
299
|
-
headers: ['Content-Type', 'Authorization'],
|
|
300
|
-
maxAge: 86400, // 24 hours
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Disable Health Checks
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
await startFlow({
|
|
313
|
-
sources: {
|
|
314
|
-
express: {
|
|
315
|
-
code: sourceExpress,
|
|
316
|
-
config: {
|
|
317
|
-
settings: {
|
|
318
|
-
port: 8080,
|
|
319
|
-
status: false, // Disable /health and /ready endpoints
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### Custom Endpoint Paths
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
await startFlow({
|
|
331
|
-
sources: {
|
|
332
|
-
express: {
|
|
333
|
-
code: sourceExpress,
|
|
334
|
-
config: {
|
|
335
|
-
settings: {
|
|
336
|
-
port: 8080,
|
|
337
|
-
paths: ['/api/v1/events'], // Custom path (GET + POST)
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Multi-Path with Method Control
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
await startFlow({
|
|
349
|
-
sources: {
|
|
350
|
-
express: {
|
|
351
|
-
code: sourceExpress,
|
|
352
|
-
config: {
|
|
353
|
-
settings: {
|
|
354
|
-
port: 8080,
|
|
355
|
-
paths: [
|
|
356
|
-
'/collect', // GET + POST (default)
|
|
357
|
-
{ path: '/pixel', methods: ['GET'] }, // GET only (pixel tracking)
|
|
358
|
-
{ path: '/ingest', methods: ['POST'] }, // POST only (JSON ingestion)
|
|
359
|
-
{ path: '/webhooks/*', methods: ['POST'] }, // POST wildcard
|
|
360
|
-
],
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### Ingest Metadata
|
|
369
|
-
|
|
370
|
-
Extract request metadata (IP, headers) and forward it to processors and
|
|
371
|
-
destinations:
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
await startFlow({
|
|
375
|
-
sources: {
|
|
376
|
-
express: {
|
|
377
|
-
code: sourceExpress,
|
|
378
|
-
config: {
|
|
379
|
-
settings: { port: 8080 },
|
|
380
|
-
ingest: {
|
|
381
|
-
ip: 'ip',
|
|
382
|
-
ua: 'headers.user-agent',
|
|
383
|
-
origin: 'headers.origin',
|
|
384
|
-
referer: 'headers.referer',
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Available ingest paths:**
|
|
393
|
-
|
|
394
|
-
| Path | Description |
|
|
395
|
-
| ----------- | ------------------------------------------------ |
|
|
396
|
-
| `ip` | Client IP address |
|
|
397
|
-
| `headers.*` | HTTP headers (user-agent, origin, referer, etc.) |
|
|
398
|
-
| `protocol` | Request protocol (http/https) |
|
|
399
|
-
| `method` | HTTP method (GET, POST, etc.) |
|
|
400
|
-
| `hostname` | Request hostname |
|
|
401
|
-
| `url` | Full request URL |
|
|
402
|
-
|
|
403
|
-
**Advanced mapping:**
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
ingest: {
|
|
407
|
-
// Custom function for geo lookup
|
|
408
|
-
country: { fn: (req) => geoip.lookup(req.ip)?.country },
|
|
409
|
-
|
|
410
|
-
// Conditional extraction
|
|
411
|
-
devMode: {
|
|
412
|
-
key: 'headers.x-debug',
|
|
413
|
-
condition: (req) => req.hostname === 'localhost',
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
// Nested structure
|
|
417
|
-
request: {
|
|
418
|
-
map: {
|
|
419
|
-
ua: 'headers.user-agent',
|
|
420
|
-
origin: 'headers.origin',
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Extend Express App
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
const { collector } = await startFlow({
|
|
430
|
-
sources: {
|
|
431
|
-
express: {
|
|
432
|
-
code: sourceExpress,
|
|
433
|
-
config: {
|
|
434
|
-
settings: { port: 8080 },
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
// Access Express app for advanced customization
|
|
441
|
-
const expressSource = collector.sources.express;
|
|
442
|
-
const app = expressSource.app;
|
|
443
|
-
|
|
444
|
-
// Add authentication middleware
|
|
445
|
-
app.use('/collect', authMiddleware);
|
|
446
|
-
|
|
447
|
-
// Add rate limiting
|
|
448
|
-
import rateLimit from 'express-rate-limit';
|
|
449
|
-
app.use(
|
|
450
|
-
'/collect',
|
|
451
|
-
rateLimit({
|
|
452
|
-
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
453
|
-
max: 100, // Limit each IP to 100 requests per windowMs
|
|
454
|
-
}),
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
// Add custom logging
|
|
458
|
-
app.use((req, res, next) => {
|
|
459
|
-
console.log(`${req.method} ${req.path}`);
|
|
460
|
-
next();
|
|
461
|
-
});
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
## Event Format
|
|
465
|
-
|
|
466
|
-
### Single Event
|
|
467
|
-
|
|
468
|
-
```json
|
|
469
|
-
{
|
|
470
|
-
"event": "page view",
|
|
471
|
-
"data": {
|
|
472
|
-
"title": "Home Page",
|
|
473
|
-
"path": "/"
|
|
474
|
-
},
|
|
475
|
-
"context": {
|
|
476
|
-
"language": ["en", 0],
|
|
477
|
-
"currency": ["USD", 0]
|
|
478
|
-
},
|
|
479
|
-
"user": {
|
|
480
|
-
"id": "user123",
|
|
481
|
-
"device": "device456"
|
|
482
|
-
},
|
|
483
|
-
"globals": {
|
|
484
|
-
"appVersion": "1.0.0"
|
|
485
|
-
},
|
|
486
|
-
"consent": {
|
|
487
|
-
"functional": true,
|
|
488
|
-
"marketing": true
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
### Query Parameters (GET)
|
|
494
|
-
|
|
495
|
-
For pixel tracking, use nested bracket notation:
|
|
496
|
-
|
|
497
|
-
```
|
|
498
|
-
?event=page%20view
|
|
499
|
-
&data[title]=Home%20Page
|
|
500
|
-
&data[path]=/
|
|
501
|
-
&user[id]=user123
|
|
502
|
-
&consent[marketing]=true
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
This is automatically parsed by `requestToData` from `@walkeros/core`.
|
|
506
|
-
|
|
507
|
-
## Architecture
|
|
508
|
-
|
|
509
|
-
### Infrastructure Ownership
|
|
510
|
-
|
|
511
|
-
The Express source **owns its HTTP infrastructure**:
|
|
512
|
-
|
|
513
|
-
- ✅ Creates Express application
|
|
514
|
-
- ✅ Configures middleware (JSON parsing, CORS)
|
|
515
|
-
- ✅ Registers routes (POST, GET, OPTIONS)
|
|
516
|
-
- ✅ Starts HTTP server (if port configured)
|
|
517
|
-
- ✅ Handles graceful shutdown (SIGTERM, SIGINT)
|
|
518
|
-
|
|
519
|
-
This design enables:
|
|
520
|
-
|
|
521
|
-
1. **Turn-key deployment** - Just specify a port and deploy
|
|
522
|
-
2. **Docker-friendly** - Perfect for containerized environments
|
|
523
|
-
3. **Flexibility** - App-only mode for custom integrations
|
|
524
|
-
|
|
525
|
-
### Request Flow
|
|
526
|
-
|
|
527
|
-
```
|
|
528
|
-
┌─────────────────────────────────────────┐
|
|
529
|
-
│ HTTP Client (Browser, Server, etc.) │
|
|
530
|
-
└─────────────────────────────────────────┘
|
|
531
|
-
↓
|
|
532
|
-
┌─────────────────────────────────────────┐
|
|
533
|
-
│ EXPRESS SOURCE │
|
|
534
|
-
│ - Receives HTTP request │
|
|
535
|
-
│ - Parses body/query params │
|
|
536
|
-
│ - Validates request structure │
|
|
537
|
-
│ - Calls env.push() → Collector │
|
|
538
|
-
│ - Returns HTTP response │
|
|
539
|
-
└─────────────────────────────────────────┘
|
|
540
|
-
↓
|
|
541
|
-
┌─────────────────────────────────────────┐
|
|
542
|
-
│ COLLECTOR │
|
|
543
|
-
│ - Event validation & processing │
|
|
544
|
-
│ - Consent management │
|
|
545
|
-
│ - Mapping rules │
|
|
546
|
-
│ - Routes to destinations │
|
|
547
|
-
└─────────────────────────────────────────┘
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
## Deployment
|
|
551
|
-
|
|
552
|
-
Use the [walkerOS Docker image](https://hub.docker.com/r/walkeros/flow) for
|
|
553
|
-
deployment:
|
|
554
|
-
|
|
555
|
-
```bash
|
|
556
|
-
# Bundle your flow with a Dockerfile
|
|
557
|
-
walkeros bundle flow.json --dockerfile
|
|
558
|
-
|
|
559
|
-
# Build and run
|
|
560
|
-
cd dist
|
|
561
|
-
docker build -t my-flow .
|
|
562
|
-
docker run -p 8080:8080 my-flow
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
See the [Docker documentation](https://www.walkeros.io/docs/apps/docker) for
|
|
566
|
-
Cloud Run, Kubernetes, and other deployment options.
|
|
567
|
-
|
|
568
|
-
## Testing
|
|
569
|
-
|
|
570
|
-
The package includes comprehensive tests using mocked Express Request/Response
|
|
571
|
-
objects.
|
|
572
|
-
|
|
573
|
-
**Run tests:**
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
npm test
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
**Example test:**
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
import { sourceExpress } from '@walkeros/server-source-express';
|
|
583
|
-
|
|
584
|
-
test('should process POST event', async () => {
|
|
585
|
-
const mockPush = jest.fn().mockResolvedValue({ event: { id: 'test' } });
|
|
586
|
-
|
|
587
|
-
const source = await sourceExpress(
|
|
588
|
-
{},
|
|
589
|
-
{
|
|
590
|
-
push: mockPush,
|
|
591
|
-
command: jest.fn(),
|
|
592
|
-
elb: jest.fn(),
|
|
593
|
-
},
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
const req = {
|
|
597
|
-
method: 'POST',
|
|
598
|
-
body: { event: 'page view', data: { title: 'Home' } },
|
|
599
|
-
headers: {},
|
|
600
|
-
get: () => undefined,
|
|
601
|
-
};
|
|
602
|
-
const res = {
|
|
603
|
-
status: jest.fn().returnThis(),
|
|
604
|
-
json: jest.fn(),
|
|
605
|
-
send: jest.fn(),
|
|
606
|
-
set: jest.fn(),
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
await source.push(req, res);
|
|
610
|
-
|
|
611
|
-
expect(res.status).toHaveBeenCalledWith(200);
|
|
612
|
-
expect(mockPush).toHaveBeenCalled();
|
|
613
|
-
});
|
|
614
|
-
```
|
|
50
|
+
Feel free to contribute by submitting an
|
|
51
|
+
[issue](https://github.com/elbwalker/walkerOS/issues), starting a
|
|
52
|
+
[discussion](https://github.com/elbwalker/walkerOS/discussions), or getting in
|
|
53
|
+
[contact](https://calendly.com/elb-alexander/30min).
|
|
615
54
|
|
|
616
55
|
## License
|
|
617
56
|
|
|
618
57
|
MIT
|
|
619
|
-
|
|
620
|
-
## Links
|
|
621
|
-
|
|
622
|
-
- [walkerOS Documentation](https://github.com/elbwalker/walkerOS)
|
|
623
|
-
- [Express.js](https://expressjs.com/)
|
|
624
|
-
- [GitHub Repository](https://github.com/elbwalker/walkerOS)
|
|
625
|
-
- [Report Issues](https://github.com/elbwalker/walkerOS/issues)
|
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"),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"])},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();
|
|
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"),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"])},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),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});await context.withScope(req,respond,async env2=>{if("GET"===req.method){const parsedData=(0,import_core.requestToData)(req.url);return parsedData&&"object"==typeof parsedData&&await env2.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return await env2.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=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 * 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 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 const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import 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"]}
|
|
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 * 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 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 if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.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 // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n });\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"),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"])},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();
|
|
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"),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"])},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),"string"==typeof options.body||Buffer.isBuffer(options.body)?res.send(options.body):res.json(options.body)});await context.withScope(req,respond,async env2=>{if("GET"===req.method){const parsedData=requestToData(req.url);return parsedData&&"object"==typeof parsedData&&await env2.push(parsedData),void respond({body:TRANSPARENT_GIF,headers:{"Content-Type":"image/gif","Cache-Control":"no-cache, no-store, must-revalidate"}})}if("POST"===req.method){const eventData=req.body&&"object"==typeof req.body?req.body:{};return await env2.push(eventData),void respond({body:{success:!0,timestamp:Date.now()}})}res.status(405).json({success:!1,error:"Method not allowed. Use POST, GET, or OPTIONS."})})}catch(error){res.status(500).json({success:!1,error:error instanceof Error?error.message:"Internal server error"})}},resolvedPaths=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 * 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 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 const push = async (req: Request, res: Response): Promise<void> => {\n try {\n // Handle OPTIONS for CORS preflight\n if (req.method === 'OPTIONS') {\n setCorsHeaders(res, settings.cors);\n res.status(204).send();\n return;\n }\n\n // Extract ingest metadata from request (if config.ingest is defined)\n await context.setIngest(req);\n\n // Create per-request respond — first call wins (idempotent)\n const respond = createRespond((options) => {\n const status = options.status ?? 200;\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n res.set(key, value);\n }\n }\n res.status(status);\n if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.body);\n }\n });\n context.setRespond(respond);\n\n // Handle GET requests (pixel tracking)\n if (req.method === 'GET') {\n // Parse query parameters to event data using requestToData\n const parsedData = requestToData(req.url);\n\n // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Internal server error',\n });\n }\n };\n\n // Register handlers per route config\n const resolvedPaths = settings.paths.map((entry) =>\n typeof entry === 'string'\n ? { path: entry, methods: ['GET', 'POST'] as const }\n : {\n path: entry.path,\n methods: entry.methods || (['GET', 'POST'] as const),\n },\n );\n\n for (const route of resolvedPaths) {\n if (route.methods.includes('POST')) app.post(route.path, push);\n if (route.methods.includes('GET')) app.get(route.path, push);\n app.options(route.path, push); // Always register OPTIONS for CORS\n }\n\n // Source owns the HTTP server (if port configured)\n let server: ReturnType<typeof app.listen> | undefined;\n\n if (settings.port !== undefined) {\n server = app.listen(settings.port, () => {\n const routeLines = resolvedPaths\n .map((r) => {\n const methods = [...r.methods, 'OPTIONS'].join(', ');\n return ` ${methods} ${r.path}`;\n })\n .join('\\n');\n env.logger.info(\n `Express source listening on port ${settings.port}\\n` + routeLines,\n );\n });\n }\n\n const instance: ExpressSource = {\n type: 'express',\n config: {\n ...config,\n settings,\n },\n push,\n httpHandler: app,\n app,\n server,\n destroy: (_context) =>\n new Promise<void>((resolve, reject) => {\n if (!server) return resolve();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n\n return instance;\n};\n\n// Export types (avoid re-exporting duplicates from schemas)\nexport type {\n ExpressSource,\n Config,\n PartialConfig,\n Types,\n EventRequest,\n EventResponse,\n RequestBody,\n ResponseBody,\n Push,\n Env,\n Mapping,\n InitSettings,\n Settings,\n RouteConfig,\n RouteMethod,\n} from './types';\n\n// Export utils\nexport { setCorsHeaders, TRANSPARENT_GIF } from './utils';\n\nexport default sourceExpress;\n","import 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":[]}
|
|
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 * 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 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 if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {\n res.send(options.body);\n } else {\n res.json(options.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 // Send to collector\n if (parsedData && typeof parsedData === 'object') {\n await env.push(parsedData);\n }\n\n // Default: 1x1 GIF (skipped if a step already called respond)\n respond({\n body: TRANSPARENT_GIF,\n headers: {\n 'Content-Type': 'image/gif',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n });\n return;\n }\n\n // Handle POST requests (standard event ingestion)\n if (req.method === 'POST') {\n const eventData =\n req.body && typeof req.body === 'object' ? req.body : {};\n\n await env.push(eventData);\n\n respond({ body: { success: true, timestamp: Date.now() } });\n return;\n }\n\n // Unsupported method\n res.status(405).json({\n success: false,\n error: 'Method not allowed. Use POST, GET, or OPTIONS.',\n });\n });\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.1.0
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"type": "source",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server"
|
|
@@ -128,9 +128,7 @@
|
|
|
128
128
|
"description": "CORS configuration: false = disabled, true = allow all origins (default), object = custom configuration"
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
|
-
"required": [
|
|
132
|
-
"cors"
|
|
133
|
-
],
|
|
131
|
+
"required": [],
|
|
134
132
|
"additionalProperties": false
|
|
135
133
|
}
|
|
136
134
|
},
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/server-source-express",
|
|
3
3
|
"description": "Express server source for walkerOS",
|
|
4
|
-
"version": "4.1.0
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
|
-
"dist/**"
|
|
10
|
+
"dist/**",
|
|
11
|
+
"CHANGELOG.md"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsup --silent",
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
"update": "npx npm-check-updates -u && npm update"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@walkeros/collector": "4.1.0
|
|
23
|
-
"@walkeros/core": "4.1.0
|
|
23
|
+
"@walkeros/collector": "4.1.0",
|
|
24
|
+
"@walkeros/core": "4.1.0",
|
|
24
25
|
"express": "^5.2.1",
|
|
25
26
|
"cors": "^2.8.5"
|
|
26
27
|
},
|