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.
Files changed (33) hide show
  1. package/dist/index.d.ts +6 -6
  2. package/dist/runtime/application/api/logging.d.ts +1 -0
  3. package/dist/runtime/application/api/logging.js +28 -11
  4. package/dist/runtime/application/api/logging.js.map +1 -1
  5. package/dist/runtime/index.d.ts +2 -2
  6. package/dist/runtime/index.js +2 -2
  7. package/dist/runtime/index.js.map +1 -1
  8. package/dist/runtime/injectables.d.ts +7 -7
  9. package/dist/runtime/injectables.js +6 -6
  10. package/dist/runtime/injectables.js.map +1 -1
  11. package/dist/runtime/server/config.d.ts +4 -4
  12. package/dist/runtime/server/config.js +2 -2
  13. package/dist/runtime/server/config.js.map +1 -1
  14. package/dist/runtime/{pubsub → subscription}/manager.d.ts +15 -14
  15. package/dist/runtime/{pubsub → subscription}/manager.js +59 -8
  16. package/dist/runtime/subscription/manager.js.map +1 -0
  17. package/dist/runtime/{pubsub → subscription}/redis.d.ts +7 -5
  18. package/dist/runtime/{pubsub → subscription}/redis.js +37 -14
  19. package/dist/runtime/subscription/redis.js.map +1 -0
  20. package/dist/runtime/workers/base.d.ts +2 -2
  21. package/dist/runtime/workers/base.js +5 -5
  22. package/dist/runtime/workers/base.js.map +1 -1
  23. package/package.json +11 -11
  24. package/src/runtime/application/api/logging.ts +36 -10
  25. package/src/runtime/index.ts +2 -2
  26. package/src/runtime/injectables.ts +16 -18
  27. package/src/runtime/server/config.ts +5 -5
  28. package/src/runtime/{pubsub → subscription}/manager.ts +118 -26
  29. package/src/runtime/subscription/redis.ts +157 -0
  30. package/src/runtime/workers/base.ts +7 -7
  31. package/dist/runtime/pubsub/manager.js.map +0 -1
  32. package/dist/runtime/pubsub/redis.js.map +0 -1
  33. 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 { pubSubAdapter } from '../injectables.ts'
14
+ import { subscriptionAdapter } from '../injectables.ts'
15
15
 
16
- export type PubSubAdapterEvent = { channel: string; payload: any }
16
+ export type SubscriptionAdapterEvent = { channel: string; payload: any }
17
17
 
18
- export interface PubSubAdapterType {
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<PubSubAdapterEvent>
23
+ ): AsyncGenerator<SubscriptionAdapterEvent>
24
24
  initialize(): Promise<void>
25
25
  dispose(): Promise<void>
26
26
  }
27
27
 
28
- export type PubSubChannel = {
28
+ export type SubscriptionChannel = {
29
29
  stream: Readable
30
30
  subscription: TAnySubscriptionContract
31
31
  event: TAnyEventContract
32
32
  }
33
33
 
34
- export type PubSubSubscribe = <
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 PubSubPublish = <
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 PubSubManagerOptions = { logger: Logger; container: Container }
75
+ export type SubscriptionManagerOptions = {
76
+ logger: Logger
77
+ container: Container
78
+ }
76
79
 
77
- export class PubSubManager {
78
- readonly subscriptions = new Map<string, PubSubChannel>()
80
+ export class SubscriptionManager {
81
+ readonly subscriptions = new Map<string, SubscriptionChannel>()
82
+ protected readonly logger: Logger
79
83
 
80
- constructor(protected readonly options: PubSubManagerOptions) {}
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(pubSubAdapter)
89
+ return this.options.container.resolve(subscriptionAdapter)
84
90
  }
85
91
 
86
- subscribe: PubSubSubscribe = async (
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', () => this.subscriptions.delete(channel))
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
- return mergeEventStreams(streams, signal)
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: PubSubPublish = async (event, options, data) => {
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
- return await adapter.publish(channel, payload)
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.options.logger.error(
128
- `Failed to publish event "${event.name}" on channel "${channel}": ${error.message}`,
200
+ this.logger.error(
201
+ { channel, event: event.name, error },
202
+ 'Failed to publish pubsub event',
129
203
  )
130
- return Promise.reject(error)
204
+ throw error
131
205
  }
132
206
  }
133
207
 
134
208
  private createEventStream(
135
- iterable: AsyncGenerator<PubSubAdapterEvent>,
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
- pubsub: PubSubManager
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.pubsub = new PubSubManager({
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.pubSubPublish,
42
- this.pubsub.publish.bind(this.pubsub),
41
+ injectables.publish,
42
+ this.subscriptionManager.publish.bind(this.subscriptionManager),
43
43
  ),
44
44
  provision(
45
- injectables.pubSubSubscribe,
46
- this.pubsub.subscribe.bind(this.pubsub),
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
- }