@wooksjs/event-wf 0.7.14 → 0.7.15

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 CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  let _wooksjs_event_core = require("@wooksjs/event-core");
3
3
  let _wooksjs_event_http = require("@wooksjs/event-http");
4
4
  let _wooksjs_http_body = require("@wooksjs/http-body");
5
- let node_crypto = require("node:crypto");
5
+ let _prostojs_wf_outlets = require("@prostojs/wf/outlets");
6
6
  let _prostojs_wf = require("@prostojs/wf");
7
7
  let wooks = require("wooks");
8
8
 
@@ -320,219 +320,6 @@ function createOutletHandler(wfApp) {
320
320
  });
321
321
  }
322
322
 
323
- //#endregion
324
- //#region node_modules/.pnpm/@prostojs+wf@0.2.1/node_modules/@prostojs/wf/dist/outlets/index.mjs
325
- /**
326
- * Generic outlet request. Use for custom outlets.
327
- *
328
- * @example
329
- * return outlet('pending-task', {
330
- * payload: ApprovalForm,
331
- * target: managerId,
332
- * context: { orderId, amount },
333
- * })
334
- */
335
- function outlet(name, data) {
336
- return { inputRequired: {
337
- outlet: name,
338
- ...data
339
- } };
340
- }
341
- /**
342
- * Pause for HTTP form input. The outlet returns the payload (form definition)
343
- * and state token in the HTTP response.
344
- *
345
- * @example
346
- * return outletHttp(LoginForm)
347
- * return outletHttp(LoginForm, { error: 'Invalid credentials' })
348
- */
349
- function outletHttp(payload, context) {
350
- return outlet("http", {
351
- payload,
352
- context
353
- });
354
- }
355
- /**
356
- * Pause and send email with a magic link containing the state token.
357
- *
358
- * @example
359
- * return outletEmail('user@test.com', 'invite', { name: 'Alice' })
360
- */
361
- function outletEmail(target, template, context) {
362
- return outlet("email", {
363
- target,
364
- template,
365
- context
366
- });
367
- }
368
- /**
369
- * Self-contained AES-256-GCM encrypted state strategy.
370
- *
371
- * Workflow state is encrypted into a base64url token that travels with the
372
- * transport (cookie, URL param, hidden field). No server-side storage needed.
373
- *
374
- * Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
375
- *
376
- * ## Security warning — replay
377
- *
378
- * This strategy is STATELESS. It cannot enforce single-use semantics:
379
- * `consume()` is a no-op alias for `retrieve()` because there is no
380
- * server-side record to delete. Anyone who obtains a copy of the token
381
- * (browser history, server logs, shoulder-surfing, intermediate proxy,
382
- * shared device) can replay it until the TTL expires.
383
- *
384
- * Use `EncapsulatedStateStrategy` ONLY when BOTH of the following hold:
385
- *
386
- * 1. Every workflow step is idempotent — re-executing a step with the same
387
- * input produces no harmful side effects (pure data collection,
388
- * validation-only steps).
389
- * 2. The flow is not security-sensitive — no credential changes, financial
390
- * operations, account provisioning, permission grants, or any other
391
- * privileged action.
392
- *
393
- * For auth flows (login, password reset, invite accept), financial
394
- * operations, or anything with meaningful side effects, use
395
- * `HandleStateStrategy` with a durable `WfStateStore`. `HandleStateStrategy`
396
- * supports true single-use tokens via atomic `getAndDelete` at the store
397
- * layer.
398
- *
399
- * @example
400
- * const strategy = new EncapsulatedStateStrategy({
401
- * secret: crypto.randomBytes(32),
402
- * defaultTtl: 3600_000, // 1 hour
403
- * });
404
- * const token = await strategy.persist(state);
405
- * const recovered = await strategy.retrieve(token);
406
- */
407
- var EncapsulatedStateStrategy = class {
408
- /** @throws if secret is not exactly 32 bytes */
409
- constructor(config) {
410
- this.config = config;
411
- this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
412
- if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
413
- }
414
- /**
415
- * Encrypt workflow state into a self-contained token.
416
- *
417
- * NOTE: the `overrides.handle` hint from `WfStateStrategy` is silently
418
- * ignored — the encapsulated token IS the ciphertext of the state, so a
419
- * fixed handle cannot map to changing state. Callers that need handle
420
- * stability across calls must use `HandleStateStrategy`.
421
- *
422
- * @param state — workflow state to persist
423
- * @param options.ttl — time-to-live in ms (overrides defaultTtl)
424
- * @returns base64url-encoded encrypted token
425
- */
426
- async persist(state, options, _overrides) {
427
- const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
428
- const exp = ttl > 0 ? Date.now() + ttl : 0;
429
- const payload = JSON.stringify({
430
- s: state,
431
- e: exp
432
- });
433
- const iv = (0, node_crypto.randomBytes)(12);
434
- const cipher = (0, node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
435
- const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
436
- const tag = cipher.getAuthTag();
437
- return Buffer.concat([
438
- iv,
439
- tag,
440
- encrypted
441
- ]).toString("base64url");
442
- }
443
- /** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
444
- async retrieve(token) {
445
- return this.decrypt(token);
446
- }
447
- /**
448
- * Stateless — CANNOT invalidate the token. Returns identical result to
449
- * `retrieve()`. See the class-level security warning.
450
- *
451
- * This method exists only to satisfy the `WfStateStrategy` contract.
452
- * Callers that need true single-use semantics must use
453
- * `HandleStateStrategy`.
454
- */
455
- async consume(token) {
456
- return this.decrypt(token);
457
- }
458
- decrypt(token) {
459
- try {
460
- const buf = Buffer.from(token, "base64url");
461
- if (buf.length < 28) return null;
462
- const iv = buf.subarray(0, 12);
463
- const tag = buf.subarray(12, 28);
464
- const ciphertext = buf.subarray(28);
465
- const decipher = (0, node_crypto.createDecipheriv)("aes-256-gcm", this.key, iv);
466
- decipher.setAuthTag(tag);
467
- const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
468
- const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
469
- if (exp > 0 && Date.now() > exp) return null;
470
- return state;
471
- } catch {
472
- return null;
473
- }
474
- }
475
- };
476
- var HandleStateStrategy = class {
477
- constructor(config) {
478
- this.config = config;
479
- }
480
- async persist(state, options, overrides) {
481
- const handle = overrides?.handle ?? (this.config.generateHandle ?? node_crypto.randomUUID)();
482
- const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
483
- const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
484
- await this.config.store.set(handle, state, expiresAt);
485
- return handle;
486
- }
487
- async retrieve(token) {
488
- return (await this.config.store.get(token))?.state ?? null;
489
- }
490
- async consume(token) {
491
- return (await this.config.store.getAndDelete(token))?.state ?? null;
492
- }
493
- };
494
- /**
495
- * In-memory state store for development and testing.
496
- * State is lost on process restart.
497
- */
498
- var WfStateStoreMemory = class {
499
- constructor() {
500
- this.store = /* @__PURE__ */ new Map();
501
- }
502
- async set(handle, state, expiresAt) {
503
- this.store.set(handle, {
504
- state,
505
- expiresAt
506
- });
507
- }
508
- async get(handle) {
509
- const entry = this.store.get(handle);
510
- if (!entry) return null;
511
- if (entry.expiresAt && Date.now() > entry.expiresAt) {
512
- this.store.delete(handle);
513
- return null;
514
- }
515
- return entry;
516
- }
517
- async delete(handle) {
518
- this.store.delete(handle);
519
- }
520
- async getAndDelete(handle) {
521
- const entry = await this.get(handle);
522
- if (entry) this.store.delete(handle);
523
- return entry;
524
- }
525
- async cleanup() {
526
- const now = Date.now();
527
- let count = 0;
528
- for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
529
- this.store.delete(handle);
530
- count++;
531
- }
532
- return count;
533
- }
534
- };
535
-
536
323
  //#endregion
