@nitra/nats 4.2.3 → 4.3.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/package.json +3 -3
- package/src/index.js +2 -1
- package/src/nats.js +17 -3
- package/src/pending-count.js +8 -1
- package/src/read-m.js +99 -0
- package/src/{worker.js → read.js} +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/nats",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "nats helper",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nats",
|
|
@@ -27,8 +27,8 @@
|
|
|
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
|
-
"@nitra/check-env": "^4.1.
|
|
31
|
-
"@nitra/pino": "^2.
|
|
30
|
+
"@nitra/check-env": "^4.1.1",
|
|
31
|
+
"@nitra/pino": "^2.12.0",
|
|
32
32
|
"js-yaml": "^4.1.1"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
package/src/index.js
CHANGED
package/src/nats.js
CHANGED
|
@@ -3,9 +3,6 @@ import { connect } from '@nats-io/transport-node'
|
|
|
3
3
|
import { jetstream, jetstreamManager } from '@nats-io/jetstream'
|
|
4
4
|
import { env } from 'node:process'
|
|
5
5
|
|
|
6
|
-
// AckPolicy
|
|
7
|
-
// export { AckPolicy } from '@nats-io/jetstream'
|
|
8
|
-
|
|
9
6
|
let nc
|
|
10
7
|
|
|
11
8
|
// Експортуємо JetStream контекст
|
|
@@ -13,6 +10,23 @@ export let js
|
|
|
13
10
|
// Експортуємо менеджер JetStream
|
|
14
11
|
export let jsm
|
|
15
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Припиняє приймати нові повідомлення для існуючих підписок (дренить підписки).
|
|
15
|
+
* Дає дочитати/доробити те, що вже “в дорозі”/в черзі (залежить від типу підписки й вашого хендлера).
|
|
16
|
+
* Флашить (дочікується відправки) вже ініційовані публікації.
|
|
17
|
+
* Після цього закриває з’єднання.
|
|
18
|
+
* Для CLI-скриптів це потрібно, інакше процес буде "висіти" через відкритий сокет.
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
export async function closeNats() {
|
|
22
|
+
// якщо фейковий режим або ще не підключались — нічого закривати
|
|
23
|
+
if (!nc) return
|
|
24
|
+
|
|
25
|
+
await nc.drain()
|
|
26
|
+
|
|
27
|
+
return nc.closed()
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
// якщо задані демо дані, то використовуємо фейковий NATS
|
|
17
31
|
if (env.NATS_FAKE_DATA) {
|
|
18
32
|
// Створюємо фейкові об'єкти для js та jsm
|
package/src/pending-count.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsm } from './nats.js'
|
|
|
2
2
|
import { log } from '@nitra/pino'
|
|
3
3
|
import { env } from 'node:process'
|
|
4
4
|
import { stream } from './stream.js'
|
|
5
|
+
import { ClosedConnectionError } from '@nats-io/nats-core'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Повертає кількість непрочитаних (pending) повідомлень для durable consumer.
|
|
@@ -17,7 +18,13 @@ export async function getPendingCount(consumer) {
|
|
|
17
18
|
try {
|
|
18
19
|
const info = await jsm.consumers.info(stream, consumer)
|
|
19
20
|
return info.num_pending + info.num_ack_pending
|
|
20
|
-
} catch {
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// Перевірити що це ClosedConnectionError
|
|
23
|
+
if (error instanceof ClosedConnectionError) {
|
|
24
|
+
log.error('getPendingCount: nats connection already closed')
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
log.error(`consumer ${consumer} not found`)
|
|
22
29
|
return 0
|
|
23
30
|
}
|
package/src/read-m.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { log } from '@nitra/pino'
|
|
2
|
+
import { env, exit } from 'node:process'
|
|
3
|
+
import { js, closeNats } from './nats.js'
|
|
4
|
+
import { stream } from './stream.js'
|
|
5
|
+
|
|
6
|
+
// Кешуємо результат перевірки фейкових даних
|
|
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
|
+
}
|
|
16
|
+
|
|
17
|
+
// state variables
|
|
18
|
+
let msgs = []
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Зчитує N повідомлень з JetStream для вказаного consumer-а.
|
|
22
|
+
* @param {string} consumer - durable_name для consumer(за замовчуванням durable_name = subject)
|
|
23
|
+
* @param {number} count - кількість повідомлень, яку треба зчитати
|
|
24
|
+
* @returns {Promise<object[]>} - масив декодованих даних повідомлень
|
|
25
|
+
*/
|
|
26
|
+
export async function readM(consumer, count = 1) {
|
|
27
|
+
if (!Number.isFinite(count) || count <= 0) {
|
|
28
|
+
throw new Error(`Invalid count: ${count}. Must be a positive number.`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// скидаємо попередній стан (якщо readM викликають повторно без finishM/nakM)
|
|
32
|
+
msgs = []
|
|
33
|
+
|
|
34
|
+
// якщо задані демо дані, то повертаємо їх
|
|
35
|
+
if (FAKE_DATA) {
|
|
36
|
+
log.info('env.NATS_FAKE_DATA:', env.NATS_FAKE_DATA)
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(FAKE_DATA)) return FAKE_DATA.slice(0, count)
|
|
39
|
+
|
|
40
|
+
// якщо передали не масив — повторюємо один і той самий payload N разів
|
|
41
|
+
return Array.from({ length: count }, () => FAKE_DATA)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const consumerObj = await js.consumers.get(stream, consumer)
|
|
45
|
+
|
|
46
|
+
// час очікування (в мс), протягом якого fetch() буде чекати повідомлення перед тим, як завершити ітератор без повідомлень.
|
|
47
|
+
const iter = await consumerObj.fetch({ max_messages: count, expires: 2000 })
|
|
48
|
+
|
|
49
|
+
const decodedMessages = []
|
|
50
|
+
for await (const msg of iter) {
|
|
51
|
+
msgs.push(msg)
|
|
52
|
+
|
|
53
|
+
let decoded
|
|
54
|
+
try {
|
|
55
|
+
decoded = JSON.parse(msg.data)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
log.error('Failed to parse message data', { error, data: msg.data })
|
|
58
|
+
msg.ack() // НЕ повертаємо повідомлення назад в чергу при помилці парсингу, бо зациклиться
|
|
59
|
+
throw new Error(`Invalid JSON in message: ${error.message}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
decodedMessages.push(decoded)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!decodedMessages.length) {
|
|
66
|
+
log.info(`${consumer} - no msg...`)
|
|
67
|
+
exit(0) // якщо не було жодного повідомлення
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (decodedMessages.length < count) {
|
|
71
|
+
log.info(`${consumer} - read ${decodedMessages.length}/${count} msg(s)`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return decodedMessages
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Підтверджує (ack) всі прочитані повідомлення.
|
|
79
|
+
* Якщо не викликати, повідомлення будуть повернуті у чергу (nak) після таймауту.
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
*/
|
|
82
|
+
export const finishM = async () => {
|
|
83
|
+
// oxlint-disable-next-line require-await
|
|
84
|
+
for (const msg of msgs) msg?.ack()
|
|
85
|
+
msgs = []
|
|
86
|
+
return closeNats()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Повертає всі прочитані повідомлення назад в чергу (nak).
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
export const nakM = async () => {
|
|
94
|
+
// oxlint-disable-next-line require-await
|
|
95
|
+
for (const msg of msgs) msg?.nak()
|
|
96
|
+
msgs = []
|
|
97
|
+
|
|
98
|
+
return closeNats()
|
|
99
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { log } from '@nitra/pino'
|
|
2
2
|
import { env, exit } from 'node:process'
|
|
3
|
-
import { js } from './nats.js'
|
|
3
|
+
import { js, closeNats } from './nats.js'
|
|
4
4
|
import { stream } from './stream.js'
|
|
5
5
|
|
|
6
6
|
// Кешуємо результат перевірки фейкових даних
|
|
@@ -57,7 +57,8 @@ export async function read(consumer) {
|
|
|
57
57
|
|
|
58
58
|
const consumerObj = await js.consumers.get(stream, consumer)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// час очікування (в мс), протягом якого fetch() буде чекати повідомлення перед тим, як завершити ітератор без повідомлень.
|
|
61
|
+
const iter = await consumerObj.fetch({ max_messages: 1, expires: 2000 })
|
|
61
62
|
for await (const msgI of iter) {
|
|
62
63
|
msg = msgI
|
|
63
64
|
|
|
@@ -80,6 +81,7 @@ export async function read(consumer) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
log.info(`${consumer} - no msg...`)
|
|
84
|
+
await closeNats()
|
|
83
85
|
exit(0) // якщо не було жодного повідомлення
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -89,8 +91,8 @@ export async function read(consumer) {
|
|
|
89
91
|
* @returns {Promise<void>}
|
|
90
92
|
*/
|
|
91
93
|
export const finish = async () => {
|
|
92
|
-
|
|
93
|
-
return
|
|
94
|
+
await msg?.ack()
|
|
95
|
+
return closeNats()
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/**
|
|
@@ -98,6 +100,6 @@ export const finish = async () => {
|
|
|
98
100
|
* @returns {Promise<void>}
|
|
99
101
|
*/
|
|
100
102
|
export const nak = async () => {
|
|
101
|
-
|
|
102
|
-
return
|
|
103
|
+
await msg?.nak()
|
|
104
|
+
return closeNats()
|
|
103
105
|
}
|