@rshval/back-kit 1.1.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 +538 -0
- package/dist/api.d.ts +17 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +51 -0
- package/dist/api.js.map +1 -0
- package/dist/audit-log.d.ts +36 -0
- package/dist/audit-log.d.ts.map +1 -0
- package/dist/audit-log.js +45 -0
- package/dist/audit-log.js.map +1 -0
- package/dist/cache-middleware.d.ts +27 -0
- package/dist/cache-middleware.d.ts.map +1 -0
- package/dist/cache-middleware.js +72 -0
- package/dist/cache-middleware.js.map +1 -0
- package/dist/cache.d.ts +24 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +76 -0
- package/dist/cache.js.map +1 -0
- package/dist/database.d.ts +24 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +90 -0
- package/dist/database.js.map +1 -0
- package/dist/email.d.ts +34 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +26 -0
- package/dist/email.js.map +1 -0
- package/dist/global.d.ts +24 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/global.js +20 -0
- package/dist/global.js.map +1 -0
- package/dist/helpers.d.ts +16 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +19 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/mail-template.d.ts +41 -0
- package/dist/mail-template.d.ts.map +1 -0
- package/dist/mail-template.js +51 -0
- package/dist/mail-template.js.map +1 -0
- package/dist/paymaster.d.ts +51 -0
- package/dist/paymaster.d.ts.map +1 -0
- package/dist/paymaster.js +127 -0
- package/dist/paymaster.js.map +1 -0
- package/dist/seed.d.ts +15 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +57 -0
- package/dist/seed.js.map +1 -0
- package/dist/site-helpers.d.ts +15 -0
- package/dist/site-helpers.d.ts.map +1 -0
- package/dist/site-helpers.js +21 -0
- package/dist/site-helpers.js.map +1 -0
- package/dist/socket-client.d.ts +19 -0
- package/dist/socket-client.d.ts.map +1 -0
- package/dist/socket-client.js +66 -0
- package/dist/socket-client.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +6 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +26 -0
- package/dist/validation.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# @rshval/back-kit
|
|
2
|
+
|
|
3
|
+
Публичный npm-пакет `@rshval/back-kit` с серверными утилитами для Node.js/TypeScript проектов.
|
|
4
|
+
|
|
5
|
+
> Репозиторий используется для внутренних проектов автора. Любое использование третьими лицами выполняется на их страх и риск.
|
|
6
|
+
|
|
7
|
+
## Установка
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @rshval/back-kit
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Пакет ESM-only (`"type": "module"`). Для CommonJS используйте динамический `import()`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Все экспортируемые функции и примеры использования
|
|
18
|
+
|
|
19
|
+
Ниже перечислены все публичные экспорты из пакета (через `src/index.ts`) и короткие примеры.
|
|
20
|
+
|
|
21
|
+
### 1) Email
|
|
22
|
+
|
|
23
|
+
#### `createMailOptions({ from, to, subject, text })`
|
|
24
|
+
Создаёт объект параметров письма для nodemailer. Поддерживает `to` как строку (обычная отправка) или объект формы (`MailOptionsBody`) для заявок/обратной связи.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createMailOptions } from '@rshval/back-kit'
|
|
28
|
+
|
|
29
|
+
const options = createMailOptions({
|
|
30
|
+
from: 'robot@example.com',
|
|
31
|
+
to: 'user@example.com',
|
|
32
|
+
subject: 'Hello',
|
|
33
|
+
text: 'Welcome!'
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### `sendEmailWithConfig({ nodemailerConfig, to, subject, text })`
|
|
38
|
+
Создаёт transport через nodemailer, отправляет письмо и возвращает строку с результатом отправки.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { sendEmailWithConfig } from '@rshval/back-kit'
|
|
42
|
+
|
|
43
|
+
await sendEmailWithConfig({
|
|
44
|
+
nodemailerConfig: {
|
|
45
|
+
host: 'smtp.example.com',
|
|
46
|
+
port: 465,
|
|
47
|
+
secure: true,
|
|
48
|
+
auth: { user: 'robot@example.com', pass: '***' }
|
|
49
|
+
},
|
|
50
|
+
to: 'user@example.com',
|
|
51
|
+
subject: 'Reset password',
|
|
52
|
+
text: 'Code: 123456'
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 2) Конфиг и JWT
|
|
59
|
+
|
|
60
|
+
#### `getBaseUrlByConfig(config, baseUrl?)`
|
|
61
|
+
Строит base URL по конфигу приложения. В `development` добавляет `:port`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { getBaseUrlByConfig } from '@rshval/back-kit'
|
|
65
|
+
|
|
66
|
+
const base = getBaseUrlByConfig(
|
|
67
|
+
{
|
|
68
|
+
NODE_ENV: 'development',
|
|
69
|
+
server: { domain: 'localhost', port: 3000 },
|
|
70
|
+
jwt: {}
|
|
71
|
+
},
|
|
72
|
+
'/api'
|
|
73
|
+
)
|
|
74
|
+
// //localhost:3000/api
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### `createTokenByConfig({ config, user, expiresIn })`
|
|
78
|
+
Создаёт JWT-токен на основе `config.jwt.JWT_KEY` (или `JWT_KEY_NO_ENV`).
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createTokenByConfig } from '@rshval/back-kit'
|
|
82
|
+
|
|
83
|
+
const token = createTokenByConfig({
|
|
84
|
+
config: {
|
|
85
|
+
server: { domain: 'example.com' },
|
|
86
|
+
jwt: { JWT_KEY: 'super-secret' }
|
|
87
|
+
},
|
|
88
|
+
user: { _id: '64a...' },
|
|
89
|
+
expiresIn: '7d'
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 3) Вспомогательные утилиты
|
|
96
|
+
|
|
97
|
+
#### `createPinCode(min?, max?)`
|
|
98
|
+
Генерирует случайный числовой PIN в диапазоне (`10000..99990` по умолчанию).
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { createPinCode } from '@rshval/back-kit'
|
|
102
|
+
|
|
103
|
+
const pin = createPinCode()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `getIp(req)`
|
|
107
|
+
Пытается определить IP клиента из `req.ip`, сокетов и `x-forwarded-for`.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { getIp } from '@rshval/back-kit'
|
|
111
|
+
|
|
112
|
+
const ip = await getIp(req)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `translitUrl(str)`
|
|
116
|
+
Транслитерирует строку в URL-friendly slug (`-`, lower-case).
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { translitUrl } from '@rshval/back-kit'
|
|
120
|
+
|
|
121
|
+
const slug = translitUrl('Пример страницы')
|
|
122
|
+
// primer-stranicy
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### 4) Валидация
|
|
128
|
+
|
|
129
|
+
#### `patternEmail()`
|
|
130
|
+
Возвращает RegExp для проверки email.
|
|
131
|
+
|
|
132
|
+
#### `patternPassword()`
|
|
133
|
+
Возвращает RegExp для пароля (минимум 8 символов, буквы + цифры).
|
|
134
|
+
|
|
135
|
+
#### `isValidEmail(val)`
|
|
136
|
+
Проверяет корректность email.
|
|
137
|
+
|
|
138
|
+
#### `isValidPhoneNumber(val)`
|
|
139
|
+
Проверяет номер телефона через `libphonenumber-js`.
|
|
140
|
+
|
|
141
|
+
#### `isValidCode(code, length)`
|
|
142
|
+
Проверяет длину кода.
|
|
143
|
+
|
|
144
|
+
#### `isEmpty(val)`
|
|
145
|
+
Проверяет пустую строку (`trim`) или пустой объект.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import {
|
|
149
|
+
patternEmail,
|
|
150
|
+
patternPassword,
|
|
151
|
+
isValidEmail,
|
|
152
|
+
isValidPhoneNumber,
|
|
153
|
+
isValidCode,
|
|
154
|
+
isEmpty
|
|
155
|
+
} from '@rshval/back-kit'
|
|
156
|
+
|
|
157
|
+
const emailRegex = patternEmail()
|
|
158
|
+
const passwordRegex = patternPassword()
|
|
159
|
+
|
|
160
|
+
isValidEmail('user@example.com') // true
|
|
161
|
+
isValidPhoneNumber('+79991234567') // true/false
|
|
162
|
+
isValidCode('123456', 6) // true
|
|
163
|
+
isEmpty(' ') // true
|
|
164
|
+
isEmpty({}) // true
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### 5) Билдеры (привязка к конфигу)
|
|
170
|
+
|
|
171
|
+
#### `buildGetBaseUrl(config)`
|
|
172
|
+
Возвращает функцию `(baseUrl?) => string`.
|
|
173
|
+
|
|
174
|
+
#### `buildCreateToken(config)`
|
|
175
|
+
Возвращает функцию создания JWT с уже «зашитым» конфигом.
|
|
176
|
+
|
|
177
|
+
#### `buildSendEmail(nodemailerConfig)`
|
|
178
|
+
Возвращает функцию отправки email с уже «зашитым» SMTP-конфигом.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import {
|
|
182
|
+
buildGetBaseUrl,
|
|
183
|
+
buildCreateToken,
|
|
184
|
+
buildSendEmail
|
|
185
|
+
} from '@rshval/back-kit'
|
|
186
|
+
|
|
187
|
+
const getBaseUrl = buildGetBaseUrl({
|
|
188
|
+
NODE_ENV: 'production',
|
|
189
|
+
server: { domain: 'example.com' },
|
|
190
|
+
jwt: { JWT_KEY: 'secret' }
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const createToken = buildCreateToken({
|
|
194
|
+
server: { domain: 'example.com' },
|
|
195
|
+
jwt: { JWT_KEY: 'secret' }
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const sendEmail = buildSendEmail({
|
|
199
|
+
host: 'smtp.example.com',
|
|
200
|
+
port: 465,
|
|
201
|
+
secure: true,
|
|
202
|
+
auth: { user: 'robot@example.com', pass: '***' }
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### 6) База данных
|
|
209
|
+
|
|
210
|
+
#### `startMongoDatabase(options)`
|
|
211
|
+
Запускает подключение к MongoDB через mongoose, логирует статусы и умеет ретраить подключение.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { startMongoDatabase } from '@rshval/back-kit'
|
|
215
|
+
|
|
216
|
+
await startMongoDatabase({
|
|
217
|
+
config: {
|
|
218
|
+
name: 'main',
|
|
219
|
+
connect: process.env.MONGO_URI,
|
|
220
|
+
params: {}
|
|
221
|
+
},
|
|
222
|
+
logger: console
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### 7) Кэш
|
|
229
|
+
|
|
230
|
+
#### `createCacheService(options?)`
|
|
231
|
+
Создаёт LRU cache-сервис с методами:
|
|
232
|
+
- `get(key)`
|
|
233
|
+
- `set(key, data, compareKey?, compareValue?, ttlMs?)`
|
|
234
|
+
- `delete(key)`
|
|
235
|
+
- `getId(val)`
|
|
236
|
+
- `keys()`
|
|
237
|
+
- `clear()`
|
|
238
|
+
- `entries()` *(если `exposeEntries: true`)*
|
|
239
|
+
- `values()` *(если `exposeValues: true`)*
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { createCacheService } from '@rshval/back-kit'
|
|
243
|
+
|
|
244
|
+
const cache = createCacheService({ ttl: 60_000 })
|
|
245
|
+
const key = cache.getId({ service: 'users', page: 1 })
|
|
246
|
+
await cache.set(key, [{ s: 'state', data: [1, 2, 3] }])
|
|
247
|
+
const value = await cache.get(key)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### `createCacheMiddleware({ cache, ... })`
|
|
251
|
+
Создаёт middleware-обёртку над cache-сервисом с методами:
|
|
252
|
+
- `get(id)`
|
|
253
|
+
- `set(id, data, expDataTime)`
|
|
254
|
+
- `del(id)`
|
|
255
|
+
- `delByPrefix(prefix)` *(если `includeDelByPrefix: true`)*
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { createCacheService, createCacheMiddleware } from '@rshval/back-kit'
|
|
259
|
+
|
|
260
|
+
const cache = createCacheService({ supportTtlInSet: true })
|
|
261
|
+
const cm = createCacheMiddleware({
|
|
262
|
+
cache,
|
|
263
|
+
passTtlToCacheSet: true,
|
|
264
|
+
includeDelByPrefix: true
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await cm.set('users:list', [{ id: 1 }], 60_000)
|
|
268
|
+
const cached = await cm.get('users:list')
|
|
269
|
+
await cm.delByPrefix?.('users:')
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### 8) Логирование
|
|
275
|
+
|
|
276
|
+
#### `createLoggerService({ rootdir? })`
|
|
277
|
+
Возвращает фабрику логгеров. Для каждого namespace (`std`) пишет:
|
|
278
|
+
- `logs/<std>/stdout.log`
|
|
279
|
+
- `logs/<std>/stderr.log`
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { createLoggerService } from '@rshval/back-kit'
|
|
283
|
+
|
|
284
|
+
const makeLogger = createLoggerService({ rootdir: process.cwd() })
|
|
285
|
+
const logger = makeLogger('api')
|
|
286
|
+
logger.log('started')
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### 9) WebSocket клиент
|
|
292
|
+
|
|
293
|
+
#### `createSocketClientService(options)`
|
|
294
|
+
Создаёт сервис клиента `socket.io` с методами:
|
|
295
|
+
- `doSocketClient()` — старт подключения
|
|
296
|
+
- `getSocketClient()` — вернуть текущий инстанс
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { createSocketClientService } from '@rshval/back-kit'
|
|
300
|
+
|
|
301
|
+
const ws = createSocketClientService({
|
|
302
|
+
wsName: 'worker',
|
|
303
|
+
socketBase: 'https://example.com',
|
|
304
|
+
logger: console,
|
|
305
|
+
runWorkers: () => {
|
|
306
|
+
// jobs
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
ws.doSocketClient()
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
### 10) Paymaster
|
|
316
|
+
|
|
317
|
+
#### `createPaymasterService({ paymaster, clientServer, serverBaseUrl })`
|
|
318
|
+
Создаёт сервис с методами:
|
|
319
|
+
- `createPaymentLink({...})` — сформировать ссылку на оплату
|
|
320
|
+
- `validateCallback(payload, rawBody?)` — проверить подпись callback
|
|
321
|
+
- `parseCallback(payload)` — нормализовать callback в удобный объект
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { createPaymasterService } from '@rshval/back-kit'
|
|
325
|
+
|
|
326
|
+
const paymaster = createPaymasterService({
|
|
327
|
+
paymaster: {
|
|
328
|
+
merchantId: 'merchant-id',
|
|
329
|
+
secretKey: 'secret',
|
|
330
|
+
checkoutUrl: 'https://paymaster.ru/Payment/Init',
|
|
331
|
+
checkSignature: true
|
|
332
|
+
},
|
|
333
|
+
clientServer: 'https://site.example.com',
|
|
334
|
+
serverBaseUrl: 'https://api.example.com'
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const link = paymaster.createPaymentLink({
|
|
338
|
+
requestId: 'order_123',
|
|
339
|
+
amount: 1499,
|
|
340
|
+
description: 'Order #123'
|
|
341
|
+
})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### 11) Почтовые шаблоны
|
|
347
|
+
|
|
348
|
+
#### `createMailTemplateService({ findTemplate, sendEmail })`
|
|
349
|
+
Создаёт сервис шаблонов email с методами:
|
|
350
|
+
- `extractVariables(template)` — список `{{variables}}`
|
|
351
|
+
- `renderTemplate(template, context?)` — рендер subject/body
|
|
352
|
+
- `sendByKey({ key, to, context? })` — найти шаблон, отрендерить и отправить
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { createMailTemplateService } from '@rshval/back-kit'
|
|
356
|
+
|
|
357
|
+
const mailTemplates = createMailTemplateService({
|
|
358
|
+
findTemplate: async ({ key }) =>
|
|
359
|
+
key === 'welcome'
|
|
360
|
+
? { subject: 'Hi, {{user.name}}', bodyText: 'Hello {{user.name}}!' }
|
|
361
|
+
: null,
|
|
362
|
+
sendEmail: async (to, subject, text) => {
|
|
363
|
+
console.log('send', to, subject, text)
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
await mailTemplates.sendByKey({
|
|
368
|
+
key: 'welcome',
|
|
369
|
+
to: 'user@example.com',
|
|
370
|
+
context: { user: { name: 'Alex' } }
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### 12) Аудит изменений
|
|
377
|
+
|
|
378
|
+
#### `buildAuditChanges({ beforeData, afterData, fieldsToCheck, protectedFields? })`
|
|
379
|
+
Сравнивает данные «до/после» и возвращает массив изменений по выбранным полям.
|
|
380
|
+
|
|
381
|
+
#### `createAuditLog({ ..., save })`
|
|
382
|
+
Создаёт запись аудита через переданную функцию `save`. Если изменений нет — возвращает `null`.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { buildAuditChanges, createAuditLog } from '@rshval/back-kit'
|
|
386
|
+
|
|
387
|
+
const changes = buildAuditChanges({
|
|
388
|
+
beforeData: { name: 'Old', role: 'user' },
|
|
389
|
+
afterData: { name: 'New', role: 'user' },
|
|
390
|
+
fieldsToCheck: ['name', 'role'],
|
|
391
|
+
protectedFields: ['role']
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
await createAuditLog({
|
|
395
|
+
entityType: 'user',
|
|
396
|
+
entityId: '507f1f77bcf86cd799439011',
|
|
397
|
+
action: 'update',
|
|
398
|
+
changedBy: '507f191e810c19729de860ea',
|
|
399
|
+
changes,
|
|
400
|
+
save: async (payload) => payload
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
### 13) Seed-функции
|
|
407
|
+
|
|
408
|
+
#### `createSeedFunctions({ cache, retryDelayMs? })`
|
|
409
|
+
Возвращает методы:
|
|
410
|
+
- `checkDatabaseIsConnected()` — проверяет состояние БД через кэш
|
|
411
|
+
- `setSeedData(SeedModel, arr)` — добавляет seed-данные после подтверждения подключения
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { createSeedFunctions, createCacheService } from '@rshval/back-kit'
|
|
415
|
+
|
|
416
|
+
const cache = createCacheService()
|
|
417
|
+
const { setSeedData } = createSeedFunctions({ cache })
|
|
418
|
+
|
|
419
|
+
// await setSeedData(UserModel, [{ name: 'Admin' }])
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### 14) API-клиент
|
|
425
|
+
|
|
426
|
+
#### `createApiService({ userAgent, logger? })`
|
|
427
|
+
Возвращает HTTP-клиент с методами:
|
|
428
|
+
- `get(path, token?)`
|
|
429
|
+
- `getXml(path, token?)`
|
|
430
|
+
- `del(path, token?)`
|
|
431
|
+
- `post(path, data, token?, xml?, contentType?)`
|
|
432
|
+
- `put(path, data, token)`
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { createApiService } from '@rshval/back-kit'
|
|
436
|
+
|
|
437
|
+
const api = createApiService({ userAgent: 'my-service/1.0.0', logger: console })
|
|
438
|
+
|
|
439
|
+
const users = await api.get('https://api.example.com/users')
|
|
440
|
+
const created = await api.post('https://api.example.com/users', { name: 'Alex' })
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Также экспортируется тип токена:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
import type { Token } from '@rshval/back-kit'
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### 15) Прочие экспортированные утилиты
|
|
452
|
+
|
|
453
|
+
#### `add(a, b)`
|
|
454
|
+
Возвращает сумму двух чисел.
|
|
455
|
+
|
|
456
|
+
#### `test2()`
|
|
457
|
+
Логирует `99` и возвращает `8`.
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
import { add, test2 } from '@rshval/back-kit'
|
|
461
|
+
|
|
462
|
+
add(2, 3) // 5
|
|
463
|
+
test2() // 8
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Подготовка к публикации в npm
|
|
469
|
+
|
|
470
|
+
### 1) Требования
|
|
471
|
+
|
|
472
|
+
- Node.js 18+
|
|
473
|
+
- npm 9+
|
|
474
|
+
- доступ к npm-аккаунту, имеющему право публиковать пакет `@rshval/back-kit`
|
|
475
|
+
|
|
476
|
+
### 2) Сборка и проверка содержимого публикации
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
npm install
|
|
480
|
+
npm run build
|
|
481
|
+
npm run pack:dry
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
`npm run pack:dry` показывает, какие файлы реально попадут в npm-пакет.
|
|
485
|
+
|
|
486
|
+
### 3) Логин в npm
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
npm login
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Проверить текущего пользователя:
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
npm whoami
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 4) Публикация
|
|
499
|
+
|
|
500
|
+
1. Обновите версию:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
npm version patch
|
|
504
|
+
# или minor / major
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
2. Опубликуйте пакет:
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
npm publish
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> Пакет публикуется как `public` (см. `publishConfig.access`).
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
> Если при `npm publish` появляется ошибка `E403` с текстом
|
|
517
|
+
> `Two-factor authentication or granular access token with bypass 2fa enabled is required`,
|
|
518
|
+
> значит текущий токен не подходит для публикации.
|
|
519
|
+
>
|
|
520
|
+
> Варианты решения:
|
|
521
|
+
> - выполните `npm login --auth-type=web` и подтвердите OTP при публикации;
|
|
522
|
+
> - или создайте **granular access token** на npmjs.com с правами `read and write`
|
|
523
|
+
> для пакета и включённым `bypass 2FA for publishing`, затем обновите токен локально
|
|
524
|
+
> через `npm logout && npm login` (или настройкой в `.npmrc`).
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## Разработка (standalone)
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
npm install
|
|
532
|
+
npm run build
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Экспорты
|
|
536
|
+
|
|
537
|
+
- ESM: `dist/index.js`
|
|
538
|
+
- Типы: `dist/index.d.ts`
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Token {
|
|
2
|
+
type: 'Token' | 'Bearer';
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
interface CreateApiServiceOptions {
|
|
6
|
+
userAgent: string;
|
|
7
|
+
logger?: Pick<Console, 'error'>;
|
|
8
|
+
}
|
|
9
|
+
export declare const createApiService: ({ userAgent, logger, }: CreateApiServiceOptions) => {
|
|
10
|
+
get: (path: string, token?: Token) => Promise<any>;
|
|
11
|
+
getXml: (path: string, token?: Token) => Promise<any>;
|
|
12
|
+
del: (path: string, token?: Token) => Promise<any>;
|
|
13
|
+
post: (path: string, data: Record<string, unknown>, token?: Token, xml?: boolean, contentType?: string) => Promise<any>;
|
|
14
|
+
put: (path: string, data: Record<string, unknown>, token: Token) => Promise<any>;
|
|
15
|
+
};
|
|
16
|
+
export type { Token };
|
|
17
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,uBAAuB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CACjC;AAWD,eAAO,MAAM,gBAAgB,GAAI,wBAG9B,uBAAuB;gBA4DV,MAAM,UAAU,KAAK;mBAClB,MAAM,UAAU,KAAK;gBAExB,MAAM,UAAU,KAAK;iBAGzB,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UACrB,KAAK,QACP,OAAO,gBACC,MAAM;gBAEV,MAAM,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,KAAK;CAGlE,CAAC;AAEF,YAAY,EAAE,KAAK,EAAE,CAAC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const createApiService = ({ userAgent, logger, }) => {
|
|
2
|
+
const send = async ({ method, path, data, token, xml, contentType, }) => {
|
|
3
|
+
const requestHeaders = new Headers();
|
|
4
|
+
requestHeaders.set('User-Agent', userAgent);
|
|
5
|
+
if (token) {
|
|
6
|
+
requestHeaders.set('Authorization', ` ${token.type} ${token.key}`);
|
|
7
|
+
}
|
|
8
|
+
let body;
|
|
9
|
+
if (data) {
|
|
10
|
+
requestHeaders.set('Content-Type', contentType ? contentType : 'application/json');
|
|
11
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
12
|
+
body = new URLSearchParams(data);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
body = JSON.stringify(data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const opts = {
|
|
19
|
+
method,
|
|
20
|
+
headers: requestHeaders,
|
|
21
|
+
body,
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(path, opts);
|
|
25
|
+
if (res.ok || res.status === 200 || res.status === 422) {
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
if (xml) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return text ? JSON.parse(text) : {};
|
|
31
|
+
}
|
|
32
|
+
logger?.error('[' + new Date() + '] api error', res.status);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger?.error('[' + new Date() + '] error in API, options: ' + opts);
|
|
37
|
+
logger?.error(error);
|
|
38
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
39
|
+
console.log('request was aborted');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
get: (path, token) => send({ method: 'GET', path, token }),
|
|
45
|
+
getXml: (path, token) => send({ method: 'GET', path, token, xml: true }),
|
|
46
|
+
del: (path, token) => send({ method: 'DELETE', path, token }),
|
|
47
|
+
post: (path, data, token, xml, contentType) => send({ method: 'POST', path, data, token, xml, contentType }),
|
|
48
|
+
put: (path, data, token) => send({ method: 'PUT', path, data, token }),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
export declare const buildAuditChanges: ({ beforeData, afterData, fieldsToCheck, protectedFields, }: {
|
|
3
|
+
beforeData: Record<string, unknown>;
|
|
4
|
+
afterData: Record<string, unknown>;
|
|
5
|
+
fieldsToCheck: string[];
|
|
6
|
+
protectedFields?: string[];
|
|
7
|
+
}) => {
|
|
8
|
+
field: string;
|
|
9
|
+
before: unknown;
|
|
10
|
+
after: unknown;
|
|
11
|
+
}[];
|
|
12
|
+
export declare const createAuditLog: ({ entityType, entityId, action, changedBy, changes, meta, save, }: {
|
|
13
|
+
entityType: string;
|
|
14
|
+
entityId: mongoose.Types.ObjectId | string;
|
|
15
|
+
action: string;
|
|
16
|
+
changedBy?: mongoose.Types.ObjectId | string;
|
|
17
|
+
changes: Array<{
|
|
18
|
+
field: string;
|
|
19
|
+
before: unknown;
|
|
20
|
+
after: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
save: (payload: {
|
|
24
|
+
entityType: string;
|
|
25
|
+
entityId: mongoose.Types.ObjectId | string;
|
|
26
|
+
action: string;
|
|
27
|
+
changedBy?: mongoose.Types.ObjectId | string;
|
|
28
|
+
changes: Array<{
|
|
29
|
+
field: string;
|
|
30
|
+
before: unknown;
|
|
31
|
+
after: unknown;
|
|
32
|
+
}>;
|
|
33
|
+
meta?: Record<string, unknown>;
|
|
34
|
+
}) => Promise<unknown>;
|
|
35
|
+
}) => Promise<unknown>;
|
|
36
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AA6BhC,eAAO,MAAM,iBAAiB,GAAI,4DAK/B;IACD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;;;;GAWA,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,mEAQlC;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;IAC7C,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,OAAO,EAAE;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;QAC3C,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;QAC7C,OAAO,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QACnE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB,qBAaA,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
const normalizeValue = (value) => {
|
|
3
|
+
if (value instanceof mongoose.Types.ObjectId) {
|
|
4
|
+
return value.toString();
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map((item) => normalizeValue(item));
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object') {
|
|
10
|
+
const doc = value;
|
|
11
|
+
if (typeof doc.toObject === 'function') {
|
|
12
|
+
return normalizeValue(doc.toObject());
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
const valuesAreEqual = (left, right) => {
|
|
18
|
+
return (JSON.stringify(normalizeValue(left)) ===
|
|
19
|
+
JSON.stringify(normalizeValue(right)));
|
|
20
|
+
};
|
|
21
|
+
export const buildAuditChanges = ({ beforeData, afterData, fieldsToCheck, protectedFields = [], }) => {
|
|
22
|
+
const protectedSet = new Set(protectedFields);
|
|
23
|
+
return fieldsToCheck
|
|
24
|
+
.filter((field) => !protectedSet.has(field))
|
|
25
|
+
.filter((field) => !valuesAreEqual(beforeData[field], afterData[field]))
|
|
26
|
+
.map((field) => ({
|
|
27
|
+
field,
|
|
28
|
+
before: normalizeValue(beforeData[field]),
|
|
29
|
+
after: normalizeValue(afterData[field]),
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
32
|
+
export const createAuditLog = async ({ entityType, entityId, action, changedBy, changes, meta, save, }) => {
|
|
33
|
+
if (!changes.length) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return save({
|
|
37
|
+
entityType,
|
|
38
|
+
entityId,
|
|
39
|
+
action,
|
|
40
|
+
changedBy,
|
|
41
|
+
changes,
|
|
42
|
+
meta,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=audit-log.js.map
|