@c15t/backend 2.0.0-rc.0 → 2.0.0-rc.10

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 (336) hide show
  1. package/README.md +3 -3
  2. package/dist/302.js +473 -0
  3. package/dist/583.js +540 -0
  4. package/dist/915.js +1771 -0
  5. package/dist/cache.cjs +5 -5
  6. package/dist/cache.js +4 -415
  7. package/dist/core.cjs +1356 -120
  8. package/dist/core.js +163 -1981
  9. package/dist/db/adapters/drizzle.cjs +1 -1
  10. package/dist/db/adapters/drizzle.js +1 -2
  11. package/dist/db/adapters/kysely.cjs +1 -1
  12. package/dist/db/adapters/kysely.js +1 -2
  13. package/dist/db/adapters/mongo.cjs +1 -1
  14. package/dist/db/adapters/mongo.js +1 -2
  15. package/dist/db/adapters/prisma.cjs +1 -1
  16. package/dist/db/adapters/prisma.js +1 -2
  17. package/dist/db/adapters/typeorm.cjs +1 -1
  18. package/dist/db/adapters/typeorm.js +1 -2
  19. package/dist/db/adapters.cjs +1 -1
  20. package/dist/db/migrator.cjs +1 -1
  21. package/dist/db/schema.cjs +43 -3
  22. package/dist/db/schema.js +35 -4
  23. package/dist/define-config.cjs +1 -1
  24. package/dist/edge.cjs +1106 -0
  25. package/dist/edge.js +190 -0
  26. package/dist/router.cjs +885 -123
  27. package/dist/router.js +1 -1507
  28. package/dist/{types.cjs → types/index.cjs} +1 -1
  29. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  30. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  31. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  32. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  33. package/{dist → dist-types}/cache/gvl-resolver.d.ts +0 -1
  34. package/{dist → dist-types}/cache/index.d.ts +0 -1
  35. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  36. package/{dist → dist-types}/cache/types.d.ts +0 -1
  37. package/{dist → dist-types}/core.d.ts +8 -1
  38. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  39. package/dist-types/db/registry/consent-policy.d.ts +78 -0
  40. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  41. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  42. package/dist-types/db/registry/index.d.ts +118 -0
  43. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  44. package/{dist → dist-types}/db/registry/subject.d.ts +0 -2
  45. package/{dist → dist-types}/db/registry/types.d.ts +1 -1
  46. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  47. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  48. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  49. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  50. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  51. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  52. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
  53. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  54. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -32
  55. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -2
  56. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
  57. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +3 -3
  58. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
  59. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +7 -2
  60. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
  61. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +455 -28
  62. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  63. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -3
  64. package/{dist → dist-types}/db/schema/index.d.ts +908 -86
  65. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  66. package/dist-types/define-config.d.ts +17 -0
  67. package/dist-types/edge/index.d.ts +5 -0
  68. package/dist-types/edge/init-handler.d.ts +40 -0
  69. package/dist-types/edge/resolve-consent.d.ts +80 -0
  70. package/dist-types/edge/types.d.ts +13 -0
  71. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  72. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  73. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  74. package/{dist → dist-types}/handlers/init/index.d.ts +2 -3
  75. package/dist-types/handlers/init/policy.d.ts +26 -0
  76. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  77. package/dist-types/handlers/init/translations.d.ts +48 -0
  78. package/dist-types/handlers/legal-document/current.handler.d.ts +11 -0
  79. package/dist-types/handlers/legal-document/snapshot.d.ts +39 -0
  80. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  81. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  82. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  83. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +3 -2
  84. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  85. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +3 -2
  86. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -2
  87. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  88. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +3 -1
  89. package/{dist → dist-types}/init.d.ts +4 -7
  90. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  91. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  92. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  93. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  94. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +0 -1
  95. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  96. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  97. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  98. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  99. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  100. package/dist-types/policies/builder.d.ts +127 -0
  101. package/dist-types/policies/defaults.d.ts +2 -0
  102. package/dist-types/policies/matchers.d.ts +3 -0
  103. package/{dist → dist-types}/router.d.ts +0 -1
  104. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  105. package/{dist → dist-types}/routes/index.d.ts +1 -1
  106. package/{dist → dist-types}/routes/init.d.ts +0 -1
  107. package/dist-types/routes/legal-document.d.ts +7 -0
  108. package/{dist → dist-types}/routes/status.d.ts +0 -1
  109. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  110. package/{dist → dist-types}/types/api.d.ts +0 -1
  111. package/dist-types/types/index.d.ts +464 -0
  112. package/dist-types/utils/background.d.ts +6 -0
  113. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
  114. package/{dist → dist-types}/utils/env.d.ts +0 -1
  115. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  116. package/{dist → dist-types}/utils/instrumentation.d.ts +2 -3
  117. package/{dist → dist-types}/utils/logger.d.ts +0 -1
  118. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  119. package/dist-types/version.d.ts +1 -0
  120. package/docs/README.md +49 -0
  121. package/docs/api/configuration.md +208 -0
  122. package/docs/api/endpoints.md +211 -0
  123. package/docs/guides/caching.md +85 -0
  124. package/docs/guides/database-setup.md +128 -0
  125. package/docs/guides/edge-deployment.md +251 -0
  126. package/docs/guides/framework-integration.md +142 -0
  127. package/docs/guides/iab-tcf.md +89 -0
  128. package/docs/guides/observability.md +96 -0
  129. package/docs/guides/policy-packs.md +396 -0
  130. package/docs/quickstart.md +129 -0
  131. package/package.json +53 -39
  132. package/.turbo/turbo-build.log +0 -49
  133. package/CHANGELOG.md +0 -89
  134. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  135. package/dist/cache/adapters/index.d.ts.map +0 -1
  136. package/dist/cache/adapters/memory.d.ts.map +0 -1
  137. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  138. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  139. package/dist/cache/index.d.ts.map +0 -1
  140. package/dist/cache/keys.d.ts.map +0 -1
  141. package/dist/cache/types.d.ts.map +0 -1
  142. package/dist/core.d.ts.map +0 -1
  143. package/dist/db/adapters/drizzle.d.ts +0 -2
  144. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  145. package/dist/db/adapters/index.d.ts +0 -2
  146. package/dist/db/adapters/index.d.ts.map +0 -1
  147. package/dist/db/adapters/kysely.d.ts +0 -2
  148. package/dist/db/adapters/kysely.d.ts.map +0 -1
  149. package/dist/db/adapters/mongo.d.ts +0 -2
  150. package/dist/db/adapters/mongo.d.ts.map +0 -1
  151. package/dist/db/adapters/prisma.d.ts +0 -2
  152. package/dist/db/adapters/prisma.d.ts.map +0 -1
  153. package/dist/db/adapters/typeorm.d.ts +0 -2
  154. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  155. package/dist/db/migrator/index.d.ts.map +0 -1
  156. package/dist/db/registry/consent-policy.d.ts +0 -23
  157. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  158. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  159. package/dist/db/registry/domain.d.ts.map +0 -1
  160. package/dist/db/registry/index.d.ts +0 -57
  161. package/dist/db/registry/index.d.ts.map +0 -1
  162. package/dist/db/registry/subject.d.ts.map +0 -1
  163. package/dist/db/registry/types.d.ts.map +0 -1
  164. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  165. package/dist/db/registry/utils.d.ts.map +0 -1
  166. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  167. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  168. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  169. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  170. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  171. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  172. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  173. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  174. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  175. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  176. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  177. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  178. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  179. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  180. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  181. package/dist/db/schema/index.d.ts.map +0 -1
  182. package/dist/db/tenant-scope.d.ts.map +0 -1
  183. package/dist/define-config.d.ts +0 -5
  184. package/dist/define-config.d.ts.map +0 -1
  185. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  186. package/dist/handlers/consent/index.d.ts +0 -12
  187. package/dist/handlers/consent/index.d.ts.map +0 -1
  188. package/dist/handlers/init/geo.d.ts.map +0 -1
  189. package/dist/handlers/init/index.d.ts.map +0 -1
  190. package/dist/handlers/init/translations.d.ts +0 -28
  191. package/dist/handlers/init/translations.d.ts.map +0 -1
  192. package/dist/handlers/status/index.d.ts +0 -7
  193. package/dist/handlers/status/index.d.ts.map +0 -1
  194. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  195. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  196. package/dist/handlers/subject/index.d.ts +0 -10
  197. package/dist/handlers/subject/index.d.ts.map +0 -1
  198. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  199. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  200. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  201. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  202. package/dist/init.d.ts.map +0 -1
  203. package/dist/middleware/auth/index.d.ts.map +0 -1
  204. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  205. package/dist/middleware/cors/cors.d.ts.map +0 -1
  206. package/dist/middleware/cors/index.d.ts +0 -30
  207. package/dist/middleware/cors/index.d.ts.map +0 -1
  208. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  209. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  210. package/dist/middleware/openapi/config.d.ts.map +0 -1
  211. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  212. package/dist/middleware/openapi/index.d.ts +0 -12
  213. package/dist/middleware/openapi/index.d.ts.map +0 -1
  214. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  215. package/dist/router.d.ts.map +0 -1
  216. package/dist/routes/consent.d.ts.map +0 -1
  217. package/dist/routes/index.d.ts.map +0 -1
  218. package/dist/routes/init.d.ts.map +0 -1
  219. package/dist/routes/status.d.ts.map +0 -1
  220. package/dist/routes/subject.d.ts.map +0 -1
  221. package/dist/types/api.d.ts.map +0 -1
  222. package/dist/types/index.d.ts +0 -255
  223. package/dist/types/index.d.ts.map +0 -1
  224. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  225. package/dist/utils/env.d.ts.map +0 -1
  226. package/dist/utils/extract-error-message.d.ts.map +0 -1
  227. package/dist/utils/index.d.ts +0 -4
  228. package/dist/utils/index.d.ts.map +0 -1
  229. package/dist/utils/instrumentation.d.ts.map +0 -1
  230. package/dist/utils/logger.d.ts.map +0 -1
  231. package/dist/utils/metrics.d.ts.map +0 -1
  232. package/dist/version.d.ts +0 -2
  233. package/dist/version.d.ts.map +0 -1
  234. package/knip.json +0 -31
  235. package/rslib.config.ts +0 -93
  236. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  237. package/src/cache/adapters/index.ts +0 -22
  238. package/src/cache/adapters/memory.ts +0 -111
  239. package/src/cache/adapters/upstash-redis.ts +0 -113
  240. package/src/cache/gvl-resolver.ts +0 -289
  241. package/src/cache/index.ts +0 -34
  242. package/src/cache/keys.ts +0 -68
  243. package/src/cache/types.ts +0 -66
  244. package/src/core.ts +0 -368
  245. package/src/db/migrator/index.ts +0 -80
  246. package/src/db/registry/consent-policy.test.ts +0 -451
  247. package/src/db/registry/consent-policy.ts +0 -82
  248. package/src/db/registry/consent-purpose.test.ts +0 -428
  249. package/src/db/registry/consent-purpose.ts +0 -61
  250. package/src/db/registry/domain.test.ts +0 -445
  251. package/src/db/registry/domain.ts +0 -91
  252. package/src/db/registry/index.ts +0 -14
  253. package/src/db/registry/subject.test.ts +0 -388
  254. package/src/db/registry/subject.ts +0 -129
  255. package/src/db/registry/types.ts +0 -10
  256. package/src/db/registry/utils/generate-id.test.ts +0 -216
  257. package/src/db/registry/utils/generate-id.ts +0 -133
  258. package/src/db/registry/utils.ts +0 -133
  259. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  260. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  261. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  262. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  263. package/src/db/schema/1.0.0/consent.ts +0 -20
  264. package/src/db/schema/1.0.0/domain.ts +0 -12
  265. package/src/db/schema/1.0.0/index.ts +0 -48
  266. package/src/db/schema/1.0.0/subject.ts +0 -12
  267. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  268. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  269. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  270. package/src/db/schema/2.0.0/consent.ts +0 -26
  271. package/src/db/schema/2.0.0/domain.ts +0 -12
  272. package/src/db/schema/2.0.0/index.ts +0 -47
  273. package/src/db/schema/2.0.0/subject.ts +0 -14
  274. package/src/db/schema/index.ts +0 -15
  275. package/src/db/tenant-scope.test.ts +0 -750
  276. package/src/db/tenant-scope.ts +0 -103
  277. package/src/define-config.ts +0 -5
  278. package/src/handlers/consent/check.handler.ts +0 -126
  279. package/src/handlers/init/geo.test.ts +0 -317
  280. package/src/handlers/init/geo.ts +0 -195
  281. package/src/handlers/init/index.test.ts +0 -205
  282. package/src/handlers/init/index.ts +0 -114
  283. package/src/handlers/init/translations.test.ts +0 -121
  284. package/src/handlers/init/translations.ts +0 -72
  285. package/src/handlers/status/status.handler.test.ts +0 -155
  286. package/src/handlers/status/status.handler.ts +0 -51
  287. package/src/handlers/subject/get.handler.ts +0 -93
  288. package/src/handlers/subject/list.handler.ts +0 -93
  289. package/src/handlers/subject/patch.handler.ts +0 -122
  290. package/src/handlers/subject/post.handler.test.ts +0 -294
  291. package/src/handlers/subject/post.handler.ts +0 -254
  292. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  293. package/src/handlers/utils/consent-enrichment.ts +0 -218
  294. package/src/init.test.ts +0 -126
  295. package/src/init.ts +0 -87
  296. package/src/middleware/auth/index.ts +0 -11
  297. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  298. package/src/middleware/auth/validate-api-key.ts +0 -107
  299. package/src/middleware/cors/cors.test.ts +0 -135
  300. package/src/middleware/cors/cors.ts +0 -186
  301. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  302. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  303. package/src/middleware/cors/process-cors.ts +0 -91
  304. package/src/middleware/openapi/config.ts +0 -29
  305. package/src/middleware/openapi/handlers.ts +0 -34
  306. package/src/middleware/process-ip/index.test.ts +0 -195
  307. package/src/middleware/process-ip/index.ts +0 -199
  308. package/src/router.ts +0 -15
  309. package/src/routes/consent.ts +0 -52
  310. package/src/routes/index.ts +0 -10
  311. package/src/routes/init.ts +0 -102
  312. package/src/routes/status.ts +0 -46
  313. package/src/routes/subject.ts +0 -152
  314. package/src/types/api.ts +0 -48
  315. package/src/types/index.ts +0 -288
  316. package/src/utils/create-telemetry-options.test.ts +0 -302
  317. package/src/utils/create-telemetry-options.ts +0 -229
  318. package/src/utils/env.ts +0 -84
  319. package/src/utils/extract-error-message.ts +0 -21
  320. package/src/utils/instrumentation.test.ts +0 -185
  321. package/src/utils/instrumentation.ts +0 -196
  322. package/src/utils/logger.ts +0 -41
  323. package/src/utils/metrics.test.ts +0 -323
  324. package/src/utils/metrics.ts +0 -402
  325. package/src/utils/telemetry-pii.test.ts +0 -325
  326. package/src/version.ts +0 -2
  327. package/tsconfig.json +0 -11
  328. package/vitest.config.ts +0 -28
  329. /package/dist/{types.js → types/index.js} +0 -0
  330. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  331. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  332. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  333. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  334. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  335. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  336. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/router.js CHANGED
