@we8/cms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +78 -0
  2. package/dist/auth/auth.d.ts +87 -0
  3. package/dist/auth/auth.d.ts.map +1 -0
  4. package/dist/auth/auth.js +90 -0
  5. package/dist/auth/auth.js.map +1 -0
  6. package/dist/auth/routes.admin.d.ts +22 -0
  7. package/dist/auth/routes.admin.d.ts.map +1 -0
  8. package/dist/auth/routes.admin.js +106 -0
  9. package/dist/auth/routes.admin.js.map +1 -0
  10. package/dist/auth/schema.d.ts +1395 -0
  11. package/dist/auth/schema.d.ts.map +1 -0
  12. package/dist/auth/schema.js +76 -0
  13. package/dist/auth/schema.js.map +1 -0
  14. package/dist/env.d.ts +64 -0
  15. package/dist/env.d.ts.map +1 -0
  16. package/dist/env.js +48 -0
  17. package/dist/env.js.map +1 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +170 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/keys/middleware.d.ts +54 -0
  23. package/dist/keys/middleware.d.ts.map +1 -0
  24. package/dist/keys/middleware.js +84 -0
  25. package/dist/keys/middleware.js.map +1 -0
  26. package/dist/keys/routes.admin.d.ts +16 -0
  27. package/dist/keys/routes.admin.d.ts.map +1 -0
  28. package/dist/keys/routes.admin.js +34 -0
  29. package/dist/keys/routes.admin.js.map +1 -0
  30. package/dist/keys/service.d.ts +36 -0
  31. package/dist/keys/service.d.ts.map +1 -0
  32. package/dist/keys/service.js +120 -0
  33. package/dist/keys/service.js.map +1 -0
  34. package/dist/lib/bots.d.ts +24 -0
  35. package/dist/lib/bots.d.ts.map +1 -0
  36. package/dist/lib/bots.js +79 -0
  37. package/dist/lib/bots.js.map +1 -0
  38. package/dist/lib/email.d.ts +62 -0
  39. package/dist/lib/email.d.ts.map +1 -0
  40. package/dist/lib/email.js +64 -0
  41. package/dist/lib/email.js.map +1 -0
  42. package/dist/lib/errors.d.ts +12 -0
  43. package/dist/lib/errors.d.ts.map +1 -0
  44. package/dist/lib/errors.js +23 -0
  45. package/dist/lib/errors.js.map +1 -0
  46. package/dist/lib/notify.d.ts +20 -0
  47. package/dist/lib/notify.d.ts.map +1 -0
  48. package/dist/lib/notify.js +60 -0
  49. package/dist/lib/notify.js.map +1 -0
  50. package/dist/lib/retention.d.ts +15 -0
  51. package/dist/lib/retention.d.ts.map +1 -0
  52. package/dist/lib/retention.js +40 -0
  53. package/dist/lib/retention.js.map +1 -0
  54. package/dist/lib/sanitize.d.ts +14 -0
  55. package/dist/lib/sanitize.d.ts.map +1 -0
  56. package/dist/lib/sanitize.js +145 -0
  57. package/dist/lib/sanitize.js.map +1 -0
  58. package/dist/lib/turnstile.d.ts +7 -0
  59. package/dist/lib/turnstile.d.ts.map +1 -0
  60. package/dist/lib/turnstile.js +32 -0
  61. package/dist/lib/turnstile.js.map +1 -0
  62. package/dist/lib/validate.d.ts +11 -0
  63. package/dist/lib/validate.d.ts.map +1 -0
  64. package/dist/lib/validate.js +22 -0
  65. package/dist/lib/validate.js.map +1 -0
  66. package/dist/lib/word-count.d.ts +7 -0
  67. package/dist/lib/word-count.d.ts.map +1 -0
  68. package/dist/lib/word-count.js +14 -0
  69. package/dist/lib/word-count.js.map +1 -0
  70. package/dist/middleware/auth.d.ts +46 -0
  71. package/dist/middleware/auth.d.ts.map +1 -0
  72. package/dist/middleware/auth.js +60 -0
  73. package/dist/middleware/auth.js.map +1 -0
  74. package/dist/middleware/cors.d.ts +33 -0
  75. package/dist/middleware/cors.d.ts.map +1 -0
  76. package/dist/middleware/cors.js +55 -0
  77. package/dist/middleware/cors.js.map +1 -0
  78. package/dist/middleware/module-gate.d.ts +11 -0
  79. package/dist/middleware/module-gate.d.ts.map +1 -0
  80. package/dist/middleware/module-gate.js +18 -0
  81. package/dist/middleware/module-gate.js.map +1 -0
  82. package/dist/middleware/rate-limit.d.ts +13 -0
  83. package/dist/middleware/rate-limit.d.ts.map +1 -0
  84. package/dist/middleware/rate-limit.js +30 -0
  85. package/dist/middleware/rate-limit.js.map +1 -0
  86. package/dist/middleware/request-id.d.ts +8 -0
  87. package/dist/middleware/request-id.d.ts.map +1 -0
  88. package/dist/middleware/request-id.js +11 -0
  89. package/dist/middleware/request-id.js.map +1 -0
  90. package/dist/modules/aeo/checks.d.ts +41 -0
  91. package/dist/modules/aeo/checks.d.ts.map +1 -0
  92. package/dist/modules/aeo/checks.js +127 -0
  93. package/dist/modules/aeo/checks.js.map +1 -0
  94. package/dist/modules/aeo/llms.d.ts +24 -0
  95. package/dist/modules/aeo/llms.d.ts.map +1 -0
  96. package/dist/modules/aeo/llms.js +92 -0
  97. package/dist/modules/aeo/llms.js.map +1 -0
  98. package/dist/modules/aeo/routes.admin.d.ts +12 -0
  99. package/dist/modules/aeo/routes.admin.d.ts.map +1 -0
  100. package/dist/modules/aeo/routes.admin.js +37 -0
  101. package/dist/modules/aeo/routes.admin.js.map +1 -0
  102. package/dist/modules/analytics/db.d.ts +24 -0
  103. package/dist/modules/analytics/db.d.ts.map +1 -0
  104. package/dist/modules/analytics/db.js +24 -0
  105. package/dist/modules/analytics/db.js.map +1 -0
  106. package/dist/modules/analytics/routes.admin.d.ts +6 -0
  107. package/dist/modules/analytics/routes.admin.d.ts.map +1 -0
  108. package/dist/modules/analytics/routes.admin.js +21 -0
  109. package/dist/modules/analytics/routes.admin.js.map +1 -0
  110. package/dist/modules/analytics/routes.public.d.ts +6 -0
  111. package/dist/modules/analytics/routes.public.d.ts.map +1 -0
  112. package/dist/modules/analytics/routes.public.js +33 -0
  113. package/dist/modules/analytics/routes.public.js.map +1 -0
  114. package/dist/modules/analytics/service.d.ts +16 -0
  115. package/dist/modules/analytics/service.d.ts.map +1 -0
  116. package/dist/modules/analytics/service.js +148 -0
  117. package/dist/modules/analytics/service.js.map +1 -0
  118. package/dist/modules/authors/routes.admin.d.ts +6 -0
  119. package/dist/modules/authors/routes.admin.d.ts.map +1 -0
  120. package/dist/modules/authors/routes.admin.js +29 -0
  121. package/dist/modules/authors/routes.admin.js.map +1 -0
  122. package/dist/modules/authors/routes.public.d.ts +6 -0
  123. package/dist/modules/authors/routes.public.d.ts.map +1 -0
  124. package/dist/modules/authors/routes.public.js +11 -0
  125. package/dist/modules/authors/routes.public.js.map +1 -0
  126. package/dist/modules/authors/service.d.ts +26 -0
  127. package/dist/modules/authors/service.d.ts.map +1 -0
  128. package/dist/modules/authors/service.js +116 -0
  129. package/dist/modules/authors/service.js.map +1 -0
  130. package/dist/modules/content/db.d.ts +127 -0
  131. package/dist/modules/content/db.d.ts.map +1 -0
  132. package/dist/modules/content/db.js +203 -0
  133. package/dist/modules/content/db.js.map +1 -0
  134. package/dist/modules/content/publish.d.ts +38 -0
  135. package/dist/modules/content/publish.d.ts.map +1 -0
  136. package/dist/modules/content/publish.js +184 -0
  137. package/dist/modules/content/publish.js.map +1 -0
  138. package/dist/modules/content/routes.admin.d.ts +8 -0
  139. package/dist/modules/content/routes.admin.d.ts.map +1 -0
  140. package/dist/modules/content/routes.admin.js +122 -0
  141. package/dist/modules/content/routes.admin.js.map +1 -0
  142. package/dist/modules/content/routes.public.d.ts +6 -0
  143. package/dist/modules/content/routes.public.d.ts.map +1 -0
  144. package/dist/modules/content/routes.public.js +56 -0
  145. package/dist/modules/content/routes.public.js.map +1 -0
  146. package/dist/modules/content/service.d.ts +137 -0
  147. package/dist/modules/content/service.d.ts.map +1 -0
  148. package/dist/modules/content/service.js +348 -0
  149. package/dist/modules/content/service.js.map +1 -0
  150. package/dist/modules/forms/db.d.ts +42 -0
  151. package/dist/modules/forms/db.d.ts.map +1 -0
  152. package/dist/modules/forms/db.js +62 -0
  153. package/dist/modules/forms/db.js.map +1 -0
  154. package/dist/modules/forms/forms-routes.admin.d.ts +6 -0
  155. package/dist/modules/forms/forms-routes.admin.d.ts.map +1 -0
  156. package/dist/modules/forms/forms-routes.admin.js +37 -0
  157. package/dist/modules/forms/forms-routes.admin.js.map +1 -0
  158. package/dist/modules/forms/forms-service.d.ts +18 -0
  159. package/dist/modules/forms/forms-service.d.ts.map +1 -0
  160. package/dist/modules/forms/forms-service.js +121 -0
  161. package/dist/modules/forms/forms-service.js.map +1 -0
  162. package/dist/modules/forms/routes.public.d.ts +6 -0
  163. package/dist/modules/forms/routes.public.d.ts.map +1 -0
  164. package/dist/modules/forms/routes.public.js +41 -0
  165. package/dist/modules/forms/routes.public.js.map +1 -0
  166. package/dist/modules/forms/submissions-routes.admin.d.ts +6 -0
  167. package/dist/modules/forms/submissions-routes.admin.d.ts.map +1 -0
  168. package/dist/modules/forms/submissions-routes.admin.js +46 -0
  169. package/dist/modules/forms/submissions-routes.admin.js.map +1 -0
  170. package/dist/modules/forms/submissions-service.d.ts +45 -0
  171. package/dist/modules/forms/submissions-service.d.ts.map +1 -0
  172. package/dist/modules/forms/submissions-service.js +174 -0
  173. package/dist/modules/forms/submissions-service.js.map +1 -0
  174. package/dist/modules/media/db.d.ts +15 -0
  175. package/dist/modules/media/db.d.ts.map +1 -0
  176. package/dist/modules/media/db.js +24 -0
  177. package/dist/modules/media/db.js.map +1 -0
  178. package/dist/modules/media/routes.admin.d.ts +6 -0
  179. package/dist/modules/media/routes.admin.d.ts.map +1 -0
  180. package/dist/modules/media/routes.admin.js +30 -0
  181. package/dist/modules/media/routes.admin.js.map +1 -0
  182. package/dist/modules/media/routes.public.d.ts +6 -0
  183. package/dist/modules/media/routes.public.d.ts.map +1 -0
  184. package/dist/modules/media/routes.public.js +35 -0
  185. package/dist/modules/media/routes.public.js.map +1 -0
  186. package/dist/modules/media/service.d.ts +32 -0
  187. package/dist/modules/media/service.d.ts.map +1 -0
  188. package/dist/modules/media/service.js +99 -0
  189. package/dist/modules/media/service.js.map +1 -0
  190. package/dist/modules/openapi/spec.d.ts +21 -0
  191. package/dist/modules/openapi/spec.d.ts.map +1 -0
  192. package/dist/modules/openapi/spec.js +468 -0
  193. package/dist/modules/openapi/spec.js.map +1 -0
  194. package/dist/modules/seo/jsonld.d.ts +25 -0
  195. package/dist/modules/seo/jsonld.d.ts.map +1 -0
  196. package/dist/modules/seo/jsonld.js +67 -0
  197. package/dist/modules/seo/jsonld.js.map +1 -0
  198. package/dist/modules/seo/routes.public.d.ts +6 -0
  199. package/dist/modules/seo/routes.public.d.ts.map +1 -0
  200. package/dist/modules/seo/routes.public.js +39 -0
  201. package/dist/modules/seo/routes.public.js.map +1 -0
  202. package/dist/modules/seo/sitemap.d.ts +3 -0
  203. package/dist/modules/seo/sitemap.d.ts.map +1 -0
  204. package/dist/modules/seo/sitemap.js +39 -0
  205. package/dist/modules/seo/sitemap.js.map +1 -0
  206. package/dist/modules/settings/routes.admin.d.ts +12 -0
  207. package/dist/modules/settings/routes.admin.d.ts.map +1 -0
  208. package/dist/modules/settings/routes.admin.js +27 -0
  209. package/dist/modules/settings/routes.admin.js.map +1 -0
  210. package/dist/modules/site-documents/routes.admin.d.ts +10 -0
  211. package/dist/modules/site-documents/routes.admin.d.ts.map +1 -0
  212. package/dist/modules/site-documents/routes.admin.js +29 -0
  213. package/dist/modules/site-documents/routes.admin.js.map +1 -0
  214. package/dist/modules/site-documents/routes.public.d.ts +6 -0
  215. package/dist/modules/site-documents/routes.public.d.ts.map +1 -0
  216. package/dist/modules/site-documents/routes.public.js +16 -0
  217. package/dist/modules/site-documents/routes.public.js.map +1 -0
  218. package/dist/modules/site-documents/service.d.ts +11 -0
  219. package/dist/modules/site-documents/service.d.ts.map +1 -0
  220. package/dist/modules/site-documents/service.js +42 -0
  221. package/dist/modules/site-documents/service.js.map +1 -0
  222. package/dist/modules/webhooks/dispatch.d.ts +14 -0
  223. package/dist/modules/webhooks/dispatch.d.ts.map +1 -0
  224. package/dist/modules/webhooks/dispatch.js +84 -0
  225. package/dist/modules/webhooks/dispatch.js.map +1 -0
  226. package/dist/shared/constants.d.ts +79 -0
  227. package/dist/shared/constants.d.ts.map +1 -0
  228. package/dist/shared/constants.js +89 -0
  229. package/dist/shared/constants.js.map +1 -0
  230. package/dist/shared/envelope.d.ts +37 -0
  231. package/dist/shared/envelope.d.ts.map +1 -0
  232. package/dist/shared/envelope.js +46 -0
  233. package/dist/shared/envelope.js.map +1 -0
  234. package/dist/shared/hash.d.ts +2 -0
  235. package/dist/shared/hash.d.ts.map +1 -0
  236. package/dist/shared/hash.js +8 -0
  237. package/dist/shared/hash.js.map +1 -0
  238. package/dist/shared/index.d.ts +13 -0
  239. package/dist/shared/index.d.ts.map +1 -0
  240. package/dist/shared/index.js +13 -0
  241. package/dist/shared/index.js.map +1 -0
  242. package/dist/shared/schemas/aeo.d.ts +182 -0
  243. package/dist/shared/schemas/aeo.d.ts.map +1 -0
  244. package/dist/shared/schemas/aeo.js +43 -0
  245. package/dist/shared/schemas/aeo.js.map +1 -0
  246. package/dist/shared/schemas/analytics.d.ts +277 -0
  247. package/dist/shared/schemas/analytics.d.ts.map +1 -0
  248. package/dist/shared/schemas/analytics.js +74 -0
  249. package/dist/shared/schemas/analytics.js.map +1 -0
  250. package/dist/shared/schemas/api-key.d.ts +77 -0
  251. package/dist/shared/schemas/api-key.d.ts.map +1 -0
  252. package/dist/shared/schemas/api-key.js +27 -0
  253. package/dist/shared/schemas/api-key.js.map +1 -0
  254. package/dist/shared/schemas/author.d.ts +147 -0
  255. package/dist/shared/schemas/author.d.ts.map +1 -0
  256. package/dist/shared/schemas/author.js +27 -0
  257. package/dist/shared/schemas/author.js.map +1 -0
  258. package/dist/shared/schemas/form.d.ts +341 -0
  259. package/dist/shared/schemas/form.d.ts.map +1 -0
  260. package/dist/shared/schemas/form.js +117 -0
  261. package/dist/shared/schemas/form.js.map +1 -0
  262. package/dist/shared/schemas/index.d.ts +12 -0
  263. package/dist/shared/schemas/index.d.ts.map +1 -0
  264. package/dist/shared/schemas/index.js +12 -0
  265. package/dist/shared/schemas/index.js.map +1 -0
  266. package/dist/shared/schemas/media.d.ts +42 -0
  267. package/dist/shared/schemas/media.d.ts.map +1 -0
  268. package/dist/shared/schemas/media.js +16 -0
  269. package/dist/shared/schemas/media.js.map +1 -0
  270. package/dist/shared/schemas/pagination.d.ts +34 -0
  271. package/dist/shared/schemas/pagination.d.ts.map +1 -0
  272. package/dist/shared/schemas/pagination.js +21 -0
  273. package/dist/shared/schemas/pagination.js.map +1 -0
  274. package/dist/shared/schemas/post.d.ts +862 -0
  275. package/dist/shared/schemas/post.d.ts.map +1 -0
  276. package/dist/shared/schemas/post.js +187 -0
  277. package/dist/shared/schemas/post.js.map +1 -0
  278. package/dist/shared/schemas/settings.d.ts +839 -0
  279. package/dist/shared/schemas/settings.d.ts.map +1 -0
  280. package/dist/shared/schemas/settings.js +202 -0
  281. package/dist/shared/schemas/settings.js.map +1 -0
  282. package/dist/shared/schemas/site-document.d.ts +38 -0
  283. package/dist/shared/schemas/site-document.d.ts.map +1 -0
  284. package/dist/shared/schemas/site-document.js +21 -0
  285. package/dist/shared/schemas/site-document.js.map +1 -0
  286. package/dist/shared/schemas/webhook.d.ts +39 -0
  287. package/dist/shared/schemas/webhook.d.ts.map +1 -0
  288. package/dist/shared/schemas/webhook.js +20 -0
  289. package/dist/shared/schemas/webhook.js.map +1 -0
  290. package/dist/spa.d.ts +9 -0
  291. package/dist/spa.d.ts.map +1 -0
  292. package/dist/spa.js +57 -0
  293. package/dist/spa.js.map +1 -0
  294. package/migrations/0001_settings.sql +39 -0
  295. package/migrations/0002_content.sql +135 -0
  296. package/migrations/0003_forms.sql +35 -0
  297. package/migrations/0004_analytics.sql +42 -0
  298. package/migrations/0005_media.sql +17 -0
  299. package/migrations/0006_site_documents.sql +18 -0
  300. package/migrations/0007_auth.sql +81 -0
  301. package/migrations/0008_api_keys.sql +20 -0
  302. package/package.json +68 -0
  303. package/scripts/bundle-admin.mjs +54 -0
  304. package/seed/local.sql +63 -0
