@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,83 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { silentLogger } from '../logging/index.js';
3
+ const RELEASE_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
4
+ /** Redis lock (SET NX PX + token-checked release). Without Redis, a per-process lock. */
5
+ export function createLock(redis, prefix = 'lock:') {
6
+ const local = new Set();
7
+ return {
8
+ async runExclusive(key, ttlMs, fn) {
9
+ if (!redis) {
10
+ if (local.has(key))
11
+ return false;
12
+ local.add(key);
13
+ try {
14
+ await fn();
15
+ }
16
+ finally {
17
+ local.delete(key);
18
+ }
19
+ return true;
20
+ }
21
+ const token = randomUUID();
22
+ const acquired = await redis.set(prefix + key, token, 'PX', ttlMs, 'NX').catch(() => null);
23
+ if (acquired !== 'OK')
24
+ return false;
25
+ try {
26
+ await fn();
27
+ }
28
+ finally {
29
+ await redis.eval(RELEASE_SCRIPT, 1, prefix + key, token).catch(() => undefined);
30
+ }
31
+ return true;
32
+ },
33
+ };
34
+ }
35
+ /**
36
+ * Interval job that never overlaps itself, can be triggered early by events (`trigger()`), and
37
+ * optionally runs on one replica at a time.
38
+ */
39
+ export class ScheduledJob {
40
+ options;
41
+ timer = null;
42
+ running = false;
43
+ logger;
44
+ constructor(options) {
45
+ this.options = options;
46
+ this.logger = options.logger ?? silentLogger;
47
+ }
48
+ start() {
49
+ if (this.timer)
50
+ return;
51
+ this.timer = setInterval(() => { void this.trigger(); }, this.options.intervalMs);
52
+ this.timer.unref?.();
53
+ if (this.options.runOnStart)
54
+ void this.trigger();
55
+ }
56
+ stop() {
57
+ if (this.timer)
58
+ clearInterval(this.timer);
59
+ this.timer = null;
60
+ }
61
+ get isRunning() {
62
+ return this.running;
63
+ }
64
+ async trigger() {
65
+ if (this.running)
66
+ return;
67
+ this.running = true;
68
+ try {
69
+ const run = async () => { await this.options.work(); };
70
+ if (this.options.lock)
71
+ await this.options.lock.runExclusive(`job:${this.options.name}`, Math.max(this.options.intervalMs, 30_000), run);
72
+ else
73
+ await run();
74
+ }
75
+ catch (error) {
76
+ this.logger.error('Scheduled job failed', { job: this.options.name, error: error.message });
77
+ }
78
+ finally {
79
+ this.running = false;
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scheduling/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { hmacSha256, safeEqual } from '../common/crypto.js';
2
+ /** HMAC-SHA256 expiring URL signatures for private files (streams, downloads, originals). */
3
+ export function createUrlSigner(secret) {
4
+ if (!secret || secret.length < 16)
5
+ throw new Error('URL signing secret must be at least 16 characters');
6
+ const expiry = (ttlSec) => Math.floor(Date.now() / 1000) + ttlSec;
7
+ const mac = (target, exp) => hmacSha256(secret, `${target}|${exp}`);
8
+ const signer = {
9
+ sign(path, ttlSec) {
10
+ const exp = expiry(ttlSec);
11
+ return { exp, sig: mac(path, exp) };
12
+ },
13
+ signPrefix(prefix, ttlSec) {
14
+ const exp = expiry(ttlSec);
15
+ return { exp, sig: mac(`prefix:${prefix}`, exp), sp: prefix };
16
+ },
17
+ signedPath(path, ttlSec, options = {}) {
18
+ const params = options.prefix ? signer.signPrefix(options.prefix, ttlSec) : signer.sign(path, ttlSec);
19
+ const qs = new URLSearchParams({ exp: String(params.exp), sig: params.sig, ...(params.sp ? { sp: params.sp } : {}) });
20
+ return `${path}${path.includes('?') ? '&' : '?'}${qs.toString()}`;
21
+ },
22
+ verify(path, params, now = Date.now()) {
23
+ const exp = Number(params.exp);
24
+ if (!params.sig || !Number.isFinite(exp) || exp * 1000 < now)
25
+ return false;
26
+ if (params.sp) {
27
+ if (!path.startsWith(params.sp))
28
+ return false;
29
+ return safeEqual(params.sig, mac(`prefix:${params.sp}`, exp));
30
+ }
31
+ return safeEqual(params.sig, mac(path, exp));
32
+ },
33
+ };
34
+ return signer;
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAmB5D,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACxG,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;IAC1E,MAAM,GAAG,GAAG,CAAC,MAAc,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;IAEpF,MAAM,MAAM,GAAc;QACxB,IAAI,CAAC,IAAI,EAAE,MAAM;YACf,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QACtC,CAAC;QACD,UAAU,CAAC,MAAM,EAAE,MAAM;YACvB,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,UAAU,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QAChE,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,EAAE;YACnC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACtG,MAAM,EAAE,GAAG,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACtH,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;QACpE,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YACnC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG;gBAAE,OAAO,KAAK,CAAC;YAC3E,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC9C,OAAO,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YAChE,CAAC;YACD,OAAO,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { Server } from 'socket.io';
2
+ import { verifyIdentityHeaders } from '../identity/index.js';
3
+ import { silentLogger } from '../logging/index.js';
4
+ export const userRoom = (userId) => `user:${userId}`;
5
+ export function createSocketServer(httpServer, options) {
6
+ const logger = options.logger ?? silentLogger;
7
+ const io = new Server(httpServer, {
8
+ path: options.path,
9
+ cors: { origin: options.corsOrigins?.length ? options.corsOrigins : false, credentials: true },
10
+ ...(options.adapter ? { adapter: options.adapter } : {}),
11
+ ...options.serverOptions,
12
+ });
13
+ io.use(async (socket, next) => {
14
+ try {
15
+ const user = await options.authenticate(socket);
16
+ if (!user)
17
+ return next(new Error('Authentication failed'));
18
+ socket.data.user = user;
19
+ next();
20
+ }
21
+ catch (error) {
22
+ logger.warn('Socket authentication rejected', { error: error.message });
23
+ next(new Error('Authentication failed'));
24
+ }
25
+ });
26
+ io.on('connection', (socket) => {
27
+ const user = socket.data.user;
28
+ const rooms = options.rooms ? options.rooms(user) : [userRoom(user.id)];
29
+ void socket.join(rooms);
30
+ logger.debug('Socket connected', { userId: user.id, rooms });
31
+ socket.on('disconnect', (reason) => logger.debug('Socket disconnected', { userId: user.id, reason }));
32
+ });
33
+ return {
34
+ io,
35
+ emitToUser: (userId, event, payload) => { io.to(userRoom(userId)).emit(event, payload); },
36
+ emitToRoom: (room, event, payload) => { io.to(room).emit(event, payload); },
37
+ close: () => new Promise((resolve) => { io.close(() => resolve()); }),
38
+ };
39
+ }
40
+ /** Trusts the gateway-signed identity headers forwarded on the WebSocket upgrade request. */
41
+ export function gatewaySocketAuth(secret) {
42
+ return (socket) => {
43
+ const result = verifyIdentityHeaders(socket.handshake.headers, secret);
44
+ return result.status === 'authenticated' ? result.user : null;
45
+ };
46
+ }
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/socket/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAmC,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA2BnD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,QAAQ,MAAM,EAAE,CAAC;AAE7D,MAAM,UAAU,kBAAkB,CAAC,UAAsB,EAAE,OAA4B;IACrF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE;QAChC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE;QAC9F,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,OAAO,CAAC,aAAa;KACzB,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;YAC3D,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACxB,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YACnF,IAAI,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAgB,CAAC;QAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACxE,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7D,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACxG,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,EAAE;QACF,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QACzF,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3E,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;KAC5E,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,CAAC,MAAc,EAAmB,EAAE;QACzC,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,SAAS,CAAC,OAAiC,EAAE,MAAM,CAAC,CAAC;QACjG,OAAO,MAAM,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { newId } from '../common/id.js';
2
+ import { signIdentityHeaders } from '../identity/index.js';
3
+ import { buildEnvelope } from '../messaging/envelope.js';
4
+ /** A logger spy: records every line (level, message, meta) so tests can assert on log context. */
5
+ export class RecordingLogger {
6
+ lines = [];
7
+ debug(message, meta = {}) { this.lines.push({ level: 'debug', message, meta }); }
8
+ info(message, meta = {}) { this.lines.push({ level: 'info', message, meta }); }
9
+ warn(message, meta = {}) { this.lines.push({ level: 'warn', message, meta }); }
10
+ error(message, meta = {}) { this.lines.push({ level: 'error', message, meta }); }
11
+ }
12
+ /** An AuthUser for tests. */
13
+ export function makeAuthUser(overrides = {}) {
14
+ return { id: newId(), email: 'user@example.com', roles: [], permissions: [], scopes: {}, attrs: {}, ...overrides };
15
+ }
16
+ /** Gateway-signed identity headers for route tests (use with supertest `.set()`). */
17
+ export function identityHeaders(user, secret) {
18
+ return signIdentityHeaders(user, secret);
19
+ }
20
+ /** Records everything published/sent so tests can assert on events, commands and direct queue sends. */
21
+ export class RecordingBus {
22
+ source;
23
+ events = [];
24
+ commands = [];
25
+ queued = [];
26
+ failWith = null;
27
+ constructor(source = 'test') {
28
+ this.source = source;
29
+ }
30
+ async publish(type, data, meta) {
31
+ if (this.failWith)
32
+ throw this.failWith;
33
+ const envelope = buildEnvelope(type, data, this.source, meta);
34
+ this.events.push(envelope);
35
+ return envelope;
36
+ }
37
+ async publishEnvelope(envelope) {
38
+ if (this.failWith)
39
+ throw this.failWith;
40
+ this.events.push(envelope);
41
+ }
42
+ async send(command, data, meta) {
43
+ if (this.failWith)
44
+ throw this.failWith;
45
+ const envelope = buildEnvelope(command, data, this.source, meta);
46
+ this.commands.push(envelope);
47
+ return envelope;
48
+ }
49
+ async sendEnvelope(envelope) {
50
+ if (this.failWith)
51
+ throw this.failWith;
52
+ this.commands.push(envelope);
53
+ }
54
+ async sendToQueue(queue, payload, options = {}) {
55
+ if (this.failWith)
56
+ throw this.failWith;
57
+ this.queued.push({ queue, payload, options });
58
+ }
59
+ eventsOfType(type) {
60
+ return this.events.filter((e) => e.type === type);
61
+ }
62
+ commandsOfType(command) {
63
+ return this.commands.filter((e) => e.type === command);
64
+ }
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAqB,MAAM,0BAA0B,CAAC;AAU5E,kGAAkG;AAClG,MAAM,OAAO,eAAe;IACjB,KAAK,GAAc,EAAE,CAAC;IAC/B,KAAK,CAAC,OAAe,EAAE,OAAgC,EAAE,IAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACxH,IAAI,CAAC,OAAe,EAAE,OAAgC,EAAE,IAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtH,IAAI,CAAC,OAAe,EAAE,OAAgC,EAAE,IAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtH,KAAK,CAAC,OAAe,EAAE,OAAgC,EAAE,IAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;CACzH;AAED,6BAA6B;AAC7B,MAAM,UAAU,YAAY,CAAC,YAA+B,EAAE;IAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC;AACrH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,IAAc,EAAE,MAAc;IAC5D,OAAO,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,wGAAwG;AACxG,MAAM,OAAO,YAAY;IAMM;IALpB,MAAM,GAAoB,EAAE,CAAC;IAC7B,QAAQ,GAAoB,EAAE,CAAC;IAC/B,MAAM,GAA0E,EAAE,CAAC;IAC5F,QAAQ,GAAiB,IAAI,CAAC;IAE9B,YAA6B,SAAS,MAAM;QAAf,WAAM,GAAN,MAAM,CAAS;IAAG,CAAC;IAEhD,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,IAAO,EAAE,IAAmB;QACzD,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAyB,CAAC,CAAC;QAC5C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,QAAuB;QAC3C,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,OAAe,EAAE,IAAO,EAAE,IAAmB;QACzD,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAyB,CAAC,CAAC;QAC9C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAuB;QACxC,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,OAAgB,EAAE,UAA4B,EAAE;QAC/E,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,cAAc,CAAC,OAAe;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IACzD,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { ID_PATTERN } from '../common/id.js';
3
+ import { ValidationError } from '../common/errors.js';
4
+ export const idSchema = z.string().regex(ID_PATTERN, 'Invalid id');
5
+ export const idParamSchema = z.object({ id: idSchema });
6
+ export const paginationSchema = z.object({
7
+ page: z.coerce.number().int().min(1).default(1),
8
+ limit: z.coerce.number().int().min(1).max(100).default(20),
9
+ });
10
+ export const sortOrderSchema = z.enum(['asc', 'desc']).default('desc');
11
+ export const emailSchema = z.string().trim().toLowerCase().email('Invalid email address').max(191);
12
+ export const slugSchema = z.string().trim().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Invalid slug');
13
+ /** Length-first password rule (NIST 800-63B): 8–128 chars, no composition rules. Tighten per app if needed. */
14
+ export const passwordSchema = z.string().min(8, 'Password must be at least 8 characters').max(128, 'Password is too long');
15
+ /** Accepts `true/false/1/0/yes/no` from query strings. */
16
+ export const booleanish = z.union([z.boolean(), z.enum(['true', 'false', '1', '0', 'yes', 'no'])]).transform((v) => v === true || v === 'true' || v === '1' || v === 'yes');
17
+ /** Comma list or repeated query param → string[]. */
18
+ export const csvList = z.union([z.string(), z.array(z.string())]).transform((v) => (Array.isArray(v) ? v : v.split(',')).map((s) => s.trim()).filter(Boolean));
19
+ /** Converts Zod issues into the `{ field: [messages] }` shape used by the error envelope. */
20
+ export function zodIssuesToErrors(error) {
21
+ const errors = {};
22
+ for (const issue of error.issues) {
23
+ const key = issue.path.join('.') || '_';
24
+ (errors[key] ??= []).push(issue.message);
25
+ }
26
+ return errors;
27
+ }
28
+ /** Parses `input` or throws a ValidationError (422). */
29
+ export function parseOrThrow(schema, input) {
30
+ const result = schema.safeParse(input);
31
+ if (!result.success)
32
+ throw new ValidationError(zodIssuesToErrors(result.error));
33
+ return result.data;
34
+ }
35
+ export { z };
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/validation/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { AuthUser } from '../types/index.js';
2
+ /**
3
+ * Permission and ownership checks. Keep every authorization decision in these helpers (not
4
+ * inline in controllers) so the rules are identical across endpoints and services.
5
+ *
6
+ * Permission strings are dotted (`users.manage`, `media.manage.any`). Grants may use
7
+ * wildcards: `*` grants everything; `media.*` grants any permission starting with `media.`.
8
+ */
9
+ export declare const WILDCARD_PERMISSION = "*";
10
+ /** True when the user holds at least one of `required`. */
11
+ export declare function hasPermission(user: Pick<AuthUser, 'permissions'> | null | undefined, ...required: string[]): boolean;
12
+ export declare function hasAllPermissions(user: Pick<AuthUser, 'permissions'> | null | undefined, ...required: string[]): boolean;
13
+ export declare function hasRole(user: Pick<AuthUser, 'roles'> | null | undefined, ...roles: string[]): boolean;
14
+ export declare function requireUser(user: AuthUser | null | undefined): AuthUser;
15
+ export declare function assertPermission(user: AuthUser | null | undefined, ...required: string[]): AuthUser;
16
+ /** IDs the user may act on within a scope (e.g. profiles they are a member of). */
17
+ export declare function scopeIds(user: Pick<AuthUser, 'scopes'> | null | undefined, scope: string): string[];
18
+ /**
19
+ * Self-owned data: allow when the caller *is* `ownerUserId`, or holds `bypassPermission`.
20
+ * Throws ForbiddenError otherwise.
21
+ */
22
+ export declare function assertSelfOrPermission(user: AuthUser | null | undefined, ownerUserId: string, bypassPermission?: string): AuthUser;
23
+ /**
24
+ * Scoped data: allow when `resourceScopeId` is one of the caller's scope IDs, or the caller holds
25
+ * `bypassPermission`.
26
+ */
27
+ export declare function assertInScope(user: AuthUser | null | undefined, scope: string, resourceScopeId: string, bypassPermission?: string): AuthUser;
28
+ /**
29
+ * Resolves the single scope ID a request acts on. With one membership it is implicit; with
30
+ * several the client names one (e.g. `x-active-profile-id`), which must be a member ID.
31
+ * IDs from request bodies are never trusted — only the gateway-signed scopes.
32
+ */
33
+ export declare function resolveActiveScopeId(user: AuthUser | null | undefined, scope: string, requested?: string | null): string;
34
+ /**
35
+ * Like `resolveActiveScopeId`, for screens that work without a selector: with several memberships
36
+ * and none named, the first (primary) one is used. A named ID must still be a member ID.
37
+ */
38
+ export declare function resolvePrimaryScopeId(user: AuthUser | null | undefined, scope: string, requested?: string | null): string;
39
+ export type OwnerFilter = {
40
+ all: true;
41
+ } | {
42
+ all: false;
43
+ ids: string[];
44
+ };
45
+ /**
46
+ * Repository filter for list queries: `{ all: true }` for callers holding `bypassPermission`,
47
+ * otherwise the caller's own scope IDs (possibly empty → return nothing).
48
+ */
49
+ export declare function ownerFilter(user: AuthUser | null | undefined, scope: string, bypassPermission?: string): OwnerFilter;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/access/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAOvC,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAGpH;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAGxH;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAErG;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,CAGvE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,CAInG;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnG;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAKlI;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAK5I;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CASxH;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGzH;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,GAAG,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAExE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW,CAIpH"}
@@ -0,0 +1,43 @@
1
+ import type { Logger } from '../types/index.js';
2
+ /** Sends operator alerts (DLQ depth, stalled queues…). Never throws: alerting must not break its caller. */
3
+ export interface AlertSender {
4
+ /** True when the alert was accepted; false when it was not sent (not configured, or the hook failed). */
5
+ send(messages: string[], options?: {
6
+ alertType?: string;
7
+ }): Promise<boolean>;
8
+ }
9
+ /** The body the platform's Slack notification app accepts (the same one `health-monitor.sh` posts). */
10
+ export interface SlackAlertPayload {
11
+ messages: string[];
12
+ alert_type: string;
13
+ app_name: string;
14
+ environment: string;
15
+ consolidate: boolean;
16
+ }
17
+ export interface SlackAlerterOptions {
18
+ /** The notification app's URL. Empty = not configured: alerts are logged and skipped. */
19
+ url: string;
20
+ /** Product name shown on the alert, e.g. `Showbiz`. */
21
+ app: string;
22
+ /** `staging`, `production`… */
23
+ environment: string;
24
+ logger?: Logger;
25
+ timeoutMs?: number;
26
+ /** Injected in tests; defaults to the global `fetch`. */
27
+ fetch?: typeof fetch;
28
+ }
29
+ /**
30
+ * Posts alerts to a Slack notification webhook as `{ messages, alert_type, app_name, environment, consolidate }`.
31
+ * An unset URL is a valid "alerts off" configuration: the first skipped alert is warned about once, then silently.
32
+ */
33
+ export declare class SlackAlerter implements AlertSender {
34
+ private readonly options;
35
+ private warnedUnconfigured;
36
+ private readonly logger;
37
+ constructor(options: SlackAlerterOptions);
38
+ get configured(): boolean;
39
+ send(messages: string[], options?: {
40
+ alertType?: string;
41
+ }): Promise<boolean>;
42
+ }
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/alerting/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,4GAA4G;AAC5G,MAAM,WAAW,WAAW;IAC1B,yGAAyG;IACzG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9E;AAED,uGAAuG;AACvG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,YAAa,YAAW,WAAW;IAIlC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEH,OAAO,EAAE,mBAAmB;IAIzD,IAAI,UAAU,IAAI,OAAO,CAExB;IAEK,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAgCvF"}
@@ -0,0 +1,45 @@
1
+ import { Redis, type RedisOptions } from 'ioredis';
2
+ import type { Logger } from '../types/index.js';
3
+ export interface CacheSetOptions {
4
+ ttlSec?: number;
5
+ /** Tags let one call invalidate every key derived from an entity (e.g. `user:01J…`). */
6
+ tags?: string[];
7
+ }
8
+ export interface Cache {
9
+ get<T>(key: string): Promise<T | undefined>;
10
+ set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>;
11
+ del(...keys: string[]): Promise<void>;
12
+ /** Cache-aside: returns the cached value or loads, stores and returns it. Concurrent misses share one load. */
13
+ wrap<T>(key: string, loader: () => Promise<T>, options?: CacheSetOptions & {
14
+ cacheNull?: boolean;
15
+ }): Promise<T>;
16
+ invalidateTags(...tags: string[]): Promise<void>;
17
+ }
18
+ export interface CacheOptions {
19
+ /** Null → in-memory only (tests, local tooling). */
20
+ redis: Redis | null;
21
+ /** Key namespace, e.g. `auth` → keys `auth:<key>`. Combine with the client-level env prefix. */
22
+ namespace: string;
23
+ defaultTtlSec?: number;
24
+ /** Upper bound for the in-memory layer / fallback. */
25
+ memoryMaxEntries?: number;
26
+ logger?: Logger;
27
+ }
28
+ /**
29
+ * Redis-backed cache with tag invalidation. If Redis errors, reads fall through to the loader
30
+ * and writes go to a bounded in-memory store, so a cache outage degrades latency rather than
31
+ * availability.
32
+ */
33
+ export declare function createCache(options: CacheOptions): Cache;
34
+ export interface RedisClientOptions {
35
+ url?: string;
36
+ db?: number;
37
+ /** Prepended to every key by ioredis, e.g. `showbiz:prod:`. */
38
+ keyPrefix?: string;
39
+ logger?: Logger;
40
+ options?: RedisOptions;
41
+ }
42
+ /** ioredis client that never crashes the process on connection errors and retries forever. */
43
+ export declare function createRedisClient(opts: RedisClientOptions): Redis;
44
+ export type { Redis };
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,+GAA+G;IAC/G,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChH,cAAc,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA+BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,CA+FxD;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,KAAK,CAmBjE;AAED,YAAY,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare const sleep: (ms: number) => Promise<void>;
2
+ export interface RetryOptions {
3
+ retries: number;
4
+ /** Delay before attempt n (1-based retry number). Default: 200ms × 2^(n-1), capped at 5s. */
5
+ delayMs?: (attempt: number) => number;
6
+ shouldRetry?: (error: unknown) => boolean;
7
+ }
8
+ /** Runs `fn`, retrying failures with exponential back-off. */
9
+ export declare function retry<T>(fn: (attempt: number) => Promise<T>, options: RetryOptions): Promise<T>;
10
+ /** Rejects with `onTimeout()` if `promise` does not settle within `ms`. */
11
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, onTimeout: () => Error): Promise<T>;
12
+ /** Splits `items` into arrays of at most `size`. */
13
+ export declare function chunk<T>(items: readonly T[], size: number): T[][];
14
+ //# sourceMappingURL=async.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../../src/common/async.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAAsD,CAAC;AAEtG,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC3C;AAED,8DAA8D;AAC9D,wBAAsB,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAYrG;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAMlG;AAED,oDAAoD;AACpD,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,CAIjE"}
@@ -0,0 +1,17 @@
1
+ export type AuditAction = 'create' | 'update' | 'delete';
2
+ type AuditRow<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'> & {
3
+ id: string;
4
+ parentId: string;
5
+ action: AuditAction;
6
+ };
7
+ /**
8
+ * Builds the row for a `*_audits` sibling table: a copy of the parent row plus `action`,
9
+ * `parentId`, and a fresh `id`. The parent's timestamps are dropped so the audit row records
10
+ * when the change happened. The actor lands in `createdBy`/`updatedBy` exactly as on the parent
11
+ * (null for system operations). Strip secrets (e.g. password hashes) before calling.
12
+ */
13
+ export declare function buildAuditRow<T extends {
14
+ id: string;
15
+ }>(row: T, action: AuditAction, actorId?: string | null): AuditRow<T>;
16
+ export {};
17
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/common/audit.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD,KAAK,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAErH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,CAS/H"}
@@ -0,0 +1,18 @@
1
+ export declare function sha256(value: string | Buffer): string;
2
+ export declare function hmacSha256(secret: string | Buffer, value: string): string;
3
+ /** Constant-time string comparison (false when lengths differ). */
4
+ export declare function safeEqual(a: string, b: string): boolean;
5
+ /** URL-safe random token (default 32 bytes of entropy). */
6
+ export declare function randomToken(bytes?: number): string;
7
+ /** Numeric one-time code, e.g. `'042917'`. Uses a CSPRNG. */
8
+ export declare function randomNumericCode(length?: number): string;
9
+ /**
10
+ * Hash a value that must be looked up later (refresh tokens, reset tokens, OTPs) with a
11
+ * server-side pepper so a database leak alone cannot be replayed.
12
+ */
13
+ export declare function hashToken(token: string, pepper: string): string;
14
+ /** AES-256-GCM encrypt. Output: `v1:<iv>:<tag>:<ciphertext>` (base64url parts). */
15
+ export declare function encryptSecret(plaintext: string, key: string): string;
16
+ export declare function decryptSecret(payload: string, key: string): string;
17
+ export declare function isEncryptedSecret(value: unknown): value is string;
18
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/common/crypto.ts"],"names":[],"mappings":"AAEA,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,KAAK,SAAK,GAAG,MAAM,CAE9C;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,MAAM,SAAI,GAAG,MAAM,CAIpD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/D;AAcD,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQlE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEjE"}
@@ -0,0 +1,71 @@
1
+ export type ErrorDetails = Record<string, string[]>;
2
+ /**
3
+ * Base class for errors that map to an HTTP status. Throw these from services; the shared
4
+ * error middleware turns them into the standard envelope. Anything that is not an AppError is
5
+ * reported as a 500 with a generic message so internals never leak.
6
+ */
7
+ export declare class AppError extends Error {
8
+ readonly statusCode: number;
9
+ readonly code: string;
10
+ readonly errors?: ErrorDetails;
11
+ /** Consumers dead-letter immediately instead of retrying when this is false. */
12
+ readonly retryable: boolean;
13
+ constructor(statusCode: number, message: string, options?: {
14
+ code?: string;
15
+ errors?: ErrorDetails;
16
+ retryable?: boolean;
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ export declare class BadRequestError extends AppError {
21
+ constructor(message?: string, options?: {
22
+ code?: string;
23
+ errors?: ErrorDetails;
24
+ });
25
+ }
26
+ export declare class UnauthorizedError extends AppError {
27
+ constructor(message?: string, options?: {
28
+ code?: string;
29
+ });
30
+ }
31
+ export declare class ForbiddenError extends AppError {
32
+ constructor(message?: string, options?: {
33
+ code?: string;
34
+ });
35
+ }
36
+ export declare class NotFoundError extends AppError {
37
+ constructor(message?: string, options?: {
38
+ code?: string;
39
+ });
40
+ }
41
+ export declare class ConflictError extends AppError {
42
+ constructor(message?: string, options?: {
43
+ code?: string;
44
+ errors?: ErrorDetails;
45
+ });
46
+ }
47
+ export declare class ValidationError extends AppError {
48
+ constructor(errors: ErrorDetails, message?: string);
49
+ }
50
+ export declare class TooManyRequestsError extends AppError {
51
+ constructor(message?: string, options?: {
52
+ code?: string;
53
+ });
54
+ }
55
+ export declare class ServiceUnavailableError extends AppError {
56
+ constructor(message?: string, options?: {
57
+ code?: string;
58
+ cause?: unknown;
59
+ });
60
+ }
61
+ /** A failure that will never succeed on retry (bad payload, unknown template, …). */
62
+ export declare class NonRetryableError extends AppError {
63
+ constructor(message: string, options?: {
64
+ code?: string;
65
+ cause?: unknown;
66
+ });
67
+ }
68
+ export declare function isAppError(error: unknown): error is AppError;
69
+ /** Message safe to show a caller; hides internals for unexpected errors. */
70
+ export declare function publicMessage(error: unknown): string;
71
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/common/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAEpD;;;;GAIG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,YAAY,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAQ9I;AAgBD,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,SAAgB,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,YAAY,CAAA;KAAO;CAC5F;AACD,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,OAAO,SAA4B,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CACjF;AACD,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,SAAsD,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CAC3G;AACD,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,SAAuB,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CAC5E;AACD,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,SAA4B,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,YAAY,CAAA;KAAO;CACxG;AACD,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,MAAM,EAAE,YAAY,EAAE,OAAO,SAAsB;CAChE;AACD,qBAAa,oBAAqB,SAAQ,QAAQ;gBACpC,OAAO,SAA+C,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CACpG;AACD,qBAAa,uBAAwB,SAAQ,QAAQ;gBACvC,OAAO,SAAoC,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAC1G;AAED,qFAAqF;AACrF,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAG9E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIpD"}
@@ -0,0 +1,14 @@
1
+ export declare const ID_LENGTH = 26;
2
+ export declare const ID_PATTERN: RegExp;
3
+ /** A new time-ordered, monotonic ULID. */
4
+ export declare function newId(): string;
5
+ export declare function isId(value: unknown): value is string;
6
+ /** Creation time encoded in a ULID. */
7
+ export declare function idTimestamp(id: string): Date;
8
+ /**
9
+ * A stable, valid ID for seed/fixture rows: the same `(namespace, n)` always yields the same ID, so
10
+ * seeds in different services can reference each other's rows without sharing a database.
11
+ * `namespace` is two Crockford characters (e.g. `PR` for profiles).
12
+ */
13
+ export declare function fixtureId(namespace: string, n: number): string;
14
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../../src/common/id.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,UAAU,QAA6B,CAAC;AAErD,0CAA0C;AAC1C,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEpD;AAED,uCAAuC;AACvC,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5C;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9D"}