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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/dist/302.js +473 -0
  2. package/dist/364.js +1140 -0
  3. package/dist/583.js +540 -0
  4. package/dist/cache.cjs +1 -1
  5. package/dist/cache.js +4 -415
  6. package/dist/core.cjs +849 -96
  7. package/dist/core.js +147 -1817
  8. package/dist/db/adapters/drizzle.cjs +1 -1
  9. package/dist/db/adapters/drizzle.js +1 -2
  10. package/dist/db/adapters/kysely.cjs +1 -1
  11. package/dist/db/adapters/kysely.js +1 -2
  12. package/dist/db/adapters/mongo.cjs +1 -1
  13. package/dist/db/adapters/mongo.js +1 -2
  14. package/dist/db/adapters/prisma.cjs +1 -1
  15. package/dist/db/adapters/prisma.js +1 -2
  16. package/dist/db/adapters/typeorm.cjs +1 -1
  17. package/dist/db/adapters/typeorm.js +1 -2
  18. package/dist/db/adapters.cjs +1 -1
  19. package/dist/db/migrator.cjs +1 -1
  20. package/dist/db/schema.cjs +38 -1
  21. package/dist/db/schema.js +33 -2
  22. package/dist/define-config.cjs +1 -1
  23. package/dist/edge.cjs +1106 -0
  24. package/dist/edge.js +190 -0
  25. package/dist/router.cjs +629 -81
  26. package/dist/router.js +1 -1509
  27. package/dist/types/index.cjs +1 -1
  28. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  29. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  30. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  31. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  32. package/{dist → dist-types}/cache/gvl-resolver.d.ts +1 -2
  33. package/{dist → dist-types}/cache/index.d.ts +0 -1
  34. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  35. package/{dist → dist-types}/cache/types.d.ts +0 -1
  36. package/{dist → dist-types}/core.d.ts +8 -1
  37. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  38. package/{dist → dist-types}/db/registry/consent-policy.d.ts +0 -1
  39. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  40. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  41. package/{dist → dist-types}/db/registry/index.d.ts +22 -2
  42. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  43. package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
  44. package/{dist → dist-types}/db/registry/types.d.ts +1 -2
  45. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  46. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  47. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  48. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  49. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  50. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  51. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +2 -3
  52. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  53. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -1
  54. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
  55. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +2 -3
  56. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +2 -3
  57. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +2 -3
  58. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +6 -3
  59. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +2 -3
  60. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +432 -17
  61. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  62. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +2 -3
  63. package/{dist → dist-types}/db/schema/index.d.ts +862 -33
  64. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  65. package/{dist → dist-types}/define-config.d.ts +0 -1
  66. package/dist-types/edge/index.d.ts +5 -0
  67. package/dist-types/edge/init-handler.d.ts +38 -0
  68. package/dist-types/edge/resolve-consent.d.ts +80 -0
  69. package/dist-types/edge/types.d.ts +13 -0
  70. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  71. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  72. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  73. package/{dist → dist-types}/handlers/init/index.d.ts +4 -5
  74. package/dist-types/handlers/init/policy.d.ts +26 -0
  75. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  76. package/dist-types/handlers/init/translations.d.ts +48 -0
  77. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  78. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  79. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  80. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +0 -1
  81. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  82. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +0 -1
  83. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
  84. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  85. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
  86. package/{dist → dist-types}/init.d.ts +0 -1
  87. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  88. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  89. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  90. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  91. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +1 -2
  92. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  93. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  94. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  95. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  96. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  97. package/dist-types/policies/builder.d.ts +127 -0
  98. package/dist-types/policies/defaults.d.ts +2 -0
  99. package/dist-types/policies/matchers.d.ts +3 -0
  100. package/{dist → dist-types}/router.d.ts +0 -1
  101. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  102. package/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
  103. package/{dist → dist-types}/routes/init.d.ts +0 -1
  104. package/{dist → dist-types}/routes/status.d.ts +0 -1
  105. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  106. package/{dist → dist-types}/types/api.d.ts +0 -1
  107. package/{dist → dist-types}/types/index.d.ts +110 -6
  108. package/dist-types/utils/background.d.ts +6 -0
  109. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +0 -1
  110. package/{dist → dist-types}/utils/env.d.ts +0 -1
  111. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  112. package/{dist → dist-types}/utils/instrumentation.d.ts +0 -1
  113. package/{dist → dist-types}/utils/logger.d.ts +1 -2
  114. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  115. package/dist-types/version.d.ts +1 -0
  116. package/docs/README.md +49 -0
  117. package/docs/api/configuration.md +197 -0
  118. package/docs/api/endpoints.md +211 -0
  119. package/docs/guides/caching.md +85 -0
  120. package/docs/guides/database-setup.md +128 -0
  121. package/docs/guides/edge-deployment.md +248 -0
  122. package/docs/guides/framework-integration.md +142 -0
  123. package/docs/guides/iab-tcf.md +89 -0
  124. package/docs/guides/observability.md +96 -0
  125. package/docs/guides/policy-packs.md +396 -0
  126. package/docs/quickstart.md +129 -0
  127. package/package.json +45 -31
  128. package/.turbo/turbo-build.log +0 -49
  129. package/CHANGELOG.md +0 -123
  130. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  131. package/dist/cache/adapters/index.d.ts.map +0 -1
  132. package/dist/cache/adapters/memory.d.ts.map +0 -1
  133. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  134. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  135. package/dist/cache/index.d.ts.map +0 -1
  136. package/dist/cache/keys.d.ts.map +0 -1
  137. package/dist/cache/types.d.ts.map +0 -1
  138. package/dist/core.d.ts.map +0 -1
  139. package/dist/db/adapters/drizzle.d.ts +0 -2
  140. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  141. package/dist/db/adapters/index.d.ts +0 -2
  142. package/dist/db/adapters/index.d.ts.map +0 -1
  143. package/dist/db/adapters/kysely.d.ts +0 -2
  144. package/dist/db/adapters/kysely.d.ts.map +0 -1
  145. package/dist/db/adapters/mongo.d.ts +0 -2
  146. package/dist/db/adapters/mongo.d.ts.map +0 -1
  147. package/dist/db/adapters/prisma.d.ts +0 -2
  148. package/dist/db/adapters/prisma.d.ts.map +0 -1
  149. package/dist/db/adapters/typeorm.d.ts +0 -2
  150. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  151. package/dist/db/migrator/index.d.ts.map +0 -1
  152. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  153. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  154. package/dist/db/registry/domain.d.ts.map +0 -1
  155. package/dist/db/registry/index.d.ts.map +0 -1
  156. package/dist/db/registry/subject.d.ts.map +0 -1
  157. package/dist/db/registry/types.d.ts.map +0 -1
  158. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  159. package/dist/db/registry/utils.d.ts.map +0 -1
  160. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  161. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  162. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  163. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  164. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  165. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  166. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  167. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  168. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  169. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  170. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  171. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  172. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  173. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  174. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  175. package/dist/db/schema/index.d.ts.map +0 -1
  176. package/dist/db/tenant-scope.d.ts.map +0 -1
  177. package/dist/define-config.d.ts.map +0 -1
  178. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  179. package/dist/handlers/consent/index.d.ts +0 -12
  180. package/dist/handlers/consent/index.d.ts.map +0 -1
  181. package/dist/handlers/init/geo.d.ts.map +0 -1
  182. package/dist/handlers/init/index.d.ts.map +0 -1
  183. package/dist/handlers/init/translations.d.ts +0 -26
  184. package/dist/handlers/init/translations.d.ts.map +0 -1
  185. package/dist/handlers/status/index.d.ts +0 -7
  186. package/dist/handlers/status/index.d.ts.map +0 -1
  187. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  188. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  189. package/dist/handlers/subject/index.d.ts +0 -10
  190. package/dist/handlers/subject/index.d.ts.map +0 -1
  191. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  192. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  193. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  194. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  195. package/dist/init.d.ts.map +0 -1
  196. package/dist/middleware/auth/index.d.ts.map +0 -1
  197. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  198. package/dist/middleware/cors/cors.d.ts.map +0 -1
  199. package/dist/middleware/cors/index.d.ts +0 -30
  200. package/dist/middleware/cors/index.d.ts.map +0 -1
  201. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  202. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  203. package/dist/middleware/openapi/config.d.ts.map +0 -1
  204. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  205. package/dist/middleware/openapi/index.d.ts +0 -12
  206. package/dist/middleware/openapi/index.d.ts.map +0 -1
  207. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  208. package/dist/router.d.ts.map +0 -1
  209. package/dist/routes/consent.d.ts.map +0 -1
  210. package/dist/routes/index.d.ts +0 -10
  211. package/dist/routes/index.d.ts.map +0 -1
  212. package/dist/routes/init.d.ts.map +0 -1
  213. package/dist/routes/status.d.ts.map +0 -1
  214. package/dist/routes/subject.d.ts.map +0 -1
  215. package/dist/types/api.d.ts.map +0 -1
  216. package/dist/types/index.d.ts.map +0 -1
  217. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  218. package/dist/utils/env.d.ts.map +0 -1
  219. package/dist/utils/extract-error-message.d.ts.map +0 -1
  220. package/dist/utils/index.d.ts +0 -4
  221. package/dist/utils/index.d.ts.map +0 -1
  222. package/dist/utils/instrumentation.d.ts.map +0 -1
  223. package/dist/utils/logger.d.ts.map +0 -1
  224. package/dist/utils/metrics.d.ts.map +0 -1
  225. package/dist/version.d.ts +0 -2
  226. package/dist/version.d.ts.map +0 -1
  227. package/knip.json +0 -31
  228. package/rslib.config.ts +0 -93
  229. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  230. package/src/cache/adapters/index.ts +0 -22
  231. package/src/cache/adapters/memory.ts +0 -111
  232. package/src/cache/adapters/upstash-redis.ts +0 -113
  233. package/src/cache/gvl-resolver.ts +0 -289
  234. package/src/cache/index.ts +0 -34
  235. package/src/cache/keys.ts +0 -68
  236. package/src/cache/types.ts +0 -66
  237. package/src/core.ts +0 -369
  238. package/src/db/migrator/index.ts +0 -80
  239. package/src/db/registry/consent-policy.test.ts +0 -451
  240. package/src/db/registry/consent-policy.ts +0 -82
  241. package/src/db/registry/consent-purpose.test.ts +0 -428
  242. package/src/db/registry/consent-purpose.ts +0 -61
  243. package/src/db/registry/domain.test.ts +0 -445
  244. package/src/db/registry/domain.ts +0 -91
  245. package/src/db/registry/index.ts +0 -14
  246. package/src/db/registry/subject.test.ts +0 -371
  247. package/src/db/registry/subject.ts +0 -126
  248. package/src/db/registry/types.ts +0 -10
  249. package/src/db/registry/utils/generate-id.test.ts +0 -216
  250. package/src/db/registry/utils/generate-id.ts +0 -133
  251. package/src/db/registry/utils.ts +0 -133
  252. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  253. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  254. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  255. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  256. package/src/db/schema/1.0.0/consent.ts +0 -20
  257. package/src/db/schema/1.0.0/domain.ts +0 -12
  258. package/src/db/schema/1.0.0/index.ts +0 -48
  259. package/src/db/schema/1.0.0/subject.ts +0 -11
  260. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  261. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  262. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  263. package/src/db/schema/2.0.0/consent.ts +0 -28
  264. package/src/db/schema/2.0.0/domain.ts +0 -12
  265. package/src/db/schema/2.0.0/index.ts +0 -47
  266. package/src/db/schema/2.0.0/subject.ts +0 -13
  267. package/src/db/schema/index.ts +0 -15
  268. package/src/db/tenant-scope.test.ts +0 -747
  269. package/src/db/tenant-scope.ts +0 -103
  270. package/src/define-config.ts +0 -19
  271. package/src/handlers/consent/check.handler.ts +0 -126
  272. package/src/handlers/init/geo.test.ts +0 -317
  273. package/src/handlers/init/geo.ts +0 -195
  274. package/src/handlers/init/index.test.ts +0 -205
  275. package/src/handlers/init/index.ts +0 -114
  276. package/src/handlers/init/translations.test.ts +0 -121
  277. package/src/handlers/init/translations.ts +0 -69
  278. package/src/handlers/status/status.handler.test.ts +0 -155
  279. package/src/handlers/status/status.handler.ts +0 -51
  280. package/src/handlers/subject/get.handler.ts +0 -92
  281. package/src/handlers/subject/list.handler.ts +0 -92
  282. package/src/handlers/subject/patch.handler.ts +0 -119
  283. package/src/handlers/subject/post.handler.test.ts +0 -294
  284. package/src/handlers/subject/post.handler.ts +0 -268
  285. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  286. package/src/handlers/utils/consent-enrichment.ts +0 -218
  287. package/src/init.test.ts +0 -122
  288. package/src/init.ts +0 -88
  289. package/src/middleware/auth/index.ts +0 -11
  290. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  291. package/src/middleware/auth/validate-api-key.ts +0 -107
  292. package/src/middleware/cors/cors.test.ts +0 -135
  293. package/src/middleware/cors/cors.ts +0 -186
  294. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  295. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  296. package/src/middleware/cors/process-cors.ts +0 -91
  297. package/src/middleware/openapi/config.ts +0 -29
  298. package/src/middleware/openapi/handlers.ts +0 -34
  299. package/src/middleware/process-ip/index.test.ts +0 -193
  300. package/src/middleware/process-ip/index.ts +0 -199
  301. package/src/router.ts +0 -15
  302. package/src/routes/consent.ts +0 -52
  303. package/src/routes/init.ts +0 -105
  304. package/src/routes/status.ts +0 -46
  305. package/src/routes/subject.ts +0 -152
  306. package/src/types/api.ts +0 -48
  307. package/src/types/index.ts +0 -391
  308. package/src/utils/create-telemetry-options.test.ts +0 -286
  309. package/src/utils/create-telemetry-options.ts +0 -229
  310. package/src/utils/env.ts +0 -84
  311. package/src/utils/extract-error-message.ts +0 -21
  312. package/src/utils/instrumentation.test.ts +0 -183
  313. package/src/utils/instrumentation.ts +0 -194
  314. package/src/utils/logger.ts +0 -41
  315. package/src/utils/metrics.test.ts +0 -311
  316. package/src/utils/metrics.ts +0 -402
  317. package/src/utils/telemetry-pii.test.ts +0 -323
  318. package/src/version.ts +0 -2
  319. package/tsconfig.json +0 -11
  320. package/vitest.config.ts +0 -28
  321. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  322. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  323. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  324. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  325. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  326. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  327. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
