@moostjs/event-wf 0.6.16 → 0.6.18
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 +64 -193
- package/dist/index.d.ts +13 -7
- package/dist/index.mjs +18 -189
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
let _wooksjs_event_wf = require("@wooksjs/event-wf");
|
|
3
3
|
let moost = require("moost");
|
|
4
4
|
let _prostojs_wf = require("@prostojs/wf");
|
|
5
|
-
let
|
|
5
|
+
let _prostojs_wf_outlets = require("@prostojs/wf/outlets");
|
|
6
6
|
|
|
7
7
|
//#region packages/event-wf/src/meta-types.ts
|
|
8
8
|
function getWfMate() {
|
|
@@ -127,7 +127,7 @@ const CONTEXT_TYPE = "WF";
|
|
|
127
127
|
* ```ts
|
|
128
128
|
* const wf = new MoostWf()
|
|
129
129
|
* app.adapter(wf).controllers(MyWorkflows).init()
|
|
130
|
-
* const result = await wf.start('my-flow', {}, input)
|
|
130
|
+
* const result = await wf.start('my-flow', {}, { input })
|
|
131
131
|
* ```
|
|
132
132
|
*/ var MoostWf = class {
|
|
133
133
|
async onNotFound() {
|
|
@@ -169,8 +169,8 @@ const CONTEXT_TYPE = "WF";
|
|
|
169
169
|
* @returns The outlet result (form payload + token), finished response, or error.
|
|
170
170
|
*/ handleOutlet(config) {
|
|
171
171
|
return (0, _wooksjs_event_wf.handleWfOutletRequest)(config, {
|
|
172
|
-
start: (schemaId, context, opts) => this.start(schemaId, context, opts
|
|
173
|
-
resume: (state, opts) => this.resume(state, opts
|
|
172
|
+
start: (schemaId, context, opts) => this.start(schemaId, context, opts),
|
|
173
|
+
resume: (state, opts) => this.resume(state, opts)
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
/**
|
|
@@ -178,10 +178,14 @@ const CONTEXT_TYPE = "WF";
|
|
|
178
178
|
*
|
|
179
179
|
* @param schemaId - Identifier of the registered workflow schema.
|
|
180
180
|
* @param initialContext - Initial context data for the workflow.
|
|
181
|
-
* @param
|
|
182
|
-
|
|
181
|
+
* @param opts - Optional run options: `input` for the first step, `eventContext`
|
|
182
|
+
* to link this run to a parent event scope (e.g. an HTTP request), and
|
|
183
|
+
* `strategy.name` to pick the initial state strategy. Passing a bare
|
|
184
|
+
* `input` value (legacy positional form) is no longer supported — wrap it
|
|
185
|
+
* in `{ input }`.
|
|
186
|
+
*/ start(schemaId, initialContext, opts) {
|
|
183
187
|
return this.wfApp.start(schemaId, initialContext, {
|
|
184
|
-
|
|
188
|
+
...opts,
|
|
185
189
|
cleanup: () => {
|
|
186
190
|
(0, moost.getMoostInfact)().unregisterScope((0, moost.useScopeId)());
|
|
187
191
|
}
|
|
@@ -191,10 +195,12 @@ const CONTEXT_TYPE = "WF";
|
|
|
191
195
|
* Resumes a previously paused workflow from a saved state.
|
|
192
196
|
*
|
|
193
197
|
* @param state - Saved workflow state containing schema, context, and step indexes.
|
|
194
|
-
* @param
|
|
195
|
-
|
|
198
|
+
* @param opts - Optional run options: `input` for the resumed step, `eventContext`
|
|
199
|
+
* for parent-chain composable access, and `strategy.name` for the strategy
|
|
200
|
+
* that loaded this state. Legacy positional `input` is no longer supported.
|
|
201
|
+
*/ resume(state, opts) {
|
|
196
202
|
return this.wfApp.resume(state, {
|
|
197
|
-
|
|
203
|
+
...opts,
|
|
198
204
|
cleanup: () => {
|
|
199
205
|
(0, moost.getMoostInfact)().unregisterScope((0, moost.useScopeId)());
|
|
200
206
|
}
|
|
@@ -269,185 +275,18 @@ const CONTEXT_TYPE = "WF";
|
|
|
269
275
|
};
|
|
270
276
|
|
|
271
277
|
//#endregion
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return { inputRequired: {
|
|
285
|
-
outlet: name,
|
|
286
|
-
...data
|
|
287
|
-
} };
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Pause for HTTP form input. The outlet returns the payload (form definition)
|
|
291
|
-
* and state token in the HTTP response.
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* return outletHttp(LoginForm)
|
|
295
|
-
* return outletHttp(LoginForm, { error: 'Invalid credentials' })
|
|
296
|
-
*/
|
|
297
|
-
function outletHttp(payload, context) {
|
|
298
|
-
return outlet("http", {
|
|
299
|
-
payload,
|
|
300
|
-
context
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Pause and send email with a magic link containing the state token.
|
|
305
|
-
*
|
|
306
|
-
* @example
|
|
307
|
-
* return outletEmail('user@test.com', 'invite', { name: 'Alice' })
|
|
308
|
-
*/
|
|
309
|
-
function outletEmail(target, template, context) {
|
|
310
|
-
return outlet("email", {
|
|
311
|
-
target,
|
|
312
|
-
template,
|
|
313
|
-
context
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Self-contained AES-256-GCM encrypted state strategy.
|
|
318
|
-
*
|
|
319
|
-
* Workflow state is encrypted into a base64url token that travels with the
|
|
320
|
-
* transport (cookie, URL param, hidden field). No server-side storage needed.
|
|
321
|
-
*
|
|
322
|
-
* Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
|
|
323
|
-
*
|
|
324
|
-
* @example
|
|
325
|
-
* const strategy = new EncapsulatedStateStrategy({
|
|
326
|
-
* secret: crypto.randomBytes(32),
|
|
327
|
-
* defaultTtl: 3600_000, // 1 hour
|
|
328
|
-
* });
|
|
329
|
-
* const token = await strategy.persist(state);
|
|
330
|
-
* const recovered = await strategy.retrieve(token);
|
|
331
|
-
*/
|
|
332
|
-
var EncapsulatedStateStrategy = class {
|
|
333
|
-
/** @throws if secret is not exactly 32 bytes */
|
|
334
|
-
constructor(config) {
|
|
335
|
-
this.config = config;
|
|
336
|
-
this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
|
|
337
|
-
if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Encrypt workflow state into a self-contained token.
|
|
341
|
-
* @param state — workflow state to persist
|
|
342
|
-
* @param options.ttl — time-to-live in ms (overrides defaultTtl)
|
|
343
|
-
* @returns base64url-encoded encrypted token
|
|
344
|
-
*/
|
|
345
|
-
async persist(state, options) {
|
|
346
|
-
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
347
|
-
const exp = ttl > 0 ? Date.now() + ttl : 0;
|
|
348
|
-
const payload = JSON.stringify({
|
|
349
|
-
s: state,
|
|
350
|
-
e: exp
|
|
351
|
-
});
|
|
352
|
-
const iv = (0, node_crypto.randomBytes)(12);
|
|
353
|
-
const cipher = (0, node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
354
|
-
const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
|
|
355
|
-
const tag = cipher.getAuthTag();
|
|
356
|
-
return Buffer.concat([
|
|
357
|
-
iv,
|
|
358
|
-
tag,
|
|
359
|
-
encrypted
|
|
360
|
-
]).toString("base64url");
|
|
361
|
-
}
|
|
362
|
-
/** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
|
|
363
|
-
async retrieve(token) {
|
|
364
|
-
return this.decrypt(token);
|
|
365
|
-
}
|
|
366
|
-
/** Same as retrieve (stateless — cannot truly invalidate a token). */
|
|
367
|
-
async consume(token) {
|
|
368
|
-
return this.decrypt(token);
|
|
369
|
-
}
|
|
370
|
-
decrypt(token) {
|
|
371
|
-
try {
|
|
372
|
-
const buf = Buffer.from(token, "base64url");
|
|
373
|
-
if (buf.length < 28) return null;
|
|
374
|
-
const iv = buf.subarray(0, 12);
|
|
375
|
-
const tag = buf.subarray(12, 28);
|
|
376
|
-
const ciphertext = buf.subarray(28);
|
|
377
|
-
const decipher = (0, node_crypto.createDecipheriv)("aes-256-gcm", this.key, iv);
|
|
378
|
-
decipher.setAuthTag(tag);
|
|
379
|
-
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
380
|
-
const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
|
|
381
|
-
if (exp > 0 && Date.now() > exp) return null;
|
|
382
|
-
return state;
|
|
383
|
-
} catch {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
var HandleStateStrategy = class {
|
|
389
|
-
constructor(config) {
|
|
390
|
-
this.config = config;
|
|
391
|
-
}
|
|
392
|
-
async persist(state, options) {
|
|
393
|
-
const handle = (this.config.generateHandle ?? node_crypto.randomUUID)();
|
|
394
|
-
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
395
|
-
const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
|
|
396
|
-
await this.config.store.set(handle, state, expiresAt);
|
|
397
|
-
return handle;
|
|
398
|
-
}
|
|
399
|
-
async retrieve(token) {
|
|
400
|
-
return (await this.config.store.get(token))?.state ?? null;
|
|
401
|
-
}
|
|
402
|
-
async consume(token) {
|
|
403
|
-
return (await this.config.store.getAndDelete(token))?.state ?? null;
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
/**
|
|
407
|
-
* In-memory state store for development and testing.
|
|
408
|
-
* State is lost on process restart.
|
|
409
|
-
*/
|
|
410
|
-
var WfStateStoreMemory = class {
|
|
411
|
-
constructor() {
|
|
412
|
-
this.store = /* @__PURE__ */ new Map();
|
|
413
|
-
}
|
|
414
|
-
async set(handle, state, expiresAt) {
|
|
415
|
-
this.store.set(handle, {
|
|
416
|
-
state,
|
|
417
|
-
expiresAt
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
async get(handle) {
|
|
421
|
-
const entry = this.store.get(handle);
|
|
422
|
-
if (!entry) return null;
|
|
423
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
424
|
-
this.store.delete(handle);
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
return entry;
|
|
428
|
-
}
|
|
429
|
-
async delete(handle) {
|
|
430
|
-
this.store.delete(handle);
|
|
431
|
-
}
|
|
432
|
-
async getAndDelete(handle) {
|
|
433
|
-
const entry = await this.get(handle);
|
|
434
|
-
if (entry) this.store.delete(handle);
|
|
435
|
-
return entry;
|
|
436
|
-
}
|
|
437
|
-
async cleanup() {
|
|
438
|
-
const now = Date.now();
|
|
439
|
-
let count = 0;
|
|
440
|
-
for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
|
|
441
|
-
this.store.delete(handle);
|
|
442
|
-
count++;
|
|
443
|
-
}
|
|
444
|
-
return count;
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
//#endregion
|
|
449
|
-
exports.EncapsulatedStateStrategy = EncapsulatedStateStrategy;
|
|
450
|
-
exports.HandleStateStrategy = HandleStateStrategy;
|
|
278
|
+
Object.defineProperty(exports, 'EncapsulatedStateStrategy', {
|
|
279
|
+
enumerable: true,
|
|
280
|
+
get: function () {
|
|
281
|
+
return _prostojs_wf_outlets.EncapsulatedStateStrategy;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
Object.defineProperty(exports, 'HandleStateStrategy', {
|
|
285
|
+
enumerable: true,
|
|
286
|
+
get: function () {
|
|
287
|
+
return _prostojs_wf_outlets.HandleStateStrategy;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
451
290
|
exports.MoostWf = MoostWf;
|
|
452
291
|
exports.Step = Step;
|
|
453
292
|
Object.defineProperty(exports, 'StepRetriableError', {
|
|
@@ -457,7 +296,12 @@ Object.defineProperty(exports, 'StepRetriableError', {
|
|
|
457
296
|
}
|
|
458
297
|
});
|
|
459
298
|
exports.StepTTL = StepTTL;
|
|
460
|
-
exports
|
|
299
|
+
Object.defineProperty(exports, 'WfStateStoreMemory', {
|
|
300
|
+
enumerable: true,
|
|
301
|
+
get: function () {
|
|
302
|
+
return _prostojs_wf_outlets.WfStateStoreMemory;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
461
305
|
exports.Workflow = Workflow;
|
|
462
306
|
exports.WorkflowParam = WorkflowParam;
|
|
463
307
|
exports.WorkflowSchema = WorkflowSchema;
|
|
@@ -485,9 +329,30 @@ Object.defineProperty(exports, 'handleWfOutletRequest', {
|
|
|
485
329
|
return _wooksjs_event_wf.handleWfOutletRequest;
|
|
486
330
|
}
|
|
487
331
|
});
|
|
488
|
-
exports
|
|
489
|
-
|
|
490
|
-
|
|
332
|
+
Object.defineProperty(exports, 'outlet', {
|
|
333
|
+
enumerable: true,
|
|
334
|
+
get: function () {
|
|
335
|
+
return _prostojs_wf_outlets.outlet;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
Object.defineProperty(exports, 'outletEmail', {
|
|
339
|
+
enumerable: true,
|
|
340
|
+
get: function () {
|
|
341
|
+
return _prostojs_wf_outlets.outletEmail;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
Object.defineProperty(exports, 'outletHttp', {
|
|
345
|
+
enumerable: true,
|
|
346
|
+
get: function () {
|
|
347
|
+
return _prostojs_wf_outlets.outletHttp;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
Object.defineProperty(exports, 'swapStrategy', {
|
|
351
|
+
enumerable: true,
|
|
352
|
+
get: function () {
|
|
353
|
+
return _wooksjs_event_wf.swapStrategy;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
491
356
|
Object.defineProperty(exports, 'useWfFinished', {
|
|
492
357
|
enumerable: true,
|
|
493
358
|
get: function () {
|
|
@@ -506,6 +371,12 @@ Object.defineProperty(exports, 'useWfState', {
|
|
|
506
371
|
return _wooksjs_event_wf.useWfState;
|
|
507
372
|
}
|
|
508
373
|
});
|
|
374
|
+
Object.defineProperty(exports, 'useWfStrategy', {
|
|
375
|
+
enumerable: true,
|
|
376
|
+
get: function () {
|
|
377
|
+
return _wooksjs_event_wf.useWfStrategy;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
509
380
|
Object.defineProperty(exports, 'wfKind', {
|
|
510
381
|
enumerable: true,
|
|
511
382
|
get: function () {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TWorkflowSchema, TWorkflowSpy, TFlowOutput } from '@prostojs/wf';
|
|
2
2
|
export { StepRetriableError, TFlowOutput, TWorkflowSchema } from '@prostojs/wf';
|
|
3
|
-
import { WooksWf, TWooksWfOptions, WfOutletTriggerConfig } from '@wooksjs/event-wf';
|
|
4
|
-
export { WfFinishedResponse, WfOutletTokenConfig, WfOutletTriggerConfig, WfOutletTriggerDeps, createEmailOutlet, createHttpOutlet, createOutletHandler, handleWfOutletRequest, useWfFinished, useWfOutlet, useWfState, wfKind } from '@wooksjs/event-wf';
|
|
3
|
+
import { WooksWf, TWooksWfOptions, WfOutletTriggerConfig, TWfRunOptions } from '@wooksjs/event-wf';
|
|
4
|
+
export { WfFinishedResponse, WfOutletTokenConfig, WfOutletTriggerConfig, WfOutletTriggerDeps, WfPauseRequest, createEmailOutlet, createHttpOutlet, createOutletHandler, handleWfOutletRequest, swapStrategy, useWfFinished, useWfOutlet, useWfState, useWfStrategy, wfKind } from '@wooksjs/event-wf';
|
|
5
5
|
import { TMoostAdapter, Moost, TMoostAdapterOptions } from 'moost';
|
|
6
6
|
export { EncapsulatedStateStrategy, HandleStateStrategy, WfOutlet, WfOutletRequest, WfOutletResult, WfState, WfStateStore, WfStateStoreMemory, WfStateStrategy, outlet, outletEmail, outletHttp } from '@prostojs/wf/outlets';
|
|
7
7
|
|
|
@@ -91,7 +91,7 @@ interface TWfHandlerMeta {
|
|
|
91
91
|
* ```ts
|
|
92
92
|
* const wf = new MoostWf()
|
|
93
93
|
* app.adapter(wf).controllers(MyWorkflows).init()
|
|
94
|
-
* const result = await wf.start('my-flow', {}, input)
|
|
94
|
+
* const result = await wf.start('my-flow', {}, { input })
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
97
97
|
declare class MoostWf<T = any, IR = any> implements TMoostAdapter<TWfHandlerMeta> {
|
|
@@ -128,20 +128,26 @@ declare class MoostWf<T = any, IR = any> implements TMoostAdapter<TWfHandlerMeta
|
|
|
128
128
|
*
|
|
129
129
|
* @param schemaId - Identifier of the registered workflow schema.
|
|
130
130
|
* @param initialContext - Initial context data for the workflow.
|
|
131
|
-
* @param
|
|
131
|
+
* @param opts - Optional run options: `input` for the first step, `eventContext`
|
|
132
|
+
* to link this run to a parent event scope (e.g. an HTTP request), and
|
|
133
|
+
* `strategy.name` to pick the initial state strategy. Passing a bare
|
|
134
|
+
* `input` value (legacy positional form) is no longer supported — wrap it
|
|
135
|
+
* in `{ input }`.
|
|
132
136
|
*/
|
|
133
|
-
start<I>(schemaId: string, initialContext: T,
|
|
137
|
+
start<I>(schemaId: string, initialContext: T, opts?: TWfRunOptions<I, T, IR>): Promise<TFlowOutput<T, I, IR>>;
|
|
134
138
|
/**
|
|
135
139
|
* Resumes a previously paused workflow from a saved state.
|
|
136
140
|
*
|
|
137
141
|
* @param state - Saved workflow state containing schema, context, and step indexes.
|
|
138
|
-
* @param
|
|
142
|
+
* @param opts - Optional run options: `input` for the resumed step, `eventContext`
|
|
143
|
+
* for parent-chain composable access, and `strategy.name` for the strategy
|
|
144
|
+
* that loaded this state. Legacy positional `input` is no longer supported.
|
|
139
145
|
*/
|
|
140
146
|
resume<I>(state: {
|
|
141
147
|
schemaId: string;
|
|
142
148
|
context: T;
|
|
143
149
|
indexes: number[];
|
|
144
|
-
},
|
|
150
|
+
}, opts?: TWfRunOptions<I, T, IR>): Promise<TFlowOutput<T, I, IR>>;
|
|
145
151
|
bindHandler<T extends object = object>(opts: TMoostAdapterOptions<TWfHandlerMeta, T>): void;
|
|
146
152
|
}
|
|
147
153
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { WooksWf, createEmailOutlet, createHttpOutlet, createOutletHandler, createWfApp, handleWfOutletRequest, handleWfOutletRequest as handleWfOutletRequest$1, useWfFinished, useWfOutlet, useWfState, useWfState as useWfState$1, wfKind } from "@wooksjs/event-wf";
|
|
1
|
+
import { WooksWf, createEmailOutlet, createHttpOutlet, createOutletHandler, createWfApp, handleWfOutletRequest, handleWfOutletRequest as handleWfOutletRequest$1, swapStrategy, useWfFinished, useWfOutlet, useWfState, useWfState as useWfState$1, useWfStrategy, wfKind } from "@wooksjs/event-wf";
|
|
2
2
|
import { Resolve, defineMoostEventHandler, getMoostInfact, getMoostMate, setControllerContext, useScopeId } from "moost";
|
|
3
3
|
import { StepRetriableError } from "@prostojs/wf";
|
|
4
|
-
import {
|
|
4
|
+
import { EncapsulatedStateStrategy, HandleStateStrategy, WfStateStoreMemory, outlet, outletEmail, outletHttp } from "@prostojs/wf/outlets";
|
|
5
5
|
|
|
6
6
|
//#region packages/event-wf/src/meta-types.ts
|
|
7
7
|
function getWfMate() {
|
|
@@ -126,7 +126,7 @@ const CONTEXT_TYPE = "WF";
|
|
|
126
126
|
* ```ts
|
|
127
127
|
* const wf = new MoostWf()
|
|
128
128
|
* app.adapter(wf).controllers(MyWorkflows).init()
|
|
129
|
-
* const result = await wf.start('my-flow', {}, input)
|
|
129
|
+
* const result = await wf.start('my-flow', {}, { input })
|
|
130
130
|
* ```
|
|
131
131
|
*/ var MoostWf = class {
|
|
132
132
|
async onNotFound() {
|
|
@@ -168,8 +168,8 @@ const CONTEXT_TYPE = "WF";
|
|
|
168
168
|
* @returns The outlet result (form payload + token), finished response, or error.
|
|
169
169
|
*/ handleOutlet(config) {
|
|
170
170
|
return handleWfOutletRequest$1(config, {
|
|
171
|
-
start: (schemaId, context, opts) => this.start(schemaId, context, opts
|
|
172
|
-
resume: (state, opts) => this.resume(state, opts
|
|
171
|
+
start: (schemaId, context, opts) => this.start(schemaId, context, opts),
|
|
172
|
+
resume: (state, opts) => this.resume(state, opts)
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
@@ -177,10 +177,14 @@ const CONTEXT_TYPE = "WF";
|
|
|
177
177
|
*
|
|
178
178
|
* @param schemaId - Identifier of the registered workflow schema.
|
|
179
179
|
* @param initialContext - Initial context data for the workflow.
|
|
180
|
-
* @param
|
|
181
|
-
|
|
180
|
+
* @param opts - Optional run options: `input` for the first step, `eventContext`
|
|
181
|
+
* to link this run to a parent event scope (e.g. an HTTP request), and
|
|
182
|
+
* `strategy.name` to pick the initial state strategy. Passing a bare
|
|
183
|
+
* `input` value (legacy positional form) is no longer supported — wrap it
|
|
184
|
+
* in `{ input }`.
|
|
185
|
+
*/ start(schemaId, initialContext, opts) {
|
|
182
186
|
return this.wfApp.start(schemaId, initialContext, {
|
|
183
|
-
|
|
187
|
+
...opts,
|
|
184
188
|
cleanup: () => {
|
|
185
189
|
getMoostInfact().unregisterScope(useScopeId());
|
|
186
190
|
}
|
|
@@ -190,10 +194,12 @@ const CONTEXT_TYPE = "WF";
|
|
|
190
194
|
* Resumes a previously paused workflow from a saved state.
|
|
191
195
|
*
|
|
192
196
|
* @param state - Saved workflow state containing schema, context, and step indexes.
|
|
193
|
-
* @param
|
|
194
|
-
|
|
197
|
+
* @param opts - Optional run options: `input` for the resumed step, `eventContext`
|
|
198
|
+
* for parent-chain composable access, and `strategy.name` for the strategy
|
|
199
|
+
* that loaded this state. Legacy positional `input` is no longer supported.
|
|
200
|
+
*/ resume(state, opts) {
|
|
195
201
|
return this.wfApp.resume(state, {
|
|
196
|
-
|
|
202
|
+
...opts,
|
|
197
203
|
cleanup: () => {
|
|
198
204
|
getMoostInfact().unregisterScope(useScopeId());
|
|
199
205
|
}
|
|
@@ -268,181 +274,4 @@ const CONTEXT_TYPE = "WF";
|
|
|
268
274
|
};
|
|
269
275
|
|
|
270
276
|
//#endregion
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Generic outlet request. Use for custom outlets.
|
|
274
|
-
*
|
|
275
|
-
* @example
|
|
276
|
-
* return outlet('pending-task', {
|
|
277
|
-
* payload: ApprovalForm,
|
|
278
|
-
* target: managerId,
|
|
279
|
-
* context: { orderId, amount },
|
|
280
|
-
* })
|
|
281
|
-
*/
|
|
282
|
-
function outlet(name, data) {
|
|
283
|
-
return { inputRequired: {
|
|
284
|
-
outlet: name,
|
|
285
|
-
...data
|
|
286
|
-
} };
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Pause for HTTP form input. The outlet returns the payload (form definition)
|
|
290
|
-
* and state token in the HTTP response.
|
|
291
|
-
*
|
|
292
|
-
* @example
|
|
293
|
-
* return outletHttp(LoginForm)
|
|
294
|
-
* return outletHttp(LoginForm, { error: 'Invalid credentials' })
|
|
295
|
-
*/
|
|
296
|
-
function outletHttp(payload, context) {
|
|
297
|
-
return outlet("http", {
|
|
298
|
-
payload,
|
|
299
|
-
context
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Pause and send email with a magic link containing the state token.
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* return outletEmail('user@test.com', 'invite', { name: 'Alice' })
|
|
307
|
-
*/
|
|
308
|
-
function outletEmail(target, template, context) {
|
|
309
|
-
return outlet("email", {
|
|
310
|
-
target,
|
|
311
|
-
template,
|
|
312
|
-
context
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Self-contained AES-256-GCM encrypted state strategy.
|
|
317
|
-
*
|
|
318
|
-
* Workflow state is encrypted into a base64url token that travels with the
|
|
319
|
-
* transport (cookie, URL param, hidden field). No server-side storage needed.
|
|
320
|
-
*
|
|
321
|
-
* Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* const strategy = new EncapsulatedStateStrategy({
|
|
325
|
-
* secret: crypto.randomBytes(32),
|
|
326
|
-
* defaultTtl: 3600_000, // 1 hour
|
|
327
|
-
* });
|
|
328
|
-
* const token = await strategy.persist(state);
|
|
329
|
-
* const recovered = await strategy.retrieve(token);
|
|
330
|
-
*/
|
|
331
|
-
var EncapsulatedStateStrategy = class {
|
|
332
|
-
/** @throws if secret is not exactly 32 bytes */
|
|
333
|
-
constructor(config) {
|
|
334
|
-
this.config = config;
|
|
335
|
-
this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
|
|
336
|
-
if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Encrypt workflow state into a self-contained token.
|
|
340
|
-
* @param state — workflow state to persist
|
|
341
|
-
* @param options.ttl — time-to-live in ms (overrides defaultTtl)
|
|
342
|
-
* @returns base64url-encoded encrypted token
|
|
343
|
-
*/
|
|
344
|
-
async persist(state, options) {
|
|
345
|
-
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
346
|
-
const exp = ttl > 0 ? Date.now() + ttl : 0;
|
|
347
|
-
const payload = JSON.stringify({
|
|
348
|
-
s: state,
|
|
349
|
-
e: exp
|
|
350
|
-
});
|
|
351
|
-
const iv = randomBytes(12);
|
|
352
|
-
const cipher = createCipheriv("aes-256-gcm", this.key, iv);
|
|
353
|
-
const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
|
|
354
|
-
const tag = cipher.getAuthTag();
|
|
355
|
-
return Buffer.concat([
|
|
356
|
-
iv,
|
|
357
|
-
tag,
|
|
358
|
-
encrypted
|
|
359
|
-
]).toString("base64url");
|
|
360
|
-
}
|
|
361
|
-
/** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
|
|
362
|
-
async retrieve(token) {
|
|
363
|
-
return this.decrypt(token);
|
|
364
|
-
}
|
|
365
|
-
/** Same as retrieve (stateless — cannot truly invalidate a token). */
|
|
366
|
-
async consume(token) {
|
|
367
|
-
return this.decrypt(token);
|
|
368
|
-
}
|
|
369
|
-
decrypt(token) {
|
|
370
|
-
try {
|
|
371
|
-
const buf = Buffer.from(token, "base64url");
|
|
372
|
-
if (buf.length < 28) return null;
|
|
373
|
-
const iv = buf.subarray(0, 12);
|
|
374
|
-
const tag = buf.subarray(12, 28);
|
|
375
|
-
const ciphertext = buf.subarray(28);
|
|
376
|
-
const decipher = createDecipheriv("aes-256-gcm", this.key, iv);
|
|
377
|
-
decipher.setAuthTag(tag);
|
|
378
|
-
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
379
|
-
const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
|
|
380
|
-
if (exp > 0 && Date.now() > exp) return null;
|
|
381
|
-
return state;
|
|
382
|
-
} catch {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
var HandleStateStrategy = class {
|
|
388
|
-
constructor(config) {
|
|
389
|
-
this.config = config;
|
|
390
|
-
}
|
|
391
|
-
async persist(state, options) {
|
|
392
|
-
const handle = (this.config.generateHandle ?? randomUUID)();
|
|
393
|
-
const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
|
|
394
|
-
const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
|
|
395
|
-
await this.config.store.set(handle, state, expiresAt);
|
|
396
|
-
return handle;
|
|
397
|
-
}
|
|
398
|
-
async retrieve(token) {
|
|
399
|
-
return (await this.config.store.get(token))?.state ?? null;
|
|
400
|
-
}
|
|
401
|
-
async consume(token) {
|
|
402
|
-
return (await this.config.store.getAndDelete(token))?.state ?? null;
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
/**
|
|
406
|
-
* In-memory state store for development and testing.
|
|
407
|
-
* State is lost on process restart.
|
|
408
|
-
*/
|
|
409
|
-
var WfStateStoreMemory = class {
|
|
410
|
-
constructor() {
|
|
411
|
-
this.store = /* @__PURE__ */ new Map();
|
|
412
|
-
}
|
|
413
|
-
async set(handle, state, expiresAt) {
|
|
414
|
-
this.store.set(handle, {
|
|
415
|
-
state,
|
|
416
|
-
expiresAt
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
async get(handle) {
|
|
420
|
-
const entry = this.store.get(handle);
|
|
421
|
-
if (!entry) return null;
|
|
422
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
423
|
-
this.store.delete(handle);
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
return entry;
|
|
427
|
-
}
|
|
428
|
-
async delete(handle) {
|
|
429
|
-
this.store.delete(handle);
|
|
430
|
-
}
|
|
431
|
-
async getAndDelete(handle) {
|
|
432
|
-
const entry = await this.get(handle);
|
|
433
|
-
if (entry) this.store.delete(handle);
|
|
434
|
-
return entry;
|
|
435
|
-
}
|
|
436
|
-
async cleanup() {
|
|
437
|
-
const now = Date.now();
|
|
438
|
-
let count = 0;
|
|
439
|
-
for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
|
|
440
|
-
this.store.delete(handle);
|
|
441
|
-
count++;
|
|
442
|
-
}
|
|
443
|
-
return count;
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
//#endregion
|
|
448
|
-
export { EncapsulatedStateStrategy, HandleStateStrategy, MoostWf, Step, StepRetriableError, StepTTL, WfStateStoreMemory, Workflow, WorkflowParam, WorkflowSchema, createEmailOutlet, createHttpOutlet, createOutletHandler, handleWfOutletRequest, outlet, outletEmail, outletHttp, useWfFinished, useWfOutlet, useWfState, wfKind };
|
|
277
|
+
export { EncapsulatedStateStrategy, HandleStateStrategy, MoostWf, Step, StepRetriableError, StepTTL, WfStateStoreMemory, Workflow, WorkflowParam, WorkflowSchema, createEmailOutlet, createHttpOutlet, createOutletHandler, handleWfOutletRequest, outlet, outletEmail, outletHttp, swapStrategy, useWfFinished, useWfOutlet, useWfState, useWfStrategy, wfKind };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/event-wf",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.18",
|
|
4
4
|
"description": "@moostjs/event-wf",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@prostojs/wf": "^0.
|
|
42
|
-
"@wooksjs/event-wf": "^0.7.
|
|
41
|
+
"@prostojs/wf": "^0.2.1",
|
|
42
|
+
"@wooksjs/event-wf": "^0.7.16"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "3.2.4"
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@prostojs/infact": "^0.4.1",
|
|
49
49
|
"@prostojs/mate": "^0.4.0",
|
|
50
|
-
"@wooksjs/event-core": "^0.7.
|
|
51
|
-
"wooks": "^0.7.
|
|
52
|
-
"moost": "^0.6.
|
|
50
|
+
"@wooksjs/event-core": "^0.7.16",
|
|
51
|
+
"wooks": "^0.7.16",
|
|
52
|
+
"moost": "^0.6.18"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"pub": "pnpm publish --access public",
|