@c15t/backend 2.0.0-rc.4 → 2.0.0-rc.6

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 (327) hide show
  1. package/dist/302.js +473 -0
  2. package/dist/364.js +1140 -0
  3. package/dist/583.js +540 -0
  4. package/dist/cache.cjs +1 -1
  5. package/dist/cache.js +4 -415
  6. package/dist/core.cjs +849 -96
  7. package/dist/core.js +147 -1817
  8. package/dist/db/adapters/drizzle.cjs +1 -1
  9. package/dist/db/adapters/drizzle.js +1 -2
  10. package/dist/db/adapters/kysely.cjs +1 -1
  11. package/dist/db/adapters/kysely.js +1 -2
  12. package/dist/db/adapters/mongo.cjs +1 -1
  13. package/dist/db/adapters/mongo.js +1 -2
  14. package/dist/db/adapters/prisma.cjs +1 -1
  15. package/dist/db/adapters/prisma.js +1 -2
  16. package/dist/db/adapters/typeorm.cjs +1 -1
  17. package/dist/db/adapters/typeorm.js +1 -2
  18. package/dist/db/adapters.cjs +1 -1
  19. package/dist/db/migrator.cjs +1 -1
  20. package/dist/db/schema.cjs +38 -1
  21. package/dist/db/schema.js +33 -2
  22. package/dist/define-config.cjs +1 -1
  23. package/dist/edge.cjs +1106 -0
  24. package/dist/edge.js +190 -0
  25. package/dist/router.cjs +629 -81
  26. package/dist/router.js +1 -1509
  27. package/dist/types/index.cjs +1 -1
  28. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  29. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  30. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  31. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  32. package/{dist → dist-types}/cache/gvl-resolver.d.ts +1 -2
  33. package/{dist → dist-types}/cache/index.d.ts +0 -1
  34. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  35. package/{dist → dist-types}/cache/types.d.ts +0 -1
  36. package/{dist → dist-types}/core.d.ts +8 -1
  37. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  38. package/{dist → dist-types}/db/registry/consent-policy.d.ts +0 -1
  39. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  40. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  41. package/{dist → dist-types}/db/registry/index.d.ts +22 -2
  42. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  43. package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
  44. package/{dist → dist-types}/db/registry/types.d.ts +1 -2
  45. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  46. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  47. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  48. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  49. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  50. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  51. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +2 -3
  52. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  53. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -1
  54. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
  55. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +2 -3
  56. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +2 -3
  57. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +2 -3
  58. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +6 -3
  59. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +2 -3
  60. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +432 -17
  61. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  62. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +2 -3
  63. package/{dist → dist-types}/db/schema/index.d.ts +862 -33
  64. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  65. package/{dist → dist-types}/define-config.d.ts +0 -1
  66. package/dist-types/edge/index.d.ts +5 -0
  67. package/dist-types/edge/init-handler.d.ts +38 -0
  68. package/dist-types/edge/resolve-consent.d.ts +80 -0
  69. package/dist-types/edge/types.d.ts +13 -0
  70. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  71. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  72. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  73. package/{dist → dist-types}/handlers/init/index.d.ts +4 -5
  74. package/dist-types/handlers/init/policy.d.ts +26 -0
  75. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  76. package/dist-types/handlers/init/translations.d.ts +48 -0
  77. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  78. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  79. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  80. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +0 -1
  81. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  82. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +0 -1
  83. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
  84. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  85. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
  86. package/{dist → dist-types}/init.d.ts +0 -1
  87. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  88. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  89. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  90. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  91. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +1 -2
  92. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  93. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  94. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  95. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  96. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  97. package/dist-types/policies/builder.d.ts +127 -0
  98. package/dist-types/policies/defaults.d.ts +2 -0
  99. package/dist-types/policies/matchers.d.ts +3 -0
  100. package/{dist → dist-types}/router.d.ts +0 -1
  101. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  102. package/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
  103. package/{dist → dist-types}/routes/init.d.ts +0 -1
  104. package/{dist → dist-types}/routes/status.d.ts +0 -1
  105. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  106. package/{dist → dist-types}/types/api.d.ts +0 -1
  107. package/{dist → dist-types}/types/index.d.ts +110 -6
  108. package/dist-types/utils/background.d.ts +6 -0
  109. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +0 -1
  110. package/{dist → dist-types}/utils/env.d.ts +0 -1
  111. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  112. package/{dist → dist-types}/utils/instrumentation.d.ts +0 -1
  113. package/{dist → dist-types}/utils/logger.d.ts +1 -2
  114. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  115. package/dist-types/version.d.ts +1 -0
  116. package/docs/README.md +49 -0
  117. package/docs/api/configuration.md +197 -0
  118. package/docs/api/endpoints.md +211 -0
  119. package/docs/guides/caching.md +85 -0
  120. package/docs/guides/database-setup.md +128 -0
  121. package/docs/guides/edge-deployment.md +248 -0
  122. package/docs/guides/framework-integration.md +142 -0
  123. package/docs/guides/iab-tcf.md +89 -0
  124. package/docs/guides/observability.md +96 -0
  125. package/docs/guides/policy-packs.md +396 -0
  126. package/docs/quickstart.md +129 -0
  127. package/package.json +45 -31
  128. package/.turbo/turbo-build.log +0 -49
  129. package/CHANGELOG.md +0 -123
  130. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  131. package/dist/cache/adapters/index.d.ts.map +0 -1
  132. package/dist/cache/adapters/memory.d.ts.map +0 -1
  133. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  134. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  135. package/dist/cache/index.d.ts.map +0 -1
  136. package/dist/cache/keys.d.ts.map +0 -1
  137. package/dist/cache/types.d.ts.map +0 -1
  138. package/dist/core.d.ts.map +0 -1
  139. package/dist/db/adapters/drizzle.d.ts +0 -2
  140. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  141. package/dist/db/adapters/index.d.ts +0 -2
  142. package/dist/db/adapters/index.d.ts.map +0 -1
  143. package/dist/db/adapters/kysely.d.ts +0 -2
  144. package/dist/db/adapters/kysely.d.ts.map +0 -1
  145. package/dist/db/adapters/mongo.d.ts +0 -2
  146. package/dist/db/adapters/mongo.d.ts.map +0 -1
  147. package/dist/db/adapters/prisma.d.ts +0 -2
  148. package/dist/db/adapters/prisma.d.ts.map +0 -1
  149. package/dist/db/adapters/typeorm.d.ts +0 -2
  150. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  151. package/dist/db/migrator/index.d.ts.map +0 -1
  152. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  153. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  154. package/dist/db/registry/domain.d.ts.map +0 -1
  155. package/dist/db/registry/index.d.ts.map +0 -1
  156. package/dist/db/registry/subject.d.ts.map +0 -1
  157. package/dist/db/registry/types.d.ts.map +0 -1
  158. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  159. package/dist/db/registry/utils.d.ts.map +0 -1
  160. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  161. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  162. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  163. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  164. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  165. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  166. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  167. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  168. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  169. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  170. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  171. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  172. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  173. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  174. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  175. package/dist/db/schema/index.d.ts.map +0 -1
  176. package/dist/db/tenant-scope.d.ts.map +0 -1
  177. package/dist/define-config.d.ts.map +0 -1
  178. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  179. package/dist/handlers/consent/index.d.ts +0 -12
  180. package/dist/handlers/consent/index.d.ts.map +0 -1
  181. package/dist/handlers/init/geo.d.ts.map +0 -1
  182. package/dist/handlers/init/index.d.ts.map +0 -1
  183. package/dist/handlers/init/translations.d.ts +0 -26
  184. package/dist/handlers/init/translations.d.ts.map +0 -1
  185. package/dist/handlers/status/index.d.ts +0 -7
  186. package/dist/handlers/status/index.d.ts.map +0 -1
  187. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  188. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  189. package/dist/handlers/subject/index.d.ts +0 -10
  190. package/dist/handlers/subject/index.d.ts.map +0 -1
  191. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  192. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  193. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  194. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  195. package/dist/init.d.ts.map +0 -1
  196. package/dist/middleware/auth/index.d.ts.map +0 -1
  197. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  198. package/dist/middleware/cors/cors.d.ts.map +0 -1
  199. package/dist/middleware/cors/index.d.ts +0 -30
  200. package/dist/middleware/cors/index.d.ts.map +0 -1
  201. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  202. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  203. package/dist/middleware/openapi/config.d.ts.map +0 -1
  204. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  205. package/dist/middleware/openapi/index.d.ts +0 -12
  206. package/dist/middleware/openapi/index.d.ts.map +0 -1
  207. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  208. package/dist/router.d.ts.map +0 -1
  209. package/dist/routes/consent.d.ts.map +0 -1
  210. package/dist/routes/index.d.ts +0 -10
  211. package/dist/routes/index.d.ts.map +0 -1
  212. package/dist/routes/init.d.ts.map +0 -1
  213. package/dist/routes/status.d.ts.map +0 -1
  214. package/dist/routes/subject.d.ts.map +0 -1
  215. package/dist/types/api.d.ts.map +0 -1
  216. package/dist/types/index.d.ts.map +0 -1
  217. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  218. package/dist/utils/env.d.ts.map +0 -1
  219. package/dist/utils/extract-error-message.d.ts.map +0 -1
  220. package/dist/utils/index.d.ts +0 -4
  221. package/dist/utils/index.d.ts.map +0 -1
  222. package/dist/utils/instrumentation.d.ts.map +0 -1
  223. package/dist/utils/logger.d.ts.map +0 -1
  224. package/dist/utils/metrics.d.ts.map +0 -1
  225. package/dist/version.d.ts +0 -2
  226. package/dist/version.d.ts.map +0 -1
  227. package/knip.json +0 -31
  228. package/rslib.config.ts +0 -93
  229. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  230. package/src/cache/adapters/index.ts +0 -22
  231. package/src/cache/adapters/memory.ts +0 -111
  232. package/src/cache/adapters/upstash-redis.ts +0 -113
  233. package/src/cache/gvl-resolver.ts +0 -289
  234. package/src/cache/index.ts +0 -34
  235. package/src/cache/keys.ts +0 -68
  236. package/src/cache/types.ts +0 -66
  237. package/src/core.ts +0 -369
  238. package/src/db/migrator/index.ts +0 -80
  239. package/src/db/registry/consent-policy.test.ts +0 -451
  240. package/src/db/registry/consent-policy.ts +0 -82
  241. package/src/db/registry/consent-purpose.test.ts +0 -428
  242. package/src/db/registry/consent-purpose.ts +0 -61
  243. package/src/db/registry/domain.test.ts +0 -445
  244. package/src/db/registry/domain.ts +0 -91
  245. package/src/db/registry/index.ts +0 -14
  246. package/src/db/registry/subject.test.ts +0 -371
  247. package/src/db/registry/subject.ts +0 -126
  248. package/src/db/registry/types.ts +0 -10
  249. package/src/db/registry/utils/generate-id.test.ts +0 -216
  250. package/src/db/registry/utils/generate-id.ts +0 -133
  251. package/src/db/registry/utils.ts +0 -133
  252. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  253. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  254. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  255. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  256. package/src/db/schema/1.0.0/consent.ts +0 -20
  257. package/src/db/schema/1.0.0/domain.ts +0 -12
  258. package/src/db/schema/1.0.0/index.ts +0 -48
  259. package/src/db/schema/1.0.0/subject.ts +0 -11
  260. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  261. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  262. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  263. package/src/db/schema/2.0.0/consent.ts +0 -28
  264. package/src/db/schema/2.0.0/domain.ts +0 -12
  265. package/src/db/schema/2.0.0/index.ts +0 -47
  266. package/src/db/schema/2.0.0/subject.ts +0 -13
  267. package/src/db/schema/index.ts +0 -15
  268. package/src/db/tenant-scope.test.ts +0 -747
  269. package/src/db/tenant-scope.ts +0 -103
  270. package/src/define-config.ts +0 -19
  271. package/src/handlers/consent/check.handler.ts +0 -126
  272. package/src/handlers/init/geo.test.ts +0 -317
  273. package/src/handlers/init/geo.ts +0 -195
  274. package/src/handlers/init/index.test.ts +0 -205
  275. package/src/handlers/init/index.ts +0 -114
  276. package/src/handlers/init/translations.test.ts +0 -121
  277. package/src/handlers/init/translations.ts +0 -69
  278. package/src/handlers/status/status.handler.test.ts +0 -155
  279. package/src/handlers/status/status.handler.ts +0 -51
  280. package/src/handlers/subject/get.handler.ts +0 -92
  281. package/src/handlers/subject/list.handler.ts +0 -92
  282. package/src/handlers/subject/patch.handler.ts +0 -119
  283. package/src/handlers/subject/post.handler.test.ts +0 -294
  284. package/src/handlers/subject/post.handler.ts +0 -268
  285. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  286. package/src/handlers/utils/consent-enrichment.ts +0 -218
  287. package/src/init.test.ts +0 -122
  288. package/src/init.ts +0 -88
  289. package/src/middleware/auth/index.ts +0 -11
  290. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  291. package/src/middleware/auth/validate-api-key.ts +0 -107
  292. package/src/middleware/cors/cors.test.ts +0 -135
  293. package/src/middleware/cors/cors.ts +0 -186
  294. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  295. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  296. package/src/middleware/cors/process-cors.ts +0 -91
  297. package/src/middleware/openapi/config.ts +0 -29
  298. package/src/middleware/openapi/handlers.ts +0 -34
  299. package/src/middleware/process-ip/index.test.ts +0 -193
  300. package/src/middleware/process-ip/index.ts +0 -199
  301. package/src/router.ts +0 -15
  302. package/src/routes/consent.ts +0 -52
  303. package/src/routes/init.ts +0 -105
  304. package/src/routes/status.ts +0 -46
  305. package/src/routes/subject.ts +0 -152
  306. package/src/types/api.ts +0 -48
  307. package/src/types/index.ts +0 -391
  308. package/src/utils/create-telemetry-options.test.ts +0 -286
  309. package/src/utils/create-telemetry-options.ts +0 -229
  310. package/src/utils/env.ts +0 -84
  311. package/src/utils/extract-error-message.ts +0 -21
  312. package/src/utils/instrumentation.test.ts +0 -183
  313. package/src/utils/instrumentation.ts +0 -194
  314. package/src/utils/logger.ts +0 -41
  315. package/src/utils/metrics.test.ts +0 -311
  316. package/src/utils/metrics.ts +0 -402
  317. package/src/utils/telemetry-pii.test.ts +0 -323
  318. package/src/version.ts +0 -2
  319. package/tsconfig.json +0 -11
  320. package/vitest.config.ts +0 -28
  321. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  322. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  323. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  324. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  325. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  326. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  327. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/core.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { createLogger as logger_createLogger } from "@c15t/logger";