@@ -0,0 +1,36 @@
1
+ import { type ApiKeyKind, type ApiKeySummary, type IssuedApiKey } from '../shared/index.js';
2
+ import type { Context } from 'hono';
3
+ /** What the public-route middleware needs from a presented key. */
4
+ export interface ApiKeyLookup {
5
+ keyId: string;
6
+ kind: ApiKeyKind;
7
+ revoked: boolean;
8
+ }
9
+ /**
10
+ * Issues a key. The plaintext secret is returned exactly once, here; only its
11
+ * SHA-256 hash is written, so nothing later can recover it, including this
12
+ * code. The caller is responsible for showing it to the admin once.
13
+ */
14
+ export declare function createApiKey(db: D1Database, kind: ApiKeyKind, label: string | undefined): Promise<IssuedApiKey>;
15
+ export declare function listApiKeys(db: D1Database): Promise<ApiKeySummary[]>;
16
+ /**
17
+ * Revokes a key. Idempotent: revoking an already-revoked key is a success,
18
+ * because the key IS revoked; only an unknown id is an error. The row stays so
19
+ * the admin can see that a key existed and when it stopped working.
20
+ */
21
+ export declare function revokeApiKey(db: D1Database, keyId: string): Promise<ApiKeySummary>;
22
+ /**
23
+ * Resolves a presented key by its hash. There is no cache: managed we8 caches
24
+ * this because a control plane fronts thousands of tenants, whereas here the
25
+ * lookup is one indexed read against the site's own database, and a cache
26
+ * would only buy a revoked key extra minutes of life.
27
+ */
28
+ export declare function lookupApiKeyByHash(db: D1Database, keyHash: string): Promise<ApiKeyLookup | null>;
29
+ /**
30
+ * The presented key, in any of the conventions the we8 dialect accepts: an
31
+ * `sk_` as `Authorization: Bearer`, a `pk_` in the `X-We8-Key` header, or a
32
+ * `?key=` query parameter for the cases where a header cannot be set. First
33
+ * one present wins, in that order.
34
+ */
35
+ export declare function extractApiKey(c: Context): string | undefined;
36
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/keys/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AA8CD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,YAAY,CAAC,CAavB;AAcD,wBAAsB,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAU1E;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAOxF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmB9B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM5D"}
@@ -0,0 +1,120 @@
1
+ import { sha256Hex } from '../shared/index.js';
2
+ import { HttpError } from '../lib/errors.js';
3
+ function toSummary(row) {
4
+ return {
5
+ id: row.id,
6
+ kind: row.kind,
7
+ prefix: row.prefix,
8
+ label: row.label,
9
+ createdAt: row.created_at,
10
+ lastUsedAt: row.last_used_at,
11
+ revokedAt: row.revoked_at,
12
+ };
13
+ }
14
+ function randomHex(byteLength) {
15
+ const bytes = new Uint8Array(byteLength);
16
+ crypto.getRandomValues(bytes);
17
+ return Array.from(bytes)
18
+ .map((b) => b.toString(16).padStart(2, '0'))
19
+ .join('');
20
+ }
21
+ /**
22
+ * Mints a bearer secret in the managed we8 dialect (`pk_live_...` /
23
+ * `sk_live_...`) plus its non-secret display prefix, and hashes it for
24
+ * storage. 24 random bytes, so guessing is not a strategy.
25
+ */
26
+ async function mintKey(kind) {
27
+ const secret = `${kind}_live_${randomHex(24)}`;
28
+ const prefix = secret.slice(0, `${kind}_live_`.length + 8);
29
+ const keyHash = await sha256Hex(secret);
30
+ return { secret, prefix, keyHash };
31
+ }
32
+ /**
33
+ * Issues a key. The plaintext secret is returned exactly once, here; only its
34
+ * SHA-256 hash is written, so nothing later can recover it, including this
35
+ * code. The caller is responsible for showing it to the admin once.
36
+ */
37
+ export async function createApiKey(db, kind, label) {
38
+ const { secret, prefix, keyHash } = await mintKey(kind);
39
+ const id = crypto.randomUUID();
40
+ await db
41
+ .prepare(`INSERT INTO api_keys (id, kind, prefix, key_hash, label) VALUES (?1, ?2, ?3, ?4, ?5)`)
42
+ .bind(id, kind, prefix, keyHash, label ?? null)
43
+ .run();
44
+ const row = await fetchApiKeyRow(db, id);
45
+ return { ...toSummary(row), secret };
46
+ }
47
+ async function fetchApiKeyRow(db, id) {
48
+ const row = await db
49
+ .prepare(`SELECT id, kind, prefix, label, created_at, last_used_at, revoked_at
50
+ FROM api_keys WHERE id = ?1`)
51
+ .bind(id)
52
+ .first();
53
+ if (!row)
54
+ throw new HttpError('not_found', 'api key not found');
55
+ return row;
56
+ }
57
+ export async function listApiKeys(db) {
58
+ // created_at has second resolution and two keys are often minted in the same
59
+ // second, so rowid (SQLite's implicit insertion order) breaks the tie.
60
+ const { results } = await db
61
+ .prepare(`SELECT id, kind, prefix, label, created_at, last_used_at, revoked_at
62
+ FROM api_keys ORDER BY created_at DESC, rowid DESC`)
63
+ .all();
64
+ return results.map(toSummary);
65
+ }
66
+ /**
67
+ * Revokes a key. Idempotent: revoking an already-revoked key is a success,
68
+ * because the key IS revoked; only an unknown id is an error. The row stays so
69
+ * the admin can see that a key existed and when it stopped working.
70
+ */
71
+ export async function revokeApiKey(db, keyId) {
72
+ await fetchApiKeyRow(db, keyId);
73
+ await db
74
+ .prepare(`UPDATE api_keys SET revoked_at = datetime('now') WHERE id = ?1 AND revoked_at IS NULL`)
75
+ .bind(keyId)
76
+ .run();
77
+ return toSummary(await fetchApiKeyRow(db, keyId));
78
+ }
79
+ /**
80
+ * Resolves a presented key by its hash. There is no cache: managed we8 caches
81
+ * this because a control plane fronts thousands of tenants, whereas here the
82
+ * lookup is one indexed read against the site's own database, and a cache
83
+ * would only buy a revoked key extra minutes of life.
84
+ */
85
+ export async function lookupApiKeyByHash(db, keyHash) {
86
+ const row = await db
87
+ .prepare(`SELECT id, kind, revoked_at FROM api_keys WHERE key_hash = ?1`)
88
+ .bind(keyHash)
89
+ .first();
90
+ if (!row)
91
+ return null;
92
+ // Advisory only, and on the hot path for every public request: a failure
93
+ // writing it must never turn a valid call into a 500.
94
+ try {
95
+ await db
96
+ .prepare(`UPDATE api_keys SET last_used_at = datetime('now') WHERE id = ?1`)
97
+ .bind(row.id)
98
+ .run();
99
+ }
100
+ catch {
101
+ // ignore: the resolved key is what matters
102
+ }
103
+ return { keyId: row.id, kind: row.kind, revoked: row.revoked_at !== null };
104
+ }
105
+ /**
106
+ * The presented key, in any of the conventions the we8 dialect accepts: an
107
+ * `sk_` as `Authorization: Bearer`, a `pk_` in the `X-We8-Key` header, or a
108
+ * `?key=` query parameter for the cases where a header cannot be set. First
109
+ * one present wins, in that order.
110
+ */
111
+ export function extractApiKey(c) {
112
+ const auth = c.req.header('Authorization');
113
+ if (auth?.startsWith('Bearer '))
114
+ return auth.slice('Bearer '.length);
115
+ const headerKey = c.req.header('X-We8-Key');
116
+ if (headerKey)
117
+ return headerKey;
118
+ return c.req.query('key') ?? undefined;
119
+ }
120
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/keys/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * User-agent classification for the visit beacon.
3
+ *
4
+ * The pattern list is the merged one managed we8 uses, with one addition that
5
+ * matters for 0.1: every entry also declares a CLASS. A single is-bot boolean
6
+ * answers "was this a human", which is the wrong question for AEO work, where
7
+ * the thing worth watching is specifically an answer engine reading the site.
8
+ * Keeping GPTBot apart from Googlebot apart from an SEO auditor is what makes
9
+ * the Engagement crawler view a signal rather than a lump.
10
+ *
11
+ * Cloudflare's Bot Management score and verifiedBot flag are consulted when
12
+ * present (score <= 30 counts as a bot). That field is an add-on, so it is
13
+ * genuinely absent at runtime on most zones despite the ambient type claiming
14
+ * it unconditionally; hence the defensive optional chaining.
15
+ */
16
+ import type { UserAgentClass } from '../shared/index.js';
17
+ export interface BotDetectionResult {
18
+ isBot: boolean;
19
+ botName: string | null;
20
+ botScore: number | null;
21
+ userAgentClass: UserAgentClass;
22
+ }
23
+ export declare function detectBot(userAgent: string | null, cf: IncomingRequestCfProperties | undefined): BotDetectionResult;
24
+ //# sourceMappingURL=bots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bots.d.ts","sourceRoot":"","sources":["../../src/lib/bots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;CAChC;AA8CD,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,EAAE,EAAE,2BAA2B,GAAG,SAAS,GAC1C,kBAAkB,CAyBpB"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * User-agent classification for the visit beacon.
3
+ *
4
+ * The pattern list is the merged one managed we8 uses, with one addition that
5
+ * matters for 0.1: every entry also declares a CLASS. A single is-bot boolean
6
+ * answers "was this a human", which is the wrong question for AEO work, where
7
+ * the thing worth watching is specifically an answer engine reading the site.
8
+ * Keeping GPTBot apart from Googlebot apart from an SEO auditor is what makes
9
+ * the Engagement crawler view a signal rather than a lump.
10
+ *
11
+ * Cloudflare's Bot Management score and verifiedBot flag are consulted when
12
+ * present (score <= 30 counts as a bot). That field is an add-on, so it is
13
+ * genuinely absent at runtime on most zones despite the ambient type claiming
14
+ * it unconditionally; hence the defensive optional chaining.
15
+ */
16
+ const BOT_PATTERNS = [
17
+ // Answer engines and model crawlers: the class 0.1 exists to surface.
18
+ { name: 'GPTBot', pattern: /GPTBot/i, class: 'ai-crawler' },
19
+ { name: 'ChatGPT-User', pattern: /ChatGPT-User/i, class: 'ai-crawler' },
20
+ { name: 'OAI-SearchBot', pattern: /OAI-SearchBot/i, class: 'ai-crawler' },
21
+ { name: 'ClaudeBot', pattern: /ClaudeBot|Claude-Web|Claude-User|anthropic-ai/i, class: 'ai-crawler' },
22
+ { name: 'PerplexityBot', pattern: /PerplexityBot|Perplexity-User/i, class: 'ai-crawler' },
23
+ { name: 'Google-Extended', pattern: /Google-Extended/i, class: 'ai-crawler' },
24
+ { name: 'Applebot-Extended', pattern: /Applebot-Extended/i, class: 'ai-crawler' },
25
+ { name: 'Bytespider', pattern: /Bytespider/i, class: 'ai-crawler' },
26
+ { name: 'CCBot', pattern: /CCBot/i, class: 'ai-crawler' },
27
+ { name: 'Meta-ExternalAgent', pattern: /Meta-ExternalAgent|FacebookBot/i, class: 'ai-crawler' },
28
+ { name: 'Amazonbot', pattern: /Amazonbot/i, class: 'ai-crawler' },
29
+ { name: 'Cohere', pattern: /cohere-ai|cohere-training-data-crawler/i, class: 'ai-crawler' },
30
+ { name: 'Diffbot', pattern: /Diffbot/i, class: 'ai-crawler' },
31
+ { name: 'Timpibot', pattern: /Timpibot/i, class: 'ai-crawler' },
32
+ // Classic search indexers.
33
+ { name: 'Googlebot', pattern: /Googlebot/i, class: 'search-crawler' },
34
+ { name: 'Bingbot', pattern: /bingbot/i, class: 'search-crawler' },
35
+ { name: 'YandexBot', pattern: /YandexBot/i, class: 'search-crawler' },
36
+ { name: 'DuckDuckBot', pattern: /DuckDuckBot/i, class: 'search-crawler' },
37
+ { name: 'Applebot', pattern: /Applebot/i, class: 'search-crawler' },
38
+ // Link unfurlers.
39
+ { name: 'FacebookExternalHit', pattern: /facebookexternalhit/i, class: 'social-crawler' },
40
+ { name: 'Twitterbot', pattern: /Twitterbot/i, class: 'social-crawler' },
41
+ { name: 'LinkedInBot', pattern: /LinkedInBot/i, class: 'social-crawler' },
42
+ { name: 'Slackbot', pattern: /Slackbot/i, class: 'social-crawler' },
43
+ { name: 'Discordbot', pattern: /Discordbot/i, class: 'social-crawler' },
44
+ // Commercial SEO auditors: traffic, not audience.
45
+ { name: 'SemrushBot', pattern: /SemrushBot/i, class: 'seo-crawler' },
46
+ { name: 'AhrefsBot', pattern: /AhrefsBot/i, class: 'seo-crawler' },
47
+ { name: 'DotBot', pattern: /DotBot/i, class: 'seo-crawler' },
48
+ { name: 'MJ12bot', pattern: /MJ12bot/i, class: 'seo-crawler' },
49
+ { name: 'DataForSeoBot', pattern: /DataForSeoBot/i, class: 'seo-crawler' },
50
+ // The catch-all, kept last so a named pattern always wins. It trades a small
51
+ // false-positive risk (a user agent that merely contains "bot") for
52
+ // materially higher recall.
53
+ { name: 'Generic crawler', pattern: /bot|crawler|spider|crawling/i, class: 'other-bot' },
54
+ ];
55
+ export function detectBot(userAgent, cf) {
56
+ const score = typeof cf?.botManagement?.score === 'number' ? cf.botManagement.score : null;
57
+ const verified = cf?.botManagement?.verifiedBot === true;
58
+ if (userAgent) {
59
+ for (const entry of BOT_PATTERNS) {
60
+ if (entry.pattern.test(userAgent)) {
61
+ return {
62
+ isBot: true,
63
+ botName: entry.name,
64
+ botScore: score,
65
+ userAgentClass: entry.class,
66
+ };
67
+ }
68
+ }
69
+ }
70
+ // Cloudflare says bot but no pattern named it: real, but unattributable.
71
+ if (verified) {
72
+ return { isBot: true, botName: 'Verified bot', botScore: score, userAgentClass: 'other-bot' };
73
+ }
74
+ if (score !== null && score <= 30) {
75
+ return { isBot: true, botName: 'Low bot score', botScore: score, userAgentClass: 'other-bot' };
76
+ }
77
+ return { isBot: false, botName: null, botScore: score, userAgentClass: 'human' };
78
+ }
79
+ //# sourceMappingURL=bots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bots.js","sourceRoot":"","sources":["../../src/lib/bots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The email seam. Managed we8 relays mail from the tenant engine to a control
3
+ * plane that owns the transport; a standalone CMS has no such plane, so
4
+ * everything that wants to send mail goes through this one interface and a
5
+ * deployment plugs a real implementation in behind it.
6
+ *
7
+ * Two implementations ship here. `NoopEmailSender` is the default and drops
8
+ * the message, so a fresh install works with no mail configuration at all;
9
+ * `ConsoleEmailSender` logs it, which is what local development wants. A pack
10
+ * supplies a real one through `registerEmailSender`; `@we8/cloudflare` sends
11
+ * over Cloudflare Email Service.
12
+ */
13
+ export interface EmailMessage {
14
+ to: string[];
15
+ subject: string;
16
+ html: string;
17
+ text: string;
18
+ }
19
+ export interface EmailSender {
20
+ send(message: EmailMessage): Promise<void>;
21
+ }
22
+ /** The default: accepts and drops. A site with no mail configured is a valid site. */
23
+ export declare class NoopEmailSender implements EmailSender {
24
+ send(_message: EmailMessage): Promise<void>;
25
+ }
26
+ /** Logs the message instead of sending it, so local development can see it. */
27
+ export declare class ConsoleEmailSender implements EmailSender {
28
+ send(message: EmailMessage): Promise<void>;
29
+ }
30
+ /** What resolveEmailSender needs from the environment. */
31
+ export interface EmailEnv {
32
+ EMAIL_MODE?: string | undefined;
33
+ }
34
+ /**
35
+ * How a deployment pack supplies a real sender. The factory receives the
36
+ * worker's whole environment as `unknown`, because the core cannot name a
37
+ * pack's bindings and must not pretend to: the pack narrows it itself.
38
+ * Returning `null` declines, which hands the decision back to the built-in
39
+ * resolution below, so a pack that is installed but not configured leaves a
40
+ * site exactly as it was.
41
+ */
42
+ export type EmailSenderFactory = (env: unknown) => EmailSender | null;
43
+ /**
44
+ * Registers the pack's sender. A composed worker calls this once at module
45
+ * scope, before any request is served; passing `null` unregisters, which is
46
+ * what tests want between cases.
47
+ */
48
+ export declare function registerEmailSender(factory: EmailSenderFactory | null): void;
49
+ /**
50
+ * Picks the sender for this environment. A registered pack factory gets first
51
+ * refusal; after that, `console` logs and anything else, including an unset
52
+ * value, drops.
53
+ */
54
+ export declare function resolveEmailSender(env: EmailEnv): EmailSender;
55
+ /**
56
+ * Sends without ever throwing at the caller. Form submission is the path this
57
+ * exists for: a submission that was stored must return success to the visitor
58
+ * whether or not the notification went out, so a sender error is logged and
59
+ * swallowed here rather than becoming a 500 on a lead that was captured fine.
60
+ */
61
+ export declare function sendBestEffort(sender: EmailSender, message: EmailMessage): Promise<void>;
62
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/lib/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,sFAAsF;AACtF,qBAAa,eAAgB,YAAW,WAAW;IACjD,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,+EAA+E;AAC/E,qBAAa,kBAAmB,YAAW,WAAW;IACpD,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAM3C;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,WAAW,GAAG,IAAI,CAAC;AAItE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CAM7D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9F"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The email seam. Managed we8 relays mail from the tenant engine to a control
3
+ * plane that owns the transport; a standalone CMS has no such plane, so
4
+ * everything that wants to send mail goes through this one interface and a
5
+ * deployment plugs a real implementation in behind it.
6
+ *
7
+ * Two implementations ship here. `NoopEmailSender` is the default and drops
8
+ * the message, so a fresh install works with no mail configuration at all;
9
+ * `ConsoleEmailSender` logs it, which is what local development wants. A pack
10
+ * supplies a real one through `registerEmailSender`; `@we8/cloudflare` sends
11
+ * over Cloudflare Email Service.
12
+ */
13
+ /** The default: accepts and drops. A site with no mail configured is a valid site. */
14
+ export class NoopEmailSender {
15
+ send(_message) {
16
+ return Promise.resolve();
17
+ }
18
+ }
19
+ /** Logs the message instead of sending it, so local development can see it. */
20
+ export class ConsoleEmailSender {
21
+ send(message) {
22
+ console.log(`[email] to=${message.to.join(', ')} subject=${JSON.stringify(message.subject)}\n${message.text}`);
23
+ return Promise.resolve();
24
+ }
25
+ }
26
+ let registeredFactory = null;
27
+ /**
28
+ * Registers the pack's sender. A composed worker calls this once at module
29
+ * scope, before any request is served; passing `null` unregisters, which is
30
+ * what tests want between cases.
31
+ */
32
+ export function registerEmailSender(factory) {
33
+ registeredFactory = factory;
34
+ }
35
+ /**
36
+ * Picks the sender for this environment. A registered pack factory gets first
37
+ * refusal; after that, `console` logs and anything else, including an unset
38
+ * value, drops.
39
+ */
40
+ export function resolveEmailSender(env) {
41
+ if (registeredFactory) {
42
+ const sender = registeredFactory(env);
43
+ if (sender)
44
+ return sender;
45
+ }
46
+ return env.EMAIL_MODE === 'console' ? new ConsoleEmailSender() : new NoopEmailSender();
47
+ }
48
+ /**
49
+ * Sends without ever throwing at the caller. Form submission is the path this
50
+ * exists for: a submission that was stored must return success to the visitor
51
+ * whether or not the notification went out, so a sender error is logged and
52
+ * swallowed here rather than becoming a 500 on a lead that was captured fine.
53
+ */
54
+ export async function sendBestEffort(sender, message) {
55
+ if (message.to.length === 0)
56
+ return;
57
+ try {
58
+ await sender.send(message);
59
+ }
60
+ catch (error) {
61
+ console.error(`[email] send failed: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+ }
64
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/lib/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,sFAAsF;AACtF,MAAM,OAAO,eAAe;IAC1B,IAAI,CAAC,QAAsB;QACzB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,OAAO,kBAAkB;IAC7B,IAAI,CAAC,OAAqB;QACxB,OAAO,CAAC,GAAG,CACT,cAAc,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,EAAE,CAClG,CAAC;QACF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF;AAiBD,IAAI,iBAAiB,GAA8B,IAAI,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAkC;IACpE,iBAAiB,GAAG,OAAO,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAa;IAC9C,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,kBAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAmB,EAAE,OAAqB;IAC7E,IAAI,OAAO,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type ErrorCode } from '../shared/index.js';
2
+ import type { Context } from 'hono';
3
+ /** Thrown by route handlers/middleware to produce a specific envelope error. */
4
+ export declare class HttpError extends Error {
5
+ readonly code: ErrorCode;
6
+ constructor(code: ErrorCode, message: string);
7
+ }
8
+ /** Registered via app.onError(); catches everything, including thrown non-HttpErrors. */
9
+ export declare function errorBoundary(error: Error, c: Context): Response;
10
+ /** Registered via app.notFound(); keeps 404s in the same envelope shape as thrown errors. */
11
+ export declare function notFoundHandler(c: Context): Response;
12
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,gFAAgF;AAChF,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;gBAEb,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;CAK7C;AAED,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAG,QAAQ,CAMhE;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,CAEpD"}
@@ -0,0 +1,23 @@
1
+ import { err, statusForCode } from '../shared/index.js';
2
+ /** Thrown by route handlers/middleware to produce a specific envelope error. */
3
+ export class HttpError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = 'HttpError';
8
+ this.code = code;
9
+ }
10
+ }
11
+ /** Registered via app.onError(); catches everything, including thrown non-HttpErrors. */
12
+ export function errorBoundary(error, c) {
13
+ if (error instanceof HttpError) {
14
+ return c.json(err(error.code, error.message), statusForCode(error.code));
15
+ }
16
+ console.error(error);
17
+ return c.json(err('internal', 'internal server error'), statusForCode('internal'));
18
+ }
19
+ /** Registered via app.notFound(); keeps 404s in the same envelope shape as thrown errors. */
20
+ export function notFoundHandler(c) {
21
+ return c.json(err('not_found', 'not found'), statusForCode('not_found'));
22
+ }
23
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,aAAa,EAAkB,MAAM,oBAAoB,CAAC;AAGxE,gFAAgF;AAChF,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAY;IAEzB,YAAY,IAAe,EAAE,OAAe;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,CAAU;IACpD,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;QAC/B,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,uBAAuB,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The messages this CMS composes. Rendering lives here, away from the sending
3
+ * seam in email.ts, so the templates can be read and tested without touching a
4
+ * transport.
5
+ */
6
+ import type { EmailMessage } from './email.js';
7
+ export interface NotificationSubmission {
8
+ email: string | null;
9
+ name: string | null;
10
+ message: string | null;
11
+ sourcePage: string | null;
12
+ }
13
+ /** The "you have mail" ping sent to the site's notify addresses after a submission. */
14
+ export declare function buildSubmissionNotification(to: string[], siteUrl: string, submission: NotificationSubmission): EmailMessage;
15
+ /**
16
+ * The password reset mail. Better Auth builds the link (it carries the
17
+ * one-time token); this only wraps it, so the URL is used verbatim.
18
+ */
19
+ export declare function buildPasswordResetEmail(to: string, url: string): EmailMessage;
20
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/lib/notify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAWD,uFAAuF;AACvF,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,MAAM,EAAE,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,sBAAsB,GACjC,YAAY,CAyBd;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAmB7E"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The messages this CMS composes. Rendering lives here, away from the sending
3
+ * seam in email.ts, so the templates can be read and tested without touching a
4
+ * transport.
5
+ */
6
+ function escapeHtml(value) {
7
+ return value
8
+ .replace(/&/g, '&amp;')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;')
11
+ .replace(/"/g, '&quot;')
12
+ .replace(/'/g, '&#39;');
13
+ }
14
+ /** The "you have mail" ping sent to the site's notify addresses after a submission. */
15
+ export function buildSubmissionNotification(to, siteUrl, submission) {
16
+ const rows = [
17
+ ['Site', siteUrl],
18
+ ['Name', submission.name ?? ''],
19
+ ['Email', submission.email ?? ''],
20
+ ['Source page', submission.sourcePage ?? ''],
21
+ ];
22
+ const html = [
23
+ '<h2>New form submission</h2>',
24
+ ...rows.map(([label, value]) => `<p><strong>${label}:</strong> ${escapeHtml(value)}</p>`),
25
+ '<p><strong>Message:</strong></p>',
26
+ `<p>${escapeHtml(submission.message ?? '').replace(/\n/g, '<br>')}</p>`,
27
+ ].join('\n');
28
+ const text = [
29
+ 'New form submission',
30
+ '',
31
+ ...rows.map(([label, value]) => `${label}: ${value}`),
32
+ '',
33
+ 'Message:',
34
+ submission.message ?? '',
35
+ ].join('\n');
36
+ return { to, subject: `New form submission on ${siteUrl}`, html, text };
37
+ }
38
+ /**
39
+ * The password reset mail. Better Auth builds the link (it carries the
40
+ * one-time token); this only wraps it, so the URL is used verbatim.
41
+ */
42
+ export function buildPasswordResetEmail(to, url) {
43
+ const text = [
44
+ 'Reset your password',
45
+ '',
46
+ 'Open this link to choose a new password. It expires shortly and can be used once.',
47
+ '',
48
+ url,
49
+ '',
50
+ 'If you did not ask for this, you can ignore this message.',
51
+ ].join('\n');
52
+ const html = [
53
+ '<h2>Reset your password</h2>',
54
+ '<p>Open this link to choose a new password. It expires shortly and can be used once.</p>',
55
+ `<p><a href="${escapeHtml(url)}">${escapeHtml(url)}</a></p>`,
56
+ '<p>If you did not ask for this, you can ignore this message.</p>',
57
+ ].join('\n');
58
+ return { to: [to], subject: 'Reset your password', html, text };
59
+ }
60
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/lib/notify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,2BAA2B,CACzC,EAAY,EACZ,OAAe,EACf,UAAkC;IAElC,MAAM,IAAI,GAA4B;QACpC,CAAC,MAAM,EAAE,OAAO,CAAC;QACjB,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,CAAC,OAAO,EAAE,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,CAAC,aAAa,EAAE,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC;KAC7C,CAAC;IAEF,MAAM,IAAI,GAAG;QACX,8BAA8B;QAC9B,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,cAAc,KAAK,cAAc,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;QACzF,kCAAkC;QAClC,MAAM,UAAU,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM;KACxE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,IAAI,GAAG;QACX,qBAAqB;QACrB,EAAE;QACF,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,KAAK,KAAK,EAAE,CAAC;QACrD,EAAE;QACF,UAAU;QACV,UAAU,CAAC,OAAO,IAAI,EAAE;KACzB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,0BAA0B,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAAU,EAAE,GAAW;IAC7D,MAAM,IAAI,GAAG;QACX,qBAAqB;QACrB,EAAE;QACF,mFAAmF;QACnF,EAAE;QACF,GAAG;QACH,EAAE;QACF,2DAA2D;KAC5D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,IAAI,GAAG;QACX,8BAA8B;QAC9B,0FAA0F;QAC1F,eAAe,UAAU,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,GAAG,CAAC,UAAU;QAC5D,kEAAkE;KACnE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,qBAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { RetentionSettings } from '../shared/index.js';
2
+ export interface RetentionResult {
3
+ pageVisitsDeleted: number;
4
+ consentRecordsDeleted: number;
5
+ rateEventsDeleted: number;
6
+ webhookDeliveriesDeleted: number;
7
+ }
8
+ /**
9
+ * The daily retention sweep, invoked from the scheduled handler in index.ts.
10
+ * Runs unconditionally against whatever retentionDays the site has configured;
11
+ * there is no dry-run mode, because this is a fixed maintenance job rather
12
+ * than a user-triggered action.
13
+ */
14
+ export declare function runRetentionCleanup(db: D1Database, retentionDays: RetentionSettings): Promise<RetentionResult>;
15
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/lib/retention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAU5D,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wBAAwB,EAAE,MAAM,CAAC;CAClC;AAsBD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAoB1B"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * These two are flat constants: they are internal bookkeeping with no reason to
3
+ * be configurable. Only visits and consent are site-configurable, through
4
+ * site_settings.retentionDays.
5
+ */
6
+ const RATE_EVENTS_RETENTION_DAYS = 2;
7
+ const WEBHOOK_DELIVERIES_RETENTION_DAYS = 30;
8
+ /**
9
+ * Table name is interpolated, not bound: D1/SQLite has no parameter
10
+ * placeholder for identifiers. Safe here because `table` is restricted to
11
+ * the literal union above, never caller/user input.
12
+ */
13
+ async function deleteOlderThanDays(db, table, column, days) {
14
+ const result = await db
15
+ .prepare(`DELETE FROM ${table} WHERE ${column} < datetime('now', ?1)`)
16
+ .bind(`-${days} days`)
17
+ .run();
18
+ return result.meta.changes;
19
+ }
20
+ /**
21
+ * The daily retention sweep, invoked from the scheduled handler in index.ts.
22
+ * Runs unconditionally against whatever retentionDays the site has configured;
23
+ * there is no dry-run mode, because this is a fixed maintenance job rather
24
+ * than a user-triggered action.
25
+ */
26
+ export async function runRetentionCleanup(db, retentionDays) {
27
+ const [pageVisitsDeleted, consentRecordsDeleted, rateEventsDeleted, webhookDeliveriesDeleted] = await Promise.all([
28
+ deleteOlderThanDays(db, 'page_visits', 'visited_at', retentionDays.visits),
29
+ deleteOlderThanDays(db, 'consent_records', 'recorded_at', retentionDays.consent),
30
+ deleteOlderThanDays(db, 'rate_events', 'created_at', RATE_EVENTS_RETENTION_DAYS),
31
+ deleteOlderThanDays(db, 'webhook_deliveries', 'created_at', WEBHOOK_DELIVERIES_RETENTION_DAYS),
32
+ ]);
33
+ return {
34
+ pageVisitsDeleted,
35
+ consentRecordsDeleted,
36
+ rateEventsDeleted,
37
+ webhookDeliveriesDeleted,
38
+ };
39
+ }
40
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/lib/retention.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,0BAA0B,GAAG,CAAC,CAAC;AACrC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AAW7C;;;;GAIG;AACH,KAAK,UAAU,mBAAmB,CAChC,EAAc,EACd,KAAqB,EACrB,MAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,EAAE;SACpB,OAAO,CAAC,eAAe,KAAK,UAAU,MAAM,wBAAwB,CAAC;SACrE,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC;SACrB,GAAG,EAAE,CAAC;IACT,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,EAAc,EACd,aAAgC;IAEhC,MAAM,CAAC,iBAAiB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,wBAAwB,CAAC,GAC3F,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,mBAAmB,CAAC,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC;QAC1E,mBAAmB,CAAC,EAAE,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,CAAC,OAAO,CAAC;QAChF,mBAAmB,CAAC,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,0BAA0B,CAAC;QAChF,mBAAmB,CACjB,EAAE,EACF,oBAAoB,EACpB,YAAY,EACZ,iCAAiC,CAClC;KACF,CAAC,CAAC;IAEL,OAAO;QACL,iBAAiB;QACjB,qBAAqB;QACrB,iBAAiB;QACjB,wBAAwB;KACzB,CAAC;AACJ,CAAC"}