From ff0c528bbae075b7c0422c668d5e171a4be08773 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 13:51:43 -0800 Subject: [PATCH 1/7] Update `@convex-dev/eslint-plugin` to 1.1.0 --- convex/counter.ts | 2 +- convex/migrationsExample.ts | 6 +- convex/presence.ts | 4 +- convex/relationshipsExample.ts | 14 +-- convex/rlsExample.ts | 2 +- convex/sessionsExample.ts | 6 +- convex/triggersExample.ts | 16 +-- package-lock.json | 105 ++++++++---------- package.json | 2 +- packages/convex-helpers/server/filter.test.ts | 2 +- packages/convex-helpers/server/migrations.ts | 6 +- .../convex-helpers/server/pagination.test.ts | 2 +- packages/convex-helpers/server/rateLimit.ts | 4 +- packages/convex-helpers/server/retries.ts | 2 +- .../server/rowLevelSecurity.test.ts | 10 +- packages/convex-helpers/server/table.test.ts | 6 +- .../convex-helpers/server/triggers.test.ts | 1 + .../convex-helpers/server/validators.test.ts | 4 +- packages/convex-helpers/server/zod3.test.ts | 2 +- .../convex-helpers/server/zod4.zod3.test.ts | 2 +- 20 files changed, 95 insertions(+), 103 deletions(-) diff --git a/convex/counter.ts b/convex/counter.ts index 821e15a5..63a88fc5 100644 --- a/convex/counter.ts +++ b/convex/counter.ts @@ -72,7 +72,7 @@ export const incrementCounter = mutation({ }); } else { counterDoc.counter += increment; - await ctx.db.replace(counterDoc._id, counterDoc); + await ctx.db.replace("counter_table", counterDoc._id, counterDoc); } }, }); diff --git a/convex/migrationsExample.ts b/convex/migrationsExample.ts index 40c7b96a..436cfe9a 100644 --- a/convex/migrationsExample.ts +++ b/convex/migrationsExample.ts @@ -30,10 +30,10 @@ export const increment = migration({ export const cleanUpBrokenRefs = migration({ table: "join_table_example", migrateOne: async (ctx, doc) => { - const user = await ctx.db.get(doc.userId); - const presence = await ctx.db.get(doc.presenceId); + const user = await ctx.db.get("users", doc.userId); + const presence = await ctx.db.get("presence", doc.presenceId); if (!user || !presence) { - await ctx.db.delete(doc._id); + await ctx.db.delete("join_table_example", doc._id); } }, }); diff --git a/convex/presence.ts b/convex/presence.ts index 1457fff5..402a669e 100644 --- a/convex/presence.ts +++ b/convex/presence.ts @@ -34,7 +34,7 @@ export const update = mutation({ .withIndex("user_room", (q) => q.eq("user", user).eq("room", room)) .unique(); if (existing) { - await db.patch(existing._id, { data, updated: Date.now() }); + await db.patch("presence", existing._id, { data, updated: Date.now() }); } else { await db.insert("presence", { user, @@ -64,7 +64,7 @@ export const heartbeat = mutation({ .withIndex("user_room", (q) => q.eq("user", user).eq("room", room)) .unique(); if (existing) { - await db.patch(existing._id, { updated: Date.now() }); + await db.patch("presence", existing._id, { updated: Date.now() }); } }, }); diff --git a/convex/relationshipsExample.ts b/convex/relationshipsExample.ts index 8f3797dd..09b60315 100644 --- a/convex/relationshipsExample.ts +++ b/convex/relationshipsExample.ts @@ -97,7 +97,7 @@ export const relationshipTest = mutation({ (await getAllOrThrow(ctx.db, userIds)).map(assertNotNull); // Now let's delete one and see if everything behaves as we expect - await ctx.db.delete(user2._id); + await ctx.db.delete("users", user2._id); assertNull(await getOneFrom(ctx.db, "users", "tokenIdentifier", "test456")); assertHasNull(await getAll(ctx.db, userIds)); try { @@ -106,7 +106,7 @@ export const relationshipTest = mutation({ console.log("Successfully caught missing userId"); } - await ctx.db.delete(presenceId2); + await ctx.db.delete("presence", presenceId2); assertHasNull( await getManyVia( ctx.db, @@ -127,10 +127,10 @@ export const relationshipTest = mutation({ } catch { console.log("Successfully caught missing presenceId"); } - await asyncMap(edges, (edge) => ctx.db.delete(edge._id)); + await asyncMap(edges, (edge) => ctx.db.delete("join_table_example", edge._id)); await asyncMap( await getManyFrom(ctx.db, "join_table_example", "by_userId", user2._id), - (edge) => ctx.db.delete(edge._id), + (edge) => ctx.db.delete("join_table_example", edge._id), ); // Testing custom index names @@ -144,7 +144,7 @@ export const relationshipTest = mutation({ (await getManyFrom(ctx.db, "presence", "user_room", userId, "user")).map( assertNotNull, ); - await ctx.db.delete(presenceId); + await ctx.db.delete("presence", presenceId); const file = await ctx.db.system.query("_storage").first(); if (!file) { @@ -180,8 +180,8 @@ export const relationshipTest = mutation({ "userId", ) ).map(assertNotNull); - await ctx.db.delete(userId); - await ctx.db.delete(edgeId); + await ctx.db.delete("users", userId); + await ctx.db.delete("join_storage_example", edgeId); return true; }, diff --git a/convex/rlsExample.ts b/convex/rlsExample.ts index 9202165f..e0f4efd3 100644 --- a/convex/rlsExample.ts +++ b/convex/rlsExample.ts @@ -78,6 +78,6 @@ export const updateName = mutationWithRLS({ // will prevent you from modifying other users. args: { name: v.string(), userId: v.id("users") }, handler: async (ctx, { name, userId }) => { - await ctx.db.patch(userId, { name }); + await ctx.db.patch("users", userId, { name }); }, }); diff --git a/convex/sessionsExample.ts b/convex/sessionsExample.ts index 1915c0cc..8c0b2aaf 100644 --- a/convex/sessionsExample.ts +++ b/convex/sessionsExample.ts @@ -131,7 +131,7 @@ export const logIn = mutationWithSession({ .withIndex("user_room", (q) => q.eq("user", ctx.sessionId)) .collect(); await Promise.all( - presenceDocs.map((doc) => ctx.db.patch(doc._id, { user: args.new })), + presenceDocs.map((doc) => ctx.db.patch("presence", doc._id, { user: args.new })), ); }, }); @@ -144,7 +144,7 @@ export const logOut = mutationWithSession({ .query("presence") .withIndex("user_room", (q) => q.eq("user", ctx.sessionId)) .collect(); - await Promise.all(presenceDocs.map((doc) => ctx.db.delete(doc._id))); + await Promise.all(presenceDocs.map((doc) => ctx.db.delete("presence", doc._id))); }, }); @@ -183,7 +183,7 @@ export const joinRoom = mutationWithSession({ ) .first(); if (existing) { - await ctx.db.patch(existing._id, { updated: Date.now() }); + await ctx.db.patch("presence", existing._id, { updated: Date.now() }); } else { await ctx.db.insert("presence", { user: ctx.sessionId, diff --git a/convex/triggersExample.ts b/convex/triggersExample.ts index cbb0a099..76657bed 100644 --- a/convex/triggersExample.ts +++ b/convex/triggersExample.ts @@ -25,7 +25,7 @@ triggers.register("counter_table", async (ctx, change) => { // Round up to the nearest multiple of 10, one at a time. // This demonstrates that triggers can trigger themselves. console.log("Incrementing counter to", change.newDoc.counter + 1); - await ctx.db.patch(change.newDoc._id, { + await ctx.db.patch("counter_table", change.newDoc._id, { counter: change.newDoc.counter + 1, }); } @@ -55,13 +55,13 @@ triggers.register("counter_table", async (ctx, change) => { } const sumDoc = (await ctx.db.query("sum_table").first())!; if (change.operation === "insert") { - await ctx.db.patch(sumDoc._id, { sum: sumDoc.sum + change.newDoc.counter }); + await ctx.db.patch("sum_table", sumDoc._id, { sum: sumDoc.sum + change.newDoc.counter }); } else if (change.operation === "update") { - await ctx.db.patch(sumDoc._id, { + await ctx.db.patch("sum_table", sumDoc._id, { sum: sumDoc.sum + change.newDoc.counter - change.oldDoc.counter, }); } else if (change.operation === "delete") { - await ctx.db.patch(sumDoc._id, { sum: sumDoc.sum - change.oldDoc.counter }); + await ctx.db.patch("sum_table", sumDoc._id, { sum: sumDoc.sum - change.oldDoc.counter }); } }); @@ -88,9 +88,9 @@ export const incrementCounterRace = mutation({ throw new Error("No counters"); } await Promise.all([ - db.patch(firstCounter._id, { counter: firstCounter.counter + 1 }), - db.patch(firstCounter._id, { counter: firstCounter.counter + 2 }), - db.patch(firstCounter._id, { counter: firstCounter.counter + 3 }), + db.patch("counter_table", firstCounter._id, { counter: firstCounter.counter + 1 }), + db.patch("counter_table", firstCounter._id, { counter: firstCounter.counter + 2 }), + db.patch("counter_table", firstCounter._id, { counter: firstCounter.counter + 3 }), ]); }, }); @@ -140,6 +140,6 @@ export const updateName = mutationWithRLS({ handler: async (ctx, { name, userId }) => { // The extra type from above still comes through console.log(ctx.foo); - await ctx.db.patch(userId, { name }); + await ctx.db.patch("users", userId, { name }); }, }); diff --git a/package-lock.json b/package-lock.json index d8972d48..3ea2a951 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@arethetypeswrong/cli": "0.18.2", - "@convex-dev/eslint-plugin": "1.0.0", + "@convex-dev/eslint-plugin": "^1.1.0", "@edge-runtime/vm": "5.0.0", "@eslint/js": "9.39.1", "@redocly/cli": "2.11.1", @@ -550,24 +550,27 @@ } }, "node_modules/@convex-dev/eslint-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@convex-dev/eslint-plugin/-/eslint-plugin-1.0.0.tgz", - "integrity": "sha512-ublJRBKcLCioNaf1ylkCHD2KzAqWE2RIQ6DA/UgXAXQW5qg4vZSWY8wy+EK11yJkSSxcGfFXDWaE1+cHaWJvNA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@convex-dev/eslint-plugin/-/eslint-plugin-1.1.1.tgz", + "integrity": "sha512-4NsTWNJJLPbti10LZsV1/7UkbaMPFxNz5Ekd3yW5bDkaoU1I0b4TJxk0V+ShbNFTJ2fSqTxm+iGy9XSNCmAoVA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@typescript-eslint/utils": "~8.38.0" + "@typescript-eslint/utils": "~8.49.0" + }, + "peerDependencies": { + "convex": "^1.31.0" } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", + "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", + "@typescript-eslint/tsconfig-utils": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "engines": { @@ -578,18 +581,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz", + "integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -600,9 +603,9 @@ } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", + "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", "dev": true, "license": "MIT", "engines": { @@ -613,13 +616,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", + "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", "dev": true, "license": "MIT", "engines": { @@ -631,21 +634,20 @@ } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", + "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/project-service": "8.49.0", + "@typescript-eslint/tsconfig-utils": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -656,20 +658,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", - "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz", + "integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0" + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -680,17 +682,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@convex-dev/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", + "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -727,21 +729,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@convex-dev/eslint-plugin/node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", diff --git a/package.json b/package.json index 82e5abc1..93da7fc4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ }, "devDependencies": { "@arethetypeswrong/cli": "0.18.2", - "@convex-dev/eslint-plugin": "1.0.0", + "@convex-dev/eslint-plugin": "^1.1.0", "@edge-runtime/vm": "5.0.0", "@eslint/js": "9.39.1", "@redocly/cli": "2.11.1", diff --git a/packages/convex-helpers/server/filter.test.ts b/packages/convex-helpers/server/filter.test.ts index cbd4eff0..e9c4bd33 100644 --- a/packages/convex-helpers/server/filter.test.ts +++ b/packages/convex-helpers/server/filter.test.ts @@ -46,7 +46,7 @@ test("filter", async () => { const withLookup = await t.run((ctx) => filter( ctx.db.query("tableB"), - async (c) => ((await ctx.db.get(c.tableAId))?.count ?? 0) > 5, + async (c) => ((await ctx.db.get("tableA", c.tableAId))?.count ?? 0) > 5, ).collect(), ); expect(withLookup).toMatchObject([ diff --git a/packages/convex-helpers/server/migrations.ts b/packages/convex-helpers/server/migrations.ts index 822a8be3..0ad24827 100644 --- a/packages/convex-helpers/server/migrations.ts +++ b/packages/convex-helpers/server/migrations.ts @@ -414,7 +414,7 @@ export function makeMigration< // 2. The migration is being resumed at a different cursor. // 3. There are two instances of the same migration racing. const worker = - state.workerId && (await ctx.db.system.get(state.workerId)); + state.workerId && (await ctx.db.system.get("_scheduled_functions", state.workerId)); if ( worker && (worker.state.kind === "pending" || @@ -631,7 +631,7 @@ export async function getStatus< docs.reverse().map(async (migration) => { const { workerId, isDone } = migration; if (isDone) return migration; - const worker = workerId && (await ctx.db.system.get(workerId)); + const worker = workerId && (await ctx.db.system.get("_scheduled_functions", workerId)); return { ...migration, workerStatus: worker?.state.kind, @@ -669,7 +669,7 @@ export async function cancelMigration( if (state.isDone) { return state; } - const worker = state.workerId && (await ctx.db.system.get(state.workerId)); + const worker = state.workerId && (await ctx.db.system.get("_scheduled_functions", state.workerId)); if ( worker && (worker.state.kind === "pending" || worker.state.kind === "inProgress") diff --git a/packages/convex-helpers/server/pagination.test.ts b/packages/convex-helpers/server/pagination.test.ts index d530da1f..67a58a56 100644 --- a/packages/convex-helpers/server/pagination.test.ts +++ b/packages/convex-helpers/server/pagination.test.ts @@ -150,7 +150,7 @@ describe("manual pagination", () => { // Delete the first doc and refresh the first page. // It should return only 2 documents because the second page has the third // document already. - await ctx.db.delete(page0[0]!._id as GenericId<"foo">); + await ctx.db.delete("foo", page0[0]!._id as GenericId<"foo">); const { page: page0Refreshed } = await getPage(ctx, { table: "foo", index: "abc", diff --git a/packages/convex-helpers/server/rateLimit.ts b/packages/convex-helpers/server/rateLimit.ts index 81f21491..1b5e5107 100644 --- a/packages/convex-helpers/server/rateLimit.ts +++ b/packages/convex-helpers/server/rateLimit.ts @@ -335,7 +335,7 @@ export async function rateLimit( const { ts, value } = status; const existing = await getExisting(ctx.db, args.name, args.key); if (existing) { - await ctx.db.patch(existing._id, { ts, value }); + await ctx.db.patch("rateLimits", existing._id, { ts, value }); } else { const { name, key } = args; await ctx.db.insert(RateLimitTable, { name, key, ts, value }); @@ -442,7 +442,7 @@ export async function resetRateLimit( ) { const existing = await getExisting(ctx.db, args.name, args.key); if (existing) { - await ctx.db.delete(existing._id); + await ctx.db.delete("rateLimits", existing._id); } } diff --git a/packages/convex-helpers/server/retries.ts b/packages/convex-helpers/server/retries.ts index 6b652549..dac95b2b 100644 --- a/packages/convex-helpers/server/retries.ts +++ b/packages/convex-helpers/server/retries.ts @@ -139,7 +139,7 @@ export function makeActionRetrier( makeFunctionReference<"action">(args.action), args.actionArgs, )); - const status = await ctx.db.system.get(job); + const status = await ctx.db.system.get("_scheduled_functions", job); if (!status) { // There is a chance a job will be deleted - after 7 days. // For now, we give up. In the future you could store information about diff --git a/packages/convex-helpers/server/rowLevelSecurity.test.ts b/packages/convex-helpers/server/rowLevelSecurity.test.ts index 786db74e..1cb09dc1 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.test.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.test.ts @@ -46,7 +46,7 @@ const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { db: wrapDatabaseWriter({ tokenIdentifier }, ctx.db, { notes: { read: async ({ tokenIdentifier }, doc) => { - const author = await ctx.db.get(doc.userId); + const author = await ctx.db.get("users", doc.userId); return tokenIdentifier === author?.tokenIdentifier; }, }, @@ -102,11 +102,13 @@ describe("row level security", () => { await expect(() => asB.run(async (ctx) => { const rls = await withRLS(ctx); + // eslint-disable-next-line @convex-dev/explicit-table-ids -- testing the implicit API return rls.db.delete(noteId); }), ).rejects.toThrow(/no read access/); await asA.run(async (ctx) => { const rls = await withRLS(ctx); + // eslint-disable-next-line @convex-dev/explicit-table-ids -- testing the implicit API return rls.db.delete(noteId); }); }); @@ -161,7 +163,7 @@ describe("row level security", () => { const db = wrapDatabaseReader({ tokenIdentifier }, ctx.db, { notes: { read: async ({ tokenIdentifier }, doc) => { - const author = await ctx.db.get(doc.userId); + const author = await ctx.db.get("users", doc.userId); return tokenIdentifier === author?.tokenIdentifier; }, }, @@ -205,7 +207,7 @@ describe("row level security", () => { { notes: { read: async ({ tokenIdentifier }, doc) => { - const author = await ctx.db.get(doc.userId); + const author = await ctx.db.get("users", doc.userId); return tokenIdentifier === author?.tokenIdentifier; }, }, @@ -302,6 +304,7 @@ describe("row level security", () => { ); // Should be able to modify (no modify rule, default allow) + // eslint-disable-next-line @convex-dev/explicit-table-ids -- testing the implicit API await db.patch(docId, { content: "Modified content" }); }); @@ -324,6 +327,7 @@ describe("row level security", () => { ); // Should NOT be able to modify (no modify rule, default deny) + // eslint-disable-next-line @convex-dev/explicit-table-ids -- testing the implicit API await db.patch(docId, { content: "Blocked modification" }); }), ).rejects.toThrow(/write access not allowed/); diff --git a/packages/convex-helpers/server/table.test.ts b/packages/convex-helpers/server/table.test.ts index 41dd5ff1..1c6aa732 100644 --- a/packages/convex-helpers/server/table.test.ts +++ b/packages/convex-helpers/server/table.test.ts @@ -61,7 +61,7 @@ export const allAtOnce = internalQuery({ export const get = internalQuery({ args: { id: Example._id }, handler: async (ctx, args) => { - return await ctx.db.get(args.id); + return await ctx.db.get("table_example", args.id); }, }); @@ -88,7 +88,7 @@ export const patch = internalMutation({ patch: v.object(partial(Example.withoutSystemFields)), }, handler: async (ctx, args) => { - await ctx.db.patch(args.id, args.patch); + await ctx.db.patch("table_example", args.id, args.patch); }, }); @@ -100,7 +100,7 @@ export const replace = internalMutation({ _id: Example._id, }, handler: async (ctx, args) => { - await ctx.db.replace(args._id, args); + await ctx.db.replace("table_example", args._id, args); }, }); diff --git a/packages/convex-helpers/server/triggers.test.ts b/packages/convex-helpers/server/triggers.test.ts index cb4e32d0..7a54bc3f 100644 --- a/packages/convex-helpers/server/triggers.test.ts +++ b/packages/convex-helpers/server/triggers.test.ts @@ -46,6 +46,7 @@ triggers.register("users", async (ctx, change) => { if (change.newDoc) { const fullName = `${change.newDoc.firstName} ${change.newDoc.lastName}`; if (change.newDoc.fullName !== fullName) { + // eslint-disable-next-line @convex-dev/explicit-table-ids -- Testing the old API await ctx.db.patch(change.id, { fullName }); } } diff --git a/packages/convex-helpers/server/validators.test.ts b/packages/convex-helpers/server/validators.test.ts index ce8707ee..7b4ebd00 100644 --- a/packages/convex-helpers/server/validators.test.ts +++ b/packages/convex-helpers/server/validators.test.ts @@ -156,13 +156,13 @@ const vv = typedV(schema); export const getSink = internalQuery({ args: { docId: vv.id("kitchenSink") }, returns: nullable(vv.doc("kitchenSink")), - handler: (ctx, args) => ctx.db.get(args.docId), + handler: (ctx, args) => ctx.db.get("kitchenSink", args.docId), }); export const getUnion = internalQuery({ args: { docId: vv.id("unionTable") }, returns: nullable(vv.doc("unionTable")), - handler: (ctx, args) => ctx.db.get(args.docId), + handler: (ctx, args) => ctx.db.get("unionTable", args.docId), }); const testApi: ApiFromModules<{ diff --git a/packages/convex-helpers/server/zod3.test.ts b/packages/convex-helpers/server/zod3.test.ts index dc059262..f27d959e 100644 --- a/packages/convex-helpers/server/zod3.test.ts +++ b/packages/convex-helpers/server/zod3.test.ts @@ -581,7 +581,7 @@ test("zod kitchen sink", async () => { }); const stored = await t.run(async (ctx) => { const id = await ctx.db.insert("sink", kitchenSink); - return ctx.db.get(id); + return ctx.db.get("sink", id); }); expect(stored).toMatchObject(omit(kitchenSink, ["optional", "default"])); }); diff --git a/packages/convex-helpers/server/zod4.zod3.test.ts b/packages/convex-helpers/server/zod4.zod3.test.ts index 29d4e498..a4cefc51 100644 --- a/packages/convex-helpers/server/zod4.zod3.test.ts +++ b/packages/convex-helpers/server/zod4.zod3.test.ts @@ -570,7 +570,7 @@ test("zod kitchen sink", async () => { }); const stored = await t.run(async (ctx) => { const id = await ctx.db.insert("sink", kitchenSink); - return ctx.db.get(id); + return ctx.db.get("sink", id); }); expect(stored).toMatchObject(omit(kitchenSink, ["optional", "default"])); }); From 2bb5c5244d68262fd679d6eeb57146918eb4f122 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 14:21:21 -0800 Subject: [PATCH 2/7] Update (WIP) --- convex/testingFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convex/testingFunctions.ts b/convex/testingFunctions.ts index 7b30ff02..63d1a719 100644 --- a/convex/testingFunctions.ts +++ b/convex/testingFunctions.ts @@ -51,7 +51,7 @@ export const clearAll = testingMutation({ handler: async ({ db, scheduler, storage }) => { for (const table of Object.keys(schema.tables)) { const docs = await db.query(table as any).collect(); - await Promise.all(docs.map((doc) => db.delete(doc._id))); + await Promise.all(docs.map((doc) => db.delete(table, doc._id))); } const scheduled = await db.system.query("_scheduled_functions").collect(); await Promise.all(scheduled.map((s) => scheduler.cancel(s._id))); From 0fbea3b54f08243f3001001ff244a3deb220d8bf Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 15:09:04 -0800 Subject: [PATCH 3/7] Update triggers/RLS --- .../convex-helpers/server/rowLevelSecurity.ts | 16 ++++++++++++---- packages/convex-helpers/server/triggers.ts | 15 +++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/convex-helpers/server/rowLevelSecurity.ts b/packages/convex-helpers/server/rowLevelSecurity.ts index f14a685c..e2052d3c 100644 --- a/packages/convex-helpers/server/rowLevelSecurity.ts +++ b/packages/convex-helpers/server/rowLevelSecurity.ts @@ -246,7 +246,10 @@ class WrapReader 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); + const doc = tableName + ? await this.db.get(tableName, id) + : // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here + await this.db.get(id); if (doc) { if (tableName && !(await this.predicate(tableName, doc))) { return null; @@ -370,7 +373,8 @@ class WrapWriter await this.checkAuth(tableName, id); return tableName ? this.db.patch(tableName, id, value) - : this.db.patch(id, value); + : // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here + this.db.patch(id, value); } replace>( @@ -388,7 +392,8 @@ class WrapWriter await this.checkAuth(tableName, id); return tableName ? this.db.replace(tableName, id, value) - : this.db.replace(id, value); + : // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here + this.db.replace(id, value); } delete>( @@ -401,7 +406,10 @@ class WrapWriter arg1 !== undefined ? [arg0, arg1] : [null, arg0]; await this.checkAuth(tableName, id); - return tableName ? this.db.delete(tableName, id) : this.db.delete(id); + return tableName + ? this.db.delete(tableName, id) + : // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here + this.db.delete(id); } get>( diff --git a/packages/convex-helpers/server/triggers.ts b/packages/convex-helpers/server/triggers.ts index 171bf681..cbebaa0e 100644 --- a/packages/convex-helpers/server/triggers.ts +++ b/packages/convex-helpers/server/triggers.ts @@ -274,6 +274,7 @@ export function writerWithTriggers< value: Partial>, ): Promise { if (!tableName) { + // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here return await innerDb.patch(id, value); } return await _execThenTrigger( @@ -283,9 +284,9 @@ export function writerWithTriggers< tableName, isWithinTrigger, async () => { - const oldDoc = (await innerDb.get(id))!; + const oldDoc = (await innerDb.get(tableName, id))!; await innerDb.patch(tableName, id, value); - const newDoc = (await innerDb.get(id))!; + const newDoc = (await innerDb.get(tableName, id))!; return [undefined, { operation: "update", id, oldDoc, newDoc }]; }, ); @@ -315,6 +316,7 @@ export function writerWithTriggers< value: WithOptionalSystemFields>, ): Promise { if (!tableName) { + // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here return await innerDb.replace(id, value); } return await _execThenTrigger( @@ -324,9 +326,9 @@ export function writerWithTriggers< tableName, isWithinTrigger, async () => { - const oldDoc = (await innerDb.get(id))!; + const oldDoc = (await innerDb.get(tableName, id))!; await innerDb.replace(tableName, id, value); - const newDoc = (await innerDb.get(id))!; + const newDoc = (await innerDb.get(tableName, id))!; return [undefined, { operation: "update", id, oldDoc, newDoc }]; }, ); @@ -351,6 +353,7 @@ export function writerWithTriggers< id: GenericId>, ): Promise { if (!tableName) { + // eslint-disable-next-line @convex-dev/explicit-table-ids -- tableName not available here– return await innerDb.delete(id); } return await _execThenTrigger( @@ -360,7 +363,7 @@ export function writerWithTriggers< tableName, isWithinTrigger, async () => { - const oldDoc = (await innerDb.get(id))!; + const oldDoc = (await innerDb.get(tableName, id))!; await innerDb.delete(tableName, id); return [undefined, { operation: "delete", id, oldDoc, newDoc: null }]; }, @@ -383,7 +386,7 @@ export function writerWithTriggers< isWithinTrigger, async () => { const id = await innerDb.insert(table, value); - const newDoc = (await innerDb.get(id))!; + const newDoc = (await innerDb.get(table, id))!; return [id, { operation: "insert", id, oldDoc: null, newDoc }]; }, ); From cec6dcb8e86a9cc439347352ee72f167846706a4 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 15:10:20 -0800 Subject: [PATCH 4/7] Update CRUD --- packages/convex-helpers/server/crud.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/convex-helpers/server/crud.ts b/packages/convex-helpers/server/crud.ts index 187c8591..0d278886 100644 --- a/packages/convex-helpers/server/crud.ts +++ b/packages/convex-helpers/server/crud.ts @@ -107,7 +107,7 @@ export function crud< if ("_id" in args) delete args._id; if ("_creationTime" in args) delete args._creationTime; const id = await ctx.db.insert(table, args); - return (await ctx.db.get(id))!; + return (await ctx.db.get(table, id))!; }, }) as RegisteredMutation< MutationVisibility, @@ -117,7 +117,7 @@ export function crud< read: query({ args: { id: v.id(table) }, handler: async (ctx, args) => { - return await ctx.db.get(args.id); + return await ctx.db.get(table, args.id); }, }) as RegisteredQuery< QueryVisibility, @@ -147,6 +147,7 @@ export function crud< }, handler: async (ctx, args) => { await ctx.db.patch( + table, args.id, args.patch as Partial>, ); @@ -164,9 +165,9 @@ export function crud< destroy: mutation({ args: { id: v.id(table) }, handler: async (ctx, args) => { - const old = await ctx.db.get(args.id); + const old = await ctx.db.get(table, args.id); if (old) { - await ctx.db.delete(args.id); + await ctx.db.delete(table, args.id); } return old; }, From 125a5960bcb5457ff935cceebfc664c0767d6a8c Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 15:13:47 -0800 Subject: [PATCH 5/7] WIP: update migrations --- packages/convex-helpers/server/migrations.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/convex-helpers/server/migrations.ts b/packages/convex-helpers/server/migrations.ts index 0ad24827..0af2372a 100644 --- a/packages/convex-helpers/server/migrations.ts +++ b/packages/convex-helpers/server/migrations.ts @@ -378,7 +378,11 @@ export function makeMigration< try { const next = await migrateOne(ctx, doc); if (next && Object.keys(next).length > 0) { - await ctx.db.patch(doc._id as GenericId, next); + await ctx.db.patch( + table, + doc._id as GenericId, + next, + ); } } catch (error) { console.error(`Document failed: ${doc._id}`); @@ -395,7 +399,7 @@ export function makeMigration< if (args.dryRun) { // Throwing an error rolls back the transaction for (const before of page) { - const after = await ctx.db.get(page[0]!._id as any); + const after = await ctx.db.get(table, page[0]!._id as any); if (JSON.stringify(before) === JSON.stringify(after)) { continue; } @@ -414,7 +418,8 @@ export function makeMigration< // 2. The migration is being resumed at a different cursor. // 3. There are two instances of the same migration racing. const worker = - state.workerId && (await ctx.db.system.get("_scheduled_functions", state.workerId)); + state.workerId && + (await ctx.db.system.get("_scheduled_functions", state.workerId)); if ( worker && (worker.state.kind === "pending" || @@ -631,7 +636,8 @@ export async function getStatus< docs.reverse().map(async (migration) => { const { workerId, isDone } = migration; if (isDone) return migration; - const worker = workerId && (await ctx.db.system.get("_scheduled_functions", workerId)); + const worker = + workerId && (await ctx.db.system.get("_scheduled_functions", workerId)); return { ...migration, workerStatus: worker?.state.kind, @@ -669,7 +675,9 @@ export async function cancelMigration( if (state.isDone) { return state; } - const worker = state.workerId && (await ctx.db.system.get("_scheduled_functions", state.workerId)); + const worker = + state.workerId && + (await ctx.db.system.get("_scheduled_functions", state.workerId)); if ( worker && (worker.state.kind === "pending" || worker.state.kind === "inProgress") From 101d6cf382b5843fda22400d6cc6a8bc553669ab Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Wed, 17 Dec 2025 15:15:40 -0800 Subject: [PATCH 6/7] Update triggers test --- packages/convex-helpers/server/triggers.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/convex-helpers/server/triggers.test.ts b/packages/convex-helpers/server/triggers.test.ts index 7a54bc3f..16429b62 100644 --- a/packages/convex-helpers/server/triggers.test.ts +++ b/packages/convex-helpers/server/triggers.test.ts @@ -158,6 +158,7 @@ test("trigger denormalizes field", async () => { lastName: "Doe", }); await t.run(async (ctx) => { + // eslint-disable-next-line @convex-dev/explicit-table-ids -- Testing the old API const user = await ctx.db.get(userId); expect(user!.fullName).toStrictEqual("John Doe"); }); @@ -170,7 +171,7 @@ test("trigger with explicit IDs denormalizes field", async () => { lastName: "Doe", }); await t.run(async (ctx) => { - const user = await ctx.db.get(userId); + const user = await ctx.db.get("users", userId); expect(user!.fullName).toStrictEqual("John Doe"); }); }); From 9041e6ba5716621b24c102108c1414f55e5ed043 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Thu, 18 Dec 2025 15:39:42 -0800 Subject: [PATCH 7/7] Pin dependency --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ea2a951..f58bf3c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@arethetypeswrong/cli": "0.18.2", - "@convex-dev/eslint-plugin": "^1.1.0", + "@convex-dev/eslint-plugin": "1.1.1", "@edge-runtime/vm": "5.0.0", "@eslint/js": "9.39.1", "@redocly/cli": "2.11.1", diff --git a/package.json b/package.json index 93da7fc4..36903ac5 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ }, "devDependencies": { "@arethetypeswrong/cli": "0.18.2", - "@convex-dev/eslint-plugin": "^1.1.0", + "@convex-dev/eslint-plugin": "1.1.1", "@edge-runtime/vm": "5.0.0", "@eslint/js": "9.39.1", "@redocly/cli": "2.11.1",