@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/core.js CHANGED
@@ -5,11 +5,14 @@ import { Hono } from "hono";
5
5
  import { cors } from "hono/cors";
6
6
  import { HTTPException } from "hono/http-exception";
7
7
  import { describeRoute, openAPIRouteHandler, resolver, validator } from "hono-openapi";
8
+ import { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, inspectPolicies, policyMatchers, resolvePolicyDecision, validatePolicyI18nConfig } from "@c15t/schema/types";
9
+ import { deepMergeTranslations, selectLanguage } from "@c15t/translations";
10
+ import { baseTranslations } from "@c15t/translations/all";
8
11
  import base_x from "base-x";
9
12
  import { fumadb } from "fumadb";
10
13
  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 { baseTranslations, deepMergeTranslations, selectLanguage } from "@c15t/translations";
14
+ import { checkConsentOutputSchema, checkConsentQuerySchema, compactDefined, dedupeTrimmedStrings, getSubjectInputSchema, getSubjectOutputSchema, initOutputSchema, listSubjectsOutputSchema, listSubjectsQuerySchema, patchSubjectOutputSchema, policyPackPresets, postSubjectInputSchema, postSubjectOutputSchema, statusOutputSchema, subjectIdSchema } from "@c15t/schema";
15
+ import { SignJWT, errors, jwtVerify } from "jose";
13
16
  import { object, optional, string } from "valibot";
14
17
  function extractBearerToken(authHeader) {
15
18
  if (!authHeader) return null;
@@ -136,12 +139,12 @@ function createCORSOptions(trustedOrigins) {
136
139
  ]
137
140
  };
138
141
  }
139
- const version_version = '2.0.0-rc.3';
142
+ const version_version = '2.0.0-rc.5';
140
143
  const config_createOpenAPIConfig = (options)=>({
141
- enabled: options.advanced?.openapi?.enabled !== false,
144
+ enabled: options.openapi?.enabled !== false,
142
145
  specPath: '/spec.json',
143
146
  docsPath: '/docs',
144
- ...options.advanced?.openapi || {}
147
+ ...options.openapi || {}
145
148
  });
