queen-mq 0.15.0 → 0.16.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
@@ -22,7 +22,7 @@ Queen MQ is a PostgreSQL-backed message queue system with a powerful feature set
22
22
  - **Consumer Groups** - Kafka-style consumer groups for scalability
23
23
  - **Flexible Semantics** - Exactly-once, at-least-once, and at-most-once delivery
24
24
  - **Transactions** - Atomic operations across push and ack
25
- - **High Performance** - 200K+ messages/sec with proper batching
25
+ - **High Performance** — 104K msg/s push, 165K msg/s fan-out with consumer groups on a single 32-core node ([benchmarks](https://github.com/queen-mq/queen/tree/master/benchmark-queen/2026-04-26))
26
26
  - **Subscription Modes** - Process from beginning, new messages only, or from timestamp
27
27
  - **Dead Letter Queue** - Automatic failure handling and monitoring
28
28
  - **Message Tracing** - Debug distributed workflows with trace timelines
@@ -179,7 +179,7 @@ const queen = new Queen({
179
179
  urls: ['http://server1:6632', 'http://server2:6632'],
180
180
  timeoutMillis: 30000,
181
181
  retryAttempts: 3,
182
- loadBalancingStrategy: 'round-robin', // or 'session'
182
+ loadBalancingStrategy: 'affinity', // or 'round-robin', 'session'
183
183
  enableFailover: true
184
184
  })
185
185
  ```
@@ -564,8 +564,10 @@ await queen.close() // Flush buffers and close connections
564
564
  timeoutMillis: 30000,
565
565
  retryAttempts: 3,
566
566
  retryDelayMillis: 1000,
567
- loadBalancingStrategy: 'round-robin',
568
- enableFailover: true
567
+ loadBalancingStrategy: 'affinity', // 'affinity' | 'round-robin' | 'session'
568
+ affinityHashRing: 128,
569
+ enableFailover: true,
570
+ healthRetryAfterMillis: 5000
569
571
  }
570
572
  ```
571
573
 
@@ -653,17 +655,18 @@ const messages: Message<OrderData>[] = await queen.queue('orders').pop()
653
655
 
654
656
  ## Documentation
655
657
 
656
- - **[Complete V2 Guide](client-v2/README.md)** - Full tutorial with all features (94 test examples)
657
- - **[HTTP API Reference](https://github.com/smartpricing/queen/blob/master/server/API.md)** - Raw HTTP endpoints
658
- - **[Server Guide](https://github.com/smartpricing/queen/blob/master/server/README.md)** - Server setup and configuration
659
- - **[Architecture Guide](https://github.com/smartpricing/queen/blob/master/documentation/ARCHITECTURE.md)** - Deep dive into internals
658
+ - **[Complete V2 Guide](client-v2/README.md)** — full tutorial with all features
659
+ - **[HTTP API Reference](https://github.com/queen-mq/queen/blob/master/server/API.md)** — raw HTTP endpoints
660
+ - **[Server Guide](https://github.com/queen-mq/queen/blob/master/server/README.md)** — server setup and configuration
661
+ - **[Architecture & internals](https://queenmq.com/architecture.html)** — published architecture overview
662
+ - **[libqueen design notes](https://github.com/queen-mq/queen/blob/master/cdocs/LIBQUEEN_IMPROVEMENTS.md)** — adaptive engine deep-dive
660
663
 
661
664
  ---
662
665
 
663
666
  ## Support
664
667
 
665
- - **GitHub:** [smartpricing/queen](https://github.com/smartpricing/queen)
666
- - **Issues:** [GitHub Issues](https://github.com/smartpricing/queen/issues)
668
+ - **GitHub:** [queen-mq/queen](https://github.com/queen-mq/queen)
669
+ - **Issues:** [GitHub Issues](https://github.com/queen-mq/queen/issues)
667
670
  - **LinkedIn:** [Smartness](https://www.linkedin.com/company/smartness-com/)
668
671
 
669
672
  ---
@@ -23,9 +23,12 @@ export class Queen {
23
23
  #admin = null
24
24
 
25
25
  constructor(config = {}) {
26
- // Configure custom logger before anything else
26
+ // Configure custom logger before anything else.
27
+ // Opt into structured field logging with `structuredLogs: true` (the logger
28
+ // must accept a leading merge object, e.g. pino/bunyan); defaults to the
29
+ // backward-compatible single-string format.
27
30
  if (config && typeof config === 'object' && !Array.isArray(config) && config.logger) {
28
- logger.configure(config.logger)
31
+ logger.configure(config.logger, { structured: config.structuredLogs === true })
29
32
  }
30
33
 
31
34
  logger.log('Queen.constructor', { config: typeof config === 'object' && !Array.isArray(config) ? { ...config, urls: config.urls?.length || 0 } : { type: typeof config } })
@@ -1788,11 +1788,13 @@ await queen.close()
1788
1788
  ### Client Defaults
1789
1789
  ```javascript
1790
1790
  {
1791
- timeoutMillis: 30000, // 30 seconds
1791
+ timeoutMillis: 30000, // 30 seconds
1792
1792
  retryAttempts: 3,
1793
1793
  retryDelayMillis: 1000,
1794
- loadBalancingStrategy: 'round-robin',
1795
- enableFailover: true
1794
+ loadBalancingStrategy: 'affinity', // 'affinity' | 'round-robin' | 'session'
1795
+ affinityHashRing: 128,
1796
+ enableFailover: true,
1797
+ healthRetryAfterMillis: 5000
1796
1798
  }
1797
1799
  ```
1798
1800
 
@@ -1993,9 +1995,10 @@ process.on('SIGINT', async () => {
1993
1995
  You now know everything about Queen v2! 🎉
1994
1996
 
1995
1997
  **Additional resources:**
1996
- - [API Documentation](../../server/API.md) - Complete API reference
1997
- - [Test Examples](../test-v2/) - 94 working test cases
1998
- - [Architecture Guide](../../docs/) - Deep dive into Queen's internals
1998
+ - [API Documentation](../../../server/API.md) — complete HTTP API reference
1999
+ - [Test Examples](../test-v2/) — runnable test cases
2000
+ - [Architecture overview](https://queenmq.com/architecture.html) — published deep-dive
2001
+ - [libqueen design notes](../../../cdocs/LIBQUEEN_IMPROVEMENTS.md) — server internals
1999
2002
 
2000
2003
  **Need help?** Check out the test files in `test-v2/` - they're full of working examples!
2001
2004
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Logger utility for Queen Client v2
3
- *
3
+ *
4
4
  * Supports pluggable logger backends (pino, winston, bunyan, etc.).
5
5
  * When no custom logger is configured, falls back to console-based logging
6
6
  * gated by the QUEEN_CLIENT_LOG environment variable (Node.js)
@@ -20,6 +20,9 @@ const LOG_ENABLED = (() => {
20
20
  })()
21
21
 
22
22
  let customLogger = null
23
+ // When true, details are passed to the custom logger as structured fields
24
+ // (pino/bunyan style: `info(fields, message)`). Opt-in for backward compatibility.
25
+ let structuredMode = false
23
26
 
24
27
  function getTimestamp() {
25
28
  return new Date().toISOString()
@@ -36,22 +39,62 @@ function formatLog(operation, details, level = 'INFO') {
36
39
 
37
40
  /**
38
41
  * Configure a custom logger backend.
42
+ *
39
43
  * The logger must implement: info(msg), warn(msg), error(msg).
40
44
  * debug(msg) is optional and falls back to info(msg) if missing.
41
45
  * When a custom logger is set, it is always active (no env var gating).
46
+ *
47
+ * By default (backward compatible) the logger is called with a single formatted
48
+ * string: `info('[operation] {json}')`.
49
+ *
50
+ * Pass `{ structured: true }` to instead call the logger pino/bunyan style —
51
+ * `info(fields, message)` — so `details` become structured, queryable fields
52
+ * (the `operation` is also added as a field and used as the message). Use this
53
+ * only with loggers that accept a leading merge object (e.g. pino, bunyan).
54
+ *
55
+ * The built-in console fallback (no custom logger) is unaffected by this option.
56
+ *
42
57
  * @param {object} logger - Logger instance (e.g. pino(), winston.createLogger())
58
+ * @param {{ structured?: boolean }} [options]
43
59
  */
44
- export function configure(logger) {
60
+ export function configure(logger, options = {}) {
45
61
  if (logger && typeof logger.info !== 'function') {
46
62
  throw new Error('Custom logger must implement info(), warn(), and error() methods')
47
63
  }
48
64
  customLogger = logger
65
+ structuredMode = options != null && options.structured === true
66
+ }
67
+
68
+ /**
69
+ * Emit a log through the configured custom logger.
70
+ * - structuredMode: `details` keys become top-level fields, `operation` is added
71
+ * as a field, and the operation is the message (pino/bunyan style).
72
+ * - default (legacy): a single `[operation] {json}` string is passed.
73
+ * Missing level methods fall back to info().
74
+ */
75
+ function emitToCustomLogger(method, operation, details) {
76
+ const fn = customLogger[method] || customLogger.info
77
+
78
+ if (structuredMode) {
79
+ let fields
80
+ if (details && typeof details === 'object' && !Array.isArray(details)) {
81
+ fields = { operation, ...details }
82
+ } else if (details !== undefined) {
83
+ fields = { operation, details }
84
+ } else {
85
+ fields = { operation }
86
+ }
87
+ fn.call(customLogger, fields, operation)
88
+ return
89
+ }
90
+
91
+ const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
92
+ fn.call(customLogger, `[${operation}] ${detailsStr}`)
49
93
  }
50
94
 
51
95
  export function log(operation, details) {
52
96
  if (customLogger) {
53
- const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
54
- customLogger.info(`[${operation}] ${detailsStr}`)
97
+ emitToCustomLogger('info', operation, details)
55
98
  return
56
99
  }
57
100
  if (!LOG_ENABLED) return
@@ -60,9 +103,7 @@ export function log(operation, details) {
60
103
 
61
104
  export function debug(operation, details) {
62
105
  if (customLogger) {
63
- const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
64
- const fn = customLogger.debug || customLogger.info
65
- fn.call(customLogger, `[${operation}] ${detailsStr}`)
106
+ emitToCustomLogger('debug', operation, details)
66
107
  return
67
108
  }
68
109
  if (!LOG_ENABLED) return
@@ -71,8 +112,7 @@ export function debug(operation, details) {
71
112
 
72
113
  export function warn(operation, details) {
73
114
  if (customLogger) {
74
- const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
75
- customLogger.warn(`[${operation}] ${detailsStr}`)
115
+ emitToCustomLogger('warn', operation, details)
76
116
  return
77
117
  }
78
118
  if (!LOG_ENABLED) return
@@ -81,8 +121,7 @@ export function warn(operation, details) {
81
121
 
82
122
  export function error(operation, details) {
83
123
  if (customLogger) {
84
- const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
85
- customLogger.error(`[${operation}] ${detailsStr}`)
124
+ emitToCustomLogger('error', operation, details)
86
125
  return
87
126
  }
88
127
  if (!LOG_ENABLED) return
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "queen-mq",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "description": "Partitioned message queue on PostgreSQL — broker client + fluent streaming SDK (windows, joins, gates) in one package",
6
6
  "main": "client-v2/index.js",
7
7
  "scripts": {
8
- "test": "node --test test-v2/streams-unit/configHash.test.js test-v2/streams-unit/operators.test.js test-v2/streams-unit/cycle.test.js test-v2/streams-unit/eventTime.test.js && node test-v2/run.js human",
9
- "test:unit": "node --test test-v2/streams-unit/configHash.test.js test-v2/streams-unit/operators.test.js test-v2/streams-unit/cycle.test.js test-v2/streams-unit/eventTime.test.js",
10
- "test:unit:e2e": "node --test test-v2/streams-unit/e2e.test.js",
8
+ "test": "node --test test-v2/streams-unit/configHash.test.js test-v2/streams-unit/operators.test.js test-v2/streams-unit/cycle.test.js test-v2/streams-unit/eventTime.test.js && node test-v2/run.js human",
9
+ "test:unit": "node --test test-v2/streams-unit/configHash.test.js test-v2/streams-unit/operators.test.js test-v2/streams-unit/cycle.test.js test-v2/streams-unit/eventTime.test.js",
10
+ "test:unit:e2e": "node --test test-v2/streams-unit/e2e.test.js",
11
11
  "test:integration": "node test-v2/run.js human",
12
- "test:streams": "node test-v2/run.js stream",
13
- "test:all": "npm run test:unit && npm run test:integration && npm run test:streams"
12
+ "test:streams": "node test-v2/run.js stream",
13
+ "test:all": "npm run test:unit && npm run test:integration && npm run test:streams"
14
14
  },
15
15
  "files": [
16
16
  "client-v2/**/*",
@@ -29,7 +29,7 @@
29
29
  "license": "Apache-2.0",
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "https://github.com/smartpricing/queen"
32
+ "url": "https://github.com/queen-mq/queen"
33
33
  },
34
34
  "keywords": [
35
35
  "message-queue",