@open-mercato/events 0.4.2-canary-51881f6bf3 → 0.4.2-canary-f728a6d66a
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/bus.js +20 -7
- package/dist/bus.js.map +2 -2
- package/package.json +2 -2
- package/src/bus.ts +48 -8
- package/src/types.ts +2 -0
package/dist/bus.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createQueue } from "@open-mercato/queue";
|
|
2
2
|
const EVENTS_QUEUE_NAME = "events";
|
|
3
|
+
function matchEventPattern(eventName, pattern) {
|
|
4
|
+
if (pattern === "*") return true;
|
|
5
|
+
if (pattern === eventName) return true;
|
|
6
|
+
if (!pattern.includes("*")) return false;
|
|
7
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]+");
|
|
8
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
9
|
+
return regex.test(eventName);
|
|
10
|
+
}
|
|
3
11
|
function createEventBus(opts) {
|
|
4
12
|
const listeners = /* @__PURE__ */ new Map();
|
|
5
13
|
const queueStrategy = opts.queueStrategy ?? (process.env.QUEUE_STRATEGY === "async" ? "async" : "local");
|
|
@@ -21,13 +29,18 @@ function createEventBus(opts) {
|
|
|
21
29
|
return queue;
|
|
22
30
|
}
|
|
23
31
|
async function deliver(event, payload) {
|
|
24
|
-
const handlers
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
for (const [pattern, handlers] of listeners) {
|
|
33
|
+
if (!matchEventPattern(event, pattern)) continue;
|
|
34
|
+
if (!handlers || handlers.size === 0) continue;
|
|
35
|
+
for (const handler of handlers) {
|
|
36
|
+
try {
|
|
37
|
+
await Promise.resolve(handler(payload, {
|
|
38
|
+
resolve: opts.resolve,
|
|
39
|
+
eventName: event
|
|
40
|
+
}));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`[events] Handler error for "${event}" (pattern: "${pattern}"):`, error);
|
|
43
|
+
}
|
|
31
44
|
}
|
|
32
45
|
}
|
|
33
46
|
}
|
package/dist/bus.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/bus.ts"],
|
|
4
|
-
"sourcesContent": ["import { createQueue } from '@open-mercato/queue'\nimport type { Queue } from '@open-mercato/queue'\nimport type {\n EventBus,\n CreateBusOptions,\n SubscriberHandler,\n SubscriberDescriptor,\n EventPayload,\n EmitOptions,\n} from './types'\n\n/** Queue name for persistent events */\nconst EVENTS_QUEUE_NAME = 'events'\n\n/** Job data structure for queued events */\ntype EventJobData = {\n event: string\n payload: EventPayload\n}\n\n/**\n * Creates an event bus instance.\n *\n * The event bus provides:\n * - In-memory event delivery to registered handlers\n * - Optional persistence via the queue package when `persistent: true`\n *\n * @param opts - Configuration options\n * @returns An EventBus instance\n *\n * @example\n * ```typescript\n * const bus = createEventBus({\n * resolve: container.resolve.bind(container),\n * queueStrategy: 'local', // or 'async' for BullMQ\n * })\n *\n * // Register a handler\n * bus.on('user.created', async (payload, ctx) => {\n * const userService = ctx.resolve('userService')\n * await userService.sendWelcomeEmail(payload.userId)\n * })\n *\n * // Emit an event (immediate delivery)\n * await bus.emit('user.created', { userId: '123' })\n *\n * // Emit with persistence (for async worker processing)\n * await bus.emit('order.placed', { orderId: '456' }, { persistent: true })\n * ```\n */\nexport function createEventBus(opts: CreateBusOptions): EventBus {\n // In-memory listeners for immediate event delivery\n const listeners = new Map<string, Set<SubscriberHandler>>()\n\n // Determine queue strategy from options or environment\n const queueStrategy = opts.queueStrategy ??\n (process.env.QUEUE_STRATEGY === 'async' ? 'async' : 'local')\n\n // Lazy-initialized queue for persistent events\n let queue: Queue<EventJobData> | null = null\n\n /**\n * Gets or creates the queue instance for persistent events.\n */\n function getQueue(): Queue<EventJobData> {\n if (!queue) {\n if (queueStrategy === 'async') {\n const redisUrl = process.env.REDIS_URL || process.env.QUEUE_REDIS_URL\n if (!redisUrl) {\n console.warn('[events] No REDIS_URL configured, falling back to localhost:6379')\n }\n queue = createQueue<EventJobData>(EVENTS_QUEUE_NAME, 'async', {\n connection: { url: redisUrl }\n })\n } else {\n queue = createQueue<EventJobData>(EVENTS_QUEUE_NAME, 'local')\n }\n }\n return queue\n }\n\n /**\n * Delivers an event to all registered in-memory handlers.\n */\n async function deliver(event: string, payload: EventPayload): Promise<void> {\n const handlers
|
|
5
|
-
"mappings": "AAAA,SAAS,mBAAmB;AAY5B,MAAM,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { createQueue } from '@open-mercato/queue'\nimport type { Queue } from '@open-mercato/queue'\nimport type {\n EventBus,\n CreateBusOptions,\n SubscriberHandler,\n SubscriberDescriptor,\n EventPayload,\n EmitOptions,\n} from './types'\n\n/** Queue name for persistent events */\nconst EVENTS_QUEUE_NAME = 'events'\n\n/**\n * Match an event name against a pattern.\n *\n * Supports:\n * - Exact match: `customers.people.created`\n * - Wildcard `*` matches single segment: `customers.*` matches `customers.people` but not `customers.people.created`\n * - Global wildcard: `*` alone matches all events\n *\n * @param eventName - The actual event name\n * @param pattern - The pattern to match against\n * @returns True if the event matches the pattern\n */\nfunction matchEventPattern(eventName: string, pattern: string): boolean {\n // Global wildcard matches all events\n if (pattern === '*') return true\n\n // Exact match\n if (pattern === eventName) return true\n\n // No wildcards in pattern means we need exact match, which already failed\n if (!pattern.includes('*')) return false\n\n // Convert pattern to regex:\n // - Escape regex special chars (except *)\n // - Replace * with [^.]+ (match one or more non-dot chars)\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '[^.]+')\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(eventName)\n}\n\n/** Job data structure for queued events */\ntype EventJobData = {\n event: string\n payload: EventPayload\n}\n\n/**\n * Creates an event bus instance.\n *\n * The event bus provides:\n * - In-memory event delivery to registered handlers\n * - Optional persistence via the queue package when `persistent: true`\n *\n * @param opts - Configuration options\n * @returns An EventBus instance\n *\n * @example\n * ```typescript\n * const bus = createEventBus({\n * resolve: container.resolve.bind(container),\n * queueStrategy: 'local', // or 'async' for BullMQ\n * })\n *\n * // Register a handler\n * bus.on('user.created', async (payload, ctx) => {\n * const userService = ctx.resolve('userService')\n * await userService.sendWelcomeEmail(payload.userId)\n * })\n *\n * // Emit an event (immediate delivery)\n * await bus.emit('user.created', { userId: '123' })\n *\n * // Emit with persistence (for async worker processing)\n * await bus.emit('order.placed', { orderId: '456' }, { persistent: true })\n * ```\n */\nexport function createEventBus(opts: CreateBusOptions): EventBus {\n // In-memory listeners for immediate event delivery\n const listeners = new Map<string, Set<SubscriberHandler>>()\n\n // Determine queue strategy from options or environment\n const queueStrategy = opts.queueStrategy ??\n (process.env.QUEUE_STRATEGY === 'async' ? 'async' : 'local')\n\n // Lazy-initialized queue for persistent events\n let queue: Queue<EventJobData> | null = null\n\n /**\n * Gets or creates the queue instance for persistent events.\n */\n function getQueue(): Queue<EventJobData> {\n if (!queue) {\n if (queueStrategy === 'async') {\n const redisUrl = process.env.REDIS_URL || process.env.QUEUE_REDIS_URL\n if (!redisUrl) {\n console.warn('[events] No REDIS_URL configured, falling back to localhost:6379')\n }\n queue = createQueue<EventJobData>(EVENTS_QUEUE_NAME, 'async', {\n connection: { url: redisUrl }\n })\n } else {\n queue = createQueue<EventJobData>(EVENTS_QUEUE_NAME, 'local')\n }\n }\n return queue\n }\n\n /**\n * Delivers an event to all registered in-memory handlers.\n * Supports wildcard pattern matching for event patterns.\n */\n async function deliver(event: string, payload: EventPayload): Promise<void> {\n // Check all registered patterns (including wildcards)\n for (const [pattern, handlers] of listeners) {\n if (!matchEventPattern(event, pattern)) continue\n if (!handlers || handlers.size === 0) continue\n\n for (const handler of handlers) {\n try {\n // Pass eventName in context for wildcard handlers\n await Promise.resolve(handler(payload, {\n resolve: opts.resolve,\n eventName: event,\n }))\n } catch (error) {\n console.error(`[events] Handler error for \"${event}\" (pattern: \"${pattern}\"):`, error)\n }\n }\n }\n }\n\n /**\n * Registers a handler for an event.\n */\n function on(event: string, handler: SubscriberHandler): void {\n if (!listeners.has(event)) {\n listeners.set(event, new Set())\n }\n listeners.get(event)!.add(handler)\n }\n\n /**\n * Registers multiple module subscribers at once.\n */\n function registerModuleSubscribers(subs: SubscriberDescriptor[]): void {\n for (const sub of subs) {\n on(sub.event, sub.handler)\n }\n }\n\n /**\n * Emits an event to all registered handlers.\n *\n * If `persistent: true`, also enqueues the event for async processing.\n */\n async function emit(\n event: string,\n payload: EventPayload,\n options?: EmitOptions\n ): Promise<void> {\n // Always deliver to in-memory handlers first\n await deliver(event, payload)\n\n // If persistent, also enqueue for async processing\n if (options?.persistent) {\n const q = getQueue()\n await q.enqueue({ event, payload })\n }\n }\n\n /**\n * Clears all events from the persistent queue.\n */\n async function clearQueue(): Promise<{ removed: number }> {\n const q = getQueue()\n return q.clear()\n }\n\n // Backward compatibility alias\n const emitEvent = emit\n\n return {\n emit,\n emitEvent, // Alias for backward compatibility\n on,\n registerModuleSubscribers,\n clearQueue,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB;AAY5B,MAAM,oBAAoB;AAc1B,SAAS,kBAAkB,WAAmB,SAA0B;AAEtE,MAAI,YAAY,IAAK,QAAO;AAG5B,MAAI,YAAY,UAAW,QAAO;AAGlC,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAKnC,QAAM,eAAe,QAClB,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,OAAO;AACzB,QAAM,QAAQ,IAAI,OAAO,IAAI,YAAY,GAAG;AAC5C,SAAO,MAAM,KAAK,SAAS;AAC7B;AAsCO,SAAS,eAAe,MAAkC;AAE/D,QAAM,YAAY,oBAAI,IAAoC;AAG1D,QAAM,gBAAgB,KAAK,kBACxB,QAAQ,IAAI,mBAAmB,UAAU,UAAU;AAGtD,MAAI,QAAoC;AAKxC,WAAS,WAAgC;AACvC,QAAI,CAAC,OAAO;AACV,UAAI,kBAAkB,SAAS;AAC7B,cAAM,WAAW,QAAQ,IAAI,aAAa,QAAQ,IAAI;AACtD,YAAI,CAAC,UAAU;AACb,kBAAQ,KAAK,kEAAkE;AAAA,QACjF;AACA,gBAAQ,YAA0B,mBAAmB,SAAS;AAAA,UAC5D,YAAY,EAAE,KAAK,SAAS;AAAA,QAC9B,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,YAA0B,mBAAmB,OAAO;AAAA,MAC9D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAMA,iBAAe,QAAQ,OAAe,SAAsC;AAE1E,eAAW,CAAC,SAAS,QAAQ,KAAK,WAAW;AAC3C,UAAI,CAAC,kBAAkB,OAAO,OAAO,EAAG;AACxC,UAAI,CAAC,YAAY,SAAS,SAAS,EAAG;AAEtC,iBAAW,WAAW,UAAU;AAC9B,YAAI;AAEF,gBAAM,QAAQ,QAAQ,QAAQ,SAAS;AAAA,YACrC,SAAS,KAAK;AAAA,YACd,WAAW;AAAA,UACb,CAAC,CAAC;AAAA,QACJ,SAAS,OAAO;AACd,kBAAQ,MAAM,+BAA+B,KAAK,gBAAgB,OAAO,OAAO,KAAK;AAAA,QACvF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAKA,WAAS,GAAG,OAAe,SAAkC;AAC3D,QAAI,CAAC,UAAU,IAAI,KAAK,GAAG;AACzB,gBAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IAChC;AACA,cAAU,IAAI,KAAK,EAAG,IAAI,OAAO;AAAA,EACnC;AAKA,WAAS,0BAA0B,MAAoC;AACrE,eAAW,OAAO,MAAM;AACtB,SAAG,IAAI,OAAO,IAAI,OAAO;AAAA,IAC3B;AAAA,EACF;AAOA,iBAAe,KACb,OACA,SACA,SACe;AAEf,UAAM,QAAQ,OAAO,OAAO;AAG5B,QAAI,SAAS,YAAY;AACvB,YAAM,IAAI,SAAS;AACnB,YAAM,EAAE,QAAQ,EAAE,OAAO,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF;AAKA,iBAAe,aAA2C;AACxD,UAAM,IAAI,SAAS;AACnB,WAAO,EAAE,MAAM;AAAA,EACjB;AAGA,QAAM,YAAY;AAElB,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/events",
|
|
3
|
-
"version": "0.4.2-canary-
|
|
3
|
+
"version": "0.4.2-canary-f728a6d66a",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@open-mercato/queue": "0.4.2-canary-
|
|
35
|
+
"@open-mercato/queue": "0.4.2-canary-f728a6d66a"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/jest": "^30.0.0",
|
package/src/bus.ts
CHANGED
|
@@ -12,6 +12,38 @@ import type {
|
|
|
12
12
|
/** Queue name for persistent events */
|
|
13
13
|
const EVENTS_QUEUE_NAME = 'events'
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Match an event name against a pattern.
|
|
17
|
+
*
|
|
18
|
+
* Supports:
|
|
19
|
+
* - Exact match: `customers.people.created`
|
|
20
|
+
* - Wildcard `*` matches single segment: `customers.*` matches `customers.people` but not `customers.people.created`
|
|
21
|
+
* - Global wildcard: `*` alone matches all events
|
|
22
|
+
*
|
|
23
|
+
* @param eventName - The actual event name
|
|
24
|
+
* @param pattern - The pattern to match against
|
|
25
|
+
* @returns True if the event matches the pattern
|
|
26
|
+
*/
|
|
27
|
+
function matchEventPattern(eventName: string, pattern: string): boolean {
|
|
28
|
+
// Global wildcard matches all events
|
|
29
|
+
if (pattern === '*') return true
|
|
30
|
+
|
|
31
|
+
// Exact match
|
|
32
|
+
if (pattern === eventName) return true
|
|
33
|
+
|
|
34
|
+
// No wildcards in pattern means we need exact match, which already failed
|
|
35
|
+
if (!pattern.includes('*')) return false
|
|
36
|
+
|
|
37
|
+
// Convert pattern to regex:
|
|
38
|
+
// - Escape regex special chars (except *)
|
|
39
|
+
// - Replace * with [^.]+ (match one or more non-dot chars)
|
|
40
|
+
const regexPattern = pattern
|
|
41
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
42
|
+
.replace(/\*/g, '[^.]+')
|
|
43
|
+
const regex = new RegExp(`^${regexPattern}$`)
|
|
44
|
+
return regex.test(eventName)
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
/** Job data structure for queued events */
|
|
16
48
|
type EventJobData = {
|
|
17
49
|
event: string
|
|
@@ -81,16 +113,24 @@ export function createEventBus(opts: CreateBusOptions): EventBus {
|
|
|
81
113
|
|
|
82
114
|
/**
|
|
83
115
|
* Delivers an event to all registered in-memory handlers.
|
|
116
|
+
* Supports wildcard pattern matching for event patterns.
|
|
84
117
|
*/
|
|
85
118
|
async function deliver(event: string, payload: EventPayload): Promise<void> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
119
|
+
// Check all registered patterns (including wildcards)
|
|
120
|
+
for (const [pattern, handlers] of listeners) {
|
|
121
|
+
if (!matchEventPattern(event, pattern)) continue
|
|
122
|
+
if (!handlers || handlers.size === 0) continue
|
|
123
|
+
|
|
124
|
+
for (const handler of handlers) {
|
|
125
|
+
try {
|
|
126
|
+
// Pass eventName in context for wildcard handlers
|
|
127
|
+
await Promise.resolve(handler(payload, {
|
|
128
|
+
resolve: opts.resolve,
|
|
129
|
+
eventName: event,
|
|
130
|
+
}))
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`[events] Handler error for "${event}" (pattern: "${pattern}"):`, error)
|
|
133
|
+
}
|
|
94
134
|
}
|
|
95
135
|
}
|
|
96
136
|
}
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type SubscriberMeta = {
|
|
|
23
23
|
export type SubscriberContext = {
|
|
24
24
|
/** DI container resolve function */
|
|
25
25
|
resolve: <T = unknown>(name: string) => T
|
|
26
|
+
/** Event name (useful for wildcard handlers to know which event was triggered) */
|
|
27
|
+
eventName?: string
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/** Event handler function signature */
|