@heavybit/shared-lib 0.0.0-stage → 1.1.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/dist/cjs/access/index.js +118 -0
- package/dist/cjs/access/index.js.map +1 -0
- package/dist/cjs/alerting/index.js +56 -0
- package/dist/cjs/alerting/index.js.map +1 -0
- package/dist/cjs/cache/index.js +167 -0
- package/dist/cjs/cache/index.js.map +1 -0
- package/dist/cjs/common/async.js +40 -0
- package/dist/cjs/common/async.js.map +1 -0
- package/dist/cjs/common/audit.js +21 -0
- package/dist/cjs/common/audit.js.map +1 -0
- package/dist/cjs/common/crypto.js +75 -0
- package/dist/cjs/common/crypto.js.map +1 -0
- package/dist/cjs/common/errors.js +90 -0
- package/dist/cjs/common/errors.js.map +1 -0
- package/dist/cjs/common/id.js +40 -0
- package/dist/cjs/common/id.js.map +1 -0
- package/dist/cjs/common/index.js +28 -0
- package/dist/cjs/common/index.js.map +1 -0
- package/dist/cjs/common/jwt.js +59 -0
- package/dist/cjs/common/jwt.js.map +1 -0
- package/dist/cjs/common/password.js +38 -0
- package/dist/cjs/common/password.js.map +1 -0
- package/dist/cjs/common/phone.js +42 -0
- package/dist/cjs/common/phone.js.map +1 -0
- package/dist/cjs/common/response.js +32 -0
- package/dist/cjs/common/response.js.map +1 -0
- package/dist/cjs/common/text.js +46 -0
- package/dist/cjs/common/text.js.map +1 -0
- package/dist/cjs/common/time.js +109 -0
- package/dist/cjs/common/time.js.map +1 -0
- package/dist/cjs/config/index.js +96 -0
- package/dist/cjs/config/index.js.map +1 -0
- package/dist/cjs/contracts/editorial-draft.js +111 -0
- package/dist/cjs/contracts/editorial-draft.js.map +1 -0
- package/dist/cjs/contracts/index.js +20 -0
- package/dist/cjs/contracts/index.js.map +1 -0
- package/dist/cjs/contracts/notification-dispatch.js +92 -0
- package/dist/cjs/contracts/notification-dispatch.js.map +1 -0
- package/dist/cjs/contracts/upload-import.js +54 -0
- package/dist/cjs/contracts/upload-import.js.map +1 -0
- package/dist/cjs/database/index.js +57 -0
- package/dist/cjs/database/index.js.map +1 -0
- package/dist/cjs/express/auth.js +72 -0
- package/dist/cjs/express/auth.js.map +1 -0
- package/dist/cjs/express/health.js +31 -0
- package/dist/cjs/express/health.js.map +1 -0
- package/dist/cjs/express/index.js +20 -0
- package/dist/cjs/express/index.js.map +1 -0
- package/dist/cjs/express/middleware.js +95 -0
- package/dist/cjs/express/middleware.js.map +1 -0
- package/dist/cjs/http/index.js +90 -0
- package/dist/cjs/http/index.js.map +1 -0
- package/dist/cjs/http/safe-fetch.js +199 -0
- package/dist/cjs/http/safe-fetch.js.map +1 -0
- package/dist/cjs/identity/index.js +117 -0
- package/dist/cjs/identity/index.js.map +1 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/knex/index.js +76 -0
- package/dist/cjs/knex/index.js.map +1 -0
- package/dist/cjs/logging/index.js +75 -0
- package/dist/cjs/logging/index.js.map +1 -0
- package/dist/cjs/messaging/connection.js +115 -0
- package/dist/cjs/messaging/connection.js.map +1 -0
- package/dist/cjs/messaging/consumer.js +134 -0
- package/dist/cjs/messaging/consumer.js.map +1 -0
- package/dist/cjs/messaging/envelope.js +23 -0
- package/dist/cjs/messaging/envelope.js.map +1 -0
- package/dist/cjs/messaging/idempotency.js +57 -0
- package/dist/cjs/messaging/idempotency.js.map +1 -0
- package/dist/cjs/messaging/index.js +62 -0
- package/dist/cjs/messaging/index.js.map +1 -0
- package/dist/cjs/messaging/monitor.js +48 -0
- package/dist/cjs/messaging/monitor.js.map +1 -0
- package/dist/cjs/messaging/outbox.js +135 -0
- package/dist/cjs/messaging/outbox.js.map +1 -0
- package/dist/cjs/messaging/publisher.js +149 -0
- package/dist/cjs/messaging/publisher.js.map +1 -0
- package/dist/cjs/messaging/rpc.js +125 -0
- package/dist/cjs/messaging/rpc.js.map +1 -0
- package/dist/cjs/messaging/staff-notifier.js +78 -0
- package/dist/cjs/messaging/staff-notifier.js.map +1 -0
- package/dist/cjs/messaging/topology.js +22 -0
- package/dist/cjs/messaging/topology.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rate-limit/index.js +61 -0
- package/dist/cjs/rate-limit/index.js.map +1 -0
- package/dist/cjs/scheduling/index.js +88 -0
- package/dist/cjs/scheduling/index.js.map +1 -0
- package/dist/cjs/security/index.js +39 -0
- package/dist/cjs/security/index.js.map +1 -0
- package/dist/cjs/socket/index.js +53 -0
- package/dist/cjs/socket/index.js.map +1 -0
- package/dist/cjs/testing/index.js +73 -0
- package/dist/cjs/testing/index.js.map +1 -0
- package/dist/cjs/types/index.js +3 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/validation/index.js +41 -0
- package/dist/cjs/validation/index.js.map +1 -0
- package/dist/esm/access/index.js +104 -0
- package/dist/esm/access/index.js.map +1 -0
- package/dist/esm/alerting/index.js +52 -0
- package/dist/esm/alerting/index.js.map +1 -0
- package/dist/esm/cache/index.js +163 -0
- package/dist/esm/cache/index.js.map +1 -0
- package/dist/esm/common/async.js +33 -0
- package/dist/esm/common/async.js.map +1 -0
- package/dist/esm/common/audit.js +18 -0
- package/dist/esm/common/audit.js.map +1 -0
- package/dist/esm/common/crypto.js +64 -0
- package/dist/esm/common/crypto.js.map +1 -0
- package/dist/esm/common/errors.js +75 -0
- package/dist/esm/common/errors.js.map +1 -0
- package/dist/esm/common/id.js +33 -0
- package/dist/esm/common/id.js.map +1 -0
- package/dist/esm/common/index.js +12 -0
- package/dist/esm/common/index.js.map +1 -0
- package/dist/esm/common/jwt.js +49 -0
- package/dist/esm/common/jwt.js.map +1 -0
- package/dist/esm/common/password.js +30 -0
- package/dist/esm/common/password.js.map +1 -0
- package/dist/esm/common/phone.js +33 -0
- package/dist/esm/common/phone.js.map +1 -0
- package/dist/esm/common/response.js +24 -0
- package/dist/esm/common/response.js.map +1 -0
- package/dist/esm/common/text.js +39 -0
- package/dist/esm/common/text.js.map +1 -0
- package/dist/esm/common/time.js +96 -0
- package/dist/esm/common/time.js.map +1 -0
- package/dist/esm/config/index.js +88 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/contracts/editorial-draft.js +108 -0
- package/dist/esm/contracts/editorial-draft.js.map +1 -0
- package/dist/esm/contracts/index.js +4 -0
- package/dist/esm/contracts/index.js.map +1 -0
- package/dist/esm/contracts/notification-dispatch.js +89 -0
- package/dist/esm/contracts/notification-dispatch.js.map +1 -0
- package/dist/esm/contracts/upload-import.js +51 -0
- package/dist/esm/contracts/upload-import.js.map +1 -0
- package/dist/esm/database/index.js +48 -0
- package/dist/esm/database/index.js.map +1 -0
- package/dist/esm/express/auth.js +63 -0
- package/dist/esm/express/auth.js.map +1 -0
- package/dist/esm/express/health.js +27 -0
- package/dist/esm/express/health.js.map +1 -0
- package/dist/esm/express/index.js +4 -0
- package/dist/esm/express/index.js.map +1 -0
- package/dist/esm/express/middleware.js +84 -0
- package/dist/esm/express/middleware.js.map +1 -0
- package/dist/esm/http/index.js +73 -0
- package/dist/esm/http/index.js.map +1 -0
- package/dist/esm/http/safe-fetch.js +193 -0
- package/dist/esm/http/safe-fetch.js.map +1 -0
- package/dist/esm/identity/index.js +111 -0
- package/dist/esm/identity/index.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/knex/index.js +66 -0
- package/dist/esm/knex/index.js.map +1 -0
- package/dist/esm/logging/index.js +67 -0
- package/dist/esm/logging/index.js.map +1 -0
- package/dist/esm/messaging/connection.js +108 -0
- package/dist/esm/messaging/connection.js.map +1 -0
- package/dist/esm/messaging/consumer.js +130 -0
- package/dist/esm/messaging/consumer.js.map +1 -0
- package/dist/esm/messaging/envelope.js +19 -0
- package/dist/esm/messaging/envelope.js.map +1 -0
- package/dist/esm/messaging/idempotency.js +52 -0
- package/dist/esm/messaging/idempotency.js.map +1 -0
- package/dist/esm/messaging/index.js +45 -0
- package/dist/esm/messaging/index.js.map +1 -0
- package/dist/esm/messaging/monitor.js +44 -0
- package/dist/esm/messaging/monitor.js.map +1 -0
- package/dist/esm/messaging/outbox.js +129 -0
- package/dist/esm/messaging/outbox.js.map +1 -0
- package/dist/esm/messaging/publisher.js +142 -0
- package/dist/esm/messaging/publisher.js.map +1 -0
- package/dist/esm/messaging/rpc.js +120 -0
- package/dist/esm/messaging/rpc.js.map +1 -0
- package/dist/esm/messaging/staff-notifier.js +73 -0
- package/dist/esm/messaging/staff-notifier.js.map +1 -0
- package/dist/esm/messaging/topology.js +16 -0
- package/dist/esm/messaging/topology.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/rate-limit/index.js +56 -0
- package/dist/esm/rate-limit/index.js.map +1 -0
- package/dist/esm/scheduling/index.js +83 -0
- package/dist/esm/scheduling/index.js.map +1 -0
- package/dist/esm/security/index.js +36 -0
- package/dist/esm/security/index.js.map +1 -0
- package/dist/esm/socket/index.js +47 -0
- package/dist/esm/socket/index.js.map +1 -0
- package/dist/esm/testing/index.js +66 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/types/index.js +2 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/validation/index.js +36 -0
- package/dist/esm/validation/index.js.map +1 -0
- package/dist/types/access/index.d.ts +50 -0
- package/dist/types/access/index.d.ts.map +1 -0
- package/dist/types/alerting/index.d.ts +43 -0
- package/dist/types/alerting/index.d.ts.map +1 -0
- package/dist/types/cache/index.d.ts +45 -0
- package/dist/types/cache/index.d.ts.map +1 -0
- package/dist/types/common/async.d.ts +14 -0
- package/dist/types/common/async.d.ts.map +1 -0
- package/dist/types/common/audit.d.ts +17 -0
- package/dist/types/common/audit.d.ts.map +1 -0
- package/dist/types/common/crypto.d.ts +18 -0
- package/dist/types/common/crypto.d.ts.map +1 -0
- package/dist/types/common/errors.d.ts +71 -0
- package/dist/types/common/errors.d.ts.map +1 -0
- package/dist/types/common/id.d.ts +14 -0
- package/dist/types/common/id.d.ts.map +1 -0
- package/dist/types/common/index.d.ts +12 -0
- package/dist/types/common/index.d.ts.map +1 -0
- package/dist/types/common/jwt.d.ts +28 -0
- package/dist/types/common/jwt.d.ts.map +1 -0
- package/dist/types/common/password.d.ts +4 -0
- package/dist/types/common/password.d.ts.map +1 -0
- package/dist/types/common/phone.d.ts +10 -0
- package/dist/types/common/phone.d.ts.map +1 -0
- package/dist/types/common/response.d.ts +12 -0
- package/dist/types/common/response.d.ts.map +1 -0
- package/dist/types/common/text.d.ts +10 -0
- package/dist/types/common/text.d.ts.map +1 -0
- package/dist/types/common/time.d.ts +40 -0
- package/dist/types/common/time.d.ts.map +1 -0
- package/dist/types/config/index.d.ts +115 -0
- package/dist/types/config/index.d.ts.map +1 -0
- package/dist/types/contracts/editorial-draft.d.ts +577 -0
- package/dist/types/contracts/editorial-draft.d.ts.map +1 -0
- package/dist/types/contracts/index.d.ts +4 -0
- package/dist/types/contracts/index.d.ts.map +1 -0
- package/dist/types/contracts/notification-dispatch.d.ts +175 -0
- package/dist/types/contracts/notification-dispatch.d.ts.map +1 -0
- package/dist/types/contracts/upload-import.d.ts +96 -0
- package/dist/types/contracts/upload-import.d.ts.map +1 -0
- package/dist/types/database/index.d.ts +25 -0
- package/dist/types/database/index.d.ts.map +1 -0
- package/dist/types/express/auth.d.ts +27 -0
- package/dist/types/express/auth.d.ts.map +1 -0
- package/dist/types/express/health.d.ts +14 -0
- package/dist/types/express/health.d.ts.map +1 -0
- package/dist/types/express/index.d.ts +4 -0
- package/dist/types/express/index.d.ts.map +1 -0
- package/dist/types/express/middleware.d.ts +20 -0
- package/dist/types/express/middleware.d.ts.map +1 -0
- package/dist/types/http/index.d.ts +34 -0
- package/dist/types/http/index.d.ts.map +1 -0
- package/dist/types/http/safe-fetch.d.ts +58 -0
- package/dist/types/http/safe-fetch.d.ts.map +1 -0
- package/dist/types/identity/index.d.ts +51 -0
- package/dist/types/identity/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/knex/index.d.ts +25 -0
- package/dist/types/knex/index.d.ts.map +1 -0
- package/dist/types/logging/index.d.ts +22 -0
- package/dist/types/logging/index.d.ts.map +1 -0
- package/dist/types/messaging/connection.d.ts +48 -0
- package/dist/types/messaging/connection.d.ts.map +1 -0
- package/dist/types/messaging/consumer.d.ts +56 -0
- package/dist/types/messaging/consumer.d.ts.map +1 -0
- package/dist/types/messaging/envelope.d.ts +16 -0
- package/dist/types/messaging/envelope.d.ts.map +1 -0
- package/dist/types/messaging/idempotency.d.ts +29 -0
- package/dist/types/messaging/idempotency.d.ts.map +1 -0
- package/dist/types/messaging/index.d.ts +39 -0
- package/dist/types/messaging/index.d.ts.map +1 -0
- package/dist/types/messaging/monitor.d.ts +23 -0
- package/dist/types/messaging/monitor.d.ts.map +1 -0
- package/dist/types/messaging/outbox.d.ts +51 -0
- package/dist/types/messaging/outbox.d.ts.map +1 -0
- package/dist/types/messaging/publisher.d.ts +84 -0
- package/dist/types/messaging/publisher.d.ts.map +1 -0
- package/dist/types/messaging/rpc.d.ts +46 -0
- package/dist/types/messaging/rpc.d.ts.map +1 -0
- package/dist/types/messaging/staff-notifier.d.ts +56 -0
- package/dist/types/messaging/staff-notifier.d.ts.map +1 -0
- package/dist/types/messaging/topology.d.ts +23 -0
- package/dist/types/messaging/topology.d.ts.map +1 -0
- package/dist/types/rate-limit/index.d.ts +28 -0
- package/dist/types/rate-limit/index.d.ts.map +1 -0
- package/dist/types/scheduling/index.d.ts +33 -0
- package/dist/types/scheduling/index.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +20 -0
- package/dist/types/security/index.d.ts.map +1 -0
- package/dist/types/socket/index.d.ts +30 -0
- package/dist/types/socket/index.d.ts.map +1 -0
- package/dist/types/testing/index.d.ts +41 -0
- package/dist/types/testing/index.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +65 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/validation/index.d.ts +34 -0
- package/dist/types/validation/index.d.ts.map +1 -0
- package/package.json +189 -4
- package/README.md +0 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomInt, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
export function sha256(value) {
|
|
3
|
+
return createHash('sha256').update(value).digest('hex');
|
|
4
|
+
}
|
|
5
|
+
export function hmacSha256(secret, value) {
|
|
6
|
+
return createHmac('sha256', secret).update(value).digest('hex');
|
|
7
|
+
}
|
|
8
|
+
/** Constant-time string comparison (false when lengths differ). */
|
|
9
|
+
export function safeEqual(a, b) {
|
|
10
|
+
const left = Buffer.from(a);
|
|
11
|
+
const right = Buffer.from(b);
|
|
12
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
13
|
+
}
|
|
14
|
+
/** URL-safe random token (default 32 bytes of entropy). */
|
|
15
|
+
export function randomToken(bytes = 32) {
|
|
16
|
+
return randomBytes(bytes).toString('base64url');
|
|
17
|
+
}
|
|
18
|
+
/** Numeric one-time code, e.g. `'042917'`. Uses a CSPRNG. */
|
|
19
|
+
export function randomNumericCode(length = 6) {
|
|
20
|
+
let code = '';
|
|
21
|
+
for (let i = 0; i < length; i += 1)
|
|
22
|
+
code += String(randomInt(0, 10));
|
|
23
|
+
return code;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Hash a value that must be looked up later (refresh tokens, reset tokens, OTPs) with a
|
|
27
|
+
* server-side pepper so a database leak alone cannot be replayed.
|
|
28
|
+
*/
|
|
29
|
+
export function hashToken(token, pepper) {
|
|
30
|
+
return hmacSha256(pepper, token);
|
|
31
|
+
}
|
|
32
|
+
const CIPHER = 'aes-256-gcm';
|
|
33
|
+
const IV_BYTES = 12;
|
|
34
|
+
const FORMAT_VERSION = 'v1';
|
|
35
|
+
/** Accepts a 32-byte key as hex/base64, or any passphrase (derived with SHA-256). */
|
|
36
|
+
function resolveKey(key) {
|
|
37
|
+
if (/^[0-9a-f]{64}$/i.test(key))
|
|
38
|
+
return Buffer.from(key, 'hex');
|
|
39
|
+
const b64 = Buffer.from(key, 'base64');
|
|
40
|
+
if (b64.length === 32 && /^[A-Za-z0-9+/=_-]+$/.test(key))
|
|
41
|
+
return b64;
|
|
42
|
+
return createHash('sha256').update(key).digest();
|
|
43
|
+
}
|
|
44
|
+
/** AES-256-GCM encrypt. Output: `v1:<iv>:<tag>:<ciphertext>` (base64url parts). */
|
|
45
|
+
export function encryptSecret(plaintext, key) {
|
|
46
|
+
const iv = randomBytes(IV_BYTES);
|
|
47
|
+
const cipher = createCipheriv(CIPHER, resolveKey(key), iv);
|
|
48
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
49
|
+
const tag = cipher.getAuthTag();
|
|
50
|
+
return [FORMAT_VERSION, iv.toString('base64url'), tag.toString('base64url'), encrypted.toString('base64url')].join(':');
|
|
51
|
+
}
|
|
52
|
+
export function decryptSecret(payload, key) {
|
|
53
|
+
const [version, iv, tag, data] = payload.split(':');
|
|
54
|
+
if (version !== FORMAT_VERSION || !iv || !tag || data === undefined) {
|
|
55
|
+
throw new Error('Unsupported encrypted payload format');
|
|
56
|
+
}
|
|
57
|
+
const decipher = createDecipheriv(CIPHER, resolveKey(key), Buffer.from(iv, 'base64url'));
|
|
58
|
+
decipher.setAuthTag(Buffer.from(tag, 'base64url'));
|
|
59
|
+
return Buffer.concat([decipher.update(Buffer.from(data, 'base64url')), decipher.final()]).toString('utf8');
|
|
60
|
+
}
|
|
61
|
+
export function isEncryptedSecret(value) {
|
|
62
|
+
return typeof value === 'string' && value.startsWith(`${FORMAT_VERSION}:`) && value.split(':').length === 4;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/common/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for errors that map to an HTTP status. Throw these from services; the shared
|
|
3
|
+
* error middleware turns them into the standard envelope. Anything that is not an AppError is
|
|
4
|
+
* reported as a 500 with a generic message so internals never leak.
|
|
5
|
+
*/
|
|
6
|
+
export class AppError extends Error {
|
|
7
|
+
statusCode;
|
|
8
|
+
code;
|
|
9
|
+
errors;
|
|
10
|
+
/** Consumers dead-letter immediately instead of retrying when this is false. */
|
|
11
|
+
retryable;
|
|
12
|
+
constructor(statusCode, message, options = {}) {
|
|
13
|
+
super(message, options.cause ? { cause: options.cause } : undefined);
|
|
14
|
+
this.name = new.target.name;
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.code = options.code ?? defaultCode(statusCode);
|
|
17
|
+
this.errors = options.errors;
|
|
18
|
+
this.retryable = options.retryable ?? statusCode >= 500;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function defaultCode(status) {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case 400: return 'BAD_REQUEST';
|
|
24
|
+
case 401: return 'UNAUTHENTICATED';
|
|
25
|
+
case 403: return 'FORBIDDEN';
|
|
26
|
+
case 404: return 'NOT_FOUND';
|
|
27
|
+
case 409: return 'CONFLICT';
|
|
28
|
+
case 422: return 'VALIDATION_FAILED';
|
|
29
|
+
case 429: return 'RATE_LIMITED';
|
|
30
|
+
case 503: return 'SERVICE_UNAVAILABLE';
|
|
31
|
+
default: return status >= 500 ? 'INTERNAL_ERROR' : 'ERROR';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class BadRequestError extends AppError {
|
|
35
|
+
constructor(message = 'Bad request', options = {}) { super(400, message, options); }
|
|
36
|
+
}
|
|
37
|
+
export class UnauthorizedError extends AppError {
|
|
38
|
+
constructor(message = 'Authentication required', options = {}) { super(401, message, options); }
|
|
39
|
+
}
|
|
40
|
+
export class ForbiddenError extends AppError {
|
|
41
|
+
constructor(message = 'You do not have permission to perform this action', options = {}) { super(403, message, options); }
|
|
42
|
+
}
|
|
43
|
+
export class NotFoundError extends AppError {
|
|
44
|
+
constructor(message = 'Resource not found', options = {}) { super(404, message, options); }
|
|
45
|
+
}
|
|
46
|
+
export class ConflictError extends AppError {
|
|
47
|
+
constructor(message = 'Resource already exists', options = {}) { super(409, message, options); }
|
|
48
|
+
}
|
|
49
|
+
export class ValidationError extends AppError {
|
|
50
|
+
constructor(errors, message = 'Validation failed') { super(422, message, { errors }); }
|
|
51
|
+
}
|
|
52
|
+
export class TooManyRequestsError extends AppError {
|
|
53
|
+
constructor(message = 'Too many requests. Please try again later.', options = {}) { super(429, message, options); }
|
|
54
|
+
}
|
|
55
|
+
export class ServiceUnavailableError extends AppError {
|
|
56
|
+
constructor(message = 'Service temporarily unavailable', options = {}) { super(503, message, options); }
|
|
57
|
+
}
|
|
58
|
+
/** A failure that will never succeed on retry (bad payload, unknown template, …). */
|
|
59
|
+
export class NonRetryableError extends AppError {
|
|
60
|
+
constructor(message, options = {}) {
|
|
61
|
+
super(422, message, { ...options, retryable: false });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function isAppError(error) {
|
|
65
|
+
return error instanceof AppError;
|
|
66
|
+
}
|
|
67
|
+
/** Message safe to show a caller; hides internals for unexpected errors. */
|
|
68
|
+
export function publicMessage(error) {
|
|
69
|
+
if (isAppError(error) && error.statusCode < 500)
|
|
70
|
+
return error.message;
|
|
71
|
+
if (isAppError(error) && error.statusCode === 503)
|
|
72
|
+
return error.message;
|
|
73
|
+
return 'Internal server error';
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/common/errors.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,UAAU,CAAS;IACnB,IAAI,CAAS;IACb,MAAM,CAAgB;IAC/B,gFAAgF;IACvE,SAAS,CAAU;IAE5B,YAAY,UAAkB,EAAE,OAAe,EAAE,UAA0F,EAAE;QAC3I,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,IAAI,GAAG,CAAC;IAC1D,CAAC;CACF;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,GAAG,CAAC,CAAC,OAAO,aAAa,CAAC;QAC/B,KAAK,GAAG,CAAC,CAAC,OAAO,iBAAiB,CAAC;QACnC,KAAK,GAAG,CAAC,CAAC,OAAO,WAAW,CAAC;QAC7B,KAAK,GAAG,CAAC,CAAC,OAAO,WAAW,CAAC;QAC7B,KAAK,GAAG,CAAC,CAAC,OAAO,UAAU,CAAC;QAC5B,KAAK,GAAG,CAAC,CAAC,OAAO,mBAAmB,CAAC;QACrC,KAAK,GAAG,CAAC,CAAC,OAAO,cAAc,CAAC;QAChC,KAAK,GAAG,CAAC,CAAC,OAAO,qBAAqB,CAAC;QACvC,OAAO,CAAC,CAAC,OAAO,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAC3C,YAAY,OAAO,GAAG,aAAa,EAAE,UAAoD,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/H;AACD,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IAC7C,YAAY,OAAO,GAAG,yBAAyB,EAAE,UAA6B,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACpH;AACD,MAAM,OAAO,cAAe,SAAQ,QAAQ;IAC1C,YAAY,OAAO,GAAG,mDAAmD,EAAE,UAA6B,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9I;AACD,MAAM,OAAO,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAO,GAAG,oBAAoB,EAAE,UAA6B,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/G;AACD,MAAM,OAAO,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAO,GAAG,yBAAyB,EAAE,UAAoD,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3I;AACD,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAC3C,YAAY,MAAoB,EAAE,OAAO,GAAG,mBAAmB,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;CACtG;AACD,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD,YAAY,OAAO,GAAG,4CAA4C,EAAE,UAA6B,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACvI;AACD,MAAM,OAAO,uBAAwB,SAAQ,QAAQ;IACnD,YAAY,OAAO,GAAG,iCAAiC,EAAE,UAA8C,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7I;AAED,qFAAqF;AACrF,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IAC7C,YAAY,OAAe,EAAE,UAA8C,EAAE;QAC3E,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,KAAK,YAAY,QAAQ,CAAC;AACnC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACtE,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACxE,OAAO,uBAAuB,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { monotonicFactory, decodeTime } from 'ulid';
|
|
2
|
+
/**
|
|
3
|
+
* Primary keys are ULIDs: 26-char Crockford base32, lexicographically sortable by creation
|
|
4
|
+
* time, so B-tree inserts stay append-only (unlike random UUIDv4) and IDs double as a coarse
|
|
5
|
+
* creation timestamp. Store them in `CHAR(26)` columns.
|
|
6
|
+
*/
|
|
7
|
+
const nextUlid = monotonicFactory();
|
|
8
|
+
export const ID_LENGTH = 26;
|
|
9
|
+
export const ID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
10
|
+
/** A new time-ordered, monotonic ULID. */
|
|
11
|
+
export function newId() {
|
|
12
|
+
return nextUlid();
|
|
13
|
+
}
|
|
14
|
+
export function isId(value) {
|
|
15
|
+
return typeof value === 'string' && ID_PATTERN.test(value);
|
|
16
|
+
}
|
|
17
|
+
/** Creation time encoded in a ULID. */
|
|
18
|
+
export function idTimestamp(id) {
|
|
19
|
+
return new Date(decodeTime(id));
|
|
20
|
+
}
|
|
21
|
+
const FIXTURE_PREFIX = '01K6SEED';
|
|
22
|
+
/**
|
|
23
|
+
* A stable, valid ID for seed/fixture rows: the same `(namespace, n)` always yields the same ID, so
|
|
24
|
+
* seeds in different services can reference each other's rows without sharing a database.
|
|
25
|
+
* `namespace` is two Crockford characters (e.g. `PR` for profiles).
|
|
26
|
+
*/
|
|
27
|
+
export function fixtureId(namespace, n) {
|
|
28
|
+
const id = `${FIXTURE_PREFIX}${namespace.toUpperCase()}${String(n).padStart(ID_LENGTH - FIXTURE_PREFIX.length - namespace.length, '0')}`;
|
|
29
|
+
if (!ID_PATTERN.test(id))
|
|
30
|
+
throw new Error(`Invalid fixture id namespace/sequence: ${namespace}/${n}`);
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../../src/common/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEpD;;;;GAIG;AACH,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;AAEpC,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAErD,0CAA0C;AAC1C,MAAM,UAAU,KAAK;IACnB,OAAO,QAAQ,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,CAAS;IACpD,MAAM,EAAE,GAAG,GAAG,cAAc,GAAG,SAAS,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,GAAG,cAAc,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;IACzI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,SAAS,IAAI,CAAC,EAAE,CAAC,CAAC;IACtG,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './id.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './response.js';
|
|
4
|
+
export * from './crypto.js';
|
|
5
|
+
export * from './password.js';
|
|
6
|
+
export * from './jwt.js';
|
|
7
|
+
export * from './text.js';
|
|
8
|
+
export * from './async.js';
|
|
9
|
+
export * from './phone.js';
|
|
10
|
+
export * from './time.js';
|
|
11
|
+
export * from './audit.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
export function signJwt(payload, secret, options) {
|
|
3
|
+
return jwt.sign(payload, secret, {
|
|
4
|
+
algorithm: 'HS256',
|
|
5
|
+
expiresIn: options.expiresIn,
|
|
6
|
+
...(options.issuer ? { issuer: options.issuer } : {}),
|
|
7
|
+
...(options.audience ? { audience: options.audience } : {}),
|
|
8
|
+
...(options.jwtId ? { jwtid: options.jwtId } : {}),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** Verifies signature, expiry and (when given) issuer/audience. Throws on any failure. */
|
|
12
|
+
export function verifyJwt(token, secret, options = {}) {
|
|
13
|
+
const decoded = jwt.verify(token, secret, {
|
|
14
|
+
algorithms: ['HS256'],
|
|
15
|
+
...(options.issuer ? { issuer: options.issuer } : {}),
|
|
16
|
+
...(options.audience ? { audience: options.audience } : {}),
|
|
17
|
+
});
|
|
18
|
+
if (typeof decoded === 'string' || !decoded.sub)
|
|
19
|
+
throw new Error('Invalid token payload');
|
|
20
|
+
return decoded;
|
|
21
|
+
}
|
|
22
|
+
export function decodeJwt(token) {
|
|
23
|
+
const decoded = jwt.decode(token);
|
|
24
|
+
return decoded && typeof decoded !== 'string' ? decoded : null;
|
|
25
|
+
}
|
|
26
|
+
export function extractBearerToken(header) {
|
|
27
|
+
if (!header)
|
|
28
|
+
return null;
|
|
29
|
+
const [scheme, token] = header.split(' ');
|
|
30
|
+
return scheme?.toLowerCase() === 'bearer' && token ? token.trim() : null;
|
|
31
|
+
}
|
|
32
|
+
/** Converts `'15m'`, `'30d'`, `3600` into seconds. */
|
|
33
|
+
export function durationToSeconds(value) {
|
|
34
|
+
if (typeof value === 'number')
|
|
35
|
+
return value;
|
|
36
|
+
const match = /^(\d+)\s*(ms|s|m|h|d|w)?$/.exec(value.trim());
|
|
37
|
+
if (!match)
|
|
38
|
+
throw new Error(`Invalid duration: ${value}`);
|
|
39
|
+
const amount = Number(match[1]);
|
|
40
|
+
switch (match[2]) {
|
|
41
|
+
case 'ms': return Math.ceil(amount / 1000);
|
|
42
|
+
case 'm': return amount * 60;
|
|
43
|
+
case 'h': return amount * 3600;
|
|
44
|
+
case 'd': return amount * 86_400;
|
|
45
|
+
case 'w': return amount * 604_800;
|
|
46
|
+
default: return amount;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../../src/common/jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import argon2 from 'argon2';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
/**
|
|
4
|
+
* Passwords are hashed with argon2id. Legacy bcrypt hashes (`$2a$`, `$2b$`, `$2y$`) still
|
|
5
|
+
* verify so migrated accounts can sign in; callers should re-hash on success when
|
|
6
|
+
* `needsRehash()` is true.
|
|
7
|
+
*/
|
|
8
|
+
const ARGON2_OPTIONS = { type: argon2.argon2id, memoryCost: 19_456, timeCost: 2, parallelism: 1 };
|
|
9
|
+
const BCRYPT_PREFIX = /^\$2[aby]\$/;
|
|
10
|
+
export async function hashPassword(password) {
|
|
11
|
+
return argon2.hash(password, ARGON2_OPTIONS);
|
|
12
|
+
}
|
|
13
|
+
export async function verifyPassword(password, hash) {
|
|
14
|
+
if (!hash)
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
if (BCRYPT_PREFIX.test(hash))
|
|
18
|
+
return await bcrypt.compare(password, hash);
|
|
19
|
+
return await argon2.verify(hash, password);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function needsRehash(hash) {
|
|
26
|
+
if (BCRYPT_PREFIX.test(hash))
|
|
27
|
+
return true;
|
|
28
|
+
return argon2.needsRehash(hash, ARGON2_OPTIONS);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=password.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password.js","sourceRoot":"","sources":["../../../src/common/password.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,MAAM,MAAM,UAAU,CAAC;AAE9B;;;;GAIG;AACH,MAAM,cAAc,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAW,CAAC;AAE3G,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,IAA+B;IACpF,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC1E,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import libphonenumber from 'google-libphonenumber';
|
|
2
|
+
const util = libphonenumber.PhoneNumberUtil.getInstance();
|
|
3
|
+
const { PhoneNumberFormat } = libphonenumber;
|
|
4
|
+
/**
|
|
5
|
+
* Normalises a phone number to E.164 (`+254712345678`). Returns null for anything that is not a
|
|
6
|
+
* valid number for the given default region.
|
|
7
|
+
*/
|
|
8
|
+
export function toE164(phone, defaultRegion = 'KE') {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = util.parseAndKeepRawInput(phone.trim(), defaultRegion);
|
|
11
|
+
return util.isValidNumber(parsed) ? util.format(parsed, PhoneNumberFormat.E164) : null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** E.164 without the plus sign, as most SMS gateways expect (`254712345678`). */
|
|
18
|
+
export function toMsisdn(phone, defaultRegion = 'KE') {
|
|
19
|
+
const e164 = toE164(phone, defaultRegion);
|
|
20
|
+
return e164 ? e164.slice(1) : null;
|
|
21
|
+
}
|
|
22
|
+
export function phoneRegion(phone, defaultRegion = 'KE') {
|
|
23
|
+
try {
|
|
24
|
+
return util.getRegionCodeForNumber(util.parse(phone, defaultRegion)) ?? null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function maskPhone(phone) {
|
|
31
|
+
return phone.length <= 4 ? '****' : `${phone.slice(0, 4)}****${phone.slice(-2)}`;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=phone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone.js","sourceRoot":"","sources":["../../../src/common/phone.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,uBAAuB,CAAC;AAEnD,MAAM,IAAI,GAAG,cAAc,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC;AAC1D,MAAM,EAAE,iBAAiB,EAAE,GAAG,cAAc,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,aAAa,GAAG,IAAI;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,aAAa,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,aAAa,GAAG,IAAI;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,aAAa,GAAG,IAAI;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,IAAI,IAAI,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Standard success envelope: `{ success, message, data }`. */
|
|
2
|
+
export function successResponse(data, message = 'Success') {
|
|
3
|
+
return { success: true, message, data };
|
|
4
|
+
}
|
|
5
|
+
/** Standard error envelope: `{ success: false, message, code?, errors? }`. */
|
|
6
|
+
export function errorResponse(message, errors, code) {
|
|
7
|
+
return { success: false, message, ...(code ? { code } : {}), ...(errors ? { errors } : {}) };
|
|
8
|
+
}
|
|
9
|
+
export function buildPaginationMeta(total, page, limit) {
|
|
10
|
+
const totalPages = limit > 0 ? Math.max(1, Math.ceil(total / limit)) : 1;
|
|
11
|
+
return { page, limit, total, totalPages, hasNext: page < totalPages, hasPrev: page > 1 };
|
|
12
|
+
}
|
|
13
|
+
/** Paginated envelope: `{ success, message, data: T[], meta }`. */
|
|
14
|
+
export function paginatedResponse(data, total, page, limit, message = 'Success') {
|
|
15
|
+
return { success: true, message, data, meta: buildPaginationMeta(total, page, limit) };
|
|
16
|
+
}
|
|
17
|
+
export function buildPaginatedResult(data, total, page, limit) {
|
|
18
|
+
return { data, meta: buildPaginationMeta(total, page, limit) };
|
|
19
|
+
}
|
|
20
|
+
/** SQL offset for 1-based pages. */
|
|
21
|
+
export function pageOffset(page, limit) {
|
|
22
|
+
return Math.max(0, (Math.max(1, page) - 1) * limit);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../../src/common/response.ts"],"names":[],"mappings":"AAEA,+DAA+D;AAC/D,MAAM,UAAU,eAAe,CAAI,IAAO,EAAE,OAAO,GAAG,SAAS;IAC7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,MAAiC,EAAE,IAAa;IAC7F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;IAC5E,MAAM,UAAU,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;AAC3F,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,iBAAiB,CAAI,IAAS,EAAE,KAAa,EAAE,IAAY,EAAE,KAAa,EAAE,OAAO,GAAG,SAAS;IAC7G,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,IAAS,EAAE,KAAa,EAAE,IAAY,EAAE,KAAa;IAC3F,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,KAAa;IACpD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** URL slug: lowercase ASCII, dash-separated, accents folded. */
|
|
2
|
+
export function slugify(value, maxLength = 80) {
|
|
3
|
+
return value
|
|
4
|
+
.normalize('NFKD')
|
|
5
|
+
.replace(/[̀-ͯ]/g, '')
|
|
6
|
+
.toLowerCase()
|
|
7
|
+
.replace(/&/g, ' and ')
|
|
8
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
9
|
+
.replace(/^-+|-+$/g, '')
|
|
10
|
+
.slice(0, maxLength)
|
|
11
|
+
.replace(/-+$/g, '');
|
|
12
|
+
}
|
|
13
|
+
/** Replaces `{{ name }}` placeholders; unknown names become empty strings. */
|
|
14
|
+
export function interpolate(template, variables, escape) {
|
|
15
|
+
return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
|
|
16
|
+
const value = path.split('.').reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), variables);
|
|
17
|
+
const text = value === undefined || value === null ? '' : String(value);
|
|
18
|
+
return escape ? escape(text) : text;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** Placeholder names used by a template, e.g. `['user.name', 'code']`. */
|
|
22
|
+
export function templateVariables(template) {
|
|
23
|
+
const names = new Set();
|
|
24
|
+
for (const match of template.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g))
|
|
25
|
+
names.add(match[1]);
|
|
26
|
+
return [...names];
|
|
27
|
+
}
|
|
28
|
+
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
29
|
+
export function escapeHtml(value) {
|
|
30
|
+
return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]);
|
|
31
|
+
}
|
|
32
|
+
/** `jane.doe@example.com` → `ja***@example.com` for logs and audit trails. */
|
|
33
|
+
export function maskEmail(email) {
|
|
34
|
+
const [local, domain] = email.split('@');
|
|
35
|
+
if (!local || !domain)
|
|
36
|
+
return '***';
|
|
37
|
+
return `${local.slice(0, 2)}***@${domain}`;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/common/text.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,SAAS,GAAG,EAAE;IACnD,OAAO,KAAK;SACT,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;SACnB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAkC,EAAE,MAAkC;IAClH,OAAO,QAAQ,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAU,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;QAC7J,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,YAAY,GAA2B,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAErH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAE,CAAC,CAAC;AAC9D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** True for IANA zones the runtime knows (`Africa/Nairobi`), false for garbage. */
|
|
2
|
+
export function isValidTimeZone(zone) {
|
|
3
|
+
try {
|
|
4
|
+
new Intl.DateTimeFormat('en-US', { timeZone: zone });
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Formats an instant in a zone, e.g. `30 Sep 2026, 14:05 EAT`. Falls back to UTC for invalid zones. */
|
|
12
|
+
export function formatInTimeZone(date, zone, locale = 'en-GB') {
|
|
13
|
+
const timeZone = isValidTimeZone(zone) ? zone : 'UTC';
|
|
14
|
+
return new Intl.DateTimeFormat(locale, {
|
|
15
|
+
timeZone,
|
|
16
|
+
day: '2-digit',
|
|
17
|
+
month: 'short',
|
|
18
|
+
year: 'numeric',
|
|
19
|
+
hour: '2-digit',
|
|
20
|
+
minute: '2-digit',
|
|
21
|
+
timeZoneName: 'short',
|
|
22
|
+
}).format(new Date(date));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Formats an instant for a person in their zone, falling back to `fallbackZone` (itself falling back to
|
|
26
|
+
* UTC) when theirs is missing or unknown. Never throws, and says when it fell back so callers can log it.
|
|
27
|
+
*/
|
|
28
|
+
export function formatInstantInZone(instant, zone, fallbackZone = 'UTC', locale = 'en-GB') {
|
|
29
|
+
const usedFallback = !(typeof zone === 'string' && zone && isValidTimeZone(zone));
|
|
30
|
+
const timezone = usedFallback ? (isValidTimeZone(fallbackZone) ? fallbackZone : 'UTC') : zone;
|
|
31
|
+
const date = new Date(instant);
|
|
32
|
+
return { text: Number.isNaN(date.getTime()) ? '' : formatInTimeZone(date, timezone, locale), timezone, usedFallback };
|
|
33
|
+
}
|
|
34
|
+
/** Offset of `zone` from UTC in minutes at `at` (positive east of Greenwich). */
|
|
35
|
+
export function timeZoneOffsetMinutes(zone, at = new Date()) {
|
|
36
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
37
|
+
timeZone: zone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
38
|
+
}).formatToParts(at);
|
|
39
|
+
const get = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
40
|
+
const asUtc = Date.UTC(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'), get('second'));
|
|
41
|
+
return Math.round((asUtc - Math.floor(at.getTime() / 1000) * 1000) / 60_000);
|
|
42
|
+
}
|
|
43
|
+
export const addSeconds = (date, seconds) => new Date(date.getTime() + seconds * 1000);
|
|
44
|
+
export const addMinutes = (date, minutes) => addSeconds(date, minutes * 60);
|
|
45
|
+
const MS_PER_MINUTE = 60_000;
|
|
46
|
+
const MS_PER_DAY = 86_400_000;
|
|
47
|
+
const CLOCK_TIME = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
48
|
+
/** Minutes since midnight of a 24-hour `HH:MM` clock time. Throws on anything else (never guesses). */
|
|
49
|
+
export function parseClockTime(value) {
|
|
50
|
+
const match = CLOCK_TIME.exec(String(value ?? '').trim());
|
|
51
|
+
if (!match)
|
|
52
|
+
throw new RangeError(`Not a 24-hour HH:MM clock time: "${value}"`);
|
|
53
|
+
return Number(match[1]) * 60 + Number(match[2]);
|
|
54
|
+
}
|
|
55
|
+
/** The local calendar date of `at` in `zone`, and how far into that local day it is. */
|
|
56
|
+
function localDate(at, zone) {
|
|
57
|
+
const local = new Date(at.getTime() + timeZoneOffsetMinutes(zone, at) * MS_PER_MINUTE);
|
|
58
|
+
const msOfDay = ((local.getTime() % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;
|
|
59
|
+
return { year: local.getUTCFullYear(), month: local.getUTCMonth(), day: local.getUTCDate(), msOfDay };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The instant a wall-clock time occurs in `zone` (month is 0-based; an overflowing day rolls over).
|
|
63
|
+
* The offset is read again at the first estimate, so the result is right across a DST change; a wall
|
|
64
|
+
* time skipped by a spring-forward resolves to the nearest valid instant.
|
|
65
|
+
*/
|
|
66
|
+
export function zonedWallTimeToInstant(year, month, day, minutesOfDay, zone) {
|
|
67
|
+
const wall = Date.UTC(year, month, day) + minutesOfDay * MS_PER_MINUTE;
|
|
68
|
+
const estimate = wall - timeZoneOffsetMinutes(zone, new Date(wall)) * MS_PER_MINUTE;
|
|
69
|
+
return new Date(wall - timeZoneOffsetMinutes(zone, new Date(estimate)) * MS_PER_MINUTE);
|
|
70
|
+
}
|
|
71
|
+
/** The local calendar day containing `at` in `zone`, as a half-open `[start, end)` UTC range (23–25 h on DST days). */
|
|
72
|
+
export function zonedDayBounds(at, zone) {
|
|
73
|
+
const { year, month, day } = localDate(at, zone);
|
|
74
|
+
return { start: zonedWallTimeToInstant(year, month, day, 0, zone), end: zonedWallTimeToInstant(year, month, day + 1, 0, zone) };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* When `at` falls inside the daily window `[start, end)` (local `HH:MM` times in `zone`; the window may
|
|
78
|
+
* cross midnight), the instant that window ends; otherwise null. `start === end` means no window.
|
|
79
|
+
*/
|
|
80
|
+
export function quietWindowEndAt(at, zone, start, end) {
|
|
81
|
+
const startMin = parseClockTime(start);
|
|
82
|
+
const endMin = parseClockTime(end);
|
|
83
|
+
if (startMin === endMin)
|
|
84
|
+
return null;
|
|
85
|
+
const { year, month, day, msOfDay } = localDate(at, zone);
|
|
86
|
+
const startMs = startMin * MS_PER_MINUTE;
|
|
87
|
+
const endMs = endMin * MS_PER_MINUTE;
|
|
88
|
+
const crossesMidnight = startMin > endMin;
|
|
89
|
+
const inside = crossesMidnight ? msOfDay >= startMs || msOfDay < endMs : msOfDay >= startMs && msOfDay < endMs;
|
|
90
|
+
if (!inside)
|
|
91
|
+
return null;
|
|
92
|
+
// A midnight-crossing window entered before midnight ends tomorrow; otherwise it ends today.
|
|
93
|
+
const endsTomorrow = crossesMidnight && msOfDay >= startMs;
|
|
94
|
+
return zonedWallTimeToInstant(year, month, day + (endsTomorrow ? 1 : 0), endMin, zone);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../../src/common/time.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* Environment building blocks. Services compose the pieces they need:
|
|
6
|
+
*
|
|
7
|
+
* const env = loadEnv(serviceEnv.merge(databaseEnv).merge(messagingEnv).extend({ MY_VAR: z.string() }));
|
|
8
|
+
*/
|
|
9
|
+
export const serviceEnv = z.object({
|
|
10
|
+
NODE_ENV: z.enum(['development', 'test', 'staging', 'production']).default('development'),
|
|
11
|
+
PORT: z.coerce.number().int().positive(),
|
|
12
|
+
SERVICE_NAME: z.string().min(1),
|
|
13
|
+
/** Short app identifier used in exchange names and key prefixes (e.g. `showbiz`). */
|
|
14
|
+
APP_NAME: z.string().min(1).default('app'),
|
|
15
|
+
LOG_LEVEL: z.enum(['error', 'warn', 'info', 'http', 'debug']).optional(),
|
|
16
|
+
CORS_ORIGINS: z.string().default(''),
|
|
17
|
+
/** Verifies gateway-signed identity headers. */
|
|
18
|
+
GATEWAY_SHARED_SECRET: z.string().min(16),
|
|
19
|
+
/** Authenticates service-to-service `/internal/*` calls. */
|
|
20
|
+
INTERNAL_SERVICE_SECRET: z.string().min(16),
|
|
21
|
+
});
|
|
22
|
+
export const databaseEnv = z.object({
|
|
23
|
+
DB_HOST: z.string().default('127.0.0.1'),
|
|
24
|
+
DB_PORT: z.coerce.number().int().positive().default(3306),
|
|
25
|
+
DB_USER: z.string().default('root'),
|
|
26
|
+
DB_PASSWORD: z.string().default(''),
|
|
27
|
+
DB_NAME: z.string().min(1),
|
|
28
|
+
DB_POOL_MAX: z.coerce.number().int().positive().default(10),
|
|
29
|
+
});
|
|
30
|
+
export const messagingEnv = z.object({
|
|
31
|
+
RABBITMQ_URL: z.string().optional(),
|
|
32
|
+
RABBITMQ_HOST: z.string().default('127.0.0.1'),
|
|
33
|
+
RABBITMQ_PORT: z.coerce.number().int().positive().default(5672),
|
|
34
|
+
RABBITMQ_USERNAME: z.string().default('guest'),
|
|
35
|
+
RABBITMQ_PASSWORD: z.string().default('guest'),
|
|
36
|
+
RABBITMQ_VHOST: z.string().default('/'),
|
|
37
|
+
/** Environment prefix for queue and exchange names (`dev`, `staging`, `prod`). */
|
|
38
|
+
QUEUE_PREFIX: z.string().default('dev'),
|
|
39
|
+
RABBITMQ_ENABLED: z.enum(['true', 'false']).default('true').transform((v) => v === 'true'),
|
|
40
|
+
});
|
|
41
|
+
export const redisEnv = z.object({
|
|
42
|
+
REDIS_URL: z.string().optional(),
|
|
43
|
+
REDIS_DB: z.coerce.number().int().min(0).default(0),
|
|
44
|
+
/** Key prefix, e.g. `app:dev:` — never share one between apps or environments. */
|
|
45
|
+
REDIS_PREFIX: z.string().default(''),
|
|
46
|
+
});
|
|
47
|
+
/** Builds an AMQP URL from discrete env vars (or returns RABBITMQ_URL when set). */
|
|
48
|
+
export function amqpUrlFromEnv(env) {
|
|
49
|
+
if (env.RABBITMQ_URL)
|
|
50
|
+
return env.RABBITMQ_URL;
|
|
51
|
+
const vhost = env.RABBITMQ_VHOST === '/' ? '%2F' : encodeURIComponent(env.RABBITMQ_VHOST.replace(/^\//, ''));
|
|
52
|
+
return `amqp://${encodeURIComponent(env.RABBITMQ_USERNAME)}:${encodeURIComponent(env.RABBITMQ_PASSWORD)}@${env.RABBITMQ_HOST}:${env.RABBITMQ_PORT}/${vhost}`;
|
|
53
|
+
}
|
|
54
|
+
/** Reads `KEY=value` lines from a dotenv file into process.env without overriding set values. */
|
|
55
|
+
export function loadDotEnv(path = resolve(process.cwd(), '.env')) {
|
|
56
|
+
if (!existsSync(path))
|
|
57
|
+
return;
|
|
58
|
+
for (const line of readFileSync(path, 'utf8').split(/\r?\n/)) {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
61
|
+
continue;
|
|
62
|
+
const eq = trimmed.indexOf('=');
|
|
63
|
+
if (eq === -1)
|
|
64
|
+
continue;
|
|
65
|
+
const key = trimmed.slice(0, eq).trim();
|
|
66
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
67
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
68
|
+
value = value.slice(1, -1);
|
|
69
|
+
if (process.env[key] === undefined)
|
|
70
|
+
process.env[key] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export class ConfigError extends Error {
|
|
74
|
+
}
|
|
75
|
+
/** Loads `.env` (if present) and validates process.env. Throws ConfigError listing every problem. */
|
|
76
|
+
export function loadEnv(schema, source = process.env, options = {}) {
|
|
77
|
+
if (options.dotenv !== false && source === process.env)
|
|
78
|
+
loadDotEnv();
|
|
79
|
+
const result = schema.safeParse(source);
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
const lines = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
|
|
82
|
+
throw new ConfigError(`Invalid environment configuration:\n${lines}`);
|
|
83
|
+
}
|
|
84
|
+
return result.data;
|
|
85
|
+
}
|
|
86
|
+
/** Comma-separated env value → trimmed, non-empty list. */
|
|
87
|
+
export const listFromEnv = (value) => (value ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"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"}
|