537
324
  //#region packages/event-wf/src/workflow.ts
538
325
  /** Workflow engine that resolves steps via Wooks router lookup. */
@@ -717,15 +504,30 @@ function createWfApp(opts, wooks$2) {
717
504
  }
718
505
 
719
506
  //#endregion
720
- exports.EncapsulatedStateStrategy = EncapsulatedStateStrategy;
721
- exports.HandleStateStrategy = HandleStateStrategy;
507
+ Object.defineProperty(exports, 'EncapsulatedStateStrategy', {
508
+ enumerable: true,
509
+ get: function () {
510
+ return _prostojs_wf_outlets.EncapsulatedStateStrategy;
511
+ }
512
+ });
513
+ Object.defineProperty(exports, 'HandleStateStrategy', {
514
+ enumerable: true,
515
+ get: function () {
516
+ return _prostojs_wf_outlets.HandleStateStrategy;
517
+ }
518
+ });
722
519
  Object.defineProperty(exports, 'StepRetriableError', {
723
520
  enumerable: true,
724
521
  get: function () {
725
522
  return _prostojs_wf.StepRetriableError;
726
523
  }
727
524
  });
728
- exports.WfStateStoreMemory = WfStateStoreMemory;
525
+ Object.defineProperty(exports, 'WfStateStoreMemory', {
526
+ enumerable: true,
527
+ get: function () {
528
+ return _prostojs_wf_outlets.WfStateStoreMemory;
529
+ }
530
+ });
729
531
  exports.WooksWf = WooksWf;
