@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,371 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import type { Subject } from '../schema';
3
- import { subjectRegistry } from './subject';
4
- import type { Registry } from './types';
5
-
6
- // Mock generateUniqueId to return a predictable value for assertions
7
- vi.mock('./utils/generate-id', () => ({
8
- generateUniqueId: vi.fn().mockResolvedValue('sub_test_123'),
9
- }));
10
-
11
- describe('subjectRegistry', () => {
12
- const mockLogger = {
13
- debug: vi.fn(),
14
- error: vi.fn(),
15
- info: vi.fn(),
16
- warn: vi.fn(),
17
- };
18
-
19
- const createMockSubject = (overrides: Partial<Subject> = {}): Subject => ({
20
- id: 'sub_test_123',
21
-
22
- externalId: null,
23
- identityProvider: 'anonymous',
24
- createdAt: new Date('2024-01-01T00:00:00.000Z'),
25
- updatedAt: new Date('2024-01-01T00:00:00.000Z'),
26
- ...overrides,
27
- });
28
-
29
- afterEach(() => {
30
- vi.clearAllMocks();
31
- });
32
-
33
- describe('findOrCreateSubject', () => {
34
- describe('when subjectId is provided', () => {
35
- it('should return existing subject when found', async () => {
36
- const mockSubject = createMockSubject({
37
- id: 'sub_existing',
38
- externalId: 'ext_123',
39
- });
40
-
41
- const db = {
42
- findFirst: vi.fn().mockResolvedValue(mockSubject),
43
- };
44
-
45
- const registry = subjectRegistry({
46
- db,
47
- ctx: { logger: mockLogger },
48
- } as unknown as Registry);
49
-
50
- const result = await registry.findOrCreateSubject({
51
- subjectId: 'sub_existing',
52
- externalSubjectId: 'ext_123',
53
- ipAddress: '192.168.1.1',
54
- });
55
-
56
- expect(db.findFirst).toHaveBeenCalledWith('subject', {
57
- where: expect.any(Function),
58
- });
59
-
60
- expect(result).toEqual(mockSubject);
61
- });
62
-
63
- it('should create new subject when subjectId not found', async () => {
64
- const createdSubject = createMockSubject({
65
- id: 'sub_new',
66
- externalId: null,
67
- identityProvider: 'anonymous',
68
- });
69
-
70
- const db = {
71
- findFirst: vi.fn().mockResolvedValue(null),
72
- create: vi.fn().mockResolvedValue(createdSubject),
73
- };
74
-
75
- const registry = subjectRegistry({
76
- db,
77
- ctx: { logger: mockLogger },
78
- } as unknown as Registry);
79
-
80
- const result = await registry.findOrCreateSubject({
81
- subjectId: 'sub_new',
82
- });
83
-
84
- expect(db.findFirst).toHaveBeenCalledWith('subject', {
85
- where: expect.any(Function),
86
- });
87
-
88
- expect(db.create).toHaveBeenCalledWith('subject', {
89
- id: 'sub_new',
90
- externalId: null,
91
- identityProvider: 'anonymous',
92
- });
93
-
94
- expect(result).toEqual(createdSubject);
95
- });
96
-
97
- it('should create new subject with externalId when both IDs provided but subject not found', async () => {
98
- const createdSubject = createMockSubject({
99
- id: 'sub_new',
100
- externalId: 'ext_123',
101
- identityProvider: 'external',
102
- });
103
-
104
- const db = {
105
- findFirst: vi.fn().mockResolvedValue(null),
106
- create: vi.fn().mockResolvedValue(createdSubject),
107
- };
108
-
109
- const registry = subjectRegistry({
110
- db,
111
- ctx: { logger: mockLogger },
112
- } as unknown as Registry);
113
-
114
- const result = await registry.findOrCreateSubject({
115
- subjectId: 'sub_new',
116
- externalSubjectId: 'ext_123',
117
- });
118
-
119
- expect(db.create).toHaveBeenCalledWith('subject', {
120
- id: 'sub_new',
121
- externalId: 'ext_123',
122
- identityProvider: 'external',
123
- });
124
-
125
- expect(result).toEqual(createdSubject);
126
- });
127
- });
128
-
129
- describe('when only externalSubjectId is provided', () => {
130
- it('should create a new subject with external ID', async () => {
131
- const mockSubject = createMockSubject({
132
- id: 'sub_test_123',
133
- externalId: 'ext_existing',
134
- identityProvider: 'external',
135
- });
136
-
137
- const db = {
138
- create: vi.fn().mockResolvedValue(mockSubject),
139
- };
140
-
141
- const registry = subjectRegistry({
142
- db,
143
- ctx: { logger: mockLogger },
144
- } as unknown as Registry);
145
-
146
- const result = await registry.findOrCreateSubject({
147
- externalSubjectId: 'ext_existing',
148
- ipAddress: '192.168.1.200',
149
- });
150
-
151
- expect(db.create).toHaveBeenCalledWith('subject', {
152
- id: 'sub_test_123',
153
- externalId: 'ext_existing',
154
- identityProvider: 'external',
155
- });
156
-
157
- expect(result).toEqual(mockSubject);
158
- expect(mockLogger.debug).toHaveBeenCalledWith(
159
- 'Creating subject with external ID (legacy flow)',
160
- { externalSubjectId: 'ext_existing' }
161
- );
162
- });
163
-
164
- it('should use custom identity provider when specified', async () => {
165
- const mockSubject = createMockSubject({
166
- id: 'sub_test_123',
167
- externalId: 'ext_new',
168
- identityProvider: 'google',
169
- });
170
-
171
- const db = {
172
- create: vi.fn().mockResolvedValue(mockSubject),
173
- };
174
-
175
- const registry = subjectRegistry({
176
- db,
177
- ctx: { logger: mockLogger },
178
- } as unknown as Registry);
179
-
180
- const result = await registry.findOrCreateSubject({
181
- externalSubjectId: 'ext_new',
182
- identityProvider: 'google',
183
- });
184
-
185
- expect(db.create).toHaveBeenCalledWith('subject', {
186
- id: 'sub_test_123',
187
- externalId: 'ext_new',
188
- identityProvider: 'google',
189
- });
190
-
191
- expect(result).toEqual(mockSubject);
192
- });
193
-
194
- it('should default identityProvider to "external"', async () => {
195
- const mockSubject = createMockSubject({
196
- externalId: 'ext_test',
197
- });
198
-
199
- const db = {
200
- create: vi.fn().mockResolvedValue(mockSubject),
201
- };
202
-
203
- const registry = subjectRegistry({
204
- db,
205
- ctx: { logger: mockLogger },
206
- } as unknown as Registry);
207
-
208
- await registry.findOrCreateSubject({
209
- externalSubjectId: 'ext_test',
210
- });
211
-
212
- expect(db.create).toHaveBeenCalledWith('subject', {
213
- id: 'sub_test_123',
214
- externalId: 'ext_test',
215
- identityProvider: 'external',
216
- });
217
- });
218
- });
219
-
220
- describe('when no identifiers are provided (anonymous subject)', () => {
221
- it('should create a new anonymous subject', async () => {
222
- const mockSubject = createMockSubject({
223
- externalId: null,
224
- });
225
-
226
- const db = {
227
- create: vi.fn().mockResolvedValue(mockSubject),
228
- };
229
-
230
- const registry = subjectRegistry({
231
- db,
232
- ctx: { logger: mockLogger },
233
- } as unknown as Registry);
234
-
235
- const result = await registry.findOrCreateSubject({
236
- ipAddress: '10.0.0.1',
237
- });
238
-
239
- expect(db.create).toHaveBeenCalledWith('subject', {
240
- id: 'sub_test_123',
241
- externalId: null,
242
- identityProvider: 'anonymous',
243
- });
244
-
245
- expect(result).toEqual(mockSubject);
246
- expect(mockLogger.debug).toHaveBeenCalledWith(
247
- 'Creating new anonymous subject'
248
- );
249
- });
250
-
251
- it('should create anonymous subject when no arguments provided', async () => {
252
- const mockSubject = createMockSubject({
253
- externalId: null,
254
- });
255
-
256
- const db = {
257
- create: vi.fn().mockResolvedValue(mockSubject),
258
- };
259
-
260
- const registry = subjectRegistry({
261
- db,
262
- ctx: { logger: mockLogger },
263
- } as unknown as Registry);
264
-
265
- const result = await registry.findOrCreateSubject({});
266
-
267
- expect(db.create).toHaveBeenCalledWith('subject', {
268
- id: 'sub_test_123',
269
- externalId: null,
270
- identityProvider: 'anonymous',
271
- });
272
-
273
- expect(result).toEqual(mockSubject);
274
- });
275
- });
276
-
277
- describe('edge cases and error handling', () => {
278
- it('should handle empty string externalSubjectId as falsy', async () => {
279
- const mockSubject = createMockSubject();
280
-
281
- const db = {
282
- create: vi.fn().mockResolvedValue(mockSubject),
283
- };
284
-
285
- const registry = subjectRegistry({
286
- db,
287
- ctx: { logger: mockLogger },
288
- } as unknown as Registry);
289
-
290
- await registry.findOrCreateSubject({
291
- externalSubjectId: '',
292
- });
293
-
294
- // Should create anonymous subject since empty string is falsy
295
- expect(db.create).toHaveBeenCalledWith('subject', {
296
- id: 'sub_test_123',
297
- externalId: null,
298
- identityProvider: 'anonymous',
299
- });
300
- });
301
-
302
- it('should handle empty string subjectId as falsy', async () => {
303
- const mockSubject = createMockSubject();
304
-
305
- const db = {
306
- create: vi.fn().mockResolvedValue(mockSubject),
307
- };
308
-
309
- const registry = subjectRegistry({
310
- db,
311
- ctx: { logger: mockLogger },
312
- } as unknown as Registry);
313
-
314
- await registry.findOrCreateSubject({
315
- subjectId: '',
316
- });
317
-
318
- // Should create anonymous subject since empty string is falsy
319
- expect(db.create).toHaveBeenCalledWith('subject', {
320
- id: 'sub_test_123',
321
- externalId: null,
322
- identityProvider: 'anonymous',
323
- });
324
- });
325
- });
326
-
327
- describe('database query construction', () => {
328
- it('should construct correct findFirst query for subjectId lookup', async () => {
329
- const mockSubject = createMockSubject();
330
- const db = {
331
- findFirst: vi.fn().mockResolvedValue(mockSubject),
332
- };
333
-
334
- const registry = subjectRegistry({
335
- db,
336
- ctx: { logger: mockLogger },
337
- } as unknown as Registry);
338
-
339
- await registry.findOrCreateSubject({
340
- subjectId: 'sub_test',
341
- });
342
-
343
- expect(db.findFirst).toHaveBeenCalledWith('subject', {
344
- where: expect.any(Function),
345
- });
346
- });
347
-
348
- it('should construct correct create call for externalSubjectId', async () => {
349
- const mockSubject = createMockSubject();
350
- const db = {
351
- create: vi.fn().mockResolvedValue(mockSubject),
352
- };
353
-
354
- const registry = subjectRegistry({
355
- db,
356
- ctx: { logger: mockLogger },
357
- } as unknown as Registry);
358
-
359
- await registry.findOrCreateSubject({
360
- externalSubjectId: 'ext_test',
361
- });
362
-
363
- expect(db.create).toHaveBeenCalledWith('subject', {
364
- id: 'sub_test_123',
365
- externalId: 'ext_test',
366
- identityProvider: 'external',
367
- });
368
- });
369
- });
370
- });
371
- });
@@ -1,126 +0,0 @@
1
- import { withDatabaseSpan } from '~/utils/instrumentation';
2
- import { getMetrics } from '~/utils/metrics';
3
- import type { Registry } from './types';
4
- import { generateUniqueId } from './utils/generate-id';
5
-
6
- export function subjectRegistry({ db, ctx }: Registry) {
7
- const { logger } = ctx;
8
- return {
9
- /**
10
- * Finds or creates a subject.
11
- *
12
- * For the subject-centric API (v2.0):
13
- * - subjectId is required and client-generated
14
- * - Creates subject if it doesn't exist with the provided ID
15
- * - externalSubjectId can be provided at creation time
16
- * - Multiple subjects can have the same externalId (1:many relationship)
17
- *
18
- * @param subjectId - Client-generated subject ID (required for v2.0)
19
- * @param externalSubjectId - Optional external user ID from auth system
20
- * @param identityProvider - Optional identity provider name
21
- * @param ipAddress - Client IP address
22
- */
23
- findOrCreateSubject: async ({
24
- subjectId,
25
- externalSubjectId,
26
- identityProvider,
27
- ipAddress = null,
28
- }: {
29
- subjectId?: string;
30
- externalSubjectId?: string;
31
- identityProvider?: string;
32
- ipAddress?: string | null;
33
- }) => {
34
- const start = Date.now();
35
- try {
36
- const result = await withDatabaseSpan(
37
- { operation: 'findOrCreate', entity: 'subject' },
38
- async () => {
39
- // If subjectId is provided (v2.0 flow), find or create with that ID
40
- if (subjectId) {
41
- logger.debug(
42
- 'Finding/Creating subject with client-generated ID',
43
- {
44
- subjectId,
45
- }
46
- );
47
-
48
- // Try to find existing subject
49
- const existingSubject = await db.findFirst('subject', {
50
- where: (b) => b('id', '=', subjectId),
51
- });
52
-
53
- if (existingSubject) {
54
- logger.debug('Found existing subject', {
55
- subjectId,
56
- });
57
- return existingSubject;
58
- }
59
-
60
- // Create new subject with client-provided ID
61
- logger.debug('Creating new subject with client-generated ID', {
62
- subjectId,
63
- });
64
-
65
- const newSubject = await db.create('subject', {
66
- id: subjectId,
67
- externalId: externalSubjectId ?? null,
68
- identityProvider: externalSubjectId
69
- ? (identityProvider ?? 'external')
70
- : 'anonymous',
71
- });
72
-
73
- logger.debug('Created new subject', {
74
- subject: newSubject,
75
- });
76
-
77
- return newSubject;
78
- }
79
-
80
- // Legacy flow: If only externalSubjectId provided, find or create
81
- // Note: This creates a new subject for each call since we don't have a subjectId
82
- // With 1:many relationship, we should not upsert by externalId anymore
83
- if (externalSubjectId) {
84
- logger.debug('Creating subject with external ID (legacy flow)', {
85
- externalSubjectId,
86
- });
87
-
88
- const subject = await db.create('subject', {
89
- id: await generateUniqueId(db, 'subject', ctx),
90
- externalId: externalSubjectId,
91
- identityProvider: identityProvider ?? 'external',
92
- });
93
-
94
- return subject;
95
- }
96
-
97
- // If no identifiers provided, create an anonymous subject
98
- logger?.debug('Creating new anonymous subject');
99
- const subject = await db.create('subject', {
100
- id: await generateUniqueId(db, 'subject', ctx),
101
- externalId: null,
102
- identityProvider: 'anonymous',
103
- });
104
-
105
- logger.debug('Created new anonymous subject', {
106
- subject,
107
- });
108
-
109
- return subject;
110
- }
111
- );
112
- getMetrics()?.recordDbQuery(
113
- { operation: 'findOrCreate', entity: 'subject' },
114
- Date.now() - start
115
- );
116
- return result;
117
- } catch (error) {
118
- getMetrics()?.recordDbError({
119
- operation: 'findOrCreate',
120
- entity: 'subject',
121
- });
122
- throw error;
123
- }
124
- },
125
- };
126
- }
@@ -1,10 +0,0 @@
1
- import type { createLogger } from '@c15t/logger';
2
- import type { InferFumaDB } from 'fumadb';
3
- import type { LatestDB } from '~/db/schema';
4
-
5
- export interface Registry {
6
- db: ReturnType<InferFumaDB<typeof LatestDB>['orm']>;
7
- ctx: {
8
- logger: ReturnType<typeof createLogger>;
9
- };
10
- }