@onlineapps/mq-client-core 4.2.0 → 5.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/CHANGELOG.md +623 -0
- package/README.md +99 -11
- package/package.json +1 -1
- package/src/BaseClient.js +105 -29
- package/src/config/configSchema.js +21 -4
- package/src/config/defaultConfig.js +14 -4
- package/src/config/deliveryPolicy.js +86 -6
- package/src/config/queueConfig.js +68 -39
- package/src/config.js +5 -1
- package/src/defaults.js +7 -1
- package/src/index.js +41 -4
- package/src/layers/PublishLayer.js +113 -5
- package/src/monitoring-publish.js +89 -40
- package/src/scheduler-request.js +32 -11
- package/src/transports/rabbitmqClient.js +1237 -316
- package/src/utils/decodeMessage.js +123 -0
- package/src/utils/publishErrors.js +256 -17
- package/jest.config.js +0 -15
- package/jest.integration.config.js +0 -16
- package/src/workers/RecoveryWorker.js +0 -130
package/src/scheduler-request.js
CHANGED
|
@@ -81,7 +81,7 @@ const describeErrors = (errors) => (errors || [])
|
|
|
81
81
|
*/
|
|
82
82
|
function validateRecurrences(document, source) {
|
|
83
83
|
if (typeof source !== 'string' || source.trim() === '') {
|
|
84
|
-
throw
|
|
84
|
+
throw invalidRequest(
|
|
85
85
|
'[mq-client-core/scheduler-request] Missing source - Expected: a name for what is being '
|
|
86
86
|
+ 'checked, because every refusal below quotes it and a wrong name sends the reader to the '
|
|
87
87
|
+ 'wrong place. Fix: pass what the caller calls this document (e.g. "the declaration").'
|
|
@@ -89,7 +89,7 @@ function validateRecurrences(document, source) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
if (!document || typeof document !== 'object' || Array.isArray(document)) {
|
|
92
|
-
throw
|
|
92
|
+
throw invalidRequest(
|
|
93
93
|
`[mq-client-core/scheduler-request] ${source} is not a declaration - Expected: an object with a "recurrences" list, `
|
|
94
94
|
+ `got ${Array.isArray(document) ? 'an array' : typeof document}. `
|
|
95
95
|
+ 'Fix: write { "recurrences": [ … ] }, or remove the file if the service declares none.'
|
|
@@ -97,7 +97,7 @@ function validateRecurrences(document, source) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
if (!validateDeclaration(document)) {
|
|
100
|
-
throw
|
|
100
|
+
throw invalidRequest(
|
|
101
101
|
`[mq-client-core/scheduler-request] ${source} does not hold: ${describeErrors(validateDeclaration.errors)} - `
|
|
102
102
|
+ 'Expected: every record carries id, cookbook_ref, cron (five fields) and an explicit '
|
|
103
103
|
+ 'IANA timezone, and nothing this shape does not declare. '
|
|
@@ -110,7 +110,7 @@ function validateRecurrences(document, source) {
|
|
|
110
110
|
const seen = new Set();
|
|
111
111
|
for (const recurrence of document.recurrences) {
|
|
112
112
|
if (seen.has(recurrence.id)) {
|
|
113
|
-
throw
|
|
113
|
+
throw invalidRequest(
|
|
114
114
|
`[mq-client-core/scheduler-request] ${source} declares the id "${recurrence.id}" twice - Expected: one record per `
|
|
115
115
|
+ 'id, because the id travels as the scheduler\'s idempotency_key and a second record '
|
|
116
116
|
+ 'would silently overwrite the first at every boot. '
|
|
@@ -123,6 +123,26 @@ function validateRecurrences(document, source) {
|
|
|
123
123
|
return document.recurrences;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The code every refusal of a builder carries — and only those (`SCHEDULER_REQUEST_INVALID`).
|
|
128
|
+
*
|
|
129
|
+
* A caller has two kinds of failure to tell apart and must never do it by the wording of a
|
|
130
|
+
* message: a request that cannot be built (a record that breaks the declaration rules, tenancy,
|
|
131
|
+
* a missing key or requester, a key the port does not declare) and a request that could not be
|
|
132
|
+
* SENT (no connection, a publish the broker refused). The wrapper's boot phase 0.7b isolates the
|
|
133
|
+
* first as a failed source and stops the boot on the second (confirmation `scheduler` 020,
|
|
134
|
+
* operational reading 1). So the builders mark their refusals with this code, and
|
|
135
|
+
* `publishSchedulerRequest()` and the port's construction never do.
|
|
136
|
+
*/
|
|
137
|
+
const SCHEDULER_REQUEST_INVALID = 'SCHEDULER_REQUEST_INVALID';
|
|
138
|
+
|
|
139
|
+
/** A builder's refusal: the message as written, plus the code that names its kind. */
|
|
140
|
+
function invalidRequest(message) {
|
|
141
|
+
const error = new Error(message);
|
|
142
|
+
error.code = SCHEDULER_REQUEST_INVALID;
|
|
143
|
+
return error;
|
|
144
|
+
}
|
|
145
|
+
|
|
126
146
|
/** A whole number a tenant or workspace id may be: ≥ 1, never 0 (confirmation scheduler 002). */
|
|
127
147
|
const isContextId = (value) => Number.isInteger(value) && value >= 1;
|
|
128
148
|
|
|
@@ -169,7 +189,7 @@ const NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
|
169
189
|
/** The key that identifies the item, whatever kind it is - required by the contract's root. */
|
|
170
190
|
function requireIdempotencyKey(idempotencyKey) {
|
|
171
191
|
if (typeof idempotencyKey !== 'string' || idempotencyKey.trim() === '') {
|
|
172
|
-
throw
|
|
192
|
+
throw invalidRequest(
|
|
173
193
|
'[mq-client-core/scheduler-request] Missing idempotency_key - Expected: the stable identity '
|
|
174
194
|
+ 'of this item, chosen by the caller. The scheduler decides a one-shot item ONCE by this '
|
|
175
195
|
+ 'key, so a key invented here would turn every retry of one fact into a second run. '
|
|
@@ -183,7 +203,7 @@ function requireIdempotencyKey(idempotencyKey) {
|
|
|
183
203
|
/** The service registering, cancelling or reporting - checked by the scheduler against services.json. */
|
|
184
204
|
function requireRequestedBy(requestedBy) {
|
|
185
205
|
if (typeof requestedBy !== 'string' || requestedBy.trim() === '') {
|
|
186
|
-
throw
|
|
206
|
+
throw invalidRequest(
|
|
187
207
|
'[mq-client-core/scheduler-request] Missing requested_by - Expected: the platform name of the '
|
|
188
208
|
+ 'service sending this request; the scheduler checks it against api/config/services.json '
|
|
189
209
|
+ "and against the cookbook's own trigger declaration. "
|
|
@@ -197,7 +217,7 @@ function requireRequestedBy(requestedBy) {
|
|
|
197
217
|
/** Both context ids, under the one rule confirmation `scheduler` 002 fixed. */
|
|
198
218
|
function requireTenancy(tenantId, workspaceId) {
|
|
199
219
|
if (!isContextId(tenantId)) {
|
|
200
|
-
throw
|
|
220
|
+
throw invalidRequest(
|
|
201
221
|
`[mq-client-core/scheduler-request] Invalid tenant_id ${JSON.stringify(tenantId)} - Expected: `
|
|
202
222
|
+ 'a whole number of at least 1. The owner rejected a reserved value: 0 is not a platform '
|
|
203
223
|
+ 'scope, and an absent tenant is an error rather than a default (confirmation scheduler 002). '
|
|
@@ -208,7 +228,7 @@ function requireTenancy(tenantId, workspaceId) {
|
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
if (!isContextId(workspaceId)) {
|
|
211
|
-
throw
|
|
231
|
+
throw invalidRequest(
|
|
212
232
|
`[mq-client-core/scheduler-request] Invalid workspace_id ${JSON.stringify(workspaceId)} - `
|
|
213
233
|
+ 'Expected: a whole number of at least 1, under the same rule as the tenant (confirmation '
|
|
214
234
|
+ 'scheduler 002). Fix: pass workspaceId with every request - a recurrence carries it in the '
|
|
@@ -242,7 +262,7 @@ function buildEventRequest({ event, tenantId, workspaceId, requestedBy, idempote
|
|
|
242
262
|
requireTenancy(tenantId, workspaceId);
|
|
243
263
|
|
|
244
264
|
if (!event || typeof event !== 'object' || Array.isArray(event)) {
|
|
245
|
-
throw
|
|
265
|
+
throw invalidRequest(
|
|
246
266
|
`[mq-client-core/scheduler-request] Missing event - Expected: { service, name } - who it `
|
|
247
267
|
+ 'happened to and what happened, both of them. '
|
|
248
268
|
+ 'Fix: pass the fact this service publishes, never a cookbook name.'
|
|
@@ -251,7 +271,7 @@ function buildEventRequest({ event, tenantId, workspaceId, requestedBy, idempote
|
|
|
251
271
|
|
|
252
272
|
for (const [field, value] of [['service', event.service], ['name', event.name]]) {
|
|
253
273
|
if (typeof value !== 'string' || !NAME_PATTERN.test(value)) {
|
|
254
|
-
throw
|
|
274
|
+
throw invalidRequest(
|
|
255
275
|
`[mq-client-core/scheduler-request] Invalid event.${field} ${JSON.stringify(value)} - `
|
|
256
276
|
+ 'Expected: the platform spelling, lower case letters, digits and dashes '
|
|
257
277
|
+ '(the pattern the scheduler\'s own contract declares). '
|
|
@@ -414,7 +434,7 @@ function createSchedulerPublisher({ mqClient, requestedBy } = {}) {
|
|
|
414
434
|
// something, and dropping it in silence hides both (`architecture-principles.md` §3).
|
|
415
435
|
for (const key of Object.keys(record)) {
|
|
416
436
|
if (!RECURRING_PORT_KEYS.includes(key)) {
|
|
417
|
-
throw
|
|
437
|
+
throw invalidRequest(
|
|
418
438
|
`[mq-client-core/scheduler-request] registerRecurring got a key the request does not declare ("${key}") - `
|
|
419
439
|
+ 'Expected: exactly { id, cookbookRef, cron, timezone, tenantId, workspaceId, input?, catchUp? }. '
|
|
420
440
|
+ 'Fix: remove the key from the record the service returns / the ctx.scheduler call.'
|
|
@@ -472,6 +492,7 @@ function createSchedulerPublisher({ mqClient, requestedBy } = {}) {
|
|
|
472
492
|
module.exports = {
|
|
473
493
|
schedulerRequestQueue,
|
|
474
494
|
SENDABLE_KINDS,
|
|
495
|
+
SCHEDULER_REQUEST_INVALID,
|
|
475
496
|
validateRecurrences,
|
|
476
497
|
buildRecurringRequest,
|
|
477
498
|
buildEventRequest,
|