@c15t/backend 2.0.0-rc.4 → 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 (308) hide show
  1. package/dist/core.cjs +830 -74
  2. package/dist/core.js +807 -75
  3. package/dist/db/schema.cjs +37 -0
  4. package/dist/db/schema.js +33 -2
  5. package/dist/edge.cjs +1106 -0
  6. package/dist/edge.js +1069 -0
  7. package/dist/router.cjs +613 -64
  8. package/dist/router.js +613 -64
  9. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  10. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  11. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  12. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  13. package/{dist → dist-types}/cache/gvl-resolver.d.ts +1 -2
  14. package/{dist → dist-types}/cache/index.d.ts +0 -1
  15. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  16. package/{dist → dist-types}/cache/types.d.ts +0 -1
  17. package/{dist → dist-types}/core.d.ts +8 -1
  18. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  19. package/{dist → dist-types}/db/registry/consent-policy.d.ts +0 -1
  20. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  21. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  22. package/{dist → dist-types}/db/registry/index.d.ts +22 -2
  23. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  24. package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
  25. package/{dist → dist-types}/db/registry/types.d.ts +1 -2
  26. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  27. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  28. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  29. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  30. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  31. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  32. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
  33. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  34. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -1
  35. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
  36. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
  37. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +1 -2
  38. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
  39. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +5 -2
  40. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
  41. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +432 -17
  42. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  43. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -2
  44. package/{dist → dist-types}/db/schema/index.d.ts +862 -33
  45. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  46. package/{dist → dist-types}/define-config.d.ts +0 -1
  47. package/dist-types/edge/index.d.ts +5 -0
  48. package/dist-types/edge/init-handler.d.ts +38 -0
  49. package/dist-types/edge/resolve-consent.d.ts +80 -0
  50. package/dist-types/edge/types.d.ts +13 -0
  51. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  52. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  53. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  54. package/{dist → dist-types}/handlers/init/index.d.ts +4 -5
  55. package/dist-types/handlers/init/policy.d.ts +26 -0
  56. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  57. package/dist-types/handlers/init/translations.d.ts +48 -0
  58. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  59. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  60. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  61. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +0 -1
  62. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  63. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +0 -1
  64. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
  65. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  66. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
  67. package/{dist → dist-types}/init.d.ts +0 -1
  68. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  69. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  70. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  71. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  72. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +1 -2
  73. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  74. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  75. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  76. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  77. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  78. package/dist-types/policies/builder.d.ts +127 -0
  79. package/dist-types/policies/defaults.d.ts +2 -0
  80. package/dist-types/policies/matchers.d.ts +3 -0
  81. package/{dist → dist-types}/router.d.ts +0 -1
  82. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  83. package/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
  84. package/{dist → dist-types}/routes/init.d.ts +0 -1
  85. package/{dist → dist-types}/routes/status.d.ts +0 -1
  86. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  87. package/{dist → dist-types}/types/api.d.ts +0 -1
  88. package/{dist → dist-types}/types/index.d.ts +110 -6
  89. package/dist-types/utils/background.d.ts +6 -0
  90. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +0 -1
  91. package/{dist → dist-types}/utils/env.d.ts +0 -1
  92. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  93. package/{dist → dist-types}/utils/instrumentation.d.ts +0 -1
  94. package/{dist → dist-types}/utils/logger.d.ts +1 -2
  95. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  96. package/dist-types/version.d.ts +1 -0
  97. package/docs/README.md +49 -0
  98. package/docs/api/configuration.md +197 -0
  99. package/docs/api/endpoints.md +211 -0
  100. package/docs/guides/caching.md +85 -0
  101. package/docs/guides/database-setup.md +128 -0
  102. package/docs/guides/edge-deployment.md +248 -0
  103. package/docs/guides/framework-integration.md +142 -0
  104. package/docs/guides/iab-tcf.md +89 -0
  105. package/docs/guides/observability.md +96 -0
  106. package/docs/guides/policy-packs.md +396 -0
  107. package/docs/quickstart.md +129 -0
  108. package/package.json +33 -19
  109. package/.turbo/turbo-build.log +0 -49
  110. package/CHANGELOG.md +0 -123
  111. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  112. package/dist/cache/adapters/index.d.ts.map +0 -1
  113. package/dist/cache/adapters/memory.d.ts.map +0 -1
  114. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  115. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  116. package/dist/cache/index.d.ts.map +0 -1
  117. package/dist/cache/keys.d.ts.map +0 -1
  118. package/dist/cache/types.d.ts.map +0 -1
  119. package/dist/core.d.ts.map +0 -1
  120. package/dist/db/adapters/drizzle.d.ts +0 -2
  121. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  122. package/dist/db/adapters/index.d.ts +0 -2
  123. package/dist/db/adapters/index.d.ts.map +0 -1
  124. package/dist/db/adapters/kysely.d.ts +0 -2
  125. package/dist/db/adapters/kysely.d.ts.map +0 -1
  126. package/dist/db/adapters/mongo.d.ts +0 -2
  127. package/dist/db/adapters/mongo.d.ts.map +0 -1
  128. package/dist/db/adapters/prisma.d.ts +0 -2
  129. package/dist/db/adapters/prisma.d.ts.map +0 -1
  130. package/dist/db/adapters/typeorm.d.ts +0 -2
  131. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  132. package/dist/db/migrator/index.d.ts.map +0 -1
  133. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  134. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  135. package/dist/db/registry/domain.d.ts.map +0 -1
  136. package/dist/db/registry/index.d.ts.map +0 -1
  137. package/dist/db/registry/subject.d.ts.map +0 -1
  138. package/dist/db/registry/types.d.ts.map +0 -1
  139. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  140. package/dist/db/registry/utils.d.ts.map +0 -1
  141. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  142. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  143. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  144. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  145. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  146. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  147. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  148. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  149. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  150. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  151. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  152. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  153. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  154. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  155. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  156. package/dist/db/schema/index.d.ts.map +0 -1
  157. package/dist/db/tenant-scope.d.ts.map +0 -1
  158. package/dist/define-config.d.ts.map +0 -1
  159. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  160. package/dist/handlers/consent/index.d.ts +0 -12
  161. package/dist/handlers/consent/index.d.ts.map +0 -1
  162. package/dist/handlers/init/geo.d.ts.map +0 -1
  163. package/dist/handlers/init/index.d.ts.map +0 -1
  164. package/dist/handlers/init/translations.d.ts +0 -26
  165. package/dist/handlers/init/translations.d.ts.map +0 -1
  166. package/dist/handlers/status/index.d.ts +0 -7
  167. package/dist/handlers/status/index.d.ts.map +0 -1
  168. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  169. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  170. package/dist/handlers/subject/index.d.ts +0 -10
  171. package/dist/handlers/subject/index.d.ts.map +0 -1
  172. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  173. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  174. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  175. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  176. package/dist/init.d.ts.map +0 -1
  177. package/dist/middleware/auth/index.d.ts.map +0 -1
  178. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  179. package/dist/middleware/cors/cors.d.ts.map +0 -1
  180. package/dist/middleware/cors/index.d.ts +0 -30
  181. package/dist/middleware/cors/index.d.ts.map +0 -1
  182. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  183. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  184. package/dist/middleware/openapi/config.d.ts.map +0 -1
  185. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  186. package/dist/middleware/openapi/index.d.ts +0 -12
  187. package/dist/middleware/openapi/index.d.ts.map +0 -1
  188. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  189. package/dist/router.d.ts.map +0 -1
  190. package/dist/routes/consent.d.ts.map +0 -1
  191. package/dist/routes/index.d.ts +0 -10
  192. package/dist/routes/index.d.ts.map +0 -1
  193. package/dist/routes/init.d.ts.map +0 -1
  194. package/dist/routes/status.d.ts.map +0 -1
  195. package/dist/routes/subject.d.ts.map +0 -1
  196. package/dist/types/api.d.ts.map +0 -1
  197. package/dist/types/index.d.ts.map +0 -1
  198. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  199. package/dist/utils/env.d.ts.map +0 -1
  200. package/dist/utils/extract-error-message.d.ts.map +0 -1
  201. package/dist/utils/index.d.ts +0 -4
  202. package/dist/utils/index.d.ts.map +0 -1
  203. package/dist/utils/instrumentation.d.ts.map +0 -1
  204. package/dist/utils/logger.d.ts.map +0 -1
  205. package/dist/utils/metrics.d.ts.map +0 -1
  206. package/dist/version.d.ts +0 -2
  207. package/dist/version.d.ts.map +0 -1
  208. package/knip.json +0 -31
  209. package/rslib.config.ts +0 -93
  210. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  211. package/src/cache/adapters/index.ts +0 -22
  212. package/src/cache/adapters/memory.ts +0 -111
  213. package/src/cache/adapters/upstash-redis.ts +0 -113
  214. package/src/cache/gvl-resolver.ts +0 -289
  215. package/src/cache/index.ts +0 -34
  216. package/src/cache/keys.ts +0 -68
  217. package/src/cache/types.ts +0 -66
  218. package/src/core.ts +0 -369
  219. package/src/db/migrator/index.ts +0 -80
  220. package/src/db/registry/consent-policy.test.ts +0 -451
  221. package/src/db/registry/consent-policy.ts +0 -82
  222. package/src/db/registry/consent-purpose.test.ts +0 -428
  223. package/src/db/registry/consent-purpose.ts +0 -61
  224. package/src/db/registry/domain.test.ts +0 -445
  225. package/src/db/registry/domain.ts +0 -91
  226. package/src/db/registry/index.ts +0 -14
  227. package/src/db/registry/subject.test.ts +0 -371
  228. package/src/db/registry/subject.ts +0 -126
  229. package/src/db/registry/types.ts +0 -10
  230. package/src/db/registry/utils/generate-id.test.ts +0 -216
  231. package/src/db/registry/utils/generate-id.ts +0 -133
  232. package/src/db/registry/utils.ts +0 -133
  233. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  234. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  235. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  236. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  237. package/src/db/schema/1.0.0/consent.ts +0 -20
  238. package/src/db/schema/1.0.0/domain.ts +0 -12
  239. package/src/db/schema/1.0.0/index.ts +0 -48
  240. package/src/db/schema/1.0.0/subject.ts +0 -11
  241. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  242. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  243. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  244. package/src/db/schema/2.0.0/consent.ts +0 -28
  245. package/src/db/schema/2.0.0/domain.ts +0 -12
  246. package/src/db/schema/2.0.0/index.ts +0 -47
  247. package/src/db/schema/2.0.0/subject.ts +0 -13
  248. package/src/db/schema/index.ts +0 -15
  249. package/src/db/tenant-scope.test.ts +0 -747
  250. package/src/db/tenant-scope.ts +0 -103
  251. package/src/define-config.ts +0 -19
  252. package/src/handlers/consent/check.handler.ts +0 -126
  253. package/src/handlers/init/geo.test.ts +0 -317
  254. package/src/handlers/init/geo.ts +0 -195
  255. package/src/handlers/init/index.test.ts +0 -205
  256. package/src/handlers/init/index.ts +0 -114
  257. package/src/handlers/init/translations.test.ts +0 -121
  258. package/src/handlers/init/translations.ts +0 -69
  259. package/src/handlers/status/status.handler.test.ts +0 -155
  260. package/src/handlers/status/status.handler.ts +0 -51
  261. package/src/handlers/subject/get.handler.ts +0 -92
  262. package/src/handlers/subject/list.handler.ts +0 -92
  263. package/src/handlers/subject/patch.handler.ts +0 -119
  264. package/src/handlers/subject/post.handler.test.ts +0 -294
  265. package/src/handlers/subject/post.handler.ts +0 -268
  266. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  267. package/src/handlers/utils/consent-enrichment.ts +0 -218
  268. package/src/init.test.ts +0 -122
  269. package/src/init.ts +0 -88
  270. package/src/middleware/auth/index.ts +0 -11
  271. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  272. package/src/middleware/auth/validate-api-key.ts +0 -107
  273. package/src/middleware/cors/cors.test.ts +0 -135
  274. package/src/middleware/cors/cors.ts +0 -186
  275. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  276. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  277. package/src/middleware/cors/process-cors.ts +0 -91
  278. package/src/middleware/openapi/config.ts +0 -29
  279. package/src/middleware/openapi/handlers.ts +0 -34
  280. package/src/middleware/process-ip/index.test.ts +0 -193
  281. package/src/middleware/process-ip/index.ts +0 -199
  282. package/src/router.ts +0 -15
  283. package/src/routes/consent.ts +0 -52
  284. package/src/routes/init.ts +0 -105
  285. package/src/routes/status.ts +0 -46
  286. package/src/routes/subject.ts +0 -152
  287. package/src/types/api.ts +0 -48
  288. package/src/types/index.ts +0 -391
  289. package/src/utils/create-telemetry-options.test.ts +0 -286
  290. package/src/utils/create-telemetry-options.ts +0 -229
  291. package/src/utils/env.ts +0 -84
  292. package/src/utils/extract-error-message.ts +0 -21
  293. package/src/utils/instrumentation.test.ts +0 -183
  294. package/src/utils/instrumentation.ts +0 -194
  295. package/src/utils/logger.ts +0 -41
  296. package/src/utils/metrics.test.ts +0 -311
  297. package/src/utils/metrics.ts +0 -402
  298. package/src/utils/telemetry-pii.test.ts +0 -323
  299. package/src/version.ts +0 -2
  300. package/tsconfig.json +0 -11
  301. package/vitest.config.ts +0 -28
  302. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  303. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  304. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  305. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  306. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  307. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  308. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/core.js CHANGED
