@onlineapps/cookbook-router 2.0.0 → 3.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/README.md CHANGED
@@ -1,3 +1,14 @@
1
+ > Status: current
2
+ > Owns: the publication of a workflow message to a service's queue, once the registry says that service is available
3
+
4
+ <!-- BEGIN GENERATED: library-uniform — regenerate: npx oa-sync-template readme-uniform --all -->
5
+ Uniform: [library/orchestration](../../connector/conn-orch-validator/manifests/library.manifest.json)
6
+
7
+ Duty sections that apply:
8
+
9
+ - `all`: L-MAIN, L-ENGINES, L-TESTS, L-TEST-SCRIPT, L-PACK-TESTS, L-PINS, L-NO-FILE-RANGE, L-CHANGELOG, L-README, L-README-REGION, L-CONSUMER
10
+ <!-- END GENERATED: library-uniform -->
11
+
1
12
  # @onlineapps/cookbook-router
2
13
 
3
14
  Publishes a workflow message to a service's workflow queue, after checking with
@@ -62,7 +73,16 @@ verbatim to both collaborators, so their keys travel through it:
62
73
  | `cacheEnabled` | `ServiceDiscovery` | `true` |
63
74
  | `cacheTTL` | `ServiceDiscovery` (ms) | `300000` |
64
75
  | `ensureQueues` | `QueueManager` — assert the queue before the first publish | `true` |
65
- | `defaultOptions` | `QueueManager` — merged into every publish and assert | `{ durable: true, persistent: true }` |
76
+ | `queueOptions` | `QueueManager` — merged into every queue declaration | `{ durable: true }` |
77
+ | `publishOptions` | `QueueManager` — merged into every publish | `{ persistent: true }` |
78
+
79
+ A queue declaration and a message are two different things, so their defaults
80
+ are two objects with two owners. `queueOptions` reaches
81
+ `mqClient.assertQueue()`, whose declared option set is `durable`, `arguments`,
82
+ `exclusive` and `autoDelete`; `publishOptions` reaches `mqClient.publish()` as
83
+ amqplib message properties. One object fed both until d.396d, which meant the
84
+ queue was declared with `persistent` and the message published with `durable` —
85
+ neither of which the receiving side reads.
66
86
 
67
87
  A key not in this table is not read by anything here.
68
88
 
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@onlineapps/cookbook-router",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Message routing for cookbook workflows - handles service discovery and queue routing",
5
+ "oa": {
6
+ "category": "orchestration"
7
+ },
5
8
  "main": "src/index.js",
6
9
  "scripts": {
7
- "test": "jest",
10
+ "test": "npm run test:unit",
11
+ "test:unit": "jest tests/unit",
8
12
  "test:watch": "jest --watch",
9
13
  "test:coverage": "jest --coverage",
10
- "docs": "jsdoc2md --files src/**/*.js > API.md"
14
+ "docs": "jsdoc2md --files 'src/**/*.js' > API.md.tmp && mv API.md.tmp API.md || (rm -f API.md.tmp; exit 1)"
11
15
  },
