@nitra/nats 3.2.0 → 4.0.2
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 +157 -44
- package/package.json +7 -3
- package/src/cli.js +81 -0
- package/src/consumer.js +55 -35
- package/src/nats.js +2 -2
- package/src/publish.js +7 -8
- package/src/stream.js +6 -5
- package/src/worker.js +3 -4
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @nitra/nats
|
|
2
2
|
|
|
3
|
-
NATS JetStream helper для Node.js
|
|
4
|
-
Простий API для публікації, обробки та моніторингу повідомлень у черзі з
|
|
3
|
+
NATS JetStream helper для Node.js
|
|
4
|
+
Простий API для публікації, обробки та моніторингу повідомлень у черзі з гнучким управлінням consumer-ами через конфігурацію та CLI інструменти.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -44,18 +44,72 @@ projectName:subjectName
|
|
|
44
44
|
```js
|
|
45
45
|
import { publish } from '@nitra/nats'
|
|
46
46
|
|
|
47
|
-
//
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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,65 +149,124 @@ console.log('pending for group:', count2)
|
|
|
95
149
|
|
|
96
150
|
## Як це працює
|
|
97
151
|
|
|
98
|
-
- **publish(subject, data
|
|
99
|
-
|
|
100
|
-
- Публікує повідомлення у subject `${stream}.${subject}
|
|
101
|
-
-
|
|
152
|
+
- **publish(subject, data):**
|
|
153
|
+
|
|
154
|
+
- Публікує повідомлення у subject `${stream}.${subject}`
|
|
155
|
+
- Перевіряє формат subject (має бути `project:subject`)
|
|
156
|
+
|
|
157
|
+
- **ensureConsumer(spec):**
|
|
158
|
+
|
|
159
|
+
- Створює consumer якщо не існує
|
|
160
|
+
- Оновлює `filter_subjects` якщо вони змінились
|
|
161
|
+
- Перестворює consumer якщо змінились `deliverPolicy` або `ackPolicy`
|
|
162
|
+
- Автоматично створює stream якщо потрібно
|
|
102
163
|
|
|
103
164
|
- **read(durableName):**
|
|
104
|
-
|
|
165
|
+
|
|
166
|
+
- Читає одне повідомлення з черги для durable consumer
|
|
105
167
|
|
|
106
168
|
- **finish():**
|
|
107
|
-
|
|
169
|
+
|
|
170
|
+
- Підтверджує (ack) повідомлення
|
|
108
171
|
|
|
109
172
|
- **getPendingCount(durableName):**
|
|
110
|
-
- Повертає кількість непрочитаних повідомлень для durable consumer
|
|
173
|
+
- Повертає кількість непрочитаних повідомлень для durable consumer
|
|
111
174
|
|
|
112
175
|
---
|
|
113
176
|
|
|
114
177
|
## Важливо
|
|
115
178
|
|
|
116
|
-
- STREAM у NATS за замовчуванням `dev` (або значення змінної `NATS_STREAM`)
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
179
|
+
- STREAM у NATS за замовчуванням `dev` (або значення змінної `NATS_STREAM`)
|
|
180
|
+
- Consumer-и потрібно створювати **явно** через `ensureConsumer` або CLI
|
|
181
|
+
- Subject має відповідати формату `project:subject`
|
|
182
|
+
- Пакет розрахований на single-message workflow (одне повідомлення на читання за раз)
|
|
183
|
+
- `ensureConsumer` розумно оновлює конфігурацію без втрати повідомлень
|
|
184
|
+
- CLI підтримує YAML конфігурації для декларативного управління consumer-ами
|
|
120
185
|
|
|
121
186
|
---
|
|
122
187
|
|
|
123
188
|
## Приклад повного workflow
|
|
124
189
|
|
|
125
190
|
```js
|
|
126
|
-
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}`)
|
|
127
218
|
|
|
128
|
-
//
|
|
129
|
-
await
|
|
219
|
+
// 4. Обробка повідомлень
|
|
220
|
+
const data1 = await read('project:subject')
|
|
221
|
+
console.log('received:', data1)
|
|
222
|
+
await finish()
|
|
130
223
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
])
|
|
224
|
+
const data2 = await read('worker-group')
|
|
225
|
+
console.log('group received:', data2)
|
|
226
|
+
await finish()
|
|
227
|
+
```
|
|
136
228
|
|
|
137
|
-
|
|
138
|
-
const count = await getPendingCount('project:subject')
|
|
139
|
-
console.log('pending:', count)
|
|
229
|
+
---
|
|
140
230
|
|
|
141
|
-
|
|
142
|
-
const count2 = await getPendingCount('worker-group')
|
|
143
|
-
console.log('pending for group:', count2)
|
|
231
|
+
## CLI Інструмент
|
|
144
232
|
|
|
145
|
-
|
|
146
|
-
const { id } = await read('project:subject')
|
|
147
|
-
console.log(`read. id: ${id}`)
|
|
148
|
-
// Підтверджуємо, що повідомлення прочитано (якщо не викликати finish, то повідомлення буде повторно надіслано)
|
|
149
|
-
await finish()
|
|
233
|
+
CLI підтримує роботу з YAML конфігураціями consumer-ів у форматі JetStream Consumer API.
|
|
150
234
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
155
243
|
```
|
|
156
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
|
+
|
|
157
270
|
---
|
|
158
271
|
|
|
159
272
|
## Ліцензія
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/nats",
|
|
3
3
|
"description": "nats helper",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.2",
|
|
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
|
},
|
|
@@ -15,14 +18,15 @@
|
|
|
15
18
|
],
|
|
16
19
|
"repository": {
|
|
17
20
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/nitra/nats.git"
|
|
21
|
+
"url": "git+https://github.com/nitra/nats.git"
|
|
19
22
|
},
|
|
20
23
|
"bugs": "https://github.com/nitra/nats/issues",
|
|
21
24
|
"homepage": "https://github.com/nitra/nats",
|
|
22
25
|
"license": "MIT",
|
|
23
26
|
"dependencies": {
|
|
24
27
|
"@nitra/check-env": "^4.1.0",
|
|
25
|
-
"@nitra/pino": "^2.
|
|
28
|
+
"@nitra/pino": "^2.8.1",
|
|
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,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { load } from 'js-yaml'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { exit } from 'node:process'
|
|
6
|
+
import { ensureConsumer } from './consumer.js'
|
|
7
|
+
import { checkSubjectFormat } from './utils.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Парсить YAML файл з конфігурацією consumer-а
|
|
11
|
+
* @returns {object} об'єкт consumer-а
|
|
12
|
+
*/
|
|
13
|
+
function parseConsumerYaml() {
|
|
14
|
+
try {
|
|
15
|
+
const filePath = path.resolve(process.cwd(), process.argv[2])
|
|
16
|
+
const fileContents = readFileSync(filePath, 'utf8')
|
|
17
|
+
const data = load(fileContents)
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
apiVersion: data.apiVersion,
|
|
21
|
+
kind: data.kind,
|
|
22
|
+
metadata: {
|
|
23
|
+
name: data.metadata?.name,
|
|
24
|
+
namespace: data.metadata?.namespace
|
|
25
|
+
},
|
|
26
|
+
spec: {
|
|
27
|
+
streamName: data.spec?.streamName,
|
|
28
|
+
durableName: data.spec?.durableName,
|
|
29
|
+
filterSubjects: data.spec?.filterSubjects,
|
|
30
|
+
deliverPolicy: data.spec?.deliverPolicy,
|
|
31
|
+
ackPolicy: data.spec?.ackPolicy
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Помилка парсингу YAML файлу: ${error.message}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Валідує структуру парсеного consumer об'єкта
|
|
41
|
+
* @param {object} consumer - Об'єкт consumer для валідації
|
|
42
|
+
* @returns {boolean} true якщо валідація пройшла успішно
|
|
43
|
+
* @throws {Error} Якщо валідація не пройшла
|
|
44
|
+
*/
|
|
45
|
+
export function validateConsumer(consumer) {
|
|
46
|
+
if (!consumer.apiVersion) throw new Error('Відсутнє поле apiVersion')
|
|
47
|
+
if (!consumer.kind) throw new Error('Відсутнє поле kind')
|
|
48
|
+
if (!consumer.metadata?.name) throw new Error('Відсутнє поле metadata.name')
|
|
49
|
+
|
|
50
|
+
for (const field of ['streamName', 'durableName', 'filterSubjects', 'deliverPolicy', 'ackPolicy']) {
|
|
51
|
+
if (!consumer.spec?.[field]) throw new Error(`Відсутнє поле spec.${field}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Array.isArray(consumer.spec.filterSubjects)) throw new Error('Поле spec.filterSubjects повинно бути масивом')
|
|
55
|
+
|
|
56
|
+
for (const subject of consumer.spec.filterSubjects) {
|
|
57
|
+
checkSubjectFormat(subject)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Виконуємо код коли скрипт запущений напряму (включає сумісність з bunx)
|
|
62
|
+
if (
|
|
63
|
+
import.meta.url === `file://${process.argv[1]}` ||
|
|
64
|
+
process.argv[1].endsWith('nitra-nats') ||
|
|
65
|
+
import.meta.url.includes('cli.js')
|
|
66
|
+
) {
|
|
67
|
+
try {
|
|
68
|
+
const consumer = parseConsumerYaml()
|
|
69
|
+
|
|
70
|
+
validateConsumer(consumer)
|
|
71
|
+
|
|
72
|
+
await ensureConsumer(consumer.spec)
|
|
73
|
+
exit(0)
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Помилка:', error.message)
|
|
76
|
+
exit(1)
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
console.error('Скрипт запущений як модуль, не можна використовувати CLI інструмент')
|
|
80
|
+
exit(1)
|
|
81
|
+
}
|
package/src/consumer.js
CHANGED
|
@@ -1,49 +1,69 @@
|
|
|
1
|
-
import { jsm,
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
11
|
-
* @param {string
|
|
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(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
10
|
+
export async function ensureConsumer(spec) {
|
|
11
|
+
// створюємо stream якщо не існує
|
|
12
|
+
const streamName = spec.streamName || stream
|
|
13
|
+
await ensureStream(streamName)
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const consumerArr = (await jsm.consumers?.list(streamName)?.next()) || []
|
|
16
|
+
const consumer = consumerArr.find(ca => ca.config.durable_name === spec.durableName)
|
|
23
17
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// }
|
|
18
|
+
// якщо не існує consumer-а створюємо його. якщо є перевіримо чи відповідає spec(якщо ні, то перестворюємо або оновлюємо)
|
|
19
|
+
if (consumer) {
|
|
20
|
+
log.info(`✅ Consumer «${spec.durableName}» already exists`)
|
|
28
21
|
|
|
29
|
-
|
|
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
|
-
// якщо
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
@@ -3,7 +3,7 @@ import { connect, JSONCodec } from 'nats'
|
|
|
3
3
|
import { env } from 'node:process'
|
|
4
4
|
|
|
5
5
|
// AckPolicy
|
|
6
|
-
export { AckPolicy } from 'nats'
|
|
6
|
+
// export { AckPolicy } from 'nats'
|
|
7
7
|
|
|
8
8
|
let nc
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ if (env.NATS_FAKE_DATA) {
|
|
|
12
12
|
nc = {
|
|
13
13
|
jetstream: () => {},
|
|
14
14
|
jetstreamManager: async () => {
|
|
15
|
-
|
|
15
|
+
{}
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
} else {
|
package/src/publish.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { jc, js, stream } from './nats.js'
|
|
2
|
-
import { ensureConsumer } from './consumer.js'
|
|
3
1
|
import { log } from '@nitra/pino'
|
|
4
2
|
import { env } from 'node:process'
|
|
3
|
+
import { jc, js, stream } from './nats.js'
|
|
4
|
+
import { checkSubjectFormat } from './utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Публікує повідомлення у JetStream для вказаного subject.
|
|
8
|
-
* Consumer створюється автоматично, якщо не існує.
|
|
9
8
|
* @param {string} subject - subject у форматі project:subject
|
|
10
9
|
* @param {object} data - дані для публікації
|
|
11
|
-
* @param {object[]} [consumers] - список consumer-ів які потрібно створити. [{durableName, filterSubject}] (необов'язково)
|
|
12
10
|
* @returns {Promise<void>}
|
|
13
11
|
*/
|
|
14
|
-
|
|
12
|
+
// oxlint-disable-next-line require-await
|
|
13
|
+
export async function publish(subject, data) {
|
|
15
14
|
// якщо задані демо дані, то ігноримо запис
|
|
16
15
|
if (env.NATS_FAKE_DATA) {
|
|
17
|
-
return
|
|
16
|
+
return
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
//
|
|
21
|
-
|
|
19
|
+
// перевіряємо чи subject відповідає формату project:subject
|
|
20
|
+
checkSubjectFormat(subject)
|
|
22
21
|
|
|
23
22
|
log.debug('publish:', data)
|
|
24
23
|
// Publish message
|
package/src/stream.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { jsm, stream } from './nats.js'
|
|
2
1
|
import { log } from '@nitra/pino'
|
|
2
|
+
import { jsm, stream } from './nats.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Перевіряє наявність або створює JetStream stream.
|
|
6
|
+
* @param streamName
|
|
6
7
|
* @returns {Promise<void>}
|
|
7
8
|
*/
|
|
8
|
-
export async function ensureStream() {
|
|
9
|
+
export async function ensureStream(streamName = stream) {
|
|
9
10
|
try {
|
|
10
|
-
await jsm.streams.info(
|
|
11
|
+
await jsm.streams.info(streamName)
|
|
11
12
|
log.debug('✅ Stream already exists')
|
|
12
13
|
} catch {
|
|
13
14
|
await jsm.streams.add({
|
|
14
|
-
name:
|
|
15
|
-
subjects: [`${
|
|
15
|
+
name: streamName,
|
|
16
|
+
subjects: [`${streamName}.>`],
|
|
16
17
|
retention: 'interest', // зберігає повідомлення поки є consumers або поки всі повідомлення не будуть прочитані
|
|
17
18
|
replicas: 1, // кількість реплік у кластері TODO: проговорити кількість реплік
|
|
18
19
|
storage: 'file' //'memory' TODO: проговорити які параметри потрібні для кластера
|
package/src/worker.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { js, jc, stream } from './nats.js'
|
|
2
|
-
import { state } from './utils.js'
|
|
3
|
-
import { exit } from 'node:process'
|
|
4
1
|
import { log } from '@nitra/pino'
|
|
5
|
-
import { env } from 'node:process'
|
|
2
|
+
import { env, exit } from 'node:process'
|
|
3
|
+
import { jc, js, stream } from './nats.js'
|
|
4
|
+
import { state } from './utils.js'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Зчитує одне повідомлення з JetStream для вказаного consumer-а.
|