@@ -1,1507 +1 @@
1
- import { checkConsentOutputSchema, checkConsentQuerySchema, getSubjectInputSchema, getSubjectOutputSchema, initOutputSchema, listSubjectsOutputSchema, listSubjectsQuerySchema, patchSubjectOutputSchema, postSubjectInputSchema, postSubjectOutputSchema, statusOutputSchema, subjectIdSchema } from "@c15t/schema";
2
- import { Hono } from "hono";
3
- import { describeRoute, resolver, validator } from "hono-openapi";
4
- import { HTTPException } from "hono/http-exception";
5
- import { SpanKind as api_SpanKind, SpanStatusCode as api_SpanStatusCode, context, metrics as api_metrics, trace as api_trace } from "@opentelemetry/api";
6
- import { baseTranslations, deepMergeTranslations, selectLanguage } from "@c15t/translations";
7
- import { object, optional, string } from "valibot";
8
- import base_x from "base-x";
9
- function extractErrorMessage(error) {
10
- if (error instanceof AggregateError && error.errors?.length > 0) {
11
- const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
12
- return `AggregateError: ${inner}`;
13
- }
14
- if (error instanceof Error) return error.message || error.name;
15
- return String(error);
16
- }
17
- const version_version = '2.0.0-rc.0';
18
- let cachedConfig = null;
19
- let cachedDefaultAttributes = {};
20
- function create_telemetry_options_isTelemetryEnabled(options) {
21
- if (options) return options.advanced?.telemetry?.enabled === true;
22
- return cachedConfig?.enabled === true;
23
- }
24
- const create_telemetry_options_getTracer = (options)=>{
25
- if (!create_telemetry_options_isTelemetryEnabled(options)) return api_trace.getTracer('c15t-noop');
26
- const tracer = options?.advanced?.telemetry?.tracer ?? cachedConfig?.tracer;
27
- if (tracer) return tracer;
28
- return api_trace.getTracer(options?.appName ?? 'c15t');
29
- };
30
- const getMeter = (options)=>{
31
- if (!create_telemetry_options_isTelemetryEnabled(options)) return api_metrics.getMeter('c15t-noop');
32
- const meter = options?.advanced?.telemetry?.meter ?? cachedConfig?.meter;
33
- if (meter) return meter;
34
- return api_metrics.getMeter(options?.appName ?? 'c15t');
35
- };
36
- function getDefaultAttributes() {
37
- return cachedDefaultAttributes;
38
- }
39
- const handleSpanError = (span, error)=>{
40
- span.setStatus({
41
- code: api_SpanStatusCode.ERROR,
42
- message: extractErrorMessage(error)
43
- });
44
- if (error instanceof Error) span.setAttribute('error.type', error.name);
45
- };
46
- const withSpanContext = async (span, operation)=>context["with"](api_trace.setSpan(context.active(), span), operation);
47
- function sanitizeAttributes(attrs) {
48
- return Object.fromEntries(Object.entries(attrs).filter(([_, v])=>null != v));
49
- }
50
- function createMetrics(meter) {
51
- const consentCreated = meter.createCounter('c15t.consent.created', {
52
- description: 'Number of consent submissions',
53
- unit: '1'
54
- });
55
- const consentAccepted = meter.createCounter('c15t.consent.accepted', {
56
- description: 'Number of consents accepted',
57
- unit: '1'
58
- });
59
- const consentRejected = meter.createCounter('c15t.consent.rejected', {
60
- description: 'Number of consents rejected',
61
- unit: '1'
62
- });
63
- const subjectCreated = meter.createCounter('c15t.subject.created', {
64
- description: 'Number of new subjects created',
65
- unit: '1'
66
- });
67
- const subjectLinked = meter.createCounter('c15t.subject.linked', {
68
- description: 'Number of subjects linked to external ID',
69
- unit: '1'
70
- });
71
- const consentCheckCount = meter.createCounter('c15t.consent_check.count', {
72
- description: 'Number of cross-device consent checks',
73
- unit: '1'
74
- });
75
- const initCount = meter.createCounter('c15t.init.count', {
76
- description: 'Number of init endpoint calls',
77
- unit: '1'
78
- });
79
- const httpRequestDuration = meter.createHistogram('c15t.http.request.duration', {
80
- description: 'HTTP request latency',
81
- unit: 'ms'
82
- });
83
- const httpRequestCount = meter.createCounter('c15t.http.request.count', {
84
- description: 'Number of HTTP requests',
85
- unit: '1'
86
- });
87
- const httpErrorCount = meter.createCounter('c15t.http.error.count', {
88
- description: 'Number of HTTP errors',
89
- unit: '1'
90
- });
91
- const dbQueryDuration = meter.createHistogram('c15t.db.query.duration', {
92
- description: 'Database query latency',
93
- unit: 'ms'
94
- });
95
- const dbQueryCount = meter.createCounter('c15t.db.query.count', {
96
- description: 'Number of database queries',
97
- unit: '1'
98
- });
99
- const dbErrorCount = meter.createCounter('c15t.db.error.count', {
100
- description: 'Number of database errors',
101
- unit: '1'
102
- });
103
- const cacheHit = meter.createCounter('c15t.cache.hit', {
104
- description: 'Number of cache hits',
105
- unit: '1'
106
- });
107
- const cacheMiss = meter.createCounter('c15t.cache.miss', {
108
- description: 'Number of cache misses',
109
- unit: '1'
110
- });
111
- const cacheLatency = meter.createHistogram('c15t.cache.latency', {
112
- description: 'Cache operation latency',
113
- unit: 'ms'
114
- });
115
- const gvlFetchDuration = meter.createHistogram('c15t.gvl.fetch.duration', {
116
- description: 'GVL fetch latency',
117
- unit: 'ms'
118
- });
119
- const gvlFetchCount = meter.createCounter('c15t.gvl.fetch.count', {
120
- description: 'Number of GVL fetches',
121
- unit: '1'
122
- });
123
- const gvlFetchError = meter.createCounter('c15t.gvl.fetch.error', {
124
- description: 'Number of GVL fetch errors',
125
- unit: '1'
126
- });
127
- return {
128
- consentCreated,
129
- consentAccepted,
130
- consentRejected,
131
- subjectCreated,
132
- subjectLinked,
133
- consentCheckCount,
134
- initCount,
135
- httpRequestDuration,
136
- httpRequestCount,
137
- httpErrorCount,
138
- dbQueryDuration,
139
- dbQueryCount,
140
- dbErrorCount,
141
- cacheHit,
142
- cacheMiss,
143
- cacheLatency,
144
- gvlFetchDuration,
145
- gvlFetchCount,
146
- gvlFetchError,
147
- recordConsentCreated (attributes) {
148
- consentCreated.add(1, sanitizeAttributes(attributes));
149
- },
150
- recordConsentAccepted (attributes) {
151
- consentAccepted.add(1, sanitizeAttributes(attributes));
152
- },
153
- recordConsentRejected (attributes) {
154
- consentRejected.add(1, sanitizeAttributes(attributes));
155
- },
156
- recordSubjectCreated (attributes) {
157
- subjectCreated.add(1, sanitizeAttributes(attributes));
158
- },
159
- recordSubjectLinked (identityProvider) {
160
- subjectLinked.add(1, {
161
- identityProvider: identityProvider || 'unknown'
162
- });
163
- },
164
- recordConsentCheck (type, found) {
165
- consentCheckCount.add(1, {
166
- type,
167
- found: String(found)
168
- });
169
- },
170
- recordInit (attributes) {
171
- initCount.add(1, sanitizeAttributes(attributes));
172
- },
173
- recordHttpRequest (attributes, durationMs) {
174
- const attrs = sanitizeAttributes(attributes);
175
- httpRequestCount.add(1, attrs);
176
- httpRequestDuration.record(durationMs, attrs);
177
- if (attributes.status >= 400) httpErrorCount.add(1, attrs);
178
- },
179
- recordDbQuery (attributes, durationMs) {
180
- const attrs = sanitizeAttributes(attributes);
181
- dbQueryCount.add(1, attrs);
182
- dbQueryDuration.record(durationMs, attrs);
183
- },
184
- recordDbError (attributes) {
185
- dbErrorCount.add(1, sanitizeAttributes(attributes));
186
- },
187
- recordCacheHit (layer) {
188
- cacheHit.add(1, {
189
- layer
190
- });
191
- },
192
- recordCacheMiss (layer) {
193
- cacheMiss.add(1, {
194
- layer
195
- });
196
- },
197
- recordCacheLatency (attributes, durationMs) {
198
- cacheLatency.record(durationMs, sanitizeAttributes(attributes));
199
- },
200
- recordGvlFetch (attributes, durationMs) {
201
- const attrs = sanitizeAttributes(attributes);
202
- gvlFetchCount.add(1, attrs);
203
- gvlFetchDuration.record(durationMs, attrs);
204
- },
205
- recordGvlError (attributes) {
206
- gvlFetchError.add(1, sanitizeAttributes(attributes));
207
- }
208
- };
209
- }
210
- let metricsInstance = null;
211
- function getMetrics(options) {
212
- if (metricsInstance) return metricsInstance;
213
- if (!create_telemetry_options_isTelemetryEnabled(options)) return null;
214
- metricsInstance = createMetrics(getMeter(options));
215
- return metricsInstance;
216
- }
217
- function parsePurposeIds(purposeIds) {
218
- if (null == purposeIds) return [];
219
- const ids = 'object' == typeof purposeIds && 'json' in purposeIds ? purposeIds.json : purposeIds;
220
- return Array.isArray(ids) ? ids : [];
221
- }
222
- async function batchLoadPolicies(policyIds, ctx) {
223
- const { db, registry } = ctx;
224
- const policyMap = new Map();
225
- if (policyIds.size > 0) {
226
- const policies = await db.findMany('consentPolicy', {
227
- where: (b)=>b('id', 'in', [
228
- ...policyIds
229
- ])
230
- });
231
- for (const p of policies)policyMap.set(p.id, p);
232
- }
233
- const uniqueTypes = new Set();
234
- for (const p of policyMap.values())uniqueTypes.add(p.type);
235
- const latestPolicyByType = new Map();
236
- for (const type of uniqueTypes){
237
- const latest = await registry.findOrCreatePolicy(type);
238
- if (latest) latestPolicyByType.set(type, latest.id);
239
- }
240
- return {
241
- policyMap,
242
- latestPolicyByType
243
- };
244
- }
245
- async function enrichConsents(consents, ctx) {
246
- if (0 === consents.length) return [];
247
- const policyIds = new Set();
248
- for (const c of consents)if (c.policyId) policyIds.add(c.policyId);
249
- const { policyMap, latestPolicyByType } = await batchLoadPolicies(policyIds, ctx);
250
- const allPurposeIds = new Set();
251
- for (const c of consents)for (const id of parsePurposeIds(c.purposeIds))allPurposeIds.add(id);
252
- const purposeMap = new Map();
253
- if (allPurposeIds.size > 0) {
254
- const purposes = await ctx.db.findMany('consentPurpose', {
255
- where: (b)=>b('id', 'in', [
256
- ...allPurposeIds
257
- ])
258
- });
259
- for (const p of purposes)purposeMap.set(p.id, p.code);
260
- }
261
- return consents.map((consent)=>{
262
- let policyType = 'unknown';
263
- let isLatestPolicy = false;
264
- if (consent.policyId) {
265
- const policy = policyMap.get(consent.policyId);
266
- if (policy) {
267
- policyType = policy.type;
268
- isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
269
- }
270
- }
271
- let preferences;
272
- const ids = parsePurposeIds(consent.purposeIds);
273
- if (ids.length > 0) {
274
- preferences = {};
275
- for (const purposeId of ids){
276
- const code = purposeMap.get(purposeId);
277
- if (code) preferences[code] = true;
278
- }
279
- }
280
- return {
281
- id: consent.id,
282
- type: policyType,
283
- policyId: consent.policyId ?? void 0,
284
- isLatestPolicy,
285
- preferences,
286
- givenAt: consent.givenAt
287
- };
288
- });
289
- }
290
- async function resolveConsentPolicies(consents, ctx) {
291
- if (0 === consents.length) return [];
292
- const policyIds = new Set();
293
- for (const c of consents)if (c.policyId) policyIds.add(c.policyId);
294
- const { policyMap, latestPolicyByType } = await batchLoadPolicies(policyIds, ctx);
295
- return consents.map((consent)=>{
296
- let policyType = 'unknown';
297
- let isLatestPolicy = false;
298
- if (consent.policyId) {
299
- const policy = policyMap.get(consent.policyId);
300
- if (policy) {
301
- policyType = policy.type;
302
- isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
303
- }
304
- }
305
- return {
306
- consentId: consent.id,
307
- policyType,
308
- policyId: consent.policyId ?? void 0,
309
- isLatestPolicy
310
- };
311
- });
312
- }
313
- const checkConsentHandler = async (c)=>{
314
- const ctx = c.get('c15tContext');
315
- const logger = ctx.logger;
316
- logger.info('Handling GET /consents/check request');
317
- const { db, registry } = ctx;
318
- const externalId = c.req.query('externalId');
319
- const type = c.req.query('type');
320
- if (!externalId) throw new HTTPException(422, {
321
- message: 'externalId query parameter is required',
322
- cause: {
323
- code: 'EXTERNAL_ID_REQUIRED'
324
- }
325
- });
326
- if (!type) throw new HTTPException(422, {
327
- message: 'type query parameter is required',
328
- cause: {
329
- code: 'TYPE_REQUIRED'
330
- }
331
- });
332
- const types = type.split(',').map((t)=>t.trim());
333
- logger.debug('Request parameters', {
334
- externalId,
335
- types
336
- });
337
- try {
338
- const subjects = await db.findMany('subject', {
339
- where: (b)=>b('externalId', '=', externalId)
340
- });
341
- const subjectIds = subjects.map((s)=>s.id);
342
- const results = {};
343
- for (const t of types)results[t] = {
344
- hasConsent: false,
345
- isLatestPolicy: false
346
- };
347
- if (0 === subjectIds.length) {
348
- logger.debug('No subjects found for externalId', {
349
- externalId
350
- });
351
- return c.json({
352
- results
353
- });
354
- }
355
- const allConsents = await Promise.all(subjectIds.map((subjectId)=>db.findMany('consent', {
356
- where: (b)=>b('subjectId', '=', subjectId)
357
- })));
358
- const consents = allConsents.flat();
359
- const policyInfos = await resolveConsentPolicies(consents, {
360
- db,
361
- registry
362
- });
363
- for (const info of policyInfos){
364
- if (!types.includes(info.policyType)) continue;
365
- const entry = results[info.policyType];
366
- if (entry) {
367
- entry.hasConsent = true;
368
- if (info.isLatestPolicy) entry.isLatestPolicy = true;
369
- }
370
- }
371
- logger.debug('Consent check results', {
372
- externalId,
373
- results
374
- });
375
- const metrics = getMetrics();
376
- if (metrics) for (const [type, result] of Object.entries(results))metrics.recordConsentCheck(type, result.hasConsent);
377
- return c.json({
378
- results
379
- });
380
- } catch (error) {
381
- logger.error('Error in GET /consents/check handler', {
382
- error: extractErrorMessage(error),
383
- errorType: error instanceof Error ? error.constructor.name : typeof error
384
- });
385
- if (error instanceof HTTPException) throw error;
386
- throw new HTTPException(500, {
387
- message: 'Internal server error',
388
- cause: {
389
- code: 'INTERNAL_SERVER_ERROR'
390
- }
391
- });
392
- }
393
- };
394
- const createConsentRoutes = ()=>{
395
- const app = new Hono();
396
- app.get('/check', describeRoute({
397
- summary: 'Check consent by external user ID',
398
- description: `Pre-banner cross-device consent check. Use to avoid showing the banner when the user has already consented on another device.
399
-
400
- **Query parameters:**
401
- - \`externalId\` – External user ID to check
402
- - \`type\` – Consent type(s) to check (comma-separated)`,
403
- tags: [
404
- 'Consent'
405
- ],
406
- responses: {
407
- 200: {
408
- description: 'Consent check result per requested type(s)',
409
- content: {
410
- 'application/json': {
411
- schema: resolver(checkConsentOutputSchema)
412
- }
413
- }
414
- },
415
- 422: {
416
- description: 'Invalid or missing query parameters'
417
- }
418
- }
419
- }), validator('query', checkConsentQuerySchema), checkConsentHandler);
420
- return app;
421
- };
422
- async function executeWithSpan(span, operation) {
423
- try {
424
- const result = await withSpanContext(span, operation);
425
- span.setStatus({
426
- code: api_SpanStatusCode.OK
427
- });
428
- return result;
429
- } catch (error) {
430
- handleSpanError(span, error);
431
- throw error;
432
- } finally{
433
- span.end();
434
- }
435
- }
436
- function resolveDefaultAttributes(options) {
437
- return options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
438
- }
439
- async function withExternalSpan(attributes, operation, options) {
440
- if (!create_telemetry_options_isTelemetryEnabled(options)) return operation();
441
- const tracer = create_telemetry_options_getTracer(options);
442
- const url = new URL(attributes.url);
443
- const spanName = `HTTP ${attributes.method} ${url.hostname}`;
444
- const span = tracer.startSpan(spanName, {
445
- kind: api_SpanKind.CLIENT,
446
- attributes: {
447
- 'http.method': attributes.method,
448
- 'http.url': `${url.origin}${url.pathname}`,
449
- 'http.host': url.hostname,
450
- ...resolveDefaultAttributes(options),
451
- ...Object.fromEntries(Object.entries(attributes).filter(([key])=>![
452
- 'url',
453
- 'method'
454
- ].includes(key)))
455
- }
456
- });
457
- return executeWithSpan(span, operation);
458
- }
459
- async function withCacheSpan(operation, layer, fn, options) {
460
- if (!create_telemetry_options_isTelemetryEnabled(options)) return fn();
461
- const tracer = create_telemetry_options_getTracer(options);
462
- const spanName = `cache.${layer}.${operation}`;
463
- const span = tracer.startSpan(spanName, {
464
- kind: api_SpanKind.CLIENT,
465
- attributes: {
466
- 'cache.operation': operation,
467
- 'cache.layer': layer,
468
- ...resolveDefaultAttributes(options)
469
- }
470
- });
471
- return executeWithSpan(span, fn);
472
- }
473
- const GVL_TTL_MS = 259200000;
474
- const memory_memoryCache = new Map();
475
- function createMemoryCacheAdapter() {
476
- return {
477
- async get (key) {
478
- const entry = memory_memoryCache.get(key);
479
- if (!entry) return null;
480
- if (Date.now() > entry.expiresAt) {
481
- memory_memoryCache.delete(key);
482
- return null;
483
- }
484
- return entry.value;
485
- },
486
- async set (key, value, ttlMs = 300000) {
487
- memory_memoryCache.set(key, {
488
- value,
489
- expiresAt: Date.now() + ttlMs
490
- });
491
- },
492
- async delete (key) {
493
- memory_memoryCache.delete(key);
494
- },
495
- async has (key) {
496
- const entry = memory_memoryCache.get(key);
497
- if (!entry) return false;
498
- if (Date.now() > entry.expiresAt) {
499
- memory_memoryCache.delete(key);
500
- return false;
501
- }
502
- return true;
503
- }
504
- };
505
- }
506
- function createGVLCacheKey(appName, language, vendorIds) {
507
- const sortedIds = vendorIds ? [
508
- ...vendorIds
509
- ].sort((a, b)=>a - b).join(',') : 'all';
510
- return `${appName}:gvl:${language}:${sortedIds}`;
511
- }
512
- const GVL_ENDPOINT = 'https://gvl.consent.io';
513
- const inflightRequests = new Map();
514
- async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
515
- const sortedVendorIds = vendorIds ? [
516
- ...vendorIds
517
- ].sort((a, b)=>a - b) : [];
518
- const dedupeKey = `${endpoint}|${language}|${sortedVendorIds.join(',')}`;
519
- const existingRequest = inflightRequests.get(dedupeKey);
520
- if (existingRequest) return existingRequest;
521
- const url = new URL(endpoint);
522
- if (sortedVendorIds.length > 0) url.searchParams.set('vendorIds', sortedVendorIds.join(','));
523
- const promise = (async ()=>{
524
- const fetchStart = Date.now();
525
- try {
526
- const gvl = await withExternalSpan({
527
- url: url.toString(),
528
- method: 'GET'
529
- }, async ()=>{
530
- const response = await fetch(url.toString(), {
531
- headers: {
532
- 'Accept-Language': language
533
- }
534
- });
535
- if (204 === response.status) return null;
536
- if (!response.ok) throw new Error(`Failed to fetch GVL: ${response.status} ${response.statusText}`);
537
- const text = await response.text();
538
- const trimmed = text.trim().replace(/^\uFEFF/, '');
539
- let parsed;
540
- try {
541
- parsed = JSON.parse(trimmed);
542
- } catch {
543
- let depth = 0;
544
- let end = -1;
545
- const start = trimmed.indexOf('{');
546
- if (start >= 0) for(let i = start; i < trimmed.length; i++){
547
- const c = trimmed[i];
548
- if ('{' === c) depth++;
549
- else if ('}' === c) {
550
- depth--;
551
- if (0 === depth) {
552
- end = i + 1;
553
- break;
554
- }
555
- }
556
- }
557
- if (end > 0) parsed = JSON.parse(trimmed.slice(0, end));
558
- else throw new SyntaxError('Invalid GVL response: not valid JSON');
559
- }
560
- if (!parsed.vendorListVersion || !parsed.purposes || !parsed.vendors) throw new Error('Invalid GVL response: missing required fields');
561
- return parsed;
562
- });
563
- getMetrics()?.recordGvlFetch({
564
- language,
565
- source: 'fetch',
566
- status: 200
567
- }, Date.now() - fetchStart);
568
- return gvl;
569
- } catch (error) {
570
- getMetrics()?.recordGvlError({
571
- language,
572
- errorType: error instanceof Error ? error.name : 'UnknownError'
573
- });
574
- throw error;
575
- } finally{
576
- inflightRequests.delete(dedupeKey);
577
- }
578
- })();
579
- inflightRequests.set(dedupeKey, promise);
580
- return promise;
581
- }
582
- function createGVLResolver(options) {
583
- const { appName, bundled, cacheAdapter, vendorIds, endpoint } = options;
584
- const memoryCache = createMemoryCacheAdapter();
585
- return {
586
- async get (language) {
587
- const cacheKey = createGVLCacheKey(appName, language, vendorIds);
588
- if (bundled?.[language]) return bundled[language];
589
- const memoryHit = await withCacheSpan('get', 'memory', ()=>memoryCache.get(cacheKey));
590
- if (memoryHit) {
591
- getMetrics()?.recordCacheHit('memory');
592
- return memoryHit;
593
- }
594
- getMetrics()?.recordCacheMiss('memory');
595
- if (cacheAdapter) {
596
- const externalHit = await withCacheSpan('get', 'external', ()=>cacheAdapter.get(cacheKey));
597
- if (externalHit) {
598
- getMetrics()?.recordCacheHit('external');
599
- await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, externalHit, 300000));
600
- return externalHit;
601
- }
602
- getMetrics()?.recordCacheMiss('external');
603
- }
604
- const gvl = await fetchGVLWithLanguage(language, vendorIds, endpoint);
605
- if (gvl) {
606
- await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, gvl, 300000));
607
- if (cacheAdapter) await withCacheSpan('set', 'external', ()=>cacheAdapter.set(cacheKey, gvl, GVL_TTL_MS));
608
- }
609
- return gvl;
610
- }
611
- };
612
- }
613
- function geo_normalizeHeader(value) {
614
- if (!value) return null;
615
- return Array.isArray(value) ? value[0] ?? null : value;
616
- }
617
- function getGeoHeaders(headers) {
618
- const countryCode = geo_normalizeHeader(headers.get('x-c15t-country')) ?? geo_normalizeHeader(headers.get('cf-ipcountry')) ?? geo_normalizeHeader(headers.get('x-vercel-ip-country')) ?? geo_normalizeHeader(headers.get('x-amz-cf-ipcountry')) ?? geo_normalizeHeader(headers.get('x-country-code'));
619
- const regionCode = geo_normalizeHeader(headers.get('x-c15t-region')) ?? geo_normalizeHeader(headers.get('x-vercel-ip-country-region')) ?? geo_normalizeHeader(headers.get('x-region-code'));
620
- return {
621
- countryCode,
622
- regionCode
623
- };
624
- }
625
- function checkJurisdiction(countryCode, regionCode) {
626
- const jurisdictions = {
627
- EU: new Set([
628
- 'AT',
629
- 'BE',
630
- 'BG',
631
- 'HR',
632
- 'CY',
633
- 'CZ',
634
- 'DK',
635
- 'EE',
636
- 'FI',
637
- 'FR',
638
- 'DE',
639
- 'GR',
640
- 'HU',
641
- 'IE',
642
- 'IT',
643
- 'LV',
644
- 'LT',
645
- 'LU',
646
- 'MT',
647
- 'NL',
648
- 'PL',
649
- 'PT',
650
- 'RO',
651
- 'SK',
652
- 'SI',
653
- 'ES',
654
- 'SE'
655
- ]),
656
- EEA: new Set([
657
- 'IS',
658
- 'NO',
659
- 'LI'
660
- ]),
661
- UK: new Set([
662
- 'GB'
663
- ]),
664
- CH: new Set([
665
- 'CH'
666
- ]),
667
- BR: new Set([
668
- 'BR'
669
- ]),
670
- CA: new Set([
671
- 'CA'
672
- ]),
673
- AU: new Set([
674
- 'AU'
675
- ]),
676
- JP: new Set([
677
- 'JP'
678
- ]),
679
- KR: new Set([
680
- 'KR'
681
- ]),
682
- US_CCPA_REGIONS: new Set([
683
- 'CA'
684
- ]),
685
- CA_QC_REGIONS: new Set([
686
- 'QC'
687
- ])
688
- };
689
- let jurisdiction = 'NONE';
690
- if (countryCode) {
691
- const normalizedCountryCode = countryCode.toUpperCase();
692
- const normalizedRegionCode = regionCode && 'string' == typeof regionCode ? (regionCode.includes('-') ? regionCode.split('-').pop() : regionCode).toUpperCase() : null;
693
- if ('US' === normalizedCountryCode && normalizedRegionCode && jurisdictions.US_CCPA_REGIONS.has(normalizedRegionCode)) return 'CCPA';
694
- if ('CA' === normalizedCountryCode && normalizedRegionCode && jurisdictions.CA_QC_REGIONS.has(normalizedRegionCode)) return 'QC_LAW25';
695
- const jurisdictionMap = [
696
- {
697
- sets: [
698
- jurisdictions.UK
699
- ],
700
- code: 'UK_GDPR'
701
- },
702
- {
703
- sets: [
704
- jurisdictions.EU,
705
- jurisdictions.EEA
706
- ],
707
- code: 'GDPR'
708
- },
709
- {
710
- sets: [
711
- jurisdictions.CH
712
- ],
713
- code: 'CH'
714
- },
715
- {
716
- sets: [
717
- jurisdictions.BR
718
- ],
719
- code: 'BR'
720
- },
721
- {
722
- sets: [
723
- jurisdictions.CA
724
- ],
725
- code: 'PIPEDA'
726
- },
727
- {
728
- sets: [
729
- jurisdictions.AU
730
- ],
731
- code: 'AU'
732
- },
733
- {
734
- sets: [
735
- jurisdictions.JP
736
- ],
737
- code: 'APPI'
738
- },
739
- {
740
- sets: [
741
- jurisdictions.KR
742
- ],
743
- code: 'PIPA'
744
- }
745
- ];
746
- for (const { sets, code } of jurisdictionMap)if (sets.some((set)=>set.has(normalizedCountryCode))) {
747
- jurisdiction = code;
748
- break;
749
- }
750
- }
751
- return jurisdiction;
752
- }
753
- async function getLocation(request, options) {
754
- if (options.advanced?.disableGeoLocation) return {
755
- countryCode: null,
756
- regionCode: null
757
- };
758
- const { countryCode, regionCode } = getGeoHeaders(request.headers);
759
- return {
760
- countryCode,
761
- regionCode
762
- };
763
- }
764
- function getJurisdiction(location, options) {
765
- if (options.advanced?.disableGeoLocation) return 'GDPR';
766
- return checkJurisdiction(location.countryCode, location.regionCode);
767
- }
768
- function isSupportedBaseLanguage(lang) {
769
- return lang in baseTranslations;
770
- }
771
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
772
- const supportedDefaultLanguages = Object.keys(baseTranslations);
773
- const supportedCustomLanguages = Object.keys(customTranslations || {});
774
- const supportedLanguages = [
775
- ...supportedDefaultLanguages,
776
- ...supportedCustomLanguages
777
- ];
778
- const preferredLanguage = selectLanguage(supportedLanguages, {
779
- header: acceptLanguage,
780
- fallback: 'en'
781
- });
782
- const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
783
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
784
- const translations = custom ? deepMergeTranslations(base, custom) : base;
785
- return {
786
- translations: translations,
787
- language: preferredLanguage
788
- };
789
- }
790
- const createInitRoute = (options)=>{
791
- const app = new Hono();
792
- app.get('/', describeRoute({
793
- summary: 'Get initial consent manager state',
794
- description: `Returns the initial state required to render the consent manager.
795
-
796
- - **Jurisdiction** – User's jurisdiction (defaults to GDPR if geo-location is disabled)
797
- - **Location** – User's location (null if geo-location is disabled)
798
- - **Translations** – Consent manager copy (from \`Accept-Language\` header)
799
- - **Branding** – Configured branding key
800
- - **GVL** – Global Vendor List when enabled
801
-
802
- Use for geo-targeted consent banners and regional compliance.`,
803
- tags: [
804
- 'Init'
805
- ],
806
- responses: {
807
- 200: {
808
- description: 'Initialization payload (jurisdiction, location, translations, branding, GVL)',
809
- content: {
810
- 'application/json': {
811
- schema: resolver(initOutputSchema)
812
- }
813
- }
814
- }
815
- }
816
- }), async (c)=>{
817
- const request = c.req.raw;
818
- const acceptLanguage = request.headers.get('accept-language') || 'en';
819
- const location = await getLocation(request, options);
820
- const jurisdiction = getJurisdiction(location, options);
821
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
822
- let gvl = null;
823
- if (options.advanced?.gvl?.enabled) {
824
- const language = translationsResult.language.split('-')[0] || 'en';
825
- const gvlResolver = createGVLResolver({
826
- appName: options.appName || 'c15t',
827
- bundled: options.advanced.gvl.bundled,
828
- cacheAdapter: options.advanced.cache?.adapter,
829
- vendorIds: options.advanced.gvl.vendorIds,
830
- endpoint: options.advanced.gvl.endpoint
831
- });
832
- gvl = await gvlResolver.get(language);
833
- }
834
- const customVendors = options.advanced?.gvl?.customVendors;
835
- const gpc = '1' === request.headers.get('sec-gpc');
836
- getMetrics()?.recordInit({
837
- jurisdiction,
838
- country: location?.countryCode ?? void 0,
839
- region: location?.regionCode ?? void 0,
840
- gpc
841
- });
842
- return c.json({
843
- jurisdiction,
844
- location,
845
- translations: translationsResult,
846
- branding: options.advanced?.branding || 'c15t',
847
- gvl,
848
- customVendors
849
- });
850
- });
851
- return app;
852
- };
853
- function getHeaders(headers) {
854
- if (!headers) return {
855
- countryCode: null,
856
- regionCode: null,
857
- acceptLanguage: null
858
- };
859
- const normalizeHeader = (value)=>{
860
- if (!value) return null;
861
- return Array.isArray(value) ? value[0] ?? null : value;
862
- };
863
- const countryCode = normalizeHeader(headers.get('x-c15t-country')) ?? normalizeHeader(headers.get('cf-ipcountry')) ?? normalizeHeader(headers.get('x-vercel-ip-country')) ?? normalizeHeader(headers.get('x-amz-cf-ipcountry')) ?? normalizeHeader(headers.get('x-country-code'));
864
- const regionCode = normalizeHeader(headers.get('x-c15t-region')) ?? normalizeHeader(headers.get('x-vercel-ip-country-region')) ?? normalizeHeader(headers.get('x-region-code'));
865
- const acceptLanguage = normalizeHeader(headers.get('accept-language'));
866
- return {
867
- countryCode,
868
- regionCode,
869
- acceptLanguage
870
- };
871
- }
872
- const statusHandler = async (c)=>{
873
- const ctx = c.get('c15tContext');
874
- const { countryCode, regionCode, acceptLanguage } = getHeaders(ctx.headers);
875
- const clientInfo = {
876
- ip: ctx.ipAddress ?? null,
877
- acceptLanguage,
878
- userAgent: ctx.userAgent ?? null,
879
- region: {
880
- countryCode,
881
- regionCode
882
- }
883
- };
884
- try {
885
- await ctx.db.findFirst('subject', {});
886
- return c.json({
887
- version: version_version,
888
- timestamp: new Date(),
889
- client: clientInfo
890
- });
891
- } catch (error) {
892
- ctx.logger.error('Database health check failed', {
893
- error
894
- });
895
- throw new HTTPException(503, {
896
- message: 'Database health check failed',
897
- cause: {
898
- code: 'SERVICE_UNAVAILABLE',
899
- error
900
- }
901
- });
902
- }
903
- };
904
- const createStatusRoute = ()=>{
905
- const app = new Hono();
906
- app.get('/', describeRoute({
907
- summary: 'Health check and API status',
908
- description: `Returns API version, timestamp, and client info (IP, region, user agent).
909
-
910
- Use for health checks, load balancer probes, and debugging. Performs a lightweight DB check; returns 503 if the database is unreachable.`,
911
- tags: [
912
- 'Status'
913
- ],
914
- responses: {
915
- 200: {
916
- description: 'API is healthy (version, timestamp, client info)',
917
- content: {
918
- 'application/json': {
919
- schema: resolver(statusOutputSchema)
920
- }
921
- }
922
- },
923
- 503: {
924
- description: 'Service unavailable (e.g. database unreachable)'
925
- }
926
- }
927
- }), statusHandler);
928
- return app;
929
- };
930
- const getSubjectHandler = async (c)=>{
931
- const ctx = c.get('c15tContext');
932
- const logger = ctx.logger;
933
- logger.info('Handling GET /subjects/:id request');
934
- const { db, registry } = ctx;
935
- const subjectId = c.req.param('id');
936
- const type = c.req.query('type');
937
- const typeFilter = type?.split(',').map((t)=>t.trim()) || [];
938
- logger.debug('Request parameters', {
939
- subjectId,
940
- typeFilter
941
- });
942
- try {
943
- const subject = await db.findFirst('subject', {
944
- where: (b)=>b('id', '=', subjectId)
945
- });
946
- if (!subject) throw new HTTPException(404, {
947
- message: 'Subject not found',
948
- cause: {
949
- code: 'SUBJECT_NOT_FOUND',
950
- subjectId
951
- }
952
- });
953
- const consents = await db.findMany('consent', {
954
- where: (b)=>b('subjectId', '=', subjectId)
955
- });
956
- const consentItems = await enrichConsents(consents, {
957
- db,
958
- registry
959
- });
960
- const filteredConsents = typeFilter.length > 0 ? consentItems.filter((consent)=>typeFilter.includes(consent.type)) : consentItems;
961
- const isValid = 0 === typeFilter.length || typeFilter.every((t)=>filteredConsents.some((consent)=>consent.type === t && consent.isLatestPolicy));
962
- return c.json({
963
- subject: {
964
- id: subject.id,
965
- externalId: subject.externalId ?? void 0,
966
- isIdentified: subject.isIdentified,
967
- createdAt: subject.createdAt
968
- },
969
- consents: filteredConsents,
970
- isValid
971
- });
972
- } catch (error) {
973
- logger.error('Error in GET /subjects/:id handler', {
974
- error: extractErrorMessage(error),
975
- errorType: error instanceof Error ? error.constructor.name : typeof error
976
- });
977
- if (error instanceof HTTPException) throw error;
978
- throw new HTTPException(500, {
979
- message: 'Internal server error',
980
- cause: {
981
- code: 'INTERNAL_SERVER_ERROR'
982
- }
983
- });
984
- }
985
- };
986
- const listSubjectsHandler = async (c)=>{
987
- const ctx = c.get('c15tContext');
988
- const logger = ctx.logger;
989
- logger.info('Handling GET /subjects request');
990
- const { db, registry } = ctx;
991
- if (!ctx.apiKeyAuthenticated) throw new HTTPException(401, {
992
- message: 'API key required. Use Authorization: Bearer <api_key>',
993
- cause: {
994
- code: 'UNAUTHORIZED'
995
- }
996
- });
997
- const externalId = c.req.query('externalId');
998
- if (!externalId) throw new HTTPException(422, {
999
- message: 'externalId query parameter is required',
1000
- cause: {
1001
- code: 'EXTERNAL_ID_REQUIRED'
1002
- }
1003
- });
1004
- logger.debug('Request parameters', {
1005
- externalId
1006
- });
1007
- try {
1008
- const subjects = await db.findMany('subject', {
1009
- where: (b)=>b('externalId', '=', externalId)
1010
- });
1011
- const subjectItems = await Promise.all(subjects.map(async (subject)=>{
1012
- const consents = await db.findMany('consent', {
1013
- where: (b)=>b('subjectId', '=', subject.id)
1014
- });
1015
- const consentItems = await enrichConsents(consents, {
1016
- db,
1017
- registry
1018
- });
1019
- return {
1020
- id: subject.id,
1021
- externalId: subject.externalId ?? externalId,
1022
- isIdentified: subject.isIdentified,
1023
- createdAt: subject.createdAt,
1024
- consents: consentItems
1025
- };
1026
- }));
1027
- logger.info('Found subjects for externalId', {
1028
- externalId,
1029
- count: subjectItems.length
1030
- });
1031
- return c.json({
1032
- subjects: subjectItems
1033
- });
1034
- } catch (error) {
1035
- logger.error('Error in GET /subjects handler', {
1036
- error: extractErrorMessage(error),
1037
- errorType: error instanceof Error ? error.constructor.name : typeof error
1038
- });
1039
- if (error instanceof HTTPException) throw error;
1040
- throw new HTTPException(500, {
1041
- message: 'Internal server error',
1042
- cause: {
1043
- code: 'INTERNAL_SERVER_ERROR'
1044
- }
1045
- });
1046
- }
1047
- };
1048
- const prefixes = {
1049
- auditLog: 'log',
1050
- consent: 'cns',
1051
- consentPolicy: 'pol',
1052
- consentPurpose: 'pur',
1053
- domain: 'dom',
1054
- subject: 'sub'
1055
- };
1056
- const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
1057
- function generateId(model) {
1058
- const buf = crypto.getRandomValues(new Uint8Array(20));
1059
- const prefix = prefixes[model];
1060
- const EPOCH_TIMESTAMP = 1700000000000;
1061
- const t = Date.now() - EPOCH_TIMESTAMP;
1062
- const high = Math.floor(t / 0x100000000);
1063
- const low = t >>> 0;
1064
- buf[0] = high >>> 24 & 255;
1065
- buf[1] = high >>> 16 & 255;
1066
- buf[2] = high >>> 8 & 255;
1067
- buf[3] = 255 & high;
1068
- buf[4] = low >>> 24 & 255;
1069
- buf[5] = low >>> 16 & 255;
1070
- buf[6] = low >>> 8 & 255;
1071
- buf[7] = 255 & low;
1072
- return `${prefix}_${b58.encode(buf)}`;
1073
- }
1074
- async function generateUniqueId(db, model, ctx, options = {}) {
1075
- const { maxRetries = 10, attempt = 0, baseDelay = 5 } = options;
1076
- if (attempt >= maxRetries) {
1077
- const error = new Error(`Failed to generate unique ID for ${model} after ${maxRetries} attempts`);
1078
- ctx?.logger?.error?.('ID generation failed', {
1079
- model,
1080
- maxRetries
1081
- });
1082
- throw error;
1083
- }
1084
- const id = generateId(model);
1085
- try {
1086
- const existing = await db.findFirst(model, {
1087
- where: (b)=>b('id', '=', id)
1088
- });
1089
- if (existing) {
1090
- ctx?.logger?.debug?.('ID conflict detected', {
1091
- id,
1092
- model,
1093
- attempt: attempt + 1,
1094
- maxRetries
1095
- });
1096
- const delay = Math.min(baseDelay * 2 ** attempt, 1000);
1097
- await new Promise((resolve)=>setTimeout(resolve, delay));
1098
- return generateUniqueId(db, model, ctx, {
1099
- maxRetries,
1100
- attempt: attempt + 1,
1101
- baseDelay
1102
- });
1103
- }
1104
- return id;
1105
- } catch (error) {
1106
- ctx?.logger?.error?.('Error checking ID uniqueness', {
1107
- error: error.message,
1108
- model,
1109
- attempt
1110
- });
1111
- if (attempt < maxRetries - 1) {
1112
- const delay = Math.min(baseDelay * 2 ** attempt, 2000);
1113
- await new Promise((resolve)=>setTimeout(resolve, delay));
1114
- return generateUniqueId(db, model, ctx, {
1115
- maxRetries,
1116
- attempt: attempt + 1,
1117
- baseDelay
1118
- });
1119
- }
1120
- throw error;
1121
- }
1122
- }
1123
- const patchSubjectHandler = async (c)=>{
1124
- const ctx = c.get('c15tContext');
1125
- const logger = ctx.logger;
1126
- logger.info('Handling PATCH /subjects/:id request');
1127
- const { db } = ctx;
1128
- const subjectId = c.req.param('id');
1129
- const body = await c.req.json();
1130
- const { externalId, identityProvider = 'external' } = body;
1131
- logger.debug('Request parameters', {
1132
- subjectId,
1133
- externalId,
1134
- identityProvider
1135
- });
1136
- try {
1137
- const subject = await db.findFirst('subject', {
1138
- where: (b)=>b('id', '=', subjectId)
1139
- });
1140
- if (!subject) throw new HTTPException(404, {
1141
- message: 'Subject not found',
1142
- cause: {
1143
- code: 'SUBJECT_NOT_FOUND',
1144
- subjectId
1145
- }
1146
- });
1147
- await db.transaction(async (tx)=>{
1148
- await tx.updateMany('subject', {
1149
- where: (b)=>b('id', '=', subjectId),
1150
- set: {
1151
- externalId,
1152
- identityProvider,
1153
- isIdentified: true,
1154
- updatedAt: new Date()
1155
- }
1156
- });
1157
- await tx.create('auditLog', {
1158
- id: await generateUniqueId(tx, 'auditLog', ctx),
1159
- subjectId,
1160
- entityType: 'subject',
1161
- entityId: subjectId,
1162
- actionType: 'identify_user',
1163
- ipAddress: ctx.ipAddress || null,
1164
- userAgent: ctx.userAgent || null,
1165
- changes: {
1166
- externalId: {
1167
- from: subject.externalId,
1168
- to: externalId
1169
- },
1170
- identityProvider: {
1171
- from: subject.identityProvider,
1172
- to: identityProvider
1173
- },
1174
- isIdentified: {
1175
- from: subject.isIdentified,
1176
- to: true
1177
- }
1178
- },
1179
- metadata: {
1180
- externalId,
1181
- identityProvider
1182
- }
1183
- });
1184
- });
1185
- logger.info('Subject linked to external ID', {
1186
- subjectId,
1187
- externalId,
1188
- identityProvider
1189
- });
1190
- getMetrics()?.recordSubjectLinked(identityProvider);
1191
- return c.json({
1192
- success: true,
1193
- subject: {
1194
- id: subjectId,
1195
- externalId,
1196
- isIdentified: true
1197
- }
1198
- });
1199
- } catch (error) {
1200
- logger.error('Error in PATCH /subjects/:id handler', {
1201
- error: extractErrorMessage(error),
1202
- errorType: error instanceof Error ? error.constructor.name : typeof error
1203
- });
1204
- if (error instanceof HTTPException) throw error;
1205
- throw new HTTPException(500, {
1206
- message: 'Internal server error',
1207
- cause: {
1208
- code: 'INTERNAL_SERVER_ERROR'
1209
- }
1210
- });
1211
- }
1212
- };
1213
- const postSubjectHandler = async (c)=>{
1214
- const ctx = c.get('c15tContext');
1215
- const logger = ctx.logger;
1216
- logger.info('Handling POST /subjects request');
1217
- const { db, registry } = ctx;
1218
- const input = await c.req.json();
1219
- const { type, subjectId, identityProvider, externalSubjectId, domain, metadata, givenAt: givenAtEpoch } = input;
1220
- const preferences = 'preferences' in input ? input.preferences : void 0;
1221
- const givenAt = new Date(givenAtEpoch);
1222
- logger.debug('Request parameters', {
1223
- type,
1224
- subjectId,
1225
- identityProvider,
1226
- externalSubjectId,
1227
- domain
1228
- });
1229
- try {
1230
- const subject = await registry.findOrCreateSubject({
1231
- subjectId,
1232
- externalSubjectId,
1233
- identityProvider,
1234
- ipAddress: ctx.ipAddress
1235
- });
1236
- if (!subject) throw new HTTPException(500, {
1237
- message: 'Failed to create subject',
1238
- cause: {
1239
- code: 'SUBJECT_CREATION_FAILED',
1240
- subjectId
1241
- }
1242
- });
1243
- logger.debug('Subject found/created', {
1244
- subjectId: subject.id
1245
- });
1246
- const domainRecord = await registry.findOrCreateDomain(domain);
1247
- if (!domainRecord) throw new HTTPException(500, {
1248
- message: 'Failed to create domain',
1249
- cause: {
1250
- code: 'DOMAIN_CREATION_FAILED',
1251
- domain
1252
- }
1253
- });
1254
- let policyId;
1255
- let purposeIds = [];
1256
- const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
1257
- if (inputPolicyId) {
1258
- policyId = inputPolicyId;
1259
- const policy = await registry.findConsentPolicyById(inputPolicyId);
1260
- if (!policy) throw new HTTPException(404, {
1261
- message: 'Policy not found',
1262
- cause: {
1263
- code: 'POLICY_NOT_FOUND',
1264
- policyId,
1265
- type
1266
- }
1267
- });
1268
- if (!policy.isActive) throw new HTTPException(400, {
1269
- message: 'Policy is inactive',
1270
- cause: {
1271
- code: 'POLICY_INACTIVE',
1272
- policyId,
1273
- type
1274
- }
1275
- });
1276
- } else {
1277
- const policy = await registry.findOrCreatePolicy(type);
1278
- if (!policy) throw new HTTPException(500, {
1279
- message: 'Failed to create policy',
1280
- cause: {
1281
- code: 'POLICY_CREATION_FAILED',
1282
- type
1283
- }
1284
- });
1285
- policyId = policy.id;
1286
- }
1287
- if (preferences) {
1288
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1289
- logger.debug('Consented purposes', {
1290
- consentedPurposes
1291
- });
1292
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1293
- const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
1294
- logger.debug('Filtered purposes', {
1295
- purposes
1296
- });
1297
- if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
1298
- consentedPurposes
1299
- });
1300
- purposeIds = purposes;
1301
- }
1302
- const existingConsent = await db.findFirst('consent', {
1303
- where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
1304
- });
1305
- if (existingConsent) {
1306
- logger.debug('Duplicate consent detected, returning existing record', {
1307
- consentId: existingConsent.id
1308
- });
1309
- return c.json({
1310
- subjectId: subject.id,
1311
- consentId: existingConsent.id,
1312
- domainId: domainRecord.id,
1313
- domain: domainRecord.name,
1314
- type,
1315
- metadata,
1316
- uiSource: input.uiSource,
1317
- givenAt: existingConsent.givenAt
1318
- });
1319
- }
1320
- const result = await db.transaction(async (tx)=>{
1321
- logger.debug('Creating consent record', {
1322
- subjectId: subject.id,
1323
- domainId: domainRecord.id,
1324
- policyId,
1325
- purposeIds
1326
- });
1327
- const consentRecord = await tx.create('consent', {
1328
- id: await generateUniqueId(tx, 'consent', ctx),
1329
- subjectId: subject.id,
1330
- domainId: domainRecord.id,
1331
- policyId,
1332
- purposeIds: {
1333
- json: purposeIds
1334
- },
1335
- metadata: metadata ? {
1336
- json: metadata
1337
- } : void 0,
1338
- ipAddress: ctx.ipAddress,
1339
- userAgent: ctx.userAgent,
1340
- jurisdiction: input.jurisdiction,
1341
- jurisdictionModel: input.jurisdictionModel,
1342
- tcString: input.tcString,
1343
- uiSource: input.uiSource,
1344
- givenAt
1345
- });
1346
- logger.debug('Created consent', {
1347
- consentRecord: consentRecord.id
1348
- });
1349
- if (!consentRecord) throw new HTTPException(500, {
1350
- message: 'Failed to create consent',
1351
- cause: {
1352
- code: 'CONSENT_CREATION_FAILED',
1353
- subjectId: subject.id,
1354
- domain
1355
- }
1356
- });
1357
- return {
1358
- consent: consentRecord
1359
- };
1360
- });
1361
- const metrics = getMetrics();
1362
- if (metrics) {
1363
- const jurisdiction = input.jurisdiction;
1364
- metrics.recordConsentCreated({
1365
- type,
1366
- jurisdiction
1367
- });
1368
- const hasAccepted = preferences && Object.values(preferences).some(Boolean);
1369
- if (hasAccepted) metrics.recordConsentAccepted({
1370
- type,
1371
- jurisdiction
1372
- });
1373
- else metrics.recordConsentRejected({
1374
- type,
1375
- jurisdiction
1376
- });
1377
- }
1378
- return c.json({
1379
- subjectId: subject.id,
1380
- consentId: result.consent.id,
1381
- domainId: domainRecord.id,
1382
- domain: domainRecord.name,
1383
- type,
1384
- metadata,
1385
- uiSource: input.uiSource,
1386
- givenAt: result.consent.givenAt
1387
- });
1388
- } catch (error) {
1389
- logger.error('Error in POST /subjects handler', {
1390
- error: extractErrorMessage(error),
1391
- errorType: error instanceof Error ? error.constructor.name : typeof error
1392
- });
1393
- if (error instanceof HTTPException) throw error;
1394
- throw new HTTPException(500, {
1395
- message: 'Internal server error',
1396
- cause: {
1397
- code: 'INTERNAL_SERVER_ERROR'
1398
- }
1399
- });
1400
- }
1401
- };
1402
- const createSubjectRoutes = ()=>{
1403
- const app = new Hono();
1404
- app.get('/:id', describeRoute({
1405
- summary: 'Get subject consent status',
1406
- description: `Returns the subject's consent status for this device. Use to check if the subject has valid consent for given policy types.
1407
-
1408
- **Query:** \`type\` – Filter by consent type(s), comma-separated (e.g. \`privacy_policy,cookie_banner\`).
1409
-
1410
- **Response:** \`subject\`, \`consents\` (matching filter), \`isValid\` (valid consent for requested type(s)).`,
1411
- tags: [
1412
- 'Subject',
1413
- 'Consent'
1414
- ],
1415
- responses: {
1416
- 200: {
1417
- description: 'Subject and consent records for the requested type(s)',
1418
- content: {
1419
- 'application/json': {
1420
- schema: resolver(getSubjectOutputSchema)
1421
- }
1422
- }
1423
- },
1424
- 404: {
1425
- description: 'Subject not found for the given ID'
1426
- }
1427
- }
1428
- }), validator('param', getSubjectInputSchema), getSubjectHandler);
1429
- app.post('/', describeRoute({
1430
- summary: 'Record consent for a subject',
1431
- description: `Creates a new consent record (append-only). Creates the subject if it does not exist.
1432
-
1433
- **Request body by \`type\`:**
1434
- - \`cookie_banner\` – Requires \`preferences\` object
1435
- - \`privacy_policy\`, \`dpa\`, \`terms_and_conditions\` – Optional \`policyId\`
1436
- - \`marketing_communications\`, \`age_verification\`, \`other\` – Optional \`preferences\``,
1437
- tags: [
1438
- 'Subject',
1439
- 'Consent'
1440
- ],
1441
- responses: {
1442
- 200: {
1443
- description: 'Consent recorded; subject and consent in response',
1444
- content: {
1445
- 'application/json': {
1446
- schema: resolver(postSubjectOutputSchema)
1447
- }
1448
- }
1449
- },
1450
- 422: {
1451
- description: 'Invalid request body (schema or validation failed)'
1452
- }
1453
- }
1454
- }), validator('json', postSubjectInputSchema), postSubjectHandler);
1455
- app.patch('/:id', describeRoute({
1456
- summary: 'Link external ID to subject',
1457
- description: 'Associates an external user ID with an existing subject (e.g. after login). Enables cross-device consent sync.',
1458
- tags: [
1459
- 'Subject'
1460
- ],
1461
- responses: {
1462
- 200: {
1463
- description: 'Subject updated with external ID',
1464
- content: {
1465
- 'application/json': {
1466
- schema: resolver(patchSubjectOutputSchema)
1467
- }
1468
- }
1469
- },
1470
- 404: {
1471
- description: 'Subject not found for the given ID'
1472
- }
1473
- }
1474
- }), validator('param', object({
1475
- id: subjectIdSchema
1476
- })), validator('json', object({
1477
- externalId: string(),
1478
- identityProvider: optional(string())
1479
- })), patchSubjectHandler);
1480
- app.get('/', describeRoute({
1481
- summary: 'List subjects by external ID (API key required)',
1482
- description: 'Returns all subjects linked to the given external ID. Requires Bearer token (API key). Use for server-side consent lookups.',
1483
- tags: [
1484
- 'Subject'
1485
- ],
1486
- security: [
1487
- {
1488
- bearerAuth: []
1489
- }
1490
- ],
1491
- responses: {
1492
- 200: {
1493
- description: 'List of subjects for the external ID',
1494
- content: {
1495
- 'application/json': {
1496
- schema: resolver(listSubjectsOutputSchema)
1497
- }
1498
- }
1499
- },
1500
- 401: {
1501
- description: 'Missing or invalid API key'
1502
- }
1503
- }
1504
- }), validator('query', listSubjectsQuerySchema), listSubjectsHandler);
1505
- return app;
1506
- };
1507
- export { createConsentRoutes, createInitRoute, createStatusRoute, createSubjectRoutes };
1
+ export { createConsentRoutes, createInitRoute, createStatusRoute, createSubjectRoutes } from "./915.js";