@@ -1,195 +0,0 @@
1
- import type { C15TOptions } from '~/types';
2
- import type { JurisdictionCode } from '~/types/api';
3
-
4
- /**
5
- * Normalizes a header value to a string or null.
6
- */
7
- function normalizeHeader(
8
- value: string | string[] | null | undefined
9
- ): string | null {
10
- if (!value) {
11
- return null;
12
- }
13
- return Array.isArray(value) ? (value[0] ?? null) : value;
14
- }
15
-
16
- /**
17
- * Gets geo-related headers from the request.
18
- */
19
- function getGeoHeaders(headers: Headers) {
20
- const countryCode =
21
- normalizeHeader(headers.get('x-c15t-country')) ??
22
- normalizeHeader(headers.get('cf-ipcountry')) ??
23
- normalizeHeader(headers.get('x-vercel-ip-country')) ??
24
- normalizeHeader(headers.get('x-amz-cf-ipcountry')) ??
25
- normalizeHeader(headers.get('x-country-code'));
26
-
27
- const regionCode =
28
- normalizeHeader(headers.get('x-c15t-region')) ??
29
- normalizeHeader(headers.get('x-vercel-ip-country-region')) ??
30
- normalizeHeader(headers.get('x-region-code'));
31
-
32
- return { countryCode, regionCode };
33
- }
34
-
35
- /**
36
- * Determines the applicable jurisdiction based on country and region codes.
37
- *
38
- * @remarks
39
- * - EU/EEA/UK map to GDPR-style regimes.
40
- * - Specific countries map to their local laws (CH, BR, CA, AU, JP, KR).
41
- * - CCPA is applied for certain US regions (e.g. California).
42
- */
43
- export function checkJurisdiction(
44
- countryCode: string | null,
45
- regionCode?: string | null
46
- ): JurisdictionCode {
47
- // Country/region sets for different jurisdictions
48
- const jurisdictions = {
49
- EU: new Set([
50
- 'AT',
51
- 'BE',
52
- 'BG',
53
- 'HR',
54
- 'CY',
55
- 'CZ',
56
- 'DK',
57
- 'EE',
58
- 'FI',
59
- 'FR',
60
- 'DE',
61
- 'GR',
62
- 'HU',
63
- 'IE',
64
- 'IT',
65
- 'LV',
66
- 'LT',
67
- 'LU',
68
- 'MT',
69
- 'NL',
70
- 'PL',
71
- 'PT',
72
- 'RO',
73
- 'SK',
74
- 'SI',
75
- 'ES',
76
- 'SE',
77
- ]),
78
- EEA: new Set(['IS', 'NO', 'LI']),
79
- UK: new Set(['GB']),
80
- CH: new Set(['CH']),
81
- BR: new Set(['BR']),
82
- CA: new Set(['CA']),
83
- AU: new Set(['AU']),
84
- JP: new Set(['JP']),
85
- KR: new Set(['KR']),
86
- US_CCPA_REGIONS: new Set([
87
- // California (CCPA/CPRA)
88
- 'CA',
89
- ]),
90
- CA_QC_REGIONS: new Set([
91
- // Quebec (Law 25)
92
- 'QC',
93
- ]),
94
- };
95
-
96
- // Default to no jurisdiction
97
- let jurisdiction: JurisdictionCode = 'NONE';
98
-
99
- // Check country/region codes against jurisdiction sets
100
- if (countryCode) {
101
- // Normalize for case-insensitive comparison
102
- const normalizedCountryCode = countryCode.toUpperCase();
103
- // Normalize region code: handle dash-separated formats like "CA-QC" or "US-CA"
104
- // by extracting the last segment as the subdivision code
105
- const normalizedRegionCode =
106
- regionCode && typeof regionCode === 'string'
107
- ? (regionCode.includes('-')
108
- ? regionCode.split('-').pop()!
109
- : regionCode
110
- ).toUpperCase()
111
- : null;
112
-
113
- // CCPA-style rules: currently applied for certain US regions only
114
- if (
115
- normalizedCountryCode === 'US' &&
116
- normalizedRegionCode &&
117
- jurisdictions.US_CCPA_REGIONS.has(normalizedRegionCode)
118
- ) {
119
- return 'CCPA';
120
- }
121
-
122
- // Quebec (Law 25): opt-in consent required
123
- if (
124
- normalizedCountryCode === 'CA' &&
125
- normalizedRegionCode &&
126
- jurisdictions.CA_QC_REGIONS.has(normalizedRegionCode)
127
- ) {
128
- return 'QC_LAW25';
129
- }
130
-
131
- const jurisdictionMap = [
132
- {
133
- sets: [jurisdictions.UK],
134
- code: 'UK_GDPR' as const,
135
- },
136
- {
137
- sets: [jurisdictions.EU, jurisdictions.EEA],
138
- code: 'GDPR' as const,
139
- },
140
- { sets: [jurisdictions.CH], code: 'CH' as const },
141
- { sets: [jurisdictions.BR], code: 'BR' as const },
142
- { sets: [jurisdictions.CA], code: 'PIPEDA' as const },
143
- { sets: [jurisdictions.AU], code: 'AU' as const },
144
- { sets: [jurisdictions.JP], code: 'APPI' as const },
145
- { sets: [jurisdictions.KR], code: 'PIPA' as const },
146
- ];
147
-
148
- // Find matching jurisdiction
149
- for (const { sets, code } of jurisdictionMap) {
150
- if (sets.some((set) => set.has(normalizedCountryCode))) {
151
- jurisdiction = code;
152
- break;
153
- }
154
- }
155
- }
156
-
157
- return jurisdiction;
158
- }
159
-
160
- /**
161
- * Gets the location from the request headers.
162
- *
163
- * @param request - The incoming request
164
- * @param options - The C15T options
165
- * @returns The location object with countryCode and regionCode
166
- */
167
- export async function getLocation(
168
- request: Request,
169
- options: C15TOptions
170
- ): Promise<{ countryCode: string | null; regionCode: string | null }> {
171
- if (options.disableGeoLocation) {
172
- return { countryCode: null, regionCode: null };
173
- }
174
-
175
- const { countryCode, regionCode } = getGeoHeaders(request.headers);
176
- return { countryCode, regionCode };
177
- }
178
-
179
- /**
180
- * Gets the jurisdiction based on location and options.
181
- *
182
- * @param location - The location object
183
- * @param options - The C15T options
184
- * @returns The jurisdiction code
185
- */
186
- export function getJurisdiction(
187
- location: { countryCode: string | null; regionCode: string | null },
188
- options: C15TOptions
189
- ): JurisdictionCode {
190
- if (options.disableGeoLocation) {
191
- return 'GDPR';
192
- }
193
-
194
- return checkJurisdiction(location.countryCode, location.regionCode);
195
- }
@@ -1,205 +0,0 @@
1
- import { baseTranslations } from '@c15t/translations/all';
2
- import { describe, expect, it } from 'vitest';
3
- import type { C15TOptions } from '~/types';
4
- import { checkJurisdiction, getJurisdiction, getLocation } from './geo';
5
- import {
6
- buildResponse,
7
- getHeaders,
8
- getTranslationsData,
9
- parseAcceptLanguage,
10
- } from './index';
11
-
12
- describe('Init Handler Utilities', () => {
13
- describe('getHeaders', () => {
14
- it('extracts country code from cf-ipcountry header', () => {
15
- const headers = new Headers({ 'cf-ipcountry': 'DE' });
16
- const result = getHeaders(headers);
17
- expect(result.countryCode).toBe('DE');
18
- });
19
-
20
- it('falls back to alternative country code headers', () => {
21
- const headers = new Headers({ 'x-vercel-ip-country': 'FR' });
22
- const result = getHeaders(headers);
23
- expect(result.countryCode).toBe('FR');
24
- });
25
-
26
- it('prioritizes cf-ipcountry over other headers', () => {
27
- const headers = new Headers({
28
- 'cf-ipcountry': 'DE',
29
- 'x-vercel-ip-country': 'FR',
30
- });
31
- const result = getHeaders(headers);
32
- expect(result.countryCode).toBe('DE');
33
- });
34
-
35
- it('extracts region code from headers', () => {
36
- const headers = new Headers({
37
- 'cf-ipcountry': 'US',
38
- 'x-vercel-ip-country-region': 'CA',
39
- });
40
- const result = getHeaders(headers);
41
- expect(result.countryCode).toBe('US');
42
- expect(result.regionCode).toBe('CA');
43
- });
44
-
45
- it('handles missing headers gracefully', () => {
46
- const headers = new Headers({});
47
- const result = getHeaders(headers);
48
- expect(result.countryCode).toBeNull();
49
- expect(result.regionCode).toBeNull();
50
- });
51
-
52
- it('handles undefined headers', () => {
53
- const result = getHeaders(undefined);
54
- expect(result.countryCode).toBeNull();
55
- expect(result.regionCode).toBeNull();
56
- expect(result.acceptLanguage).toBeNull();
57
- });
58
- });
59
-
60
- describe('checkJurisdiction', () => {
61
- it('returns GDPR for EU countries', () => {
62
- expect(checkJurisdiction('DE', null)).toBe('GDPR');
63
- expect(checkJurisdiction('FR', null)).toBe('GDPR');
64
- expect(checkJurisdiction('IT', null)).toBe('GDPR');
65
- });
66
-
67
- it('returns UK_GDPR for UK', () => {
68
- expect(checkJurisdiction('GB', null)).toBe('UK_GDPR');
69
- });
70
-
71
- it('returns CCPA for California', () => {
72
- expect(checkJurisdiction('US', 'CA')).toBe('CCPA');
73
- });
74
-
75
- it('returns NONE for non-regulated regions', () => {
76
- expect(checkJurisdiction('US', null)).toBe('NONE');
77
- expect(checkJurisdiction('US', 'TX')).toBe('NONE');
78
- });
79
-
80
- it('returns PIPEDA for Canada', () => {
81
- expect(checkJurisdiction('CA', null)).toBe('PIPEDA');
82
- });
83
-
84
- it('returns CH for Switzerland', () => {
85
- expect(checkJurisdiction('CH', null)).toBe('CH');
86
- });
87
-
88
- it('handles null country code', () => {
89
- expect(checkJurisdiction(null, null)).toBe('NONE');
90
- });
91
- });
92
-
93
- describe('parseAcceptLanguage', () => {
94
- it('returns en for null input', () => {
95
- expect(parseAcceptLanguage(null)).toBe('en');
96
- });
97
-
98
- it('parses simple language code', () => {
99
- expect(parseAcceptLanguage('de')).toBe('de');
100
- });
101
-
102
- it('parses language with region', () => {
103
- expect(parseAcceptLanguage('de-DE')).toBe('de');
104
- });
105
-
106
- it('parses Accept-Language with quality factors', () => {
107
- expect(parseAcceptLanguage('de-DE,de;q=0.9,en;q=0.8')).toBe('de');
108
- });
109
- });
110
-
111
- describe('getTranslationsData', () => {
112
- it('returns en translations for null Accept-Language', () => {
113
- const result = getTranslationsData(null);
114
- expect(result.language).toBe('en');
115
- expect(result.translations.cookieBanner.title).toBe(
116
- baseTranslations.en.cookieBanner.title
117
- );
118
- });
119
-
120
- it('returns de translations for de-DE', () => {
121
- const result = getTranslationsData('de-DE,de;q=0.9,en;q=0.8');
122
- expect(result.language).toBe('de');
123
- expect(result.translations.cookieBanner.title).toBe(
124
- baseTranslations.de.cookieBanner.title
125
- );
126
- });
127
-
128
- it('merges custom translations', () => {
129
- const customTranslations = {
130
- en: { cookieBanner: { title: 'Custom Title' } },
131
- };
132
- const result = getTranslationsData('en-US', customTranslations);
133
- expect(result.translations.cookieBanner.title).toBe('Custom Title');
134
- });
135
- });
136
-
137
- describe('buildResponse', () => {
138
- it('returns properly structured response', () => {
139
- const result = buildResponse({
140
- jurisdiction: 'GDPR',
141
- location: { countryCode: 'DE', regionCode: null },
142
- acceptLanguage: 'en',
143
- customTranslations: undefined,
144
- branding: 'c15t',
145
- });
146
-
147
- expect(result).toHaveProperty('jurisdiction');
148
- expect(result).toHaveProperty('location');
149
- expect(result).toHaveProperty('translations');
150
- expect(result).toHaveProperty('branding');
151
- expect(result.jurisdiction).toBe('GDPR');
152
- expect(result.branding).toBe('c15t');
153
- });
154
- });
155
-
156
- describe('getLocation', () => {
157
- it('returns null location when geo is disabled', async () => {
158
- const request = new Request('http://localhost', {
159
- headers: { 'cf-ipcountry': 'DE' },
160
- });
161
- const options: C15TOptions = {
162
- trustedOrigins: [],
163
- adapter: {} as C15TOptions['adapter'],
164
- disableGeoLocation: true,
165
- };
166
-
167
- const result = await getLocation(request, options);
168
- expect(result.countryCode).toBeNull();
169
- expect(result.regionCode).toBeNull();
170
- });
171
- });
172
-
173
- describe('getJurisdiction', () => {
174
- it('returns GDPR when geo is disabled', () => {
175
- const options: C15TOptions = {
176
- trustedOrigins: [],
177
- adapter: {} as C15TOptions['adapter'],
178
- disableGeoLocation: true,
179
- };
180
-
181
- const result = getJurisdiction(
182
- { countryCode: 'US', regionCode: null },
183
- options
184
- );
185
- expect(result).toBe('GDPR');
186
- });
187
-
188
- it('returns appropriate jurisdiction based on location', () => {
189
- const options: C15TOptions = {
190
- trustedOrigins: [],
191
- adapter: {} as C15TOptions['adapter'],
192
- };
193
-
194
- expect(
195
- getJurisdiction({ countryCode: 'DE', regionCode: null }, options)
196
- ).toBe('GDPR');
197
- expect(
198
- getJurisdiction({ countryCode: 'US', regionCode: 'CA' }, options)
199
- ).toBe('CCPA');
200
- expect(
201
- getJurisdiction({ countryCode: 'GB', regionCode: null }, options)
202
- ).toBe('UK_GDPR');
203
- });
204
- });
205
- });
@@ -1,114 +0,0 @@
1
- import type { GlobalVendorList, NonIABVendor } from '@c15t/schema/types';
2
- import type { Translations } from '@c15t/translations';
3
- import type { Branding } from '~/types';
4
- import type { JurisdictionCode } from '~/types/api';
5
- import { getTranslationsData } from './translations';
6
-
7
- /**
8
- * Gets the headers from the context.
9
- *
10
- * @param headers - The headers to get the headers from.
11
- * @returns The headers or null if the headers are not present.
12
- */
13
- export function getHeaders(headers: Headers | undefined) {
14
- if (!headers) {
15
- return {
16
- countryCode: null,
17
- regionCode: null,
18
- acceptLanguage: null,
19
- };
20
- }
21
-
22
- // Add this conversion to ensure headers are always string or null
23
- const normalizeHeader = (
24
- value: string | string[] | null | undefined
25
- ): string | null => {
26
- if (!value) {
27
- return null;
28
- }
29
-
30
- return Array.isArray(value) ? (value[0] ?? null) : value;
31
- };
32
-
33
- const countryCode =
34
- normalizeHeader(headers.get('x-c15t-country')) ??
35
- normalizeHeader(headers.get('cf-ipcountry')) ??
36
- normalizeHeader(headers.get('x-vercel-ip-country')) ??
37
- normalizeHeader(headers.get('x-amz-cf-ipcountry')) ??
38
- normalizeHeader(headers.get('x-country-code'));
39
-
40
- const regionCode =
41
- normalizeHeader(headers.get('x-c15t-region')) ??
42
- normalizeHeader(headers.get('x-vercel-ip-country-region')) ??
43
- normalizeHeader(headers.get('x-region-code'));
44
-
45
- // Get preferred language from Accept-Language header
46
- const acceptLanguage = normalizeHeader(headers.get('accept-language'));
47
-
48
- return {
49
- countryCode,
50
- regionCode,
51
- acceptLanguage,
52
- };
53
- }
54
-
55
- /**
56
- * Parse Accept-Language header to get the primary language code.
57
- *
58
- * @param acceptLanguage - The Accept-Language header value
59
- * @returns The primary language code (e.g., "en", "de"), defaults to "en"
60
- */
61
- export function parseAcceptLanguage(acceptLanguage: string | null): string {
62
- if (!acceptLanguage) {
63
- return 'en';
64
- }
65
-
66
- // Parse "en-US,en;q=0.9,de;q=0.8" format
67
- // Split by comma, take the first part, then extract language code
68
- const firstLanguage = acceptLanguage.split(',')[0];
69
- if (!firstLanguage) {
70
- return 'en';
71
- }
72
-
73
- // Remove quality factor if present (e.g., "en;q=0.9" -> "en")
74
- const languageWithRegion = firstLanguage.split(';')[0]?.trim();
75
- if (!languageWithRegion) {
76
- return 'en';
77
- }
78
-
79
- // Extract language code without region (e.g., "en-US" -> "en")
80
- const languageCode = languageWithRegion.split('-')[0]?.toLowerCase();
81
-
82
- return languageCode ?? 'en';
83
- }
84
-
85
- export function buildResponse({
86
- jurisdiction,
87
- location,
88
- acceptLanguage,
89
- customTranslations,
90
- branding = 'c15t',
91
- gvl,
92
- customVendors,
93
- }: {
94
- jurisdiction: JurisdictionCode;
95
- location: { countryCode: string | null; regionCode: string | null };
96
- acceptLanguage: string | null;
97
- customTranslations: Record<string, Partial<Translations>> | undefined;
98
- branding?: Branding;
99
- gvl?: GlobalVendorList;
100
- customVendors?: NonIABVendor[];
101
- }) {
102
- return {
103
- jurisdiction,
104
- location,
105
- translations: getTranslationsData(acceptLanguage, customTranslations),
106
- branding: branding,
107
- gvl: gvl ?? null,
108
- customVendors: customVendors ?? undefined,
109
- };
110
- }
111
-
112
- export { checkJurisdiction, getJurisdiction, getLocation } from './geo';
113
- // Re-export translations functions
114
- export { getTranslations, getTranslationsData } from './translations';
@@ -1,121 +0,0 @@
1
- import { baseTranslations } from '@c15t/translations/all';
2
- import { describe, expect, it } from 'vitest';
3
- import { getTranslationsData } from './translations';
4
-
5
- describe('showBanner > getTranslationsData', () => {
6
- it("should return 'en' translations when Accept-Language is null", () => {
7
- const { translations, language } = getTranslationsData(null);
8
- expect(language).toBe('en');
9
- expect(translations.cookieBanner.title).toBe(
10
- baseTranslations.en.cookieBanner.title
11
- );
12
- });
13
-
14
- it("should return 'en' translations for unsupported language", () => {
15
- const { translations, language } = getTranslationsData('xx-XX,en;q=0.9');
16
- expect(language).toBe('en');
17
- expect(translations.cookieBanner.title).toBe(
18
- baseTranslations.en.cookieBanner.title
19
- );
20
- });
21
-
22
- it("should return 'de' translations for 'de-DE'", () => {
23
- const { translations, language } = getTranslationsData(
24
- 'de-DE,de;q=0.9,en;q=0.8'
25
- );
26
- expect(language).toBe('de');
27
- expect(translations.cookieBanner.title).toBe(
28
- baseTranslations.de.cookieBanner.title
29
- );
30
- });
31
-
32
- it('should merge custom translations for the preferred language', () => {
33
- const customTranslations = {
34
- en: {
35
- cookieBanner: {
36
- title: 'My Custom Cookie Title',
37
- },
38
- },
39
- };
40
- const { translations, language } = getTranslationsData(
41
- 'en-US,en;q=0.9',
42
- customTranslations
43
- );
44
- expect(language).toBe('en');
45
- expect(translations.cookieBanner.title).toBe('My Custom Cookie Title');
46
- // Check if other properties from base translations are still there
47
- expect(translations.cookieBanner.description).toBeDefined();
48
- });
49
-
50
- it('should not merge custom translations for a different language', () => {
51
- const customTranslations = {
52
- de: {
53
- cookieBanner: {
54
- title: 'Meine benutzerdefinierte Cookie-Überschrift',
55
- },
56
- },
57
- };
58
- const { translations, language } = getTranslationsData(
59
- 'en-US,en;q=0.9',
60
- customTranslations
61
- );
62
- expect(language).toBe('en');
63
- expect(translations.cookieBanner.title).toBe(
64
- baseTranslations.en.cookieBanner.title
65
- );
66
- });
67
-
68
- it('should handle partially provided custom translations', () => {
69
- const customTranslations = {
70
- en: {
71
- cookieBanner: {
72
- // Title is NOT provided, description is.
73
- description: 'My custom description.',
74
- },
75
- },
76
- };
77
- const { translations, language } = getTranslationsData(
78
- 'en-US,en;q=0.9',
79
- customTranslations
80
- );
81
- expect(language).toBe('en');
82
- // Title should come from base
83
- expect(translations.cookieBanner.title).toBe(
84
- baseTranslations.en.cookieBanner.title
85
- );
86
- // Description should be from custom
87
- expect(translations.cookieBanner.description).toBe(
88
- 'My custom description.'
89
- );
90
- });
91
-
92
- it('should return base translations if custom translations are empty for the language', () => {
93
- const customTranslations = {
94
- de: {},
95
- };
96
- const { translations, language } = getTranslationsData(
97
- 'de-DE,de;q=0.9',
98
- customTranslations
99
- );
100
- expect(language).toBe('de');
101
- expect(translations.cookieBanner.title).toBe(
102
- baseTranslations.de.cookieBanner.title
103
- );
104
- });
105
-
106
- it('should return custom translations for unsupported base language', () => {
107
- const { translations, language } = getTranslationsData('xx-XX,en;q=0.9', {
108
- xx: {
109
- cookieBanner: {
110
- title: 'XX Title',
111
- },
112
- },
113
- });
114
-
115
- expect(language).toBe('xx');
116
- expect(translations.cookieBanner.title).toBe('XX Title');
117
- expect(translations.cookieBanner.description).toBe(
118
- baseTranslations.en.cookieBanner.description
119
- );
120
- });
121
- });
@@ -1,69 +0,0 @@
1
- import {
2
- type CompleteTranslations,
3
- deepMergeTranslations,
4
- selectLanguage,
5
- type Translations,
6
- } from '@c15t/translations';
7
- import { baseTranslations } from '@c15t/translations/all';
8
-
9
- type SupportedBaseLanguage = keyof typeof baseTranslations;
10
-
11
- function isSupportedBaseLanguage(lang: string): lang is SupportedBaseLanguage {
12
- return lang in baseTranslations;
13
- }
14
-
15
- /**
16
- * Gets the translations data for a given language, merging custom translations if provided.
17
- *
18
- * @param acceptLanguage - The `Accept-Language` header from the request.
19
- * @param customTranslations - An object containing custom translations to merge with the base translations.
20
- * @returns An object containing the final translations and the determined language.
21
- */
22
- export function getTranslationsData(
23
- acceptLanguage: string | null,
24
- customTranslations?: Record<string, Partial<Translations>>
25
- ) {
26
- const supportedDefaultLanguages = Object.keys(baseTranslations);
27
- const supportedCustomLanguages = Object.keys(customTranslations || {});
28
-
29
- const supportedLanguages = [
30
- ...supportedDefaultLanguages,
31
- ...supportedCustomLanguages,
32
- ];
33
-
34
- const preferredLanguage = selectLanguage(supportedLanguages, {
35
- header: acceptLanguage,
36
- fallback: 'en',
37
- });
38
-
39
- const base = isSupportedBaseLanguage(preferredLanguage)
40
- ? baseTranslations[preferredLanguage]
41
- : baseTranslations.en;
42
-
43
- const custom = supportedCustomLanguages.includes(preferredLanguage)
44
- ? customTranslations?.[preferredLanguage]
45
- : {};
46
-
47
- const translations = custom ? deepMergeTranslations(base, custom) : base;
48
-
49
- return {
50
- translations: translations as CompleteTranslations,
51
- language: preferredLanguage,
52
- };
53
- }
54
-
55
- /**
56
- * Gets the translations for a given language from options.
57
- *
58
- * @param acceptLanguage - The `Accept-Language` header from the request.
59
- * @param options - The C15T options containing custom translations.
60
- * @returns An object containing the final translations and the determined language.
61
- */
62
- export async function getTranslations(
63
- acceptLanguage: string,
64
- options: {
65
- customTranslations?: Record<string, Partial<Translations>>;
66
- }
67
- ) {
68
- return getTranslationsData(acceptLanguage, options.customTranslations);
69
- }