@heavybit/shared-lib 0.0.0-stage → 1.2.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.
Files changed (310) hide show
  1. package/dist/cjs/access/index.js +118 -0
  2. package/dist/cjs/access/index.js.map +1 -0
  3. package/dist/cjs/alerting/index.js +56 -0
  4. package/dist/cjs/alerting/index.js.map +1 -0
  5. package/dist/cjs/cache/index.js +167 -0
  6. package/dist/cjs/cache/index.js.map +1 -0
  7. package/dist/cjs/common/async.js +40 -0
  8. package/dist/cjs/common/async.js.map +1 -0
  9. package/dist/cjs/common/audit.js +21 -0
  10. package/dist/cjs/common/audit.js.map +1 -0
  11. package/dist/cjs/common/crypto.js +75 -0
  12. package/dist/cjs/common/crypto.js.map +1 -0
  13. package/dist/cjs/common/errors.js +90 -0
  14. package/dist/cjs/common/errors.js.map +1 -0
  15. package/dist/cjs/common/id.js +40 -0
  16. package/dist/cjs/common/id.js.map +1 -0
  17. package/dist/cjs/common/index.js +28 -0
  18. package/dist/cjs/common/index.js.map +1 -0
  19. package/dist/cjs/common/jwt.js +59 -0
  20. package/dist/cjs/common/jwt.js.map +1 -0
  21. package/dist/cjs/common/password.js +38 -0
  22. package/dist/cjs/common/password.js.map +1 -0
  23. package/dist/cjs/common/phone.js +42 -0
  24. package/dist/cjs/common/phone.js.map +1 -0
  25. package/dist/cjs/common/response.js +32 -0
  26. package/dist/cjs/common/response.js.map +1 -0
  27. package/dist/cjs/common/text.js +46 -0
  28. package/dist/cjs/common/text.js.map +1 -0
  29. package/dist/cjs/common/time.js +109 -0
  30. package/dist/cjs/common/time.js.map +1 -0
  31. package/dist/cjs/config/index.js +96 -0
  32. package/dist/cjs/config/index.js.map +1 -0
  33. package/dist/cjs/contracts/editorial-draft.js +111 -0
  34. package/dist/cjs/contracts/editorial-draft.js.map +1 -0
  35. package/dist/cjs/contracts/index.js +20 -0
  36. package/dist/cjs/contracts/index.js.map +1 -0
  37. package/dist/cjs/contracts/notification-dispatch.js +92 -0
  38. package/dist/cjs/contracts/notification-dispatch.js.map +1 -0
  39. package/dist/cjs/contracts/upload-import.js +54 -0
  40. package/dist/cjs/contracts/upload-import.js.map +1 -0
  41. package/dist/cjs/database/index.js +57 -0
  42. package/dist/cjs/database/index.js.map +1 -0
  43. package/dist/cjs/express/auth.js +72 -0
  44. package/dist/cjs/express/auth.js.map +1 -0
  45. package/dist/cjs/express/health.js +31 -0
  46. package/dist/cjs/express/health.js.map +1 -0
  47. package/dist/cjs/express/index.js +21 -0
  48. package/dist/cjs/express/index.js.map +1 -0
  49. package/dist/cjs/express/middleware.js +95 -0
  50. package/dist/cjs/express/middleware.js.map +1 -0
  51. package/dist/cjs/express/security.js +18 -0
  52. package/dist/cjs/express/security.js.map +1 -0
  53. package/dist/cjs/http/index.js +90 -0
  54. package/dist/cjs/http/index.js.map +1 -0
  55. package/dist/cjs/http/safe-fetch.js +199 -0
  56. package/dist/cjs/http/safe-fetch.js.map +1 -0
  57. package/dist/cjs/identity/index.js +117 -0
  58. package/dist/cjs/identity/index.js.map +1 -0
  59. package/dist/cjs/index.js +29 -0
  60. package/dist/cjs/index.js.map +1 -0
  61. package/dist/cjs/knex/index.js +76 -0
  62. package/dist/cjs/knex/index.js.map +1 -0
  63. package/dist/cjs/logging/index.js +75 -0
  64. package/dist/cjs/logging/index.js.map +1 -0
  65. package/dist/cjs/messaging/connection.js +115 -0
  66. package/dist/cjs/messaging/connection.js.map +1 -0
  67. package/dist/cjs/messaging/consumer.js +134 -0
  68. package/dist/cjs/messaging/consumer.js.map +1 -0
  69. package/dist/cjs/messaging/envelope.js +23 -0
  70. package/dist/cjs/messaging/envelope.js.map +1 -0
  71. package/dist/cjs/messaging/idempotency.js +57 -0
  72. package/dist/cjs/messaging/idempotency.js.map +1 -0
  73. package/dist/cjs/messaging/index.js +62 -0
  74. package/dist/cjs/messaging/index.js.map +1 -0
  75. package/dist/cjs/messaging/monitor.js +48 -0
  76. package/dist/cjs/messaging/monitor.js.map +1 -0
  77. package/dist/cjs/messaging/outbox.js +135 -0
  78. package/dist/cjs/messaging/outbox.js.map +1 -0
  79. package/dist/cjs/messaging/publisher.js +149 -0
  80. package/dist/cjs/messaging/publisher.js.map +1 -0
  81. package/dist/cjs/messaging/rpc.js +125 -0
  82. package/dist/cjs/messaging/rpc.js.map +1 -0
  83. package/dist/cjs/messaging/staff-notifier.js +78 -0
  84. package/dist/cjs/messaging/staff-notifier.js.map +1 -0
  85. package/dist/cjs/messaging/topology.js +22 -0
  86. package/dist/cjs/messaging/topology.js.map +1 -0
  87. package/dist/cjs/metrics/index.js +15 -0
  88. package/dist/cjs/metrics/index.js.map +1 -0
  89. package/dist/cjs/package.json +1 -0
  90. package/dist/cjs/rate-limit/index.js +61 -0
  91. package/dist/cjs/rate-limit/index.js.map +1 -0
  92. package/dist/cjs/scheduling/index.js +88 -0
  93. package/dist/cjs/scheduling/index.js.map +1 -0
  94. package/dist/cjs/security/index.js +39 -0
  95. package/dist/cjs/security/index.js.map +1 -0
  96. package/dist/cjs/socket/index.js +53 -0
  97. package/dist/cjs/socket/index.js.map +1 -0
  98. package/dist/cjs/testing/index.js +73 -0
  99. package/dist/cjs/testing/index.js.map +1 -0
  100. package/dist/cjs/types/index.js +3 -0
  101. package/dist/cjs/types/index.js.map +1 -0
  102. package/dist/cjs/validation/index.js +41 -0
  103. package/dist/cjs/validation/index.js.map +1 -0
  104. package/dist/esm/access/index.js +104 -0
  105. package/dist/esm/access/index.js.map +1 -0
  106. package/dist/esm/alerting/index.js +52 -0
  107. package/dist/esm/alerting/index.js.map +1 -0
  108. package/dist/esm/cache/index.js +163 -0
  109. package/dist/esm/cache/index.js.map +1 -0
  110. package/dist/esm/common/async.js +33 -0
  111. package/dist/esm/common/async.js.map +1 -0
  112. package/dist/esm/common/audit.js +18 -0
  113. package/dist/esm/common/audit.js.map +1 -0
  114. package/dist/esm/common/crypto.js +64 -0
  115. package/dist/esm/common/crypto.js.map +1 -0
  116. package/dist/esm/common/errors.js +75 -0
  117. package/dist/esm/common/errors.js.map +1 -0
  118. package/dist/esm/common/id.js +33 -0
  119. package/dist/esm/common/id.js.map +1 -0
  120. package/dist/esm/common/index.js +12 -0
  121. package/dist/esm/common/index.js.map +1 -0
  122. package/dist/esm/common/jwt.js +49 -0
  123. package/dist/esm/common/jwt.js.map +1 -0
  124. package/dist/esm/common/password.js +30 -0
  125. package/dist/esm/common/password.js.map +1 -0
  126. package/dist/esm/common/phone.js +33 -0
  127. package/dist/esm/common/phone.js.map +1 -0
  128. package/dist/esm/common/response.js +24 -0
  129. package/dist/esm/common/response.js.map +1 -0
  130. package/dist/esm/common/text.js +39 -0
  131. package/dist/esm/common/text.js.map +1 -0
  132. package/dist/esm/common/time.js +96 -0
  133. package/dist/esm/common/time.js.map +1 -0
  134. package/dist/esm/config/index.js +88 -0
  135. package/dist/esm/config/index.js.map +1 -0
  136. package/dist/esm/contracts/editorial-draft.js +108 -0
  137. package/dist/esm/contracts/editorial-draft.js.map +1 -0
  138. package/dist/esm/contracts/index.js +4 -0
  139. package/dist/esm/contracts/index.js.map +1 -0
  140. package/dist/esm/contracts/notification-dispatch.js +89 -0
  141. package/dist/esm/contracts/notification-dispatch.js.map +1 -0
  142. package/dist/esm/contracts/upload-import.js +51 -0
  143. package/dist/esm/contracts/upload-import.js.map +1 -0
  144. package/dist/esm/database/index.js +48 -0
  145. package/dist/esm/database/index.js.map +1 -0
  146. package/dist/esm/express/auth.js +63 -0
  147. package/dist/esm/express/auth.js.map +1 -0
  148. package/dist/esm/express/health.js +27 -0
  149. package/dist/esm/express/health.js.map +1 -0
  150. package/dist/esm/express/index.js +5 -0
  151. package/dist/esm/express/index.js.map +1 -0
  152. package/dist/esm/express/middleware.js +84 -0
  153. package/dist/esm/express/middleware.js.map +1 -0
  154. package/dist/esm/express/security.js +11 -0
  155. package/dist/esm/express/security.js.map +1 -0
  156. package/dist/esm/http/index.js +73 -0
  157. package/dist/esm/http/index.js.map +1 -0
  158. package/dist/esm/http/safe-fetch.js +193 -0
  159. package/dist/esm/http/safe-fetch.js.map +1 -0
  160. package/dist/esm/identity/index.js +111 -0
  161. package/dist/esm/identity/index.js.map +1 -0
  162. package/dist/esm/index.js +13 -0
  163. package/dist/esm/index.js.map +1 -0
  164. package/dist/esm/knex/index.js +66 -0
  165. package/dist/esm/knex/index.js.map +1 -0
  166. package/dist/esm/logging/index.js +67 -0
  167. package/dist/esm/logging/index.js.map +1 -0
  168. package/dist/esm/messaging/connection.js +108 -0
  169. package/dist/esm/messaging/connection.js.map +1 -0
  170. package/dist/esm/messaging/consumer.js +130 -0
  171. package/dist/esm/messaging/consumer.js.map +1 -0
  172. package/dist/esm/messaging/envelope.js +19 -0
  173. package/dist/esm/messaging/envelope.js.map +1 -0
  174. package/dist/esm/messaging/idempotency.js +52 -0
  175. package/dist/esm/messaging/idempotency.js.map +1 -0
  176. package/dist/esm/messaging/index.js +45 -0
  177. package/dist/esm/messaging/index.js.map +1 -0
  178. package/dist/esm/messaging/monitor.js +44 -0
  179. package/dist/esm/messaging/monitor.js.map +1 -0
  180. package/dist/esm/messaging/outbox.js +129 -0
  181. package/dist/esm/messaging/outbox.js.map +1 -0
  182. package/dist/esm/messaging/publisher.js +142 -0
  183. package/dist/esm/messaging/publisher.js.map +1 -0
  184. package/dist/esm/messaging/rpc.js +120 -0
  185. package/dist/esm/messaging/rpc.js.map +1 -0
  186. package/dist/esm/messaging/staff-notifier.js +73 -0
  187. package/dist/esm/messaging/staff-notifier.js.map +1 -0
  188. package/dist/esm/messaging/topology.js +16 -0
  189. package/dist/esm/messaging/topology.js.map +1 -0
  190. package/dist/esm/metrics/index.js +6 -0
  191. package/dist/esm/metrics/index.js.map +1 -0
  192. package/dist/esm/package.json +1 -0
  193. package/dist/esm/rate-limit/index.js +56 -0
  194. package/dist/esm/rate-limit/index.js.map +1 -0
  195. package/dist/esm/scheduling/index.js +83 -0
  196. package/dist/esm/scheduling/index.js.map +1 -0
  197. package/dist/esm/security/index.js +36 -0
  198. package/dist/esm/security/index.js.map +1 -0
  199. package/dist/esm/socket/index.js +47 -0
  200. package/dist/esm/socket/index.js.map +1 -0
  201. package/dist/esm/testing/index.js +66 -0
  202. package/dist/esm/testing/index.js.map +1 -0
  203. package/dist/esm/types/index.js +2 -0
  204. package/dist/esm/types/index.js.map +1 -0
  205. package/dist/esm/validation/index.js +36 -0
  206. package/dist/esm/validation/index.js.map +1 -0
  207. package/dist/types/access/index.d.ts +50 -0
  208. package/dist/types/access/index.d.ts.map +1 -0
  209. package/dist/types/alerting/index.d.ts +43 -0
  210. package/dist/types/alerting/index.d.ts.map +1 -0
  211. package/dist/types/cache/index.d.ts +45 -0
  212. package/dist/types/cache/index.d.ts.map +1 -0
  213. package/dist/types/common/async.d.ts +14 -0
  214. package/dist/types/common/async.d.ts.map +1 -0
  215. package/dist/types/common/audit.d.ts +17 -0
  216. package/dist/types/common/audit.d.ts.map +1 -0
  217. package/dist/types/common/crypto.d.ts +18 -0
  218. package/dist/types/common/crypto.d.ts.map +1 -0
  219. package/dist/types/common/errors.d.ts +71 -0
  220. package/dist/types/common/errors.d.ts.map +1 -0
  221. package/dist/types/common/id.d.ts +14 -0
  222. package/dist/types/common/id.d.ts.map +1 -0
  223. package/dist/types/common/index.d.ts +12 -0
  224. package/dist/types/common/index.d.ts.map +1 -0
  225. package/dist/types/common/jwt.d.ts +28 -0
  226. package/dist/types/common/jwt.d.ts.map +1 -0
  227. package/dist/types/common/password.d.ts +4 -0
  228. package/dist/types/common/password.d.ts.map +1 -0
  229. package/dist/types/common/phone.d.ts +10 -0
  230. package/dist/types/common/phone.d.ts.map +1 -0
  231. package/dist/types/common/response.d.ts +12 -0
  232. package/dist/types/common/response.d.ts.map +1 -0
  233. package/dist/types/common/text.d.ts +10 -0
  234. package/dist/types/common/text.d.ts.map +1 -0
  235. package/dist/types/common/time.d.ts +40 -0
  236. package/dist/types/common/time.d.ts.map +1 -0
  237. package/dist/types/config/index.d.ts +115 -0
  238. package/dist/types/config/index.d.ts.map +1 -0
  239. package/dist/types/contracts/editorial-draft.d.ts +577 -0
  240. package/dist/types/contracts/editorial-draft.d.ts.map +1 -0
  241. package/dist/types/contracts/index.d.ts +4 -0
  242. package/dist/types/contracts/index.d.ts.map +1 -0
  243. package/dist/types/contracts/notification-dispatch.d.ts +175 -0
  244. package/dist/types/contracts/notification-dispatch.d.ts.map +1 -0
  245. package/dist/types/contracts/upload-import.d.ts +96 -0
  246. package/dist/types/contracts/upload-import.d.ts.map +1 -0
  247. package/dist/types/database/index.d.ts +25 -0
  248. package/dist/types/database/index.d.ts.map +1 -0
  249. package/dist/types/express/auth.d.ts +27 -0
  250. package/dist/types/express/auth.d.ts.map +1 -0
  251. package/dist/types/express/health.d.ts +14 -0
  252. package/dist/types/express/health.d.ts.map +1 -0
  253. package/dist/types/express/index.d.ts +5 -0
  254. package/dist/types/express/index.d.ts.map +1 -0
  255. package/dist/types/express/middleware.d.ts +20 -0
  256. package/dist/types/express/middleware.d.ts.map +1 -0
  257. package/dist/types/express/security.d.ts +9 -0
  258. package/dist/types/express/security.d.ts.map +1 -0
  259. package/dist/types/http/index.d.ts +34 -0
  260. package/dist/types/http/index.d.ts.map +1 -0
  261. package/dist/types/http/safe-fetch.d.ts +58 -0
  262. package/dist/types/http/safe-fetch.d.ts.map +1 -0
  263. package/dist/types/identity/index.d.ts +51 -0
  264. package/dist/types/identity/index.d.ts.map +1 -0
  265. package/dist/types/index.d.ts +10 -0
  266. package/dist/types/index.d.ts.map +1 -0
  267. package/dist/types/knex/index.d.ts +25 -0
  268. package/dist/types/knex/index.d.ts.map +1 -0
  269. package/dist/types/logging/index.d.ts +22 -0
  270. package/dist/types/logging/index.d.ts.map +1 -0
  271. package/dist/types/messaging/connection.d.ts +48 -0
  272. package/dist/types/messaging/connection.d.ts.map +1 -0
  273. package/dist/types/messaging/consumer.d.ts +56 -0
  274. package/dist/types/messaging/consumer.d.ts.map +1 -0
  275. package/dist/types/messaging/envelope.d.ts +16 -0
  276. package/dist/types/messaging/envelope.d.ts.map +1 -0
  277. package/dist/types/messaging/idempotency.d.ts +29 -0
  278. package/dist/types/messaging/idempotency.d.ts.map +1 -0
  279. package/dist/types/messaging/index.d.ts +39 -0
  280. package/dist/types/messaging/index.d.ts.map +1 -0
  281. package/dist/types/messaging/monitor.d.ts +23 -0
  282. package/dist/types/messaging/monitor.d.ts.map +1 -0
  283. package/dist/types/messaging/outbox.d.ts +51 -0
  284. package/dist/types/messaging/outbox.d.ts.map +1 -0
  285. package/dist/types/messaging/publisher.d.ts +84 -0
  286. package/dist/types/messaging/publisher.d.ts.map +1 -0
  287. package/dist/types/messaging/rpc.d.ts +46 -0
  288. package/dist/types/messaging/rpc.d.ts.map +1 -0
  289. package/dist/types/messaging/staff-notifier.d.ts +56 -0
  290. package/dist/types/messaging/staff-notifier.d.ts.map +1 -0
  291. package/dist/types/messaging/topology.d.ts +23 -0
  292. package/dist/types/messaging/topology.d.ts.map +1 -0
  293. package/dist/types/metrics/index.d.ts +7 -0
  294. package/dist/types/metrics/index.d.ts.map +1 -0
  295. package/dist/types/rate-limit/index.d.ts +28 -0
  296. package/dist/types/rate-limit/index.d.ts.map +1 -0
  297. package/dist/types/scheduling/index.d.ts +33 -0
  298. package/dist/types/scheduling/index.d.ts.map +1 -0
  299. package/dist/types/security/index.d.ts +20 -0
  300. package/dist/types/security/index.d.ts.map +1 -0
  301. package/dist/types/socket/index.d.ts +30 -0
  302. package/dist/types/socket/index.d.ts.map +1 -0
  303. package/dist/types/testing/index.d.ts +41 -0
  304. package/dist/types/testing/index.d.ts.map +1 -0
  305. package/dist/types/types/index.d.ts +65 -0
  306. package/dist/types/types/index.d.ts.map +1 -0
  307. package/dist/types/validation/index.d.ts +34 -0
  308. package/dist/types/validation/index.d.ts.map +1 -0
  309. package/package.json +181 -4
  310. package/README.md +0 -3
