@c15t/backend 2.0.0-rc.3 → 2.0.0-rc.5

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 (314) hide show
  1. package/dist/cache.cjs +4 -4
  2. package/dist/cache.js +4 -4
  3. package/dist/core.cjs +845 -87
  4. package/dist/core.js +821 -87
  5. package/dist/db/schema.cjs +37 -0
  6. package/dist/db/schema.js +33 -2
  7. package/dist/edge.cjs +1106 -0
  8. package/dist/edge.js +1069 -0
  9. package/dist/router.cjs +621 -71
  10. package/dist/router.js +621 -71
  11. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  12. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  13. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  14. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  15. package/{dist → dist-types}/cache/gvl-resolver.d.ts +1 -2
  16. package/{dist → dist-types}/cache/index.d.ts +0 -1
  17. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  18. package/{dist → dist-types}/cache/types.d.ts +0 -1
  19. package/{dist → dist-types}/core.d.ts +8 -1
  20. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  21. package/{dist → dist-types}/db/registry/consent-policy.d.ts +0 -1
  22. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  23. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  24. package/{dist → dist-types}/db/registry/index.d.ts +22 -2
  25. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  26. package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
  27. package/{dist → dist-types}/db/registry/types.d.ts +1 -2
  28. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  29. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  30. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  31. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  32. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  33. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  34. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
  35. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  36. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -1
  37. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
  38. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
  39. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +1 -2
  40. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
  41. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +5 -2
  42. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
  43. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +432 -17
  44. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  45. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -2
  46. package/{dist → dist-types}/db/schema/index.d.ts +862 -33
  47. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  48. package/dist-types/define-config.d.ts +17 -0
  49. package/dist-types/edge/index.d.ts +5 -0
  50. package/dist-types/edge/init-handler.d.ts +38 -0
  51. package/dist-types/edge/resolve-consent.d.ts +80 -0
  52. package/dist-types/edge/types.d.ts +13 -0
  53. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  54. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  55. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  56. package/{dist → dist-types}/handlers/init/index.d.ts +4 -5
  57. package/dist-types/handlers/init/policy.d.ts +26 -0
  58. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  59. package/dist-types/handlers/init/translations.d.ts +48 -0
  60. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  61. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  62. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  63. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +0 -1
  64. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  65. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +0 -1
  66. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
  67. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  68. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
  69. package/{dist → dist-types}/init.d.ts +4 -7
  70. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  71. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  72. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  73. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  74. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +1 -2
  75. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  76. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  77. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  78. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  79. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  80. package/dist-types/policies/builder.d.ts +127 -0
  81. package/dist-types/policies/defaults.d.ts +2 -0
  82. package/dist-types/policies/matchers.d.ts +3 -0
  83. package/{dist → dist-types}/router.d.ts +0 -1
  84. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  85. package/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
  86. package/{dist → dist-types}/routes/init.d.ts +0 -1
  87. package/{dist → dist-types}/routes/status.d.ts +0 -1
  88. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  89. package/{dist → dist-types}/types/api.d.ts +0 -1
  90. package/dist-types/types/index.d.ts +443 -0
  91. package/dist-types/utils/background.d.ts +6 -0
  92. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
  93. package/{dist → dist-types}/utils/env.d.ts +0 -1
  94. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  95. package/{dist → dist-types}/utils/instrumentation.d.ts +0 -1
  96. package/{dist → dist-types}/utils/logger.d.ts +1 -2
  97. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  98. package/dist-types/version.d.ts +1 -0
  99. package/docs/README.md +49 -0
  100. package/docs/api/configuration.md +197 -0
  101. package/docs/api/endpoints.md +211 -0
  102. package/docs/guides/caching.md +85 -0
  103. package/docs/guides/database-setup.md +128 -0
  104. package/docs/guides/edge-deployment.md +248 -0
  105. package/docs/guides/framework-integration.md +142 -0
  106. package/docs/guides/iab-tcf.md +89 -0
  107. package/docs/guides/observability.md +96 -0
  108. package/docs/guides/policy-packs.md +396 -0
  109. package/docs/quickstart.md +129 -0
  110. package/package.json +37 -23
  111. package/.turbo/turbo-build.log +0 -49
  112. package/CHANGELOG.md +0 -115
  113. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  114. package/dist/cache/adapters/index.d.ts.map +0 -1
  115. package/dist/cache/adapters/memory.d.ts.map +0 -1
  116. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  117. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  118. package/dist/cache/index.d.ts.map +0 -1
  119. package/dist/cache/keys.d.ts.map +0 -1
  120. package/dist/cache/types.d.ts.map +0 -1
  121. package/dist/core.d.ts.map +0 -1
  122. package/dist/db/adapters/drizzle.d.ts +0 -2
  123. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  124. package/dist/db/adapters/index.d.ts +0 -2
  125. package/dist/db/adapters/index.d.ts.map +0 -1
  126. package/dist/db/adapters/kysely.d.ts +0 -2
  127. package/dist/db/adapters/kysely.d.ts.map +0 -1
  128. package/dist/db/adapters/mongo.d.ts +0 -2
  129. package/dist/db/adapters/mongo.d.ts.map +0 -1
  130. package/dist/db/adapters/prisma.d.ts +0 -2
  131. package/dist/db/adapters/prisma.d.ts.map +0 -1
  132. package/dist/db/adapters/typeorm.d.ts +0 -2
  133. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  134. package/dist/db/migrator/index.d.ts.map +0 -1
  135. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  136. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  137. package/dist/db/registry/domain.d.ts.map +0 -1
  138. package/dist/db/registry/index.d.ts.map +0 -1
  139. package/dist/db/registry/subject.d.ts.map +0 -1
  140. package/dist/db/registry/types.d.ts.map +0 -1
  141. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  142. package/dist/db/registry/utils.d.ts.map +0 -1
  143. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  144. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  145. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  146. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  147. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  148. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  149. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  150. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  151. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  152. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  153. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  154. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  155. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  156. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  157. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  158. package/dist/db/schema/index.d.ts.map +0 -1
  159. package/dist/db/tenant-scope.d.ts.map +0 -1
  160. package/dist/define-config.d.ts +0 -5
  161. package/dist/define-config.d.ts.map +0 -1
  162. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  163. package/dist/handlers/consent/index.d.ts +0 -12
  164. package/dist/handlers/consent/index.d.ts.map +0 -1
  165. package/dist/handlers/init/geo.d.ts.map +0 -1
  166. package/dist/handlers/init/index.d.ts.map +0 -1
  167. package/dist/handlers/init/translations.d.ts +0 -28
  168. package/dist/handlers/init/translations.d.ts.map +0 -1
  169. package/dist/handlers/status/index.d.ts +0 -7
  170. package/dist/handlers/status/index.d.ts.map +0 -1
  171. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  172. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  173. package/dist/handlers/subject/index.d.ts +0 -10
  174. package/dist/handlers/subject/index.d.ts.map +0 -1
  175. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  176. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  177. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  178. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  179. package/dist/init.d.ts.map +0 -1
  180. package/dist/middleware/auth/index.d.ts.map +0 -1
  181. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  182. package/dist/middleware/cors/cors.d.ts.map +0 -1
  183. package/dist/middleware/cors/index.d.ts +0 -30
  184. package/dist/middleware/cors/index.d.ts.map +0 -1
  185. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  186. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  187. package/dist/middleware/openapi/config.d.ts.map +0 -1
  188. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  189. package/dist/middleware/openapi/index.d.ts +0 -12
  190. package/dist/middleware/openapi/index.d.ts.map +0 -1
  191. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  192. package/dist/router.d.ts.map +0 -1
  193. package/dist/routes/consent.d.ts.map +0 -1
  194. package/dist/routes/index.d.ts +0 -10
  195. package/dist/routes/index.d.ts.map +0 -1
  196. package/dist/routes/init.d.ts.map +0 -1
  197. package/dist/routes/status.d.ts.map +0 -1
  198. package/dist/routes/subject.d.ts.map +0 -1
  199. package/dist/types/api.d.ts.map +0 -1
  200. package/dist/types/index.d.ts +0 -263
  201. package/dist/types/index.d.ts.map +0 -1
  202. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  203. package/dist/utils/env.d.ts.map +0 -1
  204. package/dist/utils/extract-error-message.d.ts.map +0 -1
  205. package/dist/utils/index.d.ts +0 -4
  206. package/dist/utils/index.d.ts.map +0 -1
  207. package/dist/utils/instrumentation.d.ts.map +0 -1
  208. package/dist/utils/logger.d.ts.map +0 -1
  209. package/dist/utils/metrics.d.ts.map +0 -1
  210. package/dist/version.d.ts +0 -2
  211. package/dist/version.d.ts.map +0 -1
  212. package/knip.json +0 -31
  213. package/rslib.config.ts +0 -93
  214. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  215. package/src/cache/adapters/index.ts +0 -22
  216. package/src/cache/adapters/memory.ts +0 -111
  217. package/src/cache/adapters/upstash-redis.ts +0 -113
  218. package/src/cache/gvl-resolver.ts +0 -289
  219. package/src/cache/index.ts +0 -34
  220. package/src/cache/keys.ts +0 -68
  221. package/src/cache/types.ts +0 -66
  222. package/src/core.ts +0 -369
  223. package/src/db/migrator/index.ts +0 -80
  224. package/src/db/registry/consent-policy.test.ts +0 -451
  225. package/src/db/registry/consent-policy.ts +0 -82
  226. package/src/db/registry/consent-purpose.test.ts +0 -428
  227. package/src/db/registry/consent-purpose.ts +0 -61
  228. package/src/db/registry/domain.test.ts +0 -445
  229. package/src/db/registry/domain.ts +0 -91
  230. package/src/db/registry/index.ts +0 -14
  231. package/src/db/registry/subject.test.ts +0 -371
  232. package/src/db/registry/subject.ts +0 -126
  233. package/src/db/registry/types.ts +0 -10
  234. package/src/db/registry/utils/generate-id.test.ts +0 -216
  235. package/src/db/registry/utils/generate-id.ts +0 -133
  236. package/src/db/registry/utils.ts +0 -133
  237. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  238. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  239. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  240. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  241. package/src/db/schema/1.0.0/consent.ts +0 -20
  242. package/src/db/schema/1.0.0/domain.ts +0 -12
  243. package/src/db/schema/1.0.0/index.ts +0 -48
  244. package/src/db/schema/1.0.0/subject.ts +0 -11
  245. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  246. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  247. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  248. package/src/db/schema/2.0.0/consent.ts +0 -28
  249. package/src/db/schema/2.0.0/domain.ts +0 -12
  250. package/src/db/schema/2.0.0/index.ts +0 -47
  251. package/src/db/schema/2.0.0/subject.ts +0 -13
  252. package/src/db/schema/index.ts +0 -15
  253. package/src/db/tenant-scope.test.ts +0 -747
  254. package/src/db/tenant-scope.ts +0 -103
  255. package/src/define-config.ts +0 -5
  256. package/src/handlers/consent/check.handler.ts +0 -126
  257. package/src/handlers/init/geo.test.ts +0 -317
  258. package/src/handlers/init/geo.ts +0 -195
  259. package/src/handlers/init/index.test.ts +0 -205
  260. package/src/handlers/init/index.ts +0 -114
  261. package/src/handlers/init/translations.test.ts +0 -121
  262. package/src/handlers/init/translations.ts +0 -72
  263. package/src/handlers/status/status.handler.test.ts +0 -155
  264. package/src/handlers/status/status.handler.ts +0 -51
  265. package/src/handlers/subject/get.handler.ts +0 -92
  266. package/src/handlers/subject/list.handler.ts +0 -92
  267. package/src/handlers/subject/patch.handler.ts +0 -119
  268. package/src/handlers/subject/post.handler.test.ts +0 -294
  269. package/src/handlers/subject/post.handler.ts +0 -268
  270. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  271. package/src/handlers/utils/consent-enrichment.ts +0 -218
  272. package/src/init.test.ts +0 -126
  273. package/src/init.ts +0 -87
  274. package/src/middleware/auth/index.ts +0 -11
  275. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  276. package/src/middleware/auth/validate-api-key.ts +0 -107
  277. package/src/middleware/cors/cors.test.ts +0 -135
  278. package/src/middleware/cors/cors.ts +0 -186
  279. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  280. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  281. package/src/middleware/cors/process-cors.ts +0 -91
  282. package/src/middleware/openapi/config.ts +0 -29
  283. package/src/middleware/openapi/handlers.ts +0 -34
  284. package/src/middleware/process-ip/index.test.ts +0 -195
  285. package/src/middleware/process-ip/index.ts +0 -199
  286. package/src/router.ts +0 -15
  287. package/src/routes/consent.ts +0 -52
  288. package/src/routes/init.ts +0 -105
  289. package/src/routes/status.ts +0 -46
  290. package/src/routes/subject.ts +0 -152
  291. package/src/types/api.ts +0 -48
  292. package/src/types/index.ts +0 -297
  293. package/src/utils/create-telemetry-options.test.ts +0 -302
  294. package/src/utils/create-telemetry-options.ts +0 -229
  295. package/src/utils/env.ts +0 -84
  296. package/src/utils/extract-error-message.ts +0 -21
  297. package/src/utils/instrumentation.test.ts +0 -185
  298. package/src/utils/instrumentation.ts +0 -196
  299. package/src/utils/logger.ts +0 -41
  300. package/src/utils/metrics.test.ts +0 -323
  301. package/src/utils/metrics.ts +0 -402
  302. package/src/utils/telemetry-pii.test.ts +0 -325
  303. package/src/version.ts +0 -2
  304. package/tsconfig.json +0 -11
  305. package/vitest.config.ts +0 -28
  306. /package/dist/{types.cjs → types/index.cjs} +0 -0
  307. /package/dist/{types.js → types/index.js} +0 -0
  308. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  309. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  310. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  311. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  312. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  313. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  314. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/router.cjs CHANGED
