import Database from "better-sqlite3";import { describe, expect, it } from "@effect/vitest";import { drizzle } from "drizzle-orm/better-sqlite3";import { Effect } from "effect";import { fumadb } from "@executor-js/fumadb";import { createDrizzleRuntimeSchemaFromTables, createDrizzleRuntimeSchemaSqlFromTables, drizzleAdapter,} from "@executor-js/fumadb/adapters/drizzle";import { memoryAdapter } from "@executor-js/fumadb/adapters/memory";import { withQueryContext, type AbstractQuery } from "@executor-js/fumadb/query";import { column, idColumn, schema, table } from "@executor-js/fumadb/schema"; interface TenantPolicyContext { readonly allowedTenantIds: ReadonlySet<string>; readonly deniedTables: ReadonlySet<string>; readonly marker: string; readonly observed: string[]; readonly allowedRegionId?: bigint;} const observe = (context: TenantPolicyContext, event: string) => { context.observed.push(`${context.marker}:${event}`);}; const assertTenantAllowed = (tableName: string, context: TenantPolicyContext, tenantId: string) => { observe(context, `${tableName}:assert`); if (!context.allowedTenantIds.has(tenantId)) { // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: FumaDB table policy callbacks reject writes by throwing throw new Error(`tenant ${tenantId} is not allowed for ${tableName}`); }}; const isReadDenied = (tableName: string, context: TenantPolicyContext) => { observe(context, `${tableName}:read`); return context.deniedTables.has(tableName);}; const authors = table("policy_authors", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), name: column("name", "string"),}).policy<TenantPolicyContext>({ name: "tenant.authors", onRead: ({ builder, context }) => { if (isReadDenied("authors", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("authors", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "authors:update"); if (set.tenantId !== undefined) assertTenantAllowed("authors", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "authors:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); },}); const posts = table("policy_posts", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), authorId: column("author_id", "varchar(255)"), title: column("title", "string"),}).policy<TenantPolicyContext>({ name: "tenant.posts", onRead: ({ builder, context }) => { if (isReadDenied("posts", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("posts", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "posts:update"); if (set.tenantId !== undefined) assertTenantAllowed("posts", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "posts:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); },}); const comments = table("policy_comments", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), postId: column("post_id", "varchar(255)"), body: column("body", "string"),}).policy<TenantPolicyContext>({ name: "tenant.comments", onRead: ({ builder, context }) => { if (isReadDenied("comments", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("comments", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "comments:update"); if (set.tenantId !== undefined) assertTenantAllowed("comments", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "comments:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); },}); // Policy predicate carries a bigint value; string-keyed grouping of these// predicates (e.g. JSON.stringify) throws on bigint.const quotas = table("policy_quotas", { id: idColumn("id", "varchar(255)"), region: column("region", "bigint"), label: column("label", "string"),}).policy<TenantPolicyContext>({ name: "tenant.quotas", onUpdate: ({ builder, context }) => builder("region", "=", context.allowedRegionId ?? BigInt(-1)),}); // The update policy predicate depends on the row itself, so rows with// different `shard` values compile to different predicates and split one// upsertMany call into separate predicate groups.const shards = table("policy_shards", { id: idColumn("id", "varchar(255)"), shard: column("shard", "string"), authorId: column("author_id", "varchar(255)"),}).policy<TenantPolicyContext>({ name: "tenant.shards", onUpdate: ({ builder, set }) => builder("shard", "=", set.shard ?? ""),}); // Ten columns, so a row-count-sized batch would bind ten parameters per row.const wideRows = table("policy_wide_rows", { id: idColumn("id", "varchar(255)"), c1: column("c1", "string"), c2: column("c2", "string"), c3: column("c3", "string"), c4: column("c4", "string"), c5: column("c5", "string"), c6: column("c6", "string"), c7: column("c7", "string"), c8: column("c8", "string"), c9: column("c9", "string"),}); // The physical column name needs identifier quoting; the bulk upsert conflict// SET clause references it as `excluded.<name>` and must quote it.const quotedNames = table("policy_quoted_names", { id: idColumn("id", "varchar(255)"), displayName: column("display-name", "string"),}); const v1 = schema({ version: "1.0.0", tables: { authors, posts, comments, quotas, shards, wideRows, quotedNames, }, relations: { authors: ({ many }) => ({ posts: many("posts"), shards: many("shards"), }), shards: ({ one }) => ({ author: one("authors", ["authorId", "id"]).foreignKey(), }), posts: ({ one, many }) => ({ author: one("authors", ["authorId", "id"]).foreignKey(), comments: many("comments"), }), comments: ({ one }) => ({ post: one("posts", ["postId", "id"]).foreignKey(), }), },}); const tablePolicyDB = fumadb({ namespace: "table_policy_test", schemas: [v1],}); type TablePolicyQuery = AbstractQuery<typeof v1>; const makeContext = ( allowedTenantIds: readonly string[], marker: string, deniedTables: readonly string[] = [],): TenantPolicyContext => ({ allowedTenantIds: new Set(allowedTenantIds), deniedTables: new Set(deniedTables), marker, observed: [],}); const makeHarness = async (options?: { readonly nativeBatch?: boolean; readonly maxBoundParameters?: number;}) => { const sqlite = new Database(":memory:"); sqlite.pragma("foreign_keys = ON"); const runtimeSchema = createDrizzleRuntimeSchemaFromTables({ tables: v1.tables, namespace: "table_policy_test", version: "1.0.0", provider: "sqlite", }); const statements: { readonly sql: string; readonly paramCount: number }[] = []; const drizzleDb = drizzle(sqlite, { schema: runtimeSchema, logger: { logQuery: (query: string, params: unknown[]) => { statements.push({ sql: query, paramCount: params.length }); }, }, }); let batchCalls = 0; if (options?.nativeBatch) { Object.assign(drizzleDb, { batch: async (queries: readonly { run: () => unknown }[]) => { batchCalls += 1; return sqlite.transaction(() => queries.map((query) => query.run()))(); }, }); } for (const statement of createDrizzleRuntimeSchemaSqlFromTables({ tables: v1.tables, namespace: "table_policy_test", version: "1.0.0", provider: "sqlite", })) { sqlite.exec(statement); } const client = tablePolicyDB.client( drizzleAdapter({ db: drizzleDb, provider: "sqlite", interactiveTransactions: options?.nativeBatch ? false : undefined, maxBoundParameters: options?.maxBoundParameters, }), ); return { orm: client.orm("1.0.0"), getBatchCalls: () => batchCalls, getStatements: (): readonly { readonly sql: string; readonly paramCount: number }[] => statements, close: async () => { sqlite.close(); }, };}; const useHarness = <A>(run: (orm: TablePolicyQuery) => Promise<A>) => Effect.acquireUseRelease( Effect.promise(() => makeHarness()), ({ orm }) => Effect.promise(() => run(orm)), ({ close }) => Effect.promise(close), ); const useStatementHarness = <A>( run: (harness: { readonly orm: TablePolicyQuery; readonly getStatements: () => readonly { readonly sql: string; readonly paramCount: number }[]; }) => Promise<A>,) => Effect.acquireUseRelease( Effect.promise(() => makeHarness()), (harness) => Effect.promise(() => run(harness)), ({ close }) => Effect.promise(close), ); const useBudgetHarness = <A>( maxBoundParameters: number, run: (orm: TablePolicyQuery) => Promise<A>,) => Effect.acquireUseRelease( Effect.promise(() => makeHarness({ maxBoundParameters })), ({ orm }) => Effect.promise(() => run(orm)), ({ close }) => Effect.promise(close), ); const useNativeBatchHarness = <A>( run: (harness: { readonly orm: TablePolicyQuery; readonly getBatchCalls: () => number; }) => Promise<A>,) => Effect.acquireUseRelease( Effect.promise(() => makeHarness({ nativeBatch: true, maxBoundParameters: 8 })), (harness) => Effect.promise(() => run(harness)), ({ close }) => Effect.promise(close), ); const seedTenants = async (orm: TablePolicyQuery) => { const seed = withQueryContext(orm, makeContext(["tenant-a", "tenant-b"], "seed")); await seed.createMany("authors", [ { id: "author-a", tenantId: "tenant-a", name: "Ada", }, { id: "author-b", tenantId: "tenant-b", name: "Bert", }, ]); await seed.createMany("posts", [ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", }, { id: "post-b-1", tenantId: "tenant-b", authorId: "author-b", title: "B One", }, ]); await seed.createMany("comments", [ { id: "comment-a-1", tenantId: "tenant-a", postId: "post-a-1", body: "A comment", }, { id: "comment-b-1", tenantId: "tenant-b", postId: "post-b-1", body: "B comment", }, ]);}; describe("FumaDB table policies", () => { it.effect( "filters reads, joins, counts, updates, deletes, and upserts through public query APIs", () => useHarness(async (orm) => { await seedTenants(orm); const tenantAContext = makeContext(["tenant-a"], "tenant-a"); const tenantA = withQueryContext(orm, tenantAContext); const allTenants = withQueryContext(orm, makeContext(["tenant-a", "tenant-b"], "all")); await expect(tenantA.count("posts")).resolves.toBe(2); await expect( tenantA.findMany("authors", { orderBy: ["id", "asc"], join: (builder) => builder.posts({ orderBy: ["id", "asc"], join: (builder) => builder.comments({ orderBy: ["id", "asc"] }), }), }), ).resolves.toEqual([ { id: "author-a", tenantId: "tenant-a", name: "Ada", posts: [ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", comments: [ { id: "comment-a-1", tenantId: "tenant-a", postId: "post-a-1", body: "A comment", }, ], }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", comments: [], }, ], }, ]); await expect( tenantA.findMany("posts", { orderBy: ["id", "asc"], join: (builder) => builder.author({ select: ["id", "tenantId"], }), }), ).resolves.toEqual([ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", author: { id: "author-a", tenantId: "tenant-a", }, }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", author: { id: "author-a", tenantId: "tenant-a", }, }, ]); await tenantA.updateMany("posts", { set: { title: "tenant-a-updated", }, }); await expect( allTenants.findMany("posts", { select: ["id", "title"], orderBy: ["id", "asc"], }), ).resolves.toEqual([ { id: "post-a-1", title: "tenant-a-updated", }, { id: "post-a-2", title: "tenant-a-updated", }, { id: "post-b-1", title: "B One", }, ]); await tenantA.deleteMany("comments", {}); await expect( allTenants.findMany("comments", { select: ["id", "tenantId"], orderBy: ["id", "asc"], }), ).resolves.toEqual([ { id: "comment-b-1", tenantId: "tenant-b", }, ]); await tenantA.upsert("posts", { where: (builder) => builder("id", "=", "post-a-2"), update: { title: "tenant-a-upserted", }, create: { id: "post-a-created-if-missing", tenantId: "tenant-a", authorId: "author-a", title: "not used", }, }); await tenantA.upsert("posts", { where: (builder) => builder("id", "=", "post-a-3"), update: { title: "not used", }, create: { id: "post-a-3", tenantId: "tenant-a", authorId: "author-a", title: "A Three", }, }); await tenantA.upsertMany("posts", { target: ["id"],Lines 1–500 of 1131. Load more
import Database from "better-sqlite3"; import { describe, expect, it } from "@effect/vitest"; import { drizzle } from "drizzle-orm/better-sqlite3"; import { Effect } from "effect"; import { fumadb } from "@executor-js/fumadb"; import { createDrizzleRuntimeSchemaFromTables, createDrizzleRuntimeSchemaSqlFromTables, drizzleAdapter, } from "@executor-js/fumadb/adapters/drizzle"; import { memoryAdapter } from "@executor-js/fumadb/adapters/memory"; import { withQueryContext, type AbstractQuery } from "@executor-js/fumadb/query"; import { column, idColumn, schema, table } from "@executor-js/fumadb/schema"; interface TenantPolicyContext { readonly allowedTenantIds: ReadonlySet<string>; readonly deniedTables: ReadonlySet<string>; readonly marker: string; readonly observed: string[]; readonly allowedRegionId?: bigint; } const observe = (context: TenantPolicyContext, event: string) => { context.observed.push(`${context.marker}:${event}`); }; const assertTenantAllowed = (tableName: string, context: TenantPolicyContext, tenantId: string) => { observe(context, `${tableName}:assert`); if (!context.allowedTenantIds.has(tenantId)) { // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: FumaDB table policy callbacks reject writes by throwing throw new Error(`tenant ${tenantId} is not allowed for ${tableName}`); } }; const isReadDenied = (tableName: string, context: TenantPolicyContext) => { observe(context, `${tableName}:read`); return context.deniedTables.has(tableName); }; const authors = table("policy_authors", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), name: column("name", "string"), }).policy<TenantPolicyContext>({ name: "tenant.authors", onRead: ({ builder, context }) => { if (isReadDenied("authors", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("authors", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "authors:update"); if (set.tenantId !== undefined) assertTenantAllowed("authors", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "authors:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); }, }); const posts = table("policy_posts", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), authorId: column("author_id", "varchar(255)"), title: column("title", "string"), }).policy<TenantPolicyContext>({ name: "tenant.posts", onRead: ({ builder, context }) => { if (isReadDenied("posts", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("posts", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "posts:update"); if (set.tenantId !== undefined) assertTenantAllowed("posts", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "posts:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); }, }); const comments = table("policy_comments", { id: idColumn("id", "varchar(255)"), tenantId: column("tenant_id", "varchar(255)"), postId: column("post_id", "varchar(255)"), body: column("body", "string"), }).policy<TenantPolicyContext>({ name: "tenant.comments", onRead: ({ builder, context }) => { if (isReadDenied("comments", context)) return false; return builder("tenantId", "in", [...context.allowedTenantIds]); }, onCreate: ({ values, context }) => assertTenantAllowed("comments", context, values.tenantId), onUpdate: ({ builder, set, context }) => { observe(context, "comments:update"); if (set.tenantId !== undefined) assertTenantAllowed("comments", context, set.tenantId); return builder("tenantId", "in", [...context.allowedTenantIds]); }, onDelete: ({ builder, context }) => { observe(context, "comments:delete"); return builder("tenantId", "in", [...context.allowedTenantIds]); }, }); // Policy predicate carries a bigint value; string-keyed grouping of these // predicates (e.g. JSON.stringify) throws on bigint. const quotas = table("policy_quotas", { id: idColumn("id", "varchar(255)"), region: column("region", "bigint"), label: column("label", "string"), }).policy<TenantPolicyContext>({ name: "tenant.quotas", onUpdate: ({ builder, context }) => builder("region", "=", context.allowedRegionId ?? BigInt(-1)), }); // The update policy predicate depends on the row itself, so rows with // different `shard` values compile to different predicates and split one // upsertMany call into separate predicate groups. const shards = table("policy_shards", { id: idColumn("id", "varchar(255)"), shard: column("shard", "string"), authorId: column("author_id", "varchar(255)"), }).policy<TenantPolicyContext>({ name: "tenant.shards", onUpdate: ({ builder, set }) => builder("shard", "=", set.shard ?? ""), }); // Ten columns, so a row-count-sized batch would bind ten parameters per row. const wideRows = table("policy_wide_rows", { id: idColumn("id", "varchar(255)"), c1: column("c1", "string"), c2: column("c2", "string"), c3: column("c3", "string"), c4: column("c4", "string"), c5: column("c5", "string"), c6: column("c6", "string"), c7: column("c7", "string"), c8: column("c8", "string"), c9: column("c9", "string"), }); // The physical column name needs identifier quoting; the bulk upsert conflict // SET clause references it as `excluded.<name>` and must quote it. const quotedNames = table("policy_quoted_names", { id: idColumn("id", "varchar(255)"), displayName: column("display-name", "string"), }); const v1 = schema({ version: "1.0.0", tables: { authors, posts, comments, quotas, shards, wideRows, quotedNames, }, relations: { authors: ({ many }) => ({ posts: many("posts"), shards: many("shards"), }), shards: ({ one }) => ({ author: one("authors", ["authorId", "id"]).foreignKey(), }), posts: ({ one, many }) => ({ author: one("authors", ["authorId", "id"]).foreignKey(), comments: many("comments"), }), comments: ({ one }) => ({ post: one("posts", ["postId", "id"]).foreignKey(), }), }, }); const tablePolicyDB = fumadb({ namespace: "table_policy_test", schemas: [v1], }); type TablePolicyQuery = AbstractQuery<typeof v1>; const makeContext = ( allowedTenantIds: readonly string[], marker: string, deniedTables: readonly string[] = [], ): TenantPolicyContext => ({ allowedTenantIds: new Set(allowedTenantIds), deniedTables: new Set(deniedTables), marker, observed: [], }); const makeHarness = async (options?: { readonly nativeBatch?: boolean; readonly maxBoundParameters?: number; }) => { const sqlite = new Database(":memory:"); sqlite.pragma("foreign_keys = ON"); const runtimeSchema = createDrizzleRuntimeSchemaFromTables({ tables: v1.tables, namespace: "table_policy_test", version: "1.0.0", provider: "sqlite", }); const statements: { readonly sql: string; readonly paramCount: number }[] = []; const drizzleDb = drizzle(sqlite, { schema: runtimeSchema, logger: { logQuery: (query: string, params: unknown[]) => { statements.push({ sql: query, paramCount: params.length }); }, }, }); let batchCalls = 0; if (options?.nativeBatch) { Object.assign(drizzleDb, { batch: async (queries: readonly { run: () => unknown }[]) => { batchCalls += 1; return sqlite.transaction(() => queries.map((query) => query.run()))(); }, }); } for (const statement of createDrizzleRuntimeSchemaSqlFromTables({ tables: v1.tables, namespace: "table_policy_test", version: "1.0.0", provider: "sqlite", })) { sqlite.exec(statement); } const client = tablePolicyDB.client( drizzleAdapter({ db: drizzleDb, provider: "sqlite", interactiveTransactions: options?.nativeBatch ? false : undefined, maxBoundParameters: options?.maxBoundParameters, }), ); return { orm: client.orm("1.0.0"), getBatchCalls: () => batchCalls, getStatements: (): readonly { readonly sql: string; readonly paramCount: number }[] => statements, close: async () => { sqlite.close(); }, }; }; const useHarness = <A>(run: (orm: TablePolicyQuery) => Promise<A>) => Effect.acquireUseRelease( Effect.promise(() => makeHarness()), ({ orm }) => Effect.promise(() => run(orm)), ({ close }) => Effect.promise(close), ); const useStatementHarness = <A>( run: (harness: { readonly orm: TablePolicyQuery; readonly getStatements: () => readonly { readonly sql: string; readonly paramCount: number }[]; }) => Promise<A>, ) => Effect.acquireUseRelease( Effect.promise(() => makeHarness()), (harness) => Effect.promise(() => run(harness)), ({ close }) => Effect.promise(close), ); const useBudgetHarness = <A>( maxBoundParameters: number, run: (orm: TablePolicyQuery) => Promise<A>, ) => Effect.acquireUseRelease( Effect.promise(() => makeHarness({ maxBoundParameters })), ({ orm }) => Effect.promise(() => run(orm)), ({ close }) => Effect.promise(close), ); const useNativeBatchHarness = <A>( run: (harness: { readonly orm: TablePolicyQuery; readonly getBatchCalls: () => number; }) => Promise<A>, ) => Effect.acquireUseRelease( Effect.promise(() => makeHarness({ nativeBatch: true, maxBoundParameters: 8 })), (harness) => Effect.promise(() => run(harness)), ({ close }) => Effect.promise(close), ); const seedTenants = async (orm: TablePolicyQuery) => { const seed = withQueryContext(orm, makeContext(["tenant-a", "tenant-b"], "seed")); await seed.createMany("authors", [ { id: "author-a", tenantId: "tenant-a", name: "Ada", }, { id: "author-b", tenantId: "tenant-b", name: "Bert", }, ]); await seed.createMany("posts", [ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", }, { id: "post-b-1", tenantId: "tenant-b", authorId: "author-b", title: "B One", }, ]); await seed.createMany("comments", [ { id: "comment-a-1", tenantId: "tenant-a", postId: "post-a-1", body: "A comment", }, { id: "comment-b-1", tenantId: "tenant-b", postId: "post-b-1", body: "B comment", }, ]); }; describe("FumaDB table policies", () => { it.effect( "filters reads, joins, counts, updates, deletes, and upserts through public query APIs", () => useHarness(async (orm) => { await seedTenants(orm); const tenantAContext = makeContext(["tenant-a"], "tenant-a"); const tenantA = withQueryContext(orm, tenantAContext); const allTenants = withQueryContext(orm, makeContext(["tenant-a", "tenant-b"], "all")); await expect(tenantA.count("posts")).resolves.toBe(2); await expect( tenantA.findMany("authors", { orderBy: ["id", "asc"], join: (builder) => builder.posts({ orderBy: ["id", "asc"], join: (builder) => builder.comments({ orderBy: ["id", "asc"] }), }), }), ).resolves.toEqual([ { id: "author-a", tenantId: "tenant-a", name: "Ada", posts: [ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", comments: [ { id: "comment-a-1", tenantId: "tenant-a", postId: "post-a-1", body: "A comment", }, ], }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", comments: [], }, ], }, ]); await expect( tenantA.findMany("posts", { orderBy: ["id", "asc"], join: (builder) => builder.author({ select: ["id", "tenantId"], }), }), ).resolves.toEqual([ { id: "post-a-1", tenantId: "tenant-a", authorId: "author-a", title: "A One", author: { id: "author-a", tenantId: "tenant-a", }, }, { id: "post-a-2", tenantId: "tenant-a", authorId: "author-a", title: "A Two", author: { id: "author-a", tenantId: "tenant-a", }, }, ]); await tenantA.updateMany("posts", { set: { title: "tenant-a-updated", }, }); await expect( allTenants.findMany("posts", { select: ["id", "title"], orderBy: ["id", "asc"], }), ).resolves.toEqual([ { id: "post-a-1", title: "tenant-a-updated", }, { id: "post-a-2", title: "tenant-a-updated", }, { id: "post-b-1", title: "B One", }, ]); await tenantA.deleteMany("comments", {}); await expect( allTenants.findMany("comments", { select: ["id", "tenantId"], orderBy: ["id", "asc"], }), ).resolves.toEqual([ { id: "comment-b-1", tenantId: "tenant-b", }, ]); await tenantA.upsert("posts", { where: (builder) => builder("id", "=", "post-a-2"), update: { title: "tenant-a-upserted", }, create: { id: "post-a-created-if-missing", tenantId: "tenant-a", authorId: "author-a", title: "not used", }, }); await tenantA.upsert("posts", { where: (builder) => builder("id", "=", "post-a-3"), update: { title: "not used", }, create: { id: "post-a-3", tenantId: "tenant-a", authorId: "author-a", title: "A Three", }, }); await tenantA.upsertMany("posts", { target: ["id"],