From 210a1b99bb10868419150d3579357154e3d9f4b5 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Fri, 5 Dec 2025 18:43:13 -0800 Subject: [PATCH 1/4] Update tests --- .../server/rowLevelSecurity.test.ts | 300 +++++++++++++----- 1 file changed, 225 insertions(+), 75 deletions(-) diff --git a/packages/convex-helpers/server/rowLevelSecurity.test.ts b/packages/convex-helpers/server/rowLevelSecurity.test.ts index 03178113..712f284b 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.test.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.test.ts @@ -38,7 +38,7 @@ const schema = defineSchema({ type DataModel = DataModelFromSchemaDefinition; type DatabaseWriter = GenericDatabaseWriter; -const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { +const withRLSImplicit = async (ctx: { db: DatabaseWriter; auth: Auth }) => { const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; if (!tokenIdentifier) throw new Error("Unauthenticated"); return { @@ -54,57 +54,147 @@ const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { }; }; +const withRLSExplicit = async (ctx: { db: DatabaseWriter; auth: Auth }) => { + const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; + if (!tokenIdentifier) throw new Error("Unauthenticated"); + return { + ...ctx, + db: wrapDatabaseWriter({ tokenIdentifier }, ctx.db, { + notes: { + read: async ({ tokenIdentifier }, doc) => { + // @ts-expect-error - testing new explicit table name API + const author = (await ctx.db.get("users", doc.userId)) as { + tokenIdentifier: string; + } | null; + return tokenIdentifier === author?.tokenIdentifier; + }, + }, + }), + }; +}; + describe("row level security", () => { - test("can only read own notes", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - const aId = await ctx.db.insert("users", { tokenIdentifier: "Person A" }); - const bId = await ctx.db.insert("users", { tokenIdentifier: "Person B" }); - await ctx.db.insert("notes", { - note: "Hello from Person A", - userId: aId, + describe("can only read own notes", () => { + test("implicit table names", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + const aId = await ctx.db.insert("users", { + tokenIdentifier: "Person A", + }); + const bId = await ctx.db.insert("users", { + tokenIdentifier: "Person B", + }); + await ctx.db.insert("notes", { + note: "Hello from Person A", + userId: aId, + }); + await ctx.db.insert("notes", { + note: "Hello from Person B", + userId: bId, + }); }); - await ctx.db.insert("notes", { - note: "Hello from Person B", - userId: bId, + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asB = t.withIdentity({ tokenIdentifier: "Person B" }); + const notesA = await asA.run(async (ctx) => { + const rls = await withRLSImplicit(ctx); + return await rls.db.query("notes").collect(); }); + expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); + + const notesB = await asB.run(async (ctx) => { + const rls = await withRLSImplicit(ctx); + return await rls.db.query("notes").collect(); + }); + expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); }); - const asA = t.withIdentity({ tokenIdentifier: "Person A" }); - const asB = t.withIdentity({ tokenIdentifier: "Person B" }); - const notesA = await asA.run(async (ctx) => { - const rls = await withRLS(ctx); - return await rls.db.query("notes").collect(); - }); - expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); - const notesB = await asB.run(async (ctx) => { - const rls = await withRLS(ctx); - return await rls.db.query("notes").collect(); + test("explicit table names", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + const aId = await ctx.db.insert("users", { + tokenIdentifier: "Person A", + }); + const bId = await ctx.db.insert("users", { + tokenIdentifier: "Person B", + }); + await ctx.db.insert("notes", { + note: "Hello from Person A", + userId: aId, + }); + await ctx.db.insert("notes", { + note: "Hello from Person B", + userId: bId, + }); + }); + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asB = t.withIdentity({ tokenIdentifier: "Person B" }); + const notesA = await asA.run(async (ctx) => { + const rls = await withRLSExplicit(ctx); + return await rls.db.query("notes").collect(); + }); + expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); + + const notesB = await asB.run(async (ctx) => { + const rls = await withRLSExplicit(ctx); + return await rls.db.query("notes").collect(); + }); + expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); }); - expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); }); - test("cannot delete someone else's note", async () => { - const t = convexTest(schema, modules); - const noteId = await t.run(async (ctx) => { - const aId = await ctx.db.insert("users", { tokenIdentifier: "Person A" }); - await ctx.db.insert("users", { tokenIdentifier: "Person B" }); - return ctx.db.insert("notes", { - note: "Hello from Person A", - userId: aId, + describe("cannot delete someone else's note", () => { + test("implicit table names", async () => { + const t = convexTest(schema, modules); + const noteId = await t.run(async (ctx) => { + const aId = await ctx.db.insert("users", { + tokenIdentifier: "Person A", + }); + await ctx.db.insert("users", { tokenIdentifier: "Person B" }); + return ctx.db.insert("notes", { + note: "Hello from Person A", + userId: aId, + }); }); - }); - const asA = t.withIdentity({ tokenIdentifier: "Person A" }); - const asB = t.withIdentity({ tokenIdentifier: "Person B" }); - await expect(() => - asB.run(async (ctx) => { - const rls = await withRLS(ctx); + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asB = t.withIdentity({ tokenIdentifier: "Person B" }); + await expect(() => + asB.run(async (ctx) => { + const rls = await withRLSImplicit(ctx); + return rls.db.delete(noteId); + }), + ).rejects.toThrow(/no read access/); + await asA.run(async (ctx) => { + const rls = await withRLSImplicit(ctx); return rls.db.delete(noteId); - }), - ).rejects.toThrow(/no read access/); - await asA.run(async (ctx) => { - const rls = await withRLS(ctx); - return rls.db.delete(noteId); + }); + }); + + test("explicit table names", async () => { + const t = convexTest(schema, modules); + const noteId = await t.run(async (ctx) => { + const aId = await ctx.db.insert("users", { + tokenIdentifier: "Person A", + }); + await ctx.db.insert("users", { tokenIdentifier: "Person B" }); + return ctx.db.insert("notes", { + note: "Hello from Person A", + userId: aId, + }); + }); + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asB = t.withIdentity({ tokenIdentifier: "Person B" }); + await expect(() => + asB.run(async (ctx) => { + const rls = await withRLSExplicit(ctx); + // @ts-expect-error - testing new explicit table name API + return rls.db.delete("notes", noteId); + }), + ).rejects.toThrow(/no read access/); + await asA.run(async (ctx) => { + const rls = await withRLSExplicit(ctx); + // @ts-expect-error - testing new explicit table name API + return rls.db.delete("notes", noteId); + }); }); }); @@ -244,39 +334,72 @@ describe("row level security", () => { ).rejects.toThrow(/insert access not allowed/); }); - test("default deny policy blocks modifications to tables without rules", async () => { - const t = convexTest(schema, modules); - const docId = await t.run(async (ctx) => { - await ctx.db.insert("users", { tokenIdentifier: "Person A" }); - return ctx.db.insert("publicData", { content: "Initial content" }); - }); + describe("default deny policy blocks modifications to tables without rules", () => { + test("implicit table names", async () => { + const t = convexTest(schema, modules); + const docId = await t.run(async (ctx) => { + await ctx.db.insert("users", { tokenIdentifier: "Person A" }); + return ctx.db.insert("publicData", { content: "Initial content" }); + }); - const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); - // Test with default allow - await asA.run(async (ctx) => { - const tokenIdentifier = (await ctx.auth.getUserIdentity()) - ?.tokenIdentifier; - if (!tokenIdentifier) throw new Error("Unauthenticated"); + // Test with default allow + await asA.run(async (ctx) => { + const tokenIdentifier = (await ctx.auth.getUserIdentity()) + ?.tokenIdentifier; + if (!tokenIdentifier) throw new Error("Unauthenticated"); - const db = wrapDatabaseWriter( - { tokenIdentifier }, - ctx.db, - { - publicData: { - read: async () => true, // Allow reads + const db = wrapDatabaseWriter( + { tokenIdentifier }, + ctx.db, + { + publicData: { + read: async () => true, // Allow reads + }, }, - }, - { defaultPolicy: "allow" }, - ); + { defaultPolicy: "allow" }, + ); + + // Should be able to modify (no modify rule, default allow) + await db.patch(docId, { content: "Modified content" }); + }); - // Should be able to modify (no modify rule, default allow) - await db.patch(docId, { content: "Modified content" }); + // Test with default deny + await expect(() => + asA.run(async (ctx) => { + const tokenIdentifier = (await ctx.auth.getUserIdentity()) + ?.tokenIdentifier; + if (!tokenIdentifier) throw new Error("Unauthenticated"); + + const db = wrapDatabaseWriter( + { tokenIdentifier }, + ctx.db, + { + publicData: { + read: async () => true, // Allow reads but no modify rule + }, + }, + { defaultPolicy: "deny" }, + ); + + // Should NOT be able to modify (no modify rule, default deny) + await db.patch(docId, { content: "Blocked modification" }); + }), + ).rejects.toThrow(/write access not allowed/); }); - // Test with default deny - await expect(() => - asA.run(async (ctx) => { + test("explicit table names", async () => { + const t = convexTest(schema, modules); + const docId = await t.run(async (ctx) => { + await ctx.db.insert("users", { tokenIdentifier: "Person A" }); + return ctx.db.insert("publicData", { content: "Initial content" }); + }); + + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + + // Test with default allow + await asA.run(async (ctx) => { const tokenIdentifier = (await ctx.auth.getUserIdentity()) ?.tokenIdentifier; if (!tokenIdentifier) throw new Error("Unauthenticated"); @@ -286,16 +409,43 @@ describe("row level security", () => { ctx.db, { publicData: { - read: async () => true, // Allow reads but no modify rule + read: async () => true, // Allow reads }, }, - { defaultPolicy: "deny" }, + { defaultPolicy: "allow" }, ); - // Should NOT be able to modify (no modify rule, default deny) - await db.patch(docId, { content: "Blocked modification" }); - }), - ).rejects.toThrow(/write access not allowed/); + // Should be able to modify (no modify rule, default allow) + // @ts-expect-error - testing new explicit table name API + await db.patch("publicData", docId, { content: "Modified content" }); + }); + + // Test with default deny + await expect(() => + asA.run(async (ctx) => { + const tokenIdentifier = (await ctx.auth.getUserIdentity()) + ?.tokenIdentifier; + if (!tokenIdentifier) throw new Error("Unauthenticated"); + + const db = wrapDatabaseWriter( + { tokenIdentifier }, + ctx.db, + { + publicData: { + read: async () => true, // Allow reads but no modify rule + }, + }, + { defaultPolicy: "deny" }, + ); + + // Should NOT be able to modify (no modify rule, default deny) + // @ts-expect-error - testing new explicit table name API + await db.patch("publicData", docId, { + content: "Blocked modification", + }); + }), + ).rejects.toThrow(/write access not allowed/); + }); }); }); @@ -303,7 +453,7 @@ const mutation = mutationGeneric as MutationBuilder; const mutationWithRLS = customMutation( mutation, - customCtx((ctx) => withRLS(ctx)), + customCtx((ctx) => withRLSImplicit(ctx)), ); customMutation( From f4bd4a05faa4960fcd00522ed9694085a5452a1f Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Fri, 5 Dec 2025 18:47:09 -0800 Subject: [PATCH 2/4] Simplify --- .../server/rowLevelSecurity.test.ts | 114 +++++------------- 1 file changed, 28 insertions(+), 86 deletions(-) diff --git a/packages/convex-helpers/server/rowLevelSecurity.test.ts b/packages/convex-helpers/server/rowLevelSecurity.test.ts index 712f284b..917a4434 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.test.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.test.ts @@ -38,7 +38,7 @@ const schema = defineSchema({ type DataModel = DataModelFromSchemaDefinition; type DatabaseWriter = GenericDatabaseWriter; -const withRLSImplicit = async (ctx: { db: DatabaseWriter; auth: Auth }) => { +const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; if (!tokenIdentifier) throw new Error("Unauthenticated"); return { @@ -54,92 +54,34 @@ const withRLSImplicit = async (ctx: { db: DatabaseWriter; auth: Auth }) => { }; }; -const withRLSExplicit = async (ctx: { db: DatabaseWriter; auth: Auth }) => { - const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; - if (!tokenIdentifier) throw new Error("Unauthenticated"); - return { - ...ctx, - db: wrapDatabaseWriter({ tokenIdentifier }, ctx.db, { - notes: { - read: async ({ tokenIdentifier }, doc) => { - // @ts-expect-error - testing new explicit table name API - const author = (await ctx.db.get("users", doc.userId)) as { - tokenIdentifier: string; - } | null; - return tokenIdentifier === author?.tokenIdentifier; - }, - }, - }), - }; -}; - describe("row level security", () => { - describe("can only read own notes", () => { - test("implicit table names", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - const aId = await ctx.db.insert("users", { - tokenIdentifier: "Person A", - }); - const bId = await ctx.db.insert("users", { - tokenIdentifier: "Person B", - }); - await ctx.db.insert("notes", { - note: "Hello from Person A", - userId: aId, - }); - await ctx.db.insert("notes", { - note: "Hello from Person B", - userId: bId, - }); - }); - const asA = t.withIdentity({ tokenIdentifier: "Person A" }); - const asB = t.withIdentity({ tokenIdentifier: "Person B" }); - const notesA = await asA.run(async (ctx) => { - const rls = await withRLSImplicit(ctx); - return await rls.db.query("notes").collect(); + test("can only read own notes", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + const aId = await ctx.db.insert("users", { tokenIdentifier: "Person A" }); + const bId = await ctx.db.insert("users", { tokenIdentifier: "Person B" }); + await ctx.db.insert("notes", { + note: "Hello from Person A", + userId: aId, }); - expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); - - const notesB = await asB.run(async (ctx) => { - const rls = await withRLSImplicit(ctx); - return await rls.db.query("notes").collect(); + await ctx.db.insert("notes", { + note: "Hello from Person B", + userId: bId, }); - expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); }); + const asA = t.withIdentity({ tokenIdentifier: "Person A" }); + const asB = t.withIdentity({ tokenIdentifier: "Person B" }); + const notesA = await asA.run(async (ctx) => { + const rls = await withRLS(ctx); + return await rls.db.query("notes").collect(); + }); + expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); - test("explicit table names", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - const aId = await ctx.db.insert("users", { - tokenIdentifier: "Person A", - }); - const bId = await ctx.db.insert("users", { - tokenIdentifier: "Person B", - }); - await ctx.db.insert("notes", { - note: "Hello from Person A", - userId: aId, - }); - await ctx.db.insert("notes", { - note: "Hello from Person B", - userId: bId, - }); - }); - const asA = t.withIdentity({ tokenIdentifier: "Person A" }); - const asB = t.withIdentity({ tokenIdentifier: "Person B" }); - const notesA = await asA.run(async (ctx) => { - const rls = await withRLSExplicit(ctx); - return await rls.db.query("notes").collect(); - }); - expect(notesA).toMatchObject([{ note: "Hello from Person A" }]); - - const notesB = await asB.run(async (ctx) => { - const rls = await withRLSExplicit(ctx); - return await rls.db.query("notes").collect(); - }); - expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); + const notesB = await asB.run(async (ctx) => { + const rls = await withRLS(ctx); + return await rls.db.query("notes").collect(); }); + expect(notesB).toMatchObject([{ note: "Hello from Person B" }]); }); describe("cannot delete someone else's note", () => { @@ -159,12 +101,12 @@ describe("row level security", () => { const asB = t.withIdentity({ tokenIdentifier: "Person B" }); await expect(() => asB.run(async (ctx) => { - const rls = await withRLSImplicit(ctx); + const rls = await withRLS(ctx); return rls.db.delete(noteId); }), ).rejects.toThrow(/no read access/); await asA.run(async (ctx) => { - const rls = await withRLSImplicit(ctx); + const rls = await withRLS(ctx); return rls.db.delete(noteId); }); }); @@ -185,13 +127,13 @@ describe("row level security", () => { const asB = t.withIdentity({ tokenIdentifier: "Person B" }); await expect(() => asB.run(async (ctx) => { - const rls = await withRLSExplicit(ctx); + const rls = await withRLS(ctx); // @ts-expect-error - testing new explicit table name API return rls.db.delete("notes", noteId); }), ).rejects.toThrow(/no read access/); await asA.run(async (ctx) => { - const rls = await withRLSExplicit(ctx); + const rls = await withRLS(ctx); // @ts-expect-error - testing new explicit table name API return rls.db.delete("notes", noteId); }); @@ -453,7 +395,7 @@ const mutation = mutationGeneric as MutationBuilder; const mutationWithRLS = customMutation( mutation, - customCtx((ctx) => withRLSImplicit(ctx)), + customCtx((ctx) => withRLS(ctx)), ); customMutation( From 64527207e541752cccf421b2027f4b94845d7820 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Fri, 5 Dec 2025 19:10:05 -0800 Subject: [PATCH 3/4] Fix implementations --- .../convex-helpers/server/rowLevelSecurity.ts | 106 ++++++++++++++---- 1 file changed, 85 insertions(+), 21 deletions(-) diff --git a/packages/convex-helpers/server/rowLevelSecurity.ts b/packages/convex-helpers/server/rowLevelSecurity.ts index 4743fb88..4740ea69 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.ts @@ -11,6 +11,7 @@ import type { GenericQueryCtx, QueryInitializer, TableNamesInDataModel, + WithOptionalSystemFields, WithoutSystemFields, } from "convex/server"; import type { GenericId } from "convex/values"; @@ -235,12 +236,18 @@ class WrapReader return await this.rules[tableName]!.read!(this.ctx, doc); } - async get( + get>( + table: NonUnion, id: GenericId, - ): Promise | null> { + ): Promise | null>; + get>( + id: GenericId, + ): Promise | null>; + async get(arg0: any, arg1?: any): Promise { + const [tableName, id]: [string | null, GenericId] = + arg1 !== undefined ? [arg0, arg1] : [this.tableName(arg0), arg0]; const doc = await this.db.get(id); if (doc) { - const tableName = this.tableName(id); if (tableName && !(await this.predicate(tableName, doc))) { return null; } @@ -265,6 +272,7 @@ class WrapWriter db: GenericDatabaseWriter; system: GenericDatabaseWriter["system"]; reader: GenericDatabaseReader; + // reader: WrapReader; rules: Rules; config: RLSConfig; @@ -291,12 +299,14 @@ class WrapWriter this.rules = rules; this.config = config ?? { defaultPolicy: "allow" }; } + normalizeId>( tableName: TableName, id: string, ): GenericId | null { return this.db.normalizeId(tableName, id); } + async insert( table: TableName, value: any, @@ -311,6 +321,7 @@ class WrapWriter } return await this.db.insert(table, value); } + tableName( id: GenericId, ): TableName | null { @@ -321,16 +332,22 @@ class WrapWriter } return null; } - async checkAuth(id: GenericId) { + + async checkAuth( + tableNameArg: string | null, + id: GenericId, + ) { // Note all writes already do a `db.get` internally, so this isn't // an extra read; it's just populating the cache earlier. // Since we call `this.get`, read access controls apply and this may return // null even if the document exists. - const doc = await this.get(id); + const doc = tableNameArg + ? await this.get(tableNameArg as any, id) + : await this.get(id); if (doc === null) { throw new Error("no read access or doc does not exist"); } - const tableName = this.tableName(id); + const tableName = tableNameArg ?? this.tableName(id); if (tableName === null) { return; } @@ -338,28 +355,75 @@ class WrapWriter throw new Error("write access not allowed"); } } - async patch( + + patch>( + table: NonUnion, id: GenericId, - value: Partial, - ): Promise { - await this.checkAuth(id); - return await this.db.patch(id, value); + value: Partial>, + ): Promise; + patch>( + id: GenericId, + value: Partial>, + ): Promise; + async patch(arg0: any, arg1: any, arg2?: any): Promise { + const [tableName, id, value]: [string | null, GenericId, any] = + arg2 !== undefined ? [arg0, arg1, arg2] : [null, arg0, arg1]; + await this.checkAuth(tableName, id); + return tableName + ? // @ts-expect-error -- patch supports 3 args since convex@1.25.4 + this.db.patch(tableName, id, value) + : this.db.patch(id, value); } - async replace( + + replace>( + table: NonUnion, id: GenericId, - value: any, - ): Promise { - await this.checkAuth(id); - return await this.db.replace(id, value); + value: WithOptionalSystemFields>, + ): Promise; + replace>( + id: GenericId, + value: WithOptionalSystemFields>, + ): Promise; + async replace(arg0: any, arg1: any, arg2?: any): Promise { + const [tableName, id, value]: [string | null, GenericId, any] = + arg2 !== undefined ? [arg0, arg1, arg2] : [null, arg0, arg1]; + await this.checkAuth(tableName, id); + return tableName + ? // @ts-expect-error -- replace supports 3 args since convex@1.25.4 + this.db.replace(tableName, id, value) + : this.db.replace(id, value); } - async delete(id: GenericId): Promise { - await this.checkAuth(id); - return await this.db.delete(id); + + delete>( + table: NonUnion, + id: GenericId, + ): Promise; + delete(id: GenericId>): Promise; + async delete(arg0: any, arg1?: any): Promise { + const [tableName, id]: [string | null, GenericId] = + arg1 !== undefined ? [arg0, arg1] : [null, arg0]; + await this.checkAuth(tableName, id); + + return tableName + ? // @ts-expect-error -- delete supports 2 args since convex@1.25.4 + this.db.delete(tableName, id) + : this.db.delete(id); } - get(id: GenericId): Promise { - return this.reader.get(id); + + get>( + table: NonUnion, + id: GenericId, + ): Promise | null>; + get>( + id: GenericId, + ): Promise | null>; + get(arg0: any, arg1?: any): Promise { + // @ts-expect-error -- get supports 2 args since convex@1.25.4 + return this.reader.get(arg0, arg1); } query(tableName: TableName): QueryInitializer { return this.reader.query(tableName); } } + +type NonUnion = T extends never ? never : T; From 6896b08039b6e95eee65c4c83b6acbe849aa6dd8 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Fri, 5 Dec 2025 19:14:06 -0800 Subject: [PATCH 4/4] fix --- packages/convex-helpers/server/rowLevelSecurity.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/convex-helpers/server/rowLevelSecurity.ts b/packages/convex-helpers/server/rowLevelSecurity.ts index 4740ea69..73e93812 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.ts @@ -272,7 +272,6 @@ class WrapWriter db: GenericDatabaseWriter; system: GenericDatabaseWriter["system"]; reader: GenericDatabaseReader; - // reader: WrapReader; rules: Rules; config: RLSConfig;