@@ -54,18 +54,18 @@ const api_namespaceObject = require("@opentelemetry/api");
54
54
  let cachedConfig = null;
55
55
  let cachedDefaultAttributes = {};
56
56
  function create_telemetry_options_isTelemetryEnabled(options) {
57
- if (options) return options.advanced?.telemetry?.enabled === true;
57
+ if (options) return options.telemetry?.enabled === true;
58
58
  return cachedConfig?.enabled === true;
59
59
  }
60
60
  const create_telemetry_options_getTracer = (options)=>{
61
61
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.trace.getTracer('c15t-noop');
62
- const tracer = options?.advanced?.telemetry?.tracer ?? cachedConfig?.tracer;
62
+ const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
63
63
  if (tracer) return tracer;
64
64
  return api_namespaceObject.trace.getTracer(options?.appName ?? 'c15t');
65
65
  };
66
66
  const getMeter = (options)=>{
67
67
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.metrics.getMeter('c15t-noop');
68
- const meter = options?.advanced?.telemetry?.meter ?? cachedConfig?.meter;
68
+ const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
69
69
  if (meter) return meter;
70
70
  return api_namespaceObject.metrics.getMeter(options?.appName ?? 'c15t');
71
71
  };
@@ -470,7 +470,7 @@ async function executeWithSpan(span, operation) {
470
470
  }
