@nitra/nats 3.1.3 → 4.0.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/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @nitra/nats
2
2
 
3
3
  NATS JetStream helper для Node.js.
4
- Простий API для публікації, обробки та моніторингу повідомлень у черзі з автоматичним створенням consumer для кожного subject або кастомного durable consumer.
4
+ Простий API для публікації, обробки та моніторингу повідомлень у черзі з гнучким управлінням consumer-ами через конфігурацію та CLI інструменти.
5
5
 
6
6
  ---
7
7
 
8
8
  ## Встановлення
9
9
 
10
10
  ```sh
11
- yarn add @nitra/nats
11
+ bun add @nitra/nats
12
12
  # або
13
13
  npm install @nitra/nats
14
14
  ```
@@ -44,18 +44,72 @@ projectName:subjectName
44
44
  ```js
45
45
  import { publish } from '@nitra/nats'
46
46
 
47
- // Мінімальний варіант (durable_name = subject)
47
+ // Публікація повідомлення
48
48
  await publish('project:subject', { id: 1, foo: 'bar' })
49
+ await publish('service:notifications', { message: 'Hello!' })
50
+ ```
51
+
52
+ - Повідомлення публікується у subject `dev.project:subject` (або `${NATS_STREAM}.project:subject`)
53
+ - Consumer-и потрібно створювати окремо через `ensureConsumer` або CLI
54
+
55
+ ---
56
+
57
+ ## Управління Consumer-ами
58
+
59
+ ### Програмне створення consumer-а
60
+
61
+ ```js
62
+ import { ensureConsumer } from '@nitra/nats'
63
+
64
+ // Створення простого consumer-а
65
+ await ensureConsumer({
66
+ streamName: 'dev',
67
+ durableName: 'project:subject',
68
+ filterSubjects: ['project:subject'],
69
+ deliverPolicy: 'all',
70
+ ackPolicy: 'explicit'
71
+ })
72
+
73
+ // Створення групового consumer-а для кількох subject-ів
74
+ await ensureConsumer({
75
+ streamName: 'dev',
76
+ durableName: 'worker-group',
77
+ filterSubjects: ['project:orders', 'project:payments'],
78
+ deliverPolicy: 'all',
79
+ ackPolicy: 'explicit'
80
+ })
81
+ ```
49
82
 
50
- // Кастомний durable consumer (наприклад, для групової обробки)
51
- await publish('project:subject', { id: 2 }, [
52
- { durableName: 'worker-group', filterSubjects: ['project:subject', 'project:subject2'] },
53
- { durableName: 'analytics', filterSubjects: ['project:subject'] }
54
- ])
83
+ ### CLI для роботи з YAML конфігураціями
84
+
85
+ Створіть YAML файл з конфігурацією consumer-а:
86
+
87
+ ```yaml
88
+ # consumer.yaml
89
+ apiVersion: jetstream.nats.io/v1beta2
90
+ kind: Consumer
91
+ metadata:
92
+ name: 'nats:test'
93
+ namespace: dev
94
+ spec:
95
+ streamName: dev
96
+ durableName: 'nats:test'
97
+ filterSubjects:
98
+ - 'nats:subject'
99
+ - 'nats:subject2'
100
+ deliverPolicy: all
101
+ ackPolicy: explicit
55
102
  ```
56
103
 
57
- - Consumer буде створений автоматично, якщо не існує.
58
- - Повідомлення публікується у subject `dev.project:subject` (або `${NATS_STREAM}.project:subject`).
104
+ Застосуйте конфігурацію:
105
+
106
+ ```bash
107
+ # З змінними середовища
108
+ NODE_ENV=development NATS_URL=nats://localhost:4222 NATS_STREAM=dev node cli.js consumer.yaml
109
+
110
+ # Або через npx після публікації
111
+ npx @nitra/nats consumer.yaml
112
+ ```
59
113
 
60
114
  ---
61
115
 
@@ -95,68 +149,124 @@ console.log('pending for group:', count2)
95
149
 
96
150
  ## Як це працює
97
151
 
98
- - **publish(subject, data, consumers?):**
152
+ - **publish(subject, data):**
99
153
 