2
- import { SpanKind, SpanStatusCode as api_SpanStatusCode, context as api_context, metrics as api_metrics, trace } from "@opentelemetry/api";
2
+ import { SpanStatusCode } from "@opentelemetry/api";
3
3
  import { apiReference } from "@scalar/hono-api-reference";
4
4
  import { Hono } from "hono";
5
5
  import { cors } from "hono/cors";
6
6
  import { HTTPException } from "hono/http-exception";
7
- import { describeRoute, openAPIRouteHandler, resolver, validator } from "hono-openapi";
7
+ import { openAPIRouteHandler } from "hono-openapi";
8
8
  import base_x from "base-x";
9
- import { fumadb } from "fumadb";
10
- import { column, idColumn, schema, table as schema_table } from "fumadb/schema";
11
- import { checkConsentOutputSchema, checkConsentQuerySchema, getSubjectInputSchema, getSubjectOutputSchema, initOutputSchema, listSubjectsOutputSchema, listSubjectsQuerySchema, patchSubjectOutputSchema, postSubjectInputSchema, postSubjectOutputSchema, statusOutputSchema, subjectIdSchema } from "@c15t/schema";
12
- import { deepMergeTranslations, selectLanguage } from "@c15t/translations";
13
- import { baseTranslations } from "@c15t/translations/all";
14
- import { object, optional, string } from "valibot";
9
+ import { compactDefined, dedupeTrimmedStrings, policyPackPresets } from "@c15t/schema";
10
+ import { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, policyMatchers } from "@c15t/schema/types";
11
+ import { version as version_version, extractErrorMessage, withDatabaseSpan, getTraceContext as create_telemetry_options_getTraceContext, createRequestSpan, handleSpanError, createTelemetryOptions, getMetrics, isTelemetryEnabled, withSpanContext } from "./302.js";
12
+ import { validateMessages, inspectPolicies as policy_inspectPolicies } from "./583.js";
13
+ import { DB } from "./db/schema.js";
14
+ import { createStatusRoute, createInitRoute, createConsentRoutes, createSubjectRoutes } from "./364.js";
15
15
  function extractBearerToken(authHeader) {
16
16
  if (!authHeader) return null;
17
17
  const parts = authHeader.split(' ');
@@ -137,8 +137,7 @@ function createCORSOptions(trustedOrigins) {
137
137
  ]
138
138
  };
139
139
  }