471
471
  }
472
472
  function resolveDefaultAttributes(options) {
473
- return options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
473
+ return options?.telemetry?.defaultAttributes || getDefaultAttributes();
474
474
  }
475
475
  async function withExternalSpan(attributes, operation, options) {
476
476
  if (!create_telemetry_options_isTelemetryEnabled(options)) return operation();
@@ -646,6 +646,124 @@ function createGVLResolver(options) {
646
646
  }
647
647
  };
648
648
  }
649
+ const external_jose_namespaceObject = require("jose");
650
+ const POLICY_SNAPSHOT_JWT_HEADER = {
651
+ alg: 'HS256',
652
+ typ: 'JWT'
653
+ };
654
+ const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
655
+ const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
656
+ function resolveSnapshotIssuer(options) {
657
+ return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
658
+ }
659
+ function resolveSnapshotAudience(params) {
660
+ const configuredAudience = params.options?.audience?.trim();
661
+ if (configuredAudience) return configuredAudience;
662
+ return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
663
+ }
664
+ function getSigningKey(secret) {
665
+ return new TextEncoder().encode(secret);
666
+ }
667
+ function isPolicySnapshotPayload(payload) {
668
+ return 'string' == typeof payload.policyId && 'string' == typeof payload.fingerprint && 'string' == typeof payload.matchedBy && 'string' == typeof payload.jurisdiction && 'string' == typeof payload.model && 'string' == typeof payload.iss && 'string' == typeof payload.aud && 'string' == typeof payload.sub && 'number' == typeof payload.iat && 'number' == typeof payload.exp;
669
+ }
670
+ async function createPolicySnapshotToken(params) {
671
+ const { options } = params;
672
+ if (!options?.signingKey) return;
673
+ const iat = Math.floor(Date.now() / 1000);
674
+ const ttlSeconds = options.ttlSeconds ?? 1800;
675
+ const exp = iat + ttlSeconds;
676
+ const iss = resolveSnapshotIssuer(options);
677
+ const aud = resolveSnapshotAudience({
678
+ options,
679
+ tenantId: params.tenantId
680
+ });
681
+ const payload = {
682
+ iss,
683
+ aud,
684
+ sub: params.policyId,
685
+ tenantId: params.tenantId,
686
+ policyId: params.policyId,
687
+ fingerprint: params.fingerprint,
688
+ matchedBy: params.matchedBy,
689
+ country: params.country,
690
+ region: params.region,
691
+ jurisdiction: params.jurisdiction,
692
+ language: params.language,
693
+ model: params.model,
694
+ policyI18n: params.policyI18n,
695
+ expiryDays: params.expiryDays,
696
+ scopeMode: params.scopeMode,
697
+ uiMode: params.uiMode,
698
+ bannerUi: params.bannerUi,
699
+ dialogUi: params.dialogUi,
700
+ categories: params.categories,
701
+ preselectedCategories: params.preselectedCategories,
702
+ gpc: params.gpc,
703
+ proofConfig: params.proofConfig,
704
+ iat,
705
+ exp
706
+ };
707
+ const token = await new external_jose_namespaceObject.SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
708
+ return {
709
+ token,
710
+ payload
711
+ };
712
+ }
713
+ async function verifyPolicySnapshotToken(params) {
714
+ const { token, options, tenantId } = params;
715
+ if (!options?.signingKey) return {
716
+ valid: false,
717
+ reason: 'missing'
718
+ };
719
+ if (!token) return {
720
+ valid: false,
721
+ reason: 'missing'
722
+ };
723
+ if (3 !== token.split('.').length) return {
724
+ valid: false,
725
+ reason: 'malformed'
726
+ };
727
+ try {
728
+ const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, getSigningKey(options.signingKey), {
729
+ issuer: resolveSnapshotIssuer(options),
730
+ audience: resolveSnapshotAudience({
731
+ options,
732
+ tenantId
733
+ })
734
+ });
735
+ const header = protectedHeader;
736
+ if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
737
+ valid: false,
738
+ reason: 'invalid'
739
+ };
740
+ if (!isPolicySnapshotPayload(payload)) return {
741
+ valid: false,
742
+ reason: 'invalid'
743
+ };
744
+ if (payload.sub !== payload.policyId) return {
745
+ valid: false,
746
+ reason: 'invalid'
747
+ };
748
+ if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
749
+ valid: false,
750
+ reason: 'invalid'
751
+ };
752
+ return {
753
+ valid: true,
754
+ payload
755
+ };
756
+ } catch (error) {
757
+ if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
758
+ valid: false,
759
+ reason: 'expired'
760
+ };
761
+ return {
762
+ valid: false,
763
+ reason: 'invalid'
764
+ };
765
+ }
766
+ }
649
767
  function geo_normalizeHeader(value) {
650
768
  if (!value) return null;
651
769
  return Array.isArray(value) ? value[0] ?? null : value;
@@ -787,7 +905,7 @@ function checkJurisdiction(countryCode, regionCode) {
787
905
  return jurisdiction;
788
906
  }
789
907
  async function getLocation(request, options) {
790
- if (options.advanced?.disableGeoLocation) return {
908
+ if (options.disableGeoLocation) return {
791
909
  countryCode: null,
792
910
  regionCode: null
793
911
  };
@@ -798,30 +916,253 @@ async function getLocation(request, options) {
798
916
  };
799
917
  }
800
918
  function getJurisdiction(location, options) {
801
- if (options.advanced?.disableGeoLocation) return 'GDPR';
919
+ if (options.disableGeoLocation) return 'GDPR';
802
920
  return checkJurisdiction(location.countryCode, location.regionCode);
803
921
  }
922
+ const schema_types_namespaceObject = require("@c15t/schema/types");
923
+ async function resolvePolicyDecision(params) {
924
+ return (0, schema_types_namespaceObject.resolvePolicyDecision)({
925
+ policies: params.policies,
926
+ countryCode: params.countryCode,
927
+ regionCode: params.regionCode,
928
+ jurisdiction: params.jurisdiction,
929
+ iabEnabled: params.iabEnabled
930
+ });
931
+ }
804
932
  const translations_namespaceObject = require("@c15t/translations");
933
+ const all_namespaceObject = require("@c15t/translations/all");
934
+ const DEFAULT_PROFILE = 'default';
935
+ const warnedKeys = new Set();
805
936
  function isSupportedBaseLanguage(lang) {
806
- return lang in translations_namespaceObject.baseTranslations;
937
+ return lang in all_namespaceObject.baseTranslations;
938
+ }
939
+ function warnOnce(logger, key, message, metadata) {
940
+ if (!logger || warnedKeys.has(key)) return;
941
+ warnedKeys.add(key);
942
+ logger.warn(message, metadata);
807
943
  }
808
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
809
- const supportedDefaultLanguages = Object.keys(translations_namespaceObject.baseTranslations);
810
- const supportedCustomLanguages = Object.keys(customTranslations || {});
811
- const supportedLanguages = [
812
- ...supportedDefaultLanguages,
813
- ...supportedCustomLanguages
944
+ function normalizeLanguage(value) {
945
+ if (!value) return;
946
+ const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
947
+ if (!normalized) return;
948
+ return normalized.split('-')[0] ?? void 0;
949
+ }
950
+ function normalizeProfiles(params) {
951
+ const profiles = params.i18n?.messages;
952
+ const legacy = params.customTranslations;
953
+ if (profiles && Object.keys(profiles).length > 0) {
954
+ if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
955
+ return profiles;
956
+ }
957
+ if (legacy && Object.keys(legacy).length > 0) {
958
+ warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
959
+ return {
960
+ [DEFAULT_PROFILE]: {
961
+ translations: legacy
962
+ }
963
+ };
964
+ }
965
+ return {};
966
+ }
967
+ function buildCandidates(input) {
968
+ const raw = [
969
+ {
970
+ language: input.language,
971
+ reason: 'profile_language'
972
+ },
973
+ {
974
+ language: input.fallbackLanguage,
975
+ reason: 'profile_fallback'
976
+ }
814
977
  ];
815
- const preferredLanguage = (0, translations_namespaceObject.selectLanguage)(supportedLanguages, {
978
+ const dedupe = new Set();
979
+ return raw.filter((candidate)=>{
980
+ const key = candidate.language;
981
+ if (dedupe.has(key)) return false;
982
+ dedupe.add(key);
983
+ return true;
984
+ });
985
+ }
986
+ function getProfileLanguages(profiles, profile) {
987
+ return Object.keys(profiles[profile]?.translations ?? {}).sort();
988
+ }
989
+ function getSelectableLanguages(input) {
990
+ return getProfileLanguages(input.profiles, input.profile);
991
+ }
992
+ function resolveFallbackLanguage(input) {
993
+ const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
994
+ const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
995
+ if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
996
+ if (profileLanguages.includes('en')) return 'en';
997
+ return profileLanguages[0] ?? configuredFallbackLanguage;
998
+ }
999
+ function resolveActiveProfile(input) {
1000
+ const requestedProfile = input.policyProfile ?? input.defaultProfile;
1001
+ if (input.profiles[requestedProfile]) return requestedProfile;
1002
+ if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
1003
+ return input.defaultProfile;
1004
+ }
1005
+ function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
1006
+ const profiles = normalizeProfiles({
1007
+ customTranslations,
1008
+ i18n: options?.i18n,
1009
+ logger: options?.logger
1010
+ });
1011
+ const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
1012
+ const profile = resolveActiveProfile({
1013
+ profiles,
1014
+ defaultProfile,
1015
+ policyProfile: options?.policyI18n?.messageProfile,
1016
+ logger: options?.logger
1017
+ });
1018
+ const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
1019
+ profiles,
1020
+ profile
1021
+ }) : Object.keys(all_namespaceObject.baseTranslations);
1022
+ const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
1023
+ profile: profiles[profile]
1024
+ }) : 'en';
1025
+ const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
1026
+ const requestedLanguage = policyLanguage ?? (0, translations_namespaceObject.selectLanguage)(configuredLanguages, {
816
1027
  header: acceptLanguage,
817
- fallback: 'en'
1028
+ fallback: fallbackLanguage
1029
+ });
1030
+ const candidates = buildCandidates({
1031
+ language: requestedLanguage,
1032
+ fallbackLanguage
1033
+ });
1034
+ const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
1035
+ if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
1036
+ requestedProfile: profile,
1037
+ requestedLanguage,
1038
+ resolvedProfile: profile,
1039
+ resolvedLanguage: selectedCandidate.language
818
1040
  });
