@rapidrest/core 1.14.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/AlertUtils.js +71 -56
- package/dist/lib/AlertUtils.js.map +1 -1
- package/dist/lib/ApiError.js +8 -1
- package/dist/lib/ApiError.js.map +1 -1
- package/dist/lib/CacheUtils.js +23 -0
- package/dist/lib/CacheUtils.js.map +1 -0
- package/dist/lib/ClassLoader.js +48 -44
- package/dist/lib/ClassLoader.js.map +1 -1
- package/dist/lib/FileUtils.js +133 -60
- package/dist/lib/FileUtils.js.map +1 -1
- package/dist/lib/JWTUtils.js +250 -145
- package/dist/lib/JWTUtils.js.map +1 -1
- package/dist/lib/Logger.js +39 -7
- package/dist/lib/Logger.js.map +1 -1
- package/dist/lib/MemoryStore.js +8 -6
- package/dist/lib/MemoryStore.js.map +1 -1
- package/dist/lib/MessagingUtils.js +71 -35
- package/dist/lib/MessagingUtils.js.map +1 -1
- package/dist/lib/NotificationsUtils.js +60 -8
- package/dist/lib/NotificationsUtils.js.map +1 -1
- package/dist/lib/OASUtils.js +67 -18
- package/dist/lib/OASUtils.js.map +1 -1
- package/dist/lib/ObjectFactory.js +151 -63
- package/dist/lib/ObjectFactory.js.map +1 -1
- package/dist/lib/ObjectUtils.js +48 -7
- package/dist/lib/ObjectUtils.js.map +1 -1
- package/dist/lib/StringUtils.js +55 -21
- package/dist/lib/StringUtils.js.map +1 -1
- package/dist/lib/TelemetryUtils.js +41 -5
- package/dist/lib/TelemetryUtils.js.map +1 -1
- package/dist/lib/UserUtils.js +5 -6
- package/dist/lib/UserUtils.js.map +1 -1
- package/dist/lib/ValidationUtils.js +16 -4
- package/dist/lib/ValidationUtils.js.map +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/threads/ThreadPool.js +198 -58
- package/dist/lib/threads/ThreadPool.js.map +1 -1
- package/dist/types/AlertUtils.d.ts +4 -0
- package/dist/types/CacheUtils.d.ts +14 -0
- package/dist/types/ClassLoader.d.ts +10 -0
- package/dist/types/FileUtils.d.ts +23 -7
- package/dist/types/JWTUtils.d.ts +88 -18
- package/dist/types/MessagingUtils.d.ts +7 -1
- package/dist/types/NotificationsUtils.d.ts +30 -0
- package/dist/types/ObjectFactory.d.ts +25 -1
- package/dist/types/ObjectUtils.d.ts +14 -0
- package/dist/types/StringUtils.d.ts +8 -0
- package/dist/types/TelemetryUtils.d.ts +19 -1
- package/dist/types/ValidationUtils.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/threads/ThreadPool.d.ts +34 -2
- package/package.json +1 -1
|
@@ -21,10 +21,20 @@ export class Event {
|
|
|
21
21
|
this.origin = serviceName ? `${serviceName}:${config.get("version")}` : "unknown";
|
|
22
22
|
this.type = data.type;
|
|
23
23
|
this.userId = userId;
|
|
24
|
-
// Copy all fields in data to this object
|
|
25
24
|
const tmp = data;
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const protectedFields = new Set([
|
|
26
|
+
"environment",
|
|
27
|
+
"origin",
|
|
28
|
+
"uid",
|
|
29
|
+
"userId",
|
|
30
|
+
"__proto__",
|
|
31
|
+
"constructor",
|
|
32
|
+
"prototype",
|
|
33
|
+
]);
|
|
34
|
+
for (const key of Object.keys(tmp)) {
|
|
35
|
+
// We intentionally exclude only these fields from copying here. All others, including timestamp
|
|
36
|
+
// the caller can override in order to provide more accurate information about the event.
|
|
37
|
+
if (!protectedFields.has(key)) {
|
|
28
38
|
this[key] = tmp[key];
|
|
29
39
|
}
|
|
30
40
|
}
|
|
@@ -40,6 +50,14 @@ export class Event {
|
|
|
40
50
|
* The `on` function allows code within the same application or service to listen for outgoing events that have been
|
|
41
51
|
* sent via the `record` function.
|
|
42
52
|
*
|
|
53
|
+
* The utility uses an application specific JWT access token for sending telemetry events. The purpose of using an
|
|
54
|
+
* application specific JWT token versus user token is to identify a potentially compromised application server. This
|
|
55
|
+
* also allows the server to send telemetry events on behalf of the user that the user may not otherwise have permission
|
|
56
|
+
* to send.
|
|
57
|
+
*
|
|
58
|
+
* Note: This class is considered a runtime singleton and intentionally uses static state. Do not initialize this
|
|
59
|
+
* class more than once in your application.
|
|
60
|
+
*
|
|
43
61
|
* @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
|
|
44
62
|
*/
|
|
45
63
|
export class EventUtils {
|
|
@@ -48,7 +66,7 @@ export class EventUtils {
|
|
|
48
66
|
*
|
|
49
67
|
* @param config The application configuration to use.
|
|
50
68
|
* @param logger The logging utility to use.
|
|
51
|
-
* @param jwtToken The
|
|
69
|
+
* @param jwtToken The application's JWT token to send telemetry events on behalf of.
|
|
52
70
|
*/
|
|
53
71
|
static async init(config, logger, jwtToken) {
|
|
54
72
|
EventUtils.config = config;
|
|
@@ -86,7 +104,7 @@ export class EventUtils {
|
|
|
86
104
|
const response = await axios.post(url, event, {
|
|
87
105
|
headers: {
|
|
88
106
|
Authorization: "jwt " + EventUtils.token,
|
|
89
|
-
}
|
|
107
|
+
},
|
|
90
108
|
});
|
|
91
109
|
}
|
|
92
110
|
else {
|
|
@@ -132,6 +150,24 @@ export class EventUtils {
|
|
|
132
150
|
EventUtils.listeners.set(type, callbacks);
|
|
133
151
|
}
|
|
134
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Unregisters a `callback` previously registered via `on()` for the given event `type`. Since `EventUtils` is
|
|
155
|
+
* a long-lived process-wide singleton, any listener registered dynamically (rather than once at startup) must
|
|
156
|
+
* eventually be removed via this method or its closure - and anything it captures - is retained for the life
|
|
157
|
+
* of the process.
|
|
158
|
+
*
|
|
159
|
+
* @param type The type of event the callback was registered for.
|
|
160
|
+
* @param callback The exact function reference passed to `on()`.
|
|
161
|
+
*/
|
|
162
|
+
static off(type, callback) {
|
|
163
|
+
const callbacks = EventUtils.listeners.get(type);
|
|
164
|
+
if (callbacks) {
|
|
165
|
+
const idx = callbacks.indexOf(callback);
|
|
166
|
+
if (idx >= 0) {
|
|
167
|
+
callbacks.splice(idx, 1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
135
171
|
}
|
|
136
172
|
EventUtils.initialized = false;
|
|
137
173
|
EventUtils.listeners = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelemetryUtils.js","sourceRoot":"","sources":["../../src/TelemetryUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAC/E,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAYzC;;;;GAIG;AACH,MAAM,OAAO,KAAK;IA+Bd,YAAY,MAAW,EAAE,MAAc,EAAE,IAAc;QAfvD;;WAEG;QACa,cAAS,GAAS,IAAI,IAAI,EAAE,CAAC;QAazC,IAAI,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC;QACpB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACxE,MAAM,WAAW,GAAW,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,
|
|
1
|
+
{"version":3,"file":"TelemetryUtils.js","sourceRoot":"","sources":["../../src/TelemetryUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAC/E,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAYzC;;;;GAIG;AACH,MAAM,OAAO,KAAK;IA+Bd,YAAY,MAAW,EAAE,MAAc,EAAE,IAAc;QAfvD;;WAEG;QACa,cAAS,GAAS,IAAI,IAAI,EAAE,CAAC;QAazC,IAAI,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC;QACpB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACxE,MAAM,WAAW,GAAW,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,GAAG,GAAQ,IAAW,CAAC;QAC7B,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;YAC5B,aAAa;YACb,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,aAAa;YACb,WAAW;SACd,CAAC,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,gGAAgG;YAChG,yFAAyF;YACzF,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,IAAY,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC;QACL,CAAC;IACL,CAAC;CACJ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,UAAU;IAQnB;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAW,EAAE,MAAW,EAAE,QAAgB;QAC/D,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QAC3B,UAAU,CAAC,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QACjC,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QAC3B,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC;QAE5B,qCAAqC;QACrC,MAAM,OAAO,GAAQ,MAAM,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAExC,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAmB,EAAE,IAAa;QACzD,IAAI,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;YAC5F,CAAC;YAED,IAAI,IAAI,EAAE,CAAC;gBACP,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YACpB,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC5D,CAAC;YAED,0DAA0D;YAC1D,MAAM,KAAK,GAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEvG,gCAAgC;YAChC,MAAM,OAAO,GAAW,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACxE,IAAI,OAAO,EAAE,CAAC;gBACV,MAAM,GAAG,GAAW,OAAO,GAAG,SAAS,CAAC;gBACxC,MAAM,QAAQ,GAAkB,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE;oBACzD,OAAO,EAAE;wBACL,aAAa,EAAE,MAAM,GAAG,UAAU,CAAC,KAAK;qBAC3C;iBACJ,CAAC,CAAC;YACP,CAAC;iBAAM,CAAC;gBACJ,UAAU,CAAC,MAAM,EAAE,KAAK,CACpB,wFAAwF,CAC3F,CAAC;YACN,CAAC;YAED,kCAAkC;YAClC,MAAM,SAAS,GAA2B,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7E,IAAI,SAAS,EAAE,CAAC;gBACZ,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;oBACzB,IAAI,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,0CAA0C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1E,CAAC;iBAAM,CAAC;gBACJ,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACpF,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,EAAE,CAAC,IAAY,EAAE,QAAkB;QAC7C,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC5F,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,SAAS,GAA2B,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzE,sGAAsG;QACtG,yDAAyD;QACzD,sBAAsB;QACtB,IAAI,SAAS,EAAE,CAAC;YACZ,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzB,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,QAAkB;QAC9C,MAAM,SAAS,GAA2B,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzE,IAAI,SAAS,EAAE,CAAC;YACZ,MAAM,GAAG,GAAW,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;gBACX,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC7B,CAAC;QACL,CAAC;IACL,CAAC;;AA3Hc,sBAAW,GAAY,KAAK,CAAC;AAE7B,oBAAS,GAA4B,IAAI,GAAG,EAAE,CAAC"}
|
package/dist/lib/UserUtils.js
CHANGED
|
@@ -34,12 +34,10 @@ export class UserUtils {
|
|
|
34
34
|
* @param organizationUids The list of universally unique identifiers to search for.
|
|
35
35
|
*/
|
|
36
36
|
static hasOrganizations(user, organizationUids) {
|
|
37
|
-
if (user &&
|
|
37
|
+
if (user && Array.isArray(organizationUids)) {
|
|
38
38
|
for (const organizationUid of organizationUids) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
39
|
+
if (UserUtils.hasOrganization(user, organizationUid)) {
|
|
40
|
+
return true;
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
}
|
|
@@ -59,6 +57,7 @@ export class UserUtils {
|
|
|
59
57
|
const parts = externalId.split(":");
|
|
60
58
|
if (parts.length === 2 && parts[0] === type) {
|
|
61
59
|
result = parts[1];
|
|
60
|
+
break;
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
}
|
|
@@ -77,7 +76,7 @@ export class UserUtils {
|
|
|
77
76
|
if (user.roles) {
|
|
78
77
|
if (Array.isArray(user.roles)) {
|
|
79
78
|
result = user.roles.includes(role);
|
|
80
|
-
if (!result && orgUid
|
|
79
|
+
if (!result && orgUid) {
|
|
81
80
|
result = user.roles.includes(`${orgUid}.${role}`);
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserUtils.js","sourceRoot":"","sources":["../../src/UserUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAE/E;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAS;IAClB;;;;OAIG;IACI,MAAM,CAAC,eAAe,CAAC,IAAS,EAAE,eAAuB;QAC5D,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC1B,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;oBAC1B,OAAO,IAAI,CAAC;gBAChB,CAAC;YACL,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,gBAAgB,CAAC,IAAS,EAAE,gBAA2B;QACjE,IAAI,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"UserUtils.js","sourceRoot":"","sources":["../../src/UserUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,10 +43,10 @@ export class ValidationUtils {
|
|
|
43
43
|
return val;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Validates that the provided array is not empty.
|
|
46
|
+
* Validates that the provided array is not empty (nor `null`/`undefined`).
|
|
47
47
|
*/
|
|
48
48
|
static checkEmpty(val) {
|
|
49
|
-
if (val
|
|
49
|
+
if (!val || val.length === 0) {
|
|
50
50
|
throw new Error("Value cannot be empty.");
|
|
51
51
|
}
|
|
52
52
|
return val;
|
|
@@ -82,7 +82,9 @@ export class ValidationUtils {
|
|
|
82
82
|
* Validates that the provided object is not null or empty.
|
|
83
83
|
*/
|
|
84
84
|
static checkNull(val) {
|
|
85
|
-
|
|
85
|
+
// `0` is a valid, non-null value (matching `ObjectUtils.validate`'s own nullable convention), so numbers
|
|
86
|
+
// are excluded from the falsy check alongside booleans.
|
|
87
|
+
if (typeof val !== "boolean" && typeof val !== "number" && !val) {
|
|
86
88
|
throw new Error("Value cannot be null.");
|
|
87
89
|
}
|
|
88
90
|
return val;
|
|
@@ -127,7 +129,17 @@ export class ValidationUtils {
|
|
|
127
129
|
* Validates that the provided value is an entity `version` number (e.g. `value > 0`).
|
|
128
130
|
*/
|
|
129
131
|
static checkVersion(val) {
|
|
130
|
-
|
|
132
|
+
// `Number()` coerces several "empty" inputs - `null`, `""`/whitespace-only strings, `[]` - to `0`
|
|
133
|
+
// rather than `NaN`, which would otherwise let missing/malformed input silently through as if it were
|
|
134
|
+
// a legitimate version `0` instead of being rejected below. `undefined` needs no such guard since
|
|
135
|
+
// `Number(undefined)` already evaluates to `NaN`.
|
|
136
|
+
const isEmpty = val === null ||
|
|
137
|
+
(typeof val === "string" && val.trim() === "") ||
|
|
138
|
+
(Array.isArray(val) && val.length === 0);
|
|
139
|
+
const num = isEmpty ? NaN : Number(val);
|
|
140
|
+
if (isNaN(num)) {
|
|
141
|
+
throw new Error("Value is not a valid version number.");
|
|
142
|
+
}
|
|
131
143
|
return Math.max(num, 0);
|
|
132
144
|
}
|
|
133
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidationUtils.js","sourceRoot":"","sources":["../../src/ValidationUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAC/E,OAAO,SAAS,MAAM,WAAW,CAAC;AAOlC;;GAEG;AACH,MAAM,OAAO,eAAe;IACxB;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,GAAQ,EAAE,IAAiB;QAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAW;QAC/B,8FAA8F;QAC9F,8FAA8F;QAC9F,2FAA2F;QAC3F,oDAAoD;QACpD,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CAAC,GAAW;QAChC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CAAC,GAAe;QACpC,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"ValidationUtils.js","sourceRoot":"","sources":["../../src/ValidationUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAC/E,OAAO,SAAS,MAAM,WAAW,CAAC;AAOlC;;GAEG;AACH,MAAM,OAAO,eAAe;IACxB;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,GAAQ,EAAE,IAAiB;QAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAW;QAC/B,8FAA8F;QAC9F,8FAA8F;QAC9F,2FAA2F;QAC3F,oDAAoD;QACpD,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CAAC,GAAW;QAChC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CAAC,GAAe;QACpC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,OAAO,CAAC,GAAW;QAC7B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAW;QAC/B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAW;QAC/B,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAQ;QAC5B,yGAAyG;QACzG,wDAAwD;QACxD,IAAI,OAAO,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;YAC9D,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CAAC,GAAW;QAChC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,WAAW,CAAC,GAAW;QACjC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,QAAQ,CAAC,GAAW;QAC9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,SAAS,CAAC,GAAW;QAC/B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,YAAY,CAAC,GAAQ;QAC/B,kGAAkG;QAClG,sGAAsG;QACtG,kGAAkG;QAClG,kDAAkD;QAClD,MAAM,OAAO,GACT,GAAG,KAAK,IAAI;YACZ,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;YAC9C,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAW,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC;CACJ"}
|
package/dist/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./ApiError.js";
|
|
2
2
|
export * from "./AlertUtils.js";
|
|
3
|
+
export * from "./CacheUtils.js";
|
|
3
4
|
export * from "./ClassLoader.js";
|
|
4
5
|
export * from "./decorators/index.js";
|
|
5
6
|
export * from "./FileUtils.js";
|
|
@@ -7,6 +8,7 @@ export * from "./JWTUtils.js";
|
|
|
7
8
|
export * from "./Logger.js";
|
|
8
9
|
export * from "./MemoryStore.js";
|
|
9
10
|
export * from "./MessagingUtils.js";
|
|
11
|
+
export * from "./NotificationsUtils.js";
|
|
10
12
|
export * from "./ObjectFactory.js";
|
|
11
13
|
export * from "./ObjectUtils.js";
|
|
12
14
|
export * from "./OASUtils.js";
|
package/dist/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
|
|
@@ -67,6 +67,10 @@ export class ThreadPool {
|
|
|
67
67
|
constructor(max = 0, logger) {
|
|
68
68
|
/** The maximum number of threads allowed. */
|
|
69
69
|
this.maxThreads = os.cpus().length;
|
|
70
|
+
/** Tracks workers whose "exit" event has already fired, so a dead worker left in `workers` (e.g. exited
|
|
71
|
+
* without `restartOnExit`) is never sent a message via `postMessage()`, which throws synchronously once a
|
|
72
|
+
* worker has exited. */
|
|
73
|
+
this.exitedWorkers = new WeakSet();
|
|
70
74
|
// The `os.cpus().length` fallback can never actually be selected: `this.maxThreads`'s field initializer
|
|
71
75
|
// already evaluates to that same (always-truthy on a real OS) value before the constructor body runs, so
|
|
72
76
|
// it's only falsy when `os.cpus().length` itself is falsy too. Kept as defense in depth.
|
|
@@ -78,7 +82,28 @@ export class ThreadPool {
|
|
|
78
82
|
this.shutdown = false;
|
|
79
83
|
this.workers = new Array();
|
|
80
84
|
}
|
|
81
|
-
|
|
85
|
+
/**
|
|
86
|
+
* @param onRestart Invoked with the replacement `Worker` whenever `restartOnExit` recreates the worker for
|
|
87
|
+
* `idx`. Lets `start()` re-attach its readiness-tracking listeners to the replacement - without this, a
|
|
88
|
+
* worker that crashes and is restarted before ever reporting ready would leave `start()`'s returned promise
|
|
89
|
+
* hanging forever, since nothing would be listening for the *replacement's* readiness signal.
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* Invokes every callback registered for `type` via `on()`, passing `idx` and optional `data`. Centralizes
|
|
93
|
+
* the "look up listeners, no-op if none registered" dispatch idiom shared by every worker event handler in
|
|
94
|
+
* `createWorker()`/`stop()` below, so a future change to how listeners are invoked - or a new message type
|
|
95
|
+
* that needs routing to a given event - only has to be made in one place instead of risking a hand-copied
|
|
96
|
+
* site being missed (as happened with WorkerMessageType.ERROR previously falling through to "message").
|
|
97
|
+
*/
|
|
98
|
+
dispatch(type, idx, data) {
|
|
99
|
+
const listeners = this.callbacks.get(type);
|
|
100
|
+
if (listeners) {
|
|
101
|
+
for (const callback of listeners) {
|
|
102
|
+
callback(idx, data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
createWorker(idx, options, onRestart) {
|
|
82
107
|
if (!options.entry || !!options.worker) {
|
|
83
108
|
options.entry = path.join(_dirname, "ThreadWorkerEntry.js");
|
|
84
109
|
}
|
|
@@ -88,31 +113,48 @@ export class ThreadPool {
|
|
|
88
113
|
};
|
|
89
114
|
this.logger?.debug(`Creating thread worker: ${JSON.stringify(options)}`);
|
|
90
115
|
const worker = new Worker(options.entry, workerOptions);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
// Dispatches registered "online" callbacks for entry-mode pools, which have no other way to signal
|
|
117
|
+
// readiness at the application level. Guarded to worker-mode's exclusion (`!options.worker`) so a
|
|
118
|
+
// worker-mode pool - which reports readiness via the WorkerMessageType.ONLINE message handled below -
|
|
119
|
+
// doesn't fire "online" callbacks twice for the same worker.
|
|
120
|
+
worker.on("online", () => {
|
|
121
|
+
if (options && !options.worker) {
|
|
122
|
+
this.dispatch("online", idx);
|
|
97
123
|
}
|
|
98
124
|
});
|
|
125
|
+
worker.on("error", (error) => {
|
|
126
|
+
this.dispatch("error", idx, error);
|
|
127
|
+
});
|
|
99
128
|
worker.on("exit", async (code) => {
|
|
129
|
+
// Mark this worker instance as exited regardless of shutdown/restart state so send()/sendAll()/
|
|
130
|
+
// sendTo() never call postMessage() on it - postMessage() throws synchronously once a worker has
|
|
131
|
+
// exited, and a restarted worker gets a brand new Worker instance anyway so marking the old one here
|
|
132
|
+
// is harmless.
|
|
133
|
+
this.exitedWorkers.add(worker);
|
|
100
134
|
// While stop() is in progress it fires "exit" callbacks itself, using the authoritative exit code
|
|
101
135
|
// returned by terminate() - which is what triggers this very event. Firing them again here would
|
|
102
136
|
// invoke every registered "exit" callback twice per worker for a single stop() call.
|
|
103
137
|
if (!this.shutdown) {
|
|
104
|
-
|
|
105
|
-
if (listeners) {
|
|
106
|
-
for (const callback of listeners) {
|
|
107
|
-
callback(idx, code);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
138
|
+
this.dispatch("exit", idx, code);
|
|
110
139
|
}
|
|
111
140
|
// Restart worker thread
|
|
112
141
|
if (options?.restartOnExit && !this.shutdown) {
|
|
113
142
|
worker.removeAllListeners();
|
|
114
|
-
|
|
115
|
-
|
|
143
|
+
// createWorker() calls `new Worker(...)`, which can throw synchronously (e.g. non-cloneable
|
|
144
|
+
// workerData, thread-limit/OOM errors) - especially likely in a crash-loop scenario, which is
|
|
145
|
+
// exactly when restartOnExit is most active. This listener is itself async, and Node does not
|
|
146
|
+
// attach a rejection handler to an EventEmitter listener's returned promise, so an uncaught throw
|
|
147
|
+
// here would become an unhandled rejection capable of crashing the whole host process. Routing
|
|
148
|
+
// the failure to "error" listeners instead keeps it recoverable, consistent with how every other
|
|
149
|
+
// worker failure in this file is reported.
|
|
150
|
+
try {
|
|
151
|
+
const newWorker = this.createWorker(idx, options, onRestart);
|
|
152
|
+
this.workers[idx] = newWorker;
|
|
153
|
+
onRestart?.(newWorker);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.dispatch("error", idx, error);
|
|
157
|
+
}
|
|
116
158
|
}
|
|
117
159
|
});
|
|
118
160
|
worker.on("message", (msg) => {
|
|
@@ -121,24 +163,17 @@ export class ThreadPool {
|
|
|
121
163
|
this.logger?.log(msg.data);
|
|
122
164
|
break;
|
|
123
165
|
case WorkerMessageType.ONLINE:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
166
|
+
this.dispatch("online", idx);
|
|
167
|
+
break;
|
|
168
|
+
case WorkerMessageType.ERROR:
|
|
169
|
+
// A post-startup runtime error reported by the worker (see ThreadWorkerEntry.js's message
|
|
170
|
+
// handler). Must route to "error" listeners like the native worker.on("error", ...) case
|
|
171
|
+
// above, not fall through to "message" - otherwise anything monitoring failures only via
|
|
172
|
+
// pool.on("error", ...) never learns the worker failed.
|
|
173
|
+
this.dispatch("error", idx, msg.data);
|
|
132
174
|
break;
|
|
133
175
|
default:
|
|
134
|
-
|
|
135
|
-
const listeners = this.callbacks.get("message");
|
|
136
|
-
if (listeners) {
|
|
137
|
-
for (const callback of listeners) {
|
|
138
|
-
callback(idx, msg);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
176
|
+
this.dispatch("message", idx, msg);
|
|
142
177
|
break;
|
|
143
178
|
}
|
|
144
179
|
});
|
|
@@ -152,61 +187,155 @@ export class ThreadPool {
|
|
|
152
187
|
*/
|
|
153
188
|
start(options, num = this.max) {
|
|
154
189
|
this.shutdown = false;
|
|
190
|
+
// Terminate any workers left over from a previous start() call so they aren't leaked (their thread and
|
|
191
|
+
// listeners would otherwise become unreachable once `this.workers[i]` is overwritten below).
|
|
192
|
+
for (const worker of this.workers) {
|
|
193
|
+
worker.removeAllListeners();
|
|
194
|
+
void worker.terminate();
|
|
195
|
+
}
|
|
196
|
+
this.workers.length = 0;
|
|
155
197
|
return new Promise((resolve, reject) => {
|
|
156
198
|
let numReady = 0;
|
|
157
|
-
|
|
158
|
-
|
|
199
|
+
let settled = false;
|
|
200
|
+
// Copied rather than mutated in place - `options` may be the caller's own object (reused across
|
|
201
|
+
// multiple start() calls, or even frozen), and it shouldn't gain an `allowTs` property - or, via
|
|
202
|
+
// createWorker() below, an `entry` property - that the caller never set themselves. Everything past
|
|
203
|
+
// this point operates on `resolvedOptions`, never the original `options` parameter.
|
|
204
|
+
const resolvedOptions = { ...options };
|
|
205
|
+
resolvedOptions.allowTs = "allowTs" in resolvedOptions ? resolvedOptions.allowTs : true;
|
|
206
|
+
const target = Math.min(num, this.maxThreads);
|
|
207
|
+
if (target <= 0) {
|
|
208
|
+
// Nothing to create; resolve immediately instead of hanging forever waiting for readiness signals
|
|
209
|
+
// that will never arrive.
|
|
210
|
+
this.logger?.debug("No worker threads to create.");
|
|
211
|
+
resolve();
|
|
212
|
+
return;
|
|
159
213
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
214
|
+
// If any worker fails to start, terminate every worker created during this start() call (the
|
|
215
|
+
// failed one and any siblings that succeeded) before rejecting. Without this, a failed start()
|
|
216
|
+
// leaks running worker threads that the caller has no reference to, since the returned promise
|
|
217
|
+
// is the only thing that told them startup failed - they have no reason to call stop(). `shutdown`
|
|
218
|
+
// is set first (mirroring stop()) so createWorker()'s persistent "exit" handler treats this as a
|
|
219
|
+
// controlled teardown rather than a crash to restart via `restartOnExit`. Listeners are
|
|
220
|
+
// deliberately left attached (unlike the leftover-worker cleanup above) rather than calling
|
|
221
|
+
// `removeAllListeners()`: a worker mid-teardown that still fires a genuine "error" event with no
|
|
222
|
+
// listener left on it would make Node throw and crash the whole process.
|
|
223
|
+
const failStartup = (error) => {
|
|
224
|
+
if (settled) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
settled = true;
|
|
228
|
+
clearTimeout(timeoutHandle);
|
|
229
|
+
this.shutdown = true;
|
|
230
|
+
for (const worker of this.workers) {
|
|
231
|
+
void worker.terminate();
|
|
232
|
+
}
|
|
233
|
+
this.workers.length = 0;
|
|
234
|
+
reject(error);
|
|
235
|
+
};
|
|
236
|
+
// Guard against a worker that never reports readiness and never errors either.
|
|
237
|
+
const timeoutMs = resolvedOptions.startupTimeoutMs ?? ThreadPool.START_TIMEOUT_MS;
|
|
238
|
+
const timeoutHandle = setTimeout(() => {
|
|
239
|
+
failStartup(new Error(`Timed out waiting for worker threads to start after ${timeoutMs}ms.`));
|
|
240
|
+
}, timeoutMs);
|
|
241
|
+
timeoutHandle.unref?.();
|
|
242
|
+
const succeed = () => {
|
|
243
|
+
settled = true;
|
|
244
|
+
clearTimeout(timeoutHandle);
|
|
245
|
+
resolve();
|
|
246
|
+
};
|
|
247
|
+
// Attaches the readiness-tracking listeners used to resolve/reject this start() call to `w`. Defined
|
|
248
|
+
// once per slot and re-invoked (via createWorker's `onRestart` hook below) on any replacement worker
|
|
249
|
+
// created by `restartOnExit` before this promise settles, so a worker that crashes and restarts
|
|
250
|
+
// during startup - before ever reporting ready - still lets start() resolve once the *replacement*
|
|
251
|
+
// becomes ready, instead of hanging forever with no listener tracking it.
|
|
252
|
+
const attachReadyListeners = (w) => {
|
|
253
|
+
let ready = false;
|
|
254
|
+
w.on("online", () => {
|
|
255
|
+
if (settled) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!resolvedOptions.worker) {
|
|
259
|
+
ready = true;
|
|
165
260
|
numReady++;
|
|
166
261
|
}
|
|
167
262
|
// When all workers have finished being created resolve
|
|
168
|
-
if (numReady >=
|
|
169
|
-
|
|
263
|
+
if (numReady >= target) {
|
|
264
|
+
succeed();
|
|
170
265
|
}
|
|
171
266
|
});
|
|
172
|
-
worker
|
|
267
|
+
// If a worker fails to start before it ever reaches readiness, reject instead of leaving the
|
|
268
|
+
// returned promise hanging forever.
|
|
269
|
+
w.on("error", (error) => {
|
|
270
|
+
if (!ready) {
|
|
271
|
+
failStartup(error);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
w.on("message", (msg) => {
|
|
275
|
+
if (settled) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
173
278
|
switch (msg.type) {
|
|
174
279
|
case WorkerMessageType.ERROR:
|
|
175
|
-
|
|
176
|
-
|
|
280
|
+
failStartup(msg.data);
|
|
281
|
+
return;
|
|
177
282
|
case WorkerMessageType.ONLINE:
|
|
283
|
+
ready = true;
|
|
178
284
|
numReady++;
|
|
179
285
|
break;
|
|
180
286
|
}
|
|
181
287
|
// When all workers have finished being created resolve
|
|
182
|
-
if (numReady >=
|
|
183
|
-
|
|
288
|
+
if (numReady >= target) {
|
|
289
|
+
succeed();
|
|
184
290
|
}
|
|
185
291
|
});
|
|
292
|
+
};
|
|
293
|
+
for (let i = 0; i < target; i++) {
|
|
294
|
+
const worker = this.createWorker(i, resolvedOptions, (newWorker) => {
|
|
295
|
+
if (!settled) {
|
|
296
|
+
attachReadyListeners(newWorker);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
attachReadyListeners(worker);
|
|
186
300
|
this.workers[i] = worker;
|
|
187
301
|
}
|
|
188
302
|
this.logger?.debug(`Created ${this.workers.length} worker(s)`);
|
|
189
303
|
});
|
|
190
304
|
}
|
|
191
305
|
/**
|
|
192
|
-
* Stops all running thread executions.
|
|
306
|
+
* Stops all running thread executions. Each worker is given `ThreadPool.STOP_GRACE_PERIOD_MS` to gracefully
|
|
307
|
+
* finish its own `stop()` and exit on its own before being force-terminated.
|
|
193
308
|
*/
|
|
194
309
|
async stop() {
|
|
195
310
|
const listeners = this.callbacks.get("exit");
|
|
196
311
|
this.shutdown = true;
|
|
197
|
-
// Send the stop signal so the workers know to
|
|
312
|
+
// Send the stop signal so the workers know to shut down gracefully
|
|
198
313
|
this.sendAll({ type: WorkerMessageType.STOP });
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
314
|
+
// Terminate all workers concurrently so shutdown latency is bounded by the slowest worker rather than
|
|
315
|
+
// scaling linearly with pool size. Each worker gets a grace period to exit on its own (i.e. for its
|
|
316
|
+
// ThreadWorker.stop() to actually finish) before being force-terminated, so in-flight cleanup work
|
|
317
|
+
// (flushing writes, closing connections, etc.) isn't cut off by an arbitrary fixed delay.
|
|
318
|
+
await Promise.all(this.workers.map(async (worker, idx) => {
|
|
319
|
+
let exitCode;
|
|
320
|
+
if (this.exitedWorkers.has(worker)) {
|
|
321
|
+
// Already exited (e.g. crashed with no restartOnExit, before stop() was ever called) - there's
|
|
322
|
+
// nothing to wait for or terminate.
|
|
323
|
+
exitCode = 0;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const exitedOnOwn = new Promise((resolve) => worker.once("exit", resolve));
|
|
327
|
+
const timedOut = sleep(ThreadPool.STOP_GRACE_PERIOD_MS).then(() => undefined);
|
|
328
|
+
exitCode = await Promise.race([exitedOnOwn, timedOut]);
|
|
329
|
+
if (exitCode === undefined) {
|
|
330
|
+
exitCode = await worker.terminate();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
204
333
|
if (listeners) {
|
|
205
334
|
for (const callback of listeners) {
|
|
206
335
|
callback(idx, exitCode);
|
|
207
336
|
}
|
|
208
337
|
}
|
|
209
|
-
}
|
|
338
|
+
}));
|
|
210
339
|
// Drop all terminated workers so `size`/`send()` don't keep referencing dead workers across a
|
|
211
340
|
// subsequent start()/stop() cycle. `workers` is a `readonly` array reference, so it's truncated in place.
|
|
212
341
|
this.workers.length = 0;
|
|
@@ -237,7 +366,9 @@ export class ThreadPool {
|
|
|
237
366
|
let idx = startIdx;
|
|
238
367
|
do {
|
|
239
368
|
const worker = this.workers[idx];
|
|
240
|
-
|
|
369
|
+
// Skip a worker that has already exited (e.g. crashed with no restartOnExit) rather than trying the
|
|
370
|
+
// next one - postMessage() throws synchronously on an exited worker.
|
|
371
|
+
if (worker && !this.exitedWorkers.has(worker)) {
|
|
241
372
|
worker.postMessage(msg);
|
|
242
373
|
this.lastThread = idx;
|
|
243
374
|
return;
|
|
@@ -247,12 +378,16 @@ export class ThreadPool {
|
|
|
247
378
|
throw new Error("No available workers in the pool.");
|
|
248
379
|
}
|
|
249
380
|
/**
|
|
250
|
-
* Sends the provided message to all worker threads in the pool.
|
|
381
|
+
* Sends the provided message to all worker threads in the pool. Workers that have already exited (e.g.
|
|
382
|
+
* crashed with no `restartOnExit`) are silently skipped rather than throwing, so one dead worker doesn't
|
|
383
|
+
* prevent delivery to every worker after it in the array.
|
|
251
384
|
* @param msg The message to send to all workers.
|
|
252
385
|
*/
|
|
253
386
|
sendAll(msg) {
|
|
254
387
|
for (const worker of this.workers) {
|
|
255
|
-
|
|
388
|
+
if (!this.exitedWorkers.has(worker)) {
|
|
389
|
+
worker.postMessage(msg);
|
|
390
|
+
}
|
|
256
391
|
}
|
|
257
392
|
}
|
|
258
393
|
/**
|
|
@@ -261,11 +396,16 @@ export class ThreadPool {
|
|
|
261
396
|
* @param msg The message to send the next available worker thread.
|
|
262
397
|
*/
|
|
263
398
|
sendTo(id, msg) {
|
|
264
|
-
|
|
265
|
-
|
|
399
|
+
const worker = this.workers[id];
|
|
400
|
+
if (worker && !this.exitedWorkers.has(worker)) {
|
|
401
|
+
worker.postMessage(msg);
|
|
266
402
|
}
|
|
267
403
|
}
|
|
268
404
|
}
|
|
405
|
+
/** The maximum amount of time, in milliseconds, `stop()` waits for a worker to exit on its own before force-terminating it. */
|
|
406
|
+
ThreadPool.STOP_GRACE_PERIOD_MS = 5000;
|
|
407
|
+
/** The default maximum amount of time, in milliseconds, `start()` waits for all workers to report readiness before rejecting. Overridable per-call via `WorkerOptions.startupTimeoutMs`. */
|
|
408
|
+
ThreadPool.START_TIMEOUT_MS = 30000;
|
|
269
409
|
__decorate([
|
|
270
410
|
Logger,
|
|
271
411
|
__metadata("design:type", Object)
|