100
- - Перевіряє/створює consumer-ів (один раз за процес).
101
- - Публікує повідомлення у subject `${stream}.${subject}`.
102
- - Якщо передати масив consumers — створює кастомні durable consumer-и з кастомними іменами та фільтрами.
154
+ - Публікує повідомлення у subject `${stream}.${subject}`
155
+ - Перевіряє формат subject (має бути `project:subject`)
156
+
157
+ - **ensureConsumer(spec):**
158
+
159
+ - Створює consumer якщо не існує
160
+ - Оновлює `filter_subjects` якщо вони змінились
161
+ - Перестворює consumer якщо змінились `deliverPolicy` або `ackPolicy`
162
+ - Автоматично створює stream якщо потрібно
103
163
 
104
164
  - **read(durableName):**
105
165
 
106
- - Читає одне повідомлення з черги для durable consumer (за замовчуванням durable_name = subject).
166
+ - Читає одне повідомлення з черги для durable consumer
107
167
 
108
168
  - **finish():**
109
169
 
110
- - Підтверджує (ack) повідомлення.
170
+ - Підтверджує (ack) повідомлення
111
171
 
112
172
  - **getPendingCount(durableName):**
113
- - Повертає кількість непрочитаних повідомлень для durable consumer.
173
+ - Повертає кількість непрочитаних повідомлень для durable consumer
114
174
 
115
175
  ---
116
176
 
117
177
  ## Важливо
118
178
 
119
- - STREAM у NATS за замовчуванням `dev` (або значення змінної `NATS_STREAM`), але subject і durable consumer-и динамічні.
120
- - Для кожного subject або кастомного durable створюється окремий consumer з іменем `durableName`.
121
- - Пакет розрахований на single-message workflow (одне повідомлення на читання за раз, публікація — необмежена).
122
- - Для паралельної обробки або кастомних сценаріїв дивись вихідний код та розширюй під свої задачі.
179
+ - STREAM у NATS за замовчуванням `dev` (або значення змінної `NATS_STREAM`)
180
+ - Consumer-и потрібно створювати **явно** через `ensureConsumer` або CLI
181
+ - Subject має відповідати формату `project:subject`
182
+ - Пакет розрахований на single-message workflow (одне повідомлення на читання за раз)
183
+ - `ensureConsumer` розумно оновлює конфігурацію без втрати повідомлень
184
+ - CLI підтримує YAML конфігурації для декларативного управління consumer-ами
123
185
 
124
186
  ---
125
187
 
126
188
  ## Приклад повного workflow
127
189
 
128
190
  ```js
129
- import { publish, read, finish, getPendingCount } from '@nitra/nats'
191
+ import { publish, ensureConsumer, read, finish, getPendingCount } from '@nitra/nats'
192
+
193
+ // 1. Створення consumer-ів
194
+ await ensureConsumer({
195
+ streamName: 'dev',
196
+ durableName: 'project:subject',
197
+ filterSubjects: ['project:subject'],
198
+ deliverPolicy: 'all',
199
+ ackPolicy: 'explicit'
200
+ })
201
+
202
+ await ensureConsumer({
203
+ streamName: 'dev',
204
+ durableName: 'worker-group',
205
+ filterSubjects: ['project:subject', 'project:orders'],
206
+ deliverPolicy: 'all',
207
+ ackPolicy: 'explicit'
208
+ })
209
+
210
+ // 2. Публікація повідомлень
211
+ await publish('project:subject', { id: 1, action: 'create' })
212
+ await publish('project:orders', { orderId: 123, amount: 100 })
213
+
214
+ // 3. Перевірка pending повідомлень
215
+ const count1 = await getPendingCount('project:subject')
216
+ const count2 = await getPendingCount('worker-group')
217
+ console.log(`pending: ${count1}, worker-group: ${count2}`)
130
218
 
131
- // Публікація для стандартного durable (durable_name = subject)
132
- await publish('project:subject', { id: 1 })
219
+ // 4. Обробка повідомлень
220
+ const data1 = await read('project:subject')
221
+ console.log('received:', data1)
222
+ await finish()
133
223
 
134
- // Публікація для кількох consumer-ів
135
- await publish('project:subject', { id: 2 }, [
136
- { durableName: 'worker-group', filterSubjects: ['project:subject', 'project:subject2'] },
137
- { durableName: 'analytics', filterSubjects: ['project:subject'] }
138
- ])
224
+ const data2 = await read('worker-group')
225
+ console.log('group received:', data2)
226
+ await finish()
227
+ ```
139
228
 
