@contractspec/lib.bus 1.57.0 → 1.58.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/dist/auditableBus.d.ts +76 -80
- package/dist/auditableBus.d.ts.map +1 -1
- package/dist/auditableBus.js +149 -132
- package/dist/browser/auditableBus.js +153 -0
- package/dist/browser/eventBus.js +28 -0
- package/dist/browser/filtering.js +147 -0
- package/dist/browser/inMemoryBus.js +25 -0
- package/dist/browser/index.js +373 -0
- package/dist/browser/metadata.js +60 -0
- package/dist/browser/subscriber.js +37 -0
- package/dist/eventBus.d.ts +9 -13
- package/dist/eventBus.d.ts.map +1 -1
- package/dist/eventBus.js +23 -26
- package/dist/filtering.d.ts +53 -57
- package/dist/filtering.d.ts.map +1 -1
- package/dist/filtering.js +139 -125
- package/dist/inMemoryBus.d.ts +6 -10
- package/dist/inMemoryBus.d.ts.map +1 -1
- package/dist/inMemoryBus.js +25 -28
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +374 -8
- package/dist/metadata.d.ts +60 -63
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +55 -76
- package/dist/node/auditableBus.js +153 -0
- package/dist/node/eventBus.js +28 -0
- package/dist/node/filtering.js +147 -0
- package/dist/node/inMemoryBus.js +25 -0
- package/dist/node/index.js +373 -0
- package/dist/node/metadata.js +60 -0
- package/dist/node/subscriber.js +37 -0
- package/dist/subscriber.d.ts +6 -10
- package/dist/subscriber.d.ts.map +1 -1
- package/dist/subscriber.js +35 -16
- package/package.json +69 -28
- package/dist/auditableBus.js.map +0 -1
- package/dist/eventBus.js.map +0 -1
- package/dist/filtering.js.map +0 -1
- package/dist/inMemoryBus.js.map +0 -1
- package/dist/metadata.js.map +0 -1
- package/dist/subscriber.js.map +0 -1
package/dist/eventBus.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventBus.d.ts","
|
|
1
|
+
{"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../src/eventBus.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,QAAQ,EAEb,KAAK,SAAS,EACf,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,SAAS,EAAE,CACT,KAAK,EAAE,QAAQ,GAAG,MAAM,EAAE,4CAA4C;IACtE,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,KAC1C,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACnC;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,UAAU,CAErE;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CAEjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,cAAc,EACpD,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAEJ,SAAS,CAAC,EAAE,UAAU,MAAM,mBAc3C"}
|
package/dist/eventBus.js
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/eventBus.ts
|
|
3
|
+
import {
|
|
4
|
+
eventKey
|
|
5
|
+
} from "@contractspec/lib.contracts";
|
|
5
6
|
function encodeEvent(envelope) {
|
|
6
|
-
|
|
7
|
+
return new TextEncoder().encode(JSON.stringify(envelope));
|
|
7
8
|
}
|
|
8
|
-
/** Helper to decode JSON string into a typed event envelope */
|
|
9
9
|
function decodeEvent(data) {
|
|
10
|
-
|
|
10
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Create a typed publisher function for a given event spec.
|
|
14
|
-
* It ensures payload conformance at compile time and builds the correct topic.
|
|
15
|
-
*/
|
|
16
12
|
function makePublisher(bus, spec) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
return async (payload, traceId) => {
|
|
14
|
+
const envelope = {
|
|
15
|
+
id: crypto.randomUUID(),
|
|
16
|
+
occurredAt: new Date().toISOString(),
|
|
17
|
+
key: spec.meta.key,
|
|
18
|
+
version: spec.meta.version,
|
|
19
|
+
payload,
|
|
20
|
+
traceId
|
|
21
|
+
};
|
|
22
|
+
await bus.publish(eventKey(spec.meta.key, spec.meta.version), encodeEvent(envelope));
|
|
23
|
+
};
|
|
28
24
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
export {
|
|
26
|
+
makePublisher,
|
|
27
|
+
encodeEvent,
|
|
28
|
+
decodeEvent
|
|
29
|
+
};
|
package/dist/filtering.d.ts
CHANGED
|
@@ -1,82 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
//#region src/filtering.d.ts
|
|
1
|
+
import type { EventKey, EventSpec } from '@contractspec/lib.contracts';
|
|
2
|
+
import type { AnySchemaModel } from '@contractspec/lib.schema';
|
|
3
|
+
import type { EventBus } from './eventBus';
|
|
4
|
+
import type { AuditableEventEnvelope } from './auditableBus';
|
|
7
5
|
/**
|
|
8
6
|
* Event filter configuration.
|
|
9
7
|
*/
|
|
10
|
-
interface EventFilter {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
export interface EventFilter {
|
|
9
|
+
/** Filter by event name pattern (supports * wildcard) */
|
|
10
|
+
eventName?: string;
|
|
11
|
+
/** Filter by domain prefix */
|
|
12
|
+
domain?: string;
|
|
13
|
+
/** Filter by version */
|
|
14
|
+
version?: string;
|
|
15
|
+
/** Filter by actor ID */
|
|
16
|
+
actorId?: string;
|
|
17
|
+
/** Filter by organization ID */
|
|
18
|
+
orgId?: string;
|
|
19
|
+
/** Filter by custom tags */
|
|
20
|
+
tags?: Record<string, string>;
|
|
21
|
+
/** Custom predicate function */
|
|
22
|
+
predicate?: (envelope: AuditableEventEnvelope) => boolean;
|
|
25
23
|
}
|
|
26
24
|
/**
|
|
27
25
|
* Check if an event matches a filter.
|
|
28
26
|
*/
|
|
29
|
-
declare function matchesFilter(envelope: AuditableEventEnvelope, filter: EventFilter): boolean;
|
|
27
|
+
export declare function matchesFilter(envelope: AuditableEventEnvelope, filter: EventFilter): boolean;
|
|
30
28
|
/**
|
|
31
29
|
* Create a filtered subscriber that only receives matching events.
|
|
32
30
|
*/
|
|
33
|
-
declare function createFilteredSubscriber(bus: EventBus, filter: EventFilter, handler: (envelope: AuditableEventEnvelope) => Promise<void>): (topic: EventKey | string) => Promise<() => Promise<void>>;
|
|
31
|
+
export declare function createFilteredSubscriber(bus: EventBus, filter: EventFilter, handler: (envelope: AuditableEventEnvelope) => Promise<void>): (topic: EventKey | string) => Promise<() => Promise<void>>;
|
|
34
32
|
/**
|
|
35
33
|
* Domain-specific event bus that filters by domain prefix.
|
|
36
34
|
*/
|
|
37
|
-
declare class DomainEventBus {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
export declare class DomainEventBus {
|
|
36
|
+
private readonly bus;
|
|
37
|
+
private readonly domain;
|
|
38
|
+
constructor(bus: EventBus, domain: string);
|
|
39
|
+
/**
|
|
40
|
+
* Publish a domain event.
|
|
41
|
+
*/
|
|
42
|
+
publish<T extends AnySchemaModel>(spec: EventSpec<T>, payload: T, metadata?: AuditableEventEnvelope['metadata']): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Subscribe to all domain events.
|
|
45
|
+
*/
|
|
46
|
+
subscribeAll(handler: (envelope: AuditableEventEnvelope) => Promise<void>): Promise<() => Promise<void>>;
|
|
47
|
+
/**
|
|
48
|
+
* Subscribe with filter.
|
|
49
|
+
*/
|
|
50
|
+
subscribeFiltered(filter: Omit<EventFilter, 'domain'>, handler: (envelope: AuditableEventEnvelope) => Promise<void>): Promise<() => Promise<void>>;
|
|
53
51
|
}
|
|
54
52
|
/**
|
|
55
53
|
* Create a domain-scoped event bus.
|
|
56
54
|
*/
|
|
57
|
-
declare function createDomainBus(bus: EventBus, domain: string): DomainEventBus;
|
|
55
|
+
export declare function createDomainBus(bus: EventBus, domain: string): DomainEventBus;
|
|
58
56
|
/**
|
|
59
57
|
* Event router that routes events to different handlers based on filters.
|
|
60
58
|
*/
|
|
61
|
-
declare class EventRouter {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
export declare class EventRouter {
|
|
60
|
+
private routes;
|
|
61
|
+
/**
|
|
62
|
+
* Add a route.
|
|
63
|
+
*/
|
|
64
|
+
route(filter: EventFilter, handler: (envelope: AuditableEventEnvelope) => Promise<void>): this;
|
|
65
|
+
/**
|
|
66
|
+
* Route an event to matching handlers.
|
|
67
|
+
*/
|
|
68
|
+
dispatch(envelope: AuditableEventEnvelope): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Create a subscriber that routes events.
|
|
71
|
+
*/
|
|
72
|
+
createSubscriber(bus: EventBus): (topic: EventKey | string) => Promise<() => Promise<void>>;
|
|
75
73
|
}
|
|
76
74
|
/**
|
|
77
75
|
* Create an event router.
|
|
78
76
|
*/
|
|
79
|
-
declare function createEventRouter(): EventRouter;
|
|
80
|
-
//#endregion
|
|
81
|
-
export { DomainEventBus, EventFilter, EventRouter, createDomainBus, createEventRouter, createFilteredSubscriber, matchesFilter };
|
|
77
|
+
export declare function createEventRouter(): EventRouter;
|
|
82
78
|
//# sourceMappingURL=filtering.d.ts.map
|
package/dist/filtering.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filtering.d.ts","
|
|
1
|
+
{"version":3,"file":"filtering.d.ts","sourceRoot":"","sources":["../src/filtering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG7D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,gCAAgC;IAChC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,OAAO,CAAC;CAC3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,sBAAsB,EAChC,MAAM,EAAE,WAAW,GAClB,OAAO,CAiDT;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,IAE9C,OAAO,QAAQ,GAAG,MAAM,kCASvC;AAED;;GAEG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM;IAGjC;;OAEG;IACG,OAAO,CAAC,CAAC,SAAS,cAAc,EACpC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAClB,OAAO,EAAE,CAAC,EACV,QAAQ,CAAC,EAAE,sBAAsB,CAAC,UAAU,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC;IAmBhB;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAC3D,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAQ/B;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EACnC,OAAO,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAC3D,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAchC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAE7E;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAGL;IAET;;OAEG;IACH,KAAK,CACH,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAC3D,IAAI;IAKP;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/D;;OAEG;IACH,gBAAgB,CACd,GAAG,EAAE,QAAQ,GACZ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAQ9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C"}
|
package/dist/filtering.js
CHANGED
|
@@ -1,134 +1,148 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/eventBus.ts
|
|
3
|
+
import {
|
|
4
|
+
eventKey
|
|
5
|
+
} from "@contractspec/lib.contracts";
|
|
6
|
+
function encodeEvent(envelope) {
|
|
7
|
+
return new TextEncoder().encode(JSON.stringify(envelope));
|
|
8
|
+
}
|
|
9
|
+
function decodeEvent(data) {
|
|
10
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
11
|
+
}
|
|
12
|
+
function makePublisher(bus, spec) {
|
|
13
|
+
return async (payload, traceId) => {
|
|
14
|
+
const envelope = {
|
|
15
|
+
id: crypto.randomUUID(),
|
|
16
|
+
occurredAt: new Date().toISOString(),
|
|
17
|
+
key: spec.meta.key,
|
|
18
|
+
version: spec.meta.version,
|
|
19
|
+
payload,
|
|
20
|
+
traceId
|
|
21
|
+
};
|
|
22
|
+
await bus.publish(eventKey(spec.meta.key, spec.meta.version), encodeEvent(envelope));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
3
25
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Check if an event matches a filter.
|
|
7
|
-
*/
|
|
26
|
+
// src/filtering.ts
|
|
27
|
+
import { satisfies } from "compare-versions";
|
|
8
28
|
function matchesFilter(envelope, filter) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
if (filter.eventName) {
|
|
30
|
+
const pattern = filter.eventName.replace(/\*/g, ".*");
|
|
31
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
32
|
+
if (!regex.test(envelope.key)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (filter.domain) {
|
|
37
|
+
if (!envelope.key.startsWith(filter.domain + ".")) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (filter.version) {
|
|
42
|
+
if (!envelope.version || !satisfies(envelope.version, filter.version)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (filter.actorId && envelope.metadata?.actorId !== filter.actorId) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (filter.orgId && envelope.metadata?.orgId !== filter.orgId) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (filter.tags) {
|
|
53
|
+
const eventTags = envelope.metadata?.tags ?? {};
|
|
54
|
+
for (const [key, value] of Object.entries(filter.tags)) {
|
|
55
|
+
if (eventTags[key] !== value) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (filter.predicate && !filter.predicate(envelope)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
27
64
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Create a filtered subscriber that only receives matching events.
|
|
30
|
-
*/
|
|
31
65
|
function createFilteredSubscriber(bus, filter, handler) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
return async (topic) => {
|
|
67
|
+
return bus.subscribe(topic, async (bytes) => {
|
|
68
|
+
const envelope = decodeEvent(bytes);
|
|
69
|
+
if (matchesFilter(envelope, filter)) {
|
|
70
|
+
await handler(envelope);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class DomainEventBus {
|
|
77
|
+
bus;
|
|
78
|
+
domain;
|
|
79
|
+
constructor(bus, domain) {
|
|
80
|
+
this.bus = bus;
|
|
81
|
+
this.domain = domain;
|
|
82
|
+
}
|
|
83
|
+
async publish(spec, payload, metadata) {
|
|
84
|
+
const eventName = spec.meta.key.startsWith(this.domain + ".") ? spec.meta.key : `${this.domain}.${spec.meta.key}`;
|
|
85
|
+
const envelope = {
|
|
86
|
+
id: crypto.randomUUID(),
|
|
87
|
+
occurredAt: new Date().toISOString(),
|
|
88
|
+
key: eventName,
|
|
89
|
+
version: spec.meta.version,
|
|
90
|
+
payload,
|
|
91
|
+
metadata
|
|
92
|
+
};
|
|
93
|
+
const bytes = new TextEncoder().encode(JSON.stringify(envelope));
|
|
94
|
+
await this.bus.publish(`${eventName}.v${spec.meta.version}`, bytes);
|
|
95
|
+
}
|
|
96
|
+
async subscribeAll(handler) {
|
|
97
|
+
return this.bus.subscribe(`${this.domain}.*`, async (bytes) => {
|
|
98
|
+
const envelope = decodeEvent(bytes);
|
|
99
|
+
await handler(envelope);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async subscribeFiltered(filter, handler) {
|
|
103
|
+
const fullFilter = {
|
|
104
|
+
...filter,
|
|
105
|
+
domain: this.domain
|
|
106
|
+
};
|
|
107
|
+
return this.bus.subscribe(`${this.domain}.*`, async (bytes) => {
|
|
108
|
+
const envelope = decodeEvent(bytes);
|
|
109
|
+
if (matchesFilter(envelope, fullFilter)) {
|
|
110
|
+
await handler(envelope);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
38
114
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Domain-specific event bus that filters by domain prefix.
|
|
41
|
-
*/
|
|
42
|
-
var DomainEventBus = class {
|
|
43
|
-
constructor(bus, domain) {
|
|
44
|
-
this.bus = bus;
|
|
45
|
-
this.domain = domain;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Publish a domain event.
|
|
49
|
-
*/
|
|
50
|
-
async publish(spec, payload, metadata) {
|
|
51
|
-
const eventName = spec.meta.key.startsWith(this.domain + ".") ? spec.meta.key : `${this.domain}.${spec.meta.key}`;
|
|
52
|
-
const envelope = {
|
|
53
|
-
id: crypto.randomUUID(),
|
|
54
|
-
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
55
|
-
key: eventName,
|
|
56
|
-
version: spec.meta.version,
|
|
57
|
-
payload,
|
|
58
|
-
metadata
|
|
59
|
-
};
|
|
60
|
-
const bytes = new TextEncoder().encode(JSON.stringify(envelope));
|
|
61
|
-
await this.bus.publish(`${eventName}.v${spec.meta.version}`, bytes);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Subscribe to all domain events.
|
|
65
|
-
*/
|
|
66
|
-
async subscribeAll(handler) {
|
|
67
|
-
return this.bus.subscribe(`${this.domain}.*`, async (bytes) => {
|
|
68
|
-
await handler(decodeEvent(bytes));
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Subscribe with filter.
|
|
73
|
-
*/
|
|
74
|
-
async subscribeFiltered(filter, handler) {
|
|
75
|
-
const fullFilter = {
|
|
76
|
-
...filter,
|
|
77
|
-
domain: this.domain
|
|
78
|
-
};
|
|
79
|
-
return this.bus.subscribe(`${this.domain}.*`, async (bytes) => {
|
|
80
|
-
const envelope = decodeEvent(bytes);
|
|
81
|
-
if (matchesFilter(envelope, fullFilter)) await handler(envelope);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
|
-
* Create a domain-scoped event bus.
|
|
87
|
-
*/
|
|
88
115
|
function createDomainBus(bus, domain) {
|
|
89
|
-
|
|
116
|
+
return new DomainEventBus(bus, domain);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class EventRouter {
|
|
120
|
+
routes = [];
|
|
121
|
+
route(filter, handler) {
|
|
122
|
+
this.routes.push({ filter, handler });
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
async dispatch(envelope) {
|
|
126
|
+
const matchingRoutes = this.routes.filter((r) => matchesFilter(envelope, r.filter));
|
|
127
|
+
await Promise.all(matchingRoutes.map((r) => r.handler(envelope)));
|
|
128
|
+
}
|
|
129
|
+
createSubscriber(bus) {
|
|
130
|
+
return async (topic) => {
|
|
131
|
+
return bus.subscribe(topic, async (bytes) => {
|
|
132
|
+
const envelope = decodeEvent(bytes);
|
|
133
|
+
await this.dispatch(envelope);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
}
|
|
90
137
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Event router that routes events to different handlers based on filters.
|
|
93
|
-
*/
|
|
94
|
-
var EventRouter = class {
|
|
95
|
-
routes = [];
|
|
96
|
-
/**
|
|
97
|
-
* Add a route.
|
|
98
|
-
*/
|
|
99
|
-
route(filter, handler) {
|
|
100
|
-
this.routes.push({
|
|
101
|
-
filter,
|
|
102
|
-
handler
|
|
103
|
-
});
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Route an event to matching handlers.
|
|
108
|
-
*/
|
|
109
|
-
async dispatch(envelope) {
|
|
110
|
-
const matchingRoutes = this.routes.filter((r) => matchesFilter(envelope, r.filter));
|
|
111
|
-
await Promise.all(matchingRoutes.map((r) => r.handler(envelope)));
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Create a subscriber that routes events.
|
|
115
|
-
*/
|
|
116
|
-
createSubscriber(bus) {
|
|
117
|
-
return async (topic) => {
|
|
118
|
-
return bus.subscribe(topic, async (bytes) => {
|
|
119
|
-
const envelope = decodeEvent(bytes);
|
|
120
|
-
await this.dispatch(envelope);
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
|
-
* Create an event router.
|
|
127
|
-
*/
|
|
128
138
|
function createEventRouter() {
|
|
129
|
-
|
|
139
|
+
return new EventRouter;
|
|
130
140
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
export {
|
|
142
|
+
matchesFilter,
|
|
143
|
+
createFilteredSubscriber,
|
|
144
|
+
createEventRouter,
|
|
145
|
+
createDomainBus,
|
|
146
|
+
EventRouter,
|
|
147
|
+
DomainEventBus
|
|
148
|
+
};
|
package/dist/inMemoryBus.d.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
//#region src/inMemoryBus.d.ts
|
|
1
|
+
import type { EventKey } from '@contractspec/lib.contracts';
|
|
2
|
+
import type { EventBus } from './eventBus';
|
|
5
3
|
/**
|
|
6
4
|
* In-memory bus for dev/test. Not for production scale.
|
|
7
5
|
* Subscribers receive events synchronously in the order they were published.
|
|
8
6
|
*/
|
|
9
|
-
declare class InMemoryBus implements EventBus {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export declare class InMemoryBus implements EventBus {
|
|
8
|
+
private listeners;
|
|
9
|
+
publish(topic: EventKey, payload: Uint8Array): Promise<void>;
|
|
10
|
+
subscribe(topic: string | RegExp, handler: (payload: Uint8Array) => Promise<void>): Promise<() => Promise<void>>;
|
|
13
11
|
}
|
|
14
|
-
//#endregion
|
|
15
|
-
export { InMemoryBus };
|
|
16
12
|
//# sourceMappingURL=inMemoryBus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inMemoryBus.d.ts","
|
|
1
|
+
{"version":3,"file":"inMemoryBus.d.ts","sourceRoot":"","sources":["../src/inMemoryBus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;GAGG;AACH,qBAAa,WAAY,YAAW,QAAQ;IAC1C,OAAO,CAAC,SAAS,CAGb;IAEE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5D,SAAS,CACb,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAC9C,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAYhC"}
|
package/dist/inMemoryBus.js
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/inMemoryBus.ts
|
|
3
|
+
class InMemoryBus {
|
|
4
|
+
listeners = new Map;
|
|
5
|
+
async publish(topic, payload) {
|
|
6
|
+
const handlers = this.listeners.get(topic);
|
|
7
|
+
if (!handlers)
|
|
8
|
+
return;
|
|
9
|
+
await Promise.all([...handlers].map((h) => h(payload)));
|
|
10
|
+
}
|
|
11
|
+
async subscribe(topic, handler) {
|
|
12
|
+
const topicStr = String(topic);
|
|
13
|
+
let set = this.listeners.get(topicStr);
|
|
14
|
+
if (!set) {
|
|
15
|
+
set = new Set;
|
|
16
|
+
this.listeners.set(topicStr, set);
|
|
17
|
+
}
|
|
18
|
+
set.add(handler);
|
|
19
|
+
return async () => {
|
|
20
|
+
set?.delete(handler);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
InMemoryBus
|
|
25
26
|
};
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
export { InMemoryBus };
|
|
29
|
-
//# sourceMappingURL=inMemoryBus.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
export * from './eventBus';
|
|
2
|
+
export * from './inMemoryBus';
|
|
3
|
+
export * from './subscriber';
|
|
4
|
+
export * from './metadata';
|
|
5
|
+
export * from './auditableBus';
|
|
6
|
+
export * from './filtering';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|