@miiajs/messaging 0.1.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/decorators.d.ts +149 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +112 -0
- package/dist/decorators.js.map +1 -0
- package/dist/group-name.d.ts +33 -0
- package/dist/group-name.d.ts.map +1 -0
- package/dist/group-name.js +25 -0
- package/dist/group-name.js.map +1 -0
- package/dist/idempotency.d.ts +66 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +42 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/in-memory-transport.d.ts +60 -0
- package/dist/in-memory-transport.d.ts.map +1 -0
- package/dist/in-memory-transport.js +143 -0
- package/dist/in-memory-transport.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/message-bus.d.ts +83 -0
- package/dist/message-bus.d.ts.map +1 -0
- package/dist/message-bus.js +218 -0
- package/dist/message-bus.js.map +1 -0
- package/dist/messaging.module.d.ts +76 -0
- package/dist/messaging.module.d.ts.map +1 -0
- package/dist/messaging.module.js +74 -0
- package/dist/messaging.module.js.map +1 -0
- package/dist/retry.d.ts +7 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +10 -0
- package/dist/retry.js.map +1 -0
- package/dist/tokens.d.ts +19 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +26 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ruslan Matiushev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @miiajs/messaging
|
|
2
|
+
|
|
3
|
+
Decorator-driven message bus for MiiaJS - retry with auto-DLQ, idempotency, named buses, W3C tracing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @miiajs/messaging
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
**[miiajs.com/docs/packages/messaging](https://miiajs.com/docs/packages/messaging)**
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
MIT
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { type DiscoverableMethodMeta } from '@miiajs/core';
|
|
2
|
+
import type { DispatchMode, MessageMeta } from './types.js';
|
|
3
|
+
export declare const ON: unique symbol;
|
|
4
|
+
export declare const IDEMPOTENT: unique symbol;
|
|
5
|
+
export interface OnMeta extends DiscoverableMethodMeta {
|
|
6
|
+
topic: string;
|
|
7
|
+
/**
|
|
8
|
+
* Explicit broker consumer group (competing-consumers worker pool). When
|
|
9
|
+
* omitted, the bus auto-derives a per-handler group `<topic>__<Class>_<method>`
|
|
10
|
+
* (optionally `<appName>:` prefixed). Setting this option puts multiple
|
|
11
|
+
* handlers (across classes or replicas) into the same broker group so the
|
|
12
|
+
* broker round-robins each message to one of them. Requires
|
|
13
|
+
* `transport.supportsCompetingConsumers === true`.
|
|
14
|
+
*/
|
|
15
|
+
group?: string;
|
|
16
|
+
concurrency?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Target bus name when multiple `MessagingModule.configure(opts, name)` are
|
|
19
|
+
* registered. Omit (or `undefined`) to target the default bus. Throws at
|
|
20
|
+
* startup if the referenced bus name has no matching `MessagingModule`.
|
|
21
|
+
*/
|
|
22
|
+
bus?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Per-handler override of the bus / transport dispatch mode. Resolution
|
|
25
|
+
* order: this field > `MessagingModule.configure({ dispatch: { mode } })` >
|
|
26
|
+
* `transport.defaultMode`. Validated against `transport.supportedModes` at
|
|
27
|
+
* `MessageBus.onReady()`.
|
|
28
|
+
*/
|
|
29
|
+
mode?: DispatchMode;
|
|
30
|
+
/**
|
|
31
|
+
* Cluster-wide fan-out for this handler. When `true`, the auto-derived group
|
|
32
|
+
* is suffixed with `__<hostname>_<pid>` so every replica gets a unique broker
|
|
33
|
+
* group and the broker delivers a copy of each message to every replica.
|
|
34
|
+
* Mutually exclusive with `group` - explicit `group` is for shared work
|
|
35
|
+
* across handlers/replicas, broadcast replicates work to every replica.
|
|
36
|
+
*
|
|
37
|
+
* Use for in-process state that every replica must update on its own
|
|
38
|
+
* (cache invalidation, websocket broadcast).
|
|
39
|
+
*/
|
|
40
|
+
broadcast?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface IdempotentMeta {
|
|
43
|
+
/** Claim lifetime in milliseconds. */
|
|
44
|
+
ttl: number;
|
|
45
|
+
key?: (payload: unknown, meta: MessageMeta) => string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Marks a method as a message handler for `topic`. At app startup, MessageBus
|
|
49
|
+
* discovers all `@On` methods via DiscoveryService (during `onReady`) and
|
|
50
|
+
* subscribes each one to the configured transport.
|
|
51
|
+
*
|
|
52
|
+
* @param topic Free-form string. Transports do not interpret dots/slashes.
|
|
53
|
+
* @param options.group Explicit broker consumer group (competing-consumers
|
|
54
|
+
* worker pool). Without it, an auto-derived
|
|
55
|
+
* per-handler group is used.
|
|
56
|
+
* @param options.concurrency Per-handler subscription concurrency. In sliding
|
|
57
|
+
* mode = number of lanes.
|
|
58
|
+
* @param options.mode Per-handler dispatch mode. Resolution: this
|
|
59
|
+
* field > bus default > `transport.defaultMode`.
|
|
60
|
+
* @param options.bus Target bus for multi-bus setups.
|
|
61
|
+
* @param options.broadcast Cluster-wide fan-out (every replica gets a copy).
|
|
62
|
+
* Mutually exclusive with `group`.
|
|
63
|
+
*
|
|
64
|
+
* **Subscription model.** Each `@On` becomes its own broker subscription with
|
|
65
|
+
* an auto-derived consumer group `<topic>__<ClassName>_<methodName>` (or
|
|
66
|
+
* `<appName>:<topic>__<ClassName>_<methodName>` if `MessagingModule.configure`
|
|
67
|
+
* provides `appName`). Within one process every handler runs independently:
|
|
68
|
+
* retry, ack/nack, mode/concurrency are isolated per handler.
|
|
69
|
+
*
|
|
70
|
+
* For replicas of the same handler running in N processes, broker round-robins
|
|
71
|
+
* each message to exactly one of the N consumers (load balance scaling).
|
|
72
|
+
*
|
|
73
|
+
* For cluster-wide fan-out (each replica processes its own copy), set
|
|
74
|
+
* `broadcast: true`. For competing-consumers worker pool across multiple
|
|
75
|
+
* handler classes, pass explicit `group: 'pool-name'` (requires
|
|
76
|
+
* `transport.supportsCompetingConsumers === true`).
|
|
77
|
+
*
|
|
78
|
+
* **Multi-topic handlers.** Decorating one method with multiple `@On` works
|
|
79
|
+
* naturally - each decoration creates its own subscription with its own
|
|
80
|
+
* auto-derived group. Useful when a single handler responds to several
|
|
81
|
+
* topics:
|
|
82
|
+
* ```ts
|
|
83
|
+
* @On('user.created')
|
|
84
|
+
* @On('user.updated')
|
|
85
|
+
* async syncToCRM(user: User) { ... }
|
|
86
|
+
* // → 2 subscriptions: user.created__SyncService_syncToCRM, user.updated__...
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* @On('user.created') // 1 worker, fan-out across handlers
|
|
92
|
+
* @On('cache.invalidate', { broadcast: true }) // copy to every replica
|
|
93
|
+
* @On('jobs', { group: 'workers' }) // explicit competing-consumers pool
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare const On: (topic: string, options?: {
|
|
97
|
+
group?: string;
|
|
98
|
+
concurrency?: number;
|
|
99
|
+
bus?: string;
|
|
100
|
+
mode?: DispatchMode;
|
|
101
|
+
broadcast?: boolean;
|
|
102
|
+
} | undefined) => (target: Function, context: ClassMethodDecoratorContext) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Skip the handler when the same logical message has already been processed.
|
|
105
|
+
*
|
|
106
|
+
* When applied to an `@On` handler, MessageBus claims an idempotency key in the
|
|
107
|
+
* configured `IdempotencyStore` before invoking the handler. If the claim
|
|
108
|
+
* already exists (duplicate delivery from XAUTOCLAIM, network blip, etc.),
|
|
109
|
+
* the handler is silently skipped and the message is acked.
|
|
110
|
+
*
|
|
111
|
+
* Default key is `${envelope.id}:${ClassName}.${methodName}` - per-handler
|
|
112
|
+
* scope, so two `@Idempotent` handlers on the same topic do NOT conflict
|
|
113
|
+
* by default. Pass an explicit `key` to override (for example, dedupe on a
|
|
114
|
+
* business identifier from payload, or share a single key across handlers).
|
|
115
|
+
*
|
|
116
|
+
* **NOT exactly-once.** If the consumer crashes after `claim()` but before
|
|
117
|
+
* the message is acked back to the broker, the claim stays in the store
|
|
118
|
+
* while the broker still considers the message un-acked. After redelivery
|
|
119
|
+
* the next consumer sees a stale claim and skips the handler, effectively
|
|
120
|
+
* losing the message. For business-critical workflows pair `@Idempotent`
|
|
121
|
+
* with a transactional outbox or use idempotent-by-design handlers
|
|
122
|
+
* (`UPDATE WHERE id=?`) instead.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* @Injectable()
|
|
127
|
+
* class PaymentService {
|
|
128
|
+
* @On('order.placed')
|
|
129
|
+
* @Idempotent({ ttl: 24 * 60 * 60 * 1000 }) // dedupe within 24h
|
|
130
|
+
* async chargeCard(order: Order) {
|
|
131
|
+
* await this.payments.charge(order.cardId, order.total)
|
|
132
|
+
* }
|
|
133
|
+
*
|
|
134
|
+
* // Custom key when the upstream may republish the same business event
|
|
135
|
+
* // with different envelope.id values:
|
|
136
|
+
* @On('payment.received')
|
|
137
|
+
* @Idempotent({ ttl: 24 * 60 * 60 * 1000, key: (p: Payment) => `payment:${p.transactionId}` })
|
|
138
|
+
* async onPayment(payment: Payment) { ... }
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @throws at app startup if any `@Idempotent` handler exists but no
|
|
143
|
+
* `IdempotencyStore` was passed to `MessagingModule.configure()`.
|
|
144
|
+
*/
|
|
145
|
+
export declare const Idempotent: (options: {
|
|
146
|
+
ttl: number;
|
|
147
|
+
key?: (payload: unknown, meta: MessageMeta) => string;
|
|
148
|
+
}) => (target: Function, context: ClassMethodDecoratorContext) => void;
|
|
149
|
+
//# sourceMappingURL=decorators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE3D,eAAO,MAAM,EAAE,eAA8B,CAAA;AAE7C,eAAO,MAAM,UAAU,eAAsC,CAAA;AAE7D,MAAM,WAAW,MAAO,SAAQ,sBAAsB;IACpD,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,KAAK,MAAM,CAAA;CACtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,EAAE;YAGS,MAAM;kBAAgB,MAAM;UAAQ,MAAM;WAAS,YAAY;gBAAc,OAAO;kFAY1G,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,UAAU;SACJ,MAAM;UAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,KAAK,MAAM;sEAM9E,CAAA"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createMethodDecorator, pushMeta, setInMapMeta } from '@miiajs/core';
|
|
2
|
+
export const ON = Symbol('miia:messaging:on');
|
|
3
|
+
export const IDEMPOTENT = Symbol('miia:messaging:idempotent');
|
|
4
|
+
/**
|
|
5
|
+
* Marks a method as a message handler for `topic`. At app startup, MessageBus
|
|
6
|
+
* discovers all `@On` methods via DiscoveryService (during `onReady`) and
|
|
7
|
+
* subscribes each one to the configured transport.
|
|
8
|
+
*
|
|
9
|
+
* @param topic Free-form string. Transports do not interpret dots/slashes.
|
|
10
|
+
* @param options.group Explicit broker consumer group (competing-consumers
|
|
11
|
+
* worker pool). Without it, an auto-derived
|
|
12
|
+
* per-handler group is used.
|
|
13
|
+
* @param options.concurrency Per-handler subscription concurrency. In sliding
|
|
14
|
+
* mode = number of lanes.
|
|
15
|
+
* @param options.mode Per-handler dispatch mode. Resolution: this
|
|
16
|
+
* field > bus default > `transport.defaultMode`.
|
|
17
|
+
* @param options.bus Target bus for multi-bus setups.
|
|
18
|
+
* @param options.broadcast Cluster-wide fan-out (every replica gets a copy).
|
|
19
|
+
* Mutually exclusive with `group`.
|
|
20
|
+
*
|
|
21
|
+
* **Subscription model.** Each `@On` becomes its own broker subscription with
|
|
22
|
+
* an auto-derived consumer group `<topic>__<ClassName>_<methodName>` (or
|
|
23
|
+
* `<appName>:<topic>__<ClassName>_<methodName>` if `MessagingModule.configure`
|
|
24
|
+
* provides `appName`). Within one process every handler runs independently:
|
|
25
|
+
* retry, ack/nack, mode/concurrency are isolated per handler.
|
|
26
|
+
*
|
|
27
|
+
* For replicas of the same handler running in N processes, broker round-robins
|
|
28
|
+
* each message to exactly one of the N consumers (load balance scaling).
|
|
29
|
+
*
|
|
30
|
+
* For cluster-wide fan-out (each replica processes its own copy), set
|
|
31
|
+
* `broadcast: true`. For competing-consumers worker pool across multiple
|
|
32
|
+
* handler classes, pass explicit `group: 'pool-name'` (requires
|
|
33
|
+
* `transport.supportsCompetingConsumers === true`).
|
|
34
|
+
*
|
|
35
|
+
* **Multi-topic handlers.** Decorating one method with multiple `@On` works
|
|
36
|
+
* naturally - each decoration creates its own subscription with its own
|
|
37
|
+
* auto-derived group. Useful when a single handler responds to several
|
|
38
|
+
* topics:
|
|
39
|
+
* ```ts
|
|
40
|
+
* @On('user.created')
|
|
41
|
+
* @On('user.updated')
|
|
42
|
+
* async syncToCRM(user: User) { ... }
|
|
43
|
+
* // → 2 subscriptions: user.created__SyncService_syncToCRM, user.updated__...
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* @On('user.created') // 1 worker, fan-out across handlers
|
|
49
|
+
* @On('cache.invalidate', { broadcast: true }) // copy to every replica
|
|
50
|
+
* @On('jobs', { group: 'workers' }) // explicit competing-consumers pool
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const On = createMethodDecorator((_target, ctx, topic, options) => {
|
|
54
|
+
pushMeta(ctx.metadata, ON, {
|
|
55
|
+
handlerName: ctx.name,
|
|
56
|
+
topic,
|
|
57
|
+
group: options?.group,
|
|
58
|
+
concurrency: options?.concurrency,
|
|
59
|
+
bus: options?.bus,
|
|
60
|
+
mode: options?.mode,
|
|
61
|
+
broadcast: options?.broadcast,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Skip the handler when the same logical message has already been processed.
|
|
66
|
+
*
|
|
67
|
+
* When applied to an `@On` handler, MessageBus claims an idempotency key in the
|
|
68
|
+
* configured `IdempotencyStore` before invoking the handler. If the claim
|
|
69
|
+
* already exists (duplicate delivery from XAUTOCLAIM, network blip, etc.),
|
|
70
|
+
* the handler is silently skipped and the message is acked.
|
|
71
|
+
*
|
|
72
|
+
* Default key is `${envelope.id}:${ClassName}.${methodName}` - per-handler
|
|
73
|
+
* scope, so two `@Idempotent` handlers on the same topic do NOT conflict
|
|
74
|
+
* by default. Pass an explicit `key` to override (for example, dedupe on a
|
|
75
|
+
* business identifier from payload, or share a single key across handlers).
|
|
76
|
+
*
|
|
77
|
+
* **NOT exactly-once.** If the consumer crashes after `claim()` but before
|
|
78
|
+
* the message is acked back to the broker, the claim stays in the store
|
|
79
|
+
* while the broker still considers the message un-acked. After redelivery
|
|
80
|
+
* the next consumer sees a stale claim and skips the handler, effectively
|
|
81
|
+
* losing the message. For business-critical workflows pair `@Idempotent`
|
|
82
|
+
* with a transactional outbox or use idempotent-by-design handlers
|
|
83
|
+
* (`UPDATE WHERE id=?`) instead.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* @Injectable()
|
|
88
|
+
* class PaymentService {
|
|
89
|
+
* @On('order.placed')
|
|
90
|
+
* @Idempotent({ ttl: 24 * 60 * 60 * 1000 }) // dedupe within 24h
|
|
91
|
+
* async chargeCard(order: Order) {
|
|
92
|
+
* await this.payments.charge(order.cardId, order.total)
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* // Custom key when the upstream may republish the same business event
|
|
96
|
+
* // with different envelope.id values:
|
|
97
|
+
* @On('payment.received')
|
|
98
|
+
* @Idempotent({ ttl: 24 * 60 * 60 * 1000, key: (p: Payment) => `payment:${p.transactionId}` })
|
|
99
|
+
* async onPayment(payment: Payment) { ... }
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @throws at app startup if any `@Idempotent` handler exists but no
|
|
104
|
+
* `IdempotencyStore` was passed to `MessagingModule.configure()`.
|
|
105
|
+
*/
|
|
106
|
+
export const Idempotent = createMethodDecorator((_target, ctx, options) => {
|
|
107
|
+
setInMapMeta(ctx.metadata, IDEMPOTENT, String(ctx.name), {
|
|
108
|
+
ttl: options.ttl,
|
|
109
|
+
key: options.key,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=decorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,YAAY,EAA+B,MAAM,cAAc,CAAA;AAGzG,MAAM,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAA;AAE7C,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAA;AA8C7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,EAAE,GAAG,qBAAqB,CAKrC,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IACjC,QAAQ,CAAC,GAAG,CAAC,QAAS,EAAE,EAAE,EAAE;QAC1B,WAAW,EAAE,GAAG,CAAC,IAAc;QAC/B,KAAK;QACL,KAAK,EAAE,OAAO,EAAE,KAAK;QACrB,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,GAAG,EAAE,OAAO,EAAE,GAAG;QACjB,IAAI,EAAE,OAAO,EAAE,IAAI;QACnB,SAAS,EAAE,OAAO,EAAE,SAAS;KACb,CAAC,CAAA;AACrB,CAAC,CAAC,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,qBAAqB,CAE7C,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IAC1B,YAAY,CAAC,GAAG,CAAC,QAAS,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACxD,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;KACQ,CAAC,CAAA;AAC7B,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface DeriveGroupNameInput {
|
|
2
|
+
topic: string;
|
|
3
|
+
ctorName: string;
|
|
4
|
+
methodName: string;
|
|
5
|
+
appName: string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Explicit user-provided group from `@On({ group: '...' })`. When set,
|
|
8
|
+
* returned as-is - explicit groups are full-qualified by the user and do
|
|
9
|
+
* NOT receive `appName` prefix.
|
|
10
|
+
*/
|
|
11
|
+
explicitGroup?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Cluster-wide fan-out opt-in. When `true`, the group is suffixed with
|
|
14
|
+
* `__<hostname>_<pid>` so every replica gets a unique broker group and
|
|
15
|
+
* the broker delivers a copy of each message to every replica. Mutually
|
|
16
|
+
* exclusive with `explicitGroup`.
|
|
17
|
+
*/
|
|
18
|
+
broadcast?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the broker consumer group name for an `@On` handler.
|
|
22
|
+
*
|
|
23
|
+
* Resolution order:
|
|
24
|
+
* - `explicitGroup` provided → returned as-is (no `appName` prefix, no broadcast suffix)
|
|
25
|
+
* - `broadcast: true` → `${appName ? appName + ':' : ''}${topic}__${ctor}_${method}__${hostname}_${pid}`
|
|
26
|
+
* - default → `${appName ? appName + ':' : ''}${topic}__${ctor}_${method}`
|
|
27
|
+
*
|
|
28
|
+
* The broadcast suffix uses only `hostname` + `pid` (no random component) so that
|
|
29
|
+
* orphan-cleanup logic in transports can reliably match previous-incarnation
|
|
30
|
+
* groups by stable host pattern when the process restarts.
|
|
31
|
+
*/
|
|
32
|
+
export declare function deriveGroupName(input: DeriveGroupNameInput): string;
|
|
33
|
+
//# sourceMappingURL=group-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-name.d.ts","sourceRoot":"","sources":["../src/group-name.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAWnE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
/**
|
|
3
|
+
* Build the broker consumer group name for an `@On` handler.
|
|
4
|
+
*
|
|
5
|
+
* Resolution order:
|
|
6
|
+
* - `explicitGroup` provided → returned as-is (no `appName` prefix, no broadcast suffix)
|
|
7
|
+
* - `broadcast: true` → `${appName ? appName + ':' : ''}${topic}__${ctor}_${method}__${hostname}_${pid}`
|
|
8
|
+
* - default → `${appName ? appName + ':' : ''}${topic}__${ctor}_${method}`
|
|
9
|
+
*
|
|
10
|
+
* The broadcast suffix uses only `hostname` + `pid` (no random component) so that
|
|
11
|
+
* orphan-cleanup logic in transports can reliably match previous-incarnation
|
|
12
|
+
* groups by stable host pattern when the process restarts.
|
|
13
|
+
*/
|
|
14
|
+
export function deriveGroupName(input) {
|
|
15
|
+
if (input.explicitGroup)
|
|
16
|
+
return input.explicitGroup;
|
|
17
|
+
const base = input.appName
|
|
18
|
+
? `${input.appName}:${input.topic}__${input.ctorName}_${input.methodName}`
|
|
19
|
+
: `${input.topic}__${input.ctorName}_${input.methodName}`;
|
|
20
|
+
if (input.broadcast) {
|
|
21
|
+
return `${base}__${hostname()}_${process.pid}`;
|
|
22
|
+
}
|
|
23
|
+
return base;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=group-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-name.js","sourceRoot":"","sources":["../src/group-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAsBlC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC,aAAa,CAAA;IAEnD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO;QACxB,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU,EAAE;QAC1E,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU,EAAE,CAAA;IAE3D,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,IAAI,KAAK,QAAQ,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IAChD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable idempotency primitive used by `@Idempotent` handlers in MessageBus.
|
|
3
|
+
*
|
|
4
|
+
* `claim(id, ttl)` is the atomic "first time?" check; `release(id)` lets the
|
|
5
|
+
* next retry attempt re-claim after a handler error. The store is transport-
|
|
6
|
+
* agnostic - a Redis Streams transport may pair with a Postgres-backed store,
|
|
7
|
+
* a NATS transport may use the broker's native dedup window plus a memory
|
|
8
|
+
* store for cross-restart safety.
|
|
9
|
+
*
|
|
10
|
+
* Implementations shipped:
|
|
11
|
+
* - `memoryIdempotencyStore()` - in-process Map with LRU eviction; default
|
|
12
|
+
* for dev/tests; not safe across processes.
|
|
13
|
+
* - `redisIdempotencyStore()` from `@miiajs/messaging-redis` - production-ready,
|
|
14
|
+
* atomic via SET NX EX.
|
|
15
|
+
*
|
|
16
|
+
* Custom backends (Postgres `INSERT ON CONFLICT`, DynamoDB conditional put)
|
|
17
|
+
* implement this interface directly.
|
|
18
|
+
*/
|
|
19
|
+
export interface IdempotencyStore {
|
|
20
|
+
/**
|
|
21
|
+
* Try to claim an ID for the given TTL (in milliseconds).
|
|
22
|
+
*
|
|
23
|
+
* - returns `true` → first claim, the framework proceeds with the handler
|
|
24
|
+
* - returns `false` → already claimed, the handler is silently skipped
|
|
25
|
+
* (treated as "already processed")
|
|
26
|
+
*
|
|
27
|
+
* Must be atomic: concurrent calls with the same id must produce exactly
|
|
28
|
+
* one `true` and the rest `false`.
|
|
29
|
+
*/
|
|
30
|
+
claim(id: string, ttlMs: number): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Release a previously-claimed id so a future delivery can re-claim.
|
|
33
|
+
* Called by MessageBus when the handler errors. Idempotent (no-op if id
|
|
34
|
+
* is not present).
|
|
35
|
+
*/
|
|
36
|
+
release(id: string): Promise<void>;
|
|
37
|
+
onDestroy?(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* DI token for the optional idempotency store.
|
|
41
|
+
*
|
|
42
|
+
* Always registered by `MessagingModule.configure()` (value is `null` when
|
|
43
|
+
* the user did not configure one). MessageBus reads it via `injectOptional`
|
|
44
|
+
* and only uses the store when a handler has `@Idempotent`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const IDEMPOTENCY_STORE = "miia:messaging:idempotency-store";
|
|
47
|
+
export interface MemoryIdempotencyStoreOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Max entries kept before LRU eviction. Default 10000.
|
|
50
|
+
*
|
|
51
|
+
* If an entry is evicted before its TTL, a duplicate may be re-processed.
|
|
52
|
+
* Acceptable trade-off for in-process stores; production deployments
|
|
53
|
+
* should use a Redis-backed store with bounded memory pressure.
|
|
54
|
+
*/
|
|
55
|
+
maxSize?: number;
|
|
56
|
+
}
|
|
57
|
+
export declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
58
|
+
private entries;
|
|
59
|
+
private maxSize;
|
|
60
|
+
constructor(options?: MemoryIdempotencyStoreOptions);
|
|
61
|
+
claim(id: string, ttlMs: number): Promise<boolean>;
|
|
62
|
+
release(id: string): Promise<void>;
|
|
63
|
+
onDestroy(): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
export declare function memoryIdempotencyStore(options?: MemoryIdempotencyStoreOptions): IdempotencyStore;
|
|
66
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAElD;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElC,SAAS,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,qCAAqC,CAAA;AAInE,MAAM,WAAW,6BAA6B;IAC5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAID,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAQ;gBAEX,OAAO,GAAE,6BAAkC;IAIjD,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBlD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAGjC;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,6BAA6B,GAAG,gBAAgB,CAEhG"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI token for the optional idempotency store.
|
|
3
|
+
*
|
|
4
|
+
* Always registered by `MessagingModule.configure()` (value is `null` when
|
|
5
|
+
* the user did not configure one). MessageBus reads it via `injectOptional`
|
|
6
|
+
* and only uses the store when a handler has `@Idempotent`.
|
|
7
|
+
*/
|
|
8
|
+
export const IDEMPOTENCY_STORE = 'miia:messaging:idempotency-store';
|
|
9
|
+
const DEFAULT_MAX_SIZE = 10_000;
|
|
10
|
+
export class MemoryIdempotencyStore {
|
|
11
|
+
entries = new Map(); // id → expiresAtMs
|
|
12
|
+
maxSize;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
15
|
+
}
|
|
16
|
+
async claim(id, ttlMs) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const existing = this.entries.get(id);
|
|
19
|
+
if (existing !== undefined && existing > now)
|
|
20
|
+
return false;
|
|
21
|
+
// Stale entry treated as absent. Delete-then-set keeps Map insertion
|
|
22
|
+
// order accurate for LRU eviction below.
|
|
23
|
+
this.entries.delete(id);
|
|
24
|
+
this.entries.set(id, now + ttlMs);
|
|
25
|
+
if (this.entries.size > this.maxSize) {
|
|
26
|
+
const oldest = this.entries.keys().next().value;
|
|
27
|
+
if (oldest !== undefined)
|
|
28
|
+
this.entries.delete(oldest);
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
async release(id) {
|
|
33
|
+
this.entries.delete(id);
|
|
34
|
+
}
|
|
35
|
+
async onDestroy() {
|
|
36
|
+
this.entries.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function memoryIdempotencyStore(options) {
|
|
40
|
+
return new MemoryIdempotencyStore(options);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAyCA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,kCAAkC,CAAA;AAenE,MAAM,gBAAgB,GAAG,MAAM,CAAA;AAE/B,MAAM,OAAO,sBAAsB;IACzB,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA,CAAC,mBAAmB;IACvD,OAAO,CAAQ;IAEvB,YAAY,UAAyC,EAAE;QACrD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAA;IACpD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAU,EAAE,KAAa;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACrC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,GAAG;YAAE,OAAO,KAAK,CAAA;QAE1D,qEAAqE;QACrE,yCAAyC;QACzC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,KAAK,CAAC,CAAA;QAEjC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC/C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvD,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAuC;IAC5E,OAAO,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC5C,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type DispatchMode, type MessageEnvelope, type MessageTransport, type HandlerResult, type RetryConfig, type SubscribeOptions, type Subscription } from './types.js';
|
|
2
|
+
export interface InMemoryTransportOptions {
|
|
3
|
+
retry?: Partial<RetryConfig>;
|
|
4
|
+
/**
|
|
5
|
+
* When true, payload is `structuredClone`d before each handler sees it.
|
|
6
|
+
* Prevents cross-handler mutation at the cost of one clone per delivery.
|
|
7
|
+
* Default false (same object identity across handlers - consistent with
|
|
8
|
+
* Node EventEmitter semantics).
|
|
9
|
+
*/
|
|
10
|
+
cloneOnPublish?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Max time `onDestroy()` waits for in-flight handlers to settle before
|
|
13
|
+
* forcing cleanup. Default 5000ms. Set 0 to skip drain (immediate cleanup).
|
|
14
|
+
*
|
|
15
|
+
* The drain phase blocks new deliveries (including scheduled retries) and
|
|
16
|
+
* awaits all currently-running handlers. Reaching the timeout logs a warn
|
|
17
|
+
* and continues with cleanup; pending handlers will keep running but their
|
|
18
|
+
* results are discarded.
|
|
19
|
+
*/
|
|
20
|
+
drainTimeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* In-process message transport. Fire-and-forget delivery via `queueMicrotask`,
|
|
24
|
+
* exponential backoff retry via `setTimeout`, auto-DLQ via re-publishing to
|
|
25
|
+
* `<topic>.dlq`.
|
|
26
|
+
*
|
|
27
|
+
* NOT persistent. Process crash mid-retry loses pending messages - do not use
|
|
28
|
+
* for durability-sensitive workloads. Use `@miiajs/messaging-redis` or another
|
|
29
|
+
* broker-backed transport in production.
|
|
30
|
+
*
|
|
31
|
+
* **Dispatch capability:** sliding-only, no competing consumers. The in-memory
|
|
32
|
+
* transport is single-process: there is no broker to round-robin messages
|
|
33
|
+
* between multiple handlers in a shared `group`. Auto-derived per-handler
|
|
34
|
+
* groups work normally (each handler is its own subscription, fan-out is
|
|
35
|
+
* automatic). Handlers requesting `mode: 'batch'` or sharing an explicit
|
|
36
|
+
* `group` are rejected at `MessageBus.onReady()`.
|
|
37
|
+
*/
|
|
38
|
+
export declare class InMemoryTransport implements MessageTransport {
|
|
39
|
+
readonly supportedModes: readonly ["sliding"];
|
|
40
|
+
readonly defaultMode: DispatchMode;
|
|
41
|
+
readonly supportsCompetingConsumers = false;
|
|
42
|
+
private subs;
|
|
43
|
+
private retry;
|
|
44
|
+
private cloneOnPublish;
|
|
45
|
+
private drainTimeoutMs;
|
|
46
|
+
private logger;
|
|
47
|
+
private pendingTimers;
|
|
48
|
+
private pendingDeliveries;
|
|
49
|
+
private destroying;
|
|
50
|
+
constructor(options?: InMemoryTransportOptions);
|
|
51
|
+
publish(envelope: MessageEnvelope): Promise<void>;
|
|
52
|
+
subscribe(topic: string, handler: (envelope: MessageEnvelope) => Promise<HandlerResult>, _options: SubscribeOptions): Promise<Subscription>;
|
|
53
|
+
onDestroy(): Promise<void>;
|
|
54
|
+
/** Tracking wrapper - every (initial and retry) delivery flows through here. */
|
|
55
|
+
private deliver;
|
|
56
|
+
private runDelivery;
|
|
57
|
+
private waitForDrain;
|
|
58
|
+
}
|
|
59
|
+
export declare function inMemoryTransport(options?: InMemoryTransportOptions): MessageTransport;
|
|
60
|
+
//# sourceMappingURL=in-memory-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-transport.d.ts","sourceRoot":"","sources":["../src/in-memory-transport.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AASD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAkB,YAAW,gBAAgB;IACxD,QAAQ,CAAC,cAAc,uBAAyD;IAChF,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAY;IAC9C,QAAQ,CAAC,0BAA0B,SAAQ;IAE3C,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,aAAa,CAA2C;IAChE,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,UAAU,CAAQ;gBAEd,OAAO,GAAE,wBAA6B;IAM5C,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjD,SAAS,CACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,CAAC,EAC9D,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC;IAmBlB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAQhC,gFAAgF;IAChF,OAAO,CAAC,OAAO;YAQD,WAAW;YAyCX,YAAY;CAkB3B;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,gBAAgB,CAE1F"}
|