@nitra/nats 4.1.0 → 4.2.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/README.md +1 -1
- package/package.json +16 -16
- package/src/cli.js +3 -2
- package/src/consumer.js +79 -38
- package/src/index.js +1 -2
- package/src/nats.js +1 -4
- package/src/pending-count.js +2 -1
- package/src/publish.js +4 -3
- package/src/stream.js +6 -29
- package/src/utils.js +15 -8
- package/src/worker.js +54 -22
- package/src/finish.js +0 -13
package/README.md
CHANGED
|
@@ -232,7 +232,7 @@ CLI підтримує роботу з YAML конфігураціями consume
|
|
|
232
232
|
|
|
233
233
|
```bash
|
|
234
234
|
# Застосування конфігурації consumer-а з YAML файлу
|
|
235
|
-
|
|
235
|
+
NATS_URL=nats://localhost:4222 node cli.js consumer.yaml
|
|
236
236
|
|
|
237
237
|
# Через npx після публікації пакету
|
|
238
238
|
npx @nitra/nats consumer.yaml
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/nats",
|
|
3
|
+
"version": "4.2.1",
|
|
3
4
|
"description": "nats helper",
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"src"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nats",
|
|
7
|
+
"nitra"
|
|
8
8
|
],
|
|
9
|
+
"homepage": "https://github.com/nitra/nats",
|
|
10
|
+
"bugs": "https://github.com/nitra/nats/issues",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/nitra/nats.git"
|
|
15
|
+
},
|
|
9
16
|
"bin": {
|
|
10
17
|
"nitra-nats": "src/cli.js"
|
|
11
18
|
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
12
23
|
"exports": {
|
|
13
24
|
".": "./src/index.js"
|
|
14
25
|
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"nitra",
|
|
17
|
-
"nats"
|
|
18
|
-
],
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/nitra/nats.git"
|
|
22
|
-
},
|
|
23
|
-
"bugs": "https://github.com/nitra/nats/issues",
|
|
24
|
-
"homepage": "https://github.com/nitra/nats",
|
|
25
|
-
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@nats-io/jetstream": "^3.3.0",
|
|
28
28
|
"@nats-io/nats-core": "^3.3.0",
|
|
29
29
|
"@nats-io/transport-node": "^3.3.0",
|
|
30
30
|
"@nitra/check-env": "^4.1.0",
|
|
31
|
-
"@nitra/pino": "^2.
|
|
31
|
+
"@nitra/pino": "^2.10.0",
|
|
32
32
|
"js-yaml": "^4.1.1"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
package/src/cli.js
CHANGED
|
@@ -28,7 +28,8 @@ function parseConsumerYaml() {
|
|
|
28
28
|
durableName: data.spec?.durableName,
|
|
29
29
|
filterSubjects: data.spec?.filterSubjects,
|
|
30
30
|
deliverPolicy: data.spec?.deliverPolicy,
|
|
31
|
-
ackPolicy: data.spec?.ackPolicy
|
|
31
|
+
ackPolicy: data.spec?.ackPolicy,
|
|
32
|
+
ackWait: data.spec?.ackWait
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
} catch (error) {
|
|
@@ -53,7 +54,7 @@ export function validateConsumer(consumer) {
|
|
|
53
54
|
if (!Array.isArray(consumer.spec.filterSubjects)) throw new Error('Поле spec.filterSubjects повинно бути масивом')
|
|
54
55
|
|
|
55
56
|
for (const subject of consumer.spec.filterSubjects) {
|
|
56
|
-
checkSubjectFormat(subject)
|
|
57
|
+
checkSubjectFormat(consumer.spec.streamName, `${consumer.spec.streamName}.${subject}`)
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
package/src/consumer.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { jsm
|
|
2
|
-
import { ensureStream } from './stream.js'
|
|
1
|
+
import { jsm } from './nats.js'
|
|
3
2
|
import { log } from '@nitra/pino'
|
|
3
|
+
import { nanos, millis } from '@nats-io/nats-core'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Створює або оновлює consumer.
|
|
7
7
|
* @param {object} spec - об'єкт consumer-а
|
|
8
|
-
* @param {string}
|
|
8
|
+
* @param {string} spec.streamName - назва stream
|
|
9
9
|
* @param {string} spec.durableName - назва consumer-а
|
|
10
10
|
* @param {string[]} spec.filterSubjects - масив subject-ів для фільтрації
|
|
11
11
|
* @param {string} spec.deliverPolicy - політика доставки
|
|
@@ -14,71 +14,112 @@ import { log } from '@nitra/pino'
|
|
|
14
14
|
*/
|
|
15
15
|
export async function ensureConsumer(spec) {
|
|
16
16
|
// створюємо stream якщо не існує
|
|
17
|
-
|
|
18
|
-
await ensureStream(streamName)
|
|
17
|
+
await ensureStream(spec.streamName)
|
|
19
18
|
|
|
20
|
-
const consumerArr = (await jsm.consumers?.list(streamName)?.next()) || []
|
|
19
|
+
const consumerArr = (await jsm.consumers?.list(spec.streamName)?.next()) || []
|
|
21
20
|
const consumer = consumerArr.find(ca => ca.config.durable_name === spec.durableName)
|
|
22
21
|
|
|
22
|
+
// Додаємо стрім до subject
|
|
23
|
+
// якщо не вказати, то consumer буде слухати всі повідомлення зі stream
|
|
24
|
+
spec.filterSubjects = spec.filterSubjects.map(fs => `${spec.streamName}.${fs}`)
|
|
25
|
+
|
|
26
|
+
const preparedSpec = prepareSpec(spec)
|
|
27
|
+
|
|
23
28
|
// якщо не існує consumer-а створюємо його. якщо є перевіримо чи відповідає spec(якщо ні, то перестворюємо або оновлюємо)
|
|
24
29
|
if (consumer) {
|
|
25
30
|
log.info(`✅ Consumer «${spec.durableName}» already exists`)
|
|
26
31
|
|
|
27
|
-
const { filter_subjects, deliver_policy, ack_policy } = consumer.config
|
|
32
|
+
const { filter_subjects, deliver_policy, ack_policy, ack_wait } = consumer.config
|
|
28
33
|
|
|
29
34
|
// якщо deliver_policy або ack_policy не відповідають spec, то перестворюємо consumer
|
|
30
35
|
if (deliver_policy !== spec.deliverPolicy || ack_policy !== spec.ackPolicy) {
|
|
31
|
-
const tempSpec =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// create temp consumer with old filter_subjects
|
|
38
|
-
await createConsumer(streamName, tempSpec)
|
|
36
|
+
const tempSpec = preparedSpec
|
|
37
|
+
tempSpec.durable_name = `temp_${preparedSpec.durable_name}`
|
|
38
|
+
|
|
39
|
+
// Цей “тимчасовий consumer” потрібен як страховка на час перестворення,
|
|
40
|
+
// щоб не втратити повідомлення у стрімі з retention: 'interest'
|
|
41
|
+
await jsm.consumers.add(spec.streamName, tempSpec)
|
|
39
42
|
// delete old consumer
|
|
40
|
-
await jsm.consumers.delete(streamName, spec.durableName)
|
|
43
|
+
await jsm.consumers.delete(spec.streamName, spec.durableName)
|
|
41
44
|
// create new consumer
|
|
42
|
-
await
|
|
45
|
+
await jsm.consumers.add(spec.streamName, preparedSpec)
|
|
46
|
+
|
|
43
47
|
// delete temp consumer
|
|
44
|
-
await jsm.consumers.delete(streamName, tempSpec.
|
|
48
|
+
await jsm.consumers.delete(spec.streamName, tempSpec.durable_name)
|
|
45
49
|
|
|
46
50
|
log.info(`🔥 Consumer «${spec.durableName}» - recreated`)
|
|
47
51
|
}
|
|
48
52
|
// якщо filter_subjects не відповідають spec, то оновлюємо consumer
|
|
49
53
|
else if (
|
|
50
|
-
spec.filterSubjects
|
|
51
|
-
|
|
54
|
+
compareArrays(spec.filterSubjects, filter_subjects) &&
|
|
55
|
+
(spec.ackWait === undefined || spec.ackWait === millis(ack_wait ?? 0))
|
|
52
56
|
) {
|
|
53
|
-
await jsm.consumers.update(streamName, spec.durableName, {
|
|
54
|
-
filter_subjects: spec.filterSubjects.map(fs => `${streamName}.${fs}`)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
log.info(`🔥 Consumer «${spec.durableName}» - updated`)
|
|
58
|
-
} else {
|
|
59
57
|
log.info(`✅ Consumer «${spec.durableName}» - no changes`)
|
|
58
|
+
} else {
|
|
59
|
+
await jsm.consumers.update(spec.streamName, spec.durableName, preparedSpec)
|
|
60
|
+
|
|
61
|
+
log.info(`🔥 Consumer «${spec.durableName}» filter_subjects - updated`)
|
|
60
62
|
}
|
|
61
63
|
} else {
|
|
62
|
-
await
|
|
64
|
+
await jsm.consumers.add(spec.streamName, preparedSpec)
|
|
63
65
|
log.info(`🔥 Consumer «${spec.durableName}» created`)
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @param {string} streamName - назва stream
|
|
70
|
+
* Підготовляє специфікацію consumer-а для створення
|
|
70
71
|
* @param {object} spec - специфікація consumer-а
|
|
71
|
-
* @
|
|
72
|
-
* @param {string[]} spec.filterSubjects - масив subject-ів для фільтрації
|
|
73
|
-
* @param {string} spec.deliverPolicy - політика доставки
|
|
74
|
-
* @param {string} spec.ackPolicy - політика підтвердження
|
|
75
|
-
* @returns {Promise<void>}
|
|
72
|
+
* @returns {object} підготовлена специфікація
|
|
76
73
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
function prepareSpec(spec) {
|
|
75
|
+
const preparedSpec = {
|
|
79
76
|
durable_name: spec.durableName,
|
|
80
77
|
ack_policy: spec.ackPolicy, // якщо не підтвердити повідомлення, воно буде повторно надіслано
|
|
81
|
-
filter_subjects: spec.filterSubjects
|
|
78
|
+
filter_subjects: spec.filterSubjects,
|
|
82
79
|
deliver_policy: spec.deliverPolicy // 'all' - всі непрочитані повідомлення
|
|
83
|
-
}
|
|
80
|
+
}
|
|
81
|
+
if (spec.ackWait) {
|
|
82
|
+
preparedSpec.ack_wait = nanos(spec.ackWait)
|
|
83
|
+
}
|
|
84
|
+
return preparedSpec
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Перевіряє наявність або створює JetStream stream.
|
|
88
|
+
* @param {string} streamName - назва stream
|
|
89
|
+
* @returns {Promise<void>}
|
|
90
|
+
*/
|
|
91
|
+
async function ensureStream(streamName) {
|
|
92
|
+
try {
|
|
93
|
+
const info = await jsm.streams.info(streamName)
|
|
94
|
+
// Перевіряємо, чи stream підтримує rollup
|
|
95
|
+
|
|
96
|
+
if (!info.config.allow_rollup_hdrs) {
|
|
97
|
+
log.warn(
|
|
98
|
+
`⚠️ Stream «${streamName}» не має увімкненого allow_rollup_hdrs. Для використання Nats-Rollup заголовка потрібно видалити stream і створити його заново з allow_rollup_hdrs: true`
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
log.info('✅ Stream already exists')
|
|
102
|
+
} catch {
|
|
103
|
+
await jsm.streams.add({
|
|
104
|
+
name: streamName,
|
|
105
|
+
subjects: [`${streamName}.>`],
|
|
106
|
+
retention: 'interest', // зберігає повідомлення поки є consumers або поки всі повідомлення не будуть прочитані
|
|
107
|
+
num_replicas: 1, // означає, що stream не реплікується — підходить для локальної розробки. Для продакшену в кластері зазвичай використовують 3 або 5.
|
|
108
|
+
storage: 'file', // 'memory'
|
|
109
|
+
allow_rollup_hdrs: true // дозволяє використання заголовка Nats-Rollup для автоматичного видалення старих повідомлень
|
|
110
|
+
})
|
|
111
|
+
log.info('✅ Stream created')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Порівнює два масиви
|
|
117
|
+
* @param {string[]} a - перший масив
|
|
118
|
+
* @param {string[]} b - другий масив
|
|
119
|
+
* @returns {boolean} - чи масиви рівні, true - рівні, false - не рівні
|
|
120
|
+
*/
|
|
121
|
+
const compareArrays = (a, b) => {
|
|
122
|
+
if (a.length !== b.length) return false
|
|
123
|
+
const setB = new Set(b)
|
|
124
|
+
return a.every(v => setB.has(v))
|
|
84
125
|
}
|
package/src/index.js
CHANGED
package/src/nats.js
CHANGED
|
@@ -61,7 +61,7 @@ if (env.NATS_FAKE_DATA) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
} else {
|
|
64
|
-
checkEnv(['NATS_URL'
|
|
64
|
+
checkEnv(['NATS_URL'])
|
|
65
65
|
|
|
66
66
|
// Connect to NATS
|
|
67
67
|
nc = await connect({ servers: env.NATS_URL })
|
|
@@ -72,6 +72,3 @@ if (env.NATS_FAKE_DATA) {
|
|
|
72
72
|
// Менеджер JetStream (для створення стрімів, конфігурацій тощо)
|
|
73
73
|
jsm = await jetstreamManager(nc)
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
// Stream name
|
|
77
|
-
export const stream = env.NATS_STREAM
|
package/src/pending-count.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsm
|
|
1
|
+
import { jsm } from './nats.js'
|
|
2
2
|
import { log } from '@nitra/pino'
|
|
3
3
|
import { env } from 'node:process'
|
|
4
|
+
import { stream } from './stream.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Повертає кількість непрочитаних (pending) повідомлень для durable consumer.
|
package/src/publish.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { log } from '@nitra/pino'
|
|
2
2
|
import { env } from 'node:process'
|
|
3
|
-
import { js
|
|
3
|
+
import { js } from './nats.js'
|
|
4
4
|
import { checkSubjectFormat } from './utils.js'
|
|
5
5
|
import { headers } from '@nats-io/nats-core'
|
|
6
6
|
import { createHash } from 'node:crypto'
|
|
7
|
+
import { stream } from './stream.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Публікує повідомлення у JetStream для вказаного subject.
|
|
@@ -19,8 +20,8 @@ export async function publish(subject, data, options = {}) {
|
|
|
19
20
|
return
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// перевіряємо чи subject відповідає формату project:subject
|
|
23
|
-
checkSubjectFormat(subject)
|
|
23
|
+
// перевіряємо чи subject відповідає формату stream.project:subject
|
|
24
|
+
checkSubjectFormat(stream, `${stream}.${subject}`)
|
|
24
25
|
|
|
25
26
|
log.debug('publish:', data)
|
|
26
27
|
const payload = JSON.stringify(data)
|
package/src/stream.js
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { checkEnv } from '@nitra/check-env'
|
|
2
|
+
import { env } from 'node:process'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @returns {Promise<void>}
|
|
8
|
-
*/
|
|
9
|
-
export async function ensureStream(streamName = stream) {
|
|
10
|
-
try {
|
|
11
|
-
const info = await jsm.streams.info(streamName)
|
|
12
|
-
// Перевіряємо, чи stream підтримує rollup
|
|
13
|
-
|
|
14
|
-
if (!info.config.allow_rollup_hdrs) {
|
|
15
|
-
log.warn(
|
|
16
|
-
`⚠️ Stream «${streamName}» не має увімкненого allow_rollup_hdrs. Для використання Nats-Rollup заголовка потрібно видалити stream і створити його заново з allow_rollup_hdrs: true`
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
log.debug('✅ Stream already exists')
|
|
20
|
-
} catch {
|
|
21
|
-
await jsm.streams.add({
|
|
22
|
-
name: streamName,
|
|
23
|
-
subjects: [`${streamName}.>`],
|
|
24
|
-
retention: 'interest', // зберігає повідомлення поки є consumers або поки всі повідомлення не будуть прочитані
|
|
25
|
-
num_replicas: 1, // означає, що stream не реплікується — підходить для локальної розробки. Для продакшену в кластері зазвичай використовують 3 або 5.
|
|
26
|
-
storage: 'file', // 'memory'
|
|
27
|
-
allow_rollup_hdrs: true // дозволяє використання заголовка Nats-Rollup для автоматичного видалення старих повідомлень
|
|
28
|
-
})
|
|
29
|
-
log.debug('✅ Stream created')
|
|
30
|
-
}
|
|
4
|
+
// якщо не задані демо дані, то перевіряємо чи задана назва stream
|
|
5
|
+
if (!env.NATS_FAKE_DATA) {
|
|
6
|
+
checkEnv(['NATS_STREAM'])
|
|
31
7
|
}
|
|
8
|
+
export const stream = env.NATS_STREAM
|
package/src/utils.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Перевіряє чи subject має правильний формат:
|
|
2
|
+
* Перевіряє чи subject має правильний формат: stream.project.subject
|
|
3
|
+
* @param {string} stream - назва stream
|
|
3
4
|
* @param {string} subject - subject для перевірки
|
|
4
5
|
* @throws {Error} - якщо subject не відповідає формату
|
|
5
6
|
*/
|
|
6
|
-
export const checkSubjectFormat = subject => {
|
|
7
|
-
const arr = subject
|
|
7
|
+
export const checkSubjectFormat = (stream, subject) => {
|
|
8
|
+
const arr = subject.split('.')
|
|
8
9
|
|
|
9
|
-
if (arr?.length
|
|
10
|
-
throw new Error('subject must be in the format:
|
|
10
|
+
if (arr?.length < 2 || !arr[0] || !arr[1]) {
|
|
11
|
+
throw new Error('subject must be in the format: stream.project:subject')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const theme = arr[1].split(':')
|
|
15
|
+
if (theme?.length < 2) {
|
|
16
|
+
throw new Error('subject must be in the format: stream.project:subject')
|
|
11
17
|
}
|
|
12
|
-
}
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
if (arr[0] !== stream) {
|
|
20
|
+
throw new Error(`${arr[0]} - stream must be - ${stream}`)
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/worker.js
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import { log } from '@nitra/pino'
|
|
2
2
|
import { env, exit } from 'node:process'
|
|
3
|
-
import { js
|
|
4
|
-
import {
|
|
3
|
+
import { js } from './nats.js'
|
|
4
|
+
import { stream } from './stream.js'
|
|
5
5
|
|
|
6
6
|
// Кешуємо результат перевірки фейкових даних
|
|
7
|
-
|
|
7
|
+
let FAKE_DATA
|
|
8
|
+
if (env.NATS_FAKE_DATA) {
|
|
9
|
+
try {
|
|
10
|
+
FAKE_DATA = JSON.parse(env.NATS_FAKE_DATA)
|
|
11
|
+
} catch (error) {
|
|
12
|
+
log.error('Failed to parse fake data', { error, data: env.NATS_FAKE_DATA })
|
|
13
|
+
throw new Error(`Invalid JSON in fake data: ${error.message}`)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
// Флаг для відстеження чи вже зареєстровані обробники подій
|
|
10
|
-
let handlersRegistered = false
|
|
18
|
+
// let handlersRegistered = false
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Обробник завершення процесу - відправляє повідомлення назад в чергу якщо воно не було підтверджене
|
|
14
22
|
*/
|
|
15
|
-
const handleExit = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
23
|
+
// const handleExit = () => {
|
|
24
|
+
// if (!state.isFinished && state.msg) {
|
|
25
|
+
// state.msg.nak()
|
|
26
|
+
// }
|
|
27
|
+
// }
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* Реєструє обробники подій для автоматичного NAK при завершенні процесу
|
|
23
31
|
*/
|
|
24
|
-
function registerExitHandlers() {
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
// function registerExitHandlers() {
|
|
33
|
+
// if (handlersRegistered) return
|
|
34
|
+
// handlersRegistered = true
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
36
|
+
// // Реєструємо хук на завершення процесу. якщо не підтвердили повідомлення або помилка - відправляємо повідомлення назад в чергу
|
|
37
|
+
// for (const signal of ['exit', 'SIGINT', 'uncaughtException', 'unhandledRejection']) {
|
|
38
|
+
// process.once(signal, handleExit)
|
|
39
|
+
// }
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
// state variables
|
|
43
|
+
// const state = { msg: null, isFinished: false }
|
|
44
|
+
let msg = null
|
|
33
45
|
|
|
34
46
|
/**
|
|
35
47
|
* Зчитує одне повідомлення з JetStream для вказаного consumer-а.
|
|
@@ -39,33 +51,53 @@ function registerExitHandlers() {
|
|
|
39
51
|
export async function read(consumer) {
|
|
40
52
|
// якщо задані демо дані, то повертаємо їх
|
|
41
53
|
if (FAKE_DATA) {
|
|
42
|
-
|
|
54
|
+
log.info('env.NATS_FAKE_DATA:', env.NATS_FAKE_DATA)
|
|
43
55
|
return FAKE_DATA
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
const consumerObj = await js.consumers.get(stream, consumer)
|
|
47
59
|
|
|
48
60
|
const iter = await consumerObj.fetch({ max_messages: 1, expires: 1000 })
|
|
49
|
-
for await (const
|
|
50
|
-
|
|
61
|
+
for await (const msgI of iter) {
|
|
62
|
+
msg = msgI
|
|
51
63
|
|
|
52
64
|
let decoded
|
|
53
65
|
try {
|
|
54
66
|
decoded = JSON.parse(msg.data)
|
|
55
67
|
} catch (error) {
|
|
56
68
|
log.error('Failed to parse message data', { error, data: msg.data })
|
|
57
|
-
msg.
|
|
69
|
+
msg.ack() // НЕ повертаємо повідомлення назад в чергу при помилці парсингу, бо зациклиться
|
|
58
70
|
throw new Error(`Invalid JSON in message: ${error.message}`)
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
log.debug('read msg', decoded)
|
|
62
74
|
|
|
63
75
|
// Реєструємо обробники подій один раз
|
|
64
|
-
registerExitHandlers()
|
|
76
|
+
// registerExitHandlers()
|
|
65
77
|
|
|
78
|
+
// Оброблюємо тільки перше повідомлення
|
|
66
79
|
return decoded
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
log.info(`${consumer} - no msg...`)
|
|
70
83
|
exit(0) // якщо не було жодного повідомлення
|
|
71
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Підтверджує (ack) останнє прочитане повідомлення.
|
|
88
|
+
* Якщо не викликати, повідомлення буде повернуто у чергу (nak).
|
|
89
|
+
* @returns {Promise<void>}
|
|
90
|
+
*/
|
|
91
|
+
export const finish = async () => {
|
|
92
|
+
// oxlint-disable-next-line require-await
|
|
93
|
+
return msg?.ack()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Повертає повідомлення назад в чергу (nak).
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
export const nak = async () => {
|
|
101
|
+
// oxlint-disable-next-line require-await
|
|
102
|
+
return msg?.nak()
|
|
103
|
+
}
|
package/src/finish.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { state } from './utils.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Підтверджує (ack) останнє прочитане повідомлення.
|
|
5
|
-
* Якщо не викликати, повідомлення буде повернуто у чергу (nak).
|
|
6
|
-
* @returns {Promise<void>}
|
|
7
|
-
*/
|
|
8
|
-
export const finish = async () => {
|
|
9
|
-
if (state.msg) {
|
|
10
|
-
await state.msg.ack()
|
|
11
|
-
state.isFinished = true
|
|
12
|
-
}
|
|
13
|
-
}
|