@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
@@ -1,402 +0,0 @@
1
- import type { Counter, Histogram, Meter } from '@opentelemetry/api';
2
- import type { C15TOptions } from '../types';
3
- import { getMeter, isTelemetryEnabled } from './create-telemetry-options';
4
-
5
- /**
6
- * Geographic attributes for consent metrics
7
- */
8
- export interface GeoAttributes {
9
- /** Legal framework (GDPR, CCPA, LGPD, etc.) */
10
- jurisdiction?: string;
11
- /** ISO country code (US, DE, FR, etc.) */
12
- country?: string;
13
- /** State/province code (CA, BY, etc.) */
14
- region?: string;
15
- /** Index signature for additional attributes */
16
- [key: string]: string | number | boolean | undefined;
17
- }
18
-
19
- /**
20
- * Consent metric attributes
21
- */
22
- export interface ConsentMetricAttributes extends GeoAttributes {
23
- /** Consent type (cookie_banner, privacy_policy, etc.) */
24
- type: string;
25
- /** Consent status (accepted, rejected, partial) */
26
- status?: string;
27
- }
28
-
29
- /**
30
- * HTTP metric attributes
31
- */
32
- export interface HttpMetricAttributes {
33
- /** HTTP method */
34
- method: string;
35
- /** Route pattern (e.g., /subjects/:id) */
36
- route: string;
37
- /** HTTP status code */
38
- status: number;
39
- /** Error type if applicable */
40
- errorType?: string;
41
- /** Index signature for additional attributes */
42
- [key: string]: string | number | boolean | undefined;
43
- }
44
-
45
- /**
46
- * Database metric attributes
47
- */
48
- export interface DbMetricAttributes {
49
- /** Operation type (find, create, update, delete) */
50
- operation: string;
51
- /** Entity type (consent, subject, audit_log, etc.) */
52
- entity: string;
53
- /** Error type if applicable */
54
- errorType?: string;
55
- /** Index signature for additional attributes */
56
- [key: string]: string | number | boolean | undefined;
57
- }
58
-
59
- /**
60
- * Cache metric attributes
61
- */
62
- export interface CacheMetricAttributes {
63
- /** Cache layer (bundled, memory, external) */
64
- layer: 'bundled' | 'memory' | 'external';
65
- /** Operation type (get, set, delete) */
66
- operation?: string;
67
- /** Index signature for additional attributes */
68
- [key: string]: string | number | boolean | undefined;
69
- }
70
-
71
- /**
72
- * GVL fetch metric attributes
73
- */
74
- export interface GvlMetricAttributes {
75
- /** Language code */
76
- language: string;
77
- /** Source of the data (cache, fetch) */
78
- source?: 'cache' | 'fetch';
79
- /** HTTP status code if fetched */
80
- status?: number;
81
- /** Error type if applicable */
82
- errorType?: string;
83
- /** Index signature for additional attributes */
84
- [key: string]: string | number | boolean | undefined;
85
- }
86
-
87
- /**
88
- * C15T Metrics container
89
- * Holds all metric instruments for the c15t backend
90
- */
91
- export interface C15TMetrics {
92
- // Business metrics
93
- readonly consentCreated: Counter;
94
- readonly consentAccepted: Counter;
95
- readonly consentRejected: Counter;
96
- readonly subjectCreated: Counter;
97
- readonly subjectLinked: Counter;
98
- readonly consentCheckCount: Counter;
99
- readonly initCount: Counter;
100
-
101
- // HTTP metrics
102
- readonly httpRequestDuration: Histogram;
103
- readonly httpRequestCount: Counter;
104
- readonly httpErrorCount: Counter;
105
-
106
- // Database metrics
107
- readonly dbQueryDuration: Histogram;
108
- readonly dbQueryCount: Counter;
109
- readonly dbErrorCount: Counter;
110
-
111
- // Cache metrics
112
- readonly cacheHit: Counter;
113
- readonly cacheMiss: Counter;
114
- readonly cacheLatency: Histogram;
115
-
116
- // GVL metrics
117
- readonly gvlFetchDuration: Histogram;
118
- readonly gvlFetchCount: Counter;
119
- readonly gvlFetchError: Counter;
120
-
121
- // Helper methods
122
- recordConsentCreated(attributes: ConsentMetricAttributes): void;
123
- recordConsentAccepted(
124
- attributes: Omit<ConsentMetricAttributes, 'status'>
125
- ): void;
126
- recordConsentRejected(
127
- attributes: Omit<ConsentMetricAttributes, 'status'>
128
- ): void;
129
- recordSubjectCreated(attributes: GeoAttributes & { domain?: string }): void;
130
- recordSubjectLinked(identityProvider?: string): void;
131
- recordConsentCheck(type: string, found: boolean): void;
132
- recordInit(attributes: GeoAttributes): void;
133
- recordHttpRequest(attributes: HttpMetricAttributes, durationMs: number): void;
134
- recordDbQuery(attributes: DbMetricAttributes, durationMs: number): void;
135
- recordDbError(attributes: DbMetricAttributes): void;
136
- recordCacheHit(layer: CacheMetricAttributes['layer']): void;
137
- recordCacheMiss(layer: CacheMetricAttributes['layer']): void;
138
- recordCacheLatency(
139
- attributes: CacheMetricAttributes,
140
- durationMs: number
141
- ): void;
142
- recordGvlFetch(attributes: GvlMetricAttributes, durationMs: number): void;
143
- recordGvlError(attributes: GvlMetricAttributes): void;
144
- }
145
-
146
- /**
147
- * Sanitize attributes - remove undefined values and convert to strings
148
- */
149
- function sanitizeAttributes<
150
- T extends Record<string, string | number | boolean | undefined>,
151
- >(attrs: T): Record<string, string | number | boolean> {
152
- return Object.fromEntries(
153
- Object.entries(attrs).filter(([_, v]) => v !== undefined && v !== null) as [
154
- string,
155
- string | number | boolean,
156
- ][]
157
- );
158
- }
159
-
160
- /**
161
- * Create a C15TMetrics instance from an OpenTelemetry Meter.
162
- *
163
- * @param meter - OpenTelemetry Meter to create instruments from
164
- * @returns C15TMetrics object with all instruments and helper methods
165
- */
166
- function createMetrics(meter: Meter): C15TMetrics {
167
- // Business metrics - High value for c15t
168
- const consentCreated = meter.createCounter('c15t.consent.created', {
169
- description: 'Number of consent submissions',
170
- unit: '1',
171
- });
172
-
173
- const consentAccepted = meter.createCounter('c15t.consent.accepted', {
174
- description: 'Number of consents accepted',
175
- unit: '1',
176
- });
177
-
178
- const consentRejected = meter.createCounter('c15t.consent.rejected', {
179
- description: 'Number of consents rejected',
180
- unit: '1',
181
- });
182
-
183
- const subjectCreated = meter.createCounter('c15t.subject.created', {
184
- description: 'Number of new subjects created',
185
- unit: '1',
186
- });
187
-
188
- const subjectLinked = meter.createCounter('c15t.subject.linked', {
189
- description: 'Number of subjects linked to external ID',
190
- unit: '1',
191
- });
192
-
193
- const consentCheckCount = meter.createCounter('c15t.consent_check.count', {
194
- description: 'Number of cross-device consent checks',
195
- unit: '1',
196
- });
197
-
198
- const initCount = meter.createCounter('c15t.init.count', {
199
- description: 'Number of init endpoint calls',
200
- unit: '1',
201
- });
202
-
203
- // HTTP metrics
204
- const httpRequestDuration = meter.createHistogram(
205
- 'c15t.http.request.duration',
206
- {
207
- description: 'HTTP request latency',
208
- unit: 'ms',
209
- }
210
- );
211
-
212
- const httpRequestCount = meter.createCounter('c15t.http.request.count', {
213
- description: 'Number of HTTP requests',
214
- unit: '1',
215
- });
216
-
217
- const httpErrorCount = meter.createCounter('c15t.http.error.count', {
218
- description: 'Number of HTTP errors',
219
- unit: '1',
220
- });
221
-
222
- // Database metrics
223
- const dbQueryDuration = meter.createHistogram('c15t.db.query.duration', {
224
- description: 'Database query latency',
225
- unit: 'ms',
226
- });
227
-
228
- const dbQueryCount = meter.createCounter('c15t.db.query.count', {
229
- description: 'Number of database queries',
230
- unit: '1',
231
- });
232
-
233
- const dbErrorCount = meter.createCounter('c15t.db.error.count', {
234
- description: 'Number of database errors',
235
- unit: '1',
236
- });
237
-
238
- // Cache metrics
239
- const cacheHit = meter.createCounter('c15t.cache.hit', {
240
- description: 'Number of cache hits',
241
- unit: '1',
242
- });
243
-
244
- const cacheMiss = meter.createCounter('c15t.cache.miss', {
245
- description: 'Number of cache misses',
246
- unit: '1',
247
- });
248
-
249
- const cacheLatency = meter.createHistogram('c15t.cache.latency', {
250
- description: 'Cache operation latency',
251
- unit: 'ms',
252
- });
253
-
254
- // GVL metrics
255
- const gvlFetchDuration = meter.createHistogram('c15t.gvl.fetch.duration', {
256
- description: 'GVL fetch latency',
257
- unit: 'ms',
258
- });
259
-
260
- const gvlFetchCount = meter.createCounter('c15t.gvl.fetch.count', {
261
- description: 'Number of GVL fetches',
262
- unit: '1',
263
- });
264
-
265
- const gvlFetchError = meter.createCounter('c15t.gvl.fetch.error', {
266
- description: 'Number of GVL fetch errors',
267
- unit: '1',
268
- });
269
-
270
- return {
271
- // Instruments
272
- consentCreated,
273
- consentAccepted,
274
- consentRejected,
275
- subjectCreated,
276
- subjectLinked,
277
- consentCheckCount,
278
- initCount,
279
- httpRequestDuration,
280
- httpRequestCount,
281
- httpErrorCount,
282
- dbQueryDuration,
283
- dbQueryCount,
284
- dbErrorCount,
285
- cacheHit,
286
- cacheMiss,
287
- cacheLatency,
288
- gvlFetchDuration,
289
- gvlFetchCount,
290
- gvlFetchError,
291
-
292
- // Helper methods
293
- recordConsentCreated(attributes: ConsentMetricAttributes): void {
294
- consentCreated.add(1, sanitizeAttributes(attributes));
295
- },
296
-
297
- recordConsentAccepted(
298
- attributes: Omit<ConsentMetricAttributes, 'status'>
299
- ): void {
300
- consentAccepted.add(1, sanitizeAttributes(attributes));
301
- },
302
-
303
- recordConsentRejected(
304
- attributes: Omit<ConsentMetricAttributes, 'status'>
305
- ): void {
306
- consentRejected.add(1, sanitizeAttributes(attributes));
307
- },
308
-
309
- recordSubjectCreated(
310
- attributes: GeoAttributes & { domain?: string }
311
- ): void {
312
- subjectCreated.add(1, sanitizeAttributes(attributes));
313
- },
314
-
315
- recordSubjectLinked(identityProvider?: string): void {
316
- subjectLinked.add(1, {
317
- identityProvider: identityProvider || 'unknown',
318
- });
319
- },
320
-
321
- recordConsentCheck(type: string, found: boolean): void {
322
- consentCheckCount.add(1, { type, found: String(found) });
323
- },
324
-
325
- recordInit(attributes: GeoAttributes): void {
326
- initCount.add(1, sanitizeAttributes(attributes));
327
- },
328
-
329
- recordHttpRequest(
330
- attributes: HttpMetricAttributes,
331
- durationMs: number
332
- ): void {
333
- const attrs = sanitizeAttributes(attributes);
334
- httpRequestCount.add(1, attrs);
335
- httpRequestDuration.record(durationMs, attrs);
336
-
337
- if (attributes.status >= 400) {
338
- httpErrorCount.add(1, attrs);
339
- }
340
- },
341
-
342
- recordDbQuery(attributes: DbMetricAttributes, durationMs: number): void {
343
- const attrs = sanitizeAttributes(attributes);
344
- dbQueryCount.add(1, attrs);
345
- dbQueryDuration.record(durationMs, attrs);
346
- },
347
-
348
- recordDbError(attributes: DbMetricAttributes): void {
349
- dbErrorCount.add(1, sanitizeAttributes(attributes));
350
- },
351
-
352
- recordCacheHit(layer: CacheMetricAttributes['layer']): void {
353
- cacheHit.add(1, { layer });
354
- },
355
-
356
- recordCacheMiss(layer: CacheMetricAttributes['layer']): void {
357
- cacheMiss.add(1, { layer });
358
- },
359
-
360
- recordCacheLatency(
361
- attributes: CacheMetricAttributes,
362
- durationMs: number
363
- ): void {
364
- cacheLatency.record(durationMs, sanitizeAttributes(attributes));
365
- },
366
-
367
- recordGvlFetch(attributes: GvlMetricAttributes, durationMs: number): void {
368
- const attrs = sanitizeAttributes(attributes);
369
- gvlFetchCount.add(1, attrs);
370
- gvlFetchDuration.record(durationMs, attrs);
371
- },
372
-
373
- recordGvlError(attributes: GvlMetricAttributes): void {
374
- gvlFetchError.add(1, sanitizeAttributes(attributes));
375
- },
376
- };
377
- }
378
-
379
- // Lazy-initialized metric instances
380
- let metricsInstance: C15TMetrics | null = null;
381
-
382
- /**
383
- * Get or create the metrics instance for the c15t backend.
384
- * Returns null if telemetry is not enabled.
385
- *
386
- * @param options - C15T options for telemetry configuration
387
- * @returns C15TMetrics instance or null if telemetry is disabled
388
- */
389
- export function getMetrics(options?: C15TOptions): C15TMetrics | null {
390
- if (metricsInstance) return metricsInstance;
391
- if (!isTelemetryEnabled(options)) return null;
392
-
393
- metricsInstance = createMetrics(getMeter(options));
394
- return metricsInstance;
395
- }
396
-
397
- /**
398
- * Reset the metrics instance (useful for testing)
399
- */
400
- export function resetMetrics(): void {
401
- metricsInstance = null;
402
- }
@@ -1,323 +0,0 @@
1
- /**
2
- * E2E test: every span attribute key must be in the allowlist,
3
- * and no attribute value may match PII patterns.
4
- *
5
- * Uses a real BasicTracerProvider + InMemorySpanExporter (no mocks).
6
- */
7
-
8
- import {
9
- BasicTracerProvider,
10
- InMemorySpanExporter,
11
- SimpleSpanProcessor,
12
- } from '@opentelemetry/sdk-trace-base';
13
- import { afterEach, beforeAll, describe, expect, it } from 'vitest';
14
- import type { C15TOptions } from '../types';
15
- import {
16
- createRequestSpan,
17
- createTelemetryOptions,
18
- resetTelemetryConfig,
19
- withRequestSpan,
20
- } from './create-telemetry-options';
21
- import {
22
- withCacheSpan,
23
- withDatabaseSpan,
24
- withExternalSpan,
25
- } from './instrumentation';
26
-
27
- // ── Allowlist ───────────────────────────────────────────────────────────
28
- const ALLOWED_KEYS = new Set([
29
- 'http.method',
30
- 'http.route',
31
- 'http.host',
32
- 'http.path',
33
- 'http.url',
34
- 'error.type',
35
- 'db.system',
36
- 'db.operation',
37
- 'db.entity',
38
- 'cache.operation',
39
- 'cache.layer',
40
- 'service.name',
41
- 'service.version',
42
- 'tenant.id',
43
- ]);
44
-
45
- const BLOCKED_KEYS = new Set(['error.stack', 'error.message']);
46
-
47
- const PII_PATTERNS = [
48
- /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, // IPv4
49
- /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, // email
50
- /at .+\(.+:\d+:\d+\)/, // stack trace line
51
- /\?[^=]+=/, // query string
52
- ];
53
-
54
- // ── Provider setup ──────────────────────────────────────────────────────
55
- const exporter = new InMemorySpanExporter();
56
- const provider = new BasicTracerProvider();
57
- provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
58
- provider.register();
59
-
60
- const tracer = provider.getTracer('pii-test');
61
-
62
- function telemetryOptions(): C15TOptions {
63
- return {
64
- trustedOrigins: [],
65
- adapter: {} as C15TOptions['adapter'],
66
- telemetry: {
67
- enabled: true,
68
- tracer,
69
- },
70
- };
71
- }
72
-
73
- // ── Helpers ─────────────────────────────────────────────────────────────
74
- function drainSpans() {
75
- const spans = exporter.getFinishedSpans();
76
- const copy = [...spans];
77
- exporter.reset();
78
- return copy;
79
- }
80
-
81
- function assertAllowlist(spans: ReturnType<typeof drainSpans>) {
82
- for (const span of spans) {
83
- const attrs = span.attributes;
84
- for (const key of Object.keys(attrs)) {
85
- expect(ALLOWED_KEYS.has(key)).toBe(true);
86
- }
87
- }
88
- }
89
-
90
- function assertNoBlockedKeys(spans: ReturnType<typeof drainSpans>) {
91
- for (const span of spans) {
92
- const attrs = span.attributes;
93
- for (const key of Object.keys(attrs)) {
94
- expect(BLOCKED_KEYS.has(key)).toBe(false);
95
- }
96
- }
97
- }
98
-
99
- function assertNoPiiValues(spans: ReturnType<typeof drainSpans>) {
100
- for (const span of spans) {
101
- const attrs = span.attributes;
102
- for (const [key, value] of Object.entries(attrs)) {
103
- if (typeof value === 'string') {
104
- for (const pattern of PII_PATTERNS) {
105
- expect(
106
- pattern.test(value),
107
- `Attribute "${key}" value "${value}" matches PII pattern ${pattern}`
108
- ).toBe(false);
109
- }
110
- }
111
- }
112
- }
113
- }
114
-
115
- // ── Tests ───────────────────────────────────────────────────────────────
116
- beforeAll(() => {
117
- createTelemetryOptions('pii-test', { enabled: true, tracer });
118
- });
119
-
120
- afterEach(() => {
121
- exporter.reset();
122
- });
123
-
124
- describe('telemetry PII safeguards', () => {
125
- // 1. withDatabaseSpan success
126
- it('withDatabaseSpan attributes pass allowlist', async () => {
127
- await withDatabaseSpan(
128
- { operation: 'find', entity: 'subject' },
129
- async () => 'ok',
130
- telemetryOptions()
131
- );
132
-
133
- const spans = drainSpans();
134
- expect(spans.length).toBe(1);
135
- assertAllowlist(spans);
136
- assertNoPiiValues(spans);
137
- });
138
-
139
- // 2. withExternalSpan success — span name = hostname only
140
- it('withExternalSpan span name is HTTP METHOD hostname only', async () => {
141
- await withExternalSpan(
142
- { url: 'https://api.example.com/v1/charges', method: 'GET' },
143
- async () => 'ok',
144
- telemetryOptions()
145
- );
146
-
147
- const spans = drainSpans();
148
- expect(spans.length).toBe(1);
149
- expect(spans[0].name).toBe('HTTP GET api.example.com');
150
- assertAllowlist(spans);
151
- assertNoPiiValues(spans);
152
- });
153
-
154
- // 3. withExternalSpan strips query params from http.url
155
- it('withExternalSpan strips query params from http.url', async () => {
156
- await withExternalSpan(
157
- {
158
- url: 'https://api.example.com/v1/data?key=secret&email=user@ex.com',
159
- method: 'POST',
160
- },
161
- async () => 'ok',
162
- telemetryOptions()
163
- );
164
-
165
- const spans = drainSpans();
166
- expect(spans.length).toBe(1);
167
- const url = spans[0].attributes['http.url'] as string;
168
- expect(url).toBe('https://api.example.com/v1/data');
169
- expect(url).not.toContain('?');
170
- assertAllowlist(spans);
171
- assertNoPiiValues(spans);
172
- });
173
-
174
- // 4. withCacheSpan success
175
- it('withCacheSpan has no unexpected attributes', async () => {
176
- await withCacheSpan('get', 'memory', async () => 'ok', telemetryOptions());
177
-
178
- const spans = drainSpans();
179
- expect(spans.length).toBe(1);
180
- assertAllowlist(spans);
181
- assertNoPiiValues(spans);
182
- });
183
-
184
- // 5. withRequestSpan success — no http.path
185
- it('withRequestSpan has no http.path attribute', async () => {
186
- await withRequestSpan(
187
- 'GET',
188
- '/subjects/abc123',
189
- async () => 'ok',
190
- telemetryOptions()
191
- );
192
-
193
- const spans = drainSpans();
194
- expect(spans.length).toBe(1);
195
- expect(spans[0].attributes['http.path']).toBeUndefined();
196
- assertAllowlist(spans);
197
- });
198
-
199
- // 6. createRequestSpan direct — no http.path
200
- it('createRequestSpan has no http.path attribute', () => {
201
- const span = createRequestSpan(
202
- 'POST',
203
- '/subjects/xyz789',
204
- telemetryOptions()
205
- );
206
-
207
- expect(span).toBeDefined();
208
- span!.end();
209
-
210
- const spans = drainSpans();
211
- expect(spans.length).toBe(1);
212
- expect(spans[0].attributes['http.path']).toBeUndefined();
213
- assertAllowlist(spans);
214
- });
215
-
216
- // 7. withDatabaseSpan error — PII in message
217
- it('withDatabaseSpan error does not leak error.message or error.stack', async () => {
218
- await expect(
219
- withDatabaseSpan(
220
- { operation: 'find', entity: 'subject' },
221
- async () => {
222
- throw new Error('User user@example.com not found at 192.168.1.1');
223
- },
224
- telemetryOptions()
225
- )
226
- ).rejects.toThrow();
227
-
228
- const spans = drainSpans();
229
- expect(spans.length).toBe(1);
230
- assertNoBlockedKeys(spans);
231
- assertNoPiiValues(spans);
232
- });
233
-
234
- // 8. withExternalSpan error — IP in message
235
- it('withExternalSpan error does not leak error.message or error.stack', async () => {
236
- await expect(
237
- withExternalSpan(
238
- { url: 'https://api.example.com/v1/data', method: 'GET' },
239
- async () => {
240
- throw new Error('Connection refused at 10.0.0.1:5432');
241
- },
242
- telemetryOptions()
243
- )
244
- ).rejects.toThrow();
245
-
246
- const spans = drainSpans();
247
- expect(spans.length).toBe(1);
248
- assertNoBlockedKeys(spans);
249
- assertNoPiiValues(spans);
250
- });
251
-
252
- // 9. withCacheSpan error
253
- it('withCacheSpan error does not leak error.message or error.stack', async () => {
254
- await expect(
255
- withCacheSpan(
256
- 'get',
257
- 'external',
258
- async () => {
259
- throw new Error('Redis timeout at 172.16.0.1');
260
- },
261
- telemetryOptions()
262
- )
263
- ).rejects.toThrow();
264
-
265
- const spans = drainSpans();
266
- expect(spans.length).toBe(1);
267
- assertNoBlockedKeys(spans);
268
- assertNoPiiValues(spans);
269
- });
270
-
271
- // 10. withRequestSpan error
272
- it('withRequestSpan error does not leak error.message or error.stack', async () => {
273
- await expect(
274
- withRequestSpan(
275
- 'GET',
276
- '/subjects/abc123',
277
- async () => {
278
- throw new Error('Subject user@corp.io failed auth');
279
- },
280
- telemetryOptions()
281
- )
282
- ).rejects.toThrow();
283
-
284
- const spans = drainSpans();
285
- expect(spans.length).toBe(1);
286
- assertNoBlockedKeys(spans);
287
- assertNoPiiValues(spans);
288
- });
289
-
290
- // 11. All 4 span types combined — every key in allowlist
291
- it('all span types combined: every attribute key is in allowlist', async () => {
292
- // database
293
- await withDatabaseSpan(
294
- { operation: 'create', entity: 'consent' },
295
- async () => 'ok',
296
- telemetryOptions()
297
- );
298
-
299
- // external
300
- await withExternalSpan(
301
- { url: 'https://vendor.example.com/api', method: 'POST' },
302
- async () => 'ok',
303
- telemetryOptions()
304
- );
305
-
306
- // cache
307
- await withCacheSpan('set', 'bundled', async () => 'ok', telemetryOptions());
308
-
309
- // request
310
- await withRequestSpan(
311
- 'DELETE',
312
- '/consents/123',
313
- async () => 'ok',
314
- telemetryOptions()
315
- );
316
-
317
- const spans = drainSpans();
318
- expect(spans.length).toBe(4);
319
- assertAllowlist(spans);
320
- assertNoBlockedKeys(spans);
321
- assertNoPiiValues(spans);
322
- });
323
- });