140
- // Pending для стандартного durable
141
- const count = await getPendingCount('project:subject')
142
- console.log('pending:', count)
229
+ ---
143
230
 
144
- // Pending для кастомного durable
145
- const count2 = await getPendingCount('worker-group')
146
- console.log('pending for group:', count2)
231
+ ## CLI Інструмент
147
232
 
148
- // Читання для стандартного durable
149
- const { id } = await read('project:subject')
150
- console.log(`read. id: ${id}`)
151
- // Підтверджуємо, що повідомлення прочитано (якщо не викликати finish, то повідомлення буде повторно надіслано)
152
- await finish()
233
+ CLI підтримує роботу з YAML конфігураціями consumer-ів у форматі JetStream Consumer API.
153
234
 
154
- // Читання для кастомного durable
155
- const data = await read('worker-group')
156
- console.log('read:', data)
157
- await finish()
235
+ ### Використання
236
+
237
+ ```bash
238
+ # Застосування конфігурації consumer-а з YAML файлу
239
+ NODE_ENV=development NATS_URL=nats://localhost:4222 NATS_STREAM=dev node cli.js consumer.yaml
240
+
241
+ # Через npx після публікації пакету
242
+ npx @nitra/nats consumer.yaml
158
243
  ```
159
244
 
245
+ ### Формат YAML конфігурації
246
+
247
+ ```yaml
248
+ apiVersion: jetstream.nats.io/v1beta2
249
+ kind: Consumer
250
+ metadata:
251
+ name: my-consumer
252
+ namespace: dev
253
+ spec:
254
+ streamName: dev
255
+ durableName: my-consumer
256
+ filterSubjects:
257
+ - project:orders
258
+ - project:payments
259
+ deliverPolicy: all
260
+ ackPolicy: explicit
261
+ ```
262
+
263
+ CLI автоматично:
264
+
265
+ - Створить consumer якщо не існує
266
+ - Оновить filter_subjects якщо вони змінились
267
+ - Перестворить consumer якщо змінились deliverPolicy або ackPolicy
268
+ - Створить stream якщо потрібно
269
+
160
270
  ---
161
271
 
162
272
  ## Ліцензія
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@nitra/nats",
3
3
  "description": "nats helper",
4
- "version": "3.1.3",
4
+ "version": "4.0.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "src"
8
8
  ],
9
+ "bin": {
10
+ "nitra-nats": "./src/cli.js"
11
+ },
9
12
  "exports": {
10
13
  ".": "./src/index.js"
11
14
  },
@@ -21,8 +24,9 @@
21
24
  "homepage": "https://github.com/nitra/nats",
22
25
  "license": "MIT",
23
26
  "dependencies": {
24
- "@nitra/check-env": "^4.0.0",
25
- "@nitra/pino": "^2.3.0",
27
+ "@nitra/check-env": "^4.1.0",
28
+ "@nitra/pino": "^2.7.4",
29
+ "js-yaml": "^4.1.1",
26
30
  "nats": "^2.29.3"
27
31
  },
