@wooksjs/event-wf 0.7.6 → 0.7.8
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/dist/index.cjs +468 -16
- package/dist/index.d.ts +192 -6
- package/dist/index.mjs +453 -13
- package/package.json +18 -6
package/dist/index.cjs
CHANGED
|
@@ -6,11 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key$
|
|
10
|
-
key$
|
|
11
|
-
if (!__hasOwnProp.call(to, key$
|
|
12
|
-
get: ((k) => from[k]).bind(null, key$
|
|
13
|
-
enumerable: !(desc = __getOwnPropDesc(from, key$
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key$2; i < n; i++) {
|
|
10
|
+
key$2 = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key$2) && key$2 !== except) __defProp(to, key$2, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key$2),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key$2)) || desc.enumerable
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
@@ -22,6 +22,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const __wooksjs_event_core = __toESM(require("@wooksjs/event-core"));
|
|
25
|
+
const __wooksjs_event_http = __toESM(require("@wooksjs/event-http"));
|
|
26
|
+
const __wooksjs_http_body = __toESM(require("@wooksjs/http-body"));
|
|
27
|
+
const node_crypto = __toESM(require("node:crypto"));
|
|
25
28
|
const __prostojs_wf = __toESM(require("@prostojs/wf"));
|
|
26
29
|
const wooks = __toESM(require("wooks"));
|
|
27
30
|
|
|
@@ -46,17 +49,14 @@ const resumeKey = (0, __wooksjs_event_core.key)("wf.resume");
|
|
|
46
49
|
* const stepInput = input<MyInput>()
|
|
47
50
|
* ```
|
|
48
51
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
resume: c.get(resumeKey)
|
|
58
|
-
};
|
|
59
|
-
}
|
|
52
|
+
const useWfState = (0, __wooksjs_event_core.defineWook)((c) => ({
|
|
53
|
+
ctx: () => c.get(wfKind.keys.inputContext),
|
|
54
|
+
input: () => c.get(wfKind.keys.input),
|
|
55
|
+
schemaId: c.get(wfKind.keys.schemaId),
|
|
56
|
+
stepId: () => c.get(wfKind.keys.stepId),
|
|
57
|
+
indexes: () => c.get(wfKind.keys.indexes),
|
|
58
|
+
resume: c.get(resumeKey)
|
|
59
|
+
}));
|
|
60
60
|
|
|
61
61
|
//#endregion
|
|
62
62
|
//#region packages/event-wf/src/event-wf.ts
|
|
@@ -75,6 +75,446 @@ function resumeWfContext(options, seeds, fn) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region packages/event-wf/src/outlets/outlet-context.ts
|
|
80
|
+
/** Registered outlet handlers, keyed by name */
|
|
81
|
+
const outletsRegistryKey = (0, __wooksjs_event_core.key)("wf.outlets.registry");
|
|
82
|
+
/** Active state strategy for current request */
|
|
83
|
+
const stateStrategyKey = (0, __wooksjs_event_core.key)("wf.outlets.stateStrategy");
|
|
84
|
+
/** Finished response set by workflow steps */
|
|
85
|
+
const wfFinishedKey = (0, __wooksjs_event_core.key)("wf.outlets.finished");
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region packages/event-wf/src/outlets/use-wf-outlet.ts
|
|
89
|
+
/**
|
|
90
|
+
* Composable for accessing outlet infrastructure from within workflow steps.
|
|
91
|
+
*
|
|
92
|
+
* Most steps don't need this — they just return `outletHttp(form)` or
|
|
93
|
+
* `outletEmail(to, template)`. This composable is for advanced cases
|
|
94
|
+
* where steps need to inspect or modify outlet state directly.
|
|
95
|
+
*/
|
|
96
|
+
const useWfOutlet = (0, __wooksjs_event_core.defineWook)((ctx) => ({
|
|
97
|
+
getStateStrategy: () => ctx.get(stateStrategyKey),
|
|
98
|
+
getOutlets: () => ctx.get(outletsRegistryKey),
|
|
99
|
+
getOutlet: (name) => ctx.get(outletsRegistryKey)?.get(name) ?? null
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region packages/event-wf/src/outlets/use-wf-finished.ts
|
|
104
|
+
/**
|
|
105
|
+
* Composable to set the completion response for a finished workflow.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* // Redirect after login
|
|
110
|
+
* useWfFinished().set({ type: 'redirect', value: '/dashboard' })
|
|
111
|
+
*
|
|
112
|
+
* // Return data
|
|
113
|
+
* useWfFinished().set({ type: 'data', value: { success: true } })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
const useWfFinished = (0, __wooksjs_event_core.defineWook)((ctx) => ({
|
|
117
|
+
set: (response) => ctx.set(wfFinishedKey, response),
|
|
118
|
+
get: () => ctx.has(wfFinishedKey) ? ctx.get(wfFinishedKey) : void 0
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region packages/event-wf/src/outlets/trigger.ts
|
|
123
|
+
const DEFAULT_CONSUME_TOKEN = { email: true };
|
|
124
|
+
/**
|
|
125
|
+
* Handle an HTTP request that starts or resumes a workflow.
|
|
126
|
+
*
|
|
127
|
+
* Reads wfs (state token) and wfid (workflow ID) from request body, query params,
|
|
128
|
+
* or cookies — configurable via `config.token`. On workflow pause, persists state
|
|
129
|
+
* and dispatches to the named outlet. On finish, returns the finished response.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* // In a wooks HTTP handler:
|
|
134
|
+
* app.post('/workflow', () => handleWfOutletRequest(config, deps))
|
|
135
|
+
*
|
|
136
|
+
* // Better — use createOutletHandler():
|
|
137
|
+
* const handle = createOutletHandler(wfApp)
|
|
138
|
+
* app.post('/workflow', () => handle(config))
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
async function handleWfOutletRequest(config, deps) {
|
|
142
|
+
const tok = config.token ?? {};
|
|
143
|
+
const tokenName = tok.name ?? "wfs";
|
|
144
|
+
const tokenRead = tok.read ?? [
|
|
145
|
+
"body",
|
|
146
|
+
"query",
|
|
147
|
+
"cookie"
|
|
148
|
+
];
|
|
149
|
+
const tokenWrite = tok.write ?? "body";
|
|
150
|
+
const wfidName = config.wfidName ?? "wfid";
|
|
151
|
+
const ctx = (0, __wooksjs_event_core.current)();
|
|
152
|
+
const registry = new Map(config.outlets.map((o) => [o.name, o]));
|
|
153
|
+
ctx.set(outletsRegistryKey, registry);
|
|
154
|
+
ctx.set(wfFinishedKey, void 0);
|
|
155
|
+
const { parseBody } = (0, __wooksjs_http_body.useBody)();
|
|
156
|
+
const { params } = (0, __wooksjs_event_http.useUrlParams)();
|
|
157
|
+
const { getCookie } = (0, __wooksjs_event_http.useCookies)();
|
|
158
|
+
const response = (0, __wooksjs_event_http.useResponse)();
|
|
159
|
+
const body = await parseBody().catch(() => void 0);
|
|
160
|
+
const queryParams = params();
|
|
161
|
+
let token;
|
|
162
|
+
for (const source of tokenRead) {
|
|
163
|
+
if (source === "body") token = body?.[tokenName];
|
|
164
|
+
else if (source === "query") token = queryParams.get(tokenName) ?? void 0;
|
|
165
|
+
else if (source === "cookie") token = getCookie(tokenName) ?? void 0;
|
|
166
|
+
if (token) break;
|
|
167
|
+
}
|
|
168
|
+
const wfid = body?.[wfidName] ?? queryParams.get(wfidName) ?? void 0;
|
|
169
|
+
const input = body?.input;
|
|
170
|
+
const resolveStrategy = (id) => typeof config.state === "function" ? config.state(id) : config.state;
|
|
171
|
+
const shouldConsume = (outletName) => {
|
|
172
|
+
if (typeof tok.consume === "boolean") return tok.consume;
|
|
173
|
+
return (tok.consume ?? DEFAULT_CONSUME_TOKEN)[outletName] ?? false;
|
|
174
|
+
};
|
|
175
|
+
let output;
|
|
176
|
+
if (token) {
|
|
177
|
+
const strategy = resolveStrategy(wfid ?? "");
|
|
178
|
+
ctx.set(stateStrategyKey, strategy);
|
|
179
|
+
const state = await strategy.retrieve(token);
|
|
180
|
+
if (!state) return {
|
|
181
|
+
error: "Invalid or expired workflow state",
|
|
182
|
+
status: 400
|
|
183
|
+
};
|
|
184
|
+
if (state.schemaId !== (wfid ?? "")) {
|
|
185
|
+
const realStrategy = resolveStrategy(state.schemaId);
|
|
186
|
+
ctx.set(stateStrategyKey, realStrategy);
|
|
187
|
+
}
|
|
188
|
+
const outletName = state.meta?.outlet;
|
|
189
|
+
if (outletName && shouldConsume(outletName)) await strategy.consume(token);
|
|
190
|
+
output = await deps.resume(state, {
|
|
191
|
+
input,
|
|
192
|
+
eventContext: ctx
|
|
193
|
+
});
|
|
194
|
+
} else if (wfid) {
|
|
195
|
+
if (config.allow?.length && !config.allow.includes(wfid)) return {
|
|
196
|
+
error: `Workflow '${wfid}' is not allowed`,
|
|
197
|
+
status: 403
|
|
198
|
+
};
|
|
199
|
+
if (config.block?.includes(wfid)) return {
|
|
200
|
+
error: `Workflow '${wfid}' is blocked`,
|
|
201
|
+
status: 403
|
|
202
|
+
};
|
|
203
|
+
const strategy = resolveStrategy(wfid);
|
|
204
|
+
ctx.set(stateStrategyKey, strategy);
|
|
205
|
+
const initialContext = config.initialContext ? config.initialContext(body, wfid) : {};
|
|
206
|
+
output = await deps.start(wfid, initialContext, {
|
|
207
|
+
input,
|
|
208
|
+
eventContext: ctx
|
|
209
|
+
});
|
|
210
|
+
} else return {
|
|
211
|
+
error: "Missing wfs (state token) or wfid (workflow ID)",
|
|
212
|
+
status: 400
|
|
213
|
+
};
|
|
214
|
+
if (output.finished) {
|
|
215
|
+
if (config.onFinished) return config.onFinished({
|
|
216
|
+
context: output.state.context,
|
|
217
|
+
schemaId: output.state.schemaId
|
|
218
|
+
});
|
|
219
|
+
const finished = ctx.get(wfFinishedKey);
|
|
220
|
+
if (finished?.cookies) for (const [name, cookie] of Object.entries(finished.cookies)) response.setCookie(name, cookie.value, cookie.options);
|
|
221
|
+
if (finished?.type === "redirect") {
|
|
222
|
+
response.setHeader("location", finished.value);
|
|
223
|
+
return { status: finished.status ?? 302 };
|
|
224
|
+
}
|
|
225
|
+
if (finished) return finished.value;
|
|
226
|
+
return { finished: true };
|
|
227
|
+
}
|
|
228
|
+
if (output.inputRequired) {
|
|
229
|
+
const outletReq = output.inputRequired;
|
|
230
|
+
const outletHandler = registry.get(outletReq.outlet);
|
|
231
|
+
if (!outletHandler) return {
|
|
232
|
+
error: `Unknown outlet: '${outletReq.outlet}'`,
|
|
233
|
+
status: 500
|
|
234
|
+
};
|
|
235
|
+
const strategy = ctx.get(stateStrategyKey);
|
|
236
|
+
const stateWithMeta = {
|
|
237
|
+
...output.state,
|
|
238
|
+
meta: { outlet: outletReq.outlet }
|
|
239
|
+
};
|
|
240
|
+
const newToken = await strategy.persist(stateWithMeta, output.expires ? { ttl: output.expires - Date.now() } : void 0);
|
|
241
|
+
if (tokenWrite === "cookie") response.setCookie(tokenName, newToken, {
|
|
242
|
+
httpOnly: true,
|
|
243
|
+
sameSite: "Strict",
|
|
244
|
+
path: "/"
|
|
245
|
+
});
|
|
246
|
+
const result = await outletHandler.deliver(outletReq, newToken);
|
|
247
|
+
if (tokenWrite === "body" && result?.response && typeof result.response === "object") return {
|
|
248
|
+
...result.response,
|
|
249
|
+
[tokenName]: newToken
|
|
250
|
+
};
|
|
251
|
+
return result?.response ?? { waiting: true };
|
|
252
|
+
}
|
|
253
|
+
if (output.error) return {
|
|
254
|
+
error: output.error.message,
|
|
255
|
+
errorList: output.errorList
|
|
256
|
+
};
|
|
257
|
+
return { error: "Unexpected workflow state" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region packages/event-wf/src/outlets/create-outlet.ts
|
|
262
|
+
/**
|
|
263
|
+
* Creates an HTTP outlet that passes through the outlet payload as
|
|
264
|
+
* the HTTP response body. This is the most common outlet — it returns
|
|
265
|
+
* forms, prompts, or data to the client.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const httpOutlet = createHttpOutlet()
|
|
270
|
+
* // Step does: return outletHttp({ fields: ['email', 'password'] })
|
|
271
|
+
* // Client receives: { fields: ['email', 'password'] }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
function createHttpOutlet(opts) {
|
|
275
|
+
return {
|
|
276
|
+
name: "http",
|
|
277
|
+
async deliver(request, _token) {
|
|
278
|
+
const body = opts?.transform ? opts.transform(request.payload, request.context) : typeof request.payload === "object" && request.payload !== null ? {
|
|
279
|
+
...request.payload,
|
|
280
|
+
...request.context
|
|
281
|
+
} : request.payload;
|
|
282
|
+
return { response: body };
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Creates an email outlet that delegates to a user-provided send function.
|
|
288
|
+
* The send function receives the target, template, context, and the state
|
|
289
|
+
* token (for embedding in magic links / verification URLs).
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const emailOutlet = createEmailOutlet(async (opts) => {
|
|
294
|
+
* await mailer.send({
|
|
295
|
+
* to: opts.target,
|
|
296
|
+
* template: opts.template,
|
|
297
|
+
* data: { ...opts.context, verifyUrl: `/verify?wfs=${opts.token}` },
|
|
298
|
+
* })
|
|
299
|
+
* })
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
function createEmailOutlet(send) {
|
|
303
|
+
return {
|
|
304
|
+
name: "email",
|
|
305
|
+
async deliver(request, token) {
|
|
306
|
+
await send({
|
|
307
|
+
target: request.target ?? "",
|
|
308
|
+
template: request.template ?? "",
|
|
309
|
+
context: request.context ?? {},
|
|
310
|
+
token
|
|
311
|
+
});
|
|
312
|
+
return { response: {
|
|
313
|
+
sent: true,
|
|
314
|
+
outlet: "email"
|
|
315
|
+
} };
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region packages/event-wf/src/outlets/create-handler.ts
|
|
322
|
+
/**
|
|
323
|
+
* Creates a pre-wired outlet handler from a workflow app instance.
|
|
324
|
+
* Eliminates the need to manually construct `WfOutletTriggerDeps`.
|
|
325
|
+
*
|
|
326
|
+
* Accepts any object with `start` and `resume` methods (WooksWf, MoostWf, etc.).
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```ts
|
|
330
|
+
* const handle = createOutletHandler(wfApp)
|
|
331
|
+
* httpApp.post('/workflow', () => handle(config))
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
function createOutletHandler(wfApp) {
|
|
335
|
+
return (config) => handleWfOutletRequest(config, {
|
|
336
|
+
start: (schemaId, context, opts) => wfApp.start(schemaId, context, opts),
|
|
337
|
+
resume: (state, opts) => wfApp.resume(state, opts)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region node_modules/.pnpm/@prostojs+wf@0.1.1/node_modules/@prostojs/wf/dist/outlets/index.mjs
|
|
343
|
+
/**
|
|
344
|
+
* Generic outlet request. Use for custom outlets.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* return outlet('pending-task', {
|
|
348
|
+
* payload: ApprovalForm,
|
|
349
|
+
* target: managerId,
|
|
350
|
+
* context: { orderId, amount },
|
|
351
|
+
* })
|
|
352
|
+
*/
|
|
353
|
+
function outlet(name, data) {
|
|
354
|
+
return { inputRequired: {
|
|
355
|
+
outlet: name,
|
|
356
|
+
...data
|
|
357
|
+
} };
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Pause for HTTP form input. The outlet returns the payload (form definition)
|
|
361
|
+
* and state token in the HTTP response.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* return outletHttp(LoginForm)
|
|
365
|
+
* return outletHttp(LoginForm, { error: 'Invalid credentials' })
|
|
366
|
+
*/
|
|
367
|
+
function outletHttp(payload, context) {
|
|
368
|
+
return outlet("http", {
|
|
369
|
+
payload,
|
|
370
|
+
context
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Pause and send email with a magic link containing the state token.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* return outletEmail('user@test.com', 'invite', { name: 'Alice' })
|
|
378
|
+
*/
|
|
379
|
+
function outletEmail(target, template, context) {
|
|
380
|
+
return outlet("email", {
|
|
381
|
+
target,
|
|
382
|
+
template,
|
|
383
|
+
context
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Self-contained AES-256-GCM encrypted state strategy.
|
|
388
|
+
*
|
|
389
|
+
* Workflow state is encrypted into a base64url token that travels with the
|
|
390
|
+
* transport (cookie, URL param, hidden field). No server-side storage needed.
|
|
391
|
+
*
|
|
392
|
+
* Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* const strategy = new EncapsulatedStateStrategy({
|
|
396
|
+
* secret: crypto.randomBytes(32),
|
|
397
|
+
* defaultTtl: 3600_000, // 1 hour
|
|
398
|
+
* });
|
|
399
|
+
* const token = await strategy.persist(state);
|
|
400
|
+
* const recovered = await strategy.retrieve(token);
|
|
401
|
+
*/
|
|
402
|
+
var EncapsulatedStateStrategy = class {
|
|
403
|
+
/** @throws if secret is not exactly 32 bytes */
|
|
404
|
+
constructor(config) {
|
|
405
|
+
this.config = config;
|
|
406
|
+
this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
|
|
407
|
+
if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Encrypt workflow state into a self-contained token.
|
|
411
|
+
* @param state — workflow state to persist
|
|
412
|
+
* @param options.ttl — time-to-live in ms (overrides defaultTtl)
|
|
413
|
+
* @returns base64url-encoded encrypted token
|
|
414
|
+
*/
|
|
415
|
+
async persist(state, options) {
|
|
416
|
+
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
417
|
+
const exp = ttl > 0 ? Date.now() + ttl : 0;
|
|
418
|
+
const payload = JSON.stringify({
|
|
419
|
+
s: state,
|
|
420
|
+
e: exp
|
|
421
|
+
});
|
|
422
|
+
const iv = (0, node_crypto.randomBytes)(12);
|
|
423
|
+
const cipher = (0, node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
424
|
+
const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
|
|
425
|
+
const tag = cipher.getAuthTag();
|
|
426
|
+
return Buffer.concat([
|
|
427
|
+
iv,
|
|
428
|
+
tag,
|
|
429
|
+
encrypted
|
|
430
|
+
]).toString("base64url");
|
|
431
|
+
}
|
|
432
|
+
/** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
|
|
433
|
+
async retrieve(token) {
|
|
434
|
+
return this.decrypt(token);
|
|
435
|
+
}
|
|
436
|
+
/** Same as retrieve (stateless — cannot truly invalidate a token). */
|
|
437
|
+
async consume(token) {
|
|
438
|
+
return this.decrypt(token);
|
|
439
|
+
}
|
|
440
|
+
decrypt(token) {
|
|
441
|
+
try {
|
|
442
|
+
const buf = Buffer.from(token, "base64url");
|
|
443
|
+
if (buf.length < 28) return null;
|
|
444
|
+
const iv = buf.subarray(0, 12);
|
|
445
|
+
const tag = buf.subarray(12, 28);
|
|
446
|
+
const ciphertext = buf.subarray(28);
|
|
447
|
+
const decipher = (0, node_crypto.createDecipheriv)("aes-256-gcm", this.key, iv);
|
|
448
|
+
decipher.setAuthTag(tag);
|
|
449
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
450
|
+
const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
|
|
451
|
+
if (exp > 0 && Date.now() > exp) return null;
|
|
452
|
+
return state;
|
|
453
|
+
} catch {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
var HandleStateStrategy = class {
|
|
459
|
+
constructor(config) {
|
|
460
|
+
this.config = config;
|
|
461
|
+
}
|
|
462
|
+
async persist(state, options) {
|
|
463
|
+
const handle = (this.config.generateHandle ?? node_crypto.randomUUID)();
|
|
464
|
+
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
465
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
|
|
466
|
+
await this.config.store.set(handle, state, expiresAt);
|
|
467
|
+
return handle;
|
|
468
|
+
}
|
|
469
|
+
async retrieve(token) {
|
|
470
|
+
return (await this.config.store.get(token))?.state ?? null;
|
|
471
|
+
}
|
|
472
|
+
async consume(token) {
|
|
473
|
+
return (await this.config.store.getAndDelete(token))?.state ?? null;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* In-memory state store for development and testing.
|
|
478
|
+
* State is lost on process restart.
|
|
479
|
+
*/
|
|
480
|
+
var WfStateStoreMemory = class {
|
|
481
|
+
constructor() {
|
|
482
|
+
this.store = /* @__PURE__ */ new Map();
|
|
483
|
+
}
|
|
484
|
+
async set(handle, state, expiresAt) {
|
|
485
|
+
this.store.set(handle, {
|
|
486
|
+
state,
|
|
487
|
+
expiresAt
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async get(handle) {
|
|
491
|
+
const entry = this.store.get(handle);
|
|
492
|
+
if (!entry) return null;
|
|
493
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
494
|
+
this.store.delete(handle);
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
return entry;
|
|
498
|
+
}
|
|
499
|
+
async delete(handle) {
|
|
500
|
+
this.store.delete(handle);
|
|
501
|
+
}
|
|
502
|
+
async getAndDelete(handle) {
|
|
503
|
+
const entry = await this.get(handle);
|
|
504
|
+
if (entry) this.store.delete(handle);
|
|
505
|
+
return entry;
|
|
506
|
+
}
|
|
507
|
+
async cleanup() {
|
|
508
|
+
const now = Date.now();
|
|
509
|
+
let count = 0;
|
|
510
|
+
for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
|
|
511
|
+
this.store.delete(handle);
|
|
512
|
+
count++;
|
|
513
|
+
}
|
|
514
|
+
return count;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
78
518
|
//#endregion
|
|
79
519
|
//#region packages/event-wf/src/workflow.ts
|
|
80
520
|
/** Workflow engine that resolves steps via Wooks router lookup. */
|
|
@@ -267,15 +707,25 @@ function createWfApp(opts, wooks$1) {
|
|
|
267
707
|
}
|
|
268
708
|
|
|
269
709
|
//#endregion
|
|
710
|
+
exports.EncapsulatedStateStrategy = EncapsulatedStateStrategy;
|
|
711
|
+
exports.HandleStateStrategy = HandleStateStrategy;
|
|
270
712
|
Object.defineProperty(exports, 'StepRetriableError', {
|
|
271
713
|
enumerable: true,
|
|
272
714
|
get: function () {
|
|
273
715
|
return __prostojs_wf.StepRetriableError;
|
|
274
716
|
}
|
|
275
717
|
});
|
|
718
|
+
exports.WfStateStoreMemory = WfStateStoreMemory;
|
|
276
719
|
exports.WooksWf = WooksWf;
|
|
720
|
+
exports.createEmailOutlet = createEmailOutlet;
|
|
721
|
+
exports.createHttpOutlet = createHttpOutlet;
|
|
722
|
+
exports.createOutletHandler = createOutletHandler;
|
|
277
723
|
exports.createWfApp = createWfApp;
|
|
278
724
|
exports.createWfContext = createWfContext;
|
|
725
|
+
exports.handleWfOutletRequest = handleWfOutletRequest;
|
|
726
|
+
exports.outlet = outlet;
|
|
727
|
+
exports.outletEmail = outletEmail;
|
|
728
|
+
exports.outletHttp = outletHttp;
|
|
279
729
|
exports.resumeKey = resumeKey;
|
|
280
730
|
exports.resumeWfContext = resumeWfContext;
|
|
281
731
|
Object.defineProperty(exports, 'useLogger', {
|
|
@@ -290,6 +740,8 @@ Object.defineProperty(exports, 'useRouteParams', {
|
|
|
290
740
|
return __wooksjs_event_core.useRouteParams;
|
|
291
741
|
}
|
|
292
742
|
});
|
|
743
|
+
exports.useWfFinished = useWfFinished;
|
|
744
|
+
exports.useWfOutlet = useWfOutlet;
|
|
293
745
|
exports.useWfState = useWfState;
|
|
294
746
|
exports.wfKind = wfKind;
|
|
295
747
|
exports.wfShortcuts = wfShortcuts;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as _wooksjs_event_core from '@wooksjs/event-core';
|
|
2
2
|
import { EventContextOptions, EventKindSeeds, EventContext } from '@wooksjs/event-core';
|
|
3
3
|
export { EventContext, EventContextOptions, useLogger, useRouteParams } from '@wooksjs/event-core';
|
|
4
|
+
import * as _prostojs_wf_outlets from '@prostojs/wf/outlets';
|
|
5
|
+
import { WfStateStrategy, WfOutlet, WfOutletRequest } from '@prostojs/wf/outlets';
|
|
6
|
+
export { EncapsulatedStateStrategy, HandleStateStrategy, WfOutlet, WfOutletRequest, WfOutletResult, WfState, WfStateStore, WfStateStoreMemory, WfStateStrategy, outlet, outletEmail, outletHttp } from '@prostojs/wf/outlets';
|
|
7
|
+
import { TFlowOutput, Workflow, Step, TWorkflowSpy, TStepHandler, TWorkflowSchema } from '@prostojs/wf';
|
|
8
|
+
export { StepRetriableError, TStepHandler, TWorkflowSchema } from '@prostojs/wf';
|
|
4
9
|
import * as wooks from 'wooks';
|
|
5
10
|
import { Wooks, TWooksHandler, TWooksOptions, WooksAdapterBase } from 'wooks';
|
|
6
11
|
import { TConsoleBase } from '@prostojs/logger';
|
|
7
|
-
import { Workflow, Step, TWorkflowSpy, TStepHandler, TWorkflowSchema, TFlowOutput } from '@prostojs/wf';
|
|
8
|
-
export { StepRetriableError, TStepHandler, TWorkflowSchema } from '@prostojs/wf';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Composable that provides access to the current workflow execution state.
|
|
@@ -16,14 +19,14 @@ export { StepRetriableError, TStepHandler, TWorkflowSchema } from '@prostojs/wf'
|
|
|
16
19
|
* const stepInput = input<MyInput>()
|
|
17
20
|
* ```
|
|
18
21
|
*/
|
|
19
|
-
declare
|
|
22
|
+
declare const useWfState: _wooksjs_event_core.WookComposable<{
|
|
20
23
|
ctx: <T>() => T;
|
|
21
24
|
input: <I>() => I | undefined;
|
|
22
25
|
schemaId: string;
|
|
23
26
|
stepId: () => string | null;
|
|
24
27
|
indexes: () => number[] | undefined;
|
|
25
28
|
resume: boolean;
|
|
26
|
-
}
|
|
29
|
+
}>;
|
|
27
30
|
|
|
28
31
|
declare const wfKind: _wooksjs_event_core.EventKind<{
|
|
29
32
|
schemaId: _wooksjs_event_core.SlotMarker<string>;
|
|
@@ -39,6 +42,189 @@ declare function createWfContext<R>(options: EventContextOptions, seeds: EventKi
|
|
|
39
42
|
/** Creates a WF event context for resuming a paused workflow and runs `fn` inside it. */
|
|
40
43
|
declare function resumeWfContext<R>(options: EventContextOptions, seeds: EventKindSeeds<typeof wfKind>, fn: () => R): R;
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Composable for accessing outlet infrastructure from within workflow steps.
|
|
47
|
+
*
|
|
48
|
+
* Most steps don't need this — they just return `outletHttp(form)` or
|
|
49
|
+
* `outletEmail(to, template)`. This composable is for advanced cases
|
|
50
|
+
* where steps need to inspect or modify outlet state directly.
|
|
51
|
+
*/
|
|
52
|
+
declare const useWfOutlet: _wooksjs_event_core.WookComposable<{
|
|
53
|
+
getStateStrategy: () => _prostojs_wf_outlets.WfStateStrategy;
|
|
54
|
+
getOutlets: () => Map<string, _prostojs_wf_outlets.WfOutlet>;
|
|
55
|
+
getOutlet: (name: string) => _prostojs_wf_outlets.WfOutlet | null;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
interface WfFinishedResponse {
|
|
59
|
+
type: 'redirect' | 'data';
|
|
60
|
+
/** Redirect URL or response body */
|
|
61
|
+
value: unknown;
|
|
62
|
+
/** HTTP status code (default 200 for data, 302 for redirect) */
|
|
63
|
+
status?: number;
|
|
64
|
+
/** Cookies to set */
|
|
65
|
+
cookies?: Record<string, {
|
|
66
|
+
value: string;
|
|
67
|
+
options?: Record<string, unknown>;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Composable to set the completion response for a finished workflow.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* // Redirect after login
|
|
77
|
+
* useWfFinished().set({ type: 'redirect', value: '/dashboard' })
|
|
78
|
+
*
|
|
79
|
+
* // Return data
|
|
80
|
+
* useWfFinished().set({ type: 'data', value: { success: true } })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare const useWfFinished: _wooksjs_event_core.WookComposable<{
|
|
84
|
+
set: (response: WfFinishedResponse) => void;
|
|
85
|
+
get: () => WfFinishedResponse | undefined;
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
interface WfOutletTokenConfig {
|
|
89
|
+
/** Where to read state token from incoming request (default: `['body', 'query', 'cookie']`) */
|
|
90
|
+
read?: Array<'body' | 'query' | 'cookie'>;
|
|
91
|
+
/** Where to write state token in response (default: `'body'`) */
|
|
92
|
+
write?: 'body' | 'cookie';
|
|
93
|
+
/** Parameter name for state token (default: `'wfs'`) */
|
|
94
|
+
name?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Token consumption mode per outlet. When `true`, the trigger calls
|
|
97
|
+
* `strategy.consume()` (single-use token) instead of `strategy.retrieve()`
|
|
98
|
+
* on resume. Defaults to `{ email: true }` — email magic links are consumed
|
|
99
|
+
* on first use, HTTP tokens are reusable.
|
|
100
|
+
*
|
|
101
|
+
* Can be a boolean (applies to all outlets) or a per-outlet-name map.
|
|
102
|
+
*/
|
|
103
|
+
consume?: boolean | Record<string, boolean>;
|
|
104
|
+
}
|
|
105
|
+
interface WfOutletTriggerConfig {
|
|
106
|
+
/** Whitelist of allowed workflow IDs. If empty, all are allowed. */
|
|
107
|
+
allow?: string[];
|
|
108
|
+
/** Blacklist of workflow IDs. Checked after allow. */
|
|
109
|
+
block?: string[];
|
|
110
|
+
/** State persistence strategy */
|
|
111
|
+
state: WfStateStrategy | ((wfid: string) => WfStateStrategy);
|
|
112
|
+
/** Registered outlets */
|
|
113
|
+
outlets: WfOutlet[];
|
|
114
|
+
/** Token configuration (reading, writing, naming, consumption) */
|
|
115
|
+
token?: WfOutletTokenConfig;
|
|
116
|
+
/** Parameter name for workflow ID (default: `'wfid'`) */
|
|
117
|
+
wfidName?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Initial workflow context factory. Called when starting a new workflow.
|
|
120
|
+
* Receives the parsed request body so you can seed context from the request.
|
|
121
|
+
* Default: `() => ({})` (empty context).
|
|
122
|
+
*/
|
|
123
|
+
initialContext?: (body: Record<string, unknown> | undefined, wfid: string) => unknown;
|
|
124
|
+
/**
|
|
125
|
+
* Called when a workflow finishes. If provided, its return value becomes the
|
|
126
|
+
* HTTP response — overriding `useWfFinished()`. This keeps steps transport-agnostic
|
|
127
|
+
* when the completion response is always the same shape.
|
|
128
|
+
*
|
|
129
|
+
* If not provided, falls back to `useWfFinished()` or `{ finished: true }`.
|
|
130
|
+
*/
|
|
131
|
+
onFinished?: (ctx: {
|
|
132
|
+
context: unknown;
|
|
133
|
+
schemaId: string;
|
|
134
|
+
}) => unknown;
|
|
135
|
+
}
|
|
136
|
+
interface WfOutletTriggerDeps {
|
|
137
|
+
/** Start a workflow. Provided by WooksWf or MoostWf. */
|
|
138
|
+
start: (schemaId: string, context: unknown, opts?: {
|
|
139
|
+
input?: unknown;
|
|
140
|
+
eventContext?: unknown;
|
|
141
|
+
}) => Promise<TFlowOutput<unknown, unknown, WfOutletRequest>>;
|
|
142
|
+
/** Resume a workflow. Provided by WooksWf or MoostWf. */
|
|
143
|
+
resume: (state: {
|
|
144
|
+
schemaId: string;
|
|
145
|
+
indexes: number[];
|
|
146
|
+
context: unknown;
|
|
147
|
+
}, opts?: {
|
|
148
|
+
input?: unknown;
|
|
149
|
+
eventContext?: unknown;
|
|
150
|
+
}) => Promise<TFlowOutput<unknown, unknown, WfOutletRequest>>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle an HTTP request that starts or resumes a workflow.
|
|
155
|
+
*
|
|
156
|
+
* Reads wfs (state token) and wfid (workflow ID) from request body, query params,
|
|
157
|
+
* or cookies — configurable via `config.token`. On workflow pause, persists state
|
|
158
|
+
* and dispatches to the named outlet. On finish, returns the finished response.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* // In a wooks HTTP handler:
|
|
163
|
+
* app.post('/workflow', () => handleWfOutletRequest(config, deps))
|
|
164
|
+
*
|
|
165
|
+
* // Better — use createOutletHandler():
|
|
166
|
+
* const handle = createOutletHandler(wfApp)
|
|
167
|
+
* app.post('/workflow', () => handle(config))
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function handleWfOutletRequest(config: WfOutletTriggerConfig, deps: WfOutletTriggerDeps): Promise<unknown>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates an HTTP outlet that passes through the outlet payload as
|
|
174
|
+
* the HTTP response body. This is the most common outlet — it returns
|
|
175
|
+
* forms, prompts, or data to the client.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const httpOutlet = createHttpOutlet()
|
|
180
|
+
* // Step does: return outletHttp({ fields: ['email', 'password'] })
|
|
181
|
+
* // Client receives: { fields: ['email', 'password'] }
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function createHttpOutlet(opts?: {
|
|
185
|
+
/** Transform the payload before returning to client */
|
|
186
|
+
transform?: (payload: unknown, context?: Record<string, unknown>) => unknown;
|
|
187
|
+
}): WfOutlet;
|
|
188
|
+
/**
|
|
189
|
+
* Creates an email outlet that delegates to a user-provided send function.
|
|
190
|
+
* The send function receives the target, template, context, and the state
|
|
191
|
+
* token (for embedding in magic links / verification URLs).
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const emailOutlet = createEmailOutlet(async (opts) => {
|
|
196
|
+
* await mailer.send({
|
|
197
|
+
* to: opts.target,
|
|
198
|
+
* template: opts.template,
|
|
199
|
+
* data: { ...opts.context, verifyUrl: `/verify?wfs=${opts.token}` },
|
|
200
|
+
* })
|
|
201
|
+
* })
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
declare function createEmailOutlet(send: (opts: {
|
|
205
|
+
target: string;
|
|
206
|
+
template: string;
|
|
207
|
+
context: Record<string, unknown>;
|
|
208
|
+
token: string;
|
|
209
|
+
}) => Promise<void>): WfOutlet;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Creates a pre-wired outlet handler from a workflow app instance.
|
|
213
|
+
* Eliminates the need to manually construct `WfOutletTriggerDeps`.
|
|
214
|
+
*
|
|
215
|
+
* Accepts any object with `start` and `resume` methods (WooksWf, MoostWf, etc.).
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* const handle = createOutletHandler(wfApp)
|
|
220
|
+
* httpApp.post('/workflow', () => handle(config))
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function createOutletHandler(wfApp: {
|
|
224
|
+
start: WfOutletTriggerDeps['start'];
|
|
225
|
+
resume: WfOutletTriggerDeps['resume'];
|
|
226
|
+
}): (config: WfOutletTriggerConfig) => Promise<unknown>;
|
|
227
|
+
|
|
42
228
|
/** Input data for creating a workflow event context. */
|
|
43
229
|
interface TWFEventInput {
|
|
44
230
|
schemaId: string;
|
|
@@ -153,5 +339,5 @@ declare class WooksWf<T = any, IR = any> extends WooksAdapterBase {
|
|
|
153
339
|
*/
|
|
154
340
|
declare function createWfApp<T>(opts?: TWooksWfOptions, wooks?: Wooks | WooksAdapterBase): WooksWf<T, any>;
|
|
155
341
|
|
|
156
|
-
export { WooksWf, createWfApp, createWfContext, resumeKey, resumeWfContext, useWfState, wfKind, wfShortcuts };
|
|
157
|
-
export type { TWFEventInput, TWfRunOptions, TWooksWfOptions };
|
|
342
|
+
export { WooksWf, createEmailOutlet, createHttpOutlet, createOutletHandler, createWfApp, createWfContext, handleWfOutletRequest, resumeKey, resumeWfContext, useWfFinished, useWfOutlet, useWfState, wfKind, wfShortcuts };
|
|
343
|
+
export type { TWFEventInput, TWfRunOptions, TWooksWfOptions, WfFinishedResponse, WfOutletTokenConfig, WfOutletTriggerConfig, WfOutletTriggerDeps };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { createEventContext, current, defineEventKind, key, slot, useLogger, useRouteParams } from "@wooksjs/event-core";
|
|
1
|
+
import { createEventContext, current, defineEventKind, defineWook, key, slot, useLogger, useRouteParams } from "@wooksjs/event-core";
|
|
2
|
+
import { useCookies, useResponse, useUrlParams } from "@wooksjs/event-http";
|
|
3
|
+
import { useBody } from "@wooksjs/http-body";
|
|
4
|
+
import { createCipheriv, createDecipheriv, randomBytes, randomUUID } from "node:crypto";
|
|
2
5
|
import { StepRetriableError, Workflow, createStep } from "@prostojs/wf";
|
|
3
6
|
import { WooksAdapterBase } from "wooks";
|
|
4
7
|
|
|
@@ -23,17 +26,14 @@ const resumeKey = key("wf.resume");
|
|
|
23
26
|
* const stepInput = input<MyInput>()
|
|
24
27
|
* ```
|
|
25
28
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
resume: c.get(resumeKey)
|
|
35
|
-
};
|
|
36
|
-
}
|
|
29
|
+
const useWfState = defineWook((c) => ({
|
|
30
|
+
ctx: () => c.get(wfKind.keys.inputContext),
|
|
31
|
+
input: () => c.get(wfKind.keys.input),
|
|
32
|
+
schemaId: c.get(wfKind.keys.schemaId),
|
|
33
|
+
stepId: () => c.get(wfKind.keys.stepId),
|
|
34
|
+
indexes: () => c.get(wfKind.keys.indexes),
|
|
35
|
+
resume: c.get(resumeKey)
|
|
36
|
+
}));
|
|
37
37
|
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region packages/event-wf/src/event-wf.ts
|
|
@@ -52,6 +52,446 @@ function resumeWfContext(options, seeds, fn) {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region packages/event-wf/src/outlets/outlet-context.ts
|
|
57
|
+
/** Registered outlet handlers, keyed by name */
|
|
58
|
+
const outletsRegistryKey = key("wf.outlets.registry");
|
|
59
|
+
/** Active state strategy for current request */
|
|
60
|
+
const stateStrategyKey = key("wf.outlets.stateStrategy");
|
|
61
|
+
/** Finished response set by workflow steps */
|
|
62
|
+
const wfFinishedKey = key("wf.outlets.finished");
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region packages/event-wf/src/outlets/use-wf-outlet.ts
|
|
66
|
+
/**
|
|
67
|
+
* Composable for accessing outlet infrastructure from within workflow steps.
|
|
68
|
+
*
|
|
69
|
+
* Most steps don't need this — they just return `outletHttp(form)` or
|
|
70
|
+
* `outletEmail(to, template)`. This composable is for advanced cases
|
|
71
|
+
* where steps need to inspect or modify outlet state directly.
|
|
72
|
+
*/
|
|
73
|
+
const useWfOutlet = defineWook((ctx) => ({
|
|
74
|
+
getStateStrategy: () => ctx.get(stateStrategyKey),
|
|
75
|
+
getOutlets: () => ctx.get(outletsRegistryKey),
|
|
76
|
+
getOutlet: (name) => ctx.get(outletsRegistryKey)?.get(name) ?? null
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region packages/event-wf/src/outlets/use-wf-finished.ts
|
|
81
|
+
/**
|
|
82
|
+
* Composable to set the completion response for a finished workflow.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* // Redirect after login
|
|
87
|
+
* useWfFinished().set({ type: 'redirect', value: '/dashboard' })
|
|
88
|
+
*
|
|
89
|
+
* // Return data
|
|
90
|
+
* useWfFinished().set({ type: 'data', value: { success: true } })
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
const useWfFinished = defineWook((ctx) => ({
|
|
94
|
+
set: (response) => ctx.set(wfFinishedKey, response),
|
|
95
|
+
get: () => ctx.has(wfFinishedKey) ? ctx.get(wfFinishedKey) : void 0
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region packages/event-wf/src/outlets/trigger.ts
|
|
100
|
+
const DEFAULT_CONSUME_TOKEN = { email: true };
|
|
101
|
+
/**
|
|
102
|
+
* Handle an HTTP request that starts or resumes a workflow.
|
|
103
|
+
*
|
|
104
|
+
* Reads wfs (state token) and wfid (workflow ID) from request body, query params,
|
|
105
|
+
* or cookies — configurable via `config.token`. On workflow pause, persists state
|
|
106
|
+
* and dispatches to the named outlet. On finish, returns the finished response.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* // In a wooks HTTP handler:
|
|
111
|
+
* app.post('/workflow', () => handleWfOutletRequest(config, deps))
|
|
112
|
+
*
|
|
113
|
+
* // Better — use createOutletHandler():
|
|
114
|
+
* const handle = createOutletHandler(wfApp)
|
|
115
|
+
* app.post('/workflow', () => handle(config))
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async function handleWfOutletRequest(config, deps) {
|
|
119
|
+
const tok = config.token ?? {};
|
|
120
|
+
const tokenName = tok.name ?? "wfs";
|
|
121
|
+
const tokenRead = tok.read ?? [
|
|
122
|
+
"body",
|
|
123
|
+
"query",
|
|
124
|
+
"cookie"
|
|
125
|
+
];
|
|
126
|
+
const tokenWrite = tok.write ?? "body";
|
|
127
|
+
const wfidName = config.wfidName ?? "wfid";
|
|
128
|
+
const ctx = current();
|
|
129
|
+
const registry = new Map(config.outlets.map((o) => [o.name, o]));
|
|
130
|
+
ctx.set(outletsRegistryKey, registry);
|
|
131
|
+
ctx.set(wfFinishedKey, void 0);
|
|
132
|
+
const { parseBody } = useBody();
|
|
133
|
+
const { params } = useUrlParams();
|
|
134
|
+
const { getCookie } = useCookies();
|
|
135
|
+
const response = useResponse();
|
|
136
|
+
const body = await parseBody().catch(() => void 0);
|
|
137
|
+
const queryParams = params();
|
|
138
|
+
let token;
|
|
139
|
+
for (const source of tokenRead) {
|
|
140
|
+
if (source === "body") token = body?.[tokenName];
|
|
141
|
+
else if (source === "query") token = queryParams.get(tokenName) ?? void 0;
|
|
142
|
+
else if (source === "cookie") token = getCookie(tokenName) ?? void 0;
|
|
143
|
+
if (token) break;
|
|
144
|
+
}
|
|
145
|
+
const wfid = body?.[wfidName] ?? queryParams.get(wfidName) ?? void 0;
|
|
146
|
+
const input = body?.input;
|
|
147
|
+
const resolveStrategy = (id) => typeof config.state === "function" ? config.state(id) : config.state;
|
|
148
|
+
const shouldConsume = (outletName) => {
|
|
149
|
+
if (typeof tok.consume === "boolean") return tok.consume;
|
|
150
|
+
return (tok.consume ?? DEFAULT_CONSUME_TOKEN)[outletName] ?? false;
|
|
151
|
+
};
|
|
152
|
+
let output;
|
|
153
|
+
if (token) {
|
|
154
|
+
const strategy = resolveStrategy(wfid ?? "");
|
|
155
|
+
ctx.set(stateStrategyKey, strategy);
|
|
156
|
+
const state = await strategy.retrieve(token);
|
|
157
|
+
if (!state) return {
|
|
158
|
+
error: "Invalid or expired workflow state",
|
|
159
|
+
status: 400
|
|
160
|
+
};
|
|
161
|
+
if (state.schemaId !== (wfid ?? "")) {
|
|
162
|
+
const realStrategy = resolveStrategy(state.schemaId);
|
|
163
|
+
ctx.set(stateStrategyKey, realStrategy);
|
|
164
|
+
}
|
|
165
|
+
const outletName = state.meta?.outlet;
|
|
166
|
+
if (outletName && shouldConsume(outletName)) await strategy.consume(token);
|
|
167
|
+
output = await deps.resume(state, {
|
|
168
|
+
input,
|
|
169
|
+
eventContext: ctx
|
|
170
|
+
});
|
|
171
|
+
} else if (wfid) {
|
|
172
|
+
if (config.allow?.length && !config.allow.includes(wfid)) return {
|
|
173
|
+
error: `Workflow '${wfid}' is not allowed`,
|
|
174
|
+
status: 403
|
|
175
|
+
};
|
|
176
|
+
if (config.block?.includes(wfid)) return {
|
|
177
|
+
error: `Workflow '${wfid}' is blocked`,
|
|
178
|
+
status: 403
|
|
179
|
+
};
|
|
180
|
+
const strategy = resolveStrategy(wfid);
|
|
181
|
+
ctx.set(stateStrategyKey, strategy);
|
|
182
|
+
const initialContext = config.initialContext ? config.initialContext(body, wfid) : {};
|
|
183
|
+
output = await deps.start(wfid, initialContext, {
|
|
184
|
+
input,
|
|
185
|
+
eventContext: ctx
|
|
186
|
+
});
|
|
187
|
+
} else return {
|
|
188
|
+
error: "Missing wfs (state token) or wfid (workflow ID)",
|
|
189
|
+
status: 400
|
|
190
|
+
};
|
|
191
|
+
if (output.finished) {
|
|
192
|
+
if (config.onFinished) return config.onFinished({
|
|
193
|
+
context: output.state.context,
|
|
194
|
+
schemaId: output.state.schemaId
|
|
195
|
+
});
|
|
196
|
+
const finished = ctx.get(wfFinishedKey);
|
|
197
|
+
if (finished?.cookies) for (const [name, cookie] of Object.entries(finished.cookies)) response.setCookie(name, cookie.value, cookie.options);
|
|
198
|
+
if (finished?.type === "redirect") {
|
|
199
|
+
response.setHeader("location", finished.value);
|
|
200
|
+
return { status: finished.status ?? 302 };
|
|
201
|
+
}
|
|
202
|
+
if (finished) return finished.value;
|
|
203
|
+
return { finished: true };
|
|
204
|
+
}
|
|
205
|
+
if (output.inputRequired) {
|
|
206
|
+
const outletReq = output.inputRequired;
|
|
207
|
+
const outletHandler = registry.get(outletReq.outlet);
|
|
208
|
+
if (!outletHandler) return {
|
|
209
|
+
error: `Unknown outlet: '${outletReq.outlet}'`,
|
|
210
|
+
status: 500
|
|
211
|
+
};
|
|
212
|
+
const strategy = ctx.get(stateStrategyKey);
|
|
213
|
+
const stateWithMeta = {
|
|
214
|
+
...output.state,
|
|
215
|
+
meta: { outlet: outletReq.outlet }
|
|
216
|
+
};
|
|
217
|
+
const newToken = await strategy.persist(stateWithMeta, output.expires ? { ttl: output.expires - Date.now() } : void 0);
|
|
218
|
+
if (tokenWrite === "cookie") response.setCookie(tokenName, newToken, {
|
|
219
|
+
httpOnly: true,
|
|
220
|
+
sameSite: "Strict",
|
|
221
|
+
path: "/"
|
|
222
|
+
});
|
|
223
|
+
const result = await outletHandler.deliver(outletReq, newToken);
|
|
224
|
+
if (tokenWrite === "body" && result?.response && typeof result.response === "object") return {
|
|
225
|
+
...result.response,
|
|
226
|
+
[tokenName]: newToken
|
|
227
|
+
};
|
|
228
|
+
return result?.response ?? { waiting: true };
|
|
229
|
+
}
|
|
230
|
+
if (output.error) return {
|
|
231
|
+
error: output.error.message,
|
|
232
|
+
errorList: output.errorList
|
|
233
|
+
};
|
|
234
|
+
return { error: "Unexpected workflow state" };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region packages/event-wf/src/outlets/create-outlet.ts
|
|
239
|
+
/**
|
|
240
|
+
* Creates an HTTP outlet that passes through the outlet payload as
|
|
241
|
+
* the HTTP response body. This is the most common outlet — it returns
|
|
242
|
+
* forms, prompts, or data to the client.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* const httpOutlet = createHttpOutlet()
|
|
247
|
+
* // Step does: return outletHttp({ fields: ['email', 'password'] })
|
|
248
|
+
* // Client receives: { fields: ['email', 'password'] }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
function createHttpOutlet(opts) {
|
|
252
|
+
return {
|
|
253
|
+
name: "http",
|
|
254
|
+
async deliver(request, _token) {
|
|
255
|
+
const body = opts?.transform ? opts.transform(request.payload, request.context) : typeof request.payload === "object" && request.payload !== null ? {
|
|
256
|
+
...request.payload,
|
|
257
|
+
...request.context
|
|
258
|
+
} : request.payload;
|
|
259
|
+
return { response: body };
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Creates an email outlet that delegates to a user-provided send function.
|
|
265
|
+
* The send function receives the target, template, context, and the state
|
|
266
|
+
* token (for embedding in magic links / verification URLs).
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const emailOutlet = createEmailOutlet(async (opts) => {
|
|
271
|
+
* await mailer.send({
|
|
272
|
+
* to: opts.target,
|
|
273
|
+
* template: opts.template,
|
|
274
|
+
* data: { ...opts.context, verifyUrl: `/verify?wfs=${opts.token}` },
|
|
275
|
+
* })
|
|
276
|
+
* })
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
function createEmailOutlet(send) {
|
|
280
|
+
return {
|
|
281
|
+
name: "email",
|
|
282
|
+
async deliver(request, token) {
|
|
283
|
+
await send({
|
|
284
|
+
target: request.target ?? "",
|
|
285
|
+
template: request.template ?? "",
|
|
286
|
+
context: request.context ?? {},
|
|
287
|
+
token
|
|
288
|
+
});
|
|
289
|
+
return { response: {
|
|
290
|
+
sent: true,
|
|
291
|
+
outlet: "email"
|
|
292
|
+
} };
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region packages/event-wf/src/outlets/create-handler.ts
|
|
299
|
+
/**
|
|
300
|
+
* Creates a pre-wired outlet handler from a workflow app instance.
|
|
301
|
+
* Eliminates the need to manually construct `WfOutletTriggerDeps`.
|
|
302
|
+
*
|
|
303
|
+
* Accepts any object with `start` and `resume` methods (WooksWf, MoostWf, etc.).
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const handle = createOutletHandler(wfApp)
|
|
308
|
+
* httpApp.post('/workflow', () => handle(config))
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
function createOutletHandler(wfApp) {
|
|
312
|
+
return (config) => handleWfOutletRequest(config, {
|
|
313
|
+
start: (schemaId, context, opts) => wfApp.start(schemaId, context, opts),
|
|
314
|
+
resume: (state, opts) => wfApp.resume(state, opts)
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region node_modules/.pnpm/@prostojs+wf@0.1.1/node_modules/@prostojs/wf/dist/outlets/index.mjs
|
|
320
|
+
/**
|
|
321
|
+
* Generic outlet request. Use for custom outlets.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* return outlet('pending-task', {
|
|
325
|
+
* payload: ApprovalForm,
|
|
326
|
+
* target: managerId,
|
|
327
|
+
* context: { orderId, amount },
|
|
328
|
+
* })
|
|
329
|
+
*/
|
|
330
|
+
function outlet(name, data) {
|
|
331
|
+
return { inputRequired: {
|
|
332
|
+
outlet: name,
|
|
333
|
+
...data
|
|
334
|
+
} };
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Pause for HTTP form input. The outlet returns the payload (form definition)
|
|
338
|
+
* and state token in the HTTP response.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* return outletHttp(LoginForm)
|
|
342
|
+
* return outletHttp(LoginForm, { error: 'Invalid credentials' })
|
|
343
|
+
*/
|
|
344
|
+
function outletHttp(payload, context) {
|
|
345
|
+
return outlet("http", {
|
|
346
|
+
payload,
|
|
347
|
+
context
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Pause and send email with a magic link containing the state token.
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* return outletEmail('user@test.com', 'invite', { name: 'Alice' })
|
|
355
|
+
*/
|
|
356
|
+
function outletEmail(target, template, context) {
|
|
357
|
+
return outlet("email", {
|
|
358
|
+
target,
|
|
359
|
+
template,
|
|
360
|
+
context
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Self-contained AES-256-GCM encrypted state strategy.
|
|
365
|
+
*
|
|
366
|
+
* Workflow state is encrypted into a base64url token that travels with the
|
|
367
|
+
* transport (cookie, URL param, hidden field). No server-side storage needed.
|
|
368
|
+
*
|
|
369
|
+
* Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* const strategy = new EncapsulatedStateStrategy({
|
|
373
|
+
* secret: crypto.randomBytes(32),
|
|
374
|
+
* defaultTtl: 3600_000, // 1 hour
|
|
375
|
+
* });
|
|
376
|
+
* const token = await strategy.persist(state);
|
|
377
|
+
* const recovered = await strategy.retrieve(token);
|
|
378
|
+
*/
|
|
379
|
+
var EncapsulatedStateStrategy = class {
|
|
380
|
+
/** @throws if secret is not exactly 32 bytes */
|
|
381
|
+
constructor(config) {
|
|
382
|
+
this.config = config;
|
|
383
|
+
this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
|
|
384
|
+
if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Encrypt workflow state into a self-contained token.
|
|
388
|
+
* @param state — workflow state to persist
|
|
389
|
+
* @param options.ttl — time-to-live in ms (overrides defaultTtl)
|
|
390
|
+
* @returns base64url-encoded encrypted token
|
|
391
|
+
*/
|
|
392
|
+
async persist(state, options) {
|
|
393
|
+
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
394
|
+
const exp = ttl > 0 ? Date.now() + ttl : 0;
|
|
395
|
+
const payload = JSON.stringify({
|
|
396
|
+
s: state,
|
|
397
|
+
e: exp
|
|
398
|
+
});
|
|
399
|
+
const iv = randomBytes(12);
|
|
400
|
+
const cipher = createCipheriv("aes-256-gcm", this.key, iv);
|
|
401
|
+
const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
|
|
402
|
+
const tag = cipher.getAuthTag();
|
|
403
|
+
return Buffer.concat([
|
|
404
|
+
iv,
|
|
405
|
+
tag,
|
|
406
|
+
encrypted
|
|
407
|
+
]).toString("base64url");
|
|
408
|
+
}
|
|
409
|
+
/** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
|
|
410
|
+
async retrieve(token) {
|
|
411
|
+
return this.decrypt(token);
|
|
412
|
+
}
|
|
413
|
+
/** Same as retrieve (stateless — cannot truly invalidate a token). */
|
|
414
|
+
async consume(token) {
|
|
415
|
+
return this.decrypt(token);
|
|
416
|
+
}
|
|
417
|
+
decrypt(token) {
|
|
418
|
+
try {
|
|
419
|
+
const buf = Buffer.from(token, "base64url");
|
|
420
|
+
if (buf.length < 28) return null;
|
|
421
|
+
const iv = buf.subarray(0, 12);
|
|
422
|
+
const tag = buf.subarray(12, 28);
|
|
423
|
+
const ciphertext = buf.subarray(28);
|
|
424
|
+
const decipher = createDecipheriv("aes-256-gcm", this.key, iv);
|
|
425
|
+
decipher.setAuthTag(tag);
|
|
426
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
427
|
+
const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
|
|
428
|
+
if (exp > 0 && Date.now() > exp) return null;
|
|
429
|
+
return state;
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
var HandleStateStrategy = class {
|
|
436
|
+
constructor(config) {
|
|
437
|
+
this.config = config;
|
|
438
|
+
}
|
|
439
|
+
async persist(state, options) {
|
|
440
|
+
const handle = (this.config.generateHandle ?? randomUUID)();
|
|
441
|
+
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
442
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
|
|
443
|
+
await this.config.store.set(handle, state, expiresAt);
|
|
444
|
+
return handle;
|
|
445
|
+
}
|
|
446
|
+
async retrieve(token) {
|
|
447
|
+
return (await this.config.store.get(token))?.state ?? null;
|
|
448
|
+
}
|
|
449
|
+
async consume(token) {
|
|
450
|
+
return (await this.config.store.getAndDelete(token))?.state ?? null;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* In-memory state store for development and testing.
|
|
455
|
+
* State is lost on process restart.
|
|
456
|
+
*/
|
|
457
|
+
var WfStateStoreMemory = class {
|
|
458
|
+
constructor() {
|
|
459
|
+
this.store = /* @__PURE__ */ new Map();
|
|
460
|
+
}
|
|
461
|
+
async set(handle, state, expiresAt) {
|
|
462
|
+
this.store.set(handle, {
|
|
463
|
+
state,
|
|
464
|
+
expiresAt
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
async get(handle) {
|
|
468
|
+
const entry = this.store.get(handle);
|
|
469
|
+
if (!entry) return null;
|
|
470
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
471
|
+
this.store.delete(handle);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
return entry;
|
|
475
|
+
}
|
|
476
|
+
async delete(handle) {
|
|
477
|
+
this.store.delete(handle);
|
|
478
|
+
}
|
|
479
|
+
async getAndDelete(handle) {
|
|
480
|
+
const entry = await this.get(handle);
|
|
481
|
+
if (entry) this.store.delete(handle);
|
|
482
|
+
return entry;
|
|
483
|
+
}
|
|
484
|
+
async cleanup() {
|
|
485
|
+
const now = Date.now();
|
|
486
|
+
let count = 0;
|
|
487
|
+
for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
|
|
488
|
+
this.store.delete(handle);
|
|
489
|
+
count++;
|
|
490
|
+
}
|
|
491
|
+
return count;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
55
495
|
//#endregion
|
|
56
496
|
//#region packages/event-wf/src/workflow.ts
|
|
57
497
|
/** Workflow engine that resolves steps via Wooks router lookup. */
|
|
@@ -244,4 +684,4 @@ function createWfApp(opts, wooks) {
|
|
|
244
684
|
}
|
|
245
685
|
|
|
246
686
|
//#endregion
|
|
247
|
-
export { StepRetriableError, WooksWf, createWfApp, createWfContext, resumeKey, resumeWfContext, useLogger, useRouteParams, useWfState, wfKind, wfShortcuts };
|
|
687
|
+
export { EncapsulatedStateStrategy, HandleStateStrategy, StepRetriableError, WfStateStoreMemory, WooksWf, createEmailOutlet, createHttpOutlet, createOutletHandler, createWfApp, createWfContext, handleWfOutletRequest, outlet, outletEmail, outletHttp, resumeKey, resumeWfContext, useLogger, useRouteParams, useWfFinished, useWfOutlet, useWfState, wfKind, wfShortcuts };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wooksjs/event-wf",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "@wooksjs/event-wf",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -42,18 +42,30 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@prostojs/wf": "^0.
|
|
45
|
+
"@prostojs/wf": "^0.1.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"typescript": "^5.9.3",
|
|
49
49
|
"vitest": "^3.2.4",
|
|
50
|
-
"@wooksjs/event-core": "^0.7.
|
|
51
|
-
"
|
|
50
|
+
"@wooksjs/event-core": "^0.7.8",
|
|
51
|
+
"@wooksjs/event-http": "^0.7.8",
|
|
52
|
+
"wooks": "^0.7.8",
|
|
53
|
+
"@wooksjs/http-body": "^0.7.8"
|
|
52
54
|
},
|
|
53
55
|
"peerDependencies": {
|
|
54
56
|
"@prostojs/logger": "^0.4.3",
|
|
55
|
-
"@wooksjs/event-core": "^0.7.
|
|
56
|
-
"
|
|
57
|
+
"@wooksjs/event-core": "^0.7.8",
|
|
58
|
+
"@wooksjs/event-http": "^0.7.8",
|
|
59
|
+
"wooks": "^0.7.8",
|
|
60
|
+
"@wooksjs/http-body": "^0.7.8"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@wooksjs/event-http": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@wooksjs/http-body": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
57
69
|
},
|
|
58
70
|
"scripts": {
|
|
59
71
|
"build": "rolldown -c ../../rolldown.config.mjs",
|