819
- const base = isSupportedBaseLanguage(preferredLanguage) ? translations_namespaceObject.baseTranslations[preferredLanguage] : translations_namespaceObject.baseTranslations.en;
820
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1041
+ let language = selectedCandidate?.language ?? requestedLanguage;
1042
+ if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
1043
+ warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
1044
+ language = 'en';
1045
+ }
1046
+ const base = isSupportedBaseLanguage(language) ? all_namespaceObject.baseTranslations[language] : all_namespaceObject.baseTranslations.en;
1047
+ const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
821
1048
  const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
822
1049
  return {
823
1050
  translations: translations,
824
- language: preferredLanguage
1051
+ language
1052
+ };
1053
+ }
1054
+ function stripIabTranslations(translations) {
1055
+ const { iab: _iab, ...rest } = translations;
1056
+ return rest;
1057
+ }
1058
+ function resolveNoPolicyFallback() {
1059
+ return {
1060
+ id: 'no_banner',
1061
+ model: 'none',
1062
+ ui: {
1063
+ mode: 'none'
1064
+ }
1065
+ };
1066
+ }
1067
+ async function resolveInitPayload(request, options, logger) {
1068
+ const acceptLanguage = request.headers.get('accept-language') || 'en';
1069
+ const location = await getLocation(request, options);
1070
+ const jurisdiction = getJurisdiction(location, options);
1071
+ const hasExplicitPolicyPack = void 0 !== options.policyPacks;
1072
+ const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
1073
+ const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await resolvePolicyDecision({
1074
+ policies: options.policyPacks,
1075
+ countryCode: location.countryCode,
1076
+ regionCode: location.regionCode,
1077
+ jurisdiction,
1078
+ iabEnabled: options.iab?.enabled === true
1079
+ });
1080
+ if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
1081
+ country: location.countryCode,
1082
+ region: location.regionCode
1083
+ });
1084
+ const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
1085
+ const iabOptions = options.iab;
1086
+ const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
1087
+ const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
1088
+ i18n: options.i18n,
1089
+ policyI18n: resolvedPolicy?.i18n,
1090
+ logger
1091
+ });
1092
+ const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
1093
+ ...translationsResult,
1094
+ translations: stripIabTranslations(translationsResult.translations)
1095
+ };
1096
+ let gvl = null;
1097
+ if (shouldIncludeIabPayload && iabOptions) {
1098
+ const language = translationsResult.language.split('-')[0] || 'en';
1099
+ const gvlResolver = createGVLResolver({
1100
+ appName: options.appName || 'c15t',
1101
+ bundled: iabOptions.bundled,
1102
+ cacheAdapter: options.cache?.adapter,
1103
+ vendorIds: iabOptions.vendorIds,
1104
+ endpoint: iabOptions.endpoint
1105
+ });
1106
+ gvl = await gvlResolver.get(language);
1107
+ }
1108
+ const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
1109
+ const snapshot = policyDecision ? await createPolicySnapshotToken({
1110
+ options: options.policySnapshot,
1111
+ tenantId: options.tenantId,
1112
+ policyId: policyDecision.policy.id,
1113
+ fingerprint: policyDecision.fingerprint,
1114
+ matchedBy: policyDecision.matchedBy,
1115
+ country: location?.countryCode ?? null,
1116
+ region: location?.regionCode ?? null,
1117
+ jurisdiction,
1118
+ language: translationsResult.language,
1119
+ model: policyDecision.policy.model,
1120
+ policyI18n: policyDecision.policy.i18n,
1121
+ expiryDays: policyDecision.policy.consent?.expiryDays,
1122
+ scopeMode: policyDecision.policy.consent?.scopeMode,
1123
+ uiMode: policyDecision.policy.ui?.mode,
1124
+ bannerUi: policyDecision.policy.ui?.banner,
1125
+ dialogUi: policyDecision.policy.ui?.dialog,
1126
+ categories: policyDecision.policy.consent?.categories,
1127
+ preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
1128
+ gpc: policyDecision.policy.consent?.gpc,
1129
+ proofConfig: policyDecision.policy.proof
1130
+ }) : void 0;
1131
+ const gpc = '1' === request.headers.get('sec-gpc');
1132
+ getMetrics()?.recordInit({
1133
+ jurisdiction,
1134
+ country: location?.countryCode ?? void 0,
1135
+ region: location?.regionCode ?? void 0,
1136
+ gpc
1137
+ });
1138
+ return {
1139
+ jurisdiction,
1140
+ location,
1141
+ translations: responseTranslations,
1142
+ branding: options.branding || 'c15t',
1143
+ ...shouldIncludeIabPayload && {
1144
+ gvl,
1145
+ customVendors
1146
+ },
1147
+ ...resolvedPolicy && {
1148
+ policy: resolvedPolicy
1149
+ },
1150
+ ...policyDecision && {
1151
+ policyDecision: {
1152
+ policyId: policyDecision.policy.id,
1153
+ fingerprint: policyDecision.fingerprint,
1154
+ matchedBy: policyDecision.matchedBy,
1155
+ country: location.countryCode,
1156
+ region: location.regionCode,
1157
+ jurisdiction
1158
+ }
1159
+ },
1160
+ ...snapshot?.token && {
1161
+ policySnapshotToken: snapshot.token
1162
+ },
1163
+ ...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
1164
+ cmpId: iabOptions.cmpId
1165
+ }
825
1166
  };
