@sera4/essentia 3.0.13 → 3.1.0-rc.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 +93 -308
- package/dist/ts/cache/index.d.ts +33 -0
- package/dist/ts/cache/index.js +175 -0
- package/dist/ts/cache/index.js.map +1 -0
- package/dist/ts/configurations/server-config.d.ts +4 -0
- package/dist/ts/configurations/server-config.js +2 -0
- package/dist/ts/configurations/server-config.js.map +1 -0
- package/dist/ts/constants/index.d.ts +7 -0
- package/dist/ts/constants/index.js +8 -0
- package/dist/ts/constants/index.js.map +1 -0
- package/dist/ts/cycle/index.d.ts +53 -0
- package/dist/ts/cycle/index.js +171 -0
- package/dist/ts/cycle/index.js.map +1 -0
- package/dist/ts/dns/dns-cache.d.ts +50 -0
- package/dist/ts/dns/dns-cache.js +144 -0
- package/dist/ts/dns/dns-cache.js.map +1 -0
- package/dist/ts/dns/index.d.ts +4 -0
- package/dist/ts/dns/index.js +4 -0
- package/dist/ts/dns/index.js.map +1 -0
- package/dist/ts/formatter/index.d.ts +45 -0
- package/dist/ts/formatter/index.js +82 -0
- package/dist/ts/formatter/index.js.map +1 -0
- package/dist/ts/hal/index.d.ts +26 -0
- package/dist/ts/hal/index.js +70 -0
- package/dist/ts/hal/index.js.map +1 -0
- package/dist/ts/health/index.d.ts +52 -0
- package/dist/ts/health/index.js +170 -0
- package/dist/ts/health/index.js.map +1 -0
- package/dist/ts/helpers/index.d.ts +1 -0
- package/dist/ts/helpers/index.js +2 -0
- package/dist/ts/helpers/index.js.map +1 -0
- package/dist/ts/helpers/test-server-wrapper.d.ts +56 -0
- package/dist/ts/helpers/test-server-wrapper.js +153 -0
- package/dist/ts/helpers/test-server-wrapper.js.map +1 -0
- package/{index.js → dist/ts/index.d.ts} +14 -11
- package/dist/ts/index.js +25 -0
- package/dist/ts/index.js.map +1 -0
- package/dist/ts/last-commit/index.d.ts +12 -0
- package/dist/ts/last-commit/index.js +82 -0
- package/dist/ts/last-commit/index.js.map +1 -0
- package/dist/ts/logger/index.d.ts +4 -0
- package/dist/ts/logger/index.js +5 -0
- package/dist/ts/logger/index.js.map +1 -0
- package/dist/ts/logger/s4-logger.d.ts +41 -0
- package/dist/ts/logger/s4-logger.js +134 -0
- package/dist/ts/logger/s4-logger.js.map +1 -0
- package/dist/ts/paginator/index.d.ts +2 -0
- package/dist/ts/paginator/index.js +3 -0
- package/dist/ts/paginator/index.js.map +1 -0
- package/dist/ts/paginator/s4-pagination.d.ts +28 -0
- package/dist/ts/paginator/s4-pagination.js +49 -0
- package/dist/ts/paginator/s4-pagination.js.map +1 -0
- package/dist/ts/paginator/sql-pagination.d.ts +52 -0
- package/dist/ts/paginator/sql-pagination.js +176 -0
- package/dist/ts/paginator/sql-pagination.js.map +1 -0
- package/dist/ts/paper-trail/helpers.d.ts +14 -0
- package/dist/ts/paper-trail/helpers.js +72 -0
- package/dist/ts/paper-trail/helpers.js.map +1 -0
- package/dist/ts/paper-trail/index.d.ts +54 -0
- package/dist/ts/paper-trail/index.js +235 -0
- package/dist/ts/paper-trail/index.js.map +1 -0
- package/dist/ts/prompts/index.d.ts +21 -0
- package/dist/ts/prompts/index.js +68 -0
- package/dist/ts/prompts/index.js.map +1 -0
- package/dist/ts/queue/index.d.ts +232 -0
- package/dist/ts/queue/index.js +446 -0
- package/dist/ts/queue/index.js.map +1 -0
- package/dist/ts/queue/publisher.d.ts +41 -0
- package/dist/ts/queue/publisher.js +87 -0
- package/dist/ts/queue/publisher.js.map +1 -0
- package/dist/ts/queue/queue-logger.d.ts +10 -0
- package/dist/ts/queue/queue-logger.js +35 -0
- package/dist/ts/queue/queue-logger.js.map +1 -0
- package/dist/ts/queue/subscriber.d.ts +55 -0
- package/dist/ts/queue/subscriber.js +104 -0
- package/dist/ts/queue/subscriber.js.map +1 -0
- package/dist/ts/safe-proxy/index.d.ts +24 -0
- package/dist/ts/safe-proxy/index.js +50 -0
- package/dist/ts/safe-proxy/index.js.map +1 -0
- package/dist/ts/serializer/index.d.ts +52 -0
- package/dist/ts/serializer/index.js +83 -0
- package/dist/ts/serializer/index.js.map +1 -0
- package/dist/ts/task-scheduler/index.d.ts +130 -0
- package/dist/ts/task-scheduler/index.js +261 -0
- package/dist/ts/task-scheduler/index.js.map +1 -0
- package/dist/ts/utils/index.d.ts +49 -0
- package/dist/ts/utils/index.js +162 -0
- package/dist/ts/utils/index.js.map +1 -0
- package/last-commit.ts +12 -0
- package/package.json +28 -11
- package/package.tar.gz +0 -0
package/README.md
CHANGED
|
@@ -1,354 +1,139 @@
|
|
|
1
|
-
# ESSENTIA
|
|
1
|
+
# ESSENTIA 3.1
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
-
[](https://nodejs.org/)
|
|
7
|
-
|
|
8
|
-
A comprehensive TypeScript library providing essential utilities for TWS services. Built with modern TypeScript practices and full type safety.
|
|
9
|
-
|
|
10
|
-
## Project Structure
|
|
11
|
-
|
|
12
|
-
All modules are written in TypeScript and located in the `/ts` directory:
|
|
13
|
-
|
|
14
|
-
- `/ts/logger` - S4Logger implementation with Winston integration
|
|
15
|
-
- `/ts/utils` - Utility functions (UUID validation, array conversion, etc.)
|
|
16
|
-
- `/ts/dns` - DNS caching functionality with configurable TTL
|
|
17
|
-
- `/ts/health` - Health check utilities for service monitoring
|
|
18
|
-
- `/ts/cycle` - Cycle deployment watcher for version tracking
|
|
19
|
-
- `/ts/constants` - Shared constants and configuration
|
|
20
|
-
- `/ts/helpers` - Helper functions and utilities
|
|
21
|
-
- `/ts/paginator` - Pagination utilities for SQL and data arrays
|
|
22
|
-
- `/ts/cache` - Redis cache helper with connection management
|
|
23
|
-
- `/ts/prompts` - CLI prompt utilities with argument parsing
|
|
24
|
-
- `/ts/safe-proxy` - Safe object property access with error handling
|
|
25
|
-
- `/ts/queue` - AMQP message queue management
|
|
26
|
-
- `/ts/last-commit` - Git commit information retrieval
|
|
27
|
-
- `/ts/hal` - HAL decorator for API responses
|
|
28
|
-
- `/ts/formatter` - Data formatting and error response utilities
|
|
29
|
-
- `/ts/serializer` - Sequelize model serialization utilities
|
|
30
|
-
- `/ts/paper-trail` - Data versioning and change tracking
|
|
31
|
-
|
|
32
|
-
## Quick Start
|
|
33
|
-
|
|
34
|
-
Install the package:
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://nodejs.org/)
|
|
35
6
|
|
|
36
|
-
|
|
37
|
-
npm install @sera4/essentia
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Basic Usage
|
|
7
|
+
Shared TypeScript library for TWS/sera4 microservices. ESM, strict mode, full type declarations.
|
|
41
8
|
|
|
42
|
-
|
|
43
|
-
import {
|
|
44
|
-
sqlPaginator,
|
|
45
|
-
paginator,
|
|
46
|
-
DnsCache,
|
|
47
|
-
S4Logger,
|
|
48
|
-
utils,
|
|
49
|
-
constants
|
|
50
|
-
} from '@sera4/essentia';
|
|
51
|
-
|
|
52
|
-
// Pagination
|
|
53
|
-
const result = sqlPaginator('SELECT * FROM users', { page: 1, limit: 10 });
|
|
54
|
-
const paginated = paginator(data, { page: 1, limit: 20 });
|
|
55
|
-
|
|
56
|
-
// DNS caching
|
|
57
|
-
const dnsCache = new DnsCache();
|
|
58
|
-
const ip = await dnsCache.resolveTarget('https://example.com');
|
|
59
|
-
|
|
60
|
-
// Logging
|
|
61
|
-
const logger = new S4Logger();
|
|
62
|
-
logger.configure({ level: 'debug', service: 'my-service' });
|
|
63
|
-
logger.info('Application started');
|
|
64
|
-
|
|
65
|
-
// Utilities
|
|
66
|
-
const isValidUuid = utils.isValidUuidV4('123e4567-e89b-12d3-a456-426614174000');
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Advanced Usage
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import {
|
|
73
|
-
sqlPaginator,
|
|
74
|
-
paginator,
|
|
75
|
-
DnsCache,
|
|
76
|
-
S4Logger,
|
|
77
|
-
HealthCheck,
|
|
78
|
-
CycleDeploymentWatcher,
|
|
79
|
-
halDecorator,
|
|
80
|
-
formatter,
|
|
81
|
-
queue,
|
|
82
|
-
paperTrail,
|
|
83
|
-
serializer,
|
|
84
|
-
lastCommit,
|
|
85
|
-
utils,
|
|
86
|
-
constants,
|
|
87
|
-
helpers,
|
|
88
|
-
prompts,
|
|
89
|
-
safeProxy
|
|
90
|
-
} from '@sera4/essentia';
|
|
91
|
-
|
|
92
|
-
// Pagination examples
|
|
93
|
-
const sqlResult = sqlPaginator('SELECT * FROM users WHERE active = ?', {
|
|
94
|
-
page: 1,
|
|
95
|
-
limit: 10,
|
|
96
|
-
orderBy: 'created_at',
|
|
97
|
-
orderDirection: 'DESC'
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const paginatedData = paginator(userList, {
|
|
101
|
-
page: 1,
|
|
102
|
-
limit: 20,
|
|
103
|
-
total: 100
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// DNS caching with configuration
|
|
107
|
-
const dnsCache = DnsCache.getInstance({
|
|
108
|
-
cacheTtlMs: 5000,
|
|
109
|
-
purgeIntervalMs: 120000,
|
|
110
|
-
debug: true
|
|
111
|
-
});
|
|
112
|
-
const ip = await dnsCache.resolveTarget('https://example.com');
|
|
113
|
-
|
|
114
|
-
// Logging with advanced configuration
|
|
115
|
-
const logger = new S4Logger();
|
|
116
|
-
logger.configure({
|
|
117
|
-
level: 'debug',
|
|
118
|
-
service: 'my-service',
|
|
119
|
-
format: 'json'
|
|
120
|
-
});
|
|
121
|
-
logger.info('Application started');
|
|
122
|
-
|
|
123
|
-
// Health checks
|
|
124
|
-
const healthCheck = new HealthCheck();
|
|
125
|
-
healthCheck.addCheck('database', async () => {
|
|
126
|
-
// Your database health check logic
|
|
127
|
-
return { status: 'healthy' };
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Cycle deployment monitoring
|
|
131
|
-
const watcher = CycleDeploymentWatcher.getInstance();
|
|
132
|
-
const currentVersion = watcher.getCurrentVersion('gateway');
|
|
133
|
-
|
|
134
|
-
// HAL decorator for API responses
|
|
135
|
-
const response = halDecorator(data, {
|
|
136
|
-
self: '/api/users',
|
|
137
|
-
next: '/api/users?page=2'
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Queue management
|
|
141
|
-
await queue.openConnection({
|
|
142
|
-
connectionUrl: 'amqp://localhost',
|
|
143
|
-
logEnabled: true
|
|
144
|
-
});
|
|
145
|
-
await queue.registerPublisher({
|
|
146
|
-
exchange: { name: 'my-exchange', type: 'topic' }
|
|
147
|
-
});
|
|
148
|
-
await queue.publishMessage('my-exchange', 'my-routing-key', { data: 'message' });
|
|
149
|
-
|
|
150
|
-
// Paper trail for data versioning
|
|
151
|
-
const trail = paperTrail(originalData, {
|
|
152
|
-
userId: 'user123',
|
|
153
|
-
action: 'update'
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Data serialization
|
|
157
|
-
const serialized = serializer(data, {
|
|
158
|
-
format: 'json',
|
|
159
|
-
pretty: true
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Last commit information
|
|
163
|
-
const commitInfo = await lastCommit.getLastCommit();
|
|
164
|
-
console.log(`Deployed version: ${commitInfo.hash}`);
|
|
165
|
-
|
|
166
|
-
// Utility functions
|
|
167
|
-
const safeData = safeProxy(data);
|
|
168
|
-
const helper = helpers.someHelperFunction();
|
|
169
|
-
const parser = prompts.setupCLIParser('My CLI Tool');
|
|
170
|
-
```
|
|
9
|
+
**Requires Node.js ≥ 22.**
|
|
171
10
|
|
|
172
|
-
|
|
11
|
+
---
|
|
173
12
|
|
|
174
|
-
|
|
13
|
+
## What's new in 3.1
|
|
175
14
|
|
|
176
|
-
|
|
177
|
-
- **Logging**: `S4Logger` - Winston-based logging utility with multiple log levels and formatting options
|
|
178
|
-
- **DNS Cache**: `DnsCache` - Singleton class for caching DNS lookups with configurable TTL
|
|
179
|
-
- **Health Checks**: `HealthCheck` - Utilities for implementing health check endpoints
|
|
180
|
-
- **Cycle Deployment Watcher**: `CycleDeploymentWatcher` - Monitor and react to changes in Cycle deployment tags
|
|
181
|
-
- **Utils**: `utils` - General utility functions (UUID validation, array conversion, route type detection, JSONB helpers)
|
|
15
|
+
**Breaking changes**
|
|
182
16
|
|
|
183
|
-
|
|
184
|
-
- **
|
|
185
|
-
-
|
|
186
|
-
- **Serializer**: `serializer` - Sequelize model serialization utilities with custom decorators
|
|
187
|
-
- **Paper Trail**: `paperTrail` - Track changes to data models with automatic versioning and diff generation
|
|
17
|
+
- **Node.js ≥ 22 required** (was ≥ 18 in 3.x)
|
|
18
|
+
- **TypeScript 6** (up from 5.3) — stricter inference rules may surface new type errors in consuming projects
|
|
19
|
+
- All test files are now `.ts` only and use the `*.test.ts` convention; the `.js` test glob has been removed from the test runner
|
|
188
20
|
|
|
189
|
-
|
|
190
|
-
- **HAL Decorator**: `halDecorator` - Adds HAL (Hypertext Application Language) support to API responses
|
|
191
|
-
- **Formatter**: `formatter` - Data formatting utilities and error response formatting
|
|
192
|
-
- **Queue**: `queue` - AMQP message queue management with publishers and subscribers
|
|
193
|
-
- **Safe Proxy**: `safeProxy` - Safe object property access with error handling
|
|
21
|
+
**New**
|
|
194
22
|
|
|
195
|
-
|
|
196
|
-
- **
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
- **Last Commit**: `lastCommit` - Retrieve and display git commit information
|
|
23
|
+
- **Biome** replaces ad-hoc linting — single tool for lint, format, and import sorting (`npm run check`)
|
|
24
|
+
- **Husky pre-commit hook** — Biome auto-fixes staged files before every commit
|
|
25
|
+
- `tsconfig.test.json` — separate typecheck pass covering both `ts/` and `test/`
|
|
26
|
+
- `.nvmrc` pinned to Node 22
|
|
200
27
|
|
|
201
|
-
|
|
28
|
+
**Improvements**
|
|
202
29
|
|
|
203
|
-
|
|
30
|
+
- Strict-mode cleanup across all modules — `noImplicitAny`, unused imports/variables enforced as errors
|
|
31
|
+
- All source files use `node:` protocol for built-in imports (`node:fs`, `node:path`, etc.)
|
|
32
|
+
- All file names normalised to kebab-case (e.g. `dns-cache.ts`, `cycle-deployment-watcher.test.ts`)
|
|
33
|
+
- New test suites added for `cache`, `hal`, `last-commit`, `paper-trail`, `safe-proxy`, `serializer`, `utils`, `s4-formatter`, `s4-pagination` — 190 tests total
|
|
204
34
|
|
|
205
|
-
|
|
35
|
+
---
|
|
206
36
|
|
|
207
|
-
|
|
208
|
-
- `getCurrentVersion(service: string)`: Returns the current version for a service
|
|
209
|
-
- `getLegacyVersion(service: string)`: Returns the legacy version for a service
|
|
210
|
-
- `getTagsForVersion(version: string)`: Returns all tags that match a specific version
|
|
211
|
-
- `addListener(listener: (tags: Record<string, string>) => void)`: Adds a listener for tag changes
|
|
212
|
-
- `destroy()`: Cleans up resources and removes all listeners
|
|
37
|
+
## Setup
|
|
213
38
|
|
|
214
|
-
|
|
39
|
+
```bash
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
npm test
|
|
43
|
+
```
|
|
215
44
|
|
|
216
|
-
|
|
45
|
+
---
|
|
217
46
|
|
|
218
|
-
##
|
|
47
|
+
## Scripts
|
|
219
48
|
|
|
220
|
-
|
|
49
|
+
| Script | What it does |
|
|
50
|
+
| ----------------------- | ---------------------------------------------- |
|
|
51
|
+
| `npm run build` | Compile TypeScript → `dist/ts/` |
|
|
52
|
+
| `npm test` | Run all tests (Mocha + Chai, JUnit XML output) |
|
|
53
|
+
| `npm run test:coverage` | Tests with c8 coverage HTML report |
|
|
54
|
+
| `npm run typecheck` | Type-check without emitting |
|
|
55
|
+
| `npm run lint` | Biome lint across `ts/` and `test/` |
|
|
56
|
+
| `npm run lint:fix` | Biome lint with auto-fix |
|
|
57
|
+
| `npm run format` | Biome format write pass |
|
|
58
|
+
| `npm run format:check` | Biome format check (CI-safe) |
|
|
59
|
+
| `npm run check` | Biome lint + format + import sort |
|
|
60
|
+
| `npm run check:fix` | `check` with auto-fix |
|
|
221
61
|
|
|
222
|
-
|
|
223
|
-
- **IntelliSense Support**: Complete autocomplete and IntelliSense in modern IDEs
|
|
224
|
-
- **Compile-time Error Checking**: Catch errors at compile time rather than runtime
|
|
225
|
-
- **Modern ES Modules**: Uses ES modules with proper import/export syntax
|
|
226
|
-
- **Declaration Files**: Generated `.d.ts` files for all modules
|
|
62
|
+
---
|
|
227
63
|
|
|
228
|
-
|
|
64
|
+
## Pre-commit hook
|
|
229
65
|
|
|
230
|
-
|
|
66
|
+
Husky runs Biome on staged files:
|
|
231
67
|
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
LoggerOptions,
|
|
235
|
-
DnsCacheConfig,
|
|
236
|
-
HealthCheckOptions,
|
|
237
|
-
PaginationOptions
|
|
238
|
-
} from '@sera4/essentia';
|
|
239
|
-
|
|
240
|
-
// Use the types in your own interfaces
|
|
241
|
-
interface MyServiceConfig {
|
|
242
|
-
logger: LoggerOptions;
|
|
243
|
-
dns: DnsCacheConfig;
|
|
244
|
-
health: HealthCheckOptions;
|
|
245
|
-
}
|
|
68
|
+
```
|
|
69
|
+
npx biome check --write --staged --no-errors-on-unmatched
|
|
246
70
|
```
|
|
247
71
|
|
|
248
|
-
|
|
72
|
+
The hook is wired via `npm run prepare` (runs automatically after `npm install`).
|
|
249
73
|
|
|
250
|
-
|
|
74
|
+
---
|
|
251
75
|
|
|
252
|
-
|
|
253
|
-
2. Follow the existing TypeScript patterns and conventions
|
|
254
|
-
3. Include appropriate type definitions and interfaces
|
|
255
|
-
4. Add unit tests for new functionality
|
|
256
|
-
5. Update this documentation as needed
|
|
257
|
-
6. Ensure all code passes the TypeScript compiler with strict mode enabled
|
|
76
|
+
## Modules
|
|
258
77
|
|
|
259
|
-
|
|
78
|
+
All source lives under `ts/`. Each module exports from its `index.ts`.
|
|
260
79
|
|
|
261
|
-
|
|
80
|
+
| Module | Export | Description |
|
|
81
|
+
| ------------------- | ------------------------------------ | --------------------------------------------------------------------------- |
|
|
82
|
+
| `ts/logger` | `s4logger`, `S4Logger` | Winston logger with trace/debug/info/warn/error levels |
|
|
83
|
+
| `ts/utils` | `utils` | UUID validation, integer array parsing, route type detection, JSONB helpers |
|
|
84
|
+
| `ts/dns` | `DnsCache` | Singleton DNS lookup cache with configurable TTL |
|
|
85
|
+
| `ts/health` | `HealthCheck` | Health check endpoint utilities |
|
|
86
|
+
| `ts/cycle` | `CycleDeploymentWatcher` | Watches Cycle environment file for deployment tag changes |
|
|
87
|
+
| `ts/constants` | `TWS_ROUTE_TYPES` | Shared route type constants |
|
|
88
|
+
| `ts/helpers` | `helpers`, `TestServerWrapper` | Misc helpers, test HTTP server wrapper |
|
|
89
|
+
| `ts/paginator` | `sqlPaginator`, `paginator` | SQL and array pagination |
|
|
90
|
+
| `ts/cache` | `cache`, `Cache`, `CacheConfigError` | Redis cache wrapper (fakeredis supported for tests) |
|
|
91
|
+
| `ts/prompts` | `setupCLIParser`, `cliSleep`, etc. | CLI argument parsing and prompt utilities |
|
|
92
|
+
| `ts/safe-proxy` | `safeProxy` | Axios wrapper that forwards auth headers between services |
|
|
93
|
+
| `ts/queue` | `queue` | AMQP publisher/subscriber with fanout support |
|
|
94
|
+
| `ts/last-commit` | `lastCommit` | Read/write git branch + commit hash to a JSON file |
|
|
95
|
+
| `ts/hal` | `halDecorator` | Decorates Sequelize models with HAL-style `_links` / `_resource` |
|
|
96
|
+
| `ts/formatter` | `S4Formatter` | Error response formatting |
|
|
97
|
+
| `ts/serializer` | `serializer` | Sequelize model serialization |
|
|
98
|
+
| `ts/paper-trail` | `paperTrail` | Sequelize hook-based audit trail (diff + revision storage) |
|
|
262
99
|
|
|
263
|
-
|
|
264
|
-
```bash
|
|
265
|
-
npm install
|
|
266
|
-
```
|
|
100
|
+
---
|
|
267
101
|
|
|
268
|
-
|
|
269
|
-
```bash
|
|
270
|
-
npm test
|
|
271
|
-
```
|
|
102
|
+
## Usage
|
|
272
103
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
npm test -- --grep "DnsCache"
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
4. Run tests with coverage:
|
|
279
|
-
```bash
|
|
280
|
-
npm run test:coverage
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
5. Run tests in watch mode (useful during development):
|
|
284
|
-
```bash
|
|
285
|
-
npm run test:watch
|
|
286
|
-
```
|
|
104
|
+
```typescript
|
|
105
|
+
import { s4logger, utils, DnsCache, queue, safeProxy } from "@sera4/essentia";
|
|
287
106
|
|
|
288
|
-
|
|
107
|
+
s4logger.configure({ level: "info", service: "my-service", format: "json" });
|
|
108
|
+
s4logger.info("Starting");
|
|
289
109
|
|
|
290
|
-
|
|
291
|
-
- Use ES modules
|
|
292
|
-
- Support TypeScript
|
|
293
|
-
- Mock external dependencies
|
|
294
|
-
- Run tests in parallel
|
|
110
|
+
const isValid = utils.isValidUuidV4("26d61a82-3587-4875-98a8-e950e1bf2350");
|
|
295
111
|
|
|
296
|
-
|
|
112
|
+
const dns = DnsCache.getInstance();
|
|
113
|
+
const ip = await dns.resolveTarget("https://example.com");
|
|
297
114
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
2. Use `.test.ts` extension for TypeScript test files
|
|
301
|
-
3. Follow the existing test patterns and conventions
|
|
302
|
-
4. Use sinon for mocking and stubbing
|
|
303
|
-
5. Include both positive and negative test cases
|
|
304
|
-
6. Test edge cases and error conditions
|
|
115
|
+
await queue.openConnection({ connectionUrl: "amqp://localhost" });
|
|
116
|
+
await queue.publishMessage("my-exchange", "routing.key", { payload: true });
|
|
305
117
|
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
import { expect } from 'chai';
|
|
309
|
-
import sinon from 'sinon';
|
|
310
|
-
import { MyModule } from '../../ts/my-module';
|
|
311
|
-
|
|
312
|
-
describe('MyModule', () => {
|
|
313
|
-
let myModule: MyModule;
|
|
314
|
-
let mockDependency: sinon.SinonStub;
|
|
315
|
-
|
|
316
|
-
beforeEach(() => {
|
|
317
|
-
myModule = new MyModule();
|
|
318
|
-
mockDependency = sinon.stub();
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
afterEach(() => {
|
|
322
|
-
sinon.restore();
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
describe('someMethod', () => {
|
|
326
|
-
it('should do something', async () => {
|
|
327
|
-
mockDependency.resolves('expected result');
|
|
328
|
-
|
|
329
|
-
const result = await myModule.someMethod();
|
|
330
|
-
|
|
331
|
-
expect(result).to.equal('expected result');
|
|
332
|
-
expect(mockDependency.calledOnce).to.be.true;
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
});
|
|
118
|
+
await safeProxy.post(res.locals, "http://other-service/endpoint", { data: {} });
|
|
336
119
|
```
|
|
337
120
|
|
|
338
|
-
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Testing
|
|
339
124
|
|
|
340
|
-
|
|
125
|
+
Tests run manually — no CI pipeline yet.
|
|
341
126
|
|
|
342
127
|
```bash
|
|
343
|
-
npm
|
|
128
|
+
npm test # all tests
|
|
129
|
+
npm run test:coverage # HTML coverage report in coverage/
|
|
130
|
+
npm test -- --grep "DnsCache" # run a specific suite
|
|
344
131
|
```
|
|
345
132
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
- Create source maps for debugging
|
|
350
|
-
- Output everything to the `dist/` directory
|
|
133
|
+
Test files live in `test/`, mirroring the `ts/` structure. Files use the `.test.ts` naming convention.
|
|
134
|
+
|
|
135
|
+
---
|
|
351
136
|
|
|
352
137
|
## License
|
|
353
138
|
|
|
354
|
-
|
|
139
|
+
Proprietary. All rights reserved.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface CacheConfig {
|
|
2
|
+
password?: string;
|
|
3
|
+
database?: number;
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
fake?: boolean;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
declare class CacheConfigError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
declare class Cache {
|
|
13
|
+
private redisClient;
|
|
14
|
+
private servicePrefix;
|
|
15
|
+
constructor(config: CacheConfig, servicePrefix: string);
|
|
16
|
+
ping(): Promise<string>;
|
|
17
|
+
properKey(key: string, path?: string): string;
|
|
18
|
+
writeHash(_path: string, key: string, value: any): Promise<number>;
|
|
19
|
+
writeHashBlocking(_path: string, key: string, value: any): void;
|
|
20
|
+
writeMultiHash(path: string, j: Record<string, any>): Promise<string>;
|
|
21
|
+
readHashKey(obj: string, key: string, autoConvert?: boolean): Promise<any>;
|
|
22
|
+
readHash(path: string): Promise<Record<string, any> | null>;
|
|
23
|
+
deleteHashKey(hash: string, key: string): Promise<void>;
|
|
24
|
+
get(key: string): Promise<string | null>;
|
|
25
|
+
getAsync(key: string): Promise<string | null>;
|
|
26
|
+
writeKey(key: string, value: any, expiry?: number): Promise<string>;
|
|
27
|
+
deleteKey(key: string): Promise<number>;
|
|
28
|
+
expire(path: string, expiry: number): Promise<number>;
|
|
29
|
+
exists(path: string): Promise<number>;
|
|
30
|
+
deleteAllKeys(): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export default function (config: CacheConfig, servicePrefix: string): Cache;
|
|
33
|
+
export { Cache, CacheConfigError };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { S4Logger } from "../logger/s4-logger.js";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const logger = new S4Logger();
|
|
6
|
+
const isEmpty = (obj) => {
|
|
7
|
+
return [Object, Array].includes(obj?.constructor) && !Object.entries(obj || {}).length;
|
|
8
|
+
};
|
|
9
|
+
class CacheConfigError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "CacheConfigError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/* Redis cache helper
|
|
16
|
+
* Example use: Place the following code in your project to setup redis use
|
|
17
|
+
*
|
|
18
|
+
* import { cache } from "@sera4/essentia";
|
|
19
|
+
* const cacheHelper = cache({ password: .., database: .., host: .., port: .. }, "service:"));
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
const convertRedisKeyValueToJson = (v) => {
|
|
23
|
+
return v && v.length > 0 && v[0] !== null ? JSON.parse(v) : null;
|
|
24
|
+
};
|
|
25
|
+
const convertRedisHashToJson = (v) => {
|
|
26
|
+
if (v) {
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const k in v) {
|
|
29
|
+
if (Object.hasOwn(v, k)) {
|
|
30
|
+
result[k] = JSON.parse(v[k]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function convertJsonToRedisValue(j) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const key in j) {
|
|
42
|
+
if (Object.hasOwn(j, key)) {
|
|
43
|
+
result[key] = JSON.stringify(j[key]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
class Cache {
|
|
49
|
+
redisClient;
|
|
50
|
+
servicePrefix;
|
|
51
|
+
constructor(config, servicePrefix) {
|
|
52
|
+
if (config === undefined || isEmpty(config)) {
|
|
53
|
+
throw new CacheConfigError("Invalid cache configuration");
|
|
54
|
+
}
|
|
55
|
+
if (servicePrefix == null) {
|
|
56
|
+
throw new CacheConfigError("Invalid service prefix");
|
|
57
|
+
}
|
|
58
|
+
if (config.fake) {
|
|
59
|
+
// used in testing
|
|
60
|
+
this.redisClient = require("fakeredis").createClient("fake backend");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.redisClient = require("redis").createClient(config);
|
|
64
|
+
}
|
|
65
|
+
this.redisClient.on("error", (err) => {
|
|
66
|
+
logger.error("Cache/Redis error: ", err);
|
|
67
|
+
});
|
|
68
|
+
this.servicePrefix = servicePrefix;
|
|
69
|
+
}
|
|
70
|
+
ping() {
|
|
71
|
+
return this.redisClient.ping();
|
|
72
|
+
}
|
|
73
|
+
properKey(key, path) {
|
|
74
|
+
let p = this.servicePrefix + key;
|
|
75
|
+
if (path) {
|
|
76
|
+
p += path;
|
|
77
|
+
}
|
|
78
|
+
return p;
|
|
79
|
+
}
|
|
80
|
+
writeHash(_path, key, value) {
|
|
81
|
+
return this.redisClient.hsetAsync(this.properKey(key), key, value);
|
|
82
|
+
}
|
|
83
|
+
writeHashBlocking(_path, key, value) {
|
|
84
|
+
this.redisClient.hset(this.properKey(key), key, value);
|
|
85
|
+
}
|
|
86
|
+
// write multiple hash keys and stringify every value
|
|
87
|
+
writeMultiHash(path, j) {
|
|
88
|
+
return this.redisClient.hmsetAsync(this.properKey(path), convertJsonToRedisValue(j));
|
|
89
|
+
}
|
|
90
|
+
readHashKey(obj, key, autoConvert = true) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
this.redisClient.hget(this.properKey(key, obj), key, (e, r) => {
|
|
93
|
+
if (e) {
|
|
94
|
+
logger.error("Redis hmget error:", e);
|
|
95
|
+
reject(e);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
resolve(autoConvert ? convertRedisKeyValueToJson(r) : r);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
readHash(path) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
this.redisClient.hgetall(this.properKey(path), (e, r) => {
|
|
106
|
+
if (e) {
|
|
107
|
+
console.log("Redis hget error:", e);
|
|
108
|
+
reject(e);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
resolve(convertRedisHashToJson(r));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
deleteHashKey(hash, key) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
this.redisClient.hdel(this.properKey(key, hash), key, (err) => {
|
|
119
|
+
if (err) {
|
|
120
|
+
logger.error("Redis hdel error:", err);
|
|
121
|
+
reject(err);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
get(key) {
|
|
130
|
+
return this.redisClient.getAsync(this.properKey(key));
|
|
131
|
+
}
|
|
132
|
+
getAsync(key) {
|
|
133
|
+
return promisify(this.redisClient.get.bind(this.redisClient))(this.properKey(key));
|
|
134
|
+
}
|
|
135
|
+
/* a single key write with optional expiry */
|
|
136
|
+
// returns bluebird promise
|
|
137
|
+
writeKey(key, value, expiry) {
|
|
138
|
+
const fullKey = this.servicePrefix + key;
|
|
139
|
+
if (expiry) {
|
|
140
|
+
return this.redisClient.setAsync(fullKey, value, "EX", expiry);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
return this.redisClient.setAsync(fullKey, value);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
deleteKey(key) {
|
|
147
|
+
return this.redisClient.delAsync(this.properKey(key));
|
|
148
|
+
}
|
|
149
|
+
expire(path, expiry) {
|
|
150
|
+
return this.redisClient.expire(path, expiry);
|
|
151
|
+
}
|
|
152
|
+
exists(path) {
|
|
153
|
+
return this.redisClient.exists(path);
|
|
154
|
+
}
|
|
155
|
+
deleteAllKeys() {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
if (process.env.NODE_ENV === "production") {
|
|
158
|
+
throw new Error("Clearing system cache is not permitted in production");
|
|
159
|
+
}
|
|
160
|
+
this.redisClient.flushdb((err, res) => {
|
|
161
|
+
if (err) {
|
|
162
|
+
reject(err);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
resolve(res);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export default function (config, servicePrefix) {
|
|
172
|
+
return new Cache(config, servicePrefix);
|
|
173
|
+
}
|
|
174
|
+
export { Cache, CacheConfigError };
|
|
175
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../ts/cache/index.ts"],"names":[],"mappings":"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"}
|