@morojs/moro 1.8.8 → 1.8.10

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.
Files changed (36) hide show
  1. package/dist/core/cluster/thread-cluster.d.ts +178 -0
  2. package/dist/core/cluster/thread-cluster.js +389 -0
  3. package/dist/core/cluster/thread-cluster.js.map +1 -0
  4. package/dist/core/framework.js.map +1 -1
  5. package/dist/core/http/moro-engine-server.d.ts +29 -1
  6. package/dist/core/http/moro-engine-server.js +375 -68
  7. package/dist/core/http/moro-engine-server.js.map +1 -1
  8. package/dist/core/http/utils/compression.js +12 -6
  9. package/dist/core/http/utils/compression.js.map +1 -1
  10. package/dist/core/http/utils/uws-worker-clustering.js +10 -6
  11. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
  12. package/dist/core/http/uws-http-server.d.ts +1 -0
  13. package/dist/core/http/uws-http-server.js +105 -22
  14. package/dist/core/http/uws-http-server.js.map +1 -1
  15. package/dist/core/jobs/leader-election.js +16 -5
  16. package/dist/core/jobs/leader-election.js.map +1 -1
  17. package/dist/core/networking/websocket-manager.js +4 -2
  18. package/dist/core/networking/websocket-manager.js.map +1 -1
  19. package/dist/core/routing/unified-router.js +60 -29
  20. package/dist/core/routing/unified-router.js.map +1 -1
  21. package/dist/core/runtime/node-adapter.d.ts +1 -1
  22. package/dist/core/runtime/node-adapter.js.map +1 -1
  23. package/dist/core/utilities/builtin.d.ts +1 -0
  24. package/dist/core/utilities/builtin.js +25 -0
  25. package/dist/core/utilities/builtin.js.map +1 -0
  26. package/dist/core/utilities/package-utils.d.ts +28 -3
  27. package/dist/core/utilities/package-utils.js +24 -12
  28. package/dist/core/utilities/package-utils.js.map +1 -1
  29. package/dist/core/workers/worker-manager.js +4 -3
  30. package/dist/core/workers/worker-manager.js.map +1 -1
  31. package/dist/moro.d.ts +5 -0
  32. package/dist/moro.js +364 -204
  33. package/dist/moro.js.map +1 -1
  34. package/dist/types/http.d.ts +1 -1
  35. package/dist/types/http.js.map +1 -1
  36. package/package.json +1 -1
@@ -5,8 +5,14 @@ import crypto from 'crypto';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import os from 'os';
8
- import cluster from 'cluster';
9
- import { isMainThread } from 'worker_threads';
8
+ import { requireBuiltin } from '../utilities/builtin.js';
9
+ // worker_threads on first use (core/utilities/builtin.ts). node:cluster is
10
+ // never loaded here: a cluster worker is exactly a process with
11
+ // NODE_UNIQUE_ID in its environment (that is how node:cluster itself decides
12
+ // isWorker), and asking cluster would pull child_process/dgram/net (~7 MB)
13
+ // into every app that starts the job scheduler.
14
+ const wt = () => requireBuiltin('worker_threads');
15
+ const isClusterWorkerProcess = () => process.env.NODE_UNIQUE_ID !== undefined;
10
16
  /**
11
17
  * LeaderElection - Ensures only one job scheduler runs across distributed instances
12
18
  * Supports:
@@ -111,11 +117,16 @@ return 0`;
111
117
  * Check if this process should participate in leader election
112
118
  */
