@flowcore/pathways 0.11.0 → 0.13.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 +102 -58
- package/README.md +133 -126
- package/esm/common/index.d.ts.map +1 -1
- package/esm/contracts/index.d.ts.map +1 -1
- package/esm/mod.d.ts.map +1 -1
- package/esm/pathways/builder.d.ts +66 -5
- package/esm/pathways/builder.d.ts.map +1 -1
- package/esm/pathways/builder.js +67 -8
- package/esm/pathways/index.d.ts.map +1 -1
- package/esm/pathways/internal-pathway.state.d.ts.map +1 -1
- package/esm/pathways/kv/bun-kv-adapter.d.ts.map +1 -1
- package/esm/pathways/kv/kv-adapter.d.ts +2 -2
- package/esm/pathways/kv/kv-adapter.d.ts.map +1 -1
- package/esm/pathways/kv/node-kv-adapter.d.ts.map +1 -1
- package/esm/pathways/kv/node-kv-adapter.js +1 -1
- package/esm/pathways/logger.js +7 -7
- package/esm/pathways/postgres/index.d.ts.map +1 -1
- package/esm/pathways/postgres/postgres-adapter.d.ts.map +1 -1
- package/esm/pathways/postgres/postgres-adapter.js +3 -2
- package/esm/pathways/session-pathway.d.ts.map +1 -1
- package/esm/pathways/session-pathway.js +3 -3
- package/esm/router/index.d.ts.map +1 -1
- package/esm/router/index.js +4 -4
- package/package.json +1 -1
- package/script/common/index.d.ts.map +1 -1
- package/script/contracts/index.d.ts.map +1 -1
- package/script/mod.d.ts.map +1 -1
- package/script/pathways/builder.d.ts +66 -5
- package/script/pathways/builder.d.ts.map +1 -1
- package/script/pathways/builder.js +69 -9
- package/script/pathways/index.d.ts.map +1 -1
- package/script/pathways/internal-pathway.state.d.ts.map +1 -1
- package/script/pathways/kv/bun-kv-adapter.d.ts.map +1 -1
- package/script/pathways/kv/kv-adapter.d.ts +2 -2
- package/script/pathways/kv/kv-adapter.d.ts.map +1 -1
- package/script/pathways/kv/node-kv-adapter.d.ts.map +1 -1
- package/script/pathways/kv/node-kv-adapter.js +1 -1
- package/script/pathways/logger.js +7 -7
- package/script/pathways/postgres/index.d.ts.map +1 -1
- package/script/pathways/postgres/postgres-adapter.d.ts.map +1 -1
- package/script/pathways/postgres/postgres-adapter.js +3 -2
- package/script/pathways/session-pathway.d.ts.map +1 -1
- package/script/pathways/session-pathway.js +3 -3
- package/script/router/index.d.ts.map +1 -1
- package/script/router/index.js +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Flowcore Pathways
|
|
2
2
|
|
|
3
|
-
A TypeScript Library for creating Flowcore Pathways, simplifying the integration with the Flowcore platform. Flowcore
|
|
3
|
+
A TypeScript Library for creating Flowcore Pathways, simplifying the integration with the Flowcore platform. Flowcore
|
|
4
|
+
Pathways helps you build event-driven applications with type-safe pathways for processing and producing events.
|
|
4
5
|
|
|
5
6
|
## Table of Contents
|
|
6
7
|
|
|
@@ -54,44 +55,44 @@ yarn add @flowcore/pathways
|
|
|
54
55
|
Here's a basic example to get you started with Flowcore Pathways:
|
|
55
56
|
|
|
56
57
|
```typescript
|
|
57
|
-
import { Type } from "@sinclair/typebox"
|
|
58
|
-
import { PathwaysBuilder } from "@flowcore/pathways"
|
|
58
|
+
import { Type } from "@sinclair/typebox"
|
|
59
|
+
import { PathwaysBuilder } from "@flowcore/pathways"
|
|
59
60
|
|
|
60
61
|
// Define your event schema
|
|
61
62
|
const userSchema = Type.Object({
|
|
62
63
|
id: Type.String(),
|
|
63
64
|
name: Type.String(),
|
|
64
|
-
email: Type.String()
|
|
65
|
-
})
|
|
65
|
+
email: Type.String(),
|
|
66
|
+
})
|
|
66
67
|
|
|
67
68
|
// Create a pathways builder
|
|
68
69
|
const pathways = new PathwaysBuilder({
|
|
69
70
|
baseUrl: "https://api.flowcore.io",
|
|
70
71
|
tenant: "your-tenant",
|
|
71
72
|
dataCore: "your-data-core",
|
|
72
|
-
apiKey: "your-api-key"
|
|
73
|
-
})
|
|
73
|
+
apiKey: "your-api-key",
|
|
74
|
+
})
|
|
74
75
|
|
|
75
76
|
// Register a pathway
|
|
76
77
|
pathways
|
|
77
78
|
.register({
|
|
78
79
|
flowType: "user",
|
|
79
80
|
eventType: "created",
|
|
80
|
-
schema: userSchema
|
|
81
|
+
schema: userSchema,
|
|
81
82
|
})
|
|
82
83
|
.handle("user/created", async (event) => {
|
|
83
|
-
console.log(`Processing user created event: ${event.eventId}`)
|
|
84
|
-
console.log(`User data:`, event.payload)
|
|
85
|
-
|
|
84
|
+
console.log(`Processing user created event: ${event.eventId}`)
|
|
85
|
+
console.log(`User data:`, event.payload)
|
|
86
|
+
|
|
86
87
|
// Process the event...
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
// You can write to another pathway if needed
|
|
89
90
|
await pathways.write("notifications/sent", {
|
|
90
91
|
userId: event.payload.id,
|
|
91
92
|
message: `Welcome ${event.payload.name}!`,
|
|
92
|
-
channel: "email"
|
|
93
|
-
})
|
|
94
|
-
})
|
|
93
|
+
channel: "email",
|
|
94
|
+
})
|
|
95
|
+
})
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
## Core Concepts
|
|
@@ -112,7 +113,7 @@ Flowcore Pathways is built around these core concepts:
|
|
|
112
113
|
The `PathwaysBuilder` is the main configuration point for your pathways:
|
|
113
114
|
|
|
114
115
|
```typescript
|
|
115
|
-
import { PathwaysBuilder } from "@flowcore/pathways"
|
|
116
|
+
import { PathwaysBuilder } from "@flowcore/pathways"
|
|
116
117
|
|
|
117
118
|
const pathways = new PathwaysBuilder({
|
|
118
119
|
baseUrl: "https://api.flowcore.io",
|
|
@@ -120,8 +121,8 @@ const pathways = new PathwaysBuilder({
|
|
|
120
121
|
dataCore: "your-data-core",
|
|
121
122
|
apiKey: "your-api-key",
|
|
122
123
|
pathwayTimeoutMs: 10000, // Optional, default is 10000 (10s)
|
|
123
|
-
logger: customLogger
|
|
124
|
-
})
|
|
124
|
+
logger: customLogger, // Optional, defaults to NoopLogger
|
|
125
|
+
})
|
|
125
126
|
```
|
|
126
127
|
|
|
127
128
|
### Registering Pathways
|
|
@@ -129,7 +130,7 @@ const pathways = new PathwaysBuilder({
|
|
|
129
130
|
Register pathways with their schemas for type-safe event handling:
|
|
130
131
|
|
|
131
132
|
```typescript
|
|
132
|
-
import { Type } from "@sinclair/typebox"
|
|
133
|
+
import { Type } from "@sinclair/typebox"
|
|
133
134
|
|
|
134
135
|
// Define your event schema
|
|
135
136
|
const orderSchema = Type.Object({
|
|
@@ -139,20 +140,20 @@ const orderSchema = Type.Object({
|
|
|
139
140
|
items: Type.Array(
|
|
140
141
|
Type.Object({
|
|
141
142
|
id: Type.String(),
|
|
142
|
-
quantity: Type.Number()
|
|
143
|
-
})
|
|
144
|
-
)
|
|
145
|
-
})
|
|
143
|
+
quantity: Type.Number(),
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
})
|
|
146
147
|
|
|
147
148
|
// Register pathway
|
|
148
149
|
pathways.register({
|
|
149
150
|
flowType: "order",
|
|
150
151
|
eventType: "placed",
|
|
151
152
|
schema: orderSchema,
|
|
152
|
-
writable: true,
|
|
153
|
-
maxRetries: 3,
|
|
154
|
-
retryDelayMs: 500
|
|
155
|
-
})
|
|
153
|
+
writable: true, // Optional, default is true
|
|
154
|
+
maxRetries: 3, // Optional, default is 3
|
|
155
|
+
retryDelayMs: 500, // Optional, default is 500
|
|
156
|
+
})
|
|
156
157
|
```
|
|
157
158
|
|
|
158
159
|
### Handling Events
|
|
@@ -160,18 +161,18 @@ pathways.register({
|
|
|
160
161
|
Set up handlers to process events for specific pathways:
|
|
161
162
|
|
|
162
163
|
```typescript
|
|
163
|
-
const pathwayKey = "order/placed"
|
|
164
|
+
const pathwayKey = "order/placed"
|
|
164
165
|
|
|
165
166
|
pathways.handle(pathwayKey, async (event) => {
|
|
166
|
-
console.log(`Processing order ${event.payload.orderId}`)
|
|
167
|
-
|
|
167
|
+
console.log(`Processing order ${event.payload.orderId}`)
|
|
168
|
+
|
|
168
169
|
// Access typed payload data
|
|
169
|
-
const { userId, total, items } = event.payload
|
|
170
|
-
|
|
170
|
+
const { userId, total, items } = event.payload
|
|
171
|
+
|
|
171
172
|
// Your business logic here
|
|
172
|
-
await updateInventory(items)
|
|
173
|
-
await notifyUser(userId, total)
|
|
174
|
-
})
|
|
173
|
+
await updateInventory(items)
|
|
174
|
+
await notifyUser(userId, total)
|
|
175
|
+
})
|
|
175
176
|
```
|
|
176
177
|
|
|
177
178
|
### Writing Events
|
|
@@ -185,27 +186,27 @@ const eventId = await pathways.write("order/placed", {
|
|
|
185
186
|
userId: "user-456",
|
|
186
187
|
total: 99.99,
|
|
187
188
|
items: [
|
|
188
|
-
{ id: "item-1", quantity: 2 }
|
|
189
|
-
]
|
|
190
|
-
})
|
|
189
|
+
{ id: "item-1", quantity: 2 },
|
|
190
|
+
],
|
|
191
|
+
})
|
|
191
192
|
|
|
192
193
|
// Write with metadata
|
|
193
194
|
const eventId2 = await pathways.write(
|
|
194
|
-
"order/placed",
|
|
195
|
+
"order/placed",
|
|
195
196
|
orderData,
|
|
196
|
-
{
|
|
197
|
+
{
|
|
197
198
|
correlationId: "corr-789",
|
|
198
|
-
source: "checkout-service"
|
|
199
|
-
}
|
|
200
|
-
)
|
|
199
|
+
source: "checkout-service",
|
|
200
|
+
},
|
|
201
|
+
)
|
|
201
202
|
|
|
202
203
|
// Fire-and-forget mode (doesn't wait for processing)
|
|
203
204
|
const eventId3 = await pathways.write(
|
|
204
|
-
"order/placed",
|
|
205
|
+
"order/placed",
|
|
205
206
|
orderData,
|
|
206
207
|
undefined,
|
|
207
|
-
{ fireAndForget: true }
|
|
208
|
-
)
|
|
208
|
+
{ fireAndForget: true },
|
|
209
|
+
)
|
|
209
210
|
```
|
|
210
211
|
|
|
211
212
|
### Error Handling
|
|
@@ -215,15 +216,15 @@ Handle errors in pathway processing:
|
|
|
215
216
|
```typescript
|
|
216
217
|
// Error handler for a specific pathway
|
|
217
218
|
pathways.onError("order/placed", (error, event) => {
|
|
218
|
-
console.error(`Error processing order ${event.payload.orderId}:`, error)
|
|
219
|
-
reportToMonitoring(error, event)
|
|
220
|
-
})
|
|
219
|
+
console.error(`Error processing order ${event.payload.orderId}:`, error)
|
|
220
|
+
reportToMonitoring(error, event)
|
|
221
|
+
})
|
|
221
222
|
|
|
222
223
|
// Global error handler for all pathways
|
|
223
224
|
pathways.onAnyError((error, event, pathway) => {
|
|
224
|
-
console.error(`Error in pathway ${pathway}:`, error)
|
|
225
|
-
reportToMonitoring(error, event, pathway)
|
|
226
|
-
})
|
|
225
|
+
console.error(`Error in pathway ${pathway}:`, error)
|
|
226
|
+
reportToMonitoring(error, event, pathway)
|
|
227
|
+
})
|
|
227
228
|
```
|
|
228
229
|
|
|
229
230
|
### Event Observability
|
|
@@ -233,18 +234,18 @@ Subscribe to events for observability at different stages:
|
|
|
233
234
|
```typescript
|
|
234
235
|
// Before processing
|
|
235
236
|
pathways.subscribe("order/placed", (event) => {
|
|
236
|
-
console.log(`About to process order ${event.payload.orderId}`)
|
|
237
|
-
}, "before")
|
|
237
|
+
console.log(`About to process order ${event.payload.orderId}`)
|
|
238
|
+
}, "before")
|
|
238
239
|
|
|
239
240
|
// After processing
|
|
240
241
|
pathways.subscribe("order/placed", (event) => {
|
|
241
|
-
console.log(`Finished processing order ${event.payload.orderId}`)
|
|
242
|
-
}, "after")
|
|
242
|
+
console.log(`Finished processing order ${event.payload.orderId}`)
|
|
243
|
+
}, "after")
|
|
243
244
|
|
|
244
245
|
// At both stages
|
|
245
246
|
pathways.subscribe("order/placed", (event) => {
|
|
246
|
-
console.log(`Event ${event.eventId} at ${new Date().toISOString()}`)
|
|
247
|
-
}, "all")
|
|
247
|
+
console.log(`Event ${event.eventId} at ${new Date().toISOString()}`)
|
|
248
|
+
}, "all")
|
|
248
249
|
```
|
|
249
250
|
|
|
250
251
|
### Setting up a Router
|
|
@@ -252,24 +253,24 @@ pathways.subscribe("order/placed", (event) => {
|
|
|
252
253
|
The `PathwayRouter` routes incoming events to the appropriate pathway:
|
|
253
254
|
|
|
254
255
|
```typescript
|
|
255
|
-
import { PathwayRouter } from "@flowcore/pathways"
|
|
256
|
+
import { PathwayRouter } from "@flowcore/pathways"
|
|
256
257
|
|
|
257
258
|
// Create a router with a secret key for validation
|
|
258
|
-
const WEBHOOK_SECRET = "your-webhook-secret"
|
|
259
|
-
const router = new PathwayRouter(pathways, WEBHOOK_SECRET)
|
|
259
|
+
const WEBHOOK_SECRET = "your-webhook-secret"
|
|
260
|
+
const router = new PathwayRouter(pathways, WEBHOOK_SECRET)
|
|
260
261
|
|
|
261
262
|
// Process an incoming event from a webhook
|
|
262
263
|
async function handleWebhook(req: Request) {
|
|
263
|
-
const event = await req.json()
|
|
264
|
-
const secret = req.headers.get("X-Webhook-Secret")
|
|
265
|
-
|
|
264
|
+
const event = await req.json()
|
|
265
|
+
const secret = req.headers.get("X-Webhook-Secret")
|
|
266
|
+
|
|
266
267
|
try {
|
|
267
268
|
// This validates the secret and routes to the right pathway
|
|
268
|
-
await router.processEvent(event, secret)
|
|
269
|
-
return new Response("Event processed", { status: 200 })
|
|
269
|
+
await router.processEvent(event, secret)
|
|
270
|
+
return new Response("Event processed", { status: 200 })
|
|
270
271
|
} catch (error) {
|
|
271
|
-
console.error("Error processing event:", error)
|
|
272
|
-
return new Response("Error processing event", { status: 500 })
|
|
272
|
+
console.error("Error processing event:", error)
|
|
273
|
+
return new Response("Error processing event", { status: 500 })
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
```
|
|
@@ -279,17 +280,17 @@ async function handleWebhook(req: Request) {
|
|
|
279
280
|
Integrate with Deno's HTTP server:
|
|
280
281
|
|
|
281
282
|
```typescript
|
|
282
|
-
import { serve } from "https://deno.land/std/http/server.ts"
|
|
283
|
+
import { serve } from "https://deno.land/std/http/server.ts"
|
|
283
284
|
|
|
284
285
|
serve(async (req: Request) => {
|
|
285
|
-
const url = new URL(req.url)
|
|
286
|
-
|
|
286
|
+
const url = new URL(req.url)
|
|
287
|
+
|
|
287
288
|
if (req.method === "POST" && url.pathname === "/webhook") {
|
|
288
|
-
return handleWebhook(req)
|
|
289
|
+
return handleWebhook(req)
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
-
return new Response("Not found", { status: 404 })
|
|
292
|
-
}, { port: 3000 })
|
|
291
|
+
|
|
292
|
+
return new Response("Not found", { status: 404 })
|
|
293
|
+
}, { port: 3000 })
|
|
293
294
|
```
|
|
294
295
|
|
|
295
296
|
### Persistence Options
|
|
@@ -306,18 +307,19 @@ const pathways = new PathwaysBuilder({
|
|
|
306
307
|
baseUrl: "https://api.flowcore.io",
|
|
307
308
|
tenant: "your-tenant",
|
|
308
309
|
dataCore: "your-data-core",
|
|
309
|
-
apiKey: "your-api-key"
|
|
310
|
-
})
|
|
310
|
+
apiKey: "your-api-key",
|
|
311
|
+
})
|
|
311
312
|
```
|
|
312
313
|
|
|
313
|
-
The internal store uses the appropriate KV adapter for your environment (Bun, Node, or Deno), but note that this state
|
|
314
|
+
The internal store uses the appropriate KV adapter for your environment (Bun, Node, or Deno), but note that this state
|
|
315
|
+
is not persistent across application restarts and should be used primarily for development.
|
|
314
316
|
|
|
315
317
|
#### PostgreSQL Persistence (Production)
|
|
316
318
|
|
|
317
319
|
For production environments, you can use PostgreSQL for reliable and scalable persistence:
|
|
318
320
|
|
|
319
321
|
```typescript
|
|
320
|
-
import {
|
|
322
|
+
import { createPostgresPathwayState, PostgresPathwayState } from "@flowcore/pathways"
|
|
321
323
|
|
|
322
324
|
// Create a PostgreSQL state handler
|
|
323
325
|
const postgresState = createPostgresPathwayState({
|
|
@@ -327,12 +329,12 @@ const postgresState = createPostgresPathwayState({
|
|
|
327
329
|
password: "postgres",
|
|
328
330
|
database: "pathway_db",
|
|
329
331
|
tableName: "pathway_state", // Optional, defaults to "pathway_state"
|
|
330
|
-
ttlMs: 300000,
|
|
331
|
-
ssl: false
|
|
332
|
-
})
|
|
332
|
+
ttlMs: 300000, // Optional, defaults to 5 minutes (300000ms)
|
|
333
|
+
ssl: false, // Optional, defaults to false
|
|
334
|
+
})
|
|
333
335
|
|
|
334
336
|
// Use PostgreSQL for pathway state
|
|
335
|
-
pathways.withPathwayState(postgresState)
|
|
337
|
+
pathways.withPathwayState(postgresState)
|
|
336
338
|
```
|
|
337
339
|
|
|
338
340
|
The PostgreSQL implementation:
|
|
@@ -352,15 +354,15 @@ Enable auditing to track events:
|
|
|
352
354
|
pathways.withAudit(
|
|
353
355
|
// Audit handler
|
|
354
356
|
(path, event) => {
|
|
355
|
-
console.log(`Audit: ${path} event ${event.eventId}`)
|
|
356
|
-
logToAuditSystem(path, event)
|
|
357
|
+
console.log(`Audit: ${path} event ${event.eventId}`)
|
|
358
|
+
logToAuditSystem(path, event)
|
|
357
359
|
},
|
|
358
360
|
// User ID resolver
|
|
359
361
|
async () => {
|
|
360
362
|
// Get the current user ID from context
|
|
361
|
-
return getCurrentUserId()
|
|
362
|
-
}
|
|
363
|
-
)
|
|
363
|
+
return getCurrentUserId()
|
|
364
|
+
},
|
|
365
|
+
)
|
|
364
366
|
```
|
|
365
367
|
|
|
366
368
|
### Custom Loggers
|
|
@@ -368,31 +370,31 @@ pathways.withAudit(
|
|
|
368
370
|
Create a custom logger:
|
|
369
371
|
|
|
370
372
|
```typescript
|
|
371
|
-
import { Logger } from "@flowcore/pathways"
|
|
373
|
+
import { Logger } from "@flowcore/pathways"
|
|
372
374
|
|
|
373
375
|
class MyCustomLogger implements Logger {
|
|
374
376
|
debug(message: string, context?: Record<string, unknown>): void {
|
|
375
|
-
console.debug(`[DEBUG] ${message}`, context)
|
|
377
|
+
console.debug(`[DEBUG] ${message}`, context)
|
|
376
378
|
}
|
|
377
|
-
|
|
379
|
+
|
|
378
380
|
info(message: string, context?: Record<string, unknown>): void {
|
|
379
|
-
console.info(`[INFO] ${message}`, context)
|
|
381
|
+
console.info(`[INFO] ${message}`, context)
|
|
380
382
|
}
|
|
381
|
-
|
|
383
|
+
|
|
382
384
|
warn(message: string, context?: Record<string, unknown>): void {
|
|
383
|
-
console.warn(`[WARN] ${message}`, context)
|
|
385
|
+
console.warn(`[WARN] ${message}`, context)
|
|
384
386
|
}
|
|
385
|
-
|
|
387
|
+
|
|
386
388
|
error(message: string, error?: Error, context?: Record<string, unknown>): void {
|
|
387
|
-
console.error(`[ERROR] ${message}`, error, context)
|
|
389
|
+
console.error(`[ERROR] ${message}`, error, context)
|
|
388
390
|
}
|
|
389
391
|
}
|
|
390
392
|
|
|
391
393
|
// Use custom logger
|
|
392
394
|
const pathways = new PathwaysBuilder({
|
|
393
395
|
// ...other config
|
|
394
|
-
logger: new MyCustomLogger()
|
|
395
|
-
})
|
|
396
|
+
logger: new MyCustomLogger(),
|
|
397
|
+
})
|
|
396
398
|
```
|
|
397
399
|
|
|
398
400
|
### Retry Mechanisms
|
|
@@ -403,32 +405,30 @@ Configure retry behavior for pathways:
|
|
|
403
405
|
// Global timeout for pathway processing
|
|
404
406
|
const pathways = new PathwaysBuilder({
|
|
405
407
|
// ...other config
|
|
406
|
-
pathwayTimeoutMs: 15000 // 15 seconds
|
|
407
|
-
})
|
|
408
|
+
pathwayTimeoutMs: 15000, // 15 seconds
|
|
409
|
+
})
|
|
408
410
|
|
|
409
411
|
// Per-pathway retry configuration
|
|
410
412
|
pathways.register({
|
|
411
413
|
flowType: "payment",
|
|
412
414
|
eventType: "process",
|
|
413
415
|
schema: paymentSchema,
|
|
414
|
-
maxRetries: 5,
|
|
415
|
-
retryDelayMs: 1000
|
|
416
|
-
})
|
|
416
|
+
maxRetries: 5, // Retry up to 5 times
|
|
417
|
+
retryDelayMs: 1000, // 1 second between retries
|
|
418
|
+
})
|
|
417
419
|
```
|
|
418
420
|
|
|
419
421
|
### Session Pathways
|
|
420
422
|
|
|
421
|
-
The `SessionPathwayBuilder` provides a way to associate session IDs with pathway operations, making it easier to track
|
|
423
|
+
The `SessionPathwayBuilder` provides a way to associate session IDs with pathway operations, making it easier to track
|
|
424
|
+
and manage user sessions in your application.
|
|
422
425
|
|
|
423
426
|
#### Setting Up Session Support
|
|
424
427
|
|
|
425
428
|
To use session-specific functionality, first configure your `PathwaysBuilder` with session support:
|
|
426
429
|
|
|
427
430
|
```typescript
|
|
428
|
-
import { PathwaysBuilder
|
|
429
|
-
|
|
430
|
-
// Create a KV adapter for storing session information
|
|
431
|
-
const sessionStore = await createKvAdapter();
|
|
431
|
+
import { PathwaysBuilder } from "@flowcore/pathways"
|
|
432
432
|
|
|
433
433
|
// Configure the builder with session support
|
|
434
434
|
const pathways = new PathwaysBuilder({
|
|
@@ -436,8 +436,8 @@ const pathways = new PathwaysBuilder({
|
|
|
436
436
|
tenant: "your-tenant",
|
|
437
437
|
dataCore: "your-data-core",
|
|
438
438
|
apiKey: "your-api-key",
|
|
439
|
-
|
|
440
|
-
})
|
|
439
|
+
enableSessionUserResolvers: true, // Enable session-specific resolvers
|
|
440
|
+
})
|
|
441
441
|
```
|
|
442
442
|
|
|
443
443
|
#### Creating Session Pathways
|
|
@@ -445,31 +445,38 @@ const pathways = new PathwaysBuilder({
|
|
|
445
445
|
Create a session-specific pathway wrapper:
|
|
446
446
|
|
|
447
447
|
```typescript
|
|
448
|
-
import { SessionPathwayBuilder } from "@flowcore/pathways"
|
|
448
|
+
import { SessionPathwayBuilder } from "@flowcore/pathways"
|
|
449
449
|
|
|
450
450
|
// Create a session with an auto-generated session ID
|
|
451
|
-
const session = new SessionPathwayBuilder(pathways)
|
|
452
|
-
const sessionId = session.getSessionId()
|
|
451
|
+
const session = new SessionPathwayBuilder(pathways)
|
|
452
|
+
const sessionId = session.getSessionId() // Get the auto-generated ID
|
|
453
453
|
|
|
454
454
|
// Or create a session with a specific session ID
|
|
455
|
-
const customSession = new SessionPathwayBuilder(pathways, "user-session-123")
|
|
455
|
+
const customSession = new SessionPathwayBuilder(pathways, "user-session-123")
|
|
456
456
|
```
|
|
457
457
|
|
|
458
458
|
#### Session-Specific User Resolvers
|
|
459
459
|
|
|
460
|
-
You can register different user resolvers for different sessions, allowing you to associate users with specific
|
|
460
|
+
You can register different user resolvers for different sessions, allowing you to associate users with specific
|
|
461
|
+
sessions:
|
|
461
462
|
|
|
462
463
|
```typescript
|
|
463
464
|
// Register a user resolver for a specific session
|
|
464
465
|
pathways.withSessionUserResolver("user-session-123", async () => {
|
|
465
466
|
// Return the user ID for this session
|
|
466
|
-
return
|
|
467
|
-
|
|
467
|
+
return {
|
|
468
|
+
entityId: "user-456",
|
|
469
|
+
entityType: "user",
|
|
470
|
+
}
|
|
471
|
+
})
|
|
468
472
|
|
|
469
473
|
// Alternative: Register directly through the session instance
|
|
470
474
|
session.withUserResolver(async () => {
|
|
471
|
-
return
|
|
472
|
-
|
|
475
|
+
return {
|
|
476
|
+
entityId: "key-789",
|
|
477
|
+
entityType: "key",
|
|
478
|
+
}
|
|
479
|
+
})
|
|
473
480
|
```
|
|
474
481
|
|
|
475
482
|
#### Writing Events with Session Context
|
|
@@ -482,16 +489,16 @@ await session.write("order/placed", {
|
|
|
482
489
|
orderId: "ord-123",
|
|
483
490
|
userId: "user-456",
|
|
484
491
|
total: 99.99,
|
|
485
|
-
items: [{ id: "item-1", quantity: 2 }]
|
|
486
|
-
})
|
|
492
|
+
items: [{ id: "item-1", quantity: 2 }],
|
|
493
|
+
})
|
|
487
494
|
|
|
488
495
|
// You can override the session ID for a specific write
|
|
489
496
|
await session.write(
|
|
490
497
|
"order/placed",
|
|
491
498
|
orderData,
|
|
492
499
|
{}, // No metadata
|
|
493
|
-
{ sessionId: "different-session" }
|
|
494
|
-
)
|
|
500
|
+
{ sessionId: "different-session" },
|
|
501
|
+
)
|
|
495
502
|
```
|
|
496
503
|
|
|
497
504
|
#### Session ID in Audit Events
|
|
@@ -501,12 +508,12 @@ When auditing is enabled, the session ID is included in the audit metadata:
|
|
|
501
508
|
```typescript
|
|
502
509
|
// Enable auditing
|
|
503
510
|
pathways.withAudit((path, event) => {
|
|
504
|
-
console.log(`Audit: ${path} event ${event.eventId}`)
|
|
511
|
+
console.log(`Audit: ${path} event ${event.eventId}`)
|
|
505
512
|
// The session ID will be included in event metadata
|
|
506
|
-
})
|
|
513
|
+
})
|
|
507
514
|
|
|
508
515
|
// Now when writing events through a session
|
|
509
|
-
await session.write("order/placed", orderData)
|
|
516
|
+
await session.write("order/placed", orderData)
|
|
510
517
|
// The session ID is automatically included in the audit metadata
|
|
511
518
|
```
|
|
512
519
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,oBAAoB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,YAAY,CAAA"}
|
package/esm/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,cAAc,mBAAmB,
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type Static, type TSchema } from "@sinclair/typebox";
|
|
2
2
|
import type { WebhookSendOptions } from "@flowcore/sdk-transformer-core";
|
|
3
3
|
import type { FlowcoreEvent } from "../contracts/event.js";
|
|
4
|
-
import type { KvAdapter } from "./kv/kv-adapter.js";
|
|
5
4
|
import type { Logger } from "./logger.js";
|
|
6
5
|
import type { EventMetadata, PathwayContract, PathwayKey, PathwayState, PathwayWriteOptions, WritablePathway } from "./types.js";
|
|
7
6
|
/**
|
|
@@ -16,11 +15,20 @@ export type AuditMode = "user" | "system";
|
|
|
16
15
|
* @param event The event data being processed
|
|
17
16
|
*/
|
|
18
17
|
export type AuditHandler = (path: string, event: FlowcoreEvent) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Represents an entity that can be used for audit purposes
|
|
20
|
+
* @property entityId The unique identifier for the entity
|
|
21
|
+
* @property entityType The type of entity (e.g., "user" or "key")
|
|
22
|
+
*/
|
|
23
|
+
export type UserResolverEntity = {
|
|
24
|
+
entityId: string;
|
|
25
|
+
entityType: "user" | "key";
|
|
26
|
+
};
|
|
19
27
|
/**
|
|
20
28
|
* Async function that resolves to the current user ID
|
|
21
29
|
* Used for audit functionality to track which user initiated an action
|
|
22
30
|
*/
|
|
23
|
-
export type UserIdResolver = () => Promise<
|
|
31
|
+
export type UserIdResolver = () => Promise<UserResolverEntity>;
|
|
24
32
|
/**
|
|
25
33
|
* Extended webhook send options with additional audit-specific options
|
|
26
34
|
*/
|
|
@@ -30,6 +38,57 @@ export interface AuditWebhookSendOptions extends WebhookSendOptions {
|
|
|
30
38
|
*/
|
|
31
39
|
headers?: Record<string, string>;
|
|
32
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* SessionUserResolver is a key-value store for storing and retrieving UserIdResolver functions
|
|
43
|
+
* with a TTL (time to live).
|
|
44
|
+
*
|
|
45
|
+
* This allows for session-specific user resolvers to be stored and reused across different
|
|
46
|
+
* sessions or operations.
|
|
47
|
+
*/
|
|
48
|
+
export interface SessionUserResolver {
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves a UserIdResolver from the session user resolver store
|
|
51
|
+
* @param key The key to retrieve the UserIdResolver for
|
|
52
|
+
* @returns The UserIdResolver or undefined if it doesn't exist
|
|
53
|
+
*/
|
|
54
|
+
get(key: string): Promise<UserIdResolver | undefined> | UserIdResolver | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Stores a UserIdResolver in the session user resolver store
|
|
57
|
+
* @param key The key to store the UserIdResolver under
|
|
58
|
+
* @param value The UserIdResolver to store
|
|
59
|
+
* @param ttlMs The time to live for the UserIdResolver in milliseconds
|
|
60
|
+
*/
|
|
61
|
+
set(key: string, value: UserIdResolver, ttlMs: number): Promise<void> | void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* SessionUserResolver implementation that uses a Map to store UserIdResolver functions
|
|
65
|
+
* with a TTL (time to live).
|
|
66
|
+
*/
|
|
67
|
+
export declare class SessionUser implements SessionUserResolver {
|
|
68
|
+
/**
|
|
69
|
+
* The underlying Map that stores UserIdResolver functions and their timeouts
|
|
70
|
+
* Using unknown for timeout to support both Node.js and Deno timer types
|
|
71
|
+
*/
|
|
72
|
+
private store;
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new SessionUser instance
|
|
75
|
+
*/
|
|
76
|
+
constructor();
|
|
77
|
+
/**
|
|
78
|
+
* Retrieves a UserIdResolver from the session user resolver store
|
|
79
|
+
* @param key The key to retrieve the UserIdResolver for
|
|
80
|
+
* @returns The UserIdResolver or undefined if it doesn't exist
|
|
81
|
+
*/
|
|
82
|
+
get(key: string): UserIdResolver | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Stores a UserIdResolver in the session user resolver store
|
|
85
|
+
* @param key The key to store the UserIdResolver under
|
|
86
|
+
* @param value The UserIdResolver to store
|
|
87
|
+
* @param ttlMs The time to live for the UserIdResolver in milliseconds
|
|
88
|
+
* @default 5 minutes
|
|
89
|
+
*/
|
|
90
|
+
set(key: string, value: UserIdResolver, ttlMs?: number): void;
|
|
91
|
+
}
|
|
33
92
|
/**
|
|
34
93
|
* Main builder class for creating and managing Flowcore pathways
|
|
35
94
|
*
|
|
@@ -79,16 +138,18 @@ export declare class PathwaysBuilder<TPathway extends Record<string, unknown> =
|
|
|
79
138
|
* @param options.apiKey The API key for authentication
|
|
80
139
|
* @param options.pathwayTimeoutMs Optional timeout for pathway processing in milliseconds
|
|
81
140
|
* @param options.logger Optional logger instance
|
|
82
|
-
* @param options.
|
|
141
|
+
* @param options.enableSessionUserResolvers Whether to enable session user resolvers
|
|
142
|
+
* @param options.overrideSessionUserResolvers Optional SessionUserResolver instance to override the default
|
|
83
143
|
*/
|
|
84
|
-
constructor({ baseUrl, tenant, dataCore, apiKey, pathwayTimeoutMs, logger,
|
|
144
|
+
constructor({ baseUrl, tenant, dataCore, apiKey, pathwayTimeoutMs, logger, enableSessionUserResolvers, overrideSessionUserResolvers, }: {
|
|
85
145
|
baseUrl: string;
|
|
86
146
|
tenant: string;
|
|
87
147
|
dataCore: string;
|
|
88
148
|
apiKey: string;
|
|
89
149
|
pathwayTimeoutMs?: number;
|
|
90
150
|
logger?: Logger;
|
|
91
|
-
|
|
151
|
+
enableSessionUserResolvers?: boolean;
|
|
152
|
+
overrideSessionUserResolvers?: SessionUserResolver;
|
|
92
153
|
});
|
|
93
154
|
/**
|
|
94
155
|
* Configures the PathwaysBuilder to use a custom pathway state implementation
|