@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,39 @@
1
+ /** URL slug: lowercase ASCII, dash-separated, accents folded. */
2
+ export function slugify(value, maxLength = 80) {
3
+ return value
4
+ .normalize('NFKD')
5
+ .replace(/[̀-ͯ]/g, '')
6
+ .toLowerCase()
7
+ .replace(/&/g, ' and ')
8
+ .replace(/[^a-z0-9]+/g, '-')
9
+ .replace(/^-+|-+$/g, '')
10
+ .slice(0, maxLength)
11
+ .replace(/-+$/g, '');
12
+ }
13
+ /** Replaces `{{ name }}` placeholders; unknown names become empty strings. */
14
+ export function interpolate(template, variables, escape) {
15
+ return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
16
+ const value = path.split('.').reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), variables);
17
+ const text = value === undefined || value === null ? '' : String(value);
18
+ return escape ? escape(text) : text;
19
+ });
20
+ }
21
+ /** Placeholder names used by a template, e.g. `['user.name', 'code']`. */
22
+ export function templateVariables(template) {
23
+ const names = new Set();
24
+ for (const match of template.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g))
25
+ names.add(match[1]);
26
+ return [...names];
27
+ }
28
+ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
29
+ export function escapeHtml(value) {
30
+ return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]);
31
+ }
32
+ /** `jane.doe@example.com` → `ja***@example.com` for logs and audit trails. */
33
+ export function maskEmail(email) {
34
+ const [local, domain] = email.split('@');
35
+ if (!local || !domain)
36
+ return '***';
37
+ return `${local.slice(0, 2)}***@${domain}`;
38
+ }
39
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/common/text.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,SAAS,GAAG,EAAE;IACnD,OAAO,KAAK;SACT,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;SACnB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAkC,EAAE,MAAkC;IAClH,OAAO,QAAQ,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAU,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;QAC7J,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,YAAY,GAA2B,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAErH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAE,CAAC,CAAC;AAC9D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,96 @@
1
+ /** True for IANA zones the runtime knows (`Africa/Nairobi`), false for garbage. */
2
+ export function isValidTimeZone(zone) {
3
+ try {
4
+ new Intl.DateTimeFormat('en-US', { timeZone: zone });
5
+ return true;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ /** Formats an instant in a zone, e.g. `30 Sep 2026, 14:05 EAT`. Falls back to UTC for invalid zones. */
12
+ export function formatInTimeZone(date, zone, locale = 'en-GB') {
13
+ const timeZone = isValidTimeZone(zone) ? zone : 'UTC';
14
+ return new Intl.DateTimeFormat(locale, {
15
+ timeZone,
16
+ day: '2-digit',
17
+ month: 'short',
18
+ year: 'numeric',
19
+ hour: '2-digit',
20
+ minute: '2-digit',
21
+ timeZoneName: 'short',
22
+ }).format(new Date(date));
23
+ }
24
+ /**
25
+ * Formats an instant for a person in their zone, falling back to `fallbackZone` (itself falling back to
26
+ * UTC) when theirs is missing or unknown. Never throws, and says when it fell back so callers can log it.
27
+ */
28
+ export function formatInstantInZone(instant, zone, fallbackZone = 'UTC', locale = 'en-GB') {
29
+ const usedFallback = !(typeof zone === 'string' && zone && isValidTimeZone(zone));
30
+ const timezone = usedFallback ? (isValidTimeZone(fallbackZone) ? fallbackZone : 'UTC') : zone;
31
+ const date = new Date(instant);
32
+ return { text: Number.isNaN(date.getTime()) ? '' : formatInTimeZone(date, timezone, locale), timezone, usedFallback };
33
+ }
34
+ /** Offset of `zone` from UTC in minutes at `at` (positive east of Greenwich). */
35
+ export function timeZoneOffsetMinutes(zone, at = new Date()) {
36
+ const parts = new Intl.DateTimeFormat('en-US', {
37
+ timeZone: zone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit',
38
+ }).formatToParts(at);
39
+ const get = (type) => Number(parts.find((p) => p.type === type)?.value);
40
+ const asUtc = Date.UTC(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'), get('second'));
41
+ return Math.round((asUtc - Math.floor(at.getTime() / 1000) * 1000) / 60_000);
42
+ }
43
+ export const addSeconds = (date, seconds) => new Date(date.getTime() + seconds * 1000);
44
+ export const addMinutes = (date, minutes) => addSeconds(date, minutes * 60);
45
+ const MS_PER_MINUTE = 60_000;
46
+ const MS_PER_DAY = 86_400_000;
47
+ const CLOCK_TIME = /^([01]\d|2[0-3]):([0-5]\d)$/;
48
+ /** Minutes since midnight of a 24-hour `HH:MM` clock time. Throws on anything else (never guesses). */
49
+ export function parseClockTime(value) {
50
+ const match = CLOCK_TIME.exec(String(value ?? '').trim());
51
+ if (!match)
52
+ throw new RangeError(`Not a 24-hour HH:MM clock time: "${value}"`);
53
+ return Number(match[1]) * 60 + Number(match[2]);
54
+ }
55
+ /** The local calendar date of `at` in `zone`, and how far into that local day it is. */
56
+ function localDate(at, zone) {
57
+ const local = new Date(at.getTime() + timeZoneOffsetMinutes(zone, at) * MS_PER_MINUTE);
58
+ const msOfDay = ((local.getTime() % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;
59
+ return { year: local.getUTCFullYear(), month: local.getUTCMonth(), day: local.getUTCDate(), msOfDay };
60
+ }
61
+ /**
62
+ * The instant a wall-clock time occurs in `zone` (month is 0-based; an overflowing day rolls over).
63
+ * The offset is read again at the first estimate, so the result is right across a DST change; a wall
64
+ * time skipped by a spring-forward resolves to the nearest valid instant.
65
+ */
66
+ export function zonedWallTimeToInstant(year, month, day, minutesOfDay, zone) {
67
+ const wall = Date.UTC(year, month, day) + minutesOfDay * MS_PER_MINUTE;
68
+ const estimate = wall - timeZoneOffsetMinutes(zone, new Date(wall)) * MS_PER_MINUTE;
69
+ return new Date(wall - timeZoneOffsetMinutes(zone, new Date(estimate)) * MS_PER_MINUTE);
70
+ }
71
+ /** The local calendar day containing `at` in `zone`, as a half-open `[start, end)` UTC range (23–25 h on DST days). */
72
+ export function zonedDayBounds(at, zone) {
73
+ const { year, month, day } = localDate(at, zone);
74
+ return { start: zonedWallTimeToInstant(year, month, day, 0, zone), end: zonedWallTimeToInstant(year, month, day + 1, 0, zone) };
75
+ }
76
+ /**
77
+ * When `at` falls inside the daily window `[start, end)` (local `HH:MM` times in `zone`; the window may
78
+ * cross midnight), the instant that window ends; otherwise null. `start === end` means no window.
79
+ */
80
+ export function quietWindowEndAt(at, zone, start, end) {
81
+ const startMin = parseClockTime(start);
82
+ const endMin = parseClockTime(end);
83
+ if (startMin === endMin)
84
+ return null;
85
+ const { year, month, day, msOfDay } = localDate(at, zone);
86
+ const startMs = startMin * MS_PER_MINUTE;
87
+ const endMs = endMin * MS_PER_MINUTE;
88
+ const crossesMidnight = startMin > endMin;
89
+ const inside = crossesMidnight ? msOfDay >= startMs || msOfDay < endMs : msOfDay >= startMs && msOfDay < endMs;
90
+ if (!inside)
91
+ return null;
92
+ // A midnight-crossing window entered before midnight ends tomorrow; otherwise it ends today.
93
+ const endsTomorrow = crossesMidnight && msOfDay >= startMs;
94
+ return zonedWallTimeToInstant(year, month, day + (endsTomorrow ? 1 : 0), endMin, zone);
95
+ }
96
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../../src/common/time.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { z } from 'zod';
4
+ /**
5
+ * Environment building blocks. Services compose the pieces they need:
6
+ *
7
+ * const env = loadEnv(serviceEnv.merge(databaseEnv).merge(messagingEnv).extend({ MY_VAR: z.string() }));
8
+ */
9
+ export const serviceEnv = z.object({
10
+ NODE_ENV: z.enum(['development', 'test', 'staging', 'production']).default('development'),
11
+ PORT: z.coerce.number().int().positive(),
12
+ SERVICE_NAME: z.string().min(1),
13
+ /** Short app identifier used in exchange names and key prefixes (e.g. `showbiz`). */
14
+ APP_NAME: z.string().min(1).default('app'),
15
+ LOG_LEVEL: z.enum(['error', 'warn', 'info', 'http', 'debug']).optional(),
16
+ CORS_ORIGINS: z.string().default(''),
17
+ /** Verifies gateway-signed identity headers. */
18
+ GATEWAY_SHARED_SECRET: z.string().min(16),
19
+ /** Authenticates service-to-service `/internal/*` calls. */
20
+ INTERNAL_SERVICE_SECRET: z.string().min(16),
21
+ });
22
+ export const databaseEnv = z.object({
23
+ DB_HOST: z.string().default('127.0.0.1'),
24
+ DB_PORT: z.coerce.number().int().positive().default(3306),
25
+ DB_USER: z.string().default('root'),
26
+ DB_PASSWORD: z.string().default(''),
27
+ DB_NAME: z.string().min(1),
28
+ DB_POOL_MAX: z.coerce.number().int().positive().default(10),
29
+ });
30
+ export const messagingEnv = z.object({
31
+ RABBITMQ_URL: z.string().optional(),
32
+ RABBITMQ_HOST: z.string().default('127.0.0.1'),
33
+ RABBITMQ_PORT: z.coerce.number().int().positive().default(5672),
34
+ RABBITMQ_USERNAME: z.string().default('guest'),
35
+ RABBITMQ_PASSWORD: z.string().default('guest'),
36
+ RABBITMQ_VHOST: z.string().default('/'),
37
+ /** Environment prefix for queue and exchange names (`dev`, `staging`, `prod`). */
38
+ QUEUE_PREFIX: z.string().default('dev'),
39
+ RABBITMQ_ENABLED: z.enum(['true', 'false']).default('true').transform((v) => v === 'true'),
40
+ });
41
+ export const redisEnv = z.object({
42
+ REDIS_URL: z.string().optional(),
43
+ REDIS_DB: z.coerce.number().int().min(0).default(0),
44
+ /** Key prefix, e.g. `app:dev:` — never share one between apps or environments. */
45
+ REDIS_PREFIX: z.string().default(''),
46
+ });
47
+ /** Builds an AMQP URL from discrete env vars (or returns RABBITMQ_URL when set). */
48
+ export function amqpUrlFromEnv(env) {
49
+ if (env.RABBITMQ_URL)
50
+ return env.RABBITMQ_URL;
51
+ const vhost = env.RABBITMQ_VHOST === '/' ? '%2F' : encodeURIComponent(env.RABBITMQ_VHOST.replace(/^\//, ''));
52
+ return `amqp://${encodeURIComponent(env.RABBITMQ_USERNAME)}:${encodeURIComponent(env.RABBITMQ_PASSWORD)}@${env.RABBITMQ_HOST}:${env.RABBITMQ_PORT}/${vhost}`;
53
+ }
54
+ /** Reads `KEY=value` lines from a dotenv file into process.env without overriding set values. */
55
+ export function loadDotEnv(path = resolve(process.cwd(), '.env')) {
56
+ if (!existsSync(path))
57
+ return;
58
+ for (const line of readFileSync(path, 'utf8').split(/\r?\n/)) {
59
+ const trimmed = line.trim();
60
+ if (!trimmed || trimmed.startsWith('#'))
61
+ continue;
62
+ const eq = trimmed.indexOf('=');
63
+ if (eq === -1)
64
+ continue;
65
+ const key = trimmed.slice(0, eq).trim();
66
+ let value = trimmed.slice(eq + 1).trim();
67
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
68
+ value = value.slice(1, -1);
69
+ if (process.env[key] === undefined)
70
+ process.env[key] = value;
71
+ }
72
+ }
73
+ export class ConfigError extends Error {
74
+ }
75
+ /** Loads `.env` (if present) and validates process.env. Throws ConfigError listing every problem. */
76
+ export function loadEnv(schema, source = process.env, options = {}) {
77
+ if (options.dotenv !== false && source === process.env)
78
+ loadDotEnv();
79
+ const result = schema.safeParse(source);
80
+ if (!result.success) {
81
+ const lines = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
82
+ throw new ConfigError(`Invalid environment configuration:\n${lines}`);
83
+ }
84
+ return result.data;
85
+ }
86
+ /** Comma-separated env value → trimmed, non-empty list. */
87
+ export const listFromEnv = (value) => (value ?? '').split(',').map((s) => s.trim()).filter(Boolean);
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import { idSchema } from '../validation/index.js';
3
+ /**
4
+ * The newswire → editorial draft hand-off (design Appendix A.4 plus `paragraphFactIds` and
5
+ * `image.uploadId`, decision R14). newswire-service stages it through its outbox; editorial-service
6
+ * validates it with the same schema before creating the story. One contract for both sides.
7
+ */
8
+ export const EDITORIAL_DRAFT_SUBMIT = 'editorial:draft.submit';
9
+ /** Field limits of the contract (editorial's story columns). */
10
+ export const EDITORIAL_DRAFT_LIMITS = {
11
+ headline: 200,
12
+ excerpt: 400,
13
+ tags: 6,
14
+ seoTitle: 70,
15
+ seoDescription: 170,
16
+ };
17
+ export const DRAFT_SOURCE_ROLES = ['primary', 'corroborating'];
18
+ export const DRAFT_RISKS = ['low', 'medium', 'high'];
19
+ export const GATE_VERDICTS = ['pass', 'review', 'block'];
20
+ /**
21
+ * Every quality gate newswire runs on a draft, in design order. A submitted draft carries one result per
22
+ * gate; editorial treats a missing one as not passed (auto-publish never runs on an unchecked draft).
23
+ */
24
+ export const QUALITY_GATE_NAMES = [
25
+ 'originality', 'grounding', 'quote_limits', 'corroboration', 'conflicts',
26
+ 'sensitivity', 'category', 'freshness', 'duplicate_of_published', 'length_format',
27
+ ];
28
+ export const FACT_KINDS = ['event', 'person', 'date', 'place', 'number', 'quote', 'context'];
29
+ const text = z.string().trim().min(1);
30
+ const httpsUrl = z
31
+ .string()
32
+ .url()
33
+ .refine((u) => u.startsWith('https://'), 'Source URL must use https');
34
+ const draftSourceSchema = z.object({
35
+ name: text.max(160),
36
+ url: httpsUrl,
37
+ publishedAt: z.string().datetime({ offset: true }).optional(),
38
+ role: z.enum(DRAFT_SOURCE_ROLES),
39
+ });
40
+ const gateResultSchema = z.object({
41
+ gate: text,
42
+ verdict: z.enum(GATE_VERDICTS),
43
+ reasons: z.array(z.string()),
44
+ metrics: z.record(z.string(), z.number()).optional(),
45
+ });
46
+ const factSupportSchema = z.object({ itemId: idSchema, paragraph: z.number().int().min(1) });
47
+ const factSchema = z.object({
48
+ id: text,
49
+ statement: text,
50
+ kind: z.enum(FACT_KINDS),
51
+ entities: z.array(z.string()),
52
+ supportedBy: z.array(factSupportSchema).min(1),
53
+ conflictsWith: z.array(z.string()),
54
+ independentSources: z.number().int().min(0),
55
+ });
56
+ const quoteSchema = factSupportSchema.extend({ text, speaker: text });
57
+ const conflictSchema = z.object({ description: text, factIds: z.array(z.string()) });
58
+ export const editorialDraftSubmitSchema = z
59
+ .object({
60
+ draftId: idSchema,
61
+ clusterId: idSchema,
62
+ headline: text.max(EDITORIAL_DRAFT_LIMITS.headline),
63
+ excerpt: text.max(EDITORIAL_DRAFT_LIMITS.excerpt),
64
+ paragraphs: z.array(text).min(1),
65
+ /** The fact-sheet fact ids each paragraph asserts (same length as `paragraphs`). */
66
+ paragraphFactIds: z.array(z.array(z.string())),
67
+ category: text.max(60),
68
+ tags: z.array(text.max(40)).max(EDITORIAL_DRAFT_LIMITS.tags),
69
+ seo: z.object({
70
+ title: text.max(EDITORIAL_DRAFT_LIMITS.seoTitle),
71
+ description: text.max(EDITORIAL_DRAFT_LIMITS.seoDescription),
72
+ }),
73
+ social: z.object({ facebook: text, x: text, whatsapp: text, instagram: text }),
74
+ sources: z.array(draftSourceSchema).min(1),
75
+ /** An imported or profile cover (upload-service id), or null when the draft has none. */
76
+ image: z.object({ uploadId: idSchema, credit: text.max(160), alt: text.max(255) }).nullable(),
77
+ relatedProfileIds: z.array(idSchema),
78
+ suggestedProfileIds: z.array(idSchema),
79
+ review: z.object({
80
+ confidence: z.number().min(0).max(1),
81
+ risk: z.enum(DRAFT_RISKS),
82
+ riskReasons: z.array(z.string()),
83
+ gates: z.array(gateResultSchema),
84
+ factSheet: z.object({
85
+ facts: z.array(factSchema),
86
+ quotes: z.array(quoteSchema),
87
+ conflicts: z.array(conflictSchema),
88
+ }),
89
+ /** Prompt slug → version used to produce the draft. */
90
+ promptVersions: z.record(z.string(), z.number().int().positive()),
91
+ }),
92
+ })
93
+ .superRefine((draft, ctx) => {
94
+ if (draft.paragraphFactIds.length !== draft.paragraphs.length) {
95
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['paragraphFactIds'], message: 'Must have one entry per paragraph' });
96
+ }
97
+ const factIds = new Set(draft.review.factSheet.facts.map((f) => f.id));
98
+ draft.paragraphFactIds.forEach((ids, i) => {
99
+ for (const id of ids) {
100
+ if (!factIds.has(id))
101
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['paragraphFactIds', i], message: `Unknown fact id ${id}` });
102
+ }
103
+ });
104
+ if (draft.sources.filter((s) => s.role === 'primary').length !== 1) {
105
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['sources'], message: 'Exactly one source must be primary' });
106
+ }
107
+ });
108
+ //# sourceMappingURL=editorial-draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorial-draft.js","sourceRoot":"","sources":["../../../src/contracts/editorial-draft.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from './editorial-draft.js';
2
+ export * from './notification-dispatch.js';
3
+ export * from './upload-import.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/contracts/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * `notification:dispatch` — the one command every Showbiz service stages (through its outbox) to have
4
+ * notification-service render a seeded template and deliver it by email and/or the in-app feed. No
5
+ * other service sends email itself. One contract for both sides: producers type their payload with it
6
+ * and notification-service validates against it, so the two cannot drift.
7
+ */
8
+ export const NOTIFICATION_DISPATCH = 'notification:dispatch';
9
+ /** Channels a dispatch may ask for. `in_app` needs a recipient `userId` (the bell feed is per user). */
10
+ export const DISPATCH_CHANNELS = ['email', 'in_app'];
11
+ /**
12
+ * What a dispatch is about. Account mail (`security`, `account`, `team`, `marketing`) goes to a user;
13
+ * `staff` is an operational alert to a configured staff address.
14
+ */
15
+ export const NOTIFICATION_CATEGORIES = ['security', 'account', 'team', 'marketing', 'staff'];
16
+ export const DISPATCH_LIMITS = {
17
+ template: 100,
18
+ email: 254,
19
+ name: 200,
20
+ dataKeys: 40,
21
+ dataKey: 64,
22
+ dataValue: 2000,
23
+ secretKeys: 10,
24
+ permission: 100,
25
+ };
26
+ /** Template variables are flat scalars: they are printed into the copy, never serialised objects. */
27
+ export const dispatchValueSchema = z.union([z.string().max(DISPATCH_LIMITS.dataValue), z.number().finite(), z.boolean(), z.null()]);
28
+ export const dispatchRecipientSchema = z.object({
29
+ email: z.string().trim().email().max(DISPATCH_LIMITS.email),
30
+ name: z.string().trim().max(DISPATCH_LIMITS.name).optional(),
31
+ userId: z.string().max(36).nullish(),
32
+ locale: z.string().max(20).nullish(),
33
+ /** IANA zone; time variables are rendered in it. */
34
+ timezone: z.string().max(64).nullish(),
35
+ });
36
+ const dataKey = z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/, 'Use a template variable name').max(DISPATCH_LIMITS.dataKey);
37
+ export const notificationDispatchSchema = z
38
+ .object({
39
+ /** Seeded `notification_templates.slug`. */
40
+ template: z.string().trim().min(1).max(DISPATCH_LIMITS.template),
41
+ category: z.enum(NOTIFICATION_CATEGORIES),
42
+ recipient: dispatchRecipientSchema,
43
+ channels: z.array(z.enum(DISPATCH_CHANNELS)).min(1).max(DISPATCH_CHANNELS.length),
44
+ data: z
45
+ .record(dataKey, dispatchValueSchema)
46
+ .default({})
47
+ .refine((data) => Object.keys(data).length <= DISPATCH_LIMITS.dataKeys, `At most ${DISPATCH_LIMITS.dataKeys} variables`),
48
+ /**
49
+ * Keys of `data` holding a one-time secret (a sign-in code, a reset or confirm link). They are used to
50
+ * render the email and nothing else: notification-service never stores them and never puts them in the
51
+ * in-app feed, and email-service masks them in what it keeps.
52
+ */
53
+ secretKeys: z.array(dataKey).max(DISPATCH_LIMITS.secretKeys).default([]),
54
+ /** Security and staff alerts are always delivered, whatever the recipient's preferences. */
55
+ bypassPreferences: z.boolean().optional(),
56
+ /** The permission staff recipients hold; informational until recipients are resolved by permission. */
57
+ targetPermission: z.string().max(DISPATCH_LIMITS.permission).optional(),
58
+ })
59
+ .superRefine((dispatch, ctx) => {
60
+ for (const key of dispatch.secretKeys) {
61
+ if (!(key in dispatch.data)) {
62
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['secretKeys'], message: `Secret key "${key}" is not in data` });
63
+ }
64
+ }
65
+ });
66
+ /**
67
+ * The seeded templates Showbiz services dispatch, with the variables (`data` keys) each dispatch carries.
68
+ * Producers send exactly these keys; a seeded template may use any subset (notification-service dead-letters
69
+ * a dispatch missing a variable its template uses). Both sides test against this table.
70
+ */
71
+ export const DISPATCH_TEMPLATE_VARIABLES = {
72
+ // auth-admin-service: account and security mail
73
+ 'account-activation': ['name', 'code', 'expiresInHours'],
74
+ 'login-otp': ['name', 'code', 'expiresInMinutes', 'device'],
75
+ 'password-reset': ['name', 'resetUrl', 'expiresInMinutes'],
76
+ 'staff-invitation': ['name', 'resetUrl', 'expiresInMinutes'],
77
+ 'password-changed': ['name', 'changedAt'],
78
+ 'new-device-login': ['name', 'device', 'ip', 'country', 'occurredAt', 'confirmUrl', 'disputeUrl'],
79
+ 'team-member-added': ['name', 'profileName', 'role', 'invitedBy'],
80
+ 'account-suspended': ['name', 'reason'],
81
+ 'welcome-listener': ['name'],
82
+ 'welcome-creator': ['name'],
83
+ // ai / newswire / editorial: staff alerts
84
+ 'ai_budget_threshold': ['requester', 'period', 'threshold_pct', 'spent_usd', 'limit_usd'],
85
+ 'newswire_source_unhealthy': ['source_name', 'source_slug', 'consecutive_failures', 'last_error', 'last_success_at'],
86
+ 'newswire_block_rate_high': ['blocked', 'total', 'block_rate_pct', 'window_hours'],
87
+ 'autopublish_audit_alert': ['problem_rate_pct', 'problem_count', 'sample_count', 'window_days', 'action_taken'],
88
+ };
89
+ //# sourceMappingURL=notification-dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-dispatch.js","sourceRoot":"","sources":["../../../src/contracts/notification-dispatch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { idSchema } from '../validation/index.js';
3
+ /**
4
+ * Licensed image import by URL (Newswire R5, decision R39). newswire-service (and later
5
+ * editorial-service) sends `upload:import`; upload-service fetches the file and answers with an
6
+ * `upload.import.completed` or `upload.import.failed` event. One contract for both sides.
7
+ */
8
+ export const UPLOAD_IMPORT = 'upload:import';
9
+ export const UPLOAD_IMPORT_EVENTS = {
10
+ COMPLETED: 'upload.import.completed',
11
+ FAILED: 'upload.import.failed',
12
+ };
13
+ /** Upload policies an import may use. */
14
+ export const UPLOAD_IMPORT_POLICIES = ['news_image'];
15
+ /** Field limits of the contract (upload-service's `uploads` columns). */
16
+ export const UPLOAD_IMPORT_LIMITS = {
17
+ url: 2048,
18
+ ownerType: 50,
19
+ licenceHolder: 160,
20
+ licenceBasis: 255,
21
+ reason: 255,
22
+ };
23
+ /**
24
+ * `upload:import` data. The URL is only length-checked here: upload-service rejects a non-https or
25
+ * unsafe URL itself, so the sender hears about it through a `failed` event instead of a dead letter.
26
+ */
27
+ export const uploadImportCommandSchema = z.object({
28
+ /** ULID the outcome event echoes back; upload-service is idempotent on it. */
29
+ importId: idSchema,
30
+ url: z.string().min(1).max(UPLOAD_IMPORT_LIMITS.url),
31
+ policy: z.enum(UPLOAD_IMPORT_POLICIES),
32
+ /** Echoed in the outcome only; the imported upload is a system row with no owner. */
33
+ ownerType: z.string().min(1).max(UPLOAD_IMPORT_LIMITS.ownerType),
34
+ ownerId: idSchema,
35
+ licence: z.object({
36
+ holder: z.string().trim().min(1).max(UPLOAD_IMPORT_LIMITS.licenceHolder),
37
+ basis: z.string().trim().min(1).max(UPLOAD_IMPORT_LIMITS.licenceBasis),
38
+ }),
39
+ });
40
+ export const uploadImportCompletedSchema = z.object({
41
+ importId: idSchema,
42
+ uploadId: idSchema,
43
+ ownerType: z.string().min(1).max(UPLOAD_IMPORT_LIMITS.ownerType),
44
+ ownerId: idSchema,
45
+ });
46
+ /** `reason` is a machine code (`too_large`, `http_404`, …; see upload-service `ImportFailureReason`). */
47
+ export const uploadImportFailedSchema = z.object({
48
+ importId: idSchema,
49
+ reason: z.string().min(1).max(UPLOAD_IMPORT_LIMITS.reason),
50
+ });
51
+ //# sourceMappingURL=upload-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-import.js","sourceRoot":"","sources":["../../../src/contracts/upload-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,SAAS,EAAE,yBAAyB;IACpC,MAAM,EAAE,sBAAsB;CACtB,CAAC;AAEX,yCAAyC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,YAAY,CAAU,CAAC;AAG9D,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,GAAG,EAAE,IAAI;IACT,SAAS,EAAE,EAAE;IACb,aAAa,EAAE,GAAG;IAClB,YAAY,EAAE,GAAG;IACjB,MAAM,EAAE,GAAG;CACH,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,8EAA8E;IAC9E,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC;IACpD,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC;IACtC,qFAAqF;IACrF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,SAAS,CAAC;IAChE,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,aAAa,CAAC;QACxE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,YAAY,CAAC;KACvE,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;IAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,SAAS,CAAC;IAChE,OAAO,EAAE,QAAQ;CAClB,CAAC,CAAC;AAEH,yGAAyG;AACzG,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,MAAM,CAAC;CAC3D,CAAC,CAAC"}
@@ -0,0 +1,48 @@
1
+ import mysql from 'mysql2/promise';
2
+ /**
3
+ * mysql2 pool for Drizzle. Sessions run in UTC and utf8mb4 so timestamps and emoji round-trip
4
+ * unchanged regardless of server defaults.
5
+ */
6
+ export function createMysqlPool(options) {
7
+ return mysql.createPool({
8
+ host: options.host,
9
+ port: options.port,
10
+ user: options.user,
11
+ password: options.password,
12
+ database: options.database,
13
+ connectionLimit: options.poolMax ?? 10,
14
+ waitForConnections: true,
15
+ enableKeepAlive: true,
16
+ timezone: 'Z',
17
+ charset: 'utf8mb4_unicode_ci',
18
+ dateStrings: false,
19
+ supportBigNumbers: true,
20
+ });
21
+ }
22
+ /** Knex config for migrations/seeds and query-builder use (same session settings as the pool). */
23
+ export function createKnexConfig(options) {
24
+ return {
25
+ client: 'mysql2',
26
+ connection: {
27
+ host: options.host,
28
+ port: options.port,
29
+ user: options.user,
30
+ password: options.password,
31
+ database: options.database,
32
+ timezone: 'Z',
33
+ charset: 'utf8mb4',
34
+ },
35
+ pool: { min: 0, max: options.poolMax ?? 10 },
36
+ migrations: { directory: options.migrationsDir ?? './migrations', tableName: 'knex_migrations', loadExtensions: ['.cjs', '.js'] },
37
+ seeds: { directory: options.seedsDir ?? './seeds', loadExtensions: ['.cjs', '.js'] },
38
+ };
39
+ }
40
+ /** Readiness probe helper. */
41
+ export async function pingMysql(pool) {
42
+ await pool.query('SELECT 1');
43
+ }
44
+ /** Escapes `%`, `_` and `\` for use inside a LIKE pattern. */
45
+ export function escapeLike(value) {
46
+ return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
47
+ }
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/database/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAWnC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,OAAO,KAAK,CAAC,UAAU,CAAC;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,eAAe,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;QACtC,kBAAkB,EAAE,IAAI;QACxB,eAAe,EAAE,IAAI;QACrB,QAAQ,EAAE,GAAG;QACb,OAAO,EAAE,oBAAoB;QAC7B,WAAW,EAAE,KAAK;QAClB,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAC;AACL,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,gBAAgB,CAAC,OAA+E;IAC9G,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE;YACV,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,QAAQ,EAAE,GAAG;YACb,OAAO,EAAE,SAAS;SACnB;QACD,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE;QAC5C,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,aAAa,IAAI,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;QACjI,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,EAAE,cAAc,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;KACrF,CAAC;AACJ,CAAC;AAED,8BAA8B;AAC9B,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAgB;IAC9C,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AAC/B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACrD,CAAC"}