@onebun/core 0.1.2 → 0.1.4

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 (79) hide show
  1. package/package.json +6 -6
  2. package/src/{application.test.ts → application/application.test.ts} +6 -5
  3. package/src/{application.ts → application/application.ts} +131 -12
  4. package/src/application/index.ts +9 -0
  5. package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
  6. package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
  7. package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
  8. package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
  9. package/src/{decorators.ts → decorators/decorators.ts} +3 -2
  10. package/src/decorators/index.ts +15 -0
  11. package/src/index.ts +47 -134
  12. package/src/module/index.ts +12 -0
  13. package/src/{module.test.ts → module/module.test.ts} +3 -2
  14. package/src/{module.ts → module/module.ts} +6 -5
  15. package/src/queue/adapters/index.ts +8 -0
  16. package/src/queue/adapters/memory.adapter.test.ts +405 -0
  17. package/src/queue/adapters/memory.adapter.ts +509 -0
  18. package/src/queue/adapters/redis.adapter.ts +673 -0
  19. package/src/queue/cron-expression.test.ts +145 -0
  20. package/src/queue/cron-expression.ts +115 -0
  21. package/src/queue/cron-parser.test.ts +185 -0
  22. package/src/queue/cron-parser.ts +287 -0
  23. package/src/queue/decorators.test.ts +292 -0
  24. package/src/queue/decorators.ts +493 -0
  25. package/src/queue/docs-examples.test.ts +449 -0
  26. package/src/queue/guards.test.ts +309 -0
  27. package/src/queue/guards.ts +307 -0
  28. package/src/queue/index.ts +118 -0
  29. package/src/queue/pattern-matcher.test.ts +191 -0
  30. package/src/queue/pattern-matcher.ts +252 -0
  31. package/src/queue/queue.service.ts +421 -0
  32. package/src/queue/scheduler.test.ts +235 -0
  33. package/src/queue/scheduler.ts +379 -0
  34. package/src/queue/types.ts +502 -0
  35. package/src/redis/index.ts +8 -0
  36. package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
  37. package/src/service-client/index.ts +10 -0
  38. package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
  39. package/src/{service-client.ts → service-client/service-client.ts} +1 -1
  40. package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
  41. package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
  42. package/src/testing/index.ts +7 -0
  43. package/src/types.ts +34 -5
  44. package/src/websocket/index.ts +50 -0
  45. package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
  46. package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
  47. package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
  48. package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
  49. /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
  50. /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
  51. /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
  52. /package/src/{config.service.ts → module/config.service.ts} +0 -0
  53. /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
  54. /package/src/{controller.ts → module/controller.ts} +0 -0
  55. /package/src/{service.test.ts → module/service.test.ts} +0 -0
  56. /package/src/{service.ts → module/service.ts} +0 -0
  57. /package/src/{redis-client.ts → redis/redis-client.ts} +0 -0
  58. /package/src/{shared-redis.ts → redis/shared-redis.ts} +0 -0
  59. /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
  60. /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
  61. /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
  62. /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
  63. /package/src/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
  64. /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
  65. /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
  66. /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
  67. /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
  68. /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
  69. /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
  70. /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
  71. /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
  72. /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
  73. /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
  74. /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
  75. /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
  76. /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
  77. /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
  78. /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
  79. /package/src/{ws.types.ts → websocket/ws.types.ts} +0 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Queue Module