113
119
  shouldParticipate() {
114
- // Only main thread and primary cluster process can be leader
115
- if (!isMainThread) {
120
+ // Only the main thread of a non-worker process can be leader. Under
121
+ // thread clustering that is exactly the cluster primary (its worker
122
+ // threads are !isMainThread); under process clustering it is the primary
123
+ // process (workers have cluster.isWorker). Either way the scheduler runs
124
+ // in the primary (Moro.startPrimaryServices), so one participant per
125
+ // cluster - no change needed for threads.
126
+ if (!wt().isMainThread) {
116
127
  return false;
117
128
  }
118
- if (cluster.isWorker) {
129
+ if (isClusterWorkerProcess()) {
119
130
  return false;
120
131
  }
121
132
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"leader-election.js","sourceRoot":"","sources":["../../../src/core/jobs/leader-election.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,iFAAiF;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAqB9C;;;;;;;GAOG;AACH,MAAM,OAAO,cAAe,SAAQ,YAAY;IACtC,QAAQ,GAAG,KAAK,CAAC;IACjB,QAAQ,CAA4B;IACpC,QAAQ,CAAU;IAClB,WAAW,CAAS;IACpB,iBAAiB,CAAS;IAC1B,cAAc,CAA8B;IAC5C,UAAU,CAA8B;IACxC,MAAM,CAAS;IACf,aAAa,GAAG,gBAAgB,CAAC;IACjC,UAAU,CAAS;IACnB,UAAU,CAAc;IACxB,WAAW,CAAO;IAClB,cAAc,GAAG,KAAK,CAAC;IACvB,mBAAmB,GAAG,CAAC,CAAC;IAExB,MAAM,CAAU,cAAc,GAAG,uBAAuB,CAAC;IAEjE,+EAA+E;IAC/E,2EAA2E;IACnE,MAAM,CAAU,oBAAoB,GAAG;;;;;;;;SAQxC,CAAC;IAER,kFAAkF;IAC1E,MAAM,CAAU,oBAAoB,GAAG;;;;;;;SAOxC,CAAC;IAER,YAAY,MAAc,EAAE,UAAiC,EAAE,QAAQ,EAAE,MAAM,EAAE;QAC/E,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC,cAAc;QAC/D,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,KAAK,CAAC,CAAC,cAAc;QAC3E,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QAEvC,8BAA8B;QAC9B,IAAI,CAAC,UAAU;YACb,OAAO,CAAC,UAAU;gBAClB,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAE3F,0CAA0C;QAC1C,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC7B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACrB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,IAAI,CAAC,aAAa,EAAE;YAClE,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;YACpE,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,oDAAoD;QACpD,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;YACjF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,IAAI,CAAC,aAAa,EAAE;YAClE,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAC;QAEH,4BAA4B;QAC5B,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAElC,wBAAwB;QACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,6DAA6D;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB;QAChC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAE3B,IAAI,CAAC;YACH,IAAI,QAAQ,GAAG,KAAK,CAAC;YAErB,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;gBAC7B,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7C,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACrC,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9C,CAAC;YAED,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC,YAAY,EAAE,CAAC;gBACpB,OAAO,IAAI,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,yCAAyC;gBACzC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,IAAI,CAAC,aAAa,EAAE;gBACpE,KAAK;gBACL,OAAO,EAAE,IAAI,CAAC,mBAAmB;aAClC,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,kBAAkB;QAC9B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,QAAQ,GAAe;YAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;YACvB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,aAAa,EAAE,IAAI,IAAI,EAAE;YACzB,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE;SACrC,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAE5C,4EAA4E;QAC5E,2CAA2C;QAC3C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC3D,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC;oBAAS,CAAC;gBACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,IAAI,CAAC,aAAa,EAAE;oBACnE,KAAK;oBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC,CAAC;gBACH,OAAO,KAAK,CAAC;YACf,CAAC;YACD,wEAAwE;QAC1E,CAAC;QAED,IAAI,CAAC;YACH,6DAA6D;YAC7D,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACpE,MAAM,cAAc,GAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAExD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;YAE9E,IAAI,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC/B,oDAAoD;gBACpD,IAAI,CAAC,UAAU,GAAG,cAAc,CAAC;gBACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE,IAAI,CAAC,aAAa,EAAE;oBAC5E,MAAM,EAAE,cAAc,CAAC,UAAU;oBACjC,GAAG,EAAE,OAAO;iBACb,CAAC,CAAC;gBACH,OAAO,KAAK,CAAC;YACf,CAAC;YAED,+DAA+D;YAC/D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACvE,cAAc,EAAE,cAAc,CAAC,UAAU;gBACzC,OAAO;aACR,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;YACvC,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,IAAI,CAAC,aAAa,EAAE;gBACnE,KAAK;gBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,eAAe,CAAC,UAAkB;QAC9C,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAChG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB;QAC/B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,cAAc,CAAC,cAAc,CAAC;YAC9C,MAAM,QAAQ,GAAe;gBAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;gBACvB,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,aAAa,EAAE,IAAI,IAAI,EAAE;gBACzB,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE;aACrC,CAAC;YAEF,yEAAyE;YACzE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CACvC,OAAO,EACP,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EACxB,IAAI,EACJ,IAAI,CAAC,WAAW,EAChB,IAAI,CACL,CAAC;YAEF,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;gBAC3B,OAAO,IAAI,CAAC;YACd,CAAC;YAED,uCAAuC;YACvC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,YAAY,EAAE,CAAC;gBACjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC7C,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACjF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACK,YAAY;QAClB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,aAAa,EAAE;YACpD,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,mBAAmB;SACnC,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC1B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS;SACtC,CAAC,CAAC;QAEH,kBAAkB;QAClB,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,QAAQ;QACpB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,IAAI,CAAC,aAAa,EAAE;YAC9D,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,yEAAyE;QACzE,uCAAuC;QACvC,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC9C,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;oBACpE,MAAM,aAAa,GAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACvD,IAAI,aAAa,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;wBACjD,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBAC1D,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,wDAAwD;gBAC1D,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACzD,uEAAuE;gBACvE,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CACzB,cAAc,CAAC,oBAAoB,EACnC,CAAC,EACD,cAAc,CAAC,cAAc,EAC7B,IAAI,CAAC,UAAU,CAChB,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9F,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAChE,CAAC;IAED;;OAEG;IACK,cAAc;QACpB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACxB,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC7B,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;oBACrE,kBAAkB;oBAClB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACtB,mBAAmB;oBACnB,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;wBACjC,KAAK,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBACnC,CAAC,EAAE,IAAI,CAAC,CAAC;oBACT,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,2BAA2B;gBACjD,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAE3B,2BAA2B;QAC3B,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,aAAa;QACnB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC;QAE3C,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9C,4EAA4E;YAC5E,wEAAwE;YACxE,mEAAmE;YACnE,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACpE,MAAM,aAAa,GAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACvD,IAAI,aAAa,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;YAC7E,CAAC;YACD,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACzD,+EAA+E;YAC/E,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAC3C,cAAc,CAAC,oBAAoB,EACnC,CAAC,EACD,cAAc,CAAC,cAAc,EAC7B,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAC/B,IAAI,CAAC,WAAW,CACjB,CAAC;YACF,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;YACrF,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC5B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa;SACzC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC;QAE/D,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YACjC,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACxB,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACnB,4BAA4B;oBAC5B,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBACpC,CAAC;qBAAM,CAAC;oBACN,gCAAgC;oBAChC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAChC,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,EAAE,aAAa,CAAC,CAAC;QAElB,2BAA2B;QAC3B,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,WAAW,GAAG,KAAK,CAAC;YAExB,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC9C,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACpE,MAAM,aAAa,GAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACvD,WAAW,GAAG,aAAa,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,CAAC;YAC7D,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACzD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;gBAC3E,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,aAAa,GAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACvD,WAAW,GAAG,aAAa,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,CAAC;gBAC7D,CAAC;YACH,CAAC;YAED,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;gBACzF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAChF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;QAEpF,IAAI,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACvE,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU;gBAClC,YAAY;aACb,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBAC5B,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU;gBAClC,YAAY;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,MAAM,QAAQ,GAAwB;YACpC,WAAW,EAAE,OAAO,CAAC,OAAO;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;SACzB,CAAC;QAEF,gBAAgB;QAChB,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC;YACxC,QAAQ,CAAC,UAAU,GAAG;gBACpB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,QAAQ,EAAE;gBAC9C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;gBACpC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS;aAChC,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;YAC1B,QAAQ,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAC7C,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;OAEG;IACI,eAAe;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;OAEG;IACI,aAAa;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,CAAC;IAED;;OAEG;IACI,aAAa;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,aAAa;QACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,IAAI,CAAC,aAAa,EAAE;YACjE,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,QAAQ;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,IAAI,CAAC,aAAa,EAAE;YACtE,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,IAAI,CAAC,QAAQ;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAE3B,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAEzB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IACvD,CAAC","sourcesContent":["// Production-grade Leader Election for Distributed Job Scheduling\n// Supports file-based and Redis-based locking for K8s and clustered environments\n\nimport { EventEmitter } from 'events';\nimport crypto from 'crypto';\nimport { Logger } from '../../types/logger.js';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport cluster from 'cluster';\nimport { isMainThread } from 'worker_threads';\n\nexport interface LeaderElectionOptions {\n strategy: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n redisClient?: any; // Optional Redis client\n forceLeader?: boolean; // Force this instance to be leader (dev/testing)\n instanceId?: string; // Custom instance ID\n}\n\nexport interface LeaderInfo {\n instanceId: string;\n hostname: string;\n pid: number;\n electedAt: Date;\n lastHeartbeat: Date;\n metadata?: Record<string, any>;\n}\n\n/**\n * LeaderElection - Ensures only one job scheduler runs across distributed instances\n * Supports:\n * - File-based locking (for local/NFS environments)\n * - Redis-based locking (for distributed systems)\n * - Kubernetes pod detection\n * - Automatic failover with health checks\n */\nexport class LeaderElection extends EventEmitter {\n private isLeader = false;\n private strategy: 'file' | 'redis' | 'none';\n private lockPath?: string;\n private lockTimeout: number;\n private heartbeatInterval: number;\n private heartbeatTimer?: NodeJS.Timeout | undefined;\n private checkTimer?: NodeJS.Timeout | undefined;\n private logger: Logger;\n private loggerContext = 'LeaderElection';\n private instanceId: string;\n private leaderInfo?: LeaderInfo;\n private redisClient?: any;\n private isShuttingDown = false;\n private lockAcquireAttempts = 0;\n\n private static readonly REDIS_LOCK_KEY = 'moro:jobs:leader:lock';\n\n // Compare-and-refresh: rewrite the value and extend the TTL only if the stored\n // lock still belongs to this instance (ownership is fenced by instanceId).\n private static readonly REDIS_REFRESH_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n return 1\nend\nreturn 0`;\n\n // Compare-and-delete: release the lock only if it still belongs to this instance.\n private static readonly REDIS_RELEASE_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n return redis.call('DEL', KEYS[1])\nend\nreturn 0`;\n\n constructor(logger: Logger, options: LeaderElectionOptions = { strategy: 'file' }) {\n super();\n this.logger = logger;\n this.strategy = options.strategy;\n this.lockTimeout = options.lockTimeout ?? 30000; // 30s default\n this.heartbeatInterval = options.heartbeatInterval ?? 10000; // 10s default\n this.redisClient = options.redisClient;\n\n // Generate unique instance ID\n this.instanceId =\n options.instanceId ||\n `${os.hostname()}-${process.pid}-${Date.now()}-${crypto.randomBytes(6).toString('hex')}`;\n\n // Setup lock path for file-based strategy\n if (this.strategy === 'file') {\n if (options.lockPath) {\n this.lockPath = options.lockPath;\n } else {\n const tmpDir = os.tmpdir();\n this.lockPath = path.join(tmpDir, 'moro-jobs-leader.lock');\n }\n }\n\n // Force leader mode (for dev/testing)\n if (options.forceLeader) {\n this.isLeader = true;\n this.logger.warn('Leader election forced - running as leader');\n }\n\n this.logger.debug('LeaderElection initialized', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n lockPath: this.lockPath,\n forceLeader: options.forceLeader,\n });\n }\n\n /**\n * Start leader election process\n */\n public async start(): Promise<void> {\n if (this.strategy === 'none') {\n this.logger.debug('Leader election disabled, assuming leader role');\n this.becomeLeader();\n return;\n }\n\n // Check if we should participate in leader election\n if (!this.shouldParticipate()) {\n this.logger.info('Not participating in leader election (worker process/thread)');\n return;\n }\n\n this.logger.info('Starting leader election...', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n });\n\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n\n // Start periodic checks\n this.startPeriodicCheck();\n }\n\n /**\n * Check if this process should participate in leader election\n */\n private shouldParticipate(): boolean {\n // Only main thread and primary cluster process can be leader\n if (!isMainThread) {\n return false;\n }\n\n if (cluster.isWorker) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Try to acquire leadership\n */\n private async tryAcquireLeadership(): Promise<boolean> {\n if (this.isShuttingDown) {\n return false;\n }\n\n this.lockAcquireAttempts++;\n\n try {\n let acquired = false;\n\n if (this.strategy === 'file') {\n acquired = await this.tryAcquireFileLock();\n } else if (this.strategy === 'redis') {\n acquired = await this.tryAcquireRedisLock();\n }\n\n if (acquired) {\n this.becomeLeader();\n return true;\n } else {\n // Check if current leader is still alive\n await this.checkLeaderHealth();\n return false;\n }\n } catch (error) {\n this.logger.error('Error during leader election', this.loggerContext, {\n error,\n attempt: this.lockAcquireAttempts,\n });\n return false;\n }\n }\n\n /**\n * Try to acquire file-based lock\n *\n * Uses O_EXCL (open flag 'wx') for the common \"no lock yet\" case so exactly one\n * instance can win the create race. An expired lock is taken over via an atomic\n * temp-write + rename. A takeover race between two instances is a last-writer-wins\n * rename, which is then reconciled by the periodic verifyLeadership() check and the\n * ownership-checked heartbeat below, so a single leader is still guaranteed.\n */\n private async tryAcquireFileLock(): Promise<boolean> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n const serialized = JSON.stringify(lockInfo);\n\n // Fast path: atomically create the lock file (O_EXCL). Fails with EEXIST if\n // another instance already holds the lock.\n try {\n const handle = await fs.promises.open(this.lockPath, 'wx');\n try {\n await handle.writeFile(serialized, 'utf-8');\n } finally {\n await handle.close();\n }\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'EEXIST') {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n // Lock file already exists — fall through to the expiry/takeover check.\n }\n\n try {\n // Read existing lock and decide whether we may take it over.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const existingLeader: LeaderInfo = JSON.parse(lockData);\n\n const lockAge = Date.now() - new Date(existingLeader.lastHeartbeat).getTime();\n\n if (lockAge < this.lockTimeout) {\n // Lock is still valid and held by another instance.\n this.leaderInfo = existingLeader;\n this.logger.debug('Leader lock held by another instance', this.loggerContext, {\n leader: existingLeader.instanceId,\n age: lockAge,\n });\n return false;\n }\n\n // Lock expired — take over with an atomic temp-write + rename.\n this.logger.warn('Leader lock expired, taking over', this.loggerContext, {\n previousLeader: existingLeader.instanceId,\n lockAge,\n });\n\n await this.atomicWriteLock(serialized);\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n }\n\n /**\n * Atomically (re)write the lock file: write to a unique temp file, then rename\n * over the target. rename() is atomic on POSIX so readers never see a partial\n * lock file.\n */\n private async atomicWriteLock(serialized: string): Promise<void> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const tempPath = `${this.lockPath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;\n await fs.promises.writeFile(tempPath, serialized, 'utf-8');\n try {\n await fs.promises.rename(tempPath, this.lockPath);\n } catch (error) {\n await fs.promises.unlink(tempPath).catch(() => {});\n throw error;\n }\n }\n\n /**\n * Try to acquire Redis-based lock\n */\n private async tryAcquireRedisLock(): Promise<boolean> {\n if (!this.redisClient) {\n throw new Error('Redis client not configured');\n }\n\n try {\n const lockKey = LeaderElection.REDIS_LOCK_KEY;\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n\n // Try to set lock with NX (only if not exists) and PX (expiration in ms)\n const result = await this.redisClient.set(\n lockKey,\n JSON.stringify(lockInfo),\n 'PX',\n this.lockTimeout,\n 'NX'\n );\n\n if (result === 'OK') {\n this.leaderInfo = lockInfo;\n return true;\n }\n\n // Lock exists, get current leader info\n const existingLock = await this.redisClient.get(lockKey);\n if (existingLock) {\n this.leaderInfo = JSON.parse(existingLock);\n }\n\n return false;\n } catch (error) {\n this.logger.error('Failed to acquire Redis lock', this.loggerContext, { error });\n return false;\n }\n }\n\n /**\n * Become leader\n */\n private becomeLeader(): void {\n if (this.isLeader) {\n return;\n }\n\n this.isLeader = true;\n this.logger.info('Became leader', this.loggerContext, {\n instanceId: this.instanceId,\n strategy: this.strategy,\n attempts: this.lockAcquireAttempts,\n });\n\n this.emit('leader:elected', {\n instanceId: this.instanceId,\n electedAt: this.leaderInfo?.electedAt,\n });\n\n // Start heartbeat\n this.startHeartbeat();\n }\n\n /**\n * Step down as leader\n */\n private async stepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.info('Stepping down as leader', this.loggerContext, {\n instanceId: this.instanceId,\n });\n\n this.isLeader = false;\n this.stopHeartbeat();\n\n // Release lock — only if we still own it, so we never delete a lock that\n // another instance has since acquired.\n try {\n if (this.strategy === 'file' && this.lockPath) {\n try {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId === this.instanceId) {\n await fs.promises.unlink(this.lockPath).catch(() => {});\n }\n } catch {\n // Lock file missing or unreadable — nothing to release.\n }\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-delete: only DEL if the stored lock still belongs to us.\n await this.redisClient.eval(\n LeaderElection.REDIS_RELEASE_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId\n );\n }\n } catch (error) {\n this.logger.error('Failed to release lock during step down', this.loggerContext, { error });\n }\n\n this.emit('leader:stepdown', { instanceId: this.instanceId });\n }\n\n /**\n * Start heartbeat to maintain leadership\n */\n private startHeartbeat(): void {\n if (this.heartbeatTimer) {\n return;\n }\n\n this.heartbeatTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n try {\n await this.sendHeartbeat();\n } catch (error) {\n this.logger.error('Heartbeat failed', this.loggerContext, { error });\n // Lost leadership\n await this.stepDown();\n // Try to reacquire\n const retryTimer = setTimeout(() => {\n void this.tryAcquireLeadership();\n }, 1000);\n retryTimer.unref(); // Don't keep process alive\n }\n })();\n }, this.heartbeatInterval);\n\n // Don't keep process alive\n this.heartbeatTimer.unref();\n }\n\n /**\n * Stop heartbeat\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n }\n }\n\n /**\n * Send heartbeat to maintain lock\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.leaderInfo) {\n return;\n }\n\n this.leaderInfo.lastHeartbeat = new Date();\n\n if (this.strategy === 'file' && this.lockPath) {\n // Verify we still own the lock before refreshing it. If the file is gone or\n // now owned by another instance, we have lost leadership — throw so the\n // heartbeat handler steps us down and attempts a clean re-acquire.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId !== this.instanceId) {\n throw new Error('Lost leadership: file lock is owned by another instance');\n }\n await this.atomicWriteLock(JSON.stringify(this.leaderInfo));\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-refresh: only rewrite + extend the TTL if we still own the lock.\n const refreshed = await this.redisClient.eval(\n LeaderElection.REDIS_REFRESH_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId,\n JSON.stringify(this.leaderInfo),\n this.lockTimeout\n );\n if (Number(refreshed) !== 1) {\n throw new Error('Lost leadership: redis lock is no longer owned by this instance');\n }\n }\n\n this.logger.debug('Heartbeat sent', this.loggerContext, { instanceId: this.instanceId });\n this.emit('leader:heartbeat', {\n instanceId: this.instanceId,\n timestamp: this.leaderInfo.lastHeartbeat,\n });\n }\n\n /**\n * Start periodic check for leadership changes\n */\n private startPeriodicCheck(): void {\n if (this.checkTimer) {\n return;\n }\n\n const checkInterval = Math.floor(this.heartbeatInterval * 1.5);\n\n this.checkTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n if (!this.isLeader) {\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n } else {\n // Verify we still hold the lock\n await this.verifyLeadership();\n }\n })();\n }, checkInterval);\n\n // Don't keep process alive\n this.checkTimer.unref();\n }\n\n /**\n * Stop periodic check\n */\n private stopPeriodicCheck(): void {\n if (this.checkTimer) {\n clearInterval(this.checkTimer);\n this.checkTimer = undefined;\n }\n }\n\n /**\n * Verify we still hold leadership\n */\n private async verifyLeadership(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n try {\n let stillLeader = false;\n\n if (this.strategy === 'file' && this.lockPath) {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n } else if (this.strategy === 'redis' && this.redisClient) {\n const lockData = await this.redisClient.get(LeaderElection.REDIS_LOCK_KEY);\n if (lockData) {\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n }\n }\n\n if (!stillLeader) {\n this.logger.warn('Lost leadership', this.loggerContext, { instanceId: this.instanceId });\n await this.stepDown();\n }\n } catch (error) {\n this.logger.error('Failed to verify leadership', this.loggerContext, { error });\n await this.stepDown();\n }\n }\n\n /**\n * Check health of current leader\n */\n private async checkLeaderHealth(): Promise<void> {\n if (!this.leaderInfo || this.leaderInfo.instanceId === this.instanceId) {\n return;\n }\n\n const heartbeatAge = Date.now() - new Date(this.leaderInfo.lastHeartbeat).getTime();\n\n if (heartbeatAge > this.lockTimeout) {\n this.logger.warn('Current leader appears unhealthy', this.loggerContext, {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n\n this.emit('leader:unhealthy', {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n }\n }\n\n /**\n * Get instance metadata\n */\n private getInstanceMetadata(): Record<string, any> {\n const metadata: Record<string, any> = {\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptime: process.uptime(),\n };\n\n // K8s detection\n if (process.env.KUBERNETES_SERVICE_HOST) {\n metadata.kubernetes = {\n podName: process.env.HOSTNAME || os.hostname(),\n namespace: process.env.POD_NAMESPACE,\n nodeName: process.env.NODE_NAME,\n };\n }\n\n // Container detection\n if (process.env.CONTAINER) {\n metadata.container = process.env.CONTAINER;\n }\n\n return metadata;\n }\n\n /**\n * Check if current instance is leader\n */\n public isCurrentLeader(): boolean {\n return this.isLeader;\n }\n\n /**\n * Get current leader info\n */\n public getLeaderInfo(): LeaderInfo | undefined {\n return this.leaderInfo ? { ...this.leaderInfo } : undefined;\n }\n\n /**\n * Get instance ID\n */\n public getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Force step down (for testing/admin)\n */\n public async forceStepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.warn('Forced step down requested', this.loggerContext, {\n instanceId: this.instanceId,\n });\n await this.stepDown();\n }\n\n /**\n * Shutdown and cleanup\n */\n public async shutdown(): Promise<void> {\n this.logger.info('LeaderElection shutting down...', this.loggerContext, {\n instanceId: this.instanceId,\n wasLeader: this.isLeader,\n });\n\n this.isShuttingDown = true;\n\n this.stopHeartbeat();\n this.stopPeriodicCheck();\n\n if (this.isLeader) {\n await this.stepDown();\n }\n\n this.removeAllListeners();\n this.logger.info('LeaderElection shutdown complete');\n }\n}\n"]}
1
+ {"version":3,"file":"leader-election.js","sourceRoot":"","sources":["../../../src/core/jobs/leader-election.ts"],"names":[],"mappings":"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","sourcesContent":["// Production-grade Leader Election for Distributed Job Scheduling\n// Supports file-based and Redis-based locking for K8s and clustered environments\n\nimport { EventEmitter } from 'events';\nimport crypto from 'crypto';\nimport { Logger } from '../../types/logger.js';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { requireBuiltin } from '../utilities/builtin.js';\n\n// worker_threads on first use (core/utilities/builtin.ts). node:cluster is\n// never loaded here: a cluster worker is exactly a process with\n// NODE_UNIQUE_ID in its environment (that is how node:cluster itself decides\n// isWorker), and asking cluster would pull child_process/dgram/net (~7 MB)\n// into every app that starts the job scheduler.\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nconst isClusterWorkerProcess = () => process.env.NODE_UNIQUE_ID !== undefined;\n\nexport interface LeaderElectionOptions {\n strategy: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n redisClient?: any; // Optional Redis client\n forceLeader?: boolean; // Force this instance to be leader (dev/testing)\n instanceId?: string; // Custom instance ID\n}\n\nexport interface LeaderInfo {\n instanceId: string;\n hostname: string;\n pid: number;\n electedAt: Date;\n lastHeartbeat: Date;\n metadata?: Record<string, any>;\n}\n\n/**\n * LeaderElection - Ensures only one job scheduler runs across distributed instances\n * Supports:\n * - File-based locking (for local/NFS environments)\n * - Redis-based locking (for distributed systems)\n * - Kubernetes pod detection\n * - Automatic failover with health checks\n */\nexport class LeaderElection extends EventEmitter {\n private isLeader = false;\n private strategy: 'file' | 'redis' | 'none';\n private lockPath?: string;\n private lockTimeout: number;\n private heartbeatInterval: number;\n private heartbeatTimer?: NodeJS.Timeout | undefined;\n private checkTimer?: NodeJS.Timeout | undefined;\n private logger: Logger;\n private loggerContext = 'LeaderElection';\n private instanceId: string;\n private leaderInfo?: LeaderInfo;\n private redisClient?: any;\n private isShuttingDown = false;\n private lockAcquireAttempts = 0;\n\n private static readonly REDIS_LOCK_KEY = 'moro:jobs:leader:lock';\n\n // Compare-and-refresh: rewrite the value and extend the TTL only if the stored\n // lock still belongs to this instance (ownership is fenced by instanceId).\n private static readonly REDIS_REFRESH_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n return 1\nend\nreturn 0`;\n\n // Compare-and-delete: release the lock only if it still belongs to this instance.\n private static readonly REDIS_RELEASE_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n return redis.call('DEL', KEYS[1])\nend\nreturn 0`;\n\n constructor(logger: Logger, options: LeaderElectionOptions = { strategy: 'file' }) {\n super();\n this.logger = logger;\n this.strategy = options.strategy;\n this.lockTimeout = options.lockTimeout ?? 30000; // 30s default\n this.heartbeatInterval = options.heartbeatInterval ?? 10000; // 10s default\n this.redisClient = options.redisClient;\n\n // Generate unique instance ID\n this.instanceId =\n options.instanceId ||\n `${os.hostname()}-${process.pid}-${Date.now()}-${crypto.randomBytes(6).toString('hex')}`;\n\n // Setup lock path for file-based strategy\n if (this.strategy === 'file') {\n if (options.lockPath) {\n this.lockPath = options.lockPath;\n } else {\n const tmpDir = os.tmpdir();\n this.lockPath = path.join(tmpDir, 'moro-jobs-leader.lock');\n }\n }\n\n // Force leader mode (for dev/testing)\n if (options.forceLeader) {\n this.isLeader = true;\n this.logger.warn('Leader election forced - running as leader');\n }\n\n this.logger.debug('LeaderElection initialized', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n lockPath: this.lockPath,\n forceLeader: options.forceLeader,\n });\n }\n\n /**\n * Start leader election process\n */\n public async start(): Promise<void> {\n if (this.strategy === 'none') {\n this.logger.debug('Leader election disabled, assuming leader role');\n this.becomeLeader();\n return;\n }\n\n // Check if we should participate in leader election\n if (!this.shouldParticipate()) {\n this.logger.info('Not participating in leader election (worker process/thread)');\n return;\n }\n\n this.logger.info('Starting leader election...', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n });\n\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n\n // Start periodic checks\n this.startPeriodicCheck();\n }\n\n /**\n * Check if this process should participate in leader election\n */\n private shouldParticipate(): boolean {\n // Only the main thread of a non-worker process can be leader. Under\n // thread clustering that is exactly the cluster primary (its worker\n // threads are !isMainThread); under process clustering it is the primary\n // process (workers have cluster.isWorker). Either way the scheduler runs\n // in the primary (Moro.startPrimaryServices), so one participant per\n // cluster - no change needed for threads.\n if (!wt().isMainThread) {\n return false;\n }\n\n if (isClusterWorkerProcess()) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Try to acquire leadership\n */\n private async tryAcquireLeadership(): Promise<boolean> {\n if (this.isShuttingDown) {\n return false;\n }\n\n this.lockAcquireAttempts++;\n\n try {\n let acquired = false;\n\n if (this.strategy === 'file') {\n acquired = await this.tryAcquireFileLock();\n } else if (this.strategy === 'redis') {\n acquired = await this.tryAcquireRedisLock();\n }\n\n if (acquired) {\n this.becomeLeader();\n return true;\n } else {\n // Check if current leader is still alive\n await this.checkLeaderHealth();\n return false;\n }\n } catch (error) {\n this.logger.error('Error during leader election', this.loggerContext, {\n error,\n attempt: this.lockAcquireAttempts,\n });\n return false;\n }\n }\n\n /**\n * Try to acquire file-based lock\n *\n * Uses O_EXCL (open flag 'wx') for the common \"no lock yet\" case so exactly one\n * instance can win the create race. An expired lock is taken over via an atomic\n * temp-write + rename. A takeover race between two instances is a last-writer-wins\n * rename, which is then reconciled by the periodic verifyLeadership() check and the\n * ownership-checked heartbeat below, so a single leader is still guaranteed.\n */\n private async tryAcquireFileLock(): Promise<boolean> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n const serialized = JSON.stringify(lockInfo);\n\n // Fast path: atomically create the lock file (O_EXCL). Fails with EEXIST if\n // another instance already holds the lock.\n try {\n const handle = await fs.promises.open(this.lockPath, 'wx');\n try {\n await handle.writeFile(serialized, 'utf-8');\n } finally {\n await handle.close();\n }\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'EEXIST') {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n // Lock file already exists — fall through to the expiry/takeover check.\n }\n\n try {\n // Read existing lock and decide whether we may take it over.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const existingLeader: LeaderInfo = JSON.parse(lockData);\n\n const lockAge = Date.now() - new Date(existingLeader.lastHeartbeat).getTime();\n\n if (lockAge < this.lockTimeout) {\n // Lock is still valid and held by another instance.\n this.leaderInfo = existingLeader;\n this.logger.debug('Leader lock held by another instance', this.loggerContext, {\n leader: existingLeader.instanceId,\n age: lockAge,\n });\n return false;\n }\n\n // Lock expired — take over with an atomic temp-write + rename.\n this.logger.warn('Leader lock expired, taking over', this.loggerContext, {\n previousLeader: existingLeader.instanceId,\n lockAge,\n });\n\n await this.atomicWriteLock(serialized);\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n }\n\n /**\n * Atomically (re)write the lock file: write to a unique temp file, then rename\n * over the target. rename() is atomic on POSIX so readers never see a partial\n * lock file.\n */\n private async atomicWriteLock(serialized: string): Promise<void> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const tempPath = `${this.lockPath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;\n await fs.promises.writeFile(tempPath, serialized, 'utf-8');\n try {\n await fs.promises.rename(tempPath, this.lockPath);\n } catch (error) {\n await fs.promises.unlink(tempPath).catch(() => {});\n throw error;\n }\n }\n\n /**\n * Try to acquire Redis-based lock\n */\n private async tryAcquireRedisLock(): Promise<boolean> {\n if (!this.redisClient) {\n throw new Error('Redis client not configured');\n }\n\n try {\n const lockKey = LeaderElection.REDIS_LOCK_KEY;\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n\n // Try to set lock with NX (only if not exists) and PX (expiration in ms)\n const result = await this.redisClient.set(\n lockKey,\n JSON.stringify(lockInfo),\n 'PX',\n this.lockTimeout,\n 'NX'\n );\n\n if (result === 'OK') {\n this.leaderInfo = lockInfo;\n return true;\n }\n\n // Lock exists, get current leader info\n const existingLock = await this.redisClient.get(lockKey);\n if (existingLock) {\n this.leaderInfo = JSON.parse(existingLock);\n }\n\n return false;\n } catch (error) {\n this.logger.error('Failed to acquire Redis lock', this.loggerContext, { error });\n return false;\n }\n }\n\n /**\n * Become leader\n */\n private becomeLeader(): void {\n if (this.isLeader) {\n return;\n }\n\n this.isLeader = true;\n this.logger.info('Became leader', this.loggerContext, {\n instanceId: this.instanceId,\n strategy: this.strategy,\n attempts: this.lockAcquireAttempts,\n });\n\n this.emit('leader:elected', {\n instanceId: this.instanceId,\n electedAt: this.leaderInfo?.electedAt,\n });\n\n // Start heartbeat\n this.startHeartbeat();\n }\n\n /**\n * Step down as leader\n */\n private async stepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.info('Stepping down as leader', this.loggerContext, {\n instanceId: this.instanceId,\n });\n\n this.isLeader = false;\n this.stopHeartbeat();\n\n // Release lock — only if we still own it, so we never delete a lock that\n // another instance has since acquired.\n try {\n if (this.strategy === 'file' && this.lockPath) {\n try {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId === this.instanceId) {\n await fs.promises.unlink(this.lockPath).catch(() => {});\n }\n } catch {\n // Lock file missing or unreadable — nothing to release.\n }\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-delete: only DEL if the stored lock still belongs to us.\n await this.redisClient.eval(\n LeaderElection.REDIS_RELEASE_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId\n );\n }\n } catch (error) {\n this.logger.error('Failed to release lock during step down', this.loggerContext, { error });\n }\n\n this.emit('leader:stepdown', { instanceId: this.instanceId });\n }\n\n /**\n * Start heartbeat to maintain leadership\n */\n private startHeartbeat(): void {\n if (this.heartbeatTimer) {\n return;\n }\n\n this.heartbeatTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n try {\n await this.sendHeartbeat();\n } catch (error) {\n this.logger.error('Heartbeat failed', this.loggerContext, { error });\n // Lost leadership\n await this.stepDown();\n // Try to reacquire\n const retryTimer = setTimeout(() => {\n void this.tryAcquireLeadership();\n }, 1000);\n retryTimer.unref(); // Don't keep process alive\n }\n })();\n }, this.heartbeatInterval);\n\n // Don't keep process alive\n this.heartbeatTimer.unref();\n }\n\n /**\n * Stop heartbeat\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n }\n }\n\n /**\n * Send heartbeat to maintain lock\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.leaderInfo) {\n return;\n }\n\n this.leaderInfo.lastHeartbeat = new Date();\n\n if (this.strategy === 'file' && this.lockPath) {\n // Verify we still own the lock before refreshing it. If the file is gone or\n // now owned by another instance, we have lost leadership — throw so the\n // heartbeat handler steps us down and attempts a clean re-acquire.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId !== this.instanceId) {\n throw new Error('Lost leadership: file lock is owned by another instance');\n }\n await this.atomicWriteLock(JSON.stringify(this.leaderInfo));\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-refresh: only rewrite + extend the TTL if we still own the lock.\n const refreshed = await this.redisClient.eval(\n LeaderElection.REDIS_REFRESH_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId,\n JSON.stringify(this.leaderInfo),\n this.lockTimeout\n );\n if (Number(refreshed) !== 1) {\n throw new Error('Lost leadership: redis lock is no longer owned by this instance');\n }\n }\n\n this.logger.debug('Heartbeat sent', this.loggerContext, { instanceId: this.instanceId });\n this.emit('leader:heartbeat', {\n instanceId: this.instanceId,\n timestamp: this.leaderInfo.lastHeartbeat,\n });\n }\n\n /**\n * Start periodic check for leadership changes\n */\n private startPeriodicCheck(): void {\n if (this.checkTimer) {\n return;\n }\n\n const checkInterval = Math.floor(this.heartbeatInterval * 1.5);\n\n this.checkTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n if (!this.isLeader) {\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n } else {\n // Verify we still hold the lock\n await this.verifyLeadership();\n }\n })();\n }, checkInterval);\n\n // Don't keep process alive\n this.checkTimer.unref();\n }\n\n /**\n * Stop periodic check\n */\n private stopPeriodicCheck(): void {\n if (this.checkTimer) {\n clearInterval(this.checkTimer);\n this.checkTimer = undefined;\n }\n }\n\n /**\n * Verify we still hold leadership\n */\n private async verifyLeadership(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n try {\n let stillLeader = false;\n\n if (this.strategy === 'file' && this.lockPath) {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n } else if (this.strategy === 'redis' && this.redisClient) {\n const lockData = await this.redisClient.get(LeaderElection.REDIS_LOCK_KEY);\n if (lockData) {\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n }\n }\n\n if (!stillLeader) {\n this.logger.warn('Lost leadership', this.loggerContext, { instanceId: this.instanceId });\n await this.stepDown();\n }\n } catch (error) {\n this.logger.error('Failed to verify leadership', this.loggerContext, { error });\n await this.stepDown();\n }\n }\n\n /**\n * Check health of current leader\n */\n private async checkLeaderHealth(): Promise<void> {\n if (!this.leaderInfo || this.leaderInfo.instanceId === this.instanceId) {\n return;\n }\n\n const heartbeatAge = Date.now() - new Date(this.leaderInfo.lastHeartbeat).getTime();\n\n if (heartbeatAge > this.lockTimeout) {\n this.logger.warn('Current leader appears unhealthy', this.loggerContext, {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n\n this.emit('leader:unhealthy', {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n }\n }\n\n /**\n * Get instance metadata\n */\n private getInstanceMetadata(): Record<string, any> {\n const metadata: Record<string, any> = {\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptime: process.uptime(),\n };\n\n // K8s detection\n if (process.env.KUBERNETES_SERVICE_HOST) {\n metadata.kubernetes = {\n podName: process.env.HOSTNAME || os.hostname(),\n namespace: process.env.POD_NAMESPACE,\n nodeName: process.env.NODE_NAME,\n };\n }\n\n // Container detection\n if (process.env.CONTAINER) {\n metadata.container = process.env.CONTAINER;\n }\n\n return metadata;\n }\n\n /**\n * Check if current instance is leader\n */\n public isCurrentLeader(): boolean {\n return this.isLeader;\n }\n\n /**\n * Get current leader info\n */\n public getLeaderInfo(): LeaderInfo | undefined {\n return this.leaderInfo ? { ...this.leaderInfo } : undefined;\n }\n\n /**\n * Get instance ID\n */\n public getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Force step down (for testing/admin)\n */\n public async forceStepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.warn('Forced step down requested', this.loggerContext, {\n instanceId: this.instanceId,\n });\n await this.stepDown();\n }\n\n /**\n * Shutdown and cleanup\n */\n public async shutdown(): Promise<void> {\n this.logger.info('LeaderElection shutting down...', this.loggerContext, {\n instanceId: this.instanceId,\n wasLeader: this.isLeader,\n });\n\n this.isShuttingDown = true;\n\n this.stopHeartbeat();\n this.stopPeriodicCheck();\n\n if (this.isLeader) {\n await this.stepDown();\n }\n\n this.removeAllListeners();\n this.logger.info('LeaderElection shutdown complete');\n }\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  // WebSocket Manager for Moro Framework
2
2
  // Manages WebSocket connections using pluggable adapters
3
- import * as zlib from 'zlib';
3
+ import { requireBuiltin } from '../utilities/builtin.js';
4
+ // zlib on first use (core/utilities/builtin.ts).
5
+ const zlibMod = () => requireBuiltin('zlib');
4
6
  import { CircuitBreaker } from '../utilities/index.js';
5
7
  import { createFrameworkLogger } from '../logger/index.js';
6
8
  import { normalizeValidationError } from '../validation/schema-interface.js';
@@ -234,7 +236,7 @@ export class WebSocketManager {
234
236
  if (buffer[0] === 0x1f && buffer[1] === 0x8b) {
235
237
  // This is gzipped data
236
238
  try {
237
- return zlib.gunzipSync(buffer);
239
+ return zlibMod().gunzipSync(buffer);
238
240
  }
239
241
  catch {
240
242
  return buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-manager.js","sourceRoot":"","sources":["../../../src/core/networking/websocket-manager.ts"],"names":[],"mappings":"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","sourcesContent":["// WebSocket Manager for Moro Framework\n// Manages WebSocket connections using pluggable adapters\nimport * as zlib from 'zlib';\nimport { Container } from '../utilities/index.js';\nimport { CircuitBreaker } from '../utilities/index.js';\nimport { ModuleConfig, WebSocketDefinition } from '../../types/module.js';\nimport { WebSocketAdapter, WebSocketNamespace, WebSocketConnection } from './websocket-adapter.js';\nimport { createFrameworkLogger } from '../logger/index.js';\nimport { normalizeValidationError } from '../validation/schema-interface.js';\n\nexport class WebSocketManager {\n private circuitBreakers = new Map<string, CircuitBreaker>();\n private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();\n private logger = createFrameworkLogger('WebSocketManager');\n\n constructor(\n private adapter: WebSocketAdapter,\n private container: Container\n ) {\n this.logger.info(`Initialized with ${adapter.getAdapterName()} adapter`, 'AdapterInit');\n }\n\n /**\n * Set custom ID generator for the adapter\n */\n setCustomIdGenerator(generator: () => string): void {\n this.adapter.setCustomIdGenerator(generator);\n }\n\n /**\n * Enable compression for the adapter\n */\n enableCompression(options?: any): void {\n this.adapter.setCompression(true, options);\n }\n\n /**\n * Get the underlying adapter\n */\n getAdapter(): WebSocketAdapter {\n return this.adapter;\n }\n\n /**\n * Get connection count across all namespaces\n */\n getConnectionCount(): number {\n return this.adapter.getConnectionCount();\n }\n\n /**\n * Create or get a namespace\n */\n getNamespace(namespace: string): WebSocketNamespace {\n return this.adapter.createNamespace(namespace);\n }\n\n /**\n * Register WebSocket handlers for a module\n */\n async registerHandler(\n namespace: WebSocketNamespace,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): Promise<void> {\n namespace.on('connection', (socket: WebSocketConnection) => {\n this.logger.debug(`New connection: ${socket.id}`, 'Connection', {\n namespace: namespace.constructor.name,\n module: moduleConfig.name,\n });\n\n this.setupSocketMiddleware(socket, moduleConfig.name);\n this.setupSocketHandlers(socket, wsConfig, moduleConfig);\n });\n }\n\n /**\n * Setup socket-specific middleware\n */\n private setupSocketMiddleware(socket: WebSocketConnection, moduleName: string): void {\n // Add module context to socket data\n socket.data.module = moduleName;\n socket.data.connectedAt = Date.now();\n\n // Setup heartbeat if supported\n if (socket.compressedEmit) {\n const heartbeatInterval = setInterval(() => {\n if (socket.connected) {\n socket.emit('heartbeat', { timestamp: Date.now() });\n } else {\n clearInterval(heartbeatInterval);\n }\n }, 30000); // 30 seconds\n\n socket.on('disconnect', () => {\n clearInterval(heartbeatInterval);\n });\n }\n\n // Log disconnection\n socket.on('disconnect', () => {\n this.logger.debug(`Socket disconnected: ${socket.id}`, 'Disconnect', {\n module: moduleName,\n duration: Date.now() - socket.data.connectedAt,\n });\n });\n }\n\n /**\n * Setup handlers for a specific WebSocket configuration\n */\n private setupSocketHandlers(\n socket: WebSocketConnection,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): void {\n socket.on(wsConfig.event, (data: any, callback?: CallableFunction) => {\n void (async () => {\n const handlerKey = `${moduleConfig.name}.${wsConfig.handler}`;\n\n try {\n // Rate limiting\n if (\n wsConfig.rateLimit &&\n !this.checkRateLimit(socket.id, handlerKey, wsConfig.rateLimit)\n ) {\n const error = {\n success: false,\n error: 'Rate limit exceeded',\n code: 'RATE_LIMIT',\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n\n // Universal validation (works with any ValidationSchema)\n if (wsConfig.validation) {\n try {\n data = await wsConfig.validation.parseAsync(data);\n } catch (validationError: any) {\n // Handle universal validation errors\n const normalizedError = normalizeValidationError(validationError);\n const error = {\n success: false,\n error: 'Validation failed',\n details: normalizedError.issues.map((issue: any) => ({\n field: issue.path.length > 0 ? issue.path.join('.') : 'data',\n message: issue.message,\n code: issue.code,\n })),\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n }\n\n // Circuit breaker protection\n const circuitBreaker = this.getCircuitBreaker(handlerKey);\n\n const result = await circuitBreaker.execute(async () => {\n const controller = this.container.resolve(moduleConfig.name);\n return await (controller as any)[wsConfig.handler](socket, data);\n });\n\n // Handle response\n if (callback) {\n callback({ success: true, data: result });\n } else if (result !== undefined) {\n socket.emit(`${wsConfig.event}:response`, { success: true, data: result });\n }\n\n // Handle room operations\n if (wsConfig.rooms) {\n wsConfig.rooms.forEach(room => socket.join(room));\n }\n\n // Handle broadcasting\n if (wsConfig.broadcast && result !== undefined) {\n socket.broadcast.emit(wsConfig.event, { success: true, data: result });\n }\n } catch (error) {\n this.logger.error('WebSocket handler error', 'HandlerError', {\n handler: handlerKey,\n error: error instanceof Error ? error.message : String(error),\n socketId: socket.id,\n });\n\n const errorResponse = {\n success: false,\n error: 'Internal server error',\n code: 'HANDLER_ERROR',\n };\n\n if (callback) callback(errorResponse);\n else socket.emit('error', errorResponse);\n }\n })();\n });\n }\n\n /**\n * Get or create circuit breaker for handler\n */\n private getCircuitBreaker(handlerKey: string): CircuitBreaker {\n if (!this.circuitBreakers.has(handlerKey)) {\n const circuitBreaker = new CircuitBreaker({\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n });\n this.circuitBreakers.set(handlerKey, circuitBreaker);\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.circuitBreakers.get(handlerKey)!;\n }\n\n /**\n * Check rate limit for socket and handler\n */\n private checkRateLimit(\n socketId: string,\n handlerKey: string,\n rateLimit: { requests: number; window: number }\n ): boolean {\n if (!this.rateLimiters.has(handlerKey)) {\n this.rateLimiters.set(handlerKey, new Map());\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const handlerLimiter = this.rateLimiters.get(handlerKey)!;\n const now = Date.now();\n const windowStart = now - rateLimit.window;\n\n // Clean old entries\n for (const [id, data] of handlerLimiter.entries()) {\n if (data.resetTime < windowStart) {\n handlerLimiter.delete(id);\n }\n }\n\n // Check current socket\n const socketData = handlerLimiter.get(socketId);\n if (!socketData) {\n handlerLimiter.set(socketId, { count: 1, resetTime: now });\n return true;\n }\n\n if (socketData.resetTime < windowStart) {\n socketData.count = 1;\n socketData.resetTime = now;\n return true;\n }\n\n if (socketData.count >= rateLimit.requests) {\n return false;\n }\n\n socketData.count++;\n return true;\n }\n\n /**\n * Process binary data efficiently\n */\n private processBinaryData(buffer: Buffer): any {\n // Example binary processing - can be extended based on needs\n if (buffer.length === 0) return buffer;\n\n // Simple compression check\n if (buffer[0] === 0x1f && buffer[1] === 0x8b) {\n // This is gzipped data\n try {\n return zlib.gunzipSync(buffer);\n } catch {\n return buffer;\n }\n }\n\n return buffer;\n }\n\n /**\n * Check if data should be compressed\n */\n private shouldCompress(data: any): boolean {\n if (typeof data === 'string') {\n return data.length > 1024;\n }\n if (Buffer.isBuffer(data)) {\n return data.length > 1024;\n }\n if (typeof data === 'object') {\n return JSON.stringify(data).length > 1024;\n }\n return false;\n }\n\n /**\n * Close the WebSocket manager and underlying adapter\n */\n async close(): Promise<void> {\n this.logger.info('Closing WebSocket manager', 'Shutdown');\n\n // Clear rate limiters\n this.rateLimiters.clear();\n\n // Clear circuit breakers\n this.circuitBreakers.clear();\n\n // Close adapter\n await this.adapter.close();\n }\n}\n"]}
1
+ {"version":3,"file":"websocket-manager.js","sourceRoot":"","sources":["../../../src/core/networking/websocket-manager.ts"],"names":[],"mappings":"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","sourcesContent":["// WebSocket Manager for Moro Framework\n// Manages WebSocket connections using pluggable adapters\nimport { requireBuiltin } from '../utilities/builtin.js';\n\n// zlib on first use (core/utilities/builtin.ts).\nconst zlibMod = () => requireBuiltin<typeof import('zlib')>('zlib');\nimport { Container } from '../utilities/index.js';\nimport { CircuitBreaker } from '../utilities/index.js';\nimport { ModuleConfig, WebSocketDefinition } from '../../types/module.js';\nimport { WebSocketAdapter, WebSocketNamespace, WebSocketConnection } from './websocket-adapter.js';\nimport { createFrameworkLogger } from '../logger/index.js';\nimport { normalizeValidationError } from '../validation/schema-interface.js';\n\nexport class WebSocketManager {\n private circuitBreakers = new Map<string, CircuitBreaker>();\n private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();\n private logger = createFrameworkLogger('WebSocketManager');\n\n constructor(\n private adapter: WebSocketAdapter,\n private container: Container\n ) {\n this.logger.info(`Initialized with ${adapter.getAdapterName()} adapter`, 'AdapterInit');\n }\n\n /**\n * Set custom ID generator for the adapter\n */\n setCustomIdGenerator(generator: () => string): void {\n this.adapter.setCustomIdGenerator(generator);\n }\n\n /**\n * Enable compression for the adapter\n */\n enableCompression(options?: any): void {\n this.adapter.setCompression(true, options);\n }\n\n /**\n * Get the underlying adapter\n */\n getAdapter(): WebSocketAdapter {\n return this.adapter;\n }\n\n /**\n * Get connection count across all namespaces\n */\n getConnectionCount(): number {\n return this.adapter.getConnectionCount();\n }\n\n /**\n * Create or get a namespace\n */\n getNamespace(namespace: string): WebSocketNamespace {\n return this.adapter.createNamespace(namespace);\n }\n\n /**\n * Register WebSocket handlers for a module\n */\n async registerHandler(\n namespace: WebSocketNamespace,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): Promise<void> {\n namespace.on('connection', (socket: WebSocketConnection) => {\n this.logger.debug(`New connection: ${socket.id}`, 'Connection', {\n namespace: namespace.constructor.name,\n module: moduleConfig.name,\n });\n\n this.setupSocketMiddleware(socket, moduleConfig.name);\n this.setupSocketHandlers(socket, wsConfig, moduleConfig);\n });\n }\n\n /**\n * Setup socket-specific middleware\n */\n private setupSocketMiddleware(socket: WebSocketConnection, moduleName: string): void {\n // Add module context to socket data\n socket.data.module = moduleName;\n socket.data.connectedAt = Date.now();\n\n // Setup heartbeat if supported\n if (socket.compressedEmit) {\n const heartbeatInterval = setInterval(() => {\n if (socket.connected) {\n socket.emit('heartbeat', { timestamp: Date.now() });\n } else {\n clearInterval(heartbeatInterval);\n }\n }, 30000); // 30 seconds\n\n socket.on('disconnect', () => {\n clearInterval(heartbeatInterval);\n });\n }\n\n // Log disconnection\n socket.on('disconnect', () => {\n this.logger.debug(`Socket disconnected: ${socket.id}`, 'Disconnect', {\n module: moduleName,\n duration: Date.now() - socket.data.connectedAt,\n });\n });\n }\n\n /**\n * Setup handlers for a specific WebSocket configuration\n */\n private setupSocketHandlers(\n socket: WebSocketConnection,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): void {\n socket.on(wsConfig.event, (data: any, callback?: CallableFunction) => {\n void (async () => {\n const handlerKey = `${moduleConfig.name}.${wsConfig.handler}`;\n\n try {\n // Rate limiting\n if (\n wsConfig.rateLimit &&\n !this.checkRateLimit(socket.id, handlerKey, wsConfig.rateLimit)\n ) {\n const error = {\n success: false,\n error: 'Rate limit exceeded',\n code: 'RATE_LIMIT',\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n\n // Universal validation (works with any ValidationSchema)\n if (wsConfig.validation) {\n try {\n data = await wsConfig.validation.parseAsync(data);\n } catch (validationError: any) {\n // Handle universal validation errors\n const normalizedError = normalizeValidationError(validationError);\n const error = {\n success: false,\n error: 'Validation failed',\n details: normalizedError.issues.map((issue: any) => ({\n field: issue.path.length > 0 ? issue.path.join('.') : 'data',\n message: issue.message,\n code: issue.code,\n })),\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n }\n\n // Circuit breaker protection\n const circuitBreaker = this.getCircuitBreaker(handlerKey);\n\n const result = await circuitBreaker.execute(async () => {\n const controller = this.container.resolve(moduleConfig.name);\n return await (controller as any)[wsConfig.handler](socket, data);\n });\n\n // Handle response\n if (callback) {\n callback({ success: true, data: result });\n } else if (result !== undefined) {\n socket.emit(`${wsConfig.event}:response`, { success: true, data: result });\n }\n\n // Handle room operations\n if (wsConfig.rooms) {\n wsConfig.rooms.forEach(room => socket.join(room));\n }\n\n // Handle broadcasting\n if (wsConfig.broadcast && result !== undefined) {\n socket.broadcast.emit(wsConfig.event, { success: true, data: result });\n }\n } catch (error) {\n this.logger.error('WebSocket handler error', 'HandlerError', {\n handler: handlerKey,\n error: error instanceof Error ? error.message : String(error),\n socketId: socket.id,\n });\n\n const errorResponse = {\n success: false,\n error: 'Internal server error',\n code: 'HANDLER_ERROR',\n };\n\n if (callback) callback(errorResponse);\n else socket.emit('error', errorResponse);\n }\n })();\n });\n }\n\n /**\n * Get or create circuit breaker for handler\n */\n private getCircuitBreaker(handlerKey: string): CircuitBreaker {\n if (!this.circuitBreakers.has(handlerKey)) {\n const circuitBreaker = new CircuitBreaker({\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n });\n this.circuitBreakers.set(handlerKey, circuitBreaker);\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.circuitBreakers.get(handlerKey)!;\n }\n\n /**\n * Check rate limit for socket and handler\n */\n private checkRateLimit(\n socketId: string,\n handlerKey: string,\n rateLimit: { requests: number; window: number }\n ): boolean {\n if (!this.rateLimiters.has(handlerKey)) {\n this.rateLimiters.set(handlerKey, new Map());\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const handlerLimiter = this.rateLimiters.get(handlerKey)!;\n const now = Date.now();\n const windowStart = now - rateLimit.window;\n\n // Clean old entries\n for (const [id, data] of handlerLimiter.entries()) {\n if (data.resetTime < windowStart) {\n handlerLimiter.delete(id);\n }\n }\n\n // Check current socket\n const socketData = handlerLimiter.get(socketId);\n if (!socketData) {\n handlerLimiter.set(socketId, { count: 1, resetTime: now });\n return true;\n }\n\n if (socketData.resetTime < windowStart) {\n socketData.count = 1;\n socketData.resetTime = now;\n return true;\n }\n\n if (socketData.count >= rateLimit.requests) {\n return false;\n }\n\n socketData.count++;\n return true;\n }\n\n /**\n * Process binary data efficiently\n */\n private processBinaryData(buffer: Buffer): any {\n // Example binary processing - can be extended based on needs\n if (buffer.length === 0) return buffer;\n\n // Simple compression check\n if (buffer[0] === 0x1f && buffer[1] === 0x8b) {\n // This is gzipped data\n try {\n return zlibMod().gunzipSync(buffer);\n } catch {\n return buffer;\n }\n }\n\n return buffer;\n }\n\n /**\n * Check if data should be compressed\n */\n private shouldCompress(data: any): boolean {\n if (typeof data === 'string') {\n return data.length > 1024;\n }\n if (Buffer.isBuffer(data)) {\n return data.length > 1024;\n }\n if (typeof data === 'object') {\n return JSON.stringify(data).length > 1024;\n }\n return false;\n }\n\n /**\n * Close the WebSocket manager and underlying adapter\n */\n async close(): Promise<void> {\n this.logger.info('Closing WebSocket manager', 'Shutdown');\n\n // Clear rate limiters\n this.rateLimiters.clear();\n\n // Clear circuit breakers\n this.circuitBreakers.clear();\n\n // Close adapter\n await this.adapter.close();\n }\n}\n"]}
@@ -611,7 +611,9 @@ export class UnifiedRouter {
611
611
  break;
612
612
  const beforeLen = beforeMw.length;
613
613
  for (let i = 0; i < beforeLen; i++) {
614
- await this.executeMiddleware(beforeMw[i], req, res);
614
+ const r = this.executeMiddleware(beforeMw[i], req, res);
615
+ if (r)
616
+ await r;
615
617
  if (res.headersSent)
616
618
  return;
617
619
  }
@@ -637,7 +639,9 @@ export class UnifiedRouter {
637
639
  }),
638
640
  });
639
641
  }
640
- await this.executeMiddleware(route.authMiddleware, req, res);
642
+ const r = this.executeMiddleware(route.authMiddleware, req, res);
643
+ if (r)
644
+ await r;
641
645
  }
642
646
  break;
643
647
  case 'validation':
@@ -658,7 +662,9 @@ export class UnifiedRouter {
658
662
  break;
659
663
  const transformLen = transformMw.length;
660
664
  for (let i = 0; i < transformLen; i++) {
661
- await this.executeMiddleware(transformMw[i], req, res);
665
+ const r = this.executeMiddleware(transformMw[i], req, res);
666
+ if (r)
667
+ await r;
662
668
  if (res.headersSent)
663
669
  return;
664
670
  }
@@ -682,7 +688,9 @@ export class UnifiedRouter {
682
688
  break;
683
689
  const afterLen = afterMw.length;
684
690
  for (let i = 0; i < afterLen; i++) {
685
- await this.executeMiddleware(afterMw[i], req, res);
691
+ const r = this.executeMiddleware(afterMw[i], req, res);
692
+ if (r)
693
+ await r;
686
694
  if (res.headersSent)
687
695
  return;
688
696
  }
@@ -695,7 +703,9 @@ export class UnifiedRouter {
695
703
  break;
696
704
  const middlewareLen = middleware.length;
697
705
  for (let i = 0; i < middlewareLen; i++) {
698
- await this.executeMiddleware(middleware[i], req, res);
706
+ const r = this.executeMiddleware(middleware[i], req, res);
707
+ if (r)
708
+ await r;
699
709
  if (res.headersSent)
700
710
  return;
701
711
  }
@@ -703,31 +713,52 @@ export class UnifiedRouter {
703
713
  }
704
714
  }
705
715
  }
706
- async executeMiddleware(middleware, req, res) {
716
+ // Sync-aware: a middleware that returns a non-thenable always settled before
717
+ // the caller could await it (next() called, or the auto-advance below), so
718
+ // the promise was pure overhead - one allocation plus an async suspension per
719
+ // middleware per request. Returns undefined in that case; callers skip the
720
+ // await. A promise is only built when the middleware is genuinely async.
721
+ // Error/advance semantics are unchanged.
722
+ executeMiddleware(middleware, req, res) {
723
+ let resolved = false;
724
+ let settle;
725
+ const next = () => {
726
+ if (resolved)
727
+ return;
728
+ resolved = true;
729
+ if (settle)
730
+ settle();
731
+ };
732
+ let result;
733
+ try {
734
+ result = middleware(req, res, next);
735
+ }
736
+ catch (error) {
737
+ // Matches the previous promise form: a throw after next() already
738
+ // advanced the chain was swallowed; otherwise it rejects/throws.
739
+ if (resolved)
740
+ return;
741
+ resolved = true;
742
+ throw error;
743
+ }
744
+ // Duck typing faster than instanceof
745
+ const isThenable = result && typeof result.then === 'function';
746
+ if (!isThenable) {
747
+ // Sync middleware: previously this either resolved via next() or was
748
+ // auto-advanced - settled either way before any await could observe it.
749
+ next();
750
+ return;
751
+ }
752
+ if (resolved) {
753
+ // next() ran synchronously, so the chain already advanced. Keep the
754
+ // returned promise's rejection handled, exactly as the old .catch(reject)
755
+ // did on an already-resolved promise, so it never surfaces as unhandled.
756
+ result.then(undefined, () => { });
757
+ return;
758
+ }
707
759
  return new Promise((resolve, reject) => {
708
- let resolved = false;
709
- const next = () => {
710
- if (!resolved) {
711
- resolved = true;
712
- resolve();
713
- }
714
- };
715
- try {
716
- const result = middleware(req, res, next);
717
- // Duck typing faster than instanceof
718
- if (result && typeof result.then === 'function') {
719
- result.then(() => !resolved && next()).catch(reject);
720
- }
721
- else if (!resolved) {
722
- next();
723
- }
724
- }
725
- catch (error) {
726
- if (!resolved) {
727
- resolved = true;
728
- reject(error);
729
- }
730
- }
760
+ settle = resolve;
761
+ result.then(() => next(), reject);
731
762
  });
732
763
  }
733
764
  // ===== Helper Methods =====