730
532
  exports.createEmailOutlet = createEmailOutlet;
731
533
  exports.createHttpOutlet = createHttpOutlet;
@@ -733,9 +535,24 @@ exports.createOutletHandler = createOutletHandler;
733
535
  exports.createWfApp = createWfApp;
734
536
  exports.createWfContext = createWfContext;
735
537
  exports.handleWfOutletRequest = handleWfOutletRequest;
736
- exports.outlet = outlet;
737
- exports.outletEmail = outletEmail;
738
- exports.outletHttp = outletHttp;
538
+ Object.defineProperty(exports, 'outlet', {
539
+ enumerable: true,
540
+ get: function () {
541
+ return _prostojs_wf_outlets.outlet;
542
+ }
543
+ });
544
+ Object.defineProperty(exports, 'outletEmail', {
545
+ enumerable: true,
546
+ get: function () {
547
+ return _prostojs_wf_outlets.outletEmail;
548
+ }
549
+ });
550
+ Object.defineProperty(exports, 'outletHttp', {
551
+ enumerable: true,
552
+ get: function () {
553
+ return _prostojs_wf_outlets.outletHttp;
554
+ }
555
+ });
739
556
  exports.resumeKey = resumeKey;
740
557
  exports.resumeWfContext = resumeWfContext;
741
558
  Object.defineProperty(exports, 'useLogger', {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createEventContext, current, defineEventKind, defineWook, key, slot, useLogger, useRouteParams } from "@wooksjs/event-core";
2
2
  import { useCookies, useResponse, useUrlParams } from "@wooksjs/event-http";
3
3
  import { useBody } from "@wooksjs/http-body";
4
- import { createCipheriv, createDecipheriv, randomBytes, randomUUID } from "node:crypto";
4
+ import { EncapsulatedStateStrategy, HandleStateStrategy, WfStateStoreMemory, outlet, outletEmail, outletHttp } from "@prostojs/wf/outlets";
5
5
  import { StepRetriableError, Workflow, createStep } from "@prostojs/wf";
6
6
  import { WooksAdapterBase } from "wooks";
7
7
 
@@ -319,219 +319,6 @@ function createOutletHandler(wfApp) {
319
319
  });
320
320
  }
321
321
 