12
16
  "keywords": [
13
17
  "cookbook",
@@ -20,10 +24,11 @@
20
24
  "license": "PROPRIETARY",
21
25
  "dependencies": {},
22
26
  "devDependencies": {
23
- "jest": "^29.7.0"
27
+ "jest": "^29.7.0",
28
+ "jsdoc-to-markdown": "^8.0.0"
24
29
  },
25
30
  "engines": {
26
- "node": ">=18.0.0"
31
+ "node": ">=24.0.0 <25"
27
32
  },
28
33
  "files": [
29
34
  "src"
package/src/index.js CHANGED
@@ -22,6 +22,9 @@ const CookbookRouter = require('./router');
22
22
  const ServiceDiscovery = require('./serviceDiscovery');
23
23
  const QueueManager = require('./queueManager');
24
24
 
25
+ // Single source of the published version — see module.exports.VERSION below.
26
+ const pkg = require('../package.json');
27
+
25
28
  module.exports = {
26
29
  // Main router class
27
30
  CookbookRouter,
@@ -35,6 +38,12 @@ module.exports = {
35
38
  return new CookbookRouter(mqClient, registryClient, options);
36
39
  },
37
40
 
38
- // Utility exports
39
- VERSION: '1.0.0'
41
+ // Utility exports.
42
+ //
43
+ // Read, never retyped: until 2026-09-03 this said '1.0.0' while the package
44
+ // was published at 2.0.0, so every consumer was told the package still sat on
45
+ // the major that the 2026-09-02 routing-rail removal had ended. A version is
46
+ // a descriptive fact and never belongs in a hand-written literal
47
+ // (.claude/rules/doc-code-binding.md §1).
48
+ VERSION: pkg.version
40
49
  };
@@ -1,21 +1,58 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * QueueManager - Queue operations and management
4
+ * What describes the QUEUE. Handed to `mqClient.assertQueue()`, whose declared
5
+ * option set is `durable`, `arguments`, `exclusive`, `autoDelete`.
5
6
  */
7
+ const DEFAULT_QUEUE_OPTIONS = Object.freeze({ durable: true });
8
+
9
+ /** What describes the MESSAGE. Handed to `mqClient.publish()`. */
10
+ const DEFAULT_PUBLISH_OPTIONS = Object.freeze({ persistent: true });
6
11
 
12
+ /**
13
+ * QueueManager - Queue operations and management
14
+ *
15
+ * A QUEUE and a MESSAGE are two different things, and neither one's defaults
16
+ * belong to the other. Until d.396d this class held ONE object for both —
17
+ * `defaultOptions: { durable: true, persistent: true }` — and spread it into the
18
+ * publish AND into the queue declaration, so each call site was handed a key it
19
+ * has no use for. Measured in both directions on the unit tier:
20
+ *
21
+ * assertQueue('split.queue', { durable: true, persistent: true })
22
+ * publish('control.publish', …, { durable: true, persistent: true })
23
+ *
24
+ * `persistent` is a message property (amqplib `Options.Publish`) and means
25
+ * nothing to a queue; `durable` is a queue property and means nothing to a
26
+ * message. Nothing objected, because `@onlineapps/mq-client-core` read the two
27
+ * keys it knew and dropped the rest in silence — and since d.396c it no longer
28
+ * does: `assertQueue()` refuses an option it does not read, by name. What was an
29
+ * invisible confusion becomes a throw the moment the pin moves, and the cure is
30
+ * not to catch it but to stop sending a message property to a queue.
31
+ *
32
+ * So there are two defaults with two owners, `queueOptions` and `publishOptions`,
33
+ * and each call site merges only its own.
34
+ */
7
35
  class QueueManager {
8
36
  constructor(mqClient, options = {}) {
9
37
  this.mqClient = mqClient;
38
+ // `...options` comes FIRST so the computed keys below survive it. It used to
39
+ // come last, which undid the merge it was written to extend: a caller passing
40
+ // `{ durable: false }` replaced the whole defaults object rather than
41
+ // overriding one key of it, and lost the other default in the process. Every
42
+ // key this class does not compute is still forwarded untouched — `router.js`
43
+ // hands its whole options object to both collaborators.
10
44
  this.options = {
45
+ ...options,
11
46
  ensureQueues: options.ensureQueues !== false,
12
- defaultOptions: {
13
- durable: true,
14
- persistent: true,
15
- ...options.defaultOptions
47
+ queueOptions: {
48
+ ...DEFAULT_QUEUE_OPTIONS,
49
+ ...options.queueOptions
16
50
  },
17
- logger: options.logger || console,
18
- ...options
51
+ publishOptions: {
52
+ ...DEFAULT_PUBLISH_OPTIONS,
53
+ ...options.publishOptions
54
+ },
55
+ logger: options.logger || console
19
56
  };
20
57
 
21
58
  this.ensuredQueues = new Set();
@@ -25,7 +62,7 @@ class QueueManager {
25
62
  * Publish message to queue
26
63
  * @param {string} queueName - Target queue name
27
64
  * @param {Object} message - Message to publish
28
- * @param {Object} options - Publishing options
65
+ * @param {Object} options - Publishing options (amqplib `Options.Publish`)
29
66
  * @returns {Promise<boolean>}
30
67
  */
31
68
  async publish(queueName, message, options = {}) {
@@ -43,9 +80,9 @@ class QueueManager {
43
80
  timestamp: new Date().toISOString()
44
81
  };
45
82
 
46
- // Merge publishing options
83
+ // Merge publishing options — the MESSAGE defaults, never the queue's.
47
84
  const publishOptions = {
48
- ...this.options.defaultOptions,
85
+ ...this.options.publishOptions,
49
86
  ...options
50
87
  };
51
88
 
@@ -73,7 +110,8 @@ class QueueManager {
73
110
  /**
74
111
  * Ensure queue exists
75
112
  * @param {string} queueName - Queue name
76
- * @param {Object} options - Queue options
113
+ * @param {Object} options - Queue options, as `mqClient.assertQueue()` declares
114
+ * them (`durable`, `arguments`, `exclusive`, `autoDelete`)
77
115
  * @returns {Promise<boolean>}
78
116
  */
79
117
  async ensureQueue(queueName, options = {}) {
@@ -82,19 +120,16 @@ class QueueManager {
82
120
  return true;
83
121
  }
84
122
 
123
+ // The QUEUE defaults, never the message's.
85
124
  const queueOptions = {
86
- ...this.options.defaultOptions,
125
+ ...this.options.queueOptions,
87
126
  ...options
88
127
  };
89
128
 
90
- try {
91
- await this.mqClient.assertQueue(queueName, queueOptions);
92
- this.ensuredQueues.add(queueName);
93
- return true;
94
- } catch (error) {
95
- throw error;
96
- }
129
+ await this.mqClient.assertQueue(queueName, queueOptions);
130
+ this.ensuredQueues.add(queueName);
131
+ return true;
97
132
  }
98
133
  }
99
134
 
100
- module.exports = QueueManager;
135
+ module.exports = QueueManager;
package/src/router.js CHANGED
@@ -27,7 +27,7 @@ class CookbookRouter {
27
27
  // reads is dead (`.claude/rules/change-discipline.md` § Removing).
28
28
  // `options` is still forwarded whole to the two collaborators below, so
29
29
  // their own keys (`cacheEnabled`, `cacheTTL`, `ensureQueues`,
30
- // `defaultOptions`) reach them unchanged.
30
+ // `queueOptions`, `publishOptions`) reach them unchanged.
31
31
  this.options = {
32
32
  logger: console,
33
33
  ...options