nmtjs 0.15.2 → 0.16.0-beta.1
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/index.d.ts +6 -6
- package/dist/runtime/application/api/logging.d.ts +1 -0
- package/dist/runtime/application/api/logging.js +28 -11
- package/dist/runtime/application/api/logging.js.map +1 -1
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/injectables.d.ts +7 -7
- package/dist/runtime/injectables.js +6 -6
- package/dist/runtime/injectables.js.map +1 -1
- package/dist/runtime/server/config.d.ts +4 -4
- package/dist/runtime/server/config.js +2 -2
- package/dist/runtime/server/config.js.map +1 -1
- package/dist/runtime/{pubsub → subscription}/manager.d.ts +15 -14
- package/dist/runtime/{pubsub → subscription}/manager.js +59 -8
- package/dist/runtime/subscription/manager.js.map +1 -0
- package/dist/runtime/{pubsub → subscription}/redis.d.ts +7 -5
- package/dist/runtime/{pubsub → subscription}/redis.js +37 -14
- package/dist/runtime/subscription/redis.js.map +1 -0
- package/dist/runtime/workers/base.d.ts +2 -2
- package/dist/runtime/workers/base.js +5 -5
- package/dist/runtime/workers/base.js.map +1 -1
- package/package.json +11 -11
- package/src/runtime/application/api/logging.ts +36 -10
- package/src/runtime/index.ts +2 -2
- package/src/runtime/injectables.ts +16 -18
- package/src/runtime/server/config.ts +5 -5
- package/src/runtime/{pubsub → subscription}/manager.ts +118 -26
- package/src/runtime/subscription/redis.ts +157 -0
- package/src/runtime/workers/base.ts +7 -7
- package/dist/runtime/pubsub/manager.js.map +0 -1
- package/dist/runtime/pubsub/redis.js.map +0 -1
- package/src/runtime/pubsub/redis.ts +0 -106
|
@@ -11,27 +11,27 @@ import type { Container, Logger } from '@nmtjs/core'
|
|
|
11
11
|
import type { t } from '@nmtjs/type'
|
|
12
12
|
import { isAbortError } from '@nmtjs/common'
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { subscriptionAdapter } from '../injectables.ts'
|
|
15
15
|
|
|
16
|
-
export type
|
|
16
|
+
export type SubscriptionAdapterEvent = { channel: string; payload: any }
|
|
17
17
|
|
|
18
|
-
export interface
|
|
18
|
+
export interface SubscriptionAdapterType {
|
|
19
19
|
publish(channel: string, payload: any): Promise<boolean>
|
|
20
20
|
subscribe(
|
|
21
21
|
channel: string,
|
|
22
22
|
signal?: AbortSignal,
|
|
23
|
-
): AsyncGenerator<
|
|
23
|
+
): AsyncGenerator<SubscriptionAdapterEvent>
|
|
24
24
|
initialize(): Promise<void>
|
|
25
25
|
dispose(): Promise<void>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export type
|
|
28
|
+
export type SubscriptionChannel = {
|
|
29
29
|
stream: Readable
|
|
30
30
|
subscription: TAnySubscriptionContract
|
|
31
31
|
event: TAnyEventContract
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export type
|
|
34
|
+
export type SubscribeFn = <
|
|
35
35
|
Contract extends TAnySubscriptionContract,
|
|
36
36
|
Events extends {
|
|
37
37
|
[K in keyof Contract['events']]?: true
|
|
@@ -63,7 +63,7 @@ export type PubSubSubscribe = <
|
|
|
63
63
|
}
|
|
64
64
|
>
|
|
65
65
|
|
|
66
|
-
export type
|
|
66
|
+
export type PublishFn = <
|
|
67
67
|
S extends TAnySubscriptionContract,
|
|
68
68
|
E extends S['events'][keyof S['events']],
|
|
69
69
|
>(
|
|
@@ -72,23 +72,24 @@ export type PubSubPublish = <
|
|
|
72
72
|
data: t.infer.decode.input<E['payload']>,
|
|
73
73
|
) => Promise<boolean>
|
|
74
74
|
|
|
75
|
-
export type
|
|
75
|
+
export type SubscriptionManagerOptions = {
|
|
76
|
+
logger: Logger
|
|
77
|
+
container: Container
|
|
78
|
+
}
|
|
76
79
|
|
|
77
|
-
export class
|
|
78
|
-
readonly subscriptions = new Map<string,
|
|
80
|
+
export class SubscriptionManager {
|
|
81
|
+
readonly subscriptions = new Map<string, SubscriptionChannel>()
|
|
82
|
+
protected readonly logger: Logger
|
|
79
83
|
|
|
80
|
-
constructor(protected readonly options:
|
|
84
|
+
constructor(protected readonly options: SubscriptionManagerOptions) {
|
|
85
|
+
this.logger = options.logger.child({ component: SubscriptionManager.name })
|
|
86
|
+
}
|
|
81
87
|
|
|
82
88
|
protected get adapter() {
|
|
83
|
-
return this.options.container.resolve(
|
|
89
|
+
return this.options.container.resolve(subscriptionAdapter)
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
subscribe:
|
|
87
|
-
subscription,
|
|
88
|
-
events,
|
|
89
|
-
options,
|
|
90
|
-
signal,
|
|
91
|
-
) => {
|
|
92
|
+
subscribe: SubscribeFn = async (subscription, events, options, signal) => {
|
|
92
93
|
const adapter = await this.adapter
|
|
93
94
|
|
|
94
95
|
const eventKeys =
|
|
@@ -96,6 +97,11 @@ export class PubSubManager {
|
|
|
96
97
|
? Object.keys(subscription.events)
|
|
97
98
|
: Object.keys(events)
|
|
98
99
|
|
|
100
|
+
this.logger.debug(
|
|
101
|
+
{ subscription: subscription.name, eventCount: eventKeys.length },
|
|
102
|
+
'Opening subscription',
|
|
103
|
+
)
|
|
104
|
+
|
|
99
105
|
const streams = Array(eventKeys.length)
|
|
100
106
|
|
|
101
107
|
for (const index in eventKeys) {
|
|
@@ -103,44 +109,115 @@ export class PubSubManager {
|
|
|
103
109
|
const channel = getChannelName(event, options)
|
|
104
110
|
if (this.subscriptions.has(channel)) {
|
|
105
111
|
streams[index] = this.subscriptions.get(channel)!.stream
|
|
112
|
+
this.logger.trace(
|
|
113
|
+
{
|
|
114
|
+
channel,
|
|
115
|
+
event: event.name,
|
|
116
|
+
activeChannels: this.subscriptions.size,
|
|
117
|
+
},
|
|
118
|
+
'Reusing pubsub channel stream',
|
|
119
|
+
)
|
|
106
120
|
} else {
|
|
107
121
|
const iterable = adapter.subscribe(channel, signal)
|
|
108
122
|
const stream = this.createEventStream(iterable)
|
|
109
|
-
stream.on('close', () =>
|
|
123
|
+
stream.on('close', () => {
|
|
124
|
+
this.subscriptions.delete(channel)
|
|
125
|
+
this.logger.debug(
|
|
126
|
+
{
|
|
127
|
+
channel,
|
|
128
|
+
event: event.name,
|
|
129
|
+
activeChannels: this.subscriptions.size,
|
|
130
|
+
},
|
|
131
|
+
'Pubsub channel stream closed',
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
stream.on('error', (error) => {
|
|
135
|
+
this.logger.warn(
|
|
136
|
+
{ channel, event: event.name, error },
|
|
137
|
+
'Pubsub channel stream failed',
|
|
138
|
+
)
|
|
139
|
+
})
|
|
110
140
|
streams[index] = stream
|
|
111
141
|
this.subscriptions.set(channel, { subscription, event, stream })
|
|
142
|
+
this.logger.debug(
|
|
143
|
+
{
|
|
144
|
+
channel,
|
|
145
|
+
event: event.name,
|
|
146
|
+
activeChannels: this.subscriptions.size,
|
|
147
|
+
},
|
|
148
|
+
'Creating channel stream',
|
|
149
|
+
)
|
|
112
150
|
}
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
|
|
153
|
+
const mergedStream = mergeEventStreams(streams, signal)
|
|
154
|
+
|
|
155
|
+
mergedStream.once('close', () => {
|
|
156
|
+
this.logger.debug(
|
|
157
|
+
{ subscription: subscription.name, eventCount: eventKeys.length },
|
|
158
|
+
'Pubsub subscription stream closed',
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
mergedStream.once('error', (error) => {
|
|
162
|
+
this.logger.warn(
|
|
163
|
+
{
|
|
164
|
+
subscription: subscription.name,
|
|
165
|
+
eventCount: eventKeys.length,
|
|
166
|
+
error,
|
|
167
|
+
},
|
|
168
|
+
'Pubsub subscription stream failed',
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return mergedStream
|
|
116
173
|
}
|
|
117
174
|
|
|
118
|
-
publish:
|
|
175
|
+
publish: PublishFn = async (event, options, data) => {
|
|
119
176
|
const adapter = await this.adapter
|
|
120
177
|
|
|
121
178
|
const channel = getChannelName(event, options)
|
|
122
179
|
|
|
180
|
+
this.logger.trace({ channel, event: event.name }, 'Publishing pubsub event')
|
|
181
|
+
|
|
123
182
|
try {
|
|
124
183
|
const payload = event.payload.encode(data)
|
|
125
|
-
|
|
184
|
+
const published = await adapter.publish(channel, payload)
|
|
185
|
+
|
|
186
|
+
if (published) {
|
|
187
|
+
this.logger.trace(
|
|
188
|
+
{ channel, event: event.name },
|
|
189
|
+
'Published pubsub event',
|
|
190
|
+
)
|
|
191
|
+
} else {
|
|
192
|
+
this.logger.warn(
|
|
193
|
+
{ channel, event: event.name },
|
|
194
|
+
'Pubsub adapter reported publish failure',
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return published
|
|
126
199
|
} catch (error: any) {
|
|
127
|
-
this.
|
|
128
|
-
|
|
200
|
+
this.logger.error(
|
|
201
|
+
{ channel, event: event.name, error },
|
|
202
|
+
'Failed to publish pubsub event',
|
|
129
203
|
)
|
|
130
|
-
|
|
204
|
+
throw error
|
|
131
205
|
}
|
|
132
206
|
}
|
|
133
207
|
|
|
134
208
|
private createEventStream(
|
|
135
|
-
iterable: AsyncGenerator<
|
|
209
|
+
iterable: AsyncGenerator<SubscriptionAdapterEvent>,
|
|
136
210
|
): Readable {
|
|
137
211
|
const { subscriptions } = this
|
|
212
|
+
const logger = this.logger
|
|
213
|
+
|
|
138
214
|
return new Readable({
|
|
139
215
|
objectMode: true,
|
|
140
216
|
read() {
|
|
141
217
|
iterable.next().then(
|
|
142
218
|
({ value, done }) => {
|
|
143
219
|
if (done) {
|
|
220
|
+
logger.trace('Pubsub adapter stream ended')
|
|
144
221
|
this.push(null)
|
|
145
222
|
} else {
|
|
146
223
|
const subscription = subscriptions.get(value.channel)
|
|
@@ -148,17 +225,32 @@ export class PubSubManager {
|
|
|
148
225
|
const { event } = subscription
|
|
149
226
|
try {
|
|
150
227
|
const data = event.payload.decode(value.payload)
|
|
228
|
+
logger.trace(
|
|
229
|
+
{ channel: value.channel, event: event.name },
|
|
230
|
+
'Received event',
|
|
231
|
+
)
|
|
151
232
|
this.push({ event: event.name, data })
|
|
152
233
|
} catch (error: any) {
|
|
234
|
+
logger.warn(
|
|
235
|
+
{ channel: value.channel, event: event.name, error },
|
|
236
|
+
'Failed to decode pubsub event payload',
|
|
237
|
+
)
|
|
153
238
|
this.destroy(error)
|
|
154
239
|
}
|
|
240
|
+
} else {
|
|
241
|
+
logger.trace(
|
|
242
|
+
{ channel: value.channel },
|
|
243
|
+
'Dropped pubsub event for inactive channel',
|
|
244
|
+
)
|
|
155
245
|
}
|
|
156
246
|
}
|
|
157
247
|
},
|
|
158
248
|
(error) => {
|
|
159
249
|
if (isAbortError(error)) {
|
|
250
|
+
logger.trace('Pubsub adapter stream aborted')
|
|
160
251
|
this.push(null)
|
|
161
252
|
} else {
|
|
253
|
+
logger.warn({ error }, 'Pubsub adapter stream failed')
|
|
162
254
|
this.destroy(error)
|
|
163
255
|
}
|
|
164
256
|
},
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import EventEmitter, { on } from 'node:events'
|
|
2
|
+
|
|
3
|
+
import type { Logger } from '@nmtjs/core'
|
|
4
|
+
import { isAbortError } from '@nmtjs/common'
|
|
5
|
+
import { createFactoryInjectable } from '@nmtjs/core'
|
|
6
|
+
|
|
7
|
+
import type { RuntimePlugin } from '../plugin.ts'
|
|
8
|
+
import type { Store } from '../types.ts'
|
|
9
|
+
import type {
|
|
10
|
+
SubscriptionAdapterEvent,
|
|
11
|
+
SubscriptionAdapterType,
|
|
12
|
+
} from './manager.ts'
|
|
13
|
+
import { storeConfig, subscriptionAdapter } from '../injectables.ts'
|
|
14
|
+
import { createStoreClient } from '../store/index.ts'
|
|
15
|
+
|
|
16
|
+
export class RedisSubscriptionAdapter implements SubscriptionAdapterType {
|
|
17
|
+
protected readonly events = new EventEmitter()
|
|
18
|
+
protected readonly listeners = new Map<string, number>()
|
|
19
|
+
protected readonly logger?: Logger
|
|
20
|
+
protected subscriberClient?: Store
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
protected readonly client: Store,
|
|
24
|
+
logger?: Logger,
|
|
25
|
+
) {
|
|
26
|
+
this.logger = logger?.child({ component: RedisSubscriptionAdapter.name })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize() {
|
|
30
|
+
this.logger?.debug('Initializing Redis adapter')
|
|
31
|
+
|
|
32
|
+
// Create a dedicated subscriber client (Redis requires separate clients for pub/sub)
|
|
33
|
+
this.subscriberClient = this.client.duplicate()
|
|
34
|
+
|
|
35
|
+
// Set up message handler
|
|
36
|
+
this.subscriberClient.on('message', (channel: string, message: string) => {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(message)
|
|
39
|
+
this.logger?.trace({ channel }, 'Received Redis message')
|
|
40
|
+
this.events.emit(channel, parsed)
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.logger?.warn({ channel, error }, 'Failed to parse Redis message')
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
this.logger?.debug('Redis adapter initialized')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async dispose() {
|
|
50
|
+
this.logger?.debug('Disposing Redis adapter')
|
|
51
|
+
|
|
52
|
+
if (this.subscriberClient) {
|
|
53
|
+
await this.subscriberClient.quit()
|
|
54
|
+
this.subscriberClient = undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.logger?.debug('Redis adapter disposed')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async publish(channel: string, payload: any): Promise<boolean> {
|
|
61
|
+
this.logger?.trace({ channel }, 'Publishing Redis message')
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await this.client.publish(channel, JSON.stringify(payload))
|
|
65
|
+
this.logger?.trace({ channel }, 'Published Redis message')
|
|
66
|
+
return true
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.logger?.warn({ channel, error }, 'Failed to publish Redis message')
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async *subscribe(
|
|
74
|
+
channel: string,
|
|
75
|
+
signal?: AbortSignal,
|
|
76
|
+
): AsyncGenerator<SubscriptionAdapterEvent> {
|
|
77
|
+
if (!this.subscriberClient) {
|
|
78
|
+
throw new Error('RedisSubscriptionAdapter not initialized')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.logger?.debug({ channel }, 'Opening Redis channel listener')
|
|
82
|
+
|
|
83
|
+
if (!this.listeners.has(channel)) {
|
|
84
|
+
await this.subscriberClient.subscribe(channel)
|
|
85
|
+
this.listeners.set(channel, 1)
|
|
86
|
+
this.logger?.debug({ channel, listeners: 1 }, 'Subscribed Redis channel')
|
|
87
|
+
} else {
|
|
88
|
+
const listeners = this.listeners.get(channel)! + 1
|
|
89
|
+
this.listeners.set(channel, listeners)
|
|
90
|
+
this.logger?.trace(
|
|
91
|
+
{ channel, listeners },
|
|
92
|
+
'Reusing Redis channel listener',
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
signal?.throwIfAborted()
|
|
98
|
+
for await (const [payload] of on(this.events, channel, { signal })) {
|
|
99
|
+
this.logger?.trace({ channel }, 'Delivering Redis message')
|
|
100
|
+
yield { channel, payload }
|
|
101
|
+
}
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
if (isAbortError(error)) {
|
|
104
|
+
this.logger?.trace({ channel }, 'Redis channel listener aborted')
|
|
105
|
+
throw error
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.logger?.warn({ channel, error }, 'Redis channel listener failed')
|
|
109
|
+
} finally {
|
|
110
|
+
const count = this.listeners.get(channel)
|
|
111
|
+
if (count !== undefined) {
|
|
112
|
+
if (count > 1) {
|
|
113
|
+
const listeners = count - 1
|
|
114
|
+
this.listeners.set(channel, listeners)
|
|
115
|
+
this.logger?.trace(
|
|
116
|
+
{ channel, listeners },
|
|
117
|
+
'Detached Redis channel listener',
|
|
118
|
+
)
|
|
119
|
+
} else {
|
|
120
|
+
await this.subscriberClient?.unsubscribe(channel)
|
|
121
|
+
this.listeners.delete(channel)
|
|
122
|
+
this.logger?.debug(
|
|
123
|
+
{ channel, listeners: 0 },
|
|
124
|
+
'Unsubscribed Redis channel',
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const RedisSubscriptionAdapterPlugin = (): RuntimePlugin => {
|
|
133
|
+
return {
|
|
134
|
+
name: 'redis-subscription-adapter',
|
|
135
|
+
hooks: {
|
|
136
|
+
'lifecycle:beforeInitialize': async (ctx) => {
|
|
137
|
+
const adapter = await ctx.container.resolve(
|
|
138
|
+
createFactoryInjectable({
|
|
139
|
+
dependencies: { config: storeConfig },
|
|
140
|
+
factory: async ({ config }) => {
|
|
141
|
+
const connection = await createStoreClient(config)
|
|
142
|
+
const adapter = new RedisSubscriptionAdapter(
|
|
143
|
+
connection,
|
|
144
|
+
ctx.logger,
|
|
145
|
+
)
|
|
146
|
+
await adapter.initialize()
|
|
147
|
+
return { adapter, connection }
|
|
148
|
+
},
|
|
149
|
+
pick: ({ adapter }) => adapter,
|
|
150
|
+
dispose: ({ connection }) => connection.quit(),
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
ctx.container.provide(subscriptionAdapter, adapter)
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -6,11 +6,11 @@ import type { BaseRuntimeOptions } from '../runtime.ts'
|
|
|
6
6
|
import type { ServerConfig } from '../server/config.ts'
|
|
7
7
|
import * as injectables from '../injectables.ts'
|
|
8
8
|
import { JobManager } from '../jobs/manager.ts'
|
|
9
|
-
import { PubSubManager } from '../pubsub/manager.ts'
|
|
10
9
|
import { BaseRuntime } from '../runtime.ts'
|
|
10
|
+
import { SubscriptionManager } from '../subscription/manager.ts'
|
|
11
11
|
|
|
12
12
|
export abstract class BaseWorkerRuntime extends BaseRuntime {
|
|
13
|
-
|
|
13
|
+
subscriptionManager: SubscriptionManager
|
|
14
14
|
jobManager?: JobManager
|
|
15
15
|
|
|
16
16
|
constructor(
|
|
@@ -20,7 +20,7 @@ export abstract class BaseWorkerRuntime extends BaseRuntime {
|
|
|
20
20
|
) {
|
|
21
21
|
super(options)
|
|
22
22
|
|
|
23
|
-
this.
|
|
23
|
+
this.subscriptionManager = new SubscriptionManager({
|
|
24
24
|
logger: this.logger,
|
|
25
25
|
container: this.container,
|
|
26
26
|
})
|
|
@@ -38,12 +38,12 @@ export abstract class BaseWorkerRuntime extends BaseRuntime {
|
|
|
38
38
|
provision(CoreInjectables.logger, this.logger),
|
|
39
39
|
provision(injectables.workerType, this.workerType),
|
|
40
40
|
provision(
|
|
41
|
-
injectables.
|
|
42
|
-
this.
|
|
41
|
+
injectables.publish,
|
|
42
|
+
this.subscriptionManager.publish.bind(this.subscriptionManager),
|
|
43
43
|
),
|
|
44
44
|
provision(
|
|
45
|
-
injectables.
|
|
46
|
-
this.
|
|
45
|
+
injectables.subscribe,
|
|
46
|
+
this.subscriptionManager.subscribe.bind(this.subscriptionManager),
|
|
47
47
|
),
|
|
48
48
|
]
|
|
49
49
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/runtime/pubsub/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../src/runtime/pubsub/redis.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import EventEmitter, { on } from 'node:events'
|
|
2
|
-
|
|
3
|
-
import { isAbortError } from '@nmtjs/common'
|
|
4
|
-
import { createFactoryInjectable, provision } from '@nmtjs/core'
|
|
5
|
-
|
|
6
|
-
import type { RuntimePlugin } from '../plugin.ts'
|
|
7
|
-
import type { Store } from '../types.ts'
|
|
8
|
-
import type { PubSubAdapterEvent, PubSubAdapterType } from './manager.ts'
|
|
9
|
-
import { pubSubAdapter, storeConfig } from '../injectables.ts'
|
|
10
|
-
import { createStoreClient } from '../store/index.ts'
|
|
11
|
-
|
|
12
|
-
export class RedisPubSubAdapter implements PubSubAdapterType {
|
|
13
|
-
protected readonly events = new EventEmitter()
|
|
14
|
-
protected readonly listeners = new Map<string, number>()
|
|
15
|
-
protected subscriberClient?: Store
|
|
16
|
-
|
|
17
|
-
constructor(protected readonly client: Store) {}
|
|
18
|
-
|
|
19
|
-
async initialize() {
|
|
20
|
-
// Create a dedicated subscriber client (Redis requires separate clients for pub/sub)
|
|
21
|
-
this.subscriberClient = this.client.duplicate()
|
|
22
|
-
|
|
23
|
-
// Set up message handler
|
|
24
|
-
this.subscriberClient.on('message', (channel: string, message: string) => {
|
|
25
|
-
try {
|
|
26
|
-
const parsed = JSON.parse(message)
|
|
27
|
-
this.events.emit(channel, parsed)
|
|
28
|
-
} catch {}
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async dispose() {
|
|
33
|
-
if (this.subscriberClient) {
|
|
34
|
-
await this.subscriberClient.quit()
|
|
35
|
-
this.subscriberClient = undefined
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async publish(channel: string, payload: any): Promise<boolean> {
|
|
40
|
-
try {
|
|
41
|
-
await this.client.publish(channel, JSON.stringify(payload))
|
|
42
|
-
return true
|
|
43
|
-
} catch {
|
|
44
|
-
return false
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async *subscribe(
|
|
49
|
-
channel: string,
|
|
50
|
-
signal?: AbortSignal,
|
|
51
|
-
): AsyncGenerator<PubSubAdapterEvent> {
|
|
52
|
-
if (!this.subscriberClient) {
|
|
53
|
-
throw new Error('RedisPubSubAdapter not initialized')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!this.listeners.has(channel)) {
|
|
57
|
-
await this.subscriberClient.subscribe(channel)
|
|
58
|
-
this.listeners.set(channel, 1)
|
|
59
|
-
} else {
|
|
60
|
-
this.listeners.set(channel, this.listeners.get(channel)! + 1)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
signal?.throwIfAborted()
|
|
65
|
-
for await (const [payload] of on(this.events, channel, { signal })) {
|
|
66
|
-
yield { channel, payload }
|
|
67
|
-
}
|
|
68
|
-
} catch (error: any) {
|
|
69
|
-
if (isAbortError(error)) throw error
|
|
70
|
-
} finally {
|
|
71
|
-
const count = this.listeners.get(channel)
|
|
72
|
-
if (count !== undefined) {
|
|
73
|
-
if (count > 1) {
|
|
74
|
-
this.listeners.set(channel, count - 1)
|
|
75
|
-
} else {
|
|
76
|
-
await this.subscriberClient?.unsubscribe(channel)
|
|
77
|
-
this.listeners.delete(channel)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export const RedisPubSubAdapterPlugin = (): RuntimePlugin => {
|
|
85
|
-
return {
|
|
86
|
-
name: 'pubsub-redis-adapter',
|
|
87
|
-
hooks: {
|
|
88
|
-
'lifecycle:beforeInitialize': async (ctx) => {
|
|
89
|
-
const adapter = await ctx.container.resolve(
|
|
90
|
-
createFactoryInjectable({
|
|
91
|
-
dependencies: { config: storeConfig },
|
|
92
|
-
factory: async ({ config }) => {
|
|
93
|
-
const connection = await createStoreClient(config)
|
|
94
|
-
const adapter = new RedisPubSubAdapter(connection)
|
|
95
|
-
await adapter.initialize()
|
|
96
|
-
return { adapter, connection }
|
|
97
|
-
},
|
|
98
|
-
pick: ({ adapter }) => adapter,
|
|
99
|
-
dispose: ({ connection }) => connection.quit(),
|
|
100
|
-
}),
|
|
101
|
-
)
|
|
102
|
-
ctx.container.provide(pubSubAdapter, adapter)
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
}
|