@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,747 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { withTenantScope } from './tenant-scope';
3
-
4
- /**
5
- * A minimal in-memory database that implements the ORM interface well enough
6
- * to prove real data isolation. Records are stored in a flat Map<table, rows[]>
7
- * and the where-builder evaluates conditions against actual row data.
8
- */
9
- function createInMemoryOrm() {
10
- const store = new Map<string, Record<string, any>[]>();
11
-
12
- const getTable = (table: string) => {
13
- if (!store.has(table)) store.set(table, []);
14
- return store.get(table)!;
15
- };
16
-
17
- // Minimal where-builder that evaluates conditions against a row
18
- const createBuilder = (row: Record<string, any>) => {
19
- const b: any = (col: string, op: string, val: any) => {
20
- if (op === '=') return row[col] === val;
21
- if (op === '!=') return row[col] !== val;
22
- return false;
23
- };
24
- b.and = (...conds: boolean[]) => conds.every(Boolean);
25
- b.or = (...conds: boolean[]) => conds.some(Boolean);
26
- return b;
27
- };
28
-
29
- const matchesWhere = (
30
- row: Record<string, any>,
31
- where?: (b: any) => boolean
32
- ) => {
33
- if (!where) return true;
34
- return where(createBuilder(row));
35
- };
36
-
37
- const orm: any = {
38
- create: (table: string, data: any) => {
39
- getTable(table).push({ ...data });
40
- return Promise.resolve({ ...data });
41
- },
42
-
43
- createMany: (table: string, items: any[]) => {
44
- const rows = items.map((d) => ({ ...d }));
45
- getTable(table).push(...rows);
46
- return Promise.resolve(rows);
47
- },
48
-
49
- findFirst: (table: string, opts?: any) => {
50
- const rows = getTable(table);
51
- const found = rows.find((r) => matchesWhere(r, opts?.where));
52
- return Promise.resolve(found ?? null);
53
- },
54
-
55
- findMany: (table: string, opts?: any) => {
56
- const rows = getTable(table);
57
- return Promise.resolve(rows.filter((r) => matchesWhere(r, opts?.where)));
58
- },
59
-
60
- count: (table: string, opts?: any) => {
61
- const rows = getTable(table);
62
- return Promise.resolve(
63
- rows.filter((r) => matchesWhere(r, opts?.where)).length
64
- );
65
- },
66
-
67
- updateMany: (table: string, opts: any) => {
68
- const rows = getTable(table);
69
- let updated = 0;
70
- for (const row of rows) {
71
- if (matchesWhere(row, opts?.where)) {
72
- Object.assign(row, opts.set);
73
- updated++;
74
- }
75
- }
76
- return Promise.resolve(updated);
77
- },
78
-
79
- deleteMany: (table: string, opts: any) => {
80
- const rows = getTable(table);
81
- const remaining = rows.filter((r) => !matchesWhere(r, opts?.where));
82
- const deleted = rows.length - remaining.length;
83
- store.set(table, remaining);
84
- return Promise.resolve(deleted);
85
- },
86
-
87
- upsert: (table: string, opts: any) => {
88
- const rows = getTable(table);
89
- const existing = rows.find((r) => matchesWhere(r, opts?.where));
90
- if (existing) {
91
- Object.assign(existing, opts.update);
92
- return Promise.resolve(existing);
93
- }
94
- const created = { ...opts.create };
95
- rows.push(created);
96
- return Promise.resolve(created);
97
- },
98
-
99
- transaction: (fn: any) => fn(orm),
100
- };
101
-
102
- return { orm, store };
103
- }
104
-
105
- function createMockOrm() {
106
- return {
107
- create: vi.fn().mockResolvedValue({ id: 'test_1' }),
108
- createMany: vi.fn().mockResolvedValue([{ _id: 'test_1' }]),
109
- findFirst: vi.fn().mockResolvedValue({ id: 'test_1' }),
110
- findMany: vi.fn().mockResolvedValue([{ id: 'test_1' }]),
111
- count: vi.fn().mockResolvedValue(1),
112
- updateMany: vi.fn().mockResolvedValue(undefined),
113
- deleteMany: vi.fn().mockResolvedValue(undefined),
114
- upsert: vi.fn().mockResolvedValue(undefined),
115
- transaction: vi.fn().mockImplementation((fn: any) => fn(createMockOrm())),
116
- } as any;
117
- }
118
-
119
- // A minimal where builder mock that captures calls for assertion
120
- function createWhereBuilder() {
121
- const builder: any = (col: string, op: string, val: any) => ({
122
- _type: 'condition',
123
- col,
124
- op,
125
- val,
126
- });
127
- builder.and = (...conditions: any[]) => ({
128
- _type: 'and',
129
- conditions,
130
- });
131
- builder.or = (...conditions: any[]) => ({
132
- _type: 'or',
133
- conditions,
134
- });
135
- builder.not = (v: any) => ({ _type: 'not', v });
136
- builder.isNull = (a: string) => ({ _type: 'isNull', a });
137
- builder.isNotNull = (a: string) => ({ _type: 'isNotNull', a });
138
- return builder;
139
- }
140
-
141
- describe('withTenantScope', () => {
142
- const tenantId = 'tenant_abc';
143
-
144
- describe('create', () => {
145
- it('should inject tenantId into created data', async () => {
146
- const db = createMockOrm();
147
- const scoped = withTenantScope(db, tenantId);
148
-
149
- await scoped.create('subject', {
150
- id: 'sub_1',
151
- externalId: null,
152
- identityProvider: 'anonymous',
153
- } as any);
154
-
155
- expect(db.create).toHaveBeenCalledWith('subject', {
156
- id: 'sub_1',
157
- externalId: null,
158
- identityProvider: 'anonymous',
159
-
160
- tenantId: 'tenant_abc',
161
- });
162
- });
163
- });
164
-
165
- describe('createMany', () => {
166
- it('should inject tenantId into all items', async () => {
167
- const db = createMockOrm();
168
- const scoped = withTenantScope(db, tenantId);
169
-
170
- await scoped.createMany('subject', [
171
- { id: 'sub_1', externalId: null } as any,
172
- { id: 'sub_2', externalId: 'ext_1' } as any,
173
- ]);
174
-
175
- expect(db.createMany).toHaveBeenCalledWith('subject', [
176
- { id: 'sub_1', externalId: null, tenantId: 'tenant_abc' },
177
- { id: 'sub_2', externalId: 'ext_1', tenantId: 'tenant_abc' },
178
- ]);
179
- });
180
- });
181
-
182
- describe('findFirst', () => {
183
- it('should add tenantId filter to where clause', async () => {
184
- const db = createMockOrm();
185
- const scoped = withTenantScope(db, tenantId);
186
-
187
- const originalWhere = (b: any) => b('id', '=', 'sub_1');
188
-
189
- await scoped.findFirst('subject', {
190
- where: originalWhere,
191
- });
192
-
193
- expect(db.findFirst).toHaveBeenCalledTimes(1);
194
- const passedOpts = db.findFirst.mock.calls[0][1];
195
-
196
- // Verify the where clause includes tenantId
197
- const b = createWhereBuilder();
198
- const result = passedOpts.where(b);
199
- expect(result).toEqual({
200
- _type: 'and',
201
- conditions: [
202
- { _type: 'condition', col: 'id', op: '=', val: 'sub_1' },
203
- {
204
- _type: 'condition',
205
- col: 'tenantId',
206
- op: '=',
207
- val: 'tenant_abc',
208
- },
209
- ],
210
- });
211
- });
212
-
213
- it('should use only tenantId filter when no original where', async () => {
214
- const db = createMockOrm();
215
- const scoped = withTenantScope(db, tenantId);
216
-
217
- await scoped.findFirst('subject', {} as any);
218
-
219
- const passedOpts = db.findFirst.mock.calls[0][1];
220
- const b = createWhereBuilder();
221
- const result = passedOpts.where(b);
222
- expect(result).toEqual({
223
- _type: 'condition',
224
- col: 'tenantId',
225
- op: '=',
226
- val: 'tenant_abc',
227
- });
228
- });
229
- });
230
-
231
- describe('findMany', () => {
232
- it('should add tenantId filter to where clause', async () => {
233
- const db = createMockOrm();
234
- const scoped = withTenantScope(db, tenantId);
235
-
236
- await scoped.findMany('consent', {
237
- where: (b: any) => b('subjectId', '=', 'sub_1'),
238
- });
239
-
240
- const passedOpts = db.findMany.mock.calls[0][1];
241
- const b = createWhereBuilder();
242
- const result = passedOpts.where(b);
243
- expect(result).toEqual({
244
- _type: 'and',
245
- conditions: [
246
- {
247
- _type: 'condition',
248
- col: 'subjectId',
249
- op: '=',
250
- val: 'sub_1',
251
- },
252
- {
253
- _type: 'condition',
254
- col: 'tenantId',
255
- op: '=',
256
- val: 'tenant_abc',
257
- },
258
- ],
259
- });
260
- });
261
-
262
- it('should handle findMany with no options', async () => {
263
- const db = createMockOrm();
264
- const scoped = withTenantScope(db, tenantId);
265
-
266
- await scoped.findMany('subject');
267
-
268
- const passedOpts = db.findMany.mock.calls[0][1];
269
- const b = createWhereBuilder();
270
- const result = passedOpts.where(b);
271
- expect(result).toEqual({
272
- _type: 'condition',
273
- col: 'tenantId',
274
- op: '=',
275
- val: 'tenant_abc',
276
- });
277
- });
278
- });
279
-
280
- describe('count', () => {
281
- it('should add tenantId filter to where clause', async () => {
282
- const db = createMockOrm();
283
- const scoped = withTenantScope(db, tenantId);
284
-
285
- await scoped.count('subject', {
286
- where: (b: any) => b('externalId', '=', 'ext_1'),
287
- });
288
-
289
- const passedOpts = db.count.mock.calls[0][1];
290
- const b = createWhereBuilder();
291
- const result = passedOpts.where(b);
292
- expect(result).toEqual({
293
- _type: 'and',
294
- conditions: [
295
- {
296
- _type: 'condition',
297
- col: 'externalId',
298
- op: '=',
299
- val: 'ext_1',
300
- },
301
- {
302
- _type: 'condition',
303
- col: 'tenantId',
304
- op: '=',
305
- val: 'tenant_abc',
306
- },
307
- ],
308
- });
309
- });
310
- });
311
-
312
- describe('updateMany', () => {
313
- it('should add tenantId filter to where clause', async () => {
314
- const db = createMockOrm();
315
- const scoped = withTenantScope(db, tenantId);
316
-
317
- await scoped.updateMany('subject', {
318
- where: (b: any) => b('id', '=', 'sub_1'),
319
- set: { identityProvider: 'google' },
320
- });
321
-
322
- const passedOpts = db.updateMany.mock.calls[0][1];
323
-
324
- // Verify set is preserved
325
- expect(passedOpts.set).toEqual({ identityProvider: 'google' });
326
-
327
- // Verify where includes tenantId
328
- const b = createWhereBuilder();
329
- const result = passedOpts.where(b);
330
- expect(result).toEqual({
331
- _type: 'and',
332
- conditions: [
333
- { _type: 'condition', col: 'id', op: '=', val: 'sub_1' },
334
- {
335
- _type: 'condition',
336
- col: 'tenantId',
337
- op: '=',
338
- val: 'tenant_abc',
339
- },
340
- ],
341
- });
342
- });
343
- });
344
-
345
- describe('deleteMany', () => {
346
- it('should add tenantId filter to where clause', async () => {
347
- const db = createMockOrm();
348
- const scoped = withTenantScope(db, tenantId);
349
-
350
- await scoped.deleteMany('consent', {
351
- where: (b: any) => b('id', '=', 'cns_1'),
352
- });
353
-
354
- const passedOpts = db.deleteMany.mock.calls[0][1];
355
- const b = createWhereBuilder();
356
- const result = passedOpts.where(b);
357
- expect(result).toEqual({
358
- _type: 'and',
359
- conditions: [
360
- { _type: 'condition', col: 'id', op: '=', val: 'cns_1' },
361
- {
362
- _type: 'condition',
363
- col: 'tenantId',
364
- op: '=',
365
- val: 'tenant_abc',
366
- },
367
- ],
368
- });
369
- });
370
- });
371
-
372
- describe('upsert', () => {
373
- it('should add tenantId to where clause and create data', async () => {
374
- const db = createMockOrm();
375
- const scoped = withTenantScope(db, tenantId);
376
-
377
- await scoped.upsert('subject', {
378
- where: (b: any) => b('externalId', '=', 'ext_1'),
379
- create: {
380
- id: 'sub_1',
381
- externalId: 'ext_1',
382
- } as any,
383
- update: { identityProvider: 'google' },
384
- });
385
-
386
- const passedOpts = db.upsert.mock.calls[0][1];
387
-
388
- // Verify create includes tenantId
389
- expect(passedOpts.create).toEqual({
390
- id: 'sub_1',
391
- externalId: 'ext_1',
392
-
393
- tenantId: 'tenant_abc',
394
- });
395
-
396
- // Verify update is preserved
397
- expect(passedOpts.update).toEqual({ identityProvider: 'google' });
398
-
399
- // Verify where includes tenantId
400
- const b = createWhereBuilder();
401
- const result = passedOpts.where(b);
402
- expect(result).toEqual({
403
- _type: 'and',
404
- conditions: [
405
- {
406
- _type: 'condition',
407
- col: 'externalId',
408
- op: '=',
409
- val: 'ext_1',
410
- },
411
- {
412
- _type: 'condition',
413
- col: 'tenantId',
414
- op: '=',
415
- val: 'tenant_abc',
416
- },
417
- ],
418
- });
419
- });
420
- });
421
-
422
- describe('transaction', () => {
423
- it('should provide a tenant-scoped ORM inside transaction', async () => {
424
- const innerDb = createMockOrm();
425
- const db = createMockOrm();
426
- db.transaction.mockImplementation((fn: any) => fn(innerDb));
427
-
428
- const scoped = withTenantScope(db, tenantId);
429
-
430
- await scoped.transaction(async (tx) => {
431
- await tx.create('subject', {
432
- id: 'sub_1',
433
- } as any);
434
- await tx.findFirst('subject', {
435
- where: (b: any) => b('id', '=', 'sub_1'),
436
- });
437
- });
438
-
439
- // The inner db should have tenantId injected
440
- expect(innerDb.create).toHaveBeenCalledWith('subject', {
441
- id: 'sub_1',
442
-
443
- tenantId: 'tenant_abc',
444
- });
445
-
446
- const findOpts = innerDb.findFirst.mock.calls[0][1];
447
- const b = createWhereBuilder();
448
- const result = findOpts.where(b);
449
- expect(result).toEqual({
450
- _type: 'and',
451
- conditions: [
452
- { _type: 'condition', col: 'id', op: '=', val: 'sub_1' },
453
- {
454
- _type: 'condition',
455
- col: 'tenantId',
456
- op: '=',
457
- val: 'tenant_abc',
458
- },
459
- ],
460
- });
461
- });
462
- });
463
-
464
- describe('data isolation (mock)', () => {
465
- it('should scope different tenants to their own data', async () => {
466
- const db = createMockOrm();
467
- const tenantA = withTenantScope(db, 'tenant_a');
468
- const tenantB = withTenantScope(db, 'tenant_b');
469
-
470
- await tenantA.create('subject', { id: 'sub_1' } as any);
471
- await tenantB.create('subject', { id: 'sub_2' } as any);
472
-
473
- expect(db.create).toHaveBeenCalledWith('subject', {
474
- id: 'sub_1',
475
- tenantId: 'tenant_a',
476
- });
477
- expect(db.create).toHaveBeenCalledWith('subject', {
478
- id: 'sub_2',
479
- tenantId: 'tenant_b',
480
- });
481
-
482
- // Each tenant's findMany should have its own tenantId filter
483
- await tenantA.findMany('subject');
484
- await tenantB.findMany('subject');
485
-
486
- const b = createWhereBuilder();
487
-
488
- const tenantAOpts = db.findMany.mock.calls[0][1];
489
- expect(tenantAOpts.where(b)).toEqual({
490
- _type: 'condition',
491
- col: 'tenantId',
492
- op: '=',
493
- val: 'tenant_a',
494
- });
495
-
496
- const tenantBOpts = db.findMany.mock.calls[1][1];
497
- expect(tenantBOpts.where(b)).toEqual({
498
- _type: 'condition',
499
- col: 'tenantId',
500
- op: '=',
501
- val: 'tenant_b',
502
- });
503
- });
504
- });
505
- });
506
-
507
- // ===========================================================================
508
- // Integration tests — real in-memory store proving row-level isolation
509
- // ===========================================================================
510
-
511
- describe('withTenantScope – row-level isolation (integration)', () => {
512
- it('tenant A cannot see subjects created by tenant B', async () => {
513
- const { orm } = createInMemoryOrm();
514
- const tenantA = withTenantScope(orm, 'tenant_a');
515
- const tenantB = withTenantScope(orm, 'tenant_b');
516
-
517
- await tenantA.create('subject', {
518
- id: 'sub_1',
519
- externalId: 'Alice',
520
- } as any);
521
- await tenantB.create('subject', { id: 'sub_2', externalId: 'Bob' } as any);
522
-
523
- const aSubjects = await tenantA.findMany('subject');
524
- const bSubjects = await tenantB.findMany('subject');
525
-
526
- expect(aSubjects).toHaveLength(1);
527
- expect(aSubjects[0]!.id).toBe('sub_1');
528
- expect(aSubjects[0]!.externalId).toBe('Alice');
529
-
530
- expect(bSubjects).toHaveLength(1);
531
- expect(bSubjects[0]!.id).toBe('sub_2');
532
- expect(bSubjects[0]!.externalId).toBe('Bob');
533
- });
534
-
535
- it('findFirst only returns records belonging to the querying tenant', async () => {
536
- const { orm } = createInMemoryOrm();
537
- const tenantA = withTenantScope(orm, 'tenant_a');
538
- const tenantB = withTenantScope(orm, 'tenant_b');
539
-
540
- await tenantA.create('consent', { id: 'cns_1', subjectId: 'sub_1' } as any);
541
-
542
- const fromA = await tenantA.findFirst('consent', {
543
- where: (b: any) => b('id', '=', 'cns_1'),
544
- });
545
- const fromB = await tenantB.findFirst('consent', {
546
- where: (b: any) => b('id', '=', 'cns_1'),
547
- });
548
-
549
- expect(fromA).not.toBeNull();
550
- expect(fromA!.id).toBe('cns_1');
551
- expect(fromB).toBeNull();
552
- });
553
-
554
- it('count only counts records belonging to the querying tenant', async () => {
555
- const { orm } = createInMemoryOrm();
556
- const tenantA = withTenantScope(orm, 'tenant_a');
557
- const tenantB = withTenantScope(orm, 'tenant_b');
558
-
559
- await tenantA.create('subject', { id: 'sub_1' } as any);
560
- await tenantA.create('subject', { id: 'sub_2' } as any);
561
- await tenantB.create('subject', { id: 'sub_3' } as any);
562
-
563
- expect(await tenantA.count('subject')).toBe(2);
564
- expect(await tenantB.count('subject')).toBe(1);
565
- });
566
-
567
- it("updateMany only affects the calling tenant's rows", async () => {
568
- const { orm } = createInMemoryOrm();
569
- const tenantA = withTenantScope(orm, 'tenant_a');
570
- const tenantB = withTenantScope(orm, 'tenant_b');
571
-
572
- await tenantA.create('subject', {
573
- id: 'sub_1',
574
- identityProvider: '1.1.1.1',
575
- } as any);
576
- await tenantB.create('subject', {
577
- id: 'sub_2',
578
- identityProvider: '2.2.2.2',
579
- } as any);
580
-
581
- // Tenant A updates all their subjects
582
- await tenantA.updateMany('subject', {
583
- where: (b: any) => b('id', '=', 'sub_1'),
584
- set: { identityProvider: '9.9.9.9' },
585
- });
586
-
587
- // Tenant A's row is updated
588
- const aRow = await tenantA.findFirst('subject', {
589
- where: (b: any) => b('id', '=', 'sub_1'),
590
- });
591
- expect(aRow!.identityProvider).toBe('9.9.9.9');
592
-
593
- // Tenant B's row is untouched
594
- const bRow = await tenantB.findFirst('subject', {
595
- where: (b: any) => b('id', '=', 'sub_2'),
596
- });
597
- expect(bRow!.identityProvider).toBe('2.2.2.2');
598
- });
599
-
600
- it("deleteMany only deletes the calling tenant's rows", async () => {
601
- const { orm } = createInMemoryOrm();
602
- const tenantA = withTenantScope(orm, 'tenant_a');
603
- const tenantB = withTenantScope(orm, 'tenant_b');
604
-
605
- await tenantA.create('domain', { id: 'dom_1', name: 'a.com' } as any);
606
- await tenantB.create('domain', { id: 'dom_2', name: 'b.com' } as any);
607
-
608
- // Tenant A deletes their domain
609
- await tenantA.deleteMany('domain', {
610
- where: (b: any) => b('id', '=', 'dom_1'),
611
- });
612
-
613
- expect(await tenantA.findMany('domain')).toHaveLength(0);
614
- expect(await tenantB.findMany('domain')).toHaveLength(1);
615
- });
616
-
617
- it('upsert creates with tenantId and only matches own rows', async () => {
618
- const { orm } = createInMemoryOrm();
619
- const tenantA = withTenantScope(orm, 'tenant_a');
620
- const tenantB = withTenantScope(orm, 'tenant_b');
621
-
622
- // Tenant A upserts — creates since row doesn't exist
623
- await tenantA.upsert('subject', {
624
- where: (b: any) => b('externalId', '=', 'ext_1'),
625
- create: {
626
- id: 'sub_1',
627
- externalId: 'ext_1',
628
- identityProvider: '1.1.1.1',
629
- } as any,
630
- update: { identityProvider: '9.9.9.9' },
631
- });
632
-
633
- // Tenant B upserts same externalId — should also CREATE (not update A's row)
634
- await tenantB.upsert('subject', {
635
- where: (b: any) => b('externalId', '=', 'ext_1'),
636
- create: {
637
- id: 'sub_2',
638
- externalId: 'ext_1',
639
- identityProvider: '2.2.2.2',
640
- } as any,
641
- update: { identityProvider: '8.8.8.8' },
642
- });
643
-
644
- const aRows = await tenantA.findMany('subject');
645
- const bRows = await tenantB.findMany('subject');
646
-
647
- // Each tenant has their own row, even though externalId is the same
648
- expect(aRows).toHaveLength(1);
649
- expect(aRows[0]!.id).toBe('sub_1');
650
- expect(aRows[0]!.identityProvider).toBe('1.1.1.1'); // unchanged
651
-
652
- expect(bRows).toHaveLength(1);
653
- expect(bRows[0]!.id).toBe('sub_2');
654
- expect(bRows[0]!.identityProvider).toBe('2.2.2.2'); // created, not updated from A
655
- });
656
-
657
- it("tenant A cannot update tenant B's row even with matching id", async () => {
658
- const { orm } = createInMemoryOrm();
659
- const tenantA = withTenantScope(orm, 'tenant_a');
660
- const tenantB = withTenantScope(orm, 'tenant_b');
661
-
662
- await tenantB.create('subject', {
663
- id: 'sub_1',
664
- identityProvider: '2.2.2.2',
665
- } as any);
666
-
667
- // Tenant A tries to update sub_1 which belongs to B
668
- await tenantA.updateMany('subject', {
669
- where: (b: any) => b('id', '=', 'sub_1'),
670
- set: { identityProvider: 'hacked' },
671
- });
672
-
673
- // B's row is still untouched
674
- const bRow = await tenantB.findFirst('subject', {
675
- where: (b: any) => b('id', '=', 'sub_1'),
676
- });
677
- expect(bRow!.identityProvider).toBe('2.2.2.2');
678
- });
679
-
680
- it("tenant A cannot delete tenant B's row even with matching id", async () => {
681
- const { orm } = createInMemoryOrm();
682
- const tenantA = withTenantScope(orm, 'tenant_a');
683
- const tenantB = withTenantScope(orm, 'tenant_b');
684
-
685
- await tenantB.create('domain', { id: 'dom_1', name: 'b.com' } as any);
686
-
687
- // Tenant A tries to delete dom_1 which belongs to B
688
- await tenantA.deleteMany('domain', {
689
- where: (b: any) => b('id', '=', 'dom_1'),
690
- });
691
-
692
- // B's row still exists
693
- expect(await tenantB.findMany('domain')).toHaveLength(1);
694
- });
695
-
696
- it('transaction inherits tenant scope', async () => {
697
- const { orm } = createInMemoryOrm();
698
- const tenantA = withTenantScope(orm, 'tenant_a');
699
- const tenantB = withTenantScope(orm, 'tenant_b');
700
-
701
- await tenantA.transaction(async (tx) => {
702
- await tx.create('subject', {
703
- id: 'sub_tx',
704
- externalId: 'TxAlice',
705
- } as any);
706
- });
707
-
708
- // Visible to tenant A
709
- const aResult = await tenantA.findFirst('subject', {
710
- where: (b: any) => b('id', '=', 'sub_tx'),
711
- });
712
- expect(aResult).not.toBeNull();
713
- expect(aResult!.externalId).toBe('TxAlice');
714
-
715
- // Invisible to tenant B
716
- const bResult = await tenantB.findFirst('subject', {
717
- where: (b: any) => b('id', '=', 'sub_tx'),
718
- });
719
- expect(bResult).toBeNull();
720
- });
721
-
722
- it('three tenants sharing same database are fully isolated', async () => {
723
- const { orm, store } = createInMemoryOrm();
724
- const t1 = withTenantScope(orm, 'inst_001');
725
- const t2 = withTenantScope(orm, 'inst_002');
726
- const t3 = withTenantScope(orm, 'inst_003');
727
-
728
- await t1.create('subject', { id: 'sub_1' } as any);
729
- await t2.create('subject', { id: 'sub_2' } as any);
730
- await t2.create('subject', { id: 'sub_3' } as any);
731
- await t3.create('subject', { id: 'sub_4' } as any);
732
- await t3.create('subject', { id: 'sub_5' } as any);
733
- await t3.create('subject', { id: 'sub_6' } as any);
734
-
735
- // Underlying store has all 6 rows
736
- expect(store.get('subject')).toHaveLength(6);
737
-
738
- // But each tenant only sees their own
739
- expect(await t1.count('subject')).toBe(1);
740
- expect(await t2.count('subject')).toBe(2);
741
- expect(await t3.count('subject')).toBe(3);
742
-
743
- expect(await t1.findMany('subject')).toHaveLength(1);
744
- expect(await t2.findMany('subject')).toHaveLength(2);
745
- expect(await t3.findMany('subject')).toHaveLength(3);
746
- });
747
- });