140
- const version_version = '2.0.0-rc.4';
141
- const config_createOpenAPIConfig = (options)=>({
140
+ const createOpenAPIConfig = (options)=>({
142
141
  enabled: options.openapi?.enabled !== false,
143
142
  specPath: '/spec.json',
144
143
  docsPath: '/docs',
@@ -253,328 +252,13 @@ function getIpAddress(req, options) {
253
252
  }
254
253
  return null;
255
254
  }
256
- function extractErrorMessage(error) {
257
- if (error instanceof AggregateError && error.errors?.length > 0) {
258
- const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
259
- return `AggregateError: ${inner}`;
260
- }
261
- if (error instanceof Error) return error.message || error.name;
262
- return String(error);
263
- }
264
- let cachedConfig = null;
265
- let cachedDefaultAttributes = {};
266
- function createTelemetryOptions(appName = 'c15t', telemetryConfig, tenantId) {
267
- const defaultAttributes = {
268
- ...telemetryConfig?.defaultAttributes || {},
269
- 'service.name': String(appName),
270
- 'service.version': version_version
271
- };
272
- if (tenantId) defaultAttributes['tenant.id'] = tenantId;
273
- const config = {
274
- enabled: telemetryConfig?.enabled ?? false,
275
- tracer: telemetryConfig?.tracer,
276
- meter: telemetryConfig?.meter,
277
- defaultAttributes
278
- };
279
- cachedConfig = config;
280
- cachedDefaultAttributes = defaultAttributes;
281
- return config;
282
- }
283
- function isTelemetryEnabled(options) {
284
- if (options) return options.telemetry?.enabled === true;
285
- return cachedConfig?.enabled === true;
286
- }
287
- const getTracer = (options)=>{
288
- if (!isTelemetryEnabled(options)) return trace.getTracer('c15t-noop');
289
- const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
290
- if (tracer) return tracer;
291
- return trace.getTracer(options?.appName ?? 'c15t');
292
- };
293
- const getMeter = (options)=>{
294
- if (!isTelemetryEnabled(options)) return api_metrics.getMeter('c15t-noop');
295
- const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
296
- if (meter) return meter;
297
- return api_metrics.getMeter(options?.appName ?? 'c15t');
298
- };
299
- function getDefaultAttributes() {
300
- return cachedDefaultAttributes;
301
- }
302
- const createRequestSpan = (method, path, options)=>{
303
- if (!isTelemetryEnabled(options)) return null;
304
- const tracer = getTracer(options);
305
- const defaultAttrs = options?.telemetry?.defaultAttributes || getDefaultAttributes();
306
- const span = tracer.startSpan(`${method} ${path}`, {
307
- attributes: {
308
- 'http.method': method,
309
- ...defaultAttrs
310
- }
311
- });
312
- return span;
313
- };
314
- const handleSpanError = (span, error)=>{
315
- span.setStatus({
316
- code: api_SpanStatusCode.ERROR,
317
- message: extractErrorMessage(error)
318
- });
319
- if (error instanceof Error) span.setAttribute('error.type', error.name);
320
- };
321
- function create_telemetry_options_getTraceContext() {
322
- const activeSpan = trace.getActiveSpan();
323
- if (!activeSpan) return null;
324
- const spanContext = activeSpan.spanContext();
325
- if (!spanContext) return null;
326
- return {
327
- traceId: spanContext.traceId,
328
- spanId: spanContext.spanId
329
- };
330
- }
331
- const withSpanContext = async (span, operation)=>api_context["with"](trace.setSpan(api_context.active(), span), operation);
332
- async function executeWithSpan(span, operation) {
333
- try {
334
- const result = await withSpanContext(span, operation);
335
- span.setStatus({
336
- code: api_SpanStatusCode.OK
337
- });
338
- return result;
339
- } catch (error) {
340
- handleSpanError(span, error);
341
- throw error;
342
- } finally{
343
- span.end();
344
- }
345
- }
346
- function resolveDefaultAttributes(options) {
347
- return options?.telemetry?.defaultAttributes || getDefaultAttributes();
348
- }
349
- async function withDatabaseSpan(attributes, operation, options) {
350
- if (!isTelemetryEnabled(options)) return operation();
351
- const tracer = getTracer(options);
352
- const spanName = `db.${attributes.entity}.${attributes.operation}`;
353
- const span = tracer.startSpan(spanName, {
354
- kind: SpanKind.CLIENT,
355
- attributes: {
356
- 'db.system': 'c15t',
357
- 'db.operation': attributes.operation,
358
- 'db.entity': attributes.entity,
359
- ...resolveDefaultAttributes(options),
360
- ...Object.fromEntries(Object.entries(attributes).filter(([key])=>![
361
- 'operation',
362
- 'entity'
363
- ].includes(key)))
364
- }
365
- });
366
- return executeWithSpan(span, operation);
367
- }
368
- async function withExternalSpan(attributes, operation, options) {
369
- if (!isTelemetryEnabled(options)) return operation();
370
- const tracer = getTracer(options);
371
- const url = new URL(attributes.url);
372
- const spanName = `HTTP ${attributes.method} ${url.hostname}`;
373
- const span = tracer.startSpan(spanName, {
374
- kind: SpanKind.CLIENT,
375
- attributes: {
376
- 'http.method': attributes.method,
377
- 'http.url': `${url.origin}${url.pathname}`,
378
- 'http.host': url.hostname,
379
- ...resolveDefaultAttributes(options),
380
- ...Object.fromEntries(Object.entries(attributes).filter(([key])=>![
381
- 'url',
382
- 'method'
383
- ].includes(key)))
384
- }
385
- });
386
- return executeWithSpan(span, operation);
387
- }
388
- async function withCacheSpan(operation, layer, fn, options) {
389
- if (!isTelemetryEnabled(options)) return fn();
390
- const tracer = getTracer(options);
391
- const spanName = `cache.${layer}.${operation}`;
392
- const span = tracer.startSpan(spanName, {
393
- kind: SpanKind.CLIENT,
394
- attributes: {
395
- 'cache.operation': operation,
396
- 'cache.layer': layer,
397
- ...resolveDefaultAttributes(options)
398
- }
399
- });
400
- return executeWithSpan(span, fn);
401
- }
402
- function sanitizeAttributes(attrs) {
403
- return Object.fromEntries(Object.entries(attrs).filter(([_, v])=>null != v));
404
- }
405
- function createMetrics(meter) {
406
- const consentCreated = meter.createCounter('c15t.consent.created', {
407
- description: 'Number of consent submissions',
408
- unit: '1'
409
- });
410
- const consentAccepted = meter.createCounter('c15t.consent.accepted', {
411
- description: 'Number of consents accepted',
412
- unit: '1'
413
- });
414
- const consentRejected = meter.createCounter('c15t.consent.rejected', {
415
- description: 'Number of consents rejected',
416
- unit: '1'
417
- });
418
- const subjectCreated = meter.createCounter('c15t.subject.created', {
419
- description: 'Number of new subjects created',
420
- unit: '1'
421
- });
422
- const subjectLinked = meter.createCounter('c15t.subject.linked', {
423
- description: 'Number of subjects linked to external ID',
424
- unit: '1'
425
- });
426
- const consentCheckCount = meter.createCounter('c15t.consent_check.count', {
427
- description: 'Number of cross-device consent checks',
428
- unit: '1'
429
- });
430
- const initCount = meter.createCounter('c15t.init.count', {
431
- description: 'Number of init endpoint calls',
432
- unit: '1'
433
- });
434
- const httpRequestDuration = meter.createHistogram('c15t.http.request.duration', {
435
- description: 'HTTP request latency',
436
- unit: 'ms'
437
- });
438
- const httpRequestCount = meter.createCounter('c15t.http.request.count', {
439
- description: 'Number of HTTP requests',
440
- unit: '1'
441
- });
442
- const httpErrorCount = meter.createCounter('c15t.http.error.count', {
443
- description: 'Number of HTTP errors',
444
- unit: '1'
445
- });
446
- const dbQueryDuration = meter.createHistogram('c15t.db.query.duration', {
447
- description: 'Database query latency',
448
- unit: 'ms'
449
- });
450
- const dbQueryCount = meter.createCounter('c15t.db.query.count', {
451
- description: 'Number of database queries',
452
- unit: '1'
453
- });
454
- const dbErrorCount = meter.createCounter('c15t.db.error.count', {
455
- description: 'Number of database errors',
456
- unit: '1'
457
- });
458
- const cacheHit = meter.createCounter('c15t.cache.hit', {
459
- description: 'Number of cache hits',
460
- unit: '1'
461
- });
462
- const cacheMiss = meter.createCounter('c15t.cache.miss', {
463
- description: 'Number of cache misses',
464
- unit: '1'
465
- });
466
- const cacheLatency = meter.createHistogram('c15t.cache.latency', {
467
- description: 'Cache operation latency',
468
- unit: 'ms'
469
- });
470
- const gvlFetchDuration = meter.createHistogram('c15t.gvl.fetch.duration', {
471
- description: 'GVL fetch latency',
472
- unit: 'ms'
473
- });
474
- const gvlFetchCount = meter.createCounter('c15t.gvl.fetch.count', {
475
- description: 'Number of GVL fetches',
476
- unit: '1'
477
- });
478
- const gvlFetchError = meter.createCounter('c15t.gvl.fetch.error', {
479
- description: 'Number of GVL fetch errors',
480
- unit: '1'
481
- });
482
- return {
483
- consentCreated,
484
- consentAccepted,
485
- consentRejected,
486
- subjectCreated,
487
- subjectLinked,
488
- consentCheckCount,
489
- initCount,
490
- httpRequestDuration,
491
- httpRequestCount,
492
- httpErrorCount,
493
- dbQueryDuration,
494
- dbQueryCount,
495
- dbErrorCount,
496
- cacheHit,
497
- cacheMiss,
498
- cacheLatency,
499
- gvlFetchDuration,
500
- gvlFetchCount,
501
- gvlFetchError,
502
- recordConsentCreated (attributes) {
503
- consentCreated.add(1, sanitizeAttributes(attributes));
504
- },
505
- recordConsentAccepted (attributes) {
506
- consentAccepted.add(1, sanitizeAttributes(attributes));
507
- },
508
- recordConsentRejected (attributes) {
509
- consentRejected.add(1, sanitizeAttributes(attributes));
510
- },
511
- recordSubjectCreated (attributes) {
512
- subjectCreated.add(1, sanitizeAttributes(attributes));
513
- },
514
- recordSubjectLinked (identityProvider) {
515
- subjectLinked.add(1, {
516
- identityProvider: identityProvider || 'unknown'
517
- });
518
- },
519
- recordConsentCheck (type, found) {
520
- consentCheckCount.add(1, {
521
- type,
522
- found: String(found)
523
- });
524
- },
525
- recordInit (attributes) {
526
- initCount.add(1, sanitizeAttributes(attributes));
527
- },
528
- recordHttpRequest (attributes, durationMs) {
529
- const attrs = sanitizeAttributes(attributes);
530
- httpRequestCount.add(1, attrs);
531
- httpRequestDuration.record(durationMs, attrs);
532
- if (attributes.status >= 400) httpErrorCount.add(1, attrs);
533
- },
534
- recordDbQuery (attributes, durationMs) {
535
- const attrs = sanitizeAttributes(attributes);
536
- dbQueryCount.add(1, attrs);
537
- dbQueryDuration.record(durationMs, attrs);
538
- },
539
- recordDbError (attributes) {
540
- dbErrorCount.add(1, sanitizeAttributes(attributes));
541
- },
542
- recordCacheHit (layer) {
543
- cacheHit.add(1, {
544
- layer
545
- });
546
- },
547
- recordCacheMiss (layer) {
548
- cacheMiss.add(1, {
549
- layer
550
- });
551
- },
552
- recordCacheLatency (attributes, durationMs) {
553
- cacheLatency.record(durationMs, sanitizeAttributes(attributes));
554
- },
555
- recordGvlFetch (attributes, durationMs) {
556
- const attrs = sanitizeAttributes(attributes);
557
- gvlFetchCount.add(1, attrs);
558
- gvlFetchDuration.record(durationMs, attrs);
559
- },
560
- recordGvlError (attributes) {
561
- gvlFetchError.add(1, sanitizeAttributes(attributes));
562
- }
563
- };
564
- }
565
- let metricsInstance = null;
566
- function getMetrics(options) {
567
- if (metricsInstance) return metricsInstance;
568
- if (!isTelemetryEnabled(options)) return null;
569
- metricsInstance = createMetrics(getMeter(options));
570
- return metricsInstance;
571
- }
572
255
  const prefixes = {
573
256
  auditLog: 'log',
574
257
  consent: 'cns',
575
258
  consentPolicy: 'pol',
576
259
  consentPurpose: 'pur',
577
260
  domain: 'dom',
261
+ runtimePolicyDecision: 'rpd',
578
262
  subject: 'sub'
579
263
  };
580
264
  const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
@@ -846,6 +530,54 @@ function domainRegistry({ db, ctx }) {
846
530
  }
847
531
  };
848
532
  }
533
+ function runtimePolicyDecisionRegistry({ db, ctx }) {
534
+ const { logger } = ctx;
535
+ return {
536
+ findOrCreateRuntimePolicyDecision: async (input)=>{
537
+ const existing = await db.findFirst('runtimePolicyDecision', {
538
+ where: (b)=>b('dedupeKey', '=', input.dedupeKey)
539
+ });
540
+ if (existing) return existing;
541
+ logger.debug('Creating runtime policy decision', {
542
+ policyId: input.policyId,
543
+ fingerprint: input.fingerprint,
544
+ matchedBy: input.matchedBy
545
+ });
546
+ return db.create('runtimePolicyDecision', {
547
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
548
+ tenantId: input.tenantId,
549
+ policyId: input.policyId,
550
+ fingerprint: input.fingerprint,
551
+ matchedBy: input.matchedBy,
552
+ countryCode: input.countryCode,
553
+ regionCode: input.regionCode,
554
+ jurisdiction: input.jurisdiction,
555
+ language: input.language,
556
+ model: input.model,
557
+ policyI18n: input.policyI18n ? {
558
+ json: input.policyI18n
559
+ } : void 0,
560
+ uiMode: input.uiMode,
561
+ bannerUi: input.bannerUi ? {
562
+ json: input.bannerUi
563
+ } : void 0,
564
+ dialogUi: input.dialogUi ? {
565
+ json: input.dialogUi
566
+ } : void 0,
567
+ categories: input.categories ? {
568
+ json: input.categories
569
+ } : void 0,
570
+ preselectedCategories: input.preselectedCategories ? {
571
+ json: input.preselectedCategories
572
+ } : void 0,
573
+ proofConfig: input.proofConfig ? {
574
+ json: input.proofConfig
575
+ } : void 0,
576
+ dedupeKey: input.dedupeKey
577
+ });
578
+ }
579
+ };
580
+ }
849
581
  function subjectRegistry({ db, ctx }) {
850
582
  const { logger } = ctx;
851
583
  return {
@@ -923,263 +655,9 @@ const createRegistry = (ctx)=>({
923
655
  ...subjectRegistry(ctx),
924
656
  ...consentPurposeRegistry(ctx),
925
657
  ...policyRegistry(ctx),
926
- ...domainRegistry(ctx)
658
+ ...domainRegistry(ctx),
659
+ ...runtimePolicyDecisionRegistry(ctx)
927
660
  });
928
- const auditLogTable = schema_table('auditLog', {
929
- id: idColumn('id', 'varchar(255)'),
930
- entityType: column('entityType', 'string'),
931
- entityId: column('entityId', 'string'),
932
- actionType: column('actionType', 'string'),
933
- subjectId: column('subjectId', 'string').nullable(),
934
- ipAddress: column('ipAddress', 'string').nullable(),
935
- userAgent: column('userAgent', 'string').nullable(),
936
- changes: column('changes', 'json').nullable(),
937
- metadata: column('metadata', 'json').nullable(),
938
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
939
- eventTimezone: column('eventTimezone', 'string').defaultTo$(()=>'UTC')
940
- });
941
- const consentTable = schema_table('consent', {
942
- id: idColumn('id', 'varchar(255)'),
943
- subjectId: column('subjectId', 'string'),
944
- domainId: column('domainId', 'string'),
945
- policyId: column('policyId', 'string').nullable(),
946
- purposeIds: column('purposeIds', 'json'),
947
- metadata: column('metadata', 'json').nullable(),
948
- ipAddress: column('ipAddress', 'string').nullable(),
949
- userAgent: column('userAgent', 'string').nullable(),
950
- status: column('status', 'string').defaultTo$(()=>'active'),
951
- withdrawalReason: column('withdrawalReason', 'string').nullable(),
952
- givenAt: column('givenAt', 'timestamp').defaultTo$('now'),
953
- validUntil: column('validUntil', 'timestamp').nullable(),
954
- isActive: column('isActive', 'bool').defaultTo$(()=>true)
955
- });
956
- const consentPolicyTable = schema_table('consentPolicy', {
957
- id: idColumn('id', 'varchar(255)'),
958
- version: column('version', 'string'),
959
- type: column('type', 'string'),
960
- name: column('name', 'string'),
961
- effectiveDate: column('effectiveDate', 'timestamp'),
962
- expirationDate: column('expirationDate', 'timestamp').nullable(),
963
- content: column('content', 'string'),
964
- contentHash: column('contentHash', 'string'),
965
- isActive: column('isActive', 'bool').defaultTo$(()=>true),
966
- createdAt: column('createdAt', 'timestamp').defaultTo$('now')
967
- });
968
- const consentPurposeTable = schema_table('consentPurpose', {
969
- id: idColumn('id', 'varchar(255)'),
970
- code: column('code', 'string'),
971
- name: column('name', 'string'),
972
- description: column("description", 'string'),
973
- isEssential: column('isEssential', 'bool'),
974
- dataCategory: column('dataCategory', 'string').nullable(),
975
- legalBasis: column('legalBasis', 'string').nullable(),
976
- isActive: column('isActive', 'bool').defaultTo$(()=>true),
977
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
978
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now')
979
- });
980
- const consentRecordTable = schema_table('consentRecord', {
981
- id: idColumn('id', 'varchar(255)'),
982
- subjectId: column('subjectId', 'string'),
983
- consentId: column('consentId', 'string').nullable(),
984
- actionType: column('actionType', 'string'),
985
- details: column('details', 'json').nullable(),
986
- createdAt: column('createdAt', 'timestamp').defaultTo$('now')
987
- });
988
- const domainTable = schema_table('domain', {
989
- id: idColumn('id', 'varchar(255)'),
990
- name: column('name', 'string').unique(),
991
- description: column("description", 'string').nullable(),
992
- allowedOrigins: column('allowedOrigins', 'json').nullable(),
993
- isVerified: column('isVerified', 'bool').defaultTo$(()=>true),
994
- isActive: column('isActive', 'bool').defaultTo$(()=>true),
995
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
996
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now')
997
- });
998
- const subjectTable = schema_table('subject', {
999
- id: idColumn('id', 'varchar(255)'),
1000
- externalId: column('externalId', 'string').nullable(),
1001
- identityProvider: column('identityProvider', 'string').nullable(),
1002
- lastIpAddress: column('lastIpAddress', 'string').nullable(),
1003
- subjectTimezone: column('subjectTimezone', 'string').nullable(),
1004
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1005
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now')
1006
- });
1007
- const v1 = schema({
1008
- version: '1.0.0',
1009
- tables: {
1010
- subject: subjectTable,
1011
- domain: domainTable,
1012
- consentPolicy: consentPolicyTable,
1013
- consentPurpose: consentPurposeTable,
1014
- consent: consentTable,
1015
- auditLog: auditLogTable,
1016
- consentRecord: consentRecordTable
1017
- },
1018
- relations: {
1019
- subject: ({ many })=>({
1020
- consents: many('consent'),
1021
- consentRecords: many('consentRecord'),
1022
- auditLogs: many('auditLog')
1023
- }),
1024
- domain: ({ many })=>({
1025
- consents: many('consent')
1026
- }),
1027
- consentPolicy: ({ many })=>({
1028
- consents: many('consent')
1029
- }),
1030
- consentPurpose: ()=>({}),
1031
- consent: ({ one, many })=>({
1032
- subject: one('subject', [
1033
- 'subjectId',
1034
- 'id'
1035
- ]).foreignKey(),
1036
- domain: one('domain', [
1037
- 'domainId',
1038
- 'id'
1039
- ]).foreignKey(),
1040
- policy: one('consentPolicy', [
1041
- 'policyId',
1042
- 'id'
1043
- ]).foreignKey(),
1044
- consentRecords: many('consentRecord')
1045
- }),
1046
- consentRecord: ({ one })=>({
1047
- subject: one('subject', [
1048
- 'subjectId',
1049
- 'id'
1050
- ]).foreignKey(),
1051
- consent: one('consent', [
1052
- 'consentId',
1053
- 'id'
1054
- ]).foreignKey()
1055
- }),
1056
- auditLog: ({ one })=>({
1057
- subject: one('subject', [
1058
- 'subjectId',
1059
- 'id'
1060
- ]).foreignKey()
1061
- })
1062
- }
1063
- });
1064
- const audit_log_auditLogTable = schema_table('auditLog', {
1065
- id: idColumn('id', 'varchar(255)'),
1066
- entityType: column('entityType', 'string'),
1067
- entityId: column('entityId', 'string'),
1068
- actionType: column('actionType', 'string'),
1069
- subjectId: column('subjectId', 'string').nullable(),
1070
- ipAddress: column('ipAddress', 'string').nullable(),
1071
- userAgent: column('userAgent', 'string').nullable(),
1072
- changes: column('changes', 'json').nullable(),
1073
- metadata: column('metadata', 'json').nullable(),
1074
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1075
- tenantId: column('tenantId', 'string').nullable()
1076
- });
1077
- const consent_consentTable = schema_table('consent', {
1078
- id: idColumn('id', 'varchar(255)'),
1079
- subjectId: column('subjectId', 'string'),
1080
- domainId: column('domainId', 'string'),
1081
- policyId: column('policyId', 'string').nullable(),
1082
- purposeIds: column('purposeIds', 'json'),
1083
- metadata: column('metadata', 'json').nullable(),
1084
- ipAddress: column('ipAddress', 'string').nullable(),
1085
- userAgent: column('userAgent', 'string').nullable(),
1086
- givenAt: column('givenAt', 'timestamp').defaultTo$('now'),
1087
- validUntil: column('validUntil', 'timestamp').nullable(),
1088
- jurisdiction: column('jurisdiction', 'string').nullable(),
1089
- jurisdictionModel: column('jurisdictionModel', 'string').nullable(),
1090
- tcString: column('tcString', 'string').nullable(),
1091
- uiSource: column('uiSource', 'string').nullable(),
1092
- consentAction: column('consentAction', 'string').nullable(),
1093
- tenantId: column('tenantId', 'string').nullable()
1094
- });
1095
- const consent_policy_consentPolicyTable = schema_table('consentPolicy', {
1096
- id: idColumn('id', 'varchar(255)'),
1097
- version: column('version', 'string'),
1098
- type: column('type', 'string'),
1099
- effectiveDate: column('effectiveDate', 'timestamp'),
1100
- isActive: column('isActive', 'bool').defaultTo$(()=>true),
1101
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1102
- tenantId: column('tenantId', 'string').nullable()
1103
- });
1104
- const consent_purpose_consentPurposeTable = schema_table('consentPurpose', {
1105
- id: idColumn('id', 'varchar(255)'),
1106
- code: column('code', 'string'),
1107
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1108
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
1109
- tenantId: column('tenantId', 'string').nullable()
1110
- });
1111
- const domain_domainTable = schema_table('domain', {
1112
- id: idColumn('id', 'varchar(255)'),
1113
- name: column('name', 'string'),
1114
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1115
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
1116
- tenantId: column('tenantId', 'string').nullable()
1117
- });
1118
- const subject_subjectTable = schema_table('subject', {
1119
- id: idColumn('id', 'varchar(255)'),
1120
- externalId: column('externalId', 'string').nullable(),
1121
- identityProvider: column('identityProvider', 'string').nullable(),
1122
- createdAt: column('createdAt', 'timestamp').defaultTo$('now'),
1123
- updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
1124
- tenantId: column('tenantId', 'string').nullable()
1125
- });
1126
- const v2 = schema({
1127
- version: '2.0.0',
1128
- tables: {
1129
- subject: subject_subjectTable,
1130
- domain: domain_domainTable,
1131
- consentPolicy: consent_policy_consentPolicyTable,
1132
- consentPurpose: consent_purpose_consentPurposeTable,
1133
- consent: consent_consentTable,
1134
- auditLog: audit_log_auditLogTable
1135
- },
1136
- relations: {
1137
- subject: ({ many })=>({
1138
- consents: many('consent'),
1139
- auditLogs: many('auditLog')
1140
- }),
1141
- domain: ({ many })=>({
1142
- consents: many('consent')
1143
- }),
1144
- consentPolicy: ({ many })=>({
1145
- consents: many('consent')
1146
- }),
1147
- consentPurpose: ()=>({}),
1148
- consent: ({ one })=>({
1149
- subject: one('subject', [
1150
- 'subjectId',
1151
- 'id'
1152
- ]).foreignKey(),
1153
- domain: one('domain', [
1154
- 'domainId',
1155
- 'id'
1156
- ]).foreignKey(),
1157
- policy: one('consentPolicy', [
1158
- 'policyId',
1159
- 'id'
1160
- ]).foreignKey()
1161
- }),
1162
- auditLog: ({ one })=>({
1163
- subject: one('subject', [
1164
- 'subjectId',
1165
- 'id'
1166
- ]).foreignKey()
1167
- })
1168
- }
1169
- });
1170
- const DB = fumadb({
1171
- namespace: 'c15t',
1172
- schemas: [
1173
- v1,
1174
- v2
1175
- ]
1176
- });
1177
- fumadb({
1178
- namespace: 'c15t',
1179
- schemas: [
1180
- v2
1181
- ]
1182
- });
1183
661
  const SCOPED_METHODS = new Set([
1184
662
  'create',
1185
663
  'createMany',
@@ -1269,6 +747,18 @@ const init = (options)=>{
1269
747
  const rawOrm = client.orm('2.0.0');
1270
748
  const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
1271
749
  const { ipAddress: _ipAddressConfig, ...baseOptions } = options;
750
+ const i18nValidation = validateMessages({
751
+ i18n: options.i18n,
752
+ customTranslations: options.customTranslations,
753
+ policies: options.policyPacks
754
+ });
755
+ for (const warning of i18nValidation.warnings)logger.warn(`i18n: ${warning}`);
756
+ if (i18nValidation.errors.length > 0) throw new Error(`Invalid i18n configuration:\n${i18nValidation.errors.map((error)=>`- ${error}`).join('\n')}`);
757
+ const policyValidation = policy_inspectPolicies(options.policyPacks ?? [], {
758
+ iabEnabled: options.iab?.enabled === true
759
+ });
760
+ for (const warning of policyValidation.warnings)logger.warn(`policyPacks: ${warning}`);
761
+ if (policyValidation.errors.length > 0) throw new Error(policyValidation.errors[0]);
1272
762
  const context = {
1273
763
  ...baseOptions,
1274
764
  appName,
@@ -1283,1252 +773,91 @@ const init = (options)=>{
1283
773
  };
1284
774
  return context;
1285
775
  };
1286
- function parsePurposeIds(purposeIds) {
1287
- if (null == purposeIds) return [];
1288
- const ids = 'object' == typeof purposeIds && 'json' in purposeIds ? purposeIds.json : purposeIds;
1289
- return Array.isArray(ids) ? ids : [];
1290
- }
1291
- async function batchLoadPolicies(policyIds, ctx) {
1292
- const { db, registry } = ctx;
1293
- const policyMap = new Map();
1294
- if (policyIds.size > 0) {
1295
- const policies = await db.findMany('consentPolicy', {
1296
- where: (b)=>b('id', 'in', [
1297
- ...policyIds
1298
- ])
1299
- });
1300
- for (const p of policies)policyMap.set(p.id, p);
1301
- }
1302
- const uniqueTypes = new Set();
1303
- for (const p of policyMap.values())uniqueTypes.add(p.type);
1304
- const latestPolicyByType = new Map();
1305
- for (const type of uniqueTypes){
1306
- const latest = await registry.findOrCreatePolicy(type);
1307
- if (latest) latestPolicyByType.set(type, latest.id);
1308
- }
1309
- return {
1310
- policyMap,
1311
- latestPolicyByType
1312
- };
1313
- }
1314
- async function enrichConsents(consents, ctx) {
1315
- if (0 === consents.length) return [];
1316
- const policyIds = new Set();
1317
- for (const c of consents)if (c.policyId) policyIds.add(c.policyId);
1318
- const { policyMap, latestPolicyByType } = await batchLoadPolicies(policyIds, ctx);
1319
- const allPurposeIds = new Set();
1320
- for (const c of consents)for (const id of parsePurposeIds(c.purposeIds))allPurposeIds.add(id);
1321
- const purposeMap = new Map();
1322
- if (allPurposeIds.size > 0) {
1323
- const purposes = await ctx.db.findMany('consentPurpose', {
1324
- where: (b)=>b('id', 'in', [
1325
- ...allPurposeIds
1326
- ])
1327
- });
1328
- for (const p of purposes)purposeMap.set(p.id, p.code);
1329
- }
1330
- return consents.map((consent)=>{
1331
- let policyType = 'unknown';
1332
- let isLatestPolicy = false;
1333
- if (consent.policyId) {
1334
- const policy = policyMap.get(consent.policyId);
1335
- if (policy) {
1336
- policyType = policy.type;
1337
- isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
1338
- }
1339
- }
1340
- let preferences;
1341
- const ids = parsePurposeIds(consent.purposeIds);
1342
- if (ids.length > 0) {
1343
- preferences = {};
1344
- for (const purposeId of ids){
1345
- const code = purposeMap.get(purposeId);
1346
- if (code) preferences[code] = true;
1347
- }
1348
- }
1349
- return {
1350
- id: consent.id,
1351
- type: policyType,
1352
- policyId: consent.policyId ?? void 0,
1353
- isLatestPolicy,
1354
- preferences,
1355
- givenAt: consent.givenAt
1356
- };
1357
- });
1358
- }
1359
- async function resolveConsentPolicies(consents, ctx) {
1360
- if (0 === consents.length) return [];
1361
- const policyIds = new Set();
1362
- for (const c of consents)if (c.policyId) policyIds.add(c.policyId);
1363
- const { policyMap, latestPolicyByType } = await batchLoadPolicies(policyIds, ctx);
1364
- return consents.map((consent)=>{
1365
- let policyType = 'unknown';
1366
- let isLatestPolicy = false;
1367
- if (consent.policyId) {
1368
- const policy = policyMap.get(consent.policyId);
1369
- if (policy) {
1370
- policyType = policy.type;
1371
- isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
1372
- }
1373
- }
1374
- return {
1375
- consentId: consent.id,
1376
- policyType,
1377
- policyId: consent.policyId ?? void 0,
1378
- isLatestPolicy
1379
- };
1380
- });
1381
- }
1382
- const checkConsentHandler = async (c)=>{
1383
- const ctx = c.get('c15tContext');
1384
- const logger = ctx.logger;
1385
- logger.info('Handling GET /consents/check request');
1386
- const { db, registry } = ctx;
1387
- const externalId = c.req.query('externalId');
1388
- const type = c.req.query('type');
1389
- if (!externalId) throw new HTTPException(422, {
1390
- message: 'externalId query parameter is required',
1391
- cause: {
1392
- code: 'EXTERNAL_ID_REQUIRED'
1393
- }
1394
- });
1395
- if (!type) throw new HTTPException(422, {
1396
- message: 'type query parameter is required',
1397
- cause: {
1398
- code: 'TYPE_REQUIRED'
1399
- }
1400
- });
1401
- const types = type.split(',').map((t)=>t.trim());
1402
- logger.debug('Request parameters', {
1403
- externalId,
1404
- types
1405
- });
1406
- try {
1407
- const subjects = await db.findMany('subject', {
1408
- where: (b)=>b('externalId', '=', externalId)
1409
- });
1410
- const subjectIds = subjects.map((s)=>s.id);
1411
- const results = {};
1412
- for (const t of types)results[t] = {
1413
- hasConsent: false,
1414
- isLatestPolicy: false
1415
- };
1416
- if (0 === subjectIds.length) {
1417
- logger.debug('No subjects found for externalId', {
1418
- externalId
1419
- });
1420
- return c.json({
1421
- results
1422
- });
1423
- }
1424
- const allConsents = await Promise.all(subjectIds.map((subjectId)=>db.findMany('consent', {
1425
- where: (b)=>b('subjectId', '=', subjectId)
1426
- })));
1427
- const consents = allConsents.flat();
1428
- const policyInfos = await resolveConsentPolicies(consents, {
1429
- db,
1430
- registry
1431
- });
1432
- for (const info of policyInfos){
1433
- if (!types.includes(info.policyType)) continue;
1434
- const entry = results[info.policyType];
1435
- if (entry) {
1436
- entry.hasConsent = true;
1437
- if (info.isLatestPolicy) entry.isLatestPolicy = true;
1438
- }
1439
- }
1440
- logger.debug('Consent check results', {
1441
- externalId,
1442
- results
1443
- });
1444
- const metrics = getMetrics();
1445
- if (metrics) for (const [type, result] of Object.entries(results))metrics.recordConsentCheck(type, result.hasConsent);
1446
- return c.json({
1447
- results
1448
- });
1449
- } catch (error) {
1450
- logger.error('Error in GET /consents/check handler', {
1451
- error: extractErrorMessage(error),
1452
- errorType: error instanceof Error ? error.constructor.name : typeof error
1453
- });
1454
- if (error instanceof HTTPException) throw error;
1455
- throw new HTTPException(500, {
1456
- message: 'Internal server error',
1457
- cause: {
1458
- code: 'INTERNAL_SERVER_ERROR'
1459
- }
1460
- });
1461
- }
776
+ const DEFAULT_FALLBACK_POLICY_INPUT = {
777
+ id: 'world_no_banner',
778
+ isDefault: true,
779
+ model: 'none',
780
+ uiMode: 'none'
1462
781
  };
1463
- const createConsentRoutes = ()=>{
1464
- const app = new Hono();
1465
- app.get('/check', describeRoute({
1466
- summary: 'Check consent by external user ID',
1467
- description: `Pre-banner cross-device consent check. Use to avoid showing the banner when the user has already consented on another device.
1468
-
1469
- **Query parameters:**
1470
- - \`externalId\` – External user ID to check
1471
- - \`type\` – Consent type(s) to check (comma-separated)`,
1472
- tags: [
1473
- 'Consent'
1474
- ],
1475
- responses: {
1476
- 200: {
1477
- description: 'Consent check result per requested type(s)',
1478
- content: {
1479
- 'application/json': {
1480
- schema: resolver(checkConsentOutputSchema)
1481
- }
1482
- }
1483
- },
1484
- 422: {
1485
- description: 'Invalid or missing query parameters'
1486
- }
1487
- }
1488
- }), validator('query', checkConsentQuerySchema), checkConsentHandler);
1489
- return app;
1490
- };
1491
- const GVL_TTL_MS = 259200000;
1492
- const memory_memoryCache = new Map();
1493
- function createMemoryCacheAdapter() {
1494
- return {
1495
- async get (key) {
1496
- const entry = memory_memoryCache.get(key);
1497
- if (!entry) return null;
1498
- if (Date.now() > entry.expiresAt) {
1499
- memory_memoryCache.delete(key);
1500
- return null;
1501
- }
1502
- return entry.value;
1503
- },
1504
- async set (key, value, ttlMs = 300000) {
1505
- memory_memoryCache.set(key, {
1506
- value,
1507
- expiresAt: Date.now() + ttlMs
1508
- });
1509
- },
1510
- async delete (key) {
1511
- memory_memoryCache.delete(key);
1512
- },
1513
- async has (key) {
1514
- const entry = memory_memoryCache.get(key);
1515
- if (!entry) return false;
1516
- if (Date.now() > entry.expiresAt) {
1517
- memory_memoryCache.delete(key);
1518
- return false;
1519
- }
1520
- return true;
1521
- }
1522
- };
1523
- }
1524
- function createGVLCacheKey(appName, language, vendorIds) {
1525
- const sortedIds = vendorIds ? [
1526
- ...vendorIds
1527
- ].sort((a, b)=>a - b).join(',') : 'all';
1528
- return `${appName}:gvl:${language}:${sortedIds}`;
782
+ function mergeMatch(input) {
783
+ return policyMatchers.merge(input.countries?.length ? policyMatchers.countries(input.countries) : {}, input.regions?.length ? policyMatchers.regions(input.regions) : {}, input.isDefault ? policyMatchers["default"]() : {});
1529
784
  }
1530
- const GVL_ENDPOINT = 'https://gvl.consent.io';
1531
- const inflightRequests = new Map();
1532
- async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
1533
- const sortedVendorIds = vendorIds ? [
1534
- ...vendorIds
1535
- ].sort((a, b)=>a - b) : [];
1536
- const dedupeKey = `${endpoint}|${language}|${sortedVendorIds.join(',')}`;
1537
- const existingRequest = inflightRequests.get(dedupeKey);
1538
- if (existingRequest) return existingRequest;
1539
- const url = new URL(endpoint);
1540
- if (sortedVendorIds.length > 0) url.searchParams.set('vendorIds', sortedVendorIds.join(','));
1541
- const promise = (async ()=>{
1542
- const fetchStart = Date.now();
1543
- try {
1544
- const gvl = await withExternalSpan({
1545
- url: url.toString(),
1546
- method: 'GET'
1547
- }, async ()=>{
1548
- const response = await fetch(url.toString(), {
1549
- headers: {
1550
- 'Accept-Language': language
1551
- }
1552
- });
1553
- if (204 === response.status) return null;
1554
- if (!response.ok) throw new Error(`Failed to fetch GVL: ${response.status} ${response.statusText}`);
1555
- const text = await response.text();
1556
- const trimmed = text.trim().replace(/^\uFEFF/, '');
1557
- let parsed;
1558
- try {
1559
- parsed = JSON.parse(trimmed);
1560
- } catch {
1561
- let depth = 0;
1562
- let end = -1;
1563
- const start = trimmed.indexOf('{');
1564
- if (start >= 0) for(let i = start; i < trimmed.length; i++){
1565
- const c = trimmed[i];
1566
- if ('{' === c) depth++;
1567
- else if ('}' === c) {
1568
- depth--;
1569
- if (0 === depth) {
1570
- end = i + 1;
1571
- break;
1572
- }
1573
- }
1574
- }
1575
- if (end > 0) parsed = JSON.parse(trimmed.slice(0, end));
1576
- else throw new SyntaxError('Invalid GVL response: not valid JSON');
1577
- }
1578
- if (!parsed.vendorListVersion || !parsed.purposes || !parsed.vendors) throw new Error('Invalid GVL response: missing required fields');
1579
- return parsed;
1580
- });
1581
- getMetrics()?.recordGvlFetch({
1582
- language,
1583
- source: 'fetch',
1584
- status: 200
1585
- }, Date.now() - fetchStart);
1586
- return gvl;
1587
- } catch (error) {
1588
- getMetrics()?.recordGvlError({
1589
- language,
1590
- errorType: error instanceof Error ? error.name : 'UnknownError'
1591
- });
1592
- throw error;
1593
- } finally{
1594
- inflightRequests.delete(dedupeKey);
1595
- }
1596
- })();
1597
- inflightRequests.set(dedupeKey, promise);
1598
- return promise;
1599
- }
1600
- function createGVLResolver(options) {
1601
- const { appName, bundled, cacheAdapter, vendorIds, endpoint } = options;
1602
- const memoryCache = createMemoryCacheAdapter();
1603
- return {
1604
- async get (language) {
1605
- const cacheKey = createGVLCacheKey(appName, language, vendorIds);
1606
- if (bundled?.[language]) return bundled[language];
1607
- const memoryHit = await withCacheSpan('get', 'memory', ()=>memoryCache.get(cacheKey));
1608
- if (memoryHit) {
1609
- getMetrics()?.recordCacheHit('memory');
1610
- return memoryHit;
1611
- }
1612
- getMetrics()?.recordCacheMiss('memory');
1613
- if (cacheAdapter) {
1614
- const externalHit = await withCacheSpan('get', 'external', ()=>cacheAdapter.get(cacheKey));
1615
- if (externalHit) {
1616
- getMetrics()?.recordCacheHit('external');
1617
- await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, externalHit, 300000));
1618
- return externalHit;
1619
- }
1620
- getMetrics()?.recordCacheMiss('external');
1621
- }
1622
- const gvl = await fetchGVLWithLanguage(language, vendorIds, endpoint);
1623
- if (gvl) {
1624
- await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, gvl, 300000));
1625
- if (cacheAdapter) await withCacheSpan('set', 'external', ()=>cacheAdapter.set(cacheKey, gvl, GVL_TTL_MS));
1626
- }
1627
- return gvl;
1628
- }
1629
- };
1630
- }
1631
- function geo_normalizeHeader(value) {
1632
- if (!value) return null;
1633
- return Array.isArray(value) ? value[0] ?? null : value;
1634
- }
1635
- function getGeoHeaders(headers) {
1636
- 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'));
1637
- 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'));
1638
- return {
1639
- countryCode,
1640
- regionCode
1641
- };
1642
- }
1643
- function checkJurisdiction(countryCode, regionCode) {
1644
- const jurisdictions = {
1645
- EU: new Set([
1646
- 'AT',
1647
- 'BE',
1648
- 'BG',
1649
- 'HR',
1650
- 'CY',
1651
- 'CZ',
1652
- 'DK',
1653
- 'EE',
1654
- 'FI',
1655
- 'FR',
1656
- 'DE',
1657
- 'GR',
1658
- 'HU',
1659
- 'IE',
1660
- 'IT',
1661
- 'LV',
1662
- 'LT',
1663
- 'LU',
1664
- 'MT',
1665
- 'NL',
1666
- 'PL',
1667
- 'PT',
1668
- 'RO',
1669
- 'SK',
1670
- 'SI',
1671
- 'ES',
1672
- 'SE'
1673
- ]),
1674
- EEA: new Set([
1675
- 'IS',
1676
- 'NO',
1677
- 'LI'
1678
- ]),
1679
- UK: new Set([
1680
- 'GB'
1681
- ]),
1682
- CH: new Set([
1683
- 'CH'
1684
- ]),
1685
- BR: new Set([
1686
- 'BR'
1687
- ]),
1688
- CA: new Set([
1689
- 'CA'
1690
- ]),
1691
- AU: new Set([
1692
- 'AU'
1693
- ]),
1694
- JP: new Set([
1695
- 'JP'
1696
- ]),
1697
- KR: new Set([
1698
- 'KR'
1699
- ]),
1700
- US_CCPA_REGIONS: new Set([
1701
- 'CA'
1702
- ]),
1703
- CA_QC_REGIONS: new Set([
1704
- 'QC'
1705
- ])
1706
- };
1707
- let jurisdiction = 'NONE';
1708
- if (countryCode) {
1709
- const normalizedCountryCode = countryCode.toUpperCase();
1710
- const normalizedRegionCode = regionCode && 'string' == typeof regionCode ? (regionCode.includes('-') ? regionCode.split('-').pop() : regionCode).toUpperCase() : null;
1711
- if ('US' === normalizedCountryCode && normalizedRegionCode && jurisdictions.US_CCPA_REGIONS.has(normalizedRegionCode)) return 'CCPA';
1712
- if ('CA' === normalizedCountryCode && normalizedRegionCode && jurisdictions.CA_QC_REGIONS.has(normalizedRegionCode)) return 'QC_LAW25';
1713
- const jurisdictionMap = [
1714
- {
1715
- sets: [
1716
- jurisdictions.UK
1717
- ],
1718
- code: 'UK_GDPR'
1719
- },
1720
- {
1721
- sets: [
1722
- jurisdictions.EU,
1723
- jurisdictions.EEA
1724
- ],
1725
- code: 'GDPR'
1726
- },
1727
- {
1728
- sets: [
1729
- jurisdictions.CH
1730
- ],
1731
- code: 'CH'
1732
- },
1733
- {
1734
- sets: [
1735
- jurisdictions.BR
1736
- ],
1737
- code: 'BR'
1738
- },
1739
- {
1740
- sets: [
1741
- jurisdictions.CA
1742
- ],
1743
- code: 'PIPEDA'
1744
- },
1745
- {
1746
- sets: [
1747
- jurisdictions.AU
1748
- ],
1749
- code: 'AU'
1750
- },
1751
- {
1752
- sets: [
1753
- jurisdictions.JP
1754
- ],
1755
- code: 'APPI'
1756
- },
1757
- {
1758
- sets: [
1759
- jurisdictions.KR
1760
- ],
1761
- code: 'PIPA'
1762
- }
1763
- ];
1764
- for (const { sets, code } of jurisdictionMap)if (sets.some((set)=>set.has(normalizedCountryCode))) {
1765
- jurisdiction = code;
1766
- break;
1767
- }
1768
- }
1769
- return jurisdiction;
785
+ function compactUiSurface(value) {
786
+ if (!value) return;
787
+ return compactDefined({
788
+ allowedActions: dedupeTrimmedStrings(value.allowedActions),
789
+ primaryActions: value.primaryActions,
790
+ layout: value.layout,
791
+ direction: value.direction,
792
+ uiProfile: value.uiProfile,
793
+ scrollLock: value.scrollLock
794
+ });
1770
795
  }
1771
- async function getLocation(request, options) {
1772
- if (options.disableGeoLocation) return {
1773
- countryCode: null,
1774
- regionCode: null
1775
- };
1776
- const { countryCode, regionCode } = getGeoHeaders(request.headers);
796
+ function buildPolicyConfig(input) {
797
+ const categories = dedupeTrimmedStrings(input.categories);
798
+ const preselectedCategories = dedupeTrimmedStrings(input.preselectedCategories);
1777
799
  return {
1778
- countryCode,
1779
- regionCode
800
+ id: input.id,
801
+ match: mergeMatch(input),
802
+ i18n: input.i18n,
803
+ consent: compactDefined({
804
+ model: input.model,
805
+ expiryDays: input.expiryDays,
806
+ scopeMode: input.scopeMode,
807
+ categories,
808
+ preselectedCategories,
809
+ gpc: input.gpc
810
+ }),
811
+ ui: compactDefined({
812
+ mode: input.uiMode,
813
+ banner: compactUiSurface(input.banner),
814
+ dialog: compactUiSurface(input.dialog)
815
+ }),
816
+ proof: compactDefined({
817
+ storeIp: input.proof?.storeIp,
818
+ storeUserAgent: input.proof?.storeUserAgent,
819
+ storeLanguage: input.proof?.storeLanguage
820
+ })
1780
821
  };
1781
822
  }
1782
- function getJurisdiction(location, options) {
1783
- if (options.disableGeoLocation) return 'GDPR';
1784
- return checkJurisdiction(location.countryCode, location.regionCode);
1785
- }
1786
- function isSupportedBaseLanguage(lang) {
1787
- return lang in baseTranslations;
823
+ function buildPolicyPack(inputs) {
824
+ return inputs.map((input)=>buildPolicyConfig(input));
1788
825
  }
1789
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
1790
- const supportedDefaultLanguages = Object.keys(baseTranslations);
1791
- const supportedCustomLanguages = Object.keys(customTranslations || {});
1792
- const supportedLanguages = [
1793
- ...supportedDefaultLanguages,
1794
- ...supportedCustomLanguages
826
+ function buildPolicyPackWithDefault(inputs, defaultPolicy) {
827
+ const pack = buildPolicyPack(inputs);
828
+ const hasDefault = pack.some((policy)=>policy.match.isDefault);
829
+ if (hasDefault) return pack;
830
+ const fallbackInput = defaultPolicy ? {
831
+ ...defaultPolicy,
832
+ isDefault: true,
833
+ countries: void 0,
834
+ regions: void 0
835
+ } : DEFAULT_FALLBACK_POLICY_INPUT;
836
+ return [
837
+ ...pack,
838
+ buildPolicyConfig(fallbackInput)
1795
839
  ];
1796
- const preferredLanguage = selectLanguage(supportedLanguages, {
1797
- header: acceptLanguage,
1798
- fallback: 'en'
1799
- });
1800
- const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
1801
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1802
- const translations = custom ? deepMergeTranslations(base, custom) : base;
1803
- return {
1804
- translations: translations,
1805
- language: preferredLanguage
1806
- };
1807
- }
1808
- const createInitRoute = (options)=>{
1809
- const app = new Hono();
1810
- app.get('/', describeRoute({
1811
- summary: 'Get initial consent manager state',
1812
- description: `Returns the initial state required to render the consent manager.
1813
-
1814
- - **Jurisdiction** – User's jurisdiction (defaults to GDPR if geo-location is disabled)
1815
- - **Location** – User's location (null if geo-location is disabled)
1816
- - **Translations** – Consent manager copy (from \`Accept-Language\` header)
1817
- - **Branding** – Configured branding key
1818
- - **GVL** – Global Vendor List when enabled
1819
-
1820
- Use for geo-targeted consent banners and regional compliance.`,
1821
- tags: [
1822
- 'Init'
1823
- ],
1824
- responses: {
1825
- 200: {
1826
- description: 'Initialization payload (jurisdiction, location, translations, branding, GVL)',
1827
- content: {
1828
- 'application/json': {
1829
- schema: resolver(initOutputSchema)
1830
- }
1831
- }
1832
- }
1833
- }
1834
- }), async (c)=>{
1835
- const request = c.req.raw;
1836
- const acceptLanguage = request.headers.get('accept-language') || 'en';
1837
- const location = await getLocation(request, options);
1838
- const jurisdiction = getJurisdiction(location, options);
1839
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations);
1840
- let gvl = null;
1841
- if (options.iab?.enabled) {
1842
- const language = translationsResult.language.split('-')[0] || 'en';
1843
- const gvlResolver = createGVLResolver({
1844
- appName: options.appName || 'c15t',
1845
- bundled: options.iab.bundled,
1846
- cacheAdapter: options.cache?.adapter,
1847
- vendorIds: options.iab.vendorIds,
1848
- endpoint: options.iab.endpoint
1849
- });
1850
- gvl = await gvlResolver.get(language);
1851
- }
1852
- const customVendors = options.iab?.customVendors;
1853
- const gpc = '1' === request.headers.get('sec-gpc');
1854
- getMetrics()?.recordInit({
1855
- jurisdiction,
1856
- country: location?.countryCode ?? void 0,
1857
- region: location?.regionCode ?? void 0,
1858
- gpc
1859
- });
1860
- return c.json({
1861
- jurisdiction,
1862
- location,
1863
- translations: translationsResult,
1864
- branding: options.branding || 'c15t',
1865
- gvl,
1866
- customVendors,
1867
- ...options.iab?.cmpId != null && {
1868
- cmpId: options.iab.cmpId
1869
- }
1870
- });
1871
- });
1872
- return app;
1873
- };
1874
- function getHeaders(headers) {
1875
- if (!headers) return {
1876
- countryCode: null,
1877
- regionCode: null,
1878
- acceptLanguage: null
1879
- };
1880
- const normalizeHeader = (value)=>{
1881
- if (!value) return null;
1882
- return Array.isArray(value) ? value[0] ?? null : value;
1883
- };
1884
- 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'));
1885
- const regionCode = normalizeHeader(headers.get('x-c15t-region')) ?? normalizeHeader(headers.get('x-vercel-ip-country-region')) ?? normalizeHeader(headers.get('x-region-code'));
1886
- const acceptLanguage = normalizeHeader(headers.get('accept-language'));
1887
- return {
1888
- countryCode,
1889
- regionCode,
1890
- acceptLanguage
1891
- };
1892
840
  }
1893
- const statusHandler = async (c)=>{
1894
- const ctx = c.get('c15tContext');
1895
- const { countryCode, regionCode, acceptLanguage } = getHeaders(ctx.headers);
1896
- const clientInfo = {
1897
- ip: ctx.ipAddress ?? null,
1898
- acceptLanguage,
1899
- userAgent: ctx.userAgent ?? null,
1900
- region: {
1901
- countryCode,
1902
- regionCode
1903
- }
1904
- };
1905
- try {
1906
- await ctx.db.findFirst('subject', {});
1907
- return c.json({
1908
- version: version_version,
1909
- timestamp: new Date(),
1910
- client: clientInfo
1911
- });
1912
- } catch (error) {
1913
- ctx.logger.error('Database health check failed', {
1914
- error
1915
- });
1916
- throw new HTTPException(503, {
1917
- message: 'Database health check failed',
1918
- cause: {
1919
- code: 'SERVICE_UNAVAILABLE',
1920
- error
1921
- }
1922
- });
1923
- }
1924
- };
1925
- const createStatusRoute = ()=>{
1926
- const app = new Hono();
1927
- app.get('/', describeRoute({
1928
- summary: 'Health check and API status',
1929
- description: `Returns API version, timestamp, and client info (IP, region, user agent).
1930
-
1931
- Use for health checks, load balancer probes, and debugging. Performs a lightweight DB check; returns 503 if the database is unreachable.`,
1932
- tags: [
1933
- 'Status'
1934
- ],
1935
- responses: {
1936
- 200: {
1937
- description: 'API is healthy (version, timestamp, client info)',
1938
- content: {
1939
- 'application/json': {
1940
- schema: resolver(statusOutputSchema)
1941
- }
1942
- }
1943
- },
1944
- 503: {
1945
- description: 'Service unavailable (e.g. database unreachable)'
1946
- }
1947
- }
1948
- }), statusHandler);
1949
- return app;
1950
- };
1951
- const getSubjectHandler = async (c)=>{
1952
- const ctx = c.get('c15tContext');
1953
- const logger = ctx.logger;
1954
- logger.info('Handling GET /subjects/:id request');
1955
- const { db, registry } = ctx;
1956
- const subjectId = c.req.param('id');
1957
- const type = c.req.query('type');
1958
- const typeFilter = type?.split(',').map((t)=>t.trim()) || [];
1959
- logger.debug('Request parameters', {
1960
- subjectId,
1961
- typeFilter
1962
- });
1963
- try {
1964
- const subject = await db.findFirst('subject', {
1965
- where: (b)=>b('id', '=', subjectId)
1966
- });
1967
- if (!subject) throw new HTTPException(404, {
1968
- message: 'Subject not found',
1969
- cause: {
1970
- code: 'SUBJECT_NOT_FOUND',
1971
- subjectId
1972
- }
1973
- });
1974
- const consents = await db.findMany('consent', {
1975
- where: (b)=>b('subjectId', '=', subjectId)
1976
- });
1977
- const consentItems = await enrichConsents(consents, {
1978
- db,
1979
- registry
1980
- });
1981
- const filteredConsents = typeFilter.length > 0 ? consentItems.filter((consent)=>typeFilter.includes(consent.type)) : consentItems;
1982
- const isValid = 0 === typeFilter.length || typeFilter.every((t)=>filteredConsents.some((consent)=>consent.type === t && consent.isLatestPolicy));
1983
- return c.json({
1984
- subject: {
1985
- id: subject.id,
1986
- externalId: subject.externalId ?? void 0,
1987
- createdAt: subject.createdAt
1988
- },
1989
- consents: filteredConsents,
1990
- isValid
1991
- });
1992
- } catch (error) {
1993
- logger.error('Error in GET /subjects/:id handler', {
1994
- error: extractErrorMessage(error),
1995
- errorType: error instanceof Error ? error.constructor.name : typeof error
1996
- });
1997
- if (error instanceof HTTPException) throw error;
1998
- throw new HTTPException(500, {
1999
- message: 'Internal server error',
2000
- cause: {
2001
- code: 'INTERNAL_SERVER_ERROR'
2002
- }
2003
- });
841
+ function composePacks(...packs) {
842
+ const seen = new Set();
843
+ const result = [];
844
+ for (const pack of packs)for (const policy of pack)if (!seen.has(policy.id)) {
845
+ seen.add(policy.id);
846
+ result.push(policy);
2004
847
  }
2005
- };
2006
- const listSubjectsHandler = async (c)=>{
2007
- const ctx = c.get('c15tContext');
2008
- const logger = ctx.logger;
2009
- logger.info('Handling GET /subjects request');
2010
- const { db, registry } = ctx;
2011
- if (!ctx.apiKeyAuthenticated) throw new HTTPException(401, {
2012
- message: 'API key required. Use Authorization: Bearer <api_key>',
2013
- cause: {
2014
- code: 'UNAUTHORIZED'
2015
- }
2016
- });
2017
- const externalId = c.req.query('externalId');
2018
- if (!externalId) throw new HTTPException(422, {
2019
- message: 'externalId query parameter is required',
2020
- cause: {
2021
- code: 'EXTERNAL_ID_REQUIRED'
2022
- }
2023
- });
2024
- logger.debug('Request parameters', {
2025
- externalId
2026
- });
2027
- try {
2028
- const subjects = await db.findMany('subject', {
2029
- where: (b)=>b('externalId', '=', externalId)
2030
- });
2031
- const subjectItems = await Promise.all(subjects.map(async (subject)=>{
2032
- const consents = await db.findMany('consent', {
2033
- where: (b)=>b('subjectId', '=', subject.id)
2034
- });
2035
- const consentItems = await enrichConsents(consents, {
2036
- db,
2037
- registry
2038
- });
2039
- return {
2040
- id: subject.id,
2041
- externalId: subject.externalId ?? externalId,
2042
- createdAt: subject.createdAt,
2043
- consents: consentItems
2044
- };
2045
- }));
2046
- logger.info('Found subjects for externalId', {
2047
- externalId,
2048
- count: subjectItems.length
2049
- });
2050
- return c.json({
2051
- subjects: subjectItems
2052
- });
2053
- } catch (error) {
2054
- logger.error('Error in GET /subjects handler', {
2055
- error: extractErrorMessage(error),
2056
- errorType: error instanceof Error ? error.constructor.name : typeof error
2057
- });
2058
- if (error instanceof HTTPException) throw error;
2059
- throw new HTTPException(500, {
2060
- message: 'Internal server error',
2061
- cause: {
2062
- code: 'INTERNAL_SERVER_ERROR'
2063
- }
2064
- });
2065
- }
2066
- };
2067
- const utils_prefixes = {
2068
- auditLog: 'log',
2069
- consent: 'cns',
2070
- consentPolicy: 'pol',
2071
- consentPurpose: 'pur',
2072
- domain: 'dom',
2073
- subject: 'sub'
2074
- };
2075
- const utils_b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
2076
- function utils_generateId(model) {
2077
- const buf = crypto.getRandomValues(new Uint8Array(20));
2078
- const prefix = utils_prefixes[model];
2079
- const EPOCH_TIMESTAMP = 1700000000000;
2080
- const t = Date.now() - EPOCH_TIMESTAMP;
2081
- const high = Math.floor(t / 0x100000000);
2082
- const low = t >>> 0;
2083
- buf[0] = high >>> 24 & 255;
2084
- buf[1] = high >>> 16 & 255;
2085
- buf[2] = high >>> 8 & 255;
2086
- buf[3] = 255 & high;
2087
- buf[4] = low >>> 24 & 255;
2088
- buf[5] = low >>> 16 & 255;
2089
- buf[6] = low >>> 8 & 255;
2090
- buf[7] = 255 & low;
2091
- return `${prefix}_${utils_b58.encode(buf)}`;
848
+ return result;
2092
849
  }
2093
- async function utils_generateUniqueId(db, model, ctx, options = {}) {
2094
- const { maxRetries = 10, attempt = 0, baseDelay = 5 } = options;
2095
- if (attempt >= maxRetries) {
2096
- const error = new Error(`Failed to generate unique ID for ${model} after ${maxRetries} attempts`);
2097
- ctx?.logger?.error?.('ID generation failed', {
2098
- model,
2099
- maxRetries
2100
- });
2101
- throw error;
2102
- }
2103
- const id = utils_generateId(model);
2104
- try {
2105
- const existing = await db.findFirst(model, {
2106
- where: (b)=>b('id', '=', id)
2107
- });
2108
- if (existing) {
2109
- ctx?.logger?.debug?.('ID conflict detected', {
2110
- id,
2111
- model,
2112
- attempt: attempt + 1,
2113
- maxRetries
2114
- });
2115
- const delay = Math.min(baseDelay * 2 ** attempt, 1000);
2116
- await new Promise((resolve)=>setTimeout(resolve, delay));
2117
- return utils_generateUniqueId(db, model, ctx, {
2118
- maxRetries,
2119
- attempt: attempt + 1,
2120
- baseDelay
2121
- });
2122
- }
2123
- return id;
2124
- } catch (error) {
2125
- ctx?.logger?.error?.('Error checking ID uniqueness', {
2126
- error: error.message,
2127
- model,
2128
- attempt
2129
- });
2130
- if (attempt < maxRetries - 1) {
2131
- const delay = Math.min(baseDelay * 2 ** attempt, 2000);
2132
- await new Promise((resolve)=>setTimeout(resolve, delay));
2133
- return utils_generateUniqueId(db, model, ctx, {
2134
- maxRetries,
2135
- attempt: attempt + 1,
2136
- baseDelay
2137
- });
2138
- }
2139
- throw error;
2140
- }
2141
- }
2142
- const patchSubjectHandler = async (c)=>{
2143
- const ctx = c.get('c15tContext');
2144
- const logger = ctx.logger;
2145
- logger.info('Handling PATCH /subjects/:id request');
2146
- const { db } = ctx;
2147
- const subjectId = c.req.param('id');
2148
- const body = await c.req.json();
2149
- const { externalId, identityProvider = 'external' } = body;
2150
- logger.debug('Request parameters', {
2151
- subjectId,
2152
- externalId,
2153
- identityProvider
2154
- });
2155
- try {
2156
- const subject = await db.findFirst('subject', {
2157
- where: (b)=>b('id', '=', subjectId)
2158
- });
2159
- if (!subject) throw new HTTPException(404, {
2160
- message: 'Subject not found',
2161
- cause: {
2162
- code: 'SUBJECT_NOT_FOUND',
2163
- subjectId
2164
- }
2165
- });
2166
- await db.transaction(async (tx)=>{
2167
- await tx.updateMany('subject', {
2168
- where: (b)=>b('id', '=', subjectId),
2169
- set: {
2170
- externalId,
2171
- identityProvider,
2172
- updatedAt: new Date()
2173
- }
2174
- });
2175
- await tx.create('auditLog', {
2176
- id: await utils_generateUniqueId(tx, 'auditLog', ctx),
2177
- subjectId,
2178
- entityType: 'subject',
2179
- entityId: subjectId,
2180
- actionType: 'identify_user',
2181
- ipAddress: ctx.ipAddress || null,
2182
- userAgent: ctx.userAgent || null,
2183
- changes: {
2184
- externalId: {
2185
- from: subject.externalId,
2186
- to: externalId
2187
- },
2188
- identityProvider: {
2189
- from: subject.identityProvider,
2190
- to: identityProvider
2191
- }
2192
- },
2193
- metadata: {
2194
- externalId,
2195
- identityProvider
2196
- }
2197
- });
2198
- });
2199
- logger.info('Subject linked to external ID', {
2200
- subjectId,
2201
- externalId,
2202
- identityProvider
2203
- });
2204
- getMetrics()?.recordSubjectLinked(identityProvider);
2205
- return c.json({
2206
- success: true,
2207
- subject: {
2208
- id: subjectId,
2209
- externalId
2210
- }
2211
- });
2212
- } catch (error) {
2213
- logger.error('Error in PATCH /subjects/:id handler', {
2214
- error: extractErrorMessage(error),
2215
- errorType: error instanceof Error ? error.constructor.name : typeof error
2216
- });
2217
- if (error instanceof HTTPException) throw error;
2218
- throw new HTTPException(500, {
2219
- message: 'Internal server error',
2220
- cause: {
2221
- code: 'INTERNAL_SERVER_ERROR'
2222
- }
2223
- });
2224
- }
2225
- };
2226
- const postSubjectHandler = async (c)=>{
2227
- const ctx = c.get('c15tContext');
2228
- const logger = ctx.logger;
2229
- logger.info('Handling POST /subjects request');
2230
- const { db, registry } = ctx;
2231
- const input = await c.req.json();
2232
- const { type, subjectId, identityProvider, externalSubjectId, domain, metadata, givenAt: givenAtEpoch } = input;
2233
- const preferences = 'preferences' in input ? input.preferences : void 0;
2234
- const givenAt = new Date(givenAtEpoch);
2235
- const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
2236
- let derivedConsentAction;
2237
- if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
2238
- else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
2239
- else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
2240
- logger.debug('Request parameters', {
2241
- type,
2242
- subjectId,
2243
- identityProvider,
2244
- externalSubjectId,
2245
- domain
2246
- });
2247
- try {
2248
- const subject = await registry.findOrCreateSubject({
2249
- subjectId,
2250
- externalSubjectId,
2251
- identityProvider,
2252
- ipAddress: ctx.ipAddress
2253
- });
2254
- if (!subject) throw new HTTPException(500, {
2255
- message: 'Failed to create subject',
2256
- cause: {
2257
- code: 'SUBJECT_CREATION_FAILED',
2258
- subjectId
2259
- }
2260
- });
2261
- logger.debug('Subject found/created', {
2262
- subjectId: subject.id
2263
- });
2264
- const domainRecord = await registry.findOrCreateDomain(domain);
2265
- if (!domainRecord) throw new HTTPException(500, {
2266
- message: 'Failed to create domain',
2267
- cause: {
2268
- code: 'DOMAIN_CREATION_FAILED',
2269
- domain
2270
- }
2271
- });
2272
- let policyId;
2273
- let purposeIds = [];
2274
- const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
2275
- if (inputPolicyId) {
2276
- policyId = inputPolicyId;
2277
- const policy = await registry.findConsentPolicyById(inputPolicyId);
2278
- if (!policy) throw new HTTPException(404, {
2279
- message: 'Policy not found',
2280
- cause: {
2281
- code: 'POLICY_NOT_FOUND',
2282
- policyId,
2283
- type
2284
- }
2285
- });
2286
- if (!policy.isActive) throw new HTTPException(400, {
2287
- message: 'Policy is inactive',
2288
- cause: {
2289
- code: 'POLICY_INACTIVE',
2290
- policyId,
2291
- type
2292
- }
2293
- });
2294
- } else {
2295
- const policy = await registry.findOrCreatePolicy(type);
2296
- if (!policy) throw new HTTPException(500, {
2297
- message: 'Failed to create policy',
2298
- cause: {
2299
- code: 'POLICY_CREATION_FAILED',
2300
- type
2301
- }
2302
- });
2303
- policyId = policy.id;
2304
- }
2305
- if (preferences) {
2306
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
2307
- logger.debug('Consented purposes', {
2308
- consentedPurposes
2309
- });
2310
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2311
- const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
2312
- logger.debug('Filtered purposes', {
2313
- purposes
2314
- });
2315
- if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
2316
- consentedPurposes
2317
- });
2318
- purposeIds = purposes;
2319
- }
2320
- const existingConsent = await db.findFirst('consent', {
2321
- where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
2322
- });
2323
- if (existingConsent) {
2324
- logger.debug('Duplicate consent detected, returning existing record', {
2325
- consentId: existingConsent.id
2326
- });
2327
- return c.json({
2328
- subjectId: subject.id,
2329
- consentId: existingConsent.id,
2330
- domainId: domainRecord.id,
2331
- domain: domainRecord.name,
2332
- type,
2333
- metadata,
2334
- uiSource: input.uiSource,
2335
- givenAt: existingConsent.givenAt
2336
- });
2337
- }
2338
- const result = await db.transaction(async (tx)=>{
2339
- logger.debug('Creating consent record', {
2340
- subjectId: subject.id,
2341
- domainId: domainRecord.id,
2342
- policyId,
2343
- purposeIds
2344
- });
2345
- const consentRecord = await tx.create('consent', {
2346
- id: await utils_generateUniqueId(tx, 'consent', ctx),
2347
- subjectId: subject.id,
2348
- domainId: domainRecord.id,
2349
- policyId,
2350
- purposeIds: {
2351
- json: purposeIds
2352
- },
2353
- metadata: metadata ? {
2354
- json: metadata
2355
- } : void 0,
2356
- ipAddress: ctx.ipAddress,
2357
- userAgent: ctx.userAgent,
2358
- jurisdiction: input.jurisdiction,
2359
- jurisdictionModel: input.jurisdictionModel,
2360
- tcString: input.tcString,
2361
- uiSource: input.uiSource,
2362
- consentAction: derivedConsentAction,
2363
- givenAt
2364
- });
2365
- logger.debug('Created consent', {
2366
- consentRecord: consentRecord.id
2367
- });
2368
- if (!consentRecord) throw new HTTPException(500, {
2369
- message: 'Failed to create consent',
2370
- cause: {
2371
- code: 'CONSENT_CREATION_FAILED',
2372
- subjectId: subject.id,
2373
- domain
2374
- }
2375
- });
2376
- return {
2377
- consent: consentRecord
2378
- };
2379
- });
2380
- const metrics = getMetrics();
2381
- if (metrics) {
2382
- const jurisdiction = input.jurisdiction;
2383
- metrics.recordConsentCreated({
2384
- type,
2385
- jurisdiction
2386
- });
2387
- const hasAccepted = preferences && Object.values(preferences).some(Boolean);
2388
- if (hasAccepted) metrics.recordConsentAccepted({
2389
- type,
2390
- jurisdiction
2391
- });
2392
- else metrics.recordConsentRejected({
2393
- type,
2394
- jurisdiction
2395
- });
2396
- }
2397
- return c.json({
2398
- subjectId: subject.id,
2399
- consentId: result.consent.id,
2400
- domainId: domainRecord.id,
2401
- domain: domainRecord.name,
2402
- type,
2403
- metadata,
2404
- uiSource: input.uiSource,
2405
- givenAt: result.consent.givenAt
2406
- });
2407
- } catch (error) {
2408
- logger.error('Error in POST /subjects handler', {
2409
- error: extractErrorMessage(error),
2410
- errorType: error instanceof Error ? error.constructor.name : typeof error
2411
- });
2412
- if (error instanceof HTTPException) throw error;
2413
- throw new HTTPException(500, {
2414
- message: 'Internal server error',
2415
- cause: {
2416
- code: 'INTERNAL_SERVER_ERROR'
2417
- }
2418
- });
2419
- }
2420
- };
2421
- const createSubjectRoutes = ()=>{
2422
- const app = new Hono();
2423
- app.get('/:id', describeRoute({
2424
- summary: 'Get subject consent status',
2425
- description: `Returns the subject's consent status for this device. Use to check if the subject has valid consent for given policy types.
2426
-
2427
- **Query:** \`type\` – Filter by consent type(s), comma-separated (e.g. \`privacy_policy,cookie_banner\`).
2428
-
2429
- **Response:** \`subject\`, \`consents\` (matching filter), \`isValid\` (valid consent for requested type(s)).`,
2430
- tags: [
2431
- 'Subject',
2432
- 'Consent'
2433
- ],
2434
- responses: {
2435
- 200: {
2436
- description: 'Subject and consent records for the requested type(s)',
2437
- content: {
2438
- 'application/json': {
2439
- schema: resolver(getSubjectOutputSchema)
2440
- }
2441
- }
2442
- },
2443
- 404: {
2444
- description: 'Subject not found for the given ID'
2445
- }
2446
- }
2447
- }), validator('param', getSubjectInputSchema), getSubjectHandler);
2448
- app.post('/', describeRoute({
2449
- summary: 'Record consent for a subject',
2450
- description: `Creates a new consent record (append-only). Creates the subject if it does not exist.
2451
-
2452
- **Request body by \`type\`:**
2453
- - \`cookie_banner\` – Requires \`preferences\` object
2454
- - \`privacy_policy\`, \`dpa\`, \`terms_and_conditions\` – Optional \`policyId\`
2455
- - \`marketing_communications\`, \`age_verification\`, \`other\` – Optional \`preferences\``,
2456
- tags: [
2457
- 'Subject',
2458
- 'Consent'
2459
- ],
2460
- responses: {
2461
- 200: {
2462
- description: 'Consent recorded; subject and consent in response',
2463
- content: {
2464
- 'application/json': {
2465
- schema: resolver(postSubjectOutputSchema)
2466
- }
2467
- }
2468
- },
2469
- 422: {
2470
- description: 'Invalid request body (schema or validation failed)'
2471
- }
2472
- }
2473
- }), validator('json', postSubjectInputSchema), postSubjectHandler);
2474
- app.patch('/:id', describeRoute({
2475
- summary: 'Link external ID to subject',
2476
- description: 'Associates an external user ID with an existing subject (e.g. after login). Enables cross-device consent sync.',
2477
- tags: [
2478
- 'Subject'
2479
- ],
2480
- responses: {
2481
- 200: {
2482
- description: 'Subject updated with external ID',
2483
- content: {
2484
- 'application/json': {
2485
- schema: resolver(patchSubjectOutputSchema)
2486
- }
2487
- }
2488
- },
2489
- 404: {
2490
- description: 'Subject not found for the given ID'
2491
- }
2492
- }
2493
- }), validator('param', object({
2494
- id: subjectIdSchema
2495
- })), validator('json', object({
2496
- externalId: string(),
2497
- identityProvider: optional(string())
2498
- })), patchSubjectHandler);
2499
- app.get('/', describeRoute({
2500
- summary: 'List subjects by external ID (API key required)',
2501
- description: 'Returns all subjects linked to the given external ID. Requires Bearer token (API key). Use for server-side consent lookups.',
2502
- tags: [
2503
- 'Subject'
2504
- ],
2505
- security: [
2506
- {
2507
- bearerAuth: []
2508
- }
2509
- ],
2510
- responses: {
2511
- 200: {
2512
- description: 'List of subjects for the external ID',
2513
- content: {
2514
- 'application/json': {
2515
- schema: resolver(listSubjectsOutputSchema)
2516
- }
2517
- }
2518
- },
2519
- 401: {
2520
- description: 'Missing or invalid API key'
2521
- }
2522
- }
2523
- }), validator('query', listSubjectsQuerySchema), listSubjectsHandler);
2524
- return app;
850
+ const policyBuilder = {
851
+ create: buildPolicyConfig,
852
+ createPack: buildPolicyPack,
853
+ createPackWithDefault: buildPolicyPackWithDefault,
854
+ composePacks: composePacks
2525
855
  };
2526
- const defineConfig = (config)=>config;
2527
856
  const c15tInstance = (options)=>{
2528
857
  const context = init(options);
2529
858
  const logger = logger_createLogger(options.logger);
2530
859
  const app = new Hono();
2531
- const openApiConfig = config_createOpenAPIConfig(options);
860
+ const openApiConfig = createOpenAPIConfig(options);
2532
861
  const basePath = options.basePath || '/';
2533
862
  const corsOptions = createCORSOptions(options.trustedOrigins);
2534
863
  app.use('*', cors(corsOptions));
@@ -2559,7 +888,7 @@ const c15tInstance = (options)=>{
2559
888
  span.updateName(`${c.req.method} ${routePattern}`);
2560
889
  span.setAttribute('http.route', routePattern);
2561
890
  span.setStatus({
2562
- code: api_SpanStatusCode.OK
891
+ code: SpanStatusCode.OK
2563
892
  });
2564
893
  } else await runNext();
2565
894
  } catch (error) {
@@ -2605,9 +934,7 @@ const c15tInstance = (options)=>{
2605
934
  }));
2606
935
  const publicSpecUrl = `${basePath}${openApiConfig.specPath}`.replace(/\/+/g, '/');
2607
936
  app.get(openApiConfig.docsPath, apiReference({
2608
- spec: {
2609
- url: publicSpecUrl
2610
- },
937
+ url: publicSpecUrl,
2611
938
  pageTitle: `${options.appName || 'c15t API'} Documentation`
2612
939
  }));
2613
940
  }
@@ -2700,4 +1027,7 @@ const c15tInstance = (options)=>{
2700
1027
  getDocsUI
2701
1028
  };
2702
1029
  };
2703
- export { c15tInstance, defineConfig, version_version as version };
1030
+ export { defineConfig } from "./define-config.js";
1031
+ export { inspectPolicies } from "./583.js";
1032
+ export { version } from "./302.js";
1033
+ export { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, c15tInstance, policyBuilder, policyMatchers, policyPackPresets };