146
149
  const DEFAULT_IP_HEADERS = [
147
150
  'x-client-ip',
@@ -236,7 +239,7 @@ function maskIpAddress(ip) {
236
239
  return ip;
237
240
  }
238
241
  function getIpAddress(req, options) {
239
- const ipAddressConfig = options.advanced?.ipAddress;
242
+ const ipAddressConfig = options.ipAddress;
240
243
  if (ipAddressConfig?.tracking === false) return null;
241
244
  const ipHeaders = ipAddressConfig?.ipAddressHeaders || DEFAULT_IP_HEADERS;
242
245
  const headers = req instanceof Request ? req.headers : req;
@@ -252,6 +255,133 @@ function getIpAddress(req, options) {
252
255
  }
253
256
  return null;
254
257
  }
258
+ const DEFAULT_PROFILE = 'default';
259
+ const warnedKeys = new Set();
260
+ function isSupportedBaseLanguage(lang) {
261
+ return lang in baseTranslations;
262
+ }
263
+ function warnOnce(logger, key, message, metadata) {
264
+ if (!logger || warnedKeys.has(key)) return;
265
+ warnedKeys.add(key);
266
+ logger.warn(message, metadata);
267
+ }
268
+ function normalizeLanguage(value) {
269
+ if (!value) return;
270
+ const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
271
+ if (!normalized) return;
272
+ return normalized.split('-')[0] ?? void 0;
273
+ }
274
+ function normalizeProfiles(params) {
275
+ const profiles = params.i18n?.messages;
276
+ const legacy = params.customTranslations;
277
+ if (profiles && Object.keys(profiles).length > 0) {
278
+ if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
279
+ return profiles;
280
+ }
281
+ if (legacy && Object.keys(legacy).length > 0) {
282
+ warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
283
+ return {
284
+ [DEFAULT_PROFILE]: {
285
+ translations: legacy
286
+ }
287
+ };
288
+ }
289
+ return {};
290
+ }
291
+ function buildCandidates(input) {
292
+ const raw = [
293
+ {
294
+ language: input.language,
295
+ reason: 'profile_language'
296
+ },
297
+ {
298
+ language: input.fallbackLanguage,
299
+ reason: 'profile_fallback'
300
+ }
301
+ ];
302
+ const dedupe = new Set();
303
+ return raw.filter((candidate)=>{
304
+ const key = candidate.language;
305
+ if (dedupe.has(key)) return false;
306
+ dedupe.add(key);
307
+ return true;
308
+ });
309
+ }
310
+ function getProfileLanguages(profiles, profile) {
311
+ return Object.keys(profiles[profile]?.translations ?? {}).sort();
312
+ }
313
+ function getSelectableLanguages(input) {
314
+ return getProfileLanguages(input.profiles, input.profile);
315
+ }
316
+ function resolveFallbackLanguage(input) {
317
+ const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
318
+ const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
319
+ if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
320
+ if (profileLanguages.includes('en')) return 'en';
321
+ return profileLanguages[0] ?? configuredFallbackLanguage;
322
+ }
323
+ function resolveActiveProfile(input) {
324
+ const requestedProfile = input.policyProfile ?? input.defaultProfile;
325
+ if (input.profiles[requestedProfile]) return requestedProfile;
326
+ if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
327
+ return input.defaultProfile;
328
+ }
329
+ function validateMessages(options) {
330
+ return validatePolicyI18nConfig({
331
+ customTranslations: options.customTranslations,
332
+ i18n: options.i18n,
333
+ policies: options.policies
334
+ });
335
+ }
336
+ function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
337
+ const profiles = normalizeProfiles({
338
+ customTranslations,
339
+ i18n: options?.i18n,
340
+ logger: options?.logger
341
+ });
342
+ const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
343
+ const profile = resolveActiveProfile({
344
+ profiles,
345
+ defaultProfile,
346
+ policyProfile: options?.policyI18n?.messageProfile,
347
+ logger: options?.logger
348
+ });
349
+ const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
350
+ profiles,
351
+ profile
352
+ }) : Object.keys(baseTranslations);
353
+ const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
354
+ profile: profiles[profile]
355
+ }) : 'en';
356
+ const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
357
+ const requestedLanguage = policyLanguage ?? selectLanguage(configuredLanguages, {
358
+ header: acceptLanguage,
359
+ fallback: fallbackLanguage
360
+ });
361
+ const candidates = buildCandidates({
362
+ language: requestedLanguage,
363
+ fallbackLanguage
364
+ });
365
+ const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
366
+ if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
367
+ requestedProfile: profile,
368
+ requestedLanguage,
369
+ resolvedProfile: profile,
370
+ resolvedLanguage: selectedCandidate.language
371
+ });
372
+ let language = selectedCandidate?.language ?? requestedLanguage;
373
+ if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
374
+ warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
375
+ language = 'en';
376
+ }
377
+ const base = isSupportedBaseLanguage(language) ? baseTranslations[language] : baseTranslations.en;
378
+ const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
379
+ const translations = custom ? deepMergeTranslations(base, custom) : base;
380
+ return {
381
+ translations: translations,
382
+ language
383
+ };
384
+ }
255
385
  function extractErrorMessage(error) {
256
386
  if (error instanceof AggregateError && error.errors?.length > 0) {
257
387
  const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
@@ -280,18 +410,18 @@ function createTelemetryOptions(appName = 'c15t', telemetryConfig, tenantId) {
280
410
  return config;
281
411
  }
282
412
  function isTelemetryEnabled(options) {
283
- if (options) return options.advanced?.telemetry?.enabled === true;
413
+ if (options) return options.telemetry?.enabled === true;
284
414
  return cachedConfig?.enabled === true;
285
415
  }
286
416
  const getTracer = (options)=>{
287
417
  if (!isTelemetryEnabled(options)) return trace.getTracer('c15t-noop');
288
- const tracer = options?.advanced?.telemetry?.tracer ?? cachedConfig?.tracer;
418
+ const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
289
419
  if (tracer) return tracer;
290
420
  return trace.getTracer(options?.appName ?? 'c15t');
291
421
  };
292
422
  const getMeter = (options)=>{
293
423
  if (!isTelemetryEnabled(options)) return api_metrics.getMeter('c15t-noop');
294
- const meter = options?.advanced?.telemetry?.meter ?? cachedConfig?.meter;
424
+ const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
295
425
  if (meter) return meter;
296
426
  return api_metrics.getMeter(options?.appName ?? 'c15t');
297
427
  };
@@ -301,7 +431,7 @@ function getDefaultAttributes() {
301
431
  const createRequestSpan = (method, path, options)=>{
302
432
  if (!isTelemetryEnabled(options)) return null;
303
433
  const tracer = getTracer(options);
304
- const defaultAttrs = options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
434
+ const defaultAttrs = options?.telemetry?.defaultAttributes || getDefaultAttributes();
305
435
  const span = tracer.startSpan(`${method} ${path}`, {
306
436
  attributes: {
307
437
  'http.method': method,
@@ -343,7 +473,7 @@ async function executeWithSpan(span, operation) {
343
473
  }
344
474
  }
345
475
  function resolveDefaultAttributes(options) {
346
- return options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
476
+ return options?.telemetry?.defaultAttributes || getDefaultAttributes();
347
477
  }
348
478
  async function withDatabaseSpan(attributes, operation, options) {
349
479
  if (!isTelemetryEnabled(options)) return operation();
@@ -574,6 +704,7 @@ const prefixes = {
574
704
  consentPolicy: 'pol',
575
705
  consentPurpose: 'pur',
576
706
  domain: 'dom',
707
+ runtimePolicyDecision: 'rpd',
577
708
  subject: 'sub'
578
709
  };
579
710
  const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
@@ -845,6 +976,54 @@ function domainRegistry({ db, ctx }) {
845
976
  }
846
977
  };
847
978
  }
979
+ function runtimePolicyDecisionRegistry({ db, ctx }) {
980
+ const { logger } = ctx;
981
+ return {
982
+ findOrCreateRuntimePolicyDecision: async (input)=>{
983
+ const existing = await db.findFirst('runtimePolicyDecision', {
984
+ where: (b)=>b('dedupeKey', '=', input.dedupeKey)
985
+ });
986
+ if (existing) return existing;
987
+ logger.debug('Creating runtime policy decision', {
988
+ policyId: input.policyId,
989
+ fingerprint: input.fingerprint,
990
+ matchedBy: input.matchedBy
991
+ });
992
+ return db.create('runtimePolicyDecision', {
993
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
994
+ tenantId: input.tenantId,
995
+ policyId: input.policyId,
996
+ fingerprint: input.fingerprint,
997
+ matchedBy: input.matchedBy,
998
+ countryCode: input.countryCode,
999
+ regionCode: input.regionCode,
1000
+ jurisdiction: input.jurisdiction,
1001
+ language: input.language,
1002
+ model: input.model,
1003
+ policyI18n: input.policyI18n ? {
1004
+ json: input.policyI18n
1005
+ } : void 0,
1006
+ uiMode: input.uiMode,
1007
+ bannerUi: input.bannerUi ? {
1008
+ json: input.bannerUi
1009
+ } : void 0,
1010
+ dialogUi: input.dialogUi ? {
1011
+ json: input.dialogUi
1012
+ } : void 0,
1013
+ categories: input.categories ? {
1014
+ json: input.categories
1015
+ } : void 0,
1016
+ preselectedCategories: input.preselectedCategories ? {
1017
+ json: input.preselectedCategories
1018
+ } : void 0,
1019
+ proofConfig: input.proofConfig ? {
1020
+ json: input.proofConfig
1021
+ } : void 0,
1022
+ dedupeKey: input.dedupeKey
1023
+ });
1024
+ }
1025
+ };
1026
+ }
848
1027
  function subjectRegistry({ db, ctx }) {
849
1028
  const { logger } = ctx;
850
1029
  return {
@@ -922,7 +1101,8 @@ const createRegistry = (ctx)=>({
922
1101
  ...subjectRegistry(ctx),
923
1102
  ...consentPurposeRegistry(ctx),
924
1103
  ...policyRegistry(ctx),
925
- ...domainRegistry(ctx)
1104
+ ...domainRegistry(ctx),
1105
+ ...runtimePolicyDecisionRegistry(ctx)
926
1106
  });
927
1107
  const auditLogTable = schema_table('auditLog', {
928
1108
  id: idColumn('id', 'varchar(255)'),
@@ -1089,6 +1269,8 @@ const consent_consentTable = schema_table('consent', {
1089
1269
  tcString: column('tcString', 'string').nullable(),
1090
1270
  uiSource: column('uiSource', 'string').nullable(),
1091
1271
  consentAction: column('consentAction', 'string').nullable(),
1272
+ runtimePolicyDecisionId: column('runtimePolicyDecisionId', 'string').nullable(),
1273
+ runtimePolicySource: column('runtimePolicySource', 'string').nullable(),
1092
1274
  tenantId: column('tenantId', 'string').nullable()
1093
1275
  });
1094
1276
  const consent_policy_consentPolicyTable = schema_table('consentPolicy', {
@@ -1114,6 +1296,27 @@ const domain_domainTable = schema_table('domain', {
1114
1296
  updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
1115
1297
  tenantId: column('tenantId', 'string').nullable()
1116
1298
  });
1299
+ const runtimePolicyDecisionTable = schema_table('runtimePolicyDecision', {
1300
+ id: idColumn('id', 'varchar(255)'),
1301
+ tenantId: column('tenantId', 'string').nullable(),
1302
+ policyId: column('policyId', 'string'),
1303
+ fingerprint: column('fingerprint', 'string'),
1304
+ matchedBy: column('matchedBy', 'string'),
1305
+ countryCode: column('countryCode', 'string').nullable(),
1306
+ regionCode: column('regionCode', 'string').nullable(),
1307
+ jurisdiction: column('jurisdiction', 'string'),
1308
+ language: column('language', 'string').nullable(),
1309
+ model: column('model', 'string'),
1310
+ policyI18n: column('policyI18n', 'json').nullable(),
1311
+ uiMode: column('uiMode', 'string').nullable(),
1312
+ bannerUi: column('bannerUi', 'json').nullable(),
1313
+ dialogUi: column('dialogUi', 'json').nullable(),
1314
+ categories: column('categories', 'json').nullable(),
1315
+ preselectedCategories: column('preselectedCategories', 'json').nullable(),
1316
+ proofConfig: column('proofConfig', 'json').nullable(),
1317
+ dedupeKey: column('dedupeKey', 'string').unique(),
1318
+ createdAt: column('createdAt', 'timestamp').defaultTo$('now')
1319
+ });
1117
1320
  const subject_subjectTable = schema_table('subject', {
1118
1321
  id: idColumn('id', 'varchar(255)'),
1119
1322
  externalId: column('externalId', 'string').nullable(),
@@ -1128,6 +1331,7 @@ const v2 = schema({
1128
1331
  subject: subject_subjectTable,
1129
1332
  domain: domain_domainTable,
1130
1333
  consentPolicy: consent_policy_consentPolicyTable,
1334
+ runtimePolicyDecision: runtimePolicyDecisionTable,
1131
1335
  consentPurpose: consent_purpose_consentPurposeTable,
1132
1336
  consent: consent_consentTable,
1133
1337
  auditLog: audit_log_auditLogTable
@@ -1143,6 +1347,9 @@ const v2 = schema({
1143
1347
  consentPolicy: ({ many })=>({
1144
1348
  consents: many('consent')
1145
1349
  }),
1350
+ runtimePolicyDecision: ({ many })=>({
1351
+ consents: many('consent')
1352
+ }),
1146
1353
  consentPurpose: ()=>({}),
1147
1354
  consent: ({ one })=>({
1148
1355
  subject: one('subject', [
@@ -1156,6 +1363,10 @@ const v2 = schema({
1156
1363
  policy: one('consentPolicy', [
1157
1364
  'policyId',
1158
1365
  'id'
1366
+ ]).foreignKey(),
1367
+ runtimePolicyDecision: one('runtimePolicyDecision', [
1368
+ 'runtimePolicyDecisionId',
1369
+ 'id'
1159
1370
  ]).foreignKey()
1160
1371
  }),
1161
1372
  auditLog: ({ one })=>({
@@ -1242,6 +1453,18 @@ function withTenantScope(db, tenantId) {
1242
1453
  }
1243
1454
  });
1244
1455
  }
1456
+ function policy_inspectPolicies(policies, options) {
1457
+ return inspectPolicies(policies, options);
1458
+ }
1459
+ async function policy_resolvePolicyDecision(params) {
1460
+ return resolvePolicyDecision({
1461
+ policies: params.policies,
1462
+ countryCode: params.countryCode,
1463
+ regionCode: params.regionCode,
1464
+ jurisdiction: params.jurisdiction,
1465
+ iabEnabled: params.iabEnabled
1466
+ });
1467
+ }
1245
1468
  let globalLogger;
1246
1469
  function initLogger(options) {
1247
1470
  globalLogger = logger_createLogger({
@@ -1256,7 +1479,7 @@ const init = (options)=>{
1256
1479
  ...options.logger,
1257
1480
  appName: String(appName)
1258
1481
  });
1259
- const telemetryOptions = createTelemetryOptions(String(appName), options.advanced?.telemetry, options.tenantId);
1482
+ const telemetryOptions = createTelemetryOptions(String(appName), options.telemetry, options.tenantId);
1260
1483
  if (isTelemetryEnabled(options)) logger.debug('Telemetry is enabled', {
1261
1484
  hasTracer: !!telemetryOptions?.tracer,
1262
1485
  hasMeter: !!telemetryOptions?.meter,
@@ -1267,8 +1490,21 @@ const init = (options)=>{
1267
1490
  const client = db.client(options.adapter);
1268
1491
  const rawOrm = client.orm('2.0.0');
1269
1492
  const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
1493
+ const { ipAddress: _ipAddressConfig, ...baseOptions } = options;
1494
+ const i18nValidation = validateMessages({
1495
+ i18n: options.i18n,
1496
+ customTranslations: options.customTranslations,
1497
+ policies: options.policyPacks
1498
+ });
1499
+ for (const warning of i18nValidation.warnings)logger.warn(`i18n: ${warning}`);
1500
+ if (i18nValidation.errors.length > 0) throw new Error(`Invalid i18n configuration:\n${i18nValidation.errors.map((error)=>`- ${error}`).join('\n')}`);
1501
+ const policyValidation = policy_inspectPolicies(options.policyPacks ?? [], {
1502
+ iabEnabled: options.iab?.enabled === true
1503
+ });
1504
+ for (const warning of policyValidation.warnings)logger.warn(`policyPacks: ${warning}`);
1505
+ if (policyValidation.errors.length > 0) throw new Error(policyValidation.errors[0]);
1270
1506
  const context = {
1271
- ...options,
1507
+ ...baseOptions,
1272
1508
  appName,
1273
1509
  logger,
1274
1510
  db: orm,
@@ -1626,6 +1862,123 @@ function createGVLResolver(options) {
1626
1862
  }
1627
1863
  };
1628
1864
  }
1865
+ const POLICY_SNAPSHOT_JWT_HEADER = {
1866
+ alg: 'HS256',
1867
+ typ: 'JWT'
1868
+ };
1869
+ const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
1870
+ const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
1871
+ function resolveSnapshotIssuer(options) {
1872
+ return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
1873
+ }
1874
+ function resolveSnapshotAudience(params) {
1875
+ const configuredAudience = params.options?.audience?.trim();
1876
+ if (configuredAudience) return configuredAudience;
1877
+ return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
1878
+ }
1879
+ function getSigningKey(secret) {
1880
+ return new TextEncoder().encode(secret);
1881
+ }
1882
+ function isPolicySnapshotPayload(payload) {
1883
+ 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;
1884
+ }
1885
+ async function createPolicySnapshotToken(params) {
1886
+ const { options } = params;
1887
+ if (!options?.signingKey) return;
1888
+ const iat = Math.floor(Date.now() / 1000);
1889
+ const ttlSeconds = options.ttlSeconds ?? 1800;
1890
+ const exp = iat + ttlSeconds;
1891
+ const iss = resolveSnapshotIssuer(options);
1892
+ const aud = resolveSnapshotAudience({
1893
+ options,
1894
+ tenantId: params.tenantId
1895
+ });
1896
+ const payload = {
1897
+ iss,
1898
+ aud,
1899
+ sub: params.policyId,
1900
+ tenantId: params.tenantId,
1901
+ policyId: params.policyId,
1902
+ fingerprint: params.fingerprint,
1903
+ matchedBy: params.matchedBy,
1904
+ country: params.country,
1905
+ region: params.region,
1906
+ jurisdiction: params.jurisdiction,
1907
+ language: params.language,
1908
+ model: params.model,
1909
+ policyI18n: params.policyI18n,
1910
+ expiryDays: params.expiryDays,
1911
+ scopeMode: params.scopeMode,
1912
+ uiMode: params.uiMode,
1913
+ bannerUi: params.bannerUi,
1914
+ dialogUi: params.dialogUi,
1915
+ categories: params.categories,
1916
+ preselectedCategories: params.preselectedCategories,
1917
+ gpc: params.gpc,
1918
+ proofConfig: params.proofConfig,
1919
+ iat,
1920
+ exp
1921
+ };
1922
+ const token = await new SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
1923
+ return {
1924
+ token,
1925
+ payload
1926
+ };
1927
+ }
1928
+ async function verifyPolicySnapshotToken(params) {
1929
+ const { token, options, tenantId } = params;
1930
+ if (!options?.signingKey) return {
1931
+ valid: false,
1932
+ reason: 'missing'
1933
+ };
1934
+ if (!token) return {
1935
+ valid: false,
1936
+ reason: 'missing'
1937
+ };
1938
+ if (3 !== token.split('.').length) return {
1939
+ valid: false,
1940
+ reason: 'malformed'
1941
+ };
1942
+ try {
1943
+ const { payload, protectedHeader } = await jwtVerify(token, getSigningKey(options.signingKey), {
1944
+ issuer: resolveSnapshotIssuer(options),
1945
+ audience: resolveSnapshotAudience({
1946
+ options,
1947
+ tenantId
1948
+ })
1949
+ });
1950
+ const header = protectedHeader;
1951
+ if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
1952
+ valid: false,
1953
+ reason: 'invalid'
1954
+ };
1955
+ if (!isPolicySnapshotPayload(payload)) return {
1956
+ valid: false,
1957
+ reason: 'invalid'
1958
+ };
1959
+ if (payload.sub !== payload.policyId) return {
1960
+ valid: false,
1961
+ reason: 'invalid'
1962
+ };
1963
+ if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
1964
+ valid: false,
1965
+ reason: 'invalid'
1966
+ };
1967
+ return {
1968
+ valid: true,
1969
+ payload
1970
+ };
1971
+ } catch (error) {
1972
+ if (error instanceof errors.JWTExpired) return {
1973
+ valid: false,
1974
+ reason: 'expired'
1975
+ };
1976
+ return {
1977
+ valid: false,
1978
+ reason: 'invalid'
1979
+ };
1980
+ }
1981
+ }
1629
1982
  function geo_normalizeHeader(value) {
1630
1983
  if (!value) return null;
1631
1984
  return Array.isArray(value) ? value[0] ?? null : value;
@@ -1767,7 +2120,7 @@ function checkJurisdiction(countryCode, regionCode) {
1767
2120
  return jurisdiction;
1768
2121
  }
1769
2122
  async function getLocation(request, options) {
1770
- if (options.advanced?.disableGeoLocation) return {
2123
+ if (options.disableGeoLocation) return {
1771
2124
  countryCode: null,
1772
2125
  regionCode: null
1773
2126
  };
@@ -1778,29 +2131,121 @@ async function getLocation(request, options) {
1778
2131
  };
1779
2132
  }
1780
2133
  function getJurisdiction(location, options) {
1781
- if (options.advanced?.disableGeoLocation) return 'GDPR';
2134
+ if (options.disableGeoLocation) return 'GDPR';
1782
2135
  return checkJurisdiction(location.countryCode, location.regionCode);
1783
2136
  }
1784
- function isSupportedBaseLanguage(lang) {
1785
- return lang in baseTranslations;
2137
+ function stripIabTranslations(translations) {
2138
+ const { iab: _iab, ...rest } = translations;
2139
+ return rest;
1786
2140
  }
1787
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
1788
- const supportedDefaultLanguages = Object.keys(baseTranslations);
1789
- const supportedCustomLanguages = Object.keys(customTranslations || {});
1790
- const supportedLanguages = [
1791
- ...supportedDefaultLanguages,
1792
- ...supportedCustomLanguages
1793
- ];
1794
- const preferredLanguage = selectLanguage(supportedLanguages, {
1795
- header: acceptLanguage,
1796
- fallback: 'en'
2141
+ function resolveNoPolicyFallback() {
2142
+ return {
2143
+ id: 'no_banner',
2144
+ model: 'none',
2145
+ ui: {
2146
+ mode: 'none'
2147
+ }
2148
+ };
2149
+ }
2150
+ async function resolveInitPayload(request, options, logger) {
2151
+ const acceptLanguage = request.headers.get('accept-language') || 'en';
2152
+ const location = await getLocation(request, options);
2153
+ const jurisdiction = getJurisdiction(location, options);
2154
+ const hasExplicitPolicyPack = void 0 !== options.policyPacks;
2155
+ const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
2156
+ const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await policy_resolvePolicyDecision({
2157
+ policies: options.policyPacks,
2158
+ countryCode: location.countryCode,
2159
+ regionCode: location.regionCode,
2160
+ jurisdiction,
2161
+ iabEnabled: options.iab?.enabled === true
2162
+ });
2163
+ if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
2164
+ country: location.countryCode,
2165
+ region: location.regionCode
2166
+ });
2167
+ const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
2168
+ const iabOptions = options.iab;
2169
+ const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
2170
+ const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
2171
+ i18n: options.i18n,
2172
+ policyI18n: resolvedPolicy?.i18n,
2173
+ logger
2174
+ });
2175
+ const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
2176
+ ...translationsResult,
2177
+ translations: stripIabTranslations(translationsResult.translations)
2178
+ };
2179
+ let gvl = null;
2180
+ if (shouldIncludeIabPayload && iabOptions) {
2181
+ const language = translationsResult.language.split('-')[0] || 'en';
2182
+ const gvlResolver = createGVLResolver({
2183
+ appName: options.appName || 'c15t',
2184
+ bundled: iabOptions.bundled,
2185
+ cacheAdapter: options.cache?.adapter,
2186
+ vendorIds: iabOptions.vendorIds,
2187
+ endpoint: iabOptions.endpoint
2188
+ });
2189
+ gvl = await gvlResolver.get(language);
2190
+ }
2191
+ const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
2192
+ const snapshot = policyDecision ? await createPolicySnapshotToken({
2193
+ options: options.policySnapshot,
2194
+ tenantId: options.tenantId,
2195
+ policyId: policyDecision.policy.id,
2196
+ fingerprint: policyDecision.fingerprint,
2197
+ matchedBy: policyDecision.matchedBy,
2198
+ country: location?.countryCode ?? null,
2199
+ region: location?.regionCode ?? null,
2200
+ jurisdiction,
2201
+ language: translationsResult.language,
2202
+ model: policyDecision.policy.model,
2203
+ policyI18n: policyDecision.policy.i18n,
2204
+ expiryDays: policyDecision.policy.consent?.expiryDays,
2205
+ scopeMode: policyDecision.policy.consent?.scopeMode,
2206
+ uiMode: policyDecision.policy.ui?.mode,
2207
+ bannerUi: policyDecision.policy.ui?.banner,
2208
+ dialogUi: policyDecision.policy.ui?.dialog,
2209
+ categories: policyDecision.policy.consent?.categories,
2210
+ preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
2211
+ gpc: policyDecision.policy.consent?.gpc,
2212
+ proofConfig: policyDecision.policy.proof
2213
+ }) : void 0;
2214
+ const gpc = '1' === request.headers.get('sec-gpc');
2215
+ getMetrics()?.recordInit({
2216
+ jurisdiction,
2217
+ country: location?.countryCode ?? void 0,
2218
+ region: location?.regionCode ?? void 0,
2219
+ gpc
1797
2220
  });
1798
- const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
1799
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1800
- const translations = custom ? deepMergeTranslations(base, custom) : base;
1801
2221
  return {
1802
- translations: translations,
1803
- language: preferredLanguage
2222
+ jurisdiction,
2223
+ location,
2224
+ translations: responseTranslations,
2225
+ branding: options.branding || 'c15t',
2226
+ ...shouldIncludeIabPayload && {
2227
+ gvl,
2228
+ customVendors
2229
+ },
2230
+ ...resolvedPolicy && {
2231
+ policy: resolvedPolicy
2232
+ },
2233
+ ...policyDecision && {
2234
+ policyDecision: {
2235
+ policyId: policyDecision.policy.id,
2236
+ fingerprint: policyDecision.fingerprint,
2237
+ matchedBy: policyDecision.matchedBy,
2238
+ country: location.countryCode,
2239
+ region: location.regionCode,
2240
+ jurisdiction
2241
+ }
2242
+ },
2243
+ ...snapshot?.token && {
2244
+ policySnapshotToken: snapshot.token
2245
+ },
2246
+ ...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
2247
+ cmpId: iabOptions.cmpId
2248
+ }
1804
2249
  };
1805
2250
  }
1806
2251
  const createInitRoute = (options)=>{
@@ -1813,7 +2258,7 @@ const createInitRoute = (options)=>{
1813
2258
  - **Location** – User's location (null if geo-location is disabled)
1814
2259
  - **Translations** – Consent manager copy (from \`Accept-Language\` header)
1815
2260
  - **Branding** – Configured branding key
1816
- - **GVL** – Global Vendor List when enabled
2261
+ - **GVL** – Global Vendor List when IAB is active for the request
1817
2262
 
1818
2263
  Use for geo-targeted consent banners and regional compliance.`,
1819
2264
  tags: [
@@ -1830,42 +2275,9 @@ Use for geo-targeted consent banners and regional compliance.`,
1830
2275
  }
1831
2276
  }
1832
2277
  }), async (c)=>{
1833
- const request = c.req.raw;
1834
- const acceptLanguage = request.headers.get('accept-language') || 'en';
1835
- const location = await getLocation(request, options);
1836
- const jurisdiction = getJurisdiction(location, options);
1837
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
1838
- let gvl = null;
1839
- if (options.advanced?.iab?.enabled) {
1840
- const language = translationsResult.language.split('-')[0] || 'en';
1841
- const gvlResolver = createGVLResolver({
1842
- appName: options.appName || 'c15t',
1843
- bundled: options.advanced.iab.bundled,
1844
- cacheAdapter: options.advanced.cache?.adapter,
1845
- vendorIds: options.advanced.iab.vendorIds,
1846
- endpoint: options.advanced.iab.endpoint
1847
- });
1848
- gvl = await gvlResolver.get(language);
1849
- }
1850
- const customVendors = options.advanced?.iab?.customVendors;
1851
- const gpc = '1' === request.headers.get('sec-gpc');
1852
- getMetrics()?.recordInit({
1853
- jurisdiction,
1854
- country: location?.countryCode ?? void 0,
1855
- region: location?.regionCode ?? void 0,
1856
- gpc
1857
- });
1858
- return c.json({
1859
- jurisdiction,
1860
- location,
1861
- translations: translationsResult,
1862
- branding: options.advanced?.branding || 'c15t',
1863
- gvl,
1864
- customVendors,
1865
- ...options.advanced?.iab?.cmpId != null && {
1866
- cmpId: options.advanced.iab.cmpId
1867
- }
1868
- });
2278
+ const ctx = c.get('c15tContext');
2279
+ const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
2280
+ return c.json(payload);
1869
2281
  });
1870
2282
  return app;
1871
2283
  };
@@ -2221,6 +2633,119 @@ const patchSubjectHandler = async (c)=>{
2221
2633
  });
2222
2634
  }
2223
2635
  };
2636
+ function buildRuntimeDecisionDedupeKey(input) {
2637
+ return [
2638
+ input.tenantId ?? 'default',
2639
+ input.fingerprint,
2640
+ input.matchedBy,
2641
+ input.countryCode ?? 'none',
2642
+ input.regionCode ?? 'none',
2643
+ input.jurisdiction,
2644
+ input.language ?? 'none'
2645
+ ].join('|');
2646
+ }
2647
+ function buildDecisionPayload(params) {
2648
+ const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
2649
+ if (snapshot?.valid && snapshot.payload) {
2650
+ const sp = snapshot.payload;
2651
+ return {
2652
+ tenantId,
2653
+ policyId: sp.policyId,
2654
+ fingerprint: sp.fingerprint,
2655
+ matchedBy: sp.matchedBy,
2656
+ countryCode: sp.country,
2657
+ regionCode: sp.region,
2658
+ jurisdiction: sp.jurisdiction,
2659
+ language: sp.language,
2660
+ model: sp.model,
2661
+ policyI18n: sp.policyI18n,
2662
+ uiMode: sp.uiMode,
2663
+ bannerUi: sp.bannerUi,
2664
+ dialogUi: sp.dialogUi,
2665
+ categories: sp.categories,
2666
+ preselectedCategories: sp.preselectedCategories,
2667
+ proofConfig: sp.proofConfig,
2668
+ dedupeKey: buildRuntimeDecisionDedupeKey({
2669
+ tenantId,
2670
+ fingerprint: sp.fingerprint,
2671
+ matchedBy: sp.matchedBy,
2672
+ countryCode: sp.country,
2673
+ regionCode: sp.region,
2674
+ jurisdiction: sp.jurisdiction,
2675
+ language: sp.language
2676
+ }),
2677
+ source: 'snapshot_token'
2678
+ };
2679
+ }
2680
+ if (decision) return {
2681
+ tenantId,
2682
+ policyId: decision.policy.id,
2683
+ fingerprint: decision.fingerprint,
2684
+ matchedBy: decision.matchedBy,
2685
+ countryCode: location.countryCode,
2686
+ regionCode: location.regionCode,
2687
+ jurisdiction,
2688
+ language,
2689
+ model: decision.policy.model,
2690
+ policyI18n: decision.policy.i18n,
2691
+ uiMode: decision.policy.ui?.mode,
2692
+ bannerUi: decision.policy.ui?.banner,
2693
+ dialogUi: decision.policy.ui?.dialog,
2694
+ categories: decision.policy.consent?.categories,
2695
+ preselectedCategories: decision.policy.consent?.preselectedCategories,
2696
+ proofConfig,
2697
+ dedupeKey: buildRuntimeDecisionDedupeKey({
2698
+ tenantId,
2699
+ fingerprint: decision.fingerprint,
2700
+ matchedBy: decision.matchedBy,
2701
+ countryCode: location.countryCode,
2702
+ regionCode: location.regionCode,
2703
+ jurisdiction,
2704
+ language
2705
+ }),
2706
+ source: 'write_time_fallback'
2707
+ };
2708
+ }
2709
+ function parseLanguageFromHeader(header) {
2710
+ if (!header) return;
2711
+ const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
2712
+ if (!firstLanguage) return;
2713
+ return firstLanguage.split('-')[0]?.toLowerCase();
2714
+ }
2715
+ function resolveSnapshotFailureMode(ctx) {
2716
+ return ctx.policySnapshot?.onValidationFailure ?? 'reject';
2717
+ }
2718
+ function buildSnapshotHttpException(reason) {
2719
+ switch(reason){
2720
+ case 'missing':
2721
+ return new HTTPException(409, {
2722
+ message: 'Policy snapshot token is required',
2723
+ cause: {
2724
+ code: 'POLICY_SNAPSHOT_REQUIRED'
2725
+ }
2726
+ });
2727
+ case 'expired':
2728
+ return new HTTPException(409, {
2729
+ message: 'Policy snapshot token has expired',
2730
+ cause: {
2731
+ code: 'POLICY_SNAPSHOT_EXPIRED'
2732
+ }
2733
+ });
2734
+ case 'malformed':
2735
+ case 'invalid':
2736
+ return new HTTPException(409, {
2737
+ message: 'Policy snapshot token is invalid',
2738
+ cause: {
2739
+ code: 'POLICY_SNAPSHOT_INVALID'
2740
+ }
2741
+ });
2742
+ default:
2743
+ {
2744
+ const _exhaustive = reason;
2745
+ throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
2746
+ }
2747
+ }
2748
+ }
2224
2749
  const postSubjectHandler = async (c)=>{
2225
2750
  const ctx = c.get('c15tContext');
2226
2751
  const logger = ctx.logger;
@@ -2232,9 +2757,6 @@ const postSubjectHandler = async (c)=>{
2232
2757
  const givenAt = new Date(givenAtEpoch);
2233
2758
  const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
2234
2759
  let derivedConsentAction;
2235
- if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
2236
- else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
2237
- else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
2238
2760
  logger.debug('Request parameters', {
2239
2761
  type,
2240
2762
  subjectId,
@@ -2243,6 +2765,50 @@ const postSubjectHandler = async (c)=>{
2243
2765
  domain
2244
2766
  });
2245
2767
  try {
2768
+ 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.');
2769
+ const request = c.req.raw ?? new Request('https://c15t.local/subjects');
2770
+ const acceptLanguage = request.headers.get('accept-language');
2771
+ const requestLanguage = parseLanguageFromHeader(acceptLanguage);
2772
+ const location = await getLocation(request, ctx);
2773
+ const resolvedJurisdiction = getJurisdiction(location, ctx);
2774
+ const snapshotVerification = await verifyPolicySnapshotToken({
2775
+ token: input.policySnapshotToken,
2776
+ options: ctx.policySnapshot,
2777
+ tenantId: ctx.tenantId
2778
+ });
2779
+ const hasValidSnapshot = snapshotVerification.valid;
2780
+ const snapshotPayload = snapshotVerification.valid ? snapshotVerification.payload : null;
2781
+ const shouldRequireSnapshot = !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
2782
+ if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(snapshotVerification.reason);
2783
+ const resolvedPolicyDecision = hasValidSnapshot ? void 0 : await policy_resolvePolicyDecision({
2784
+ policies: ctx.policyPacks,
2785
+ countryCode: location.countryCode,
2786
+ regionCode: location.regionCode,
2787
+ jurisdiction: resolvedJurisdiction,
2788
+ iabEnabled: ctx.iab?.enabled === true
2789
+ });
2790
+ const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
2791
+ id: snapshotPayload.policyId,
2792
+ model: snapshotPayload.model,
2793
+ i18n: snapshotPayload.policyI18n,
2794
+ consent: {
2795
+ expiryDays: snapshotPayload.expiryDays,
2796
+ scopeMode: snapshotPayload.scopeMode,
2797
+ categories: snapshotPayload.categories,
2798
+ preselectedCategories: snapshotPayload.preselectedCategories,
2799
+ gpc: snapshotPayload.gpc
2800
+ },
2801
+ ui: {
2802
+ mode: snapshotPayload.uiMode,
2803
+ banner: snapshotPayload.bannerUi,
2804
+ dialog: snapshotPayload.dialogUi
2805
+ },
2806
+ proof: snapshotPayload.proofConfig
2807
+ } : resolvedPolicyDecision?.policy;
2808
+ const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
2809
+ if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
2810
+ else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
2811
+ else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
2246
2812
  const subject = await registry.findOrCreateSubject({
2247
2813
  subjectId,
2248
2814
  externalSubjectId,
@@ -2269,6 +2835,7 @@ const postSubjectHandler = async (c)=>{
2269
2835
  });
2270
2836
  let policyId;
2271
2837
  let purposeIds = [];
2838
+ let appliedPreferences;
2272
2839
  const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
2273
2840
  if (inputPolicyId) {
2274
2841
  policyId = inputPolicyId;
@@ -2301,20 +2868,66 @@ const postSubjectHandler = async (c)=>{
2301
2868
  policyId = policy.id;
2302
2869
  }
2303
2870
  if (preferences) {
2304
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
2871
+ const allowedCategories = effectivePolicy?.consent?.categories;
2872
+ const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
2873
+ const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
2874
+ const appliedPreferenceEntries = Object.entries(preferences);
2875
+ let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
2876
+ if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
2877
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
2878
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
2879
+ if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new HTTPException(400, {
2880
+ message: 'Preferences include categories not allowed by policy',
2881
+ cause: {
2882
+ code: 'PURPOSE_NOT_ALLOWED',
2883
+ disallowed
2884
+ }
2885
+ });
2886
+ }
2887
+ appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
2888
+ const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
2305
2889
  logger.debug('Consented purposes', {
2306
- consentedPurposes
2890
+ consentedPurposes: filteredConsentedPurposeCodes
2307
2891
  });
2308
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2892
+ const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2309
2893
  const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
2310
2894
  logger.debug('Filtered purposes', {
2311
2895
  purposes
2312
2896
  });
2313
2897
  if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
2314
- consentedPurposes
2898
+ consentedPurposes: filteredConsentedPurposeCodes
2315
2899
  });
2316
2900
  purposeIds = purposes;
2317
2901
  }
2902
+ const expiryDays = effectivePolicy?.consent?.expiryDays;
2903
+ const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
2904
+ const proofConfig = effectivePolicy?.proof;
2905
+ const shouldStoreIp = proofConfig?.storeIp ?? true;
2906
+ const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
2907
+ const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
2908
+ const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
2909
+ const metadataWithPolicy = {
2910
+ ...metadata ?? {},
2911
+ ...shouldStoreLanguage && effectiveLanguage ? {
2912
+ policyLanguage: effectiveLanguage
2913
+ } : {}
2914
+ };
2915
+ const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
2916
+ const decisionPayload = buildDecisionPayload({
2917
+ tenantId: ctx.tenantId,
2918
+ snapshot: hasValidSnapshot && snapshotPayload ? {
2919
+ valid: true,
2920
+ payload: snapshotPayload
2921
+ } : null,
2922
+ decision: resolvedPolicyDecision,
2923
+ location: {
2924
+ countryCode: location.countryCode,
2925
+ regionCode: location.regionCode
2926
+ },
2927
+ jurisdiction: resolvedJurisdiction,
2928
+ language: effectiveLanguage,
2929
+ proofConfig
2930
+ });
2318
2931
  const existingConsent = await db.findFirst('consent', {
2319
2932
  where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
2320
2933
  });
@@ -2329,6 +2942,7 @@ const postSubjectHandler = async (c)=>{
2329
2942
  domain: domainRecord.name,
2330
2943
  type,
2331
2944
  metadata,
2945
+ appliedPreferences,
2332
2946
  uiSource: input.uiSource,
2333
2947
  givenAt: existingConsent.givenAt
2334
2948
  });
@@ -2340,6 +2954,42 @@ const postSubjectHandler = async (c)=>{
2340
2954
  policyId,
2341
2955
  purposeIds
2342
2956
  });
2957
+ const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
2958
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
2959
+ }) ?? await tx.create('runtimePolicyDecision', {
2960
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
2961
+ tenantId: decisionPayload.tenantId,
2962
+ policyId: decisionPayload.policyId,
2963
+ fingerprint: decisionPayload.fingerprint,
2964
+ matchedBy: decisionPayload.matchedBy,
2965
+ countryCode: decisionPayload.countryCode,
2966
+ regionCode: decisionPayload.regionCode,
2967
+ jurisdiction: decisionPayload.jurisdiction,
2968
+ language: decisionPayload.language,
2969
+ model: decisionPayload.model,
2970
+ policyI18n: decisionPayload.policyI18n ? {
2971
+ json: decisionPayload.policyI18n
2972
+ } : void 0,
2973
+ uiMode: decisionPayload.uiMode,
2974
+ bannerUi: decisionPayload.bannerUi ? {
2975
+ json: decisionPayload.bannerUi
2976
+ } : void 0,
2977
+ dialogUi: decisionPayload.dialogUi ? {
2978
+ json: decisionPayload.dialogUi
2979
+ } : void 0,
2980
+ categories: decisionPayload.categories ? {
2981
+ json: decisionPayload.categories
2982
+ } : void 0,
2983
+ preselectedCategories: decisionPayload.preselectedCategories ? {
2984
+ json: decisionPayload.preselectedCategories
2985
+ } : void 0,
2986
+ proofConfig: decisionPayload.proofConfig ? {
2987
+ json: decisionPayload.proofConfig
2988
+ } : void 0,
2989
+ dedupeKey: decisionPayload.dedupeKey
2990
+ }).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
2991
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
2992
+ })) : void 0;
2343
2993
  const consentRecord = await tx.create('consent', {
2344
2994
  id: await utils_generateUniqueId(tx, 'consent', ctx),
2345
2995
  subjectId: subject.id,
@@ -2348,17 +2998,20 @@ const postSubjectHandler = async (c)=>{
2348
2998
  purposeIds: {
2349
2999
  json: purposeIds
2350
3000
  },
2351
- metadata: metadata ? {
2352
- json: metadata
3001
+ metadata: Object.keys(metadataWithPolicy).length > 0 ? {
3002
+ json: metadataWithPolicy
2353
3003
  } : void 0,
2354
- ipAddress: ctx.ipAddress,
2355
- userAgent: ctx.userAgent,
2356
- jurisdiction: input.jurisdiction,
2357
- jurisdictionModel: input.jurisdictionModel,
3004
+ ipAddress: shouldStoreIp ? ctx.ipAddress : null,
3005
+ userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
3006
+ jurisdiction: effectiveJurisdiction,
3007
+ jurisdictionModel: effectiveModel,
2358
3008
  tcString: input.tcString,
2359
3009
  uiSource: input.uiSource,
2360
3010
  consentAction: derivedConsentAction,
2361
- givenAt
3011
+ givenAt,
3012
+ validUntil,
3013
+ runtimePolicyDecisionId: runtimePolicyDecision?.id,
3014
+ runtimePolicySource: decisionPayload?.source
2362
3015
  });
2363
3016
  logger.debug('Created consent', {
2364
3017
  consentRecord: consentRecord.id
@@ -2377,7 +3030,7 @@ const postSubjectHandler = async (c)=>{
2377
3030
  });
2378
3031
  const metrics = getMetrics();
2379
3032
  if (metrics) {
2380
- const jurisdiction = input.jurisdiction;
3033
+ const jurisdiction = effectiveJurisdiction;
2381
3034
  metrics.recordConsentCreated({
2382
3035
  type,
2383
3036
  jurisdiction
@@ -2399,6 +3052,7 @@ const postSubjectHandler = async (c)=>{
2399
3052
  domain: domainRecord.name,
2400
3053
  type,
2401
3054
  metadata,
3055
+ appliedPreferences,
2402
3056
  uiSource: input.uiSource,
2403
3057
  givenAt: result.consent.givenAt
2404
3058
  });
@@ -2522,6 +3176,86 @@ const createSubjectRoutes = ()=>{
2522
3176
  return app;
2523
3177
  };
2524
3178
  const defineConfig = (config)=>config;
3179
+ const DEFAULT_FALLBACK_POLICY_INPUT = {
3180
+ id: 'world_no_banner',
3181
+ isDefault: true,
3182
+ model: 'none',
3183
+ uiMode: 'none'
3184
+ };
3185
+ function mergeMatch(input) {
3186
+ return policyMatchers.merge(input.countries?.length ? policyMatchers.countries(input.countries) : {}, input.regions?.length ? policyMatchers.regions(input.regions) : {}, input.isDefault ? policyMatchers["default"]() : {});
3187
+ }
3188
+ function compactUiSurface(value) {
3189
+ if (!value) return;
3190
+ return compactDefined({
3191
+ allowedActions: dedupeTrimmedStrings(value.allowedActions),
3192
+ primaryAction: value.primaryAction,
3193
+ layout: value.layout,
3194
+ direction: value.direction,
3195
+ uiProfile: value.uiProfile,
3196
+ scrollLock: value.scrollLock
3197
+ });
3198
+ }
3199
+ function buildPolicyConfig(input) {
3200
+ const categories = dedupeTrimmedStrings(input.categories);
3201
+ const preselectedCategories = dedupeTrimmedStrings(input.preselectedCategories);
3202
+ return {
3203
+ id: input.id,
3204
+ match: mergeMatch(input),
3205
+ i18n: input.i18n,
3206
+ consent: compactDefined({
3207
+ model: input.model,
3208
+ expiryDays: input.expiryDays,
3209
+ scopeMode: input.scopeMode,
3210
+ categories,
3211
+ preselectedCategories,
3212
+ gpc: input.gpc
3213
+ }),
3214
+ ui: compactDefined({
3215
+ mode: input.uiMode,
3216
+ banner: compactUiSurface(input.banner),
3217
+ dialog: compactUiSurface(input.dialog)
3218
+ }),
3219
+ proof: compactDefined({
3220
+ storeIp: input.proof?.storeIp,
3221
+ storeUserAgent: input.proof?.storeUserAgent,
3222
+ storeLanguage: input.proof?.storeLanguage
3223
+ })
3224
+ };
3225
+ }
3226
+ function buildPolicyPack(inputs) {
3227
+ return inputs.map((input)=>buildPolicyConfig(input));
3228
+ }
3229
+ function buildPolicyPackWithDefault(inputs, defaultPolicy) {
3230
+ const pack = buildPolicyPack(inputs);
3231
+ const hasDefault = pack.some((policy)=>policy.match.isDefault);
3232
+ if (hasDefault) return pack;
3233
+ const fallbackInput = defaultPolicy ? {
3234
+ ...defaultPolicy,
3235
+ isDefault: true,
3236
+ countries: void 0,
3237
+ regions: void 0
3238
+ } : DEFAULT_FALLBACK_POLICY_INPUT;
3239
+ return [
3240
+ ...pack,
3241
+ buildPolicyConfig(fallbackInput)
3242
+ ];
3243
+ }
3244
+ function composePacks(...packs) {
3245
+ const seen = new Set();
3246
+ const result = [];
3247
+ for (const pack of packs)for (const policy of pack)if (!seen.has(policy.id)) {
3248
+ seen.add(policy.id);
3249
+ result.push(policy);
3250
+ }
3251
+ return result;
3252
+ }
3253
+ const policyBuilder = {
3254
+ create: buildPolicyConfig,
3255
+ createPack: buildPolicyPack,
3256
+ createPackWithDefault: buildPolicyPackWithDefault,
3257
+ composePacks
3258
+ };
2525
3259
  const c15tInstance = (options)=>{
2526
3260
  const context = init(options);
2527
3261
  const logger = logger_createLogger(options.logger);
@@ -2534,7 +3268,7 @@ const c15tInstance = (options)=>{
2534
3268
  app.use('*', async (c, next)=>{
2535
3269
  const request = c.req.raw;
2536
3270
  const startTime = Date.now();
2537
- const apiKeyAuthenticated = validateRequestAuth(request.headers, options.advanced?.apiKeys);
3271
+ const apiKeyAuthenticated = validateRequestAuth(request.headers, options.apiKeys);
2538
3272
  const enrichedContext = {
2539
3273
  ...context,
2540
3274
  ipAddress: getIpAddress(request, options),
@@ -2698,4 +3432,4 @@ const c15tInstance = (options)=>{
2698
3432
  getDocsUI
2699
3433
  };
2700
3434
  };
2701
- export { c15tInstance, defineConfig, version_version as version };
3435
+ export { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, c15tInstance, defineConfig, policy_inspectPolicies as inspectPolicies, policyBuilder, policyMatchers, policyPackPresets, version_version as version };