826
1167
  }
827
1168
  const createInitRoute = (options)=>{
@@ -834,7 +1175,7 @@ const createInitRoute = (options)=>{
834
1175
  - **Location** – User's location (null if geo-location is disabled)
835
1176
  - **Translations** – Consent manager copy (from \`Accept-Language\` header)
836
1177
  - **Branding** – Configured branding key
837
- - **GVL** – Global Vendor List when enabled
1178
+ - **GVL** – Global Vendor List when IAB is active for the request
838
1179
 
839
1180
  Use for geo-targeted consent banners and regional compliance.`,
840
1181
  tags: [
@@ -851,46 +1192,13 @@ Use for geo-targeted consent banners and regional compliance.`,
851
1192
  }
852
1193
  }
853
1194
  }), async (c)=>{
854
- const request = c.req.raw;
855
- const acceptLanguage = request.headers.get('accept-language') || 'en';
856
- const location = await getLocation(request, options);
857
- const jurisdiction = getJurisdiction(location, options);
858
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
859
- let gvl = null;
860
- if (options.advanced?.iab?.enabled) {
861
- const language = translationsResult.language.split('-')[0] || 'en';
862
- const gvlResolver = createGVLResolver({
863
- appName: options.appName || 'c15t',
864
- bundled: options.advanced.iab.bundled,
865
- cacheAdapter: options.advanced.cache?.adapter,
866
- vendorIds: options.advanced.iab.vendorIds,
867
- endpoint: options.advanced.iab.endpoint
868
- });
869
- gvl = await gvlResolver.get(language);
870
- }
871
- const customVendors = options.advanced?.iab?.customVendors;
872
- const gpc = '1' === request.headers.get('sec-gpc');
873
- getMetrics()?.recordInit({
874
- jurisdiction,
875
- country: location?.countryCode ?? void 0,
876
- region: location?.regionCode ?? void 0,
877
- gpc
878
- });
879
- return c.json({
880
- jurisdiction,
881
- location,
882
- translations: translationsResult,
883
- branding: options.advanced?.branding || 'c15t',
884
- gvl,
885
- customVendors,
886
- ...options.advanced?.iab?.cmpId != null && {
887
- cmpId: options.advanced.iab.cmpId
888
- }
889
- });
1195
+ const ctx = c.get('c15tContext');
1196
+ const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
1197
+ return c.json(payload);
890
1198
  });
891
1199
  return app;
892
1200
  };
893
- const version_version = '2.0.0-rc.3';
1201
+ const version_version = '2.0.0-rc.5';
894
1202
  function getHeaders(headers) {
895
1203
  if (!headers) return {
896
1204
  countryCode: null,
@@ -1246,6 +1554,119 @@ const patchSubjectHandler = async (c)=>{
1246
1554
  });
1247
1555
  }
1248
1556
  };
1557
+ function buildRuntimeDecisionDedupeKey(input) {
1558
+ return [
1559
+ input.tenantId ?? 'default',
1560
+ input.fingerprint,
1561
+ input.matchedBy,
1562
+ input.countryCode ?? 'none',
1563
+ input.regionCode ?? 'none',
1564
+ input.jurisdiction,
1565
+ input.language ?? 'none'
1566
+ ].join('|');
1567
+ }
1568
+ function buildDecisionPayload(params) {
1569
+ const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
1570
+ if (snapshot?.valid && snapshot.payload) {
1571
+ const sp = snapshot.payload;
1572
+ return {
1573
+ tenantId,
1574
+ policyId: sp.policyId,
1575
+ fingerprint: sp.fingerprint,
1576
+ matchedBy: sp.matchedBy,
1577
+ countryCode: sp.country,
1578
+ regionCode: sp.region,
1579
+ jurisdiction: sp.jurisdiction,
1580
+ language: sp.language,
1581
+ model: sp.model,
1582
+ policyI18n: sp.policyI18n,
1583
+ uiMode: sp.uiMode,
1584
+ bannerUi: sp.bannerUi,
1585
+ dialogUi: sp.dialogUi,
1586
+ categories: sp.categories,
1587
+ preselectedCategories: sp.preselectedCategories,
1588
+ proofConfig: sp.proofConfig,
1589
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1590
+ tenantId,
1591
+ fingerprint: sp.fingerprint,
1592
+ matchedBy: sp.matchedBy,
1593
+ countryCode: sp.country,
1594
+ regionCode: sp.region,
1595
+ jurisdiction: sp.jurisdiction,
1596
+ language: sp.language
1597
+ }),
1598
+ source: 'snapshot_token'
1599
+ };
1600
+ }
1601
+ if (decision) return {
1602
+ tenantId,
1603
+ policyId: decision.policy.id,
1604
+ fingerprint: decision.fingerprint,
1605
+ matchedBy: decision.matchedBy,
1606
+ countryCode: location.countryCode,
1607
+ regionCode: location.regionCode,
1608
+ jurisdiction,
1609
+ language,
1610
+ model: decision.policy.model,
1611
+ policyI18n: decision.policy.i18n,
1612
+ uiMode: decision.policy.ui?.mode,
1613
+ bannerUi: decision.policy.ui?.banner,
1614
+ dialogUi: decision.policy.ui?.dialog,
1615
+ categories: decision.policy.consent?.categories,
1616
+ preselectedCategories: decision.policy.consent?.preselectedCategories,
1617
+ proofConfig,
1618
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1619
+ tenantId,
1620
+ fingerprint: decision.fingerprint,
1621
+ matchedBy: decision.matchedBy,
1622
+ countryCode: location.countryCode,
1623
+ regionCode: location.regionCode,
1624
+ jurisdiction,
1625
+ language
1626
+ }),
1627
+ source: 'write_time_fallback'
1628
+ };
1629
+ }
1630
+ function parseLanguageFromHeader(header) {
1631
+ if (!header) return;
1632
+ const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
1633
+ if (!firstLanguage) return;
1634
+ return firstLanguage.split('-')[0]?.toLowerCase();
1635
+ }
1636
+ function resolveSnapshotFailureMode(ctx) {
1637
+ return ctx.policySnapshot?.onValidationFailure ?? 'reject';
1638
+ }
1639
+ function buildSnapshotHttpException(reason) {
1640
+ switch(reason){
1641
+ case 'missing':
1642
+ return new http_exception_namespaceObject.HTTPException(409, {
1643
+ message: 'Policy snapshot token is required',
1644
+ cause: {
1645
+ code: 'POLICY_SNAPSHOT_REQUIRED'
1646
+ }
1647
+ });
1648
+ case 'expired':
1649
+ return new http_exception_namespaceObject.HTTPException(409, {
1650
+ message: 'Policy snapshot token has expired',
1651
+ cause: {
1652
+ code: 'POLICY_SNAPSHOT_EXPIRED'
1653
+ }
1654
+ });
1655
+ case 'malformed':
1656
+ case 'invalid':
1657
+ return new http_exception_namespaceObject.HTTPException(409, {
1658
+ message: 'Policy snapshot token is invalid',
1659
+ cause: {
1660
+ code: 'POLICY_SNAPSHOT_INVALID'
1661
+ }
1662
+ });
1663
+ default:
1664
+ {
1665
+ const _exhaustive = reason;
1666
+ throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
1667
+ }
1668
+ }
1669
+ }
1249
1670
  const postSubjectHandler = async (c)=>{
1250
1671
  const ctx = c.get('c15tContext');
1251
1672
  const logger = ctx.logger;
@@ -1257,9 +1678,6 @@ const postSubjectHandler = async (c)=>{
1257
1678
  const givenAt = new Date(givenAtEpoch);
1258
1679
  const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
1259
1680
  let derivedConsentAction;
1260
- if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
1261
- else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
1262
- else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
1263
1681
  logger.debug('Request parameters', {
1264
1682
  type,
1265
1683
  subjectId,
@@ -1268,6 +1686,50 @@ const postSubjectHandler = async (c)=>{
1268
1686
  domain
1269
1687
  });
1270
1688
  try {
1689
+ if ('cookie_banner' === type) logger.warn('`cookie_banner` policy type is deprecated in 2.0 RC and will be removed in 2.0 GA. Use backend runtime `policyPacks` for banner behavior.');
1690
+ const request = c.req.raw ?? new Request('https://c15t.local/subjects');
1691
+ const acceptLanguage = request.headers.get('accept-language');
1692
+ const requestLanguage = parseLanguageFromHeader(acceptLanguage);
1693
+ const location = await getLocation(request, ctx);
1694
+ const resolvedJurisdiction = getJurisdiction(location, ctx);
1695
+ const snapshotVerification = await verifyPolicySnapshotToken({
1696
+ token: input.policySnapshotToken,
1697
+ options: ctx.policySnapshot,
1698
+ tenantId: ctx.tenantId
1699
+ });
1700
+ const hasValidSnapshot = snapshotVerification.valid;
1701
+ const snapshotPayload = snapshotVerification.valid ? snapshotVerification.payload : null;
1702
+ const shouldRequireSnapshot = !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
1703
+ if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(snapshotVerification.reason);
1704
+ const resolvedPolicyDecision = hasValidSnapshot ? void 0 : await resolvePolicyDecision({
1705
+ policies: ctx.policyPacks,
1706
+ countryCode: location.countryCode,
1707
+ regionCode: location.regionCode,
1708
+ jurisdiction: resolvedJurisdiction,
1709
+ iabEnabled: ctx.iab?.enabled === true
1710
+ });
1711
+ const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
1712
+ id: snapshotPayload.policyId,
1713
+ model: snapshotPayload.model,
1714
+ i18n: snapshotPayload.policyI18n,
1715
+ consent: {
1716
+ expiryDays: snapshotPayload.expiryDays,
1717
+ scopeMode: snapshotPayload.scopeMode,
1718
+ categories: snapshotPayload.categories,
1719
+ preselectedCategories: snapshotPayload.preselectedCategories,
1720
+ gpc: snapshotPayload.gpc
1721
+ },
1722
+ ui: {
1723
+ mode: snapshotPayload.uiMode,
1724
+ banner: snapshotPayload.bannerUi,
1725
+ dialog: snapshotPayload.dialogUi
1726
+ },
1727
+ proof: snapshotPayload.proofConfig
1728
+ } : resolvedPolicyDecision?.policy;
1729
+ const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
1730
+ if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
1731
+ else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
1732
+ else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
1271
1733
  const subject = await registry.findOrCreateSubject({
1272
1734
  subjectId,
1273
1735
  externalSubjectId,
@@ -1294,6 +1756,7 @@ const postSubjectHandler = async (c)=>{
1294
1756
  });
1295
1757
  let policyId;
1296
1758
  let purposeIds = [];
1759
+ let appliedPreferences;
1297
1760
  const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
1298
1761
  if (inputPolicyId) {
1299
1762
  policyId = inputPolicyId;
@@ -1326,20 +1789,66 @@ const postSubjectHandler = async (c)=>{
1326
1789
  policyId = policy.id;
1327
1790
  }
1328
1791
  if (preferences) {
1329
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1792
+ const allowedCategories = effectivePolicy?.consent?.categories;
1793
+ const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
1794
+ const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
1795
+ const appliedPreferenceEntries = Object.entries(preferences);
1796
+ let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
1797
+ if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
1798
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
1799
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
1800
+ if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
1801
+ message: 'Preferences include categories not allowed by policy',
1802
+ cause: {
1803
+ code: 'PURPOSE_NOT_ALLOWED',
1804
+ disallowed
1805
+ }
1806
+ });
1807
+ }
1808
+ appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
1809
+ const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1330
1810
  logger.debug('Consented purposes', {
1331
- consentedPurposes
1811
+ consentedPurposes: filteredConsentedPurposeCodes
1332
1812
  });
1333
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1813
+ const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1334
1814
  const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
1335
1815
  logger.debug('Filtered purposes', {
1336
1816
  purposes
1337
1817
  });
1338
1818
  if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
1339
- consentedPurposes
1819
+ consentedPurposes: filteredConsentedPurposeCodes
1340
1820
  });
1341
1821
  purposeIds = purposes;
1342
1822
  }
1823
+ const expiryDays = effectivePolicy?.consent?.expiryDays;
1824
+ const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
1825
+ const proofConfig = effectivePolicy?.proof;
1826
+ const shouldStoreIp = proofConfig?.storeIp ?? true;
1827
+ const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
1828
+ const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
1829
+ const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
1830
+ const metadataWithPolicy = {
1831
+ ...metadata ?? {},
1832
+ ...shouldStoreLanguage && effectiveLanguage ? {
1833
+ policyLanguage: effectiveLanguage
1834
+ } : {}
1835
+ };
1836
+ const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
1837
+ const decisionPayload = buildDecisionPayload({
1838
+ tenantId: ctx.tenantId,
1839
+ snapshot: hasValidSnapshot && snapshotPayload ? {
1840
+ valid: true,
1841
+ payload: snapshotPayload
1842
+ } : null,
1843
+ decision: resolvedPolicyDecision,
1844
+ location: {
1845
+ countryCode: location.countryCode,
1846
+ regionCode: location.regionCode
1847
+ },
1848
+ jurisdiction: resolvedJurisdiction,
1849
+ language: effectiveLanguage,
1850
+ proofConfig
1851
+ });
1343
1852
  const existingConsent = await db.findFirst('consent', {
1344
1853
  where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
1345
1854
  });
@@ -1354,6 +1863,7 @@ const postSubjectHandler = async (c)=>{
1354
1863
  domain: domainRecord.name,
1355
1864
  type,
1356
1865
  metadata,
1866
+ appliedPreferences,
1357
1867
  uiSource: input.uiSource,
1358
1868
  givenAt: existingConsent.givenAt
1359
1869
  });
@@ -1365,6 +1875,42 @@ const postSubjectHandler = async (c)=>{
1365
1875
  policyId,
1366
1876
  purposeIds
1367
1877
  });
1878
+ const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
1879
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
1880
+ }) ?? await tx.create('runtimePolicyDecision', {
1881
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
1882
+ tenantId: decisionPayload.tenantId,
1883
+ policyId: decisionPayload.policyId,
1884
+ fingerprint: decisionPayload.fingerprint,
1885
+ matchedBy: decisionPayload.matchedBy,
1886
+ countryCode: decisionPayload.countryCode,
1887
+ regionCode: decisionPayload.regionCode,
1888
+ jurisdiction: decisionPayload.jurisdiction,
1889
+ language: decisionPayload.language,
1890
+ model: decisionPayload.model,
1891
+ policyI18n: decisionPayload.policyI18n ? {
1892
+ json: decisionPayload.policyI18n
1893
+ } : void 0,
1894
+ uiMode: decisionPayload.uiMode,
1895
+ bannerUi: decisionPayload.bannerUi ? {
1896
+ json: decisionPayload.bannerUi
1897
+ } : void 0,
1898
+ dialogUi: decisionPayload.dialogUi ? {
1899
+ json: decisionPayload.dialogUi
1900
+ } : void 0,
1901
+ categories: decisionPayload.categories ? {
1902
+ json: decisionPayload.categories
1903
+ } : void 0,
1904
+ preselectedCategories: decisionPayload.preselectedCategories ? {
1905
+ json: decisionPayload.preselectedCategories
1906
+ } : void 0,
1907
+ proofConfig: decisionPayload.proofConfig ? {
1908
+ json: decisionPayload.proofConfig
1909
+ } : void 0,
1910
+ dedupeKey: decisionPayload.dedupeKey
1911
+ }).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
1912
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
1913
+ })) : void 0;
1368
1914
  const consentRecord = await tx.create('consent', {
1369
1915
  id: await generateUniqueId(tx, 'consent', ctx),
1370
1916
  subjectId: subject.id,
@@ -1373,17 +1919,20 @@ const postSubjectHandler = async (c)=>{
1373
1919
  purposeIds: {
1374
1920
  json: purposeIds
1375
1921
  },
1376
- metadata: metadata ? {
1377
- json: metadata
1922
+ metadata: Object.keys(metadataWithPolicy).length > 0 ? {
1923
+ json: metadataWithPolicy
1378
1924
  } : void 0,
1379
- ipAddress: ctx.ipAddress,
1380
- userAgent: ctx.userAgent,
1381
- jurisdiction: input.jurisdiction,
1382
- jurisdictionModel: input.jurisdictionModel,
1925
+ ipAddress: shouldStoreIp ? ctx.ipAddress : null,
1926
+ userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
1927
+ jurisdiction: effectiveJurisdiction,
1928
+ jurisdictionModel: effectiveModel,
1383
1929
  tcString: input.tcString,
1384
1930
  uiSource: input.uiSource,
1385
1931
  consentAction: derivedConsentAction,
1386
- givenAt
1932
+ givenAt,
1933
+ validUntil,
1934
+ runtimePolicyDecisionId: runtimePolicyDecision?.id,
1935
+ runtimePolicySource: decisionPayload?.source
1387
1936
  });
1388
1937
  logger.debug('Created consent', {
1389
1938
  consentRecord: consentRecord.id
@@ -1402,7 +1951,7 @@ const postSubjectHandler = async (c)=>{
1402
1951
  });
1403
1952
  const metrics = getMetrics();
1404
1953
  if (metrics) {
1405
- const jurisdiction = input.jurisdiction;
1954
+ const jurisdiction = effectiveJurisdiction;
1406
1955
  metrics.recordConsentCreated({
1407
1956
  type,
1408
1957
  jurisdiction
@@ -1424,6 +1973,7 @@ const postSubjectHandler = async (c)=>{
1424
1973
  domain: domainRecord.name,
1425
1974
  type,
1426
1975
  metadata,
1976
+ appliedPreferences,
1427
1977
  uiSource: input.uiSource,
1428
1978
  givenAt: result.consent.givenAt
1429
1979
  });