@@ -5,12 +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 { deepMergeTranslations, selectLanguage } from "@c15t/translations";
13
- import { baseTranslations } from "@c15t/translations/all";
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";
14
16
  import { object, optional, string } from "valibot";
15
17
  function extractBearerToken(authHeader) {
16
18
  if (!authHeader) return null;
@@ -137,7 +139,7 @@ function createCORSOptions(trustedOrigins) {
137
139
  ]
138
140
  };
139
141
  }
140
- const version_version = '2.0.0-rc.4';
142
+ const version_version = '2.0.0-rc.5';
141
143
  const config_createOpenAPIConfig = (options)=>({
142
144
  enabled: options.openapi?.enabled !== false,
143
145
  specPath: '/spec.json',
@@ -253,6 +255,133 @@ function getIpAddress(req, options) {
253
255
  }
254
256
  return null;
255
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
+ }
256
385
  function extractErrorMessage(error) {
257
386
  if (error instanceof AggregateError && error.errors?.length > 0) {
258
387
  const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
@@ -575,6 +704,7 @@ const prefixes = {
575
704
  consentPolicy: 'pol',
576
705
  consentPurpose: 'pur',
577
706
  domain: 'dom',
707
+ runtimePolicyDecision: 'rpd',
578
708
  subject: 'sub'
579
709
  };
580
710
  const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
@@ -846,6 +976,54 @@ function domainRegistry({ db, ctx }) {
846
976
  }
847
977
  };
848
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
+ }
849
1027
  function subjectRegistry({ db, ctx }) {
850
1028
  const { logger } = ctx;
851
1029
  return {
@@ -923,7 +1101,8 @@ const createRegistry = (ctx)=>({
923
1101
  ...subjectRegistry(ctx),
924
1102
  ...consentPurposeRegistry(ctx),
925
1103
  ...policyRegistry(ctx),
926
- ...domainRegistry(ctx)
1104
+ ...domainRegistry(ctx),
1105
+ ...runtimePolicyDecisionRegistry(ctx)
927
1106
  });
928
1107
  const auditLogTable = schema_table('auditLog', {
929
1108
  id: idColumn('id', 'varchar(255)'),
@@ -1090,6 +1269,8 @@ const consent_consentTable = schema_table('consent', {
1090
1269
  tcString: column('tcString', 'string').nullable(),
1091
1270
  uiSource: column('uiSource', 'string').nullable(),
1092
1271
  consentAction: column('consentAction', 'string').nullable(),
1272
+ runtimePolicyDecisionId: column('runtimePolicyDecisionId', 'string').nullable(),
1273
+ runtimePolicySource: column('runtimePolicySource', 'string').nullable(),
1093
1274
  tenantId: column('tenantId', 'string').nullable()
1094
1275
  });
1095
1276
  const consent_policy_consentPolicyTable = schema_table('consentPolicy', {
@@ -1115,6 +1296,27 @@ const domain_domainTable = schema_table('domain', {
1115
1296
  updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
1116
1297
  tenantId: column('tenantId', 'string').nullable()
1117
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
+ });
1118
1320
  const subject_subjectTable = schema_table('subject', {
1119
1321
  id: idColumn('id', 'varchar(255)'),
1120
1322
  externalId: column('externalId', 'string').nullable(),
@@ -1129,6 +1331,7 @@ const v2 = schema({
1129
1331
  subject: subject_subjectTable,
1130
1332
  domain: domain_domainTable,
1131
1333
  consentPolicy: consent_policy_consentPolicyTable,
1334
+ runtimePolicyDecision: runtimePolicyDecisionTable,
1132
1335
  consentPurpose: consent_purpose_consentPurposeTable,
1133
1336
  consent: consent_consentTable,
1134
1337
  auditLog: audit_log_auditLogTable
@@ -1144,6 +1347,9 @@ const v2 = schema({
1144
1347
  consentPolicy: ({ many })=>({
1145
1348
  consents: many('consent')
1146
1349
  }),
1350
+ runtimePolicyDecision: ({ many })=>({
1351
+ consents: many('consent')
1352
+ }),
1147
1353
  consentPurpose: ()=>({}),
1148
1354
  consent: ({ one })=>({
1149
1355
  subject: one('subject', [
@@ -1157,6 +1363,10 @@ const v2 = schema({
1157
1363
  policy: one('consentPolicy', [
1158
1364
  'policyId',
1159
1365
  'id'
1366
+ ]).foreignKey(),
1367
+ runtimePolicyDecision: one('runtimePolicyDecision', [
1368
+ 'runtimePolicyDecisionId',
1369
+ 'id'
1160
1370
  ]).foreignKey()
1161
1371
  }),
1162
1372
  auditLog: ({ one })=>({
@@ -1243,6 +1453,18 @@ function withTenantScope(db, tenantId) {
1243
1453
  }
1244
1454
  });
1245
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
+ }
1246
1468
  let globalLogger;
1247
1469
  function initLogger(options) {
1248
1470
  globalLogger = logger_createLogger({
@@ -1269,6 +1491,18 @@ const init = (options)=>{
1269
1491
  const rawOrm = client.orm('2.0.0');
1270
1492
  const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
1271
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]);
1272
1506
  const context = {
1273
1507
  ...baseOptions,
1274
1508
  appName,
@@ -1628,6 +1862,123 @@ function createGVLResolver(options) {
1628
1862
  }
1629
1863
  };
1630
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
+ }
1631
1982
  function geo_normalizeHeader(value) {
1632
1983
  if (!value) return null;
1633
1984
  return Array.isArray(value) ? value[0] ?? null : value;
@@ -1783,26 +2134,118 @@ function getJurisdiction(location, options) {
1783
2134
  if (options.disableGeoLocation) return 'GDPR';
1784
2135
  return checkJurisdiction(location.countryCode, location.regionCode);
1785
2136
  }
1786
- function isSupportedBaseLanguage(lang) {
1787
- return lang in baseTranslations;
2137
+ function stripIabTranslations(translations) {
2138
+ const { iab: _iab, ...rest } = translations;
2139
+ return rest;
1788
2140
  }
1789
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
1790
- const supportedDefaultLanguages = Object.keys(baseTranslations);
1791
- const supportedCustomLanguages = Object.keys(customTranslations || {});
1792
- const supportedLanguages = [
1793
- ...supportedDefaultLanguages,
1794
- ...supportedCustomLanguages
1795
- ];
1796
- const preferredLanguage = selectLanguage(supportedLanguages, {
1797
- header: acceptLanguage,
1798
- 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
1799
2220
  });
1800
- const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
1801
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1802
- const translations = custom ? deepMergeTranslations(base, custom) : base;
1803
2221
  return {
1804
- translations: translations,
1805
- 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
+ }
1806
2249
  };
1807
2250
  }
1808
2251
  const createInitRoute = (options)=>{
@@ -1815,7 +2258,7 @@ const createInitRoute = (options)=>{
1815
2258
  - **Location** – User's location (null if geo-location is disabled)
1816
2259
  - **Translations** – Consent manager copy (from \`Accept-Language\` header)
1817
2260
  - **Branding** – Configured branding key
1818
- - **GVL** – Global Vendor List when enabled
2261
+ - **GVL** – Global Vendor List when IAB is active for the request
1819
2262
 
1820
2263
  Use for geo-targeted consent banners and regional compliance.`,
1821
2264
  tags: [
@@ -1832,42 +2275,9 @@ Use for geo-targeted consent banners and regional compliance.`,
1832
2275
  }
1833
2276
  }
1834
2277
  }), async (c)=>{
1835
- const request = c.req.raw;
1836
- const acceptLanguage = request.headers.get('accept-language') || 'en';
1837
- const location = await getLocation(request, options);
1838
- const jurisdiction = getJurisdiction(location, options);
1839
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations);
1840
- let gvl = null;
1841
- if (options.iab?.enabled) {
1842
- const language = translationsResult.language.split('-')[0] || 'en';
1843
- const gvlResolver = createGVLResolver({
1844
- appName: options.appName || 'c15t',
1845
- bundled: options.iab.bundled,
1846
- cacheAdapter: options.cache?.adapter,
1847
- vendorIds: options.iab.vendorIds,
1848
- endpoint: options.iab.endpoint
1849
- });
1850
- gvl = await gvlResolver.get(language);
1851
- }
1852
- const customVendors = options.iab?.customVendors;
1853
- const gpc = '1' === request.headers.get('sec-gpc');
1854
- getMetrics()?.recordInit({
1855
- jurisdiction,
1856
- country: location?.countryCode ?? void 0,
1857
- region: location?.regionCode ?? void 0,
1858
- gpc
1859
- });
1860
- return c.json({
1861
- jurisdiction,
1862
- location,
1863
- translations: translationsResult,
1864
- branding: options.branding || 'c15t',
1865
- gvl,
1866
- customVendors,
1867
- ...options.iab?.cmpId != null && {
1868
- cmpId: options.iab.cmpId
1869
- }
1870
- });
2278
+ const ctx = c.get('c15tContext');
2279
+ const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
2280
+ return c.json(payload);
1871
2281
  });
1872
2282
  return app;
1873
2283
  };
@@ -2223,6 +2633,119 @@ const patchSubjectHandler = async (c)=>{
2223
2633
  });
2224
2634
  }
2225
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
+ }
2226
2749
  const postSubjectHandler = async (c)=>{
2227
2750
  const ctx = c.get('c15tContext');
2228
2751
  const logger = ctx.logger;
@@ -2234,9 +2757,6 @@ const postSubjectHandler = async (c)=>{
2234
2757
  const givenAt = new Date(givenAtEpoch);
2235
2758
  const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
2236
2759
  let derivedConsentAction;
2237
- if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
2238
- else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
2239
- else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
2240
2760
  logger.debug('Request parameters', {
2241
2761
  type,
2242
2762
  subjectId,
@@ -2245,6 +2765,50 @@ const postSubjectHandler = async (c)=>{
2245
2765
  domain
2246
2766
  });
2247
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';
2248
2812
  const subject = await registry.findOrCreateSubject({
2249
2813
  subjectId,
2250
2814
  externalSubjectId,
@@ -2271,6 +2835,7 @@ const postSubjectHandler = async (c)=>{
2271
2835
  });
2272
2836
  let policyId;
2273
2837
  let purposeIds = [];
2838
+ let appliedPreferences;
2274
2839
  const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
2275
2840
  if (inputPolicyId) {
2276
2841
  policyId = inputPolicyId;
@@ -2303,20 +2868,66 @@ const postSubjectHandler = async (c)=>{
2303
2868
  policyId = policy.id;
2304
2869
  }
2305
2870
  if (preferences) {
2306
- 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);
2307
2889
  logger.debug('Consented purposes', {
2308
- consentedPurposes
2890
+ consentedPurposes: filteredConsentedPurposeCodes
2309
2891
  });
2310
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2892
+ const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2311
2893
  const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
2312
2894
  logger.debug('Filtered purposes', {
2313
2895
  purposes
2314
2896
  });
2315
2897
  if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
2316
- consentedPurposes
2898
+ consentedPurposes: filteredConsentedPurposeCodes
2317
2899
  });
2318
2900
  purposeIds = purposes;
2319
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
+ });
2320
2931
  const existingConsent = await db.findFirst('consent', {
2321
2932
  where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
2322
2933
  });
@@ -2331,6 +2942,7 @@ const postSubjectHandler = async (c)=>{
2331
2942
  domain: domainRecord.name,
2332
2943
  type,
2333
2944
  metadata,
2945
+ appliedPreferences,
2334
2946
  uiSource: input.uiSource,
2335
2947
  givenAt: existingConsent.givenAt
2336
2948
  });
@@ -2342,6 +2954,42 @@ const postSubjectHandler = async (c)=>{
2342
2954
  policyId,
2343
2955
  purposeIds
2344
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;
2345
2993
  const consentRecord = await tx.create('consent', {
2346
2994
  id: await utils_generateUniqueId(tx, 'consent', ctx),
2347
2995
  subjectId: subject.id,
@@ -2350,17 +2998,20 @@ const postSubjectHandler = async (c)=>{
2350
2998
  purposeIds: {
2351
2999
  json: purposeIds
2352
3000
  },
2353
- metadata: metadata ? {
2354
- json: metadata
3001
+ metadata: Object.keys(metadataWithPolicy).length > 0 ? {
3002
+ json: metadataWithPolicy
2355
3003
  } : void 0,
2356
- ipAddress: ctx.ipAddress,
2357
- userAgent: ctx.userAgent,
2358
- jurisdiction: input.jurisdiction,
2359
- jurisdictionModel: input.jurisdictionModel,
3004
+ ipAddress: shouldStoreIp ? ctx.ipAddress : null,
3005
+ userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
3006
+ jurisdiction: effectiveJurisdiction,
3007
+ jurisdictionModel: effectiveModel,
2360
3008
  tcString: input.tcString,
2361
3009
  uiSource: input.uiSource,
2362
3010
  consentAction: derivedConsentAction,
2363
- givenAt
3011
+ givenAt,
3012
+ validUntil,
3013
+ runtimePolicyDecisionId: runtimePolicyDecision?.id,
3014
+ runtimePolicySource: decisionPayload?.source
2364
3015
  });
2365
3016
  logger.debug('Created consent', {
2366
3017
  consentRecord: consentRecord.id
@@ -2379,7 +3030,7 @@ const postSubjectHandler = async (c)=>{
2379
3030
  });
2380
3031
  const metrics = getMetrics();
2381
3032
  if (metrics) {
2382
- const jurisdiction = input.jurisdiction;
3033
+ const jurisdiction = effectiveJurisdiction;
2383
3034
  metrics.recordConsentCreated({
2384
3035
  type,
2385
3036
  jurisdiction
@@ -2401,6 +3052,7 @@ const postSubjectHandler = async (c)=>{
2401
3052
  domain: domainRecord.name,
2402
3053
  type,
2403
3054
  metadata,
3055
+ appliedPreferences,
2404
3056
  uiSource: input.uiSource,
2405
3057
  givenAt: result.consent.givenAt
2406
3058
  });
@@ -2524,6 +3176,86 @@ const createSubjectRoutes = ()=>{
2524
3176
  return app;
2525
3177
  };
2526
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
+ };
2527
3259
  const c15tInstance = (options)=>{
2528
3260
  const context = init(options);
2529
3261
  const logger = logger_createLogger(options.logger);
@@ -2700,4 +3432,4 @@ const c15tInstance = (options)=>{
2700
3432
  getDocsUI
2701
3433
  };
2702
3434
  };
2703
- 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 };