28
32
  "engines": {
package/src/cli.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import { exit } from 'node:process'
5
+ import yaml from 'js-yaml'
6
+ import { ensureConsumer } from './consumer.js'
7
+ import { checkSubjectFormat } from './utils.js'
8
+
9
+ function parseConsumerYaml() {
10
+ try {
11
+ const filePath = path.resolve(process.cwd(), process.argv[2])
12
+ const fileContents = fs.readFileSync(filePath, 'utf8')
13
+ const data = yaml.load(fileContents)
14
+
15
+ return {
16
+ apiVersion: data.apiVersion,
17
+ kind: data.kind,
18
+ metadata: {
19
+ name: data.metadata?.name,
20
+ namespace: data.metadata?.namespace
21
+ },
22
+ spec: {
23
+ streamName: data.spec?.streamName,
24
+ durableName: data.spec?.durableName,
25
+ filterSubjects: data.spec?.filterSubjects,
26
+ deliverPolicy: data.spec?.deliverPolicy,
27
+ ackPolicy: data.spec?.ackPolicy
28
+ }
29
+ }
30
+ } catch (error) {
31
+ throw new Error(`Помилка парсингу YAML файлу: ${error.message}`)
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Валідує структуру парсеного consumer об'єкта
37
+ * @param {Object} consumer - Об'єкт consumer для валідації
38
+ * @returns {boolean} true якщо валідація пройшла успішно
39
+ * @throws {Error} Якщо валідація не пройшла
40
+ */
41
+ export function validateConsumer(consumer) {
42
+ if (!consumer.apiVersion) throw new Error('Відсутнє поле apiVersion')
43
+ if (!consumer.kind) throw new Error('Відсутнє поле kind')
44
+ if (!consumer.metadata?.name) throw new Error('Відсутнє поле metadata.name')
45
+
46
+ for (const field of ['streamName', 'durableName', 'filterSubjects', 'deliverPolicy', 'ackPolicy']) {
47
+ if (!consumer.spec?.[field]) throw new Error(`Відсутнє поле spec.${field}`)
48
+ }
49
+
50
+ if (!Array.isArray(consumer.spec.filterSubjects)) throw new Error('Поле spec.filterSubjects повинно бути масивом')
51
+
52
+ for (const subject of consumer.spec.filterSubjects) {
53
+ checkSubjectFormat(subject)
54
+ }
55
+ }
56
+
57
+ // Приклад використання
58
+ if (import.meta.url === `file://${process.argv[1]}`) {
59
+ try {
60
+ const consumer = parseConsumerYaml()
61
+
62
+ validateConsumer(consumer)
63
+
64
+ await ensureConsumer(consumer.spec)
65
+ exit(0)
66
+ } catch (error) {
67
+ console.error('Помилка:', error.message)
68
+ exit(1)
69
+ }
70
+ }
package/src/consumer.js CHANGED
@@ -1,49 +1,69 @@
1
- import { jsm, AckPolicy, stream } from './nats.js'
2
- import { checkSubjectFormat } from './utils.js'
1
+ import { jsm, stream } from './nats.js'
2
+ import { ensureStream } from './stream.js'
3
3
  import { log } from '@nitra/pino'
4
- // import { ensureStream } from './stream.js'
5
-
6
- // Set для зберігання існуючих consumer
7
- const consumerReady = new Set()
8
4
 
9
5
  /**
10
- * Перевіряє наявність або створює durable consumer(а) для subject(ів).
11
- * @param {string} subject - subject у форматі project:subject
12
- * @param {object[]} consumers - список consumer-ів. [{durableName, filterSubjects}]
6
+ * Створює або оновлює consumer.
7
+ * @param {object{streamName: string, durableName: string, filterSubjects: string[], deliverPolicy: string, ackPolicy: string}} spec - об'єкт consumer-а.
13
8
  * @returns {Promise<void>}
14
9
  */
15
- export async function ensureConsumer(subject, consumers = [{ durableName: subject, filterSubjects: [subject] }]) {
16
- if (consumers.every(c => consumerReady.has(c.durableName))) {
17
- log.debug(`✅ Consumers: ${consumers.map(c => c.durableName)} already exists`)
18
- return
19
- }
10
+ export async function ensureConsumer(spec) {
11
+ // створюємо stream якщо не існує
12
+ const streamName = spec.streamName || stream
13
+ await ensureStream(streamName)
20
14
 
21
- // перевіряємо чи subject відповідає формату project:subject
22
- checkSubjectFormat(subject)
15
+ const consumerArr = (await jsm.consumers?.list(streamName)?.next()) || []
16
+ const consumer = consumerArr.find(ca => ca.config.durable_name === spec.durableName)
23
17
 
24
- // // створюємо stream якщо не існує
25
- // if (!consumerReady.size) {
26
- // await ensureStream()
27
- // }
18
+ // якщо не існує consumer-а створюємо його. якщо є перевіримо чи відповідає spec(якщо ні, то перестворюємо або оновлюємо)
19
+ if (consumer) {
20
+ log.info(`✅ Consumer «${spec.durableName}» already exists`)
28
21
 
29
- const consumerArr = (await jsm.consumers?.list(stream)?.next()) || []
30
- for (const c of consumers) {
31
- const isExists = consumerArr.some(ca => ca.config.durable_name === c.durableName)
22
+ const { filter_subjects, deliver_policy, ack_policy } = consumer.config
32
23
 
33
- // якщо не існує consumer-а який слухає subject, то створюємо за замовчуванням (durable_name = subject)
34
- if (isExists) {
35
- log.debug(`✅ Consumer «${c.durableName}» already exists`)
36
- } else {
37
- await jsm.consumers.add(stream, {
38
- durable_name: c.durableName,
39
- ack_policy: AckPolicy.Explicit, // якщо не підтвердити повідомлення, воно буде повторно надіслано
40
- filter_subjects: c.filterSubjects.map(fs => `${stream}.${fs}`), // якщо не вказати, то consumer буде слухати всі повідомлення зі stream
41
- deliver_policy: 'all' // 'all' - всі непрочитані повідомлення
42
- })
24
+ // якщо deliver_policy або ack_policy не відповідають spec, то перестворюємо consumer
25
+ if (deliver_policy !== spec.deliverPolicy || ack_policy !== spec.ackPolicy) {
26
+ const tempSpec = {
27
+ durableName: `temp_${spec.durableName}`,
28
+ filterSubjects: filter_subjects,
29
+ deliverPolicy: deliver_policy,
30
+ ackPolicy: ack_policy
31
+ }
32
+ // create temp consumer with old filter_subjects
33
+ await createConsumer(streamName, tempSpec)
34
+ // delete old consumer
35
+ await jsm.consumers.delete(streamName, spec.durableName)
36
+ // create new consumer
37
+ await createConsumer(streamName, spec)
38
+ // delete temp consumer
39
+ await jsm.consumers.delete(streamName, tempSpec.durableName)
43
40
 
44
- log.debug(`🔥 Consumer «${c.durableName}» created`)
41
+ log.info(`🔥 Consumer «${spec.durableName}» - recreated`)
45
42
  }
43
+ // якщо filter_subjects не відповідають spec, то оновлюємо consumer
44
+ else if (
45
+ spec.filterSubjects.some(fs => !filter_subjects.includes(`${streamName}.${fs}`)) ||
46
+ filter_subjects.some(fs => !spec.filterSubjects.includes(fs.split('.').pop()))
47
+ ) {
48
+ await jsm.consumers.update(streamName, spec.durableName, {
49
+ filter_subjects: spec.filterSubjects.map(fs => `${streamName}.${fs}`)
50
+ })
46
51
 
47
- consumerReady.add(c.durableName)
52
+ log.info(`🔥 Consumer «${spec.durableName}» - updated`)
53
+ } else {
54
+ log.info(`✅ Consumer «${spec.durableName}» - no changes`)
55
+ }
56
+ } else {
57
+ await createConsumer(streamName, spec)
58
+ log.info(`🔥 Consumer «${spec.durableName}» created`)
48
59
  }
49
60
  }
61
+
62
+ async function createConsumer(streamName, spec) {
63
+ await jsm.consumers.add(streamName, {
64
+ durable_name: spec.durableName,
65
+ ack_policy: spec.ackPolicy, // якщо не підтвердити повідомлення, воно буде повторно надіслано
66
+ filter_subjects: spec.filterSubjects.map(fs => `${streamName}.${fs}`), // якщо не вказати, то consumer буде слухати всі повідомлення зі stream
67
+ deliver_policy: spec.deliverPolicy // 'all' - всі непрочитані повідомлення
68
+ })
69
+ }
package/src/nats.js CHANGED
@@ -1,13 +1,26 @@
1
- import { checkEnv, env } from '@nitra/check-env'
1
+ import { checkEnv } from '@nitra/check-env'
2
2
  import { connect, JSONCodec } from 'nats'
3
-
4
- checkEnv(['NATS_URL', 'NATS_STREAM'])
3
+ import { env } from 'node:process'
5
4
 
6
5
  // AckPolicy
7
- export { AckPolicy } from 'nats'
6
+ // export { AckPolicy } from 'nats'
7
+
8
+ let nc
9
+
10
+ // якщо задані демо дані, то використовуємо фейковий NATS
11
+ if (env.NATS_FAKE_DATA) {
12
+ nc = {
13
+ jetstream: () => {},
14
+ jetstreamManager: async () => {
15
+ Promise.resolve()
16
+ }
17
+ }
18
+ } else {
19
+ checkEnv(['NATS_URL', 'NATS_STREAM'])
8
20
 
9
- // Connect to NATS
10
- const nc = await connect({ servers: env.NATS_URL })
21
+ // Connect to NATS
22
+ nc = await connect({ servers: env.NATS_URL })
23
+ }
11
24
 
12
25
  // JSONCodec
13
26
  export const jc = JSONCodec()
@@ -1,5 +1,6 @@
1
1
  import { jsm, stream } from './nats.js'
2
2
  import { log } from '@nitra/pino'
3
+ import { env } from 'node:process'
3
4
 
4
5
  /**
5
6
  * Повертає кількість непрочитаних (pending) повідомлень для durable consumer.
@@ -7,6 +8,11 @@ import { log } from '@nitra/pino'
7
8
  * @returns {Promise<number>} - кількість непрочитаних повідомлень
8
9
  */
9
10
  export async function getPendingCount(consumer) {
11
+ // якщо задані демо дані, то завжди повертаємо 0
12
+ if (env.NATS_FAKE_DATA) {
13
+ return 0
14
+ }
15
+
10
16
  try {
11
17
  const info = await jsm.consumers.info(stream, consumer)
12
18
  return info.num_pending + info.num_ack_pending
package/src/publish.js CHANGED
@@ -1,18 +1,22 @@
1
1
  import { jc, js, stream } from './nats.js'
2
- import { ensureConsumer } from './consumer.js'
2
+ import { checkSubjectFormat } from './utils.js'
3
3
  import { log } from '@nitra/pino'
4
+ import { env } from 'node:process'
4
5
 
5
6
  /**
6
7
  * Публікує повідомлення у JetStream для вказаного subject.
7
- * Consumer створюється автоматично, якщо не існує.
8
8
  * @param {string} subject - subject у форматі project:subject
9
9
  * @param {object} data - дані для публікації
10
- * @param {object[]} [consumers] - список consumer-ів які потрібно створити. [{durableName, filterSubject}] (необов'язково)
11
10
  * @returns {Promise<void>}
12
11
  */
13
- export async function publish(subject, data, consumers) {
14
- // Ensure consumer if not exists
15
- await ensureConsumer(subject, consumers)
12
+ export async function publish(subject, data) {
13
+ // якщо задані демо дані, то ігноримо запис
14
+ if (env.NATS_FAKE_DATA) {
15
+ return Promise.resolve()
16
+ }
17
+
18
+ // перевіряємо чи subject відповідає формату project:subject
19
+ checkSubjectFormat(subject)
16
20
 
17
21
  log.debug('publish:', data)
18
22
  // Publish message
package/src/stream.js CHANGED
@@ -5,14 +5,14 @@ import { log } from '@nitra/pino'
5
5
  * Перевіряє наявність або створює JetStream stream.
6
6
  * @returns {Promise<void>}
7
7
  */
8
- export async function ensureStream() {
8
+ export async function ensureStream(streamName = stream) {
9
9
  try {
10
- await jsm.streams.info(stream)
10
+ await jsm.streams.info(streamName)
11
11
  log.debug('✅ Stream already exists')
12
12
  } catch {
13
13
  await jsm.streams.add({
14
- name: stream,
15
- subjects: [`${stream}.>`],
14
+ name: streamName,
15
+ subjects: [`${streamName}.>`],
16
16
  retention: 'interest', // зберігає повідомлення поки є consumers або поки всі повідомлення не будуть прочитані
17
17
  replicas: 1, // кількість реплік у кластері TODO: проговорити кількість реплік
18
18
  storage: 'file' //'memory' TODO: проговорити які параметри потрібні для кластера
package/src/worker.js CHANGED
@@ -2,6 +2,7 @@ import { js, jc, stream } from './nats.js'
2
2
  import { state } from './utils.js'
3
3
  import { exit } from 'node:process'
4
4
  import { log } from '@nitra/pino'
5
+ import { env } from 'node:process'
5
6
 
6
7
  /**
7
8
  * Зчитує одне повідомлення з JetStream для вказаного consumer-а.
@@ -9,6 +10,12 @@ import { log } from '@nitra/pino'
9
10
  * @returns {Promise<object>} - декодовані дані повідомлення
10
11
  */
11
12
  export async function read(consumer) {
13
+ // якщо задані демо дані, то повертаємо їх
14
+ if (env.NATS_FAKE_DATA) {
15
+ console.log('env.NATS_FAKE_DATA:', env.NATS_FAKE_DATA)
16
+ return JSON.parse(env.NATS_FAKE_DATA)
17
+ }
18
+
12
19
  const consumerObj = await js.consumers.get(stream, consumer)
13
20
 
14
21
  const iter = await consumerObj.fetch({ max_messages: 1, expires: 1000 })