3
+ *
4
+ * Unified queue system for OneBun.
5
+ * Supports multiple backends: memory, redis, nats, jetstream.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ AckMode,
11
+ MessageMetadata,
12
+ Message,
13
+ PublishOptions,
14
+ RetryOptions,
15
+ DeadLetterOptions,
16
+ SubscribeOptions,
17
+ QueueEvents,
18
+ Subscription,
19
+ OverlapStrategy,
20
+ ScheduledJobOptions,
21
+ ScheduledJobInfo,
22
+ QueueFeature,
23
+ QueueAdapterType,
24
+ MessageHandler,
25
+ QueueAdapter,
26
+ BuiltInAdapterType,
27
+ QueueAdapterConstructor,
28
+ QueueConfig,
29
+ SubscribeDecoratorOptions,
30
+ CronDecoratorOptions,
31
+ IntervalDecoratorOptions,
32
+ TimeoutDecoratorOptions,
33
+ MessageExecutionContext,
34
+ MessageGuard,
35
+ MessageGuardConstructor,
36
+ } from './types';
37
+
38
+ // Cron Parser
39
+ export {
40
+ parseCronExpression,
41
+ getNextRun,
42
+ getNextRuns,
43
+ getMillisecondsUntilNextRun,
44
+ isValidCronExpression,
45
+ type CronSchedule,
46
+ } from './cron-parser';
47
+
48
+ // Cron Expression
49
+ export { CronExpression, type CronExpressionValue } from './cron-expression';
50
+
51
+ // Pattern Matcher
52
+ export {
53
+ parseQueuePattern,
54
+ matchQueuePattern,
55
+ isQueuePatternMatch,
56
+ createQueuePatternMatcher,
57
+ isQueuePattern,
58
+ getQueuePatternParams,
59
+ buildFromQueuePattern,
60
+ findMatchingTopics,
61
+ type QueuePatternMatch,
62
+ } from './pattern-matcher';
63
+
64
+ // Guards
65
+ export {
66
+ MessageExecutionContextImpl,
67
+ MessageAuthGuard,
68
+ MessageServiceGuard,
69
+ MessageHeaderGuard,
70
+ MessageTraceGuard,
71
+ MessageAllGuards,
72
+ MessageAnyGuard,
73
+ executeMessageGuards,
74
+ createMessageGuard,
75
+ } from './guards';
76
+
77
+ // Decorators
78
+ export {
79
+ Subscribe,
80
+ Cron,
81
+ Interval,
82
+ Timeout,
83
+ UseMessageGuards,
84
+ OnQueueReady,
85
+ OnQueueError,
86
+ OnMessageFailed,
87
+ OnMessageReceived,
88
+ OnMessageProcessed,
89
+ // Metadata helpers
90
+ getSubscribeMetadata,
91
+ getCronMetadata,
92
+ getIntervalMetadata,
93
+ getTimeoutMetadata,
94
+ getMessageGuards,
95
+ getLifecycleHandlers,
96
+ hasQueueDecorators,
97
+ QUEUE_METADATA,
98
+ type SubscribeMetadata,
99
+ type CronMetadata,
100
+ type IntervalMetadata,
101
+ type TimeoutMetadata,
102
+ type LifecycleMetadata,
103
+ } from './decorators';
104
+
105
+ // Scheduler
106
+ export { QueueScheduler, createQueueScheduler } from './scheduler';
107
+
108
+ // Queue Service
109
+ export {
110
+ QueueService,
111
+ QueueServiceTag,
112
+ makeQueueLayer,
113
+ createQueueService,
114
+ resolveAdapterType,
115
+ } from './queue.service';
116
+
117
+ // Adapters (re-export from adapters folder)
118
+ export * from './adapters';
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Queue Pattern Matcher Tests
3
+ */
4
+
5
+ import {
6
+ describe,
7
+ it,
8
+ expect,
9
+ } from 'bun:test';
10
+
11
+ import {
12
+ matchQueuePattern,
13
+ isQueuePatternMatch,
14
+ createQueuePatternMatcher,
15
+ isQueuePattern,
16
+ getQueuePatternParams,
17
+ buildFromQueuePattern,
18
+ findMatchingTopics,
19
+ } from './pattern-matcher';
20
+
21
+ describe('queue-pattern-matcher', () => {
22
+ describe('matchQueuePattern', () => {
23
+ it('should match exact strings', () => {
24
+ const result = matchQueuePattern('orders.created', 'orders.created');
25
+ expect(result.matched).toBe(true);
26
+ expect(result.params).toEqual({});
27
+ });
28
+
29
+ it('should not match different strings', () => {
30
+ const result = matchQueuePattern('orders.created', 'orders.updated');
31
+ expect(result.matched).toBe(false);
32
+ });
33
+
34
+ it('should match single-level wildcard (*)', () => {
35
+ const result = matchQueuePattern('orders.*', 'orders.created');
36
+ expect(result.matched).toBe(true);
37
+ expect(result.params).toEqual({});
38
+ });
39
+
40
+ it('should match single-level wildcard in the middle', () => {
41
+ const result = matchQueuePattern('orders.*.status', 'orders.123.status');
42
+ expect(result.matched).toBe(true);
43
+ });
44
+
45
+ it('should not match single-level wildcard with extra segments', () => {
46
+ const result = matchQueuePattern('orders.*', 'orders.123.status');
47
+ expect(result.matched).toBe(false);
48
+ });
49
+
50
+ it('should match multi-level wildcard (#)', () => {
51
+ const result = matchQueuePattern('events.#', 'events.user.created');
52
+ expect(result.matched).toBe(true);
53
+ });
54
+
55
+ it('should match multi-level wildcard with many segments', () => {
56
+ const result = matchQueuePattern('events.#', 'events.user.order.payment.completed');
57
+ expect(result.matched).toBe(true);
58
+ });
59
+
60
+ it('should match multi-level wildcard at end (empty)', () => {
61
+ // events.# should match events.anything, not just 'events'
62
+ const result = matchQueuePattern('events.#', 'events.');
63
+ expect(result.matched).toBe(true);
64
+ });
65
+
66
+ it('should extract named parameters', () => {
67
+ const result = matchQueuePattern('orders.{orderId}', 'orders.123');
68
+ expect(result.matched).toBe(true);
69
+ expect(result.params).toEqual({ orderId: '123' });
70
+ });
71
+
72
+ it('should extract multiple parameters', () => {
73
+ const result = matchQueuePattern('users.{userId}.orders.{orderId}', 'users.456.orders.123');
74
+ expect(result.matched).toBe(true);
75
+ expect(result.params).toEqual({ userId: '456', orderId: '123' });
76
+ });
77
+
78
+ it('should handle combined wildcard and parameters', () => {
79
+ const result = matchQueuePattern('service.{name}.*', 'service.auth.login');
80
+ expect(result.matched).toBe(true);
81
+ expect(result.params).toEqual({ name: 'auth' });
82
+ });
83
+
84
+ it('should not match when parameter value contains separator', () => {
85
+ const result = matchQueuePattern('orders.{id}', 'orders.123.extra');
86
+ expect(result.matched).toBe(false);
87
+ });
88
+ });
89
+
90
+ describe('isQueuePatternMatch', () => {
91
+ it('should return true for matching patterns', () => {
92
+ expect(isQueuePatternMatch('orders.*', 'orders.created')).toBe(true);
93
+ expect(isQueuePatternMatch('events.#', 'events.user.created')).toBe(true);
94
+ });
95
+
96
+ it('should return false for non-matching patterns', () => {
97
+ expect(isQueuePatternMatch('orders.*', 'users.created')).toBe(false);
98
+ });
99
+ });
100
+
101
+ describe('createQueuePatternMatcher', () => {
102
+ it('should create reusable matcher function', () => {
103
+ const matcher = createQueuePatternMatcher('orders.{orderId}.status');
104
+
105
+ const result1 = matcher('orders.123.status');
106
+ expect(result1.matched).toBe(true);
107
+ expect(result1.params).toEqual({ orderId: '123' });
108
+
109
+ const result2 = matcher('orders.456.status');
110
+ expect(result2.matched).toBe(true);
111
+ expect(result2.params).toEqual({ orderId: '456' });
112
+
113
+ const result3 = matcher('orders.123.other');
114
+ expect(result3.matched).toBe(false);
115
+ });
116
+
117
+ it('should optimize exact match patterns', () => {
118
+ const matcher = createQueuePatternMatcher('orders.created');
119
+
120
+ expect(matcher('orders.created').matched).toBe(true);
121
+ expect(matcher('orders.updated').matched).toBe(false);
122
+ });
123
+ });
124
+
125
+ describe('isQueuePattern', () => {
126
+ it('should return true for single-level wildcard patterns', () => {
127
+ expect(isQueuePattern('orders.*')).toBe(true);
128
+ });
129
+
130
+ it('should return true for multi-level wildcard patterns', () => {
131
+ expect(isQueuePattern('events.#')).toBe(true);
132
+ });
133
+
134
+ it('should return true for parameter patterns', () => {
135
+ expect(isQueuePattern('orders.{id}')).toBe(true);
136
+ });
137
+
138
+ it('should return false for literal strings', () => {
139
+ expect(isQueuePattern('orders.created')).toBe(false);
140
+ });
141
+ });
142
+
143
+ describe('getQueuePatternParams', () => {
144
+ it('should extract parameter names', () => {
145
+ const params = getQueuePatternParams('users.{userId}.orders.{orderId}');
146
+ expect(params).toEqual(['userId', 'orderId']);
147
+ });
148
+
149
+ it('should return empty array for patterns without parameters', () => {
150
+ const params = getQueuePatternParams('orders.*');
151
+ expect(params).toEqual([]);
152
+ });
153
+ });
154
+
155
+ describe('buildFromQueuePattern', () => {
156
+ it('should build topic from pattern and params', () => {
157
+ const result = buildFromQueuePattern('orders.{id}.status', { id: '123' });
158
+ expect(result).toBe('orders.123.status');
159
+ });
160
+
161
+ it('should handle multiple parameters', () => {
162
+ const result = buildFromQueuePattern('users.{uid}.orders.{oid}', { uid: '456', oid: '123' });
163
+ expect(result).toBe('users.456.orders.123');
164
+ });
165
+
166
+ it('should remove wildcards', () => {
167
+ const result = buildFromQueuePattern('orders.*.status', {});
168
+ expect(result).toBe('orders..status');
169
+ });
170
+ });
171
+
172
+ describe('findMatchingTopics', () => {
173
+ it('should find all matching topics', () => {
174
+ const topics = ['orders.created', 'orders.updated', 'users.created', 'orders.deleted'];
175
+ const matching = findMatchingTopics('orders.*', topics);
176
+ expect(matching).toEqual(['orders.created', 'orders.updated', 'orders.deleted']);
177
+ });
178
+
179
+ it('should find topics with multi-level wildcard', () => {
180
+ const topics = ['events.user.created', 'events.order.created', 'logs.info', 'events.deep.nested.event'];
181
+ const matching = findMatchingTopics('events.#', topics);
182
+ expect(matching).toEqual(['events.user.created', 'events.order.created', 'events.deep.nested.event']);
183
+ });
184
+
185
+ it('should return empty array when nothing matches', () => {
186
+ const topics = ['users.created', 'users.updated'];
187
+ const matching = findMatchingTopics('orders.*', topics);
188
+ expect(matching).toEqual([]);
189
+ });
190
+ });
191
+ });
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Queue Pattern Matcher
3
+ *
4
+ * Pattern matching for queue subscriptions.
5
+ * Supports both AMQP-style and MQTT-style wildcards.
6
+ *
7
+ * Supported patterns:
8
+ * - Exact match: 'orders.created' matches only 'orders.created'
9
+ * - Single-level wildcard (*): 'orders.*' matches 'orders.created', 'orders.updated'
10
+ * - Multi-level wildcard (#): 'events.#' matches 'events.user.created', 'events.order.shipped'
11
+ * - Named parameters: 'orders.{orderId}' extracts orderId from matching messages
12
+ *
13
+ * Separator: '.' (dot) is used as the separator (like AMQP/MQTT)
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Single-level wildcard
18
+ * matchQueuePattern('orders.*', 'orders.created') // true
19
+ * matchQueuePattern('orders.*', 'orders.created.new') // false
20
+ *
21
+ * // Multi-level wildcard
22
+ * matchQueuePattern('events.#', 'events.user.created') // true
23
+ * matchQueuePattern('events.#', 'events') // true
24
+ *
25
+ * // Named parameters
26
+ * matchQueuePattern('orders.{id}.status', 'orders.123.status')
27
+ * // { matched: true, params: { id: '123' } }
28
+ * ```
29
+ */
30
+
31
+ /**
32
+ * Pattern match result
33
+ */
34
+ export interface QueuePatternMatch {
35
+ matched: boolean;
36
+ params: Record<string, string>;
37
+ }
38
+
39
+ /**
40
+ * Escape special regex characters
41
+ */
42
+ function escapeRegex(str: string): string {
43
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
44
+ }
45
+
46
+ /**
47
+ * Parse a queue pattern into a regex and extract parameter names
48
+ *
49
+ * @param pattern - Pattern string with wildcards and/or parameters
50
+ * @returns Object with regex and parameter names
51
+ */
52
+ export function parseQueuePattern(pattern: string): {
53
+ regex: RegExp;
54
+ paramNames: string[];
55
+ } {
56
+ const paramNames: string[] = [];
57
+ let regexStr = '^';
58
+ let i = 0;
59
+
60
+ while (i < pattern.length) {
61
+ const char = pattern[i];
62
+
63
+ if (char === '#') {
64
+ // Multi-level wildcard: matches any number of segments (including zero)
65
+ // # must be at the end or followed by separator
66
+ if (i === pattern.length - 1) {
67
+ regexStr += '.*';
68
+ } else if (pattern[i + 1] === '.') {
69
+ regexStr += '(?:[^.]+\\.)*';
70
+ i++; // Skip the dot
71
+ } else {
72
+ // Invalid # position, treat as literal
73
+ regexStr += escapeRegex(char);
74
+ }
75
+ i++;
76
+ } else if (char === '*') {
77
+ // Single-level wildcard: matches exactly one segment
78
+ regexStr += '[^.]+';
79
+ i++;
80
+ } else if (char === '{') {
81
+ // Named parameter: {paramName}
82
+ const endIndex = pattern.indexOf('}', i);
83
+ if (endIndex === -1) {
84
+ // Invalid pattern, treat as literal
85
+ regexStr += escapeRegex(char);
86
+ i++;
87
+ } else {
88
+ const paramName = pattern.substring(i + 1, endIndex);
89
+ paramNames.push(paramName);
90
+ // Match one segment
91
+ regexStr += '([^.]+)';
92
+ i = endIndex + 1;
93
+ }
94
+ } else if (char === '.') {
95
+ // Separator
96
+ regexStr += '\\.';
97
+ i++;
98
+ } else {
99
+ // Literal character
100
+ regexStr += escapeRegex(char);
101
+ i++;
102
+ }
103
+ }
104
+
105
+ regexStr += '$';
106
+
107
+ return {
108
+ regex: new RegExp(regexStr),
109
+ paramNames,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Match a topic against a pattern
115
+ *
116
+ * @param pattern - Pattern string with wildcards and/or parameters
117
+ * @param topic - Topic to match
118
+ * @returns Match result with extracted parameters
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * matchQueuePattern('orders.*', 'orders.created')
123
+ * // { matched: true, params: {} }
124
+ *
125
+ * matchQueuePattern('orders.{orderId}', 'orders.123')
126
+ * // { matched: true, params: { orderId: '123' } }
127
+ *
128
+ * matchQueuePattern('events.#', 'events.user.created')
129
+ * // { matched: true, params: {} }
130
+ * ```
131
+ */
132
+ export function matchQueuePattern(pattern: string, topic: string): QueuePatternMatch {
133
+ // Exact match fast path
134
+ if (pattern === topic) {
135
+ return { matched: true, params: {} };
136
+ }
137
+
138
+ // Check if pattern has any special characters
139
+ if (!pattern.includes('*') && !pattern.includes('#') && !pattern.includes('{')) {
140
+ // No wildcards or parameters, must be exact match
141
+ return { matched: false, params: {} };
142
+ }
143
+
144
+ const { regex, paramNames } = parseQueuePattern(pattern);
145
+ const match = topic.match(regex);
146
+
147
+ if (!match) {
148
+ return { matched: false, params: {} };
149
+ }
150
+
151
+ // Extract parameters
152
+ const params: Record<string, string> = {};
153
+ for (let i = 0; i < paramNames.length; i++) {
154
+ params[paramNames[i]] = match[i + 1];
155
+ }
156
+
157
+ return { matched: true, params };
158
+ }
159
+
160
+ /**
161
+ * Check if a pattern matches a topic (without extracting parameters)
162
+ */
163
+ export function isQueuePatternMatch(pattern: string, topic: string): boolean {
164
+ return matchQueuePattern(pattern, topic).matched;
165
+ }
166
+
167
+ /**
168
+ * Create a pattern matcher function for a specific pattern
169
+ * (pre-compiles the regex for better performance)
170
+ *
171
+ * @param pattern - Pattern string
172
+ * @returns Function that matches topics against the pattern
173
+ */
174
+ export function createQueuePatternMatcher(pattern: string): (topic: string) => QueuePatternMatch {
175
+ // Exact match fast path
176
+ if (!pattern.includes('*') && !pattern.includes('#') && !pattern.includes('{')) {
177
+ return (topic: string) => ({
178
+ matched: pattern === topic,
179
+ params: {},
180
+ });
181
+ }
182
+
183
+ const { regex, paramNames } = parseQueuePattern(pattern);
184
+
185
+ return (topic: string): QueuePatternMatch => {
186
+ // Exact match fast path
187
+ if (pattern === topic) {
188
+ return { matched: true, params: {} };
189
+ }
190
+
191
+ const match = topic.match(regex);
192
+
193
+ if (!match) {
194
+ return { matched: false, params: {} };
195
+ }
196
+
197
+ const params: Record<string, string> = {};
198
+ for (let i = 0; i < paramNames.length; i++) {
199
+ params[paramNames[i]] = match[i + 1];
200
+ }
201
+
202
+ return { matched: true, params };
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Check if a string is a pattern (contains wildcards or parameters)
208
+ */
209
+ export function isQueuePattern(value: string): boolean {
210
+ return value.includes('*') || value.includes('#') || value.includes('{');
211
+ }
212
+
213
+ /**
214
+ * Extract parameter names from a pattern
215
+ */
216
+ export function getQueuePatternParams(pattern: string): string[] {
217
+ const params: string[] = [];
218
+ const regex = /\{([^}]+)\}/g;
219
+ let match: RegExpExecArray | null;
220
+
221
+ while ((match = regex.exec(pattern)) !== null) {
222
+ params.push(match[1]);
223
+ }
224
+
225
+ return params;
226
+ }
227
+
228
+ /**
229
+ * Build a topic from a pattern and parameters
230
+ */
231
+ export function buildFromQueuePattern(pattern: string, params: Record<string, string>): string {
232
+ let result = pattern;
233
+
234
+ // Replace parameters with values
235
+ for (const [key, value] of Object.entries(params)) {
236
+ result = result.replace(`{${key}}`, value);
237
+ }
238
+
239
+ // Remove wildcards (they shouldn't be in built topics)
240
+ result = result.replace(/[*#]/g, '');
241
+
242
+ return result;
243
+ }
244
+
245
+ /**
246
+ * Find all topics that match a pattern
247
+ */
248
+ export function findMatchingTopics(pattern: string, topics: string[]): string[] {
249
+ const matcher = createQueuePatternMatcher(pattern);
250
+
251
+ return topics.filter((topic) => matcher(topic).matched);
252
+ }