@c15t/backend 2.0.0-rc.1 → 2.0.0-rc.10

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