@@ -0,0 +1,163 @@
1
+ import { Redis } from 'ioredis';
2
+ import { silentLogger } from '../logging/index.js';
3
+ const NULL_SENTINEL = '__null__';
4
+ class MemoryStore {
5
+ max;
6
+ map = new Map();
7
+ constructor(max) {
8
+ this.max = max;
9
+ }
10
+ get(key) {
11
+ const entry = this.map.get(key);
12
+ if (!entry)
13
+ return undefined;
14
+ if (entry.expiresAt < Date.now()) {
15
+ this.map.delete(key);
16
+ return undefined;
17
+ }
18
+ // Refresh recency for LRU eviction.
19
+ this.map.delete(key);
20
+ this.map.set(key, entry);
21
+ return entry.value;
22
+ }
23
+ set(key, value, ttlSec) {
24
+ if (this.map.size >= this.max)
25
+ this.map.delete(this.map.keys().next().value);
26
+ this.map.set(key, { value, expiresAt: Date.now() + ttlSec * 1000 });
27
+ }
28
+ del(key) {
29
+ this.map.delete(key);
30
+ }
31
+ }
32
+ /**
33
+ * Redis-backed cache with tag invalidation. If Redis errors, reads fall through to the loader
34
+ * and writes go to a bounded in-memory store, so a cache outage degrades latency rather than
35
+ * availability.
36
+ */
37
+ export function createCache(options) {
38
+ const logger = options.logger ?? silentLogger;
39
+ const defaultTtl = options.defaultTtlSec ?? 60;
40
+ const memory = new MemoryStore(options.memoryMaxEntries ?? 5000);
41
+ const inflight = new Map();
42
+ const k = (key) => `${options.namespace}:${key}`;
43
+ const tagKey = (tag) => `${options.namespace}:tag:${tag}`;
44
+ let warned = false;
45
+ const redisFailed = (error) => {
46
+ if (!warned)
47
+ logger.warn('Cache backend unavailable; using in-memory fallback', { error: error.message });
48
+ warned = true;
49
+ };
50
+ const readRaw = async (key) => {
51
+ if (options.redis) {
52
+ try {
53
+ const value = await options.redis.get(k(key));
54
+ warned = false;
55
+ return value ?? undefined;
56
+ }
57
+ catch (error) {
58
+ redisFailed(error);
59
+ }
60
+ }
61
+ return memory.get(k(key));
62
+ };
63
+ const writeRaw = async (key, raw, opts = {}) => {
64
+ const ttl = opts.ttlSec ?? defaultTtl;
65
+ if (options.redis) {
66
+ try {
67
+ const pipeline = options.redis.multi().set(k(key), raw, 'EX', ttl);
68
+ for (const tag of opts.tags ?? [])
69
+ pipeline.sadd(tagKey(tag), k(key)).expire(tagKey(tag), Math.max(ttl, 86_400));
70
+ await pipeline.exec();
71
+ return;
72
+ }
73
+ catch (error) {
74
+ redisFailed(error);
75
+ }
76
+ }
77
+ memory.set(k(key), raw, ttl);
78
+ };
79
+ const cache = {
80
+ async get(key) {
81
+ const raw = await readRaw(key);
82
+ if (raw === undefined || raw === NULL_SENTINEL)
83
+ return undefined;
84
+ return JSON.parse(raw);
85
+ },
86
+ async set(key, value, opts) {
87
+ await writeRaw(key, value === null ? NULL_SENTINEL : JSON.stringify(value), opts);
88
+ },
89
+ async del(...keys) {
90
+ for (const key of keys)
91
+ memory.del(k(key));
92
+ if (options.redis && keys.length)
93
+ await options.redis.del(...keys.map(k)).catch(redisFailed);
94
+ },
95
+ async wrap(key, loader, opts = {}) {
96
+ const raw = await readRaw(key);
97
+ if (raw !== undefined) {
98
+ if (raw === NULL_SENTINEL)
99
+ return null;
100
+ return JSON.parse(raw);
101
+ }
102
+ const existing = inflight.get(key);
103
+ if (existing)
104
+ return existing;
105
+ const load = (async () => {
106
+ try {
107
+ const value = await loader();
108
+ if (value !== null && value !== undefined)
109
+ await writeRaw(key, JSON.stringify(value), opts);
110
+ else if (opts.cacheNull)
111
+ await writeRaw(key, NULL_SENTINEL, { ...opts, ttlSec: Math.min(opts.ttlSec ?? defaultTtl, 30) });
112
+ return value;
113
+ }
114
+ finally {
115
+ inflight.delete(key);
116
+ }
117
+ })();
118
+ inflight.set(key, load);
119
+ return load;
120
+ },
121
+ async invalidateTags(...tags) {
122
+ if (!options.redis)
123
+ return;
124
+ try {
125
+ for (const tag of tags) {
126
+ const members = await options.redis.smembers(tagKey(tag));
127
+ for (const member of members)
128
+ memory.del(member);
129
+ if (members.length)
130
+ await options.redis.del(...members);
131
+ await options.redis.del(tagKey(tag));
132
+ }
133
+ }
134
+ catch (error) {
135
+ redisFailed(error);
136
+ }
137
+ },
138
+ };
139
+ return cache;
140
+ }
141
+ /** ioredis client that never crashes the process on connection errors and retries forever. */
142
+ export function createRedisClient(opts) {
143
+ const logger = opts.logger ?? silentLogger;
144
+ const base = {
145
+ db: opts.db ?? 0,
146
+ keyPrefix: opts.keyPrefix || undefined,
147
+ lazyConnect: false,
148
+ enableOfflineQueue: false,
149
+ maxRetriesPerRequest: 1,
150
+ retryStrategy: (times) => Math.min(times * 500, 10_000),
151
+ ...opts.options,
152
+ };
153
+ const client = opts.url ? new Redis(opts.url, base) : new Redis(base);
154
+ let lastError = '';
155
+ client.on('error', (error) => {
156
+ if (error.message !== lastError)
157
+ logger.warn('Redis error', { error: error.message });
158
+ lastError = error.message;
159
+ });
160
+ client.on('ready', () => { lastError = ''; logger.info('Redis connected', { db: base.db }); });
161
+ return client;
162
+ }
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2
+ /** Runs `fn`, retrying failures with exponential back-off. */
3
+ export async function retry(fn, options) {
4
+ const delay = options.delayMs ?? ((n) => Math.min(5000, 200 * 2 ** (n - 1)));
5
+ let attempt = 0;
6
+ for (;;) {
7
+ try {
8
+ return await fn(attempt);
9
+ }
10
+ catch (error) {
11
+ attempt += 1;
12
+ if (attempt > options.retries || (options.shouldRetry && !options.shouldRetry(error)))
13
+ throw error;
14
+ await sleep(delay(attempt));
15
+ }
16
+ }
17
+ }
18
+ /** Rejects with `onTimeout()` if `promise` does not settle within `ms`. */
19
+ export function withTimeout(promise, ms, onTimeout) {
20
+ let timer;
21
+ return Promise.race([
22
+ promise.finally(() => clearTimeout(timer)),
23
+ new Promise((_, reject) => { timer = setTimeout(() => reject(onTimeout()), ms); }),
24
+ ]);
25
+ }
26
+ /** Splits `items` into arrays of at most `size`. */
27
+ export function chunk(items, size) {
28
+ const out = [];
29
+ for (let i = 0; i < items.length; i += size)
30
+ out.push(items.slice(i, i + size));
31
+ return out;
32
+ }
33
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.js","sourceRoot":"","sources":["../../../src/common/async.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAStG,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,EAAmC,EAAE,OAAqB;IACvF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,CAAC;YACb,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,KAAK,CAAC;YACnG,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,SAAsB;IACpF,IAAI,KAAoC,CAAC;IACzC,OAAO,OAAO,CAAC,IAAI,CAAC;QAClB,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;KAC1F,CAAC,CAAC;AACL,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,KAAK,CAAI,KAAmB,EAAE,IAAY;IACxD,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAChF,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { newId } from './id.js';
2
+ /**
3
+ * Builds the row for a `*_audits` sibling table: a copy of the parent row plus `action`,
4
+ * `parentId`, and a fresh `id`. The parent's timestamps are dropped so the audit row records
5
+ * when the change happened. The actor lands in `createdBy`/`updatedBy` exactly as on the parent
6
+ * (null for system operations). Strip secrets (e.g. password hashes) before calling.
7
+ */
8
+ export function buildAuditRow(row, action, actorId = null) {
9
+ const { id: parentId, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = row;
10
+ return {
11
+ ...rest,
12
+ id: newId(),
13
+ parentId,
14
+ action,
15
+ ...(action === 'create' ? { createdBy: actorId, updatedBy: actorId } : { updatedBy: actorId }),
16
+ };
17
+ }
18
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/common/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAMhC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAA2B,GAAM,EAAE,MAAmB,EAAE,UAAyB,IAAI;IAChH,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,GAAuD,CAAC;IACxI,OAAO;QACL,GAAI,IAAkD;QACtD,EAAE,EAAE,KAAK,EAAE;QACX,QAAQ;QACR,MAAM;QACN,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;KAC/F,CAAC;AACJ,CAAC"}
@@ -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":"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"}
@@ -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"}