@rshval/back-kit 1.1.3 → 1.1.5
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 +331 -157
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +4 -1
- package/dist/seed.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,148 +1,187 @@
|
|
|
1
1
|
# @rshval/back-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Public npm package `@rshval/back-kit` with server-side utilities for Node.js/TypeScript projects.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> This repository is used for the author's internal projects. Any third-party usage is at your own risk.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm i @rshval/back-kit
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The package is ESM-only (`"type": "module"`). For CommonJS, use dynamic `import()`.
|
|
14
|
+
|
|
15
|
+
## Quick navigation
|
|
16
|
+
|
|
17
|
+
- [Email](#1-email)
|
|
18
|
+
- [Config and JWT](#2-config-and-jwt)
|
|
19
|
+
- [Helper utilities](#3-helper-utilities)
|
|
20
|
+
- [Validation](#4-validation)
|
|
21
|
+
- [Builders](#5-builders-config-bound)
|
|
22
|
+
- [Database](#6-database)
|
|
23
|
+
- [Cache](#7-cache)
|
|
24
|
+
- [Logging](#8-logging)
|
|
25
|
+
- [WebSocket client](#9-websocket-client)
|
|
26
|
+
- [Paymaster](#10-paymaster)
|
|
27
|
+
- [Mail templates](#11-mail-templates)
|
|
28
|
+
- [Audit changes](#12-audit-changes)
|
|
29
|
+
- [Seed functions](#13-seed-functions)
|
|
30
|
+
- [API client](#14-api-client)
|
|
14
31
|
|
|
15
32
|
---
|
|
16
33
|
|
|
17
|
-
##
|
|
34
|
+
## API and usage examples
|
|
18
35
|
|
|
19
|
-
|
|
36
|
+
Below are the public exports from the package (via `src/index.ts`) with short examples. For key APIs, there are separate **production examples** that show practical integration patterns for real services.
|
|
20
37
|
|
|
21
38
|
### 1) Email
|
|
22
39
|
|
|
23
40
|
#### `createMailOptions({ from, to, subject, text })`
|
|
24
|
-
|
|
41
|
+
|
|
42
|
+
Creates a nodemailer mail options object. Supports `to` as a string (regular send) or a form object (`MailOptionsBody`) for requests/feedback scenarios.
|
|
25
43
|
|
|
26
44
|
```ts
|
|
27
|
-
import { createMailOptions } from '@rshval/back-kit'
|
|
45
|
+
import { createMailOptions } from '@rshval/back-kit';
|
|
28
46
|
|
|
29
47
|
const options = createMailOptions({
|
|
30
48
|
from: 'robot@example.com',
|
|
31
49
|
to: 'user@example.com',
|
|
32
50
|
subject: 'Hello',
|
|
33
|
-
text: 'Welcome!'
|
|
34
|
-
})
|
|
51
|
+
text: 'Welcome!',
|
|
52
|
+
});
|
|
35
53
|
```
|
|
36
54
|
|
|
37
55
|
#### `sendEmailWithConfig({ nodemailerConfig, to, subject, text })`
|
|
38
|
-
|
|
56
|
+
|
|
57
|
+
Creates a nodemailer transport, sends an email, and returns a string with the send result.
|
|
39
58
|
|
|
40
59
|
```ts
|
|
41
|
-
import { sendEmailWithConfig } from '@rshval/back-kit'
|
|
60
|
+
import { sendEmailWithConfig } from '@rshval/back-kit';
|
|
42
61
|
|
|
43
62
|
await sendEmailWithConfig({
|
|
44
63
|
nodemailerConfig: {
|
|
45
64
|
host: 'smtp.example.com',
|
|
46
65
|
port: 465,
|
|
47
66
|
secure: true,
|
|
48
|
-
auth: { user: 'robot@example.com', pass: '***' }
|
|
67
|
+
auth: { user: 'robot@example.com', pass: '***' },
|
|
49
68
|
},
|
|
50
69
|
to: 'user@example.com',
|
|
51
70
|
subject: 'Reset password',
|
|
52
|
-
text: 'Code: 123456'
|
|
53
|
-
})
|
|
71
|
+
text: 'Code: 123456',
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Production example**:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { buildSendEmail } from '@rshval/back-kit';
|
|
79
|
+
|
|
80
|
+
const sendEmailWithConfig = buildSendEmail(runtimeConfig);
|
|
81
|
+
await sendEmailWithConfig(to, subject, text);
|
|
54
82
|
```
|
|
55
83
|
|
|
56
84
|
---
|
|
57
85
|
|
|
58
|
-
### 2)
|
|
86
|
+
### 2) Config and JWT
|
|
59
87
|
|
|
60
88
|
#### `getBaseUrlByConfig(config, baseUrl?)`
|
|
61
|
-
|
|
89
|
+
|
|
90
|
+
Builds a base URL from app config. In `development`, adds `:port`.
|
|
62
91
|
|
|
63
92
|
```ts
|
|
64
|
-
import { getBaseUrlByConfig } from '@rshval/back-kit'
|
|
93
|
+
import { getBaseUrlByConfig } from '@rshval/back-kit';
|
|
65
94
|
|
|
66
95
|
const base = getBaseUrlByConfig(
|
|
67
96
|
{
|
|
68
97
|
NODE_ENV: 'development',
|
|
69
98
|
server: { domain: 'localhost', port: 3000 },
|
|
70
|
-
jwt: {}
|
|
99
|
+
jwt: {},
|
|
71
100
|
},
|
|
72
|
-
'/api'
|
|
73
|
-
)
|
|
101
|
+
'/api',
|
|
102
|
+
);
|
|
74
103
|
// //localhost:3000/api
|
|
75
104
|
```
|
|
76
105
|
|
|
77
106
|
#### `createTokenByConfig({ config, user, expiresIn })`
|
|
78
|
-
|
|
107
|
+
|
|
108
|
+
Creates a JWT token based on `config.jwt.JWT_KEY` (or `JWT_KEY_NO_ENV`).
|
|
79
109
|
|
|
80
110
|
```ts
|
|
81
|
-
import { createTokenByConfig } from '@rshval/back-kit'
|
|
111
|
+
import { createTokenByConfig } from '@rshval/back-kit';
|
|
82
112
|
|
|
83
113
|
const token = createTokenByConfig({
|
|
84
114
|
config: {
|
|
85
115
|
server: { domain: 'example.com' },
|
|
86
|
-
jwt: { JWT_KEY: 'super-secret' }
|
|
116
|
+
jwt: { JWT_KEY: 'super-secret' },
|
|
87
117
|
},
|
|
88
118
|
user: { _id: '64a...' },
|
|
89
|
-
expiresIn: '7d'
|
|
90
|
-
})
|
|
119
|
+
expiresIn: '7d',
|
|
120
|
+
});
|
|
91
121
|
```
|
|
92
122
|
|
|
93
123
|
---
|
|
94
124
|
|
|
95
|
-
### 3)
|
|
125
|
+
### 3) Helper utilities
|
|
96
126
|
|
|
97
127
|
#### `createPinCode(min?, max?)`
|
|
98
|
-
|
|
128
|
+
|
|
129
|
+
Generates a random numeric PIN in range (`10000..99990` by default).
|
|
99
130
|
|
|
100
131
|
```ts
|
|
101
|
-
import { createPinCode } from '@rshval/back-kit'
|
|
132
|
+
import { createPinCode } from '@rshval/back-kit';
|
|
102
133
|
|
|
103
|
-
const pin = createPinCode()
|
|
134
|
+
const pin = createPinCode();
|
|
104
135
|
```
|
|
105
136
|
|
|
106
137
|
#### `getIp(req)`
|
|
107
|
-
|
|
138
|
+
|
|
139
|
+
Attempts to detect client IP from `req.ip`, sockets, and `x-forwarded-for`.
|
|
108
140
|
|
|
109
141
|
```ts
|
|
110
|
-
import { getIp } from '@rshval/back-kit'
|
|
142
|
+
import { getIp } from '@rshval/back-kit';
|
|
111
143
|
|
|
112
|
-
const ip = await getIp(req)
|
|
144
|
+
const ip = await getIp(req);
|
|
113
145
|
```
|
|
114
146
|
|
|
115
147
|
#### `translitUrl(str)`
|
|
116
|
-
|
|
148
|
+
|
|
149
|
+
Transliterates a string into a URL-friendly slug (`-`, lower-case).
|
|
117
150
|
|
|
118
151
|
```ts
|
|
119
|
-
import { translitUrl } from '@rshval/back-kit'
|
|
152
|
+
import { translitUrl } from '@rshval/back-kit';
|
|
120
153
|
|
|
121
|
-
const slug = translitUrl('
|
|
154
|
+
const slug = translitUrl('Page example');
|
|
122
155
|
// primer-stranicy
|
|
123
156
|
```
|
|
124
157
|
|
|
125
158
|
---
|
|
126
159
|
|
|
127
|
-
### 4)
|
|
160
|
+
### 4) Validation
|
|
128
161
|
|
|
129
162
|
#### `patternEmail()`
|
|
130
|
-
|
|
163
|
+
|
|
164
|
+
Returns a RegExp for email validation.
|
|
131
165
|
|
|
132
166
|
#### `patternPassword()`
|
|
133
|
-
|
|
167
|
+
|
|
168
|
+
Returns a RegExp for password validation (at least 8 chars, letters + digits).
|
|
134
169
|
|
|
135
170
|
#### `isValidEmail(val)`
|
|
136
|
-
|
|
171
|
+
|
|
172
|
+
Checks whether an email is valid.
|
|
137
173
|
|
|
138
174
|
#### `isValidPhoneNumber(val)`
|
|
139
|
-
|
|
175
|
+
|
|
176
|
+
Validates a phone number via `libphonenumber-js`.
|
|
140
177
|
|
|
141
178
|
#### `isValidCode(code, length)`
|
|
142
|
-
|
|
179
|
+
|
|
180
|
+
Checks code length.
|
|
143
181
|
|
|
144
182
|
#### `isEmpty(val)`
|
|
145
|
-
|
|
183
|
+
|
|
184
|
+
Checks for an empty string (`trim`) or an empty object.
|
|
146
185
|
|
|
147
186
|
```ts
|
|
148
187
|
import {
|
|
@@ -151,152 +190,196 @@ import {
|
|
|
151
190
|
isValidEmail,
|
|
152
191
|
isValidPhoneNumber,
|
|
153
192
|
isValidCode,
|
|
154
|
-
isEmpty
|
|
155
|
-
} from '@rshval/back-kit'
|
|
193
|
+
isEmpty,
|
|
194
|
+
} from '@rshval/back-kit';
|
|
156
195
|
|
|
157
|
-
const emailRegex = patternEmail()
|
|
158
|
-
const passwordRegex = patternPassword()
|
|
196
|
+
const emailRegex = patternEmail();
|
|
197
|
+
const passwordRegex = patternPassword();
|
|
159
198
|
|
|
160
|
-
isValidEmail('user@example.com') // true
|
|
161
|
-
isValidPhoneNumber('+79991234567') // true/false
|
|
162
|
-
isValidCode('123456', 6) // true
|
|
163
|
-
isEmpty(' ') // true
|
|
164
|
-
isEmpty({}) // true
|
|
199
|
+
isValidEmail('user@example.com'); // true
|
|
200
|
+
isValidPhoneNumber('+79991234567'); // true/false
|
|
201
|
+
isValidCode('123456', 6); // true
|
|
202
|
+
isEmpty(' '); // true
|
|
203
|
+
isEmpty({}); // true
|
|
165
204
|
```
|
|
166
205
|
|
|
167
206
|
---
|
|
168
207
|
|
|
169
|
-
### 5)
|
|
208
|
+
### 5) Builders (config-bound)
|
|
170
209
|
|
|
171
210
|
#### `buildGetBaseUrl(config)`
|
|
172
|
-
|
|
211
|
+
|
|
212
|
+
Returns a function `(baseUrl?) => string`.
|
|
173
213
|
|
|
174
214
|
#### `buildCreateToken(config)`
|
|
175
|
-
|
|
215
|
+
|
|
216
|
+
Returns a JWT creator function with pre-bound config.
|
|
176
217
|
|
|
177
218
|
#### `buildSendEmail(nodemailerConfig)`
|
|
178
|
-
|
|
219
|
+
|
|
220
|
+
Returns an email sender function with pre-bound SMTP config.
|
|
179
221
|
|
|
180
222
|
```ts
|
|
181
223
|
import {
|
|
182
224
|
buildGetBaseUrl,
|
|
183
225
|
buildCreateToken,
|
|
184
|
-
buildSendEmail
|
|
185
|
-
} from '@rshval/back-kit'
|
|
226
|
+
buildSendEmail,
|
|
227
|
+
} from '@rshval/back-kit';
|
|
186
228
|
|
|
187
229
|
const getBaseUrl = buildGetBaseUrl({
|
|
188
230
|
NODE_ENV: 'production',
|
|
189
231
|
server: { domain: 'example.com' },
|
|
190
|
-
jwt: { JWT_KEY: 'secret' }
|
|
191
|
-
})
|
|
232
|
+
jwt: { JWT_KEY: 'secret' },
|
|
233
|
+
});
|
|
192
234
|
|
|
193
235
|
const createToken = buildCreateToken({
|
|
194
236
|
server: { domain: 'example.com' },
|
|
195
|
-
jwt: { JWT_KEY: 'secret' }
|
|
196
|
-
})
|
|
237
|
+
jwt: { JWT_KEY: 'secret' },
|
|
238
|
+
});
|
|
197
239
|
|
|
198
240
|
const sendEmail = buildSendEmail({
|
|
199
241
|
host: 'smtp.example.com',
|
|
200
242
|
port: 465,
|
|
201
243
|
secure: true,
|
|
202
|
-
auth: { user: 'robot@example.com', pass: '***' }
|
|
203
|
-
})
|
|
244
|
+
auth: { user: 'robot@example.com', pass: '***' },
|
|
245
|
+
});
|
|
204
246
|
```
|
|
205
247
|
|
|
206
248
|
---
|
|
207
249
|
|
|
208
|
-
### 6)
|
|
250
|
+
### 6) Database
|
|
209
251
|
|
|
210
252
|
#### `startMongoDatabase(options)`
|
|
211
|
-
|
|
253
|
+
|
|
254
|
+
Starts MongoDB connection via mongoose, logs statuses, and supports connection retries.
|
|
212
255
|
|
|
213
256
|
```ts
|
|
214
|
-
import { startMongoDatabase } from '@rshval/back-kit'
|
|
257
|
+
import { startMongoDatabase } from '@rshval/back-kit';
|
|
215
258
|
|
|
216
259
|
await startMongoDatabase({
|
|
217
260
|
config: {
|
|
218
261
|
name: 'main',
|
|
219
262
|
connect: process.env.MONGO_URI,
|
|
220
|
-
params: {}
|
|
263
|
+
params: {},
|
|
221
264
|
},
|
|
222
|
-
logger: console
|
|
223
|
-
})
|
|
265
|
+
logger: console,
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Production example**:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
await startMongoDatabase({
|
|
273
|
+
config: config.database,
|
|
274
|
+
logger,
|
|
275
|
+
setCacheStatus: (status) =>
|
|
276
|
+
cache.set(cacheIdDatabase, { status }, undefined, undefined, cacheTtl30m),
|
|
277
|
+
});
|
|
224
278
|
```
|
|
225
279
|
|
|
226
280
|
---
|
|
227
281
|
|
|
228
|
-
### 7)
|
|
282
|
+
### 7) Cache
|
|
229
283
|
|
|
230
284
|
#### `createCacheService(options?)`
|
|
231
|
-
|
|
285
|
+
|
|
286
|
+
Creates an LRU cache service with methods:
|
|
287
|
+
|
|
232
288
|
- `get(key)`
|
|
233
289
|
- `set(key, data, compareKey?, compareValue?, ttlMs?)`
|
|
234
290
|
- `delete(key)`
|
|
235
291
|
- `getId(val)`
|
|
236
292
|
- `keys()`
|
|
237
293
|
- `clear()`
|
|
238
|
-
- `entries()`
|
|
239
|
-
- `values()`
|
|
294
|
+
- `entries()` _(if `exposeEntries: true`)_
|
|
295
|
+
- `values()` _(if `exposeValues: true`)_
|
|
240
296
|
|
|
241
297
|
```ts
|
|
242
|
-
import { createCacheService } from '@rshval/back-kit'
|
|
298
|
+
import { createCacheService } from '@rshval/back-kit';
|
|
243
299
|
|
|
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)
|
|
300
|
+
const cache = createCacheService({ ttl: 60_000 });
|
|
301
|
+
const key = cache.getId({ service: 'users', page: 1 });
|
|
302
|
+
await cache.set(key, [{ s: 'state', data: [1, 2, 3] }]);
|
|
303
|
+
const value = await cache.get(key);
|
|
248
304
|
```
|
|
249
305
|
|
|
250
306
|
#### `createCacheMiddleware({ cache, ... })`
|
|
251
|
-
|
|
307
|
+
|
|
308
|
+
Creates a middleware wrapper over the cache service with methods:
|
|
309
|
+
|
|
252
310
|
- `get(id)`
|
|
253
311
|
- `set(id, data, expDataTime)`
|
|
254
312
|
- `del(id)`
|
|
255
|
-
- `delByPrefix(prefix)`
|
|
313
|
+
- `delByPrefix(prefix)` _(if `includeDelByPrefix: true`)_
|
|
256
314
|
|
|
257
315
|
```ts
|
|
258
|
-
import { createCacheService, createCacheMiddleware } from '@rshval/back-kit'
|
|
316
|
+
import { createCacheService, createCacheMiddleware } from '@rshval/back-kit';
|
|
259
317
|
|
|
260
|
-
const cache = createCacheService({ supportTtlInSet: true })
|
|
318
|
+
const cache = createCacheService({ supportTtlInSet: true });
|
|
261
319
|
const cm = createCacheMiddleware({
|
|
262
320
|
cache,
|
|
263
321
|
passTtlToCacheSet: true,
|
|
264
|
-
includeDelByPrefix: true
|
|
265
|
-
})
|
|
322
|
+
includeDelByPrefix: true,
|
|
323
|
+
});
|
|
266
324
|
|
|
267
|
-
await cm.set('users:list', [{ id: 1 }], 60_000)
|
|
268
|
-
const cached = await cm.get('users:list')
|
|
269
|
-
await cm.delByPrefix?.('users:')
|
|
325
|
+
await cm.set('users:list', [{ id: 1 }], 60_000);
|
|
326
|
+
const cached = await cm.get('users:list');
|
|
327
|
+
await cm.delByPrefix?.('users:');
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Production example**:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const cache = createCacheService({ supportTtlInSet: true });
|
|
334
|
+
const cacheMiddleware = createCacheMiddleware({
|
|
335
|
+
cache,
|
|
336
|
+
passTtlToCacheSet: true,
|
|
337
|
+
includeDelByPrefix: true,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
export const { get, set, del } = cacheMiddleware;
|
|
341
|
+
export const delByPrefix = cacheMiddleware.delByPrefix!;
|
|
270
342
|
```
|
|
271
343
|
|
|
272
344
|
---
|
|
273
345
|
|
|
274
|
-
### 8)
|
|
346
|
+
### 8) Logging
|
|
275
347
|
|
|
276
348
|
#### `createLoggerService({ rootdir? })`
|
|
277
|
-
|
|
349
|
+
|
|
350
|
+
Returns a logger factory. For each namespace (`std`) it writes:
|
|
351
|
+
|
|
278
352
|
- `logs/<std>/stdout.log`
|
|
279
353
|
- `logs/<std>/stderr.log`
|
|
280
354
|
|
|
281
355
|
```ts
|
|
282
|
-
import { createLoggerService } from '@rshval/back-kit'
|
|
356
|
+
import { createLoggerService } from '@rshval/back-kit';
|
|
283
357
|
|
|
284
|
-
const makeLogger = createLoggerService({ rootdir: process.cwd() })
|
|
285
|
-
const logger = makeLogger('api')
|
|
286
|
-
logger.log('started')
|
|
358
|
+
const makeLogger = createLoggerService({ rootdir: process.cwd() });
|
|
359
|
+
const logger = makeLogger('api');
|
|
360
|
+
logger.log('started');
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Production example**:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const loggerService = createLoggerService();
|
|
367
|
+
const logger = loggerService('api');
|
|
287
368
|
```
|
|
288
369
|
|
|
289
370
|
---
|
|
290
371
|
|
|
291
|
-
### 9) WebSocket
|
|
372
|
+
### 9) WebSocket client
|
|
292
373
|
|
|
293
374
|
#### `createSocketClientService(options)`
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
375
|
+
|
|
376
|
+
Creates a `socket.io` client service with methods:
|
|
377
|
+
|
|
378
|
+
- `doSocketClient()` — start connection
|
|
379
|
+
- `getSocketClient()` — return current instance
|
|
297
380
|
|
|
298
381
|
```ts
|
|
299
|
-
import { createSocketClientService } from '@rshval/back-kit'
|
|
382
|
+
import { createSocketClientService } from '@rshval/back-kit';
|
|
300
383
|
|
|
301
384
|
const ws = createSocketClientService({
|
|
302
385
|
wsName: 'worker',
|
|
@@ -304,10 +387,24 @@ const ws = createSocketClientService({
|
|
|
304
387
|
logger: console,
|
|
305
388
|
runWorkers: () => {
|
|
306
389
|
// jobs
|
|
307
|
-
}
|
|
308
|
-
})
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
ws.doSocketClient();
|
|
394
|
+
```
|
|
309
395
|
|
|
310
|
-
|
|
396
|
+
**Production example**:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
const socketClientService = createSocketClientService({
|
|
400
|
+
wsName: config.ws.name,
|
|
401
|
+
socketBase: SOCKET_BASE,
|
|
402
|
+
logger,
|
|
403
|
+
runWorkers: socketClientWorkers,
|
|
404
|
+
socketClientInterval: config.settings.socketClientInterval,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
socketClientService.doSocketClient();
|
|
311
408
|
```
|
|
312
409
|
|
|
313
410
|
---
|
|
@@ -315,44 +412,58 @@ ws.doSocketClient()
|
|
|
315
412
|
### 10) Paymaster
|
|
316
413
|
|
|
317
414
|
#### `createPaymasterService({ paymaster, clientServer, serverBaseUrl })`
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
- `
|
|
415
|
+
|
|
416
|
+
Creates a service with methods:
|
|
417
|
+
|
|
418
|
+
- `createPaymentLink({...})` — build payment URL
|
|
419
|
+
- `validateCallback(payload, rawBody?)` — verify callback signature
|
|
420
|
+
- `parseCallback(payload)` — normalize callback into a convenient object
|
|
322
421
|
|
|
323
422
|
```ts
|
|
324
|
-
import { createPaymasterService } from '@rshval/back-kit'
|
|
423
|
+
import { createPaymasterService } from '@rshval/back-kit';
|
|
325
424
|
|
|
326
425
|
const paymaster = createPaymasterService({
|
|
327
426
|
paymaster: {
|
|
328
427
|
merchantId: 'merchant-id',
|
|
329
428
|
secretKey: 'secret',
|
|
330
429
|
checkoutUrl: 'https://paymaster.ru/Payment/Init',
|
|
331
|
-
checkSignature: true
|
|
430
|
+
checkSignature: true,
|
|
332
431
|
},
|
|
333
432
|
clientServer: 'https://site.example.com',
|
|
334
|
-
serverBaseUrl: 'https://api.example.com'
|
|
335
|
-
})
|
|
433
|
+
serverBaseUrl: 'https://api.example.com',
|
|
434
|
+
});
|
|
336
435
|
|
|
337
436
|
const link = paymaster.createPaymentLink({
|
|
338
437
|
requestId: 'order_123',
|
|
339
438
|
amount: 1499,
|
|
340
|
-
description: 'Order #123'
|
|
341
|
-
})
|
|
439
|
+
description: 'Order #123',
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Production example**:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
export const paymasterService = createPaymasterService({
|
|
447
|
+
paymaster: config.platforms.paymaster,
|
|
448
|
+
clientServer: config.clientServer,
|
|
449
|
+
serverBaseUrl: config.server.baseUrl,
|
|
450
|
+
});
|
|
342
451
|
```
|
|
343
452
|
|
|
344
453
|
---
|
|
345
454
|
|
|
346
|
-
### 11)
|
|
455
|
+
### 11) Mail templates
|
|
347
456
|
|
|
348
457
|
#### `createMailTemplateService({ findTemplate, sendEmail })`
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
- `
|
|
458
|
+
|
|
459
|
+
Creates an email template service with methods:
|
|
460
|
+
|
|
461
|
+
- `extractVariables(template)` — list of `{{variables}}`
|
|
462
|
+
- `renderTemplate(template, context?)` — render subject/body
|
|
463
|
+
- `sendByKey({ key, to, context? })` — find template, render, and send
|
|
353
464
|
|
|
354
465
|
```ts
|
|
355
|
-
import { createMailTemplateService } from '@rshval/back-kit'
|
|
466
|
+
import { createMailTemplateService } from '@rshval/back-kit';
|
|
356
467
|
|
|
357
468
|
const mailTemplates = createMailTemplateService({
|
|
358
469
|
findTemplate: async ({ key }) =>
|
|
@@ -360,36 +471,49 @@ const mailTemplates = createMailTemplateService({
|
|
|
360
471
|
? { subject: 'Hi, {{user.name}}', bodyText: 'Hello {{user.name}}!' }
|
|
361
472
|
: null,
|
|
362
473
|
sendEmail: async (to, subject, text) => {
|
|
363
|
-
console.log('send', to, subject, text)
|
|
364
|
-
}
|
|
365
|
-
})
|
|
474
|
+
console.log('send', to, subject, text);
|
|
475
|
+
},
|
|
476
|
+
});
|
|
366
477
|
|
|
367
478
|
await mailTemplates.sendByKey({
|
|
368
479
|
key: 'welcome',
|
|
369
480
|
to: 'user@example.com',
|
|
370
|
-
context: { user: { name: 'Alex' } }
|
|
371
|
-
})
|
|
481
|
+
context: { user: { name: 'Alex' } },
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Production example**:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
const sendEmailWithConfig = buildSendEmail(runtimeConfig);
|
|
489
|
+
|
|
490
|
+
export const mailTemplateService = createMailTemplateService({
|
|
491
|
+
findTemplate: ({ key }) => MailTemplate.findOne({ key, isActive: true }),
|
|
492
|
+
sendEmail: sendEmailWithConfig,
|
|
493
|
+
});
|
|
372
494
|
```
|
|
373
495
|
|
|
374
496
|
---
|
|
375
497
|
|
|
376
|
-
### 12)
|
|
498
|
+
### 12) Audit changes
|
|
377
499
|
|
|
378
500
|
#### `buildAuditChanges({ beforeData, afterData, fieldsToCheck, protectedFields? })`
|
|
379
|
-
|
|
501
|
+
|
|
502
|
+
Compares before/after data and returns an array of changes for selected fields.
|
|
380
503
|
|
|
381
504
|
#### `createAuditLog({ ..., save })`
|
|
382
|
-
|
|
505
|
+
|
|
506
|
+
Creates an audit record via provided `save` function. Returns `null` when no changes exist.
|
|
383
507
|
|
|
384
508
|
```ts
|
|
385
|
-
import { buildAuditChanges, createAuditLog } from '@rshval/back-kit'
|
|
509
|
+
import { buildAuditChanges, createAuditLog } from '@rshval/back-kit';
|
|
386
510
|
|
|
387
511
|
const changes = buildAuditChanges({
|
|
388
512
|
beforeData: { name: 'Old', role: 'user' },
|
|
389
513
|
afterData: { name: 'New', role: 'user' },
|
|
390
514
|
fieldsToCheck: ['name', 'role'],
|
|
391
|
-
protectedFields: ['role']
|
|
392
|
-
})
|
|
515
|
+
protectedFields: ['role'],
|
|
516
|
+
});
|
|
393
517
|
|
|
394
518
|
await createAuditLog({
|
|
395
519
|
entityType: 'user',
|
|
@@ -397,34 +521,60 @@ await createAuditLog({
|
|
|
397
521
|
action: 'update',
|
|
398
522
|
changedBy: '507f191e810c19729de860ea',
|
|
399
523
|
changes,
|
|
400
|
-
save: async (payload) => payload
|
|
401
|
-
})
|
|
524
|
+
save: async (payload) => payload,
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**Production example**:
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
await createAuditLog({
|
|
532
|
+
entityType,
|
|
533
|
+
entityId,
|
|
534
|
+
action,
|
|
535
|
+
changedBy,
|
|
536
|
+
changes,
|
|
537
|
+
meta,
|
|
538
|
+
save: async (payload) => AuditLog.create(payload),
|
|
539
|
+
});
|
|
402
540
|
```
|
|
403
541
|
|
|
404
542
|
---
|
|
405
543
|
|
|
406
|
-
### 13) Seed
|
|
544
|
+
### 13) Seed functions
|
|
407
545
|
|
|
408
546
|
#### `createSeedFunctions({ cache, retryDelayMs? })`
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
547
|
+
|
|
548
|
+
Returns methods:
|
|
549
|
+
|
|
550
|
+
- `checkDatabaseIsConnected()` — checks DB state via cache
|
|
551
|
+
- `setSeedData(SeedModel, arr)` — inserts seed data after connection check
|
|
412
552
|
|
|
413
553
|
```ts
|
|
414
|
-
import { createSeedFunctions, createCacheService } from '@rshval/back-kit'
|
|
554
|
+
import { createSeedFunctions, createCacheService } from '@rshval/back-kit';
|
|
415
555
|
|
|
416
|
-
const cache = createCacheService()
|
|
417
|
-
const { setSeedData } = createSeedFunctions({ cache })
|
|
556
|
+
const cache = createCacheService();
|
|
557
|
+
const { setSeedData } = createSeedFunctions({ cache });
|
|
418
558
|
|
|
419
559
|
// await setSeedData(UserModel, [{ name: 'Admin' }])
|
|
420
560
|
```
|
|
421
561
|
|
|
562
|
+
**Production example**:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
export const { checkDatabaseIsConnected, setSeedData } = createSeedFunctions({
|
|
566
|
+
cache,
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
422
570
|
---
|
|
423
571
|
|
|
424
|
-
### 14) API
|
|
572
|
+
### 14) API client
|
|
425
573
|
|
|
426
574
|
#### `createApiService({ userAgent, logger? })`
|
|
427
|
-
|
|
575
|
+
|
|
576
|
+
Returns an HTTP client with methods:
|
|
577
|
+
|
|
428
578
|
- `get(path, token?)`
|
|
429
579
|
- `getXml(path, token?)`
|
|
430
580
|
- `del(path, token?)`
|
|
@@ -432,53 +582,77 @@ const { setSeedData } = createSeedFunctions({ cache })
|
|
|
432
582
|
- `put(path, data, token)`
|
|
433
583
|
|
|
434
584
|
```ts
|
|
435
|
-
import { createApiService } from '@rshval/back-kit'
|
|
585
|
+
import { createApiService } from '@rshval/back-kit';
|
|
436
586
|
|
|
437
|
-
const api = createApiService({
|
|
587
|
+
const api = createApiService({
|
|
588
|
+
userAgent: 'my-service/1.0.0',
|
|
589
|
+
logger: console,
|
|
590
|
+
});
|
|
438
591
|
|
|
439
|
-
const users = await api.get('https://api.example.com/users')
|
|
440
|
-
const created = await api.post('https://api.example.com/users', {
|
|
592
|
+
const users = await api.get('https://api.example.com/users');
|
|
593
|
+
const created = await api.post('https://api.example.com/users', {
|
|
594
|
+
name: 'Alex',
|
|
595
|
+
});
|
|
441
596
|
```
|
|
442
597
|
|
|
443
|
-
|
|
598
|
+
**Production example**:
|
|
444
599
|
|
|
445
600
|
```ts
|
|
446
|
-
|
|
601
|
+
const apiService = createApiService({
|
|
602
|
+
userAgent: config.api.userAgent,
|
|
603
|
+
logger,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
export const { get, getXml, del, post, put } = apiService;
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Token type is also exported:
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
import type { Token } from '@rshval/back-kit';
|
|
447
613
|
```
|
|
448
614
|
|
|
449
615
|
---
|
|
450
616
|
|
|
451
|
-
### 15)
|
|
617
|
+
### 15) Other exported utilities
|
|
452
618
|
|
|
453
619
|
#### `add(a, b)`
|
|
454
|
-
|
|
620
|
+
|
|
621
|
+
Returns the sum of two numbers.
|
|
455
622
|
|
|
456
623
|
#### `test2()`
|
|
457
|
-
|
|
624
|
+
|
|
625
|
+
Logs `99` and returns `8`.
|
|
458
626
|
|
|
459
627
|
```ts
|
|
460
|
-
import { add, test2 } from '@rshval/back-kit'
|
|
628
|
+
import { add, test2 } from '@rshval/back-kit';
|
|
461
629
|
|
|
462
|
-
add(2, 3) // 5
|
|
463
|
-
test2() // 8
|
|
630
|
+
add(2, 3); // 5
|
|
631
|
+
test2(); // 8
|
|
464
632
|
```
|
|
465
633
|
|
|
466
634
|
---
|
|
467
635
|
|
|
468
|
-
##
|
|
636
|
+
## Preparing for npm publication
|
|
637
|
+
|
|
638
|
+
See detailed guide: [`docs/publishing.md`](docs/publishing.md).
|
|
639
|
+
|
|
640
|
+
Short wording for npm description:
|
|
469
641
|
|
|
470
|
-
|
|
642
|
+
- A set of service factories (`create*`, `build*`) and ready-to-use utilities/validators.
|
|
643
|
+
- DI-friendly API: dependencies are injected externally (`logger`, `cache`, `save`, `findTemplate`).
|
|
644
|
+
- Important behavior options (`supportTtlInSet`, `passTtlToCacheSet`, `includeDelByPrefix`) should be documented near examples.
|
|
471
645
|
|
|
472
646
|
---
|
|
473
647
|
|
|
474
|
-
##
|
|
648
|
+
## Development (standalone)
|
|
475
649
|
|
|
476
650
|
```bash
|
|
477
651
|
npm install
|
|
478
652
|
npm run build
|
|
479
653
|
```
|
|
480
654
|
|
|
481
|
-
##
|
|
655
|
+
## Exports
|
|
482
656
|
|
|
483
657
|
- ESM: `dist/index.js`
|
|
484
|
-
-
|
|
658
|
+
- Types: `dist/index.d.ts`
|
package/dist/seed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAoB,KAAK,EAAE,MAAM,UAAU,CAAC;AAExD,UAAU,gBAAgB;IACxB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,CAAC;IAC1C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACpC;AAED,UAAU,0BAA0B;IAClC,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,mBAAmB,GAAI,0BAGjC,0BAA0B;;6BAwBd,OAAO,KAAK,OAClB,MAAM,EAAE,KACZ,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAoB,KAAK,EAAE,MAAM,UAAU,CAAC;AAExD,UAAU,gBAAgB;IACxB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,CAAC;IAC1C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACpC;AAED,UAAU,0BAA0B;IAClC,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,mBAAmB,GAAI,0BAGjC,0BAA0B;;6BAwBd,OAAO,KAAK,OAClB,MAAM,EAAE,KACZ,OAAO,CAAC,IAAI,CAAC;CAmDjB,CAAC"}
|
package/dist/seed.js
CHANGED
|
@@ -20,7 +20,10 @@ export const createSeedFunctions = ({ cache, retryDelayMs = 1000, }) => {
|
|
|
20
20
|
const setSeedData = async (SeedModel, arr) => {
|
|
21
21
|
const databaseIsConnected = await checkDatabaseIsConnected();
|
|
22
22
|
if (databaseIsConnected) {
|
|
23
|
-
const spinner = ora(
|
|
23
|
+
const spinner = ora({
|
|
24
|
+
text: chalk.gray('add seed data - ' + SeedModel.modelName),
|
|
25
|
+
stream: process.stdout,
|
|
26
|
+
}).start();
|
|
24
27
|
const arrs = [];
|
|
25
28
|
let done2 = 0;
|
|
26
29
|
for (let i = 0; i < arr.length; i++) {
|
package/dist/seed.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAatB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,KAAK,EACL,YAAY,GAAG,IAAI,GACQ,EAAE,EAAE;IAC/B,MAAM,wBAAwB,GAAG,KAAK,IAAI,EAAE;QAC1C,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC;YAClC,OAAO,EAAE,UAAU;SACpB,CAAC,CAAC;QAEH,IAAI,kBAAuB,CAAC;QAE5B,IAAI,eAAe,EAAE,CAAC;YACpB,kBAAkB,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACxD,CAAC;QAED,IACE,kBAAkB;YAClB,kBAAkB,CAAC,MAAM;YACzB,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,EAC5C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,KAAK,EACvB,SAAuB,EACvB,GAAa,EACE,EAAE;QACjB,MAAM,mBAAmB,GAAG,MAAM,wBAAwB,EAAE,CAAC;QAE7D,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,GAAG,
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAatB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,KAAK,EACL,YAAY,GAAG,IAAI,GACQ,EAAE,EAAE;IAC/B,MAAM,wBAAwB,GAAG,KAAK,IAAI,EAAE;QAC1C,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC;YAClC,OAAO,EAAE,UAAU;SACpB,CAAC,CAAC;QAEH,IAAI,kBAAuB,CAAC;QAE5B,IAAI,eAAe,EAAE,CAAC;YACpB,kBAAkB,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACxD,CAAC;QAED,IACE,kBAAkB;YAClB,kBAAkB,CAAC,MAAM;YACzB,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,EAC5C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,KAAK,EACvB,SAAuB,EACvB,GAAa,EACE,EAAE;QACjB,MAAM,mBAAmB,GAAG,MAAM,wBAAwB,EAAE,CAAC;QAE7D,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,GAAG,CAAC;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC,SAAS,CAAC;gBAC1D,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC,KAAK,EAAE,CAAC;YAIX,MAAM,IAAI,GAAU,EAAE,CAAC;YACvB,IAAI,KAAK,GAAG,CAAC,CAAC;YAEd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpC,MAAM,GAAG,GAAQ,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAEf,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC3B,IAAI,IAAI,GAAG,CAAC,CAAC;oBAEb,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;wBACxB,MAAM,IAAI;6BACP,IAAI,EAAE;6BACN,IAAI,CAAC,GAAG,EAAE;4BACT,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gCAC3B,OAAO,IAAI,CAAC;4BACd,CAAC;wBACH,CAAC,CAAC;6BACD,KAAK,CAAC,GAAG,EAAE;4BACV,OAAO,IAAI,CAAC;wBACd,CAAC,CAAC,CAAC;oBACP,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC;YACxE,OAAO,CAAC,OAAO,EAAE,CAAC;YAElB,OAAO;QACT,CAAC;QAED,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC;QAE1B,OAAO,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC,CAAC;IAEF,OAAO;QACL,wBAAwB;QACxB,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rshval/back-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"author": "",
|
|
27
27
|
"license": "MIT",
|
|
28
|
-
"description": "Backend
|
|
28
|
+
"description": "Backend toolkit of service factories (create*/build*), validators and Node.js API utilities with DI-friendly patterns",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
31
31
|
"@types/jsonwebtoken": "^9.0.10",
|