322
- //#endregion
323
- //#region node_modules/.pnpm/@prostojs+wf@0.2.1/node_modules/@prostojs/wf/dist/outlets/index.mjs
324
- /**
325
- * Generic outlet request. Use for custom outlets.
326
- *
327
- * @example
328
- * return outlet('pending-task', {
329
- * payload: ApprovalForm,
330
- * target: managerId,
331
- * context: { orderId, amount },
332
- * })
333
- */
334
- function outlet(name, data) {
335
- return { inputRequired: {
336
- outlet: name,
337
- ...data
338
- } };
339
- }
340
- /**
341
- * Pause for HTTP form input. The outlet returns the payload (form definition)
342
- * and state token in the HTTP response.
343
- *
344
- * @example
345
- * return outletHttp(LoginForm)
346
- * return outletHttp(LoginForm, { error: 'Invalid credentials' })
347
- */
348
- function outletHttp(payload, context) {
349
- return outlet("http", {
350
- payload,
351
- context
352
- });
353
- }
354
- /**
355
- * Pause and send email with a magic link containing the state token.
356
- *
357
- * @example
358
- * return outletEmail('user@test.com', 'invite', { name: 'Alice' })
359
- */
360
- function outletEmail(target, template, context) {
361
- return outlet("email", {
362
- target,
363
- template,
364
- context
365
- });
366
- }
367
- /**
368
- * Self-contained AES-256-GCM encrypted state strategy.
369
- *
370
- * Workflow state is encrypted into a base64url token that travels with the
371
- * transport (cookie, URL param, hidden field). No server-side storage needed.
372
- *
373
- * Token format: `base64url(iv[12] + authTag[16] + ciphertext)`
374
- *
375
- * ## Security warning — replay
376
- *
377
- * This strategy is STATELESS. It cannot enforce single-use semantics:
378
- * `consume()` is a no-op alias for `retrieve()` because there is no
379
- * server-side record to delete. Anyone who obtains a copy of the token
380
- * (browser history, server logs, shoulder-surfing, intermediate proxy,
381
- * shared device) can replay it until the TTL expires.
382
- *
383
- * Use `EncapsulatedStateStrategy` ONLY when BOTH of the following hold:
384
- *
385
- * 1. Every workflow step is idempotent — re-executing a step with the same
386
- * input produces no harmful side effects (pure data collection,
387
- * validation-only steps).
388
- * 2. The flow is not security-sensitive — no credential changes, financial
389
- * operations, account provisioning, permission grants, or any other
390
- * privileged action.
391
- *
392
- * For auth flows (login, password reset, invite accept), financial
393
- * operations, or anything with meaningful side effects, use
394
- * `HandleStateStrategy` with a durable `WfStateStore`. `HandleStateStrategy`
395
- * supports true single-use tokens via atomic `getAndDelete` at the store
396
- * layer.
397
- *
398
- * @example
399
- * const strategy = new EncapsulatedStateStrategy({
400
- * secret: crypto.randomBytes(32),
401
- * defaultTtl: 3600_000, // 1 hour
402
- * });
403
- * const token = await strategy.persist(state);
404
- * const recovered = await strategy.retrieve(token);
405
- */
406
- var EncapsulatedStateStrategy = class {
407
- /** @throws if secret is not exactly 32 bytes */
408
- constructor(config) {
409
- this.config = config;
410
- this.key = typeof config.secret === "string" ? Buffer.from(config.secret, "hex") : config.secret;
411
- if (this.key.length !== 32) throw new Error("EncapsulatedStateStrategy: secret must be exactly 32 bytes");
412
- }
413
- /**
414
- * Encrypt workflow state into a self-contained token.
415
- *
416
- * NOTE: the `overrides.handle` hint from `WfStateStrategy` is silently
417
- * ignored — the encapsulated token IS the ciphertext of the state, so a
418
- * fixed handle cannot map to changing state. Callers that need handle
419
- * stability across calls must use `HandleStateStrategy`.
420
- *
421
- * @param state — workflow state to persist
422
- * @param options.ttl — time-to-live in ms (overrides defaultTtl)
423
- * @returns base64url-encoded encrypted token
424
- */
425
- async persist(state, options, _overrides) {
426
- const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
427
- const exp = ttl > 0 ? Date.now() + ttl : 0;
428
- const payload = JSON.stringify({
429
- s: state,
430
- e: exp
431
- });
432
- const iv = randomBytes(12);
433
- const cipher = createCipheriv("aes-256-gcm", this.key, iv);
434
- const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
435
- const tag = cipher.getAuthTag();
436
- return Buffer.concat([
437
- iv,
438
- tag,
439
- encrypted
440
- ]).toString("base64url");
441
- }
442
- /** Decrypt and return workflow state. Returns null if token is invalid, expired, or tampered. */
443
- async retrieve(token) {
444
- return this.decrypt(token);
445
- }
446
- /**
447
- * Stateless — CANNOT invalidate the token. Returns identical result to
448
- * `retrieve()`. See the class-level security warning.
449
- *
450
- * This method exists only to satisfy the `WfStateStrategy` contract.
451
- * Callers that need true single-use semantics must use
452
- * `HandleStateStrategy`.
453
- */
454
- async consume(token) {
455
- return this.decrypt(token);
456
- }
457
- decrypt(token) {
458
- try {
459
- const buf = Buffer.from(token, "base64url");
460
- if (buf.length < 28) return null;
461
- const iv = buf.subarray(0, 12);
462
- const tag = buf.subarray(12, 28);
463
- const ciphertext = buf.subarray(28);
464
- const decipher = createDecipheriv("aes-256-gcm", this.key, iv);
465
- decipher.setAuthTag(tag);
466
- const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
467
- const { s: state, e: exp } = JSON.parse(decrypted.toString("utf8"));
468
- if (exp > 0 && Date.now() > exp) return null;
469
- return state;
470
- } catch {
471
- return null;
472
- }
473
- }
474
- };
475
- var HandleStateStrategy = class {
476
- constructor(config) {
477
- this.config = config;
478
- }
479
- async persist(state, options, overrides) {
480
- const handle = overrides?.handle ?? (this.config.generateHandle ?? randomUUID)();
481
- const ttl = options?.ttl ?? this.config.defaultTtl ?? 0;
482
- const expiresAt = ttl > 0 ? Date.now() + ttl : void 0;
483
- await this.config.store.set(handle, state, expiresAt);
484
- return handle;
485
- }
486
- async retrieve(token) {
487
- return (await this.config.store.get(token))?.state ?? null;
488
- }
489
- async consume(token) {
490
- return (await this.config.store.getAndDelete(token))?.state ?? null;
491
- }
492
- };
493
- /**
494
- * In-memory state store for development and testing.
495
- * State is lost on process restart.
496
- */
497
- var WfStateStoreMemory = class {
498
- constructor() {
499
- this.store = /* @__PURE__ */ new Map();
500
- }
501
- async set(handle, state, expiresAt) {
502
- this.store.set(handle, {
503
- state,
504
- expiresAt
505
- });
506
- }
507
- async get(handle) {
508
- const entry = this.store.get(handle);
509
- if (!entry) return null;
510
- if (entry.expiresAt && Date.now() > entry.expiresAt) {
511
- this.store.delete(handle);
512
- return null;
513
- }
514
- return entry;
515
- }
516
- async delete(handle) {
517
- this.store.delete(handle);
518
- }
519
- async getAndDelete(handle) {
520
- const entry = await this.get(handle);
521
- if (entry) this.store.delete(handle);
522
- return entry;
523
- }
524
- async cleanup() {
525
- const now = Date.now();
526
- let count = 0;
527
- for (const [handle, entry] of this.store) if (entry.expiresAt && now > entry.expiresAt) {
528
- this.store.delete(handle);
529
- count++;
530
- }
531
- return count;
532
- }
533
- };
534
-
535
322
  //#endregion
536
323
  //#region packages/event-wf/src/workflow.ts
537
324
  /** Workflow engine that resolves steps via Wooks router lookup. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/event-wf",
3
- "version": "0.7.14",
3
+ "version": "0.7.15",
4
4
  "description": "@wooksjs/event-wf",
5
5
  "keywords": [
6
6
  "app",
@@ -42,17 +42,17 @@
42
42
  "devDependencies": {
43
43
  "typescript": "^5.9.3",
44
44
  "vitest": "^3.2.4",
45
- "@wooksjs/event-http": "^0.7.14",
46
- "wooks": "^0.7.14",
47
- "@wooksjs/http-body": "^0.7.14",
48
- "@wooksjs/event-core": "^0.7.14"
45
+ "@wooksjs/event-core": "^0.7.15",
46
+ "@wooksjs/event-http": "^0.7.15",
47
+ "wooks": "^0.7.15",
48
+ "@wooksjs/http-body": "^0.7.15"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@prostojs/logger": "^0.4.3",
52
- "@wooksjs/event-core": "^0.7.14",
53
- "@wooksjs/event-http": "^0.7.14",
54
- "wooks": "^0.7.14",
55
- "@wooksjs/http-body": "^0.7.14"
52
+ "@wooksjs/event-core": "^0.7.15",
53
+ "wooks": "^0.7.15",
54
+ "@wooksjs/event-http": "^0.7.15",
55
+ "@wooksjs/http-body": "^0.7.15"
56
56
  },
57
57
  "peerDependenciesMeta": {
58
58
  "@wooksjs/event-http": {