Skip to content

Conversation

@MohamedBassem
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Walkthrough

This PR migrates the database layer from better-sqlite3 (synchronous SQLite) to libsql (@libsql/client, asynchronous SQLite-compatible client). Changes include dependency replacements, async/await conversions, result property updates, transaction consolidation with explicit immediate behavior, and error handling refactors across workers, models, and test infrastructure.

Changes

Cohort / File(s) Summary
Dependency Updates
apps/workers/package.json, packages/db/package.json
Added @libsql/client ^0.15.15 dependency; removed better-sqlite3 and associated type definitions; added tsdown build tool.
Build Configuration
apps/workers/tsdown.config.ts, packages/db/tsdown.config.ts
Created/updated tsdown bundler configurations; replaced better-sqlite3 with @libsql/client in external dependencies list; configured entry points and output for db migrations.
Database Core Layer
packages/db/drizzle.ts, packages/db/drizzle.config.ts, packages/db/migrate.ts, packages/db/index.ts
Switched from sqlite to turso dialect; replaced in-process SQLite with LibSQL client initialization; converted getInMemoryDB to async with temp file-backed database; updated migrator to libsql/migrator; exported LibsqlError as SqliteError; added getLibsqlError helper function.
Worker Transaction Refactoring
apps/workers/workers/crawlerWorker.ts, apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts, apps/workers/workers/inference/tagging.ts, apps/workers/workers/videoWorker.ts
Consolidated multiple separate transactions into single transactional blocks with explicit immediate behavior; added fileName and sourceUrl fields to asset records; replaced res.changes with res.rowsAffected for row-affected checks; refactored tag management flow with separate event triggers.
ORM Result Property Migration
packages/trpc/models/{bookmarks,feeds,importSessions,listInvitations,lists,rules,tags,users,webhooks,backups}.ts, packages/trpc/routers/{admin,assets,bookmarks,prompts}.ts, packages/trpc/auth.ts
Systematically replaced .changes property checks with .rowsAffected across all database mutation result handling.
Error Handling Refactor
packages/trpc/models/{lists,tags,users}.ts
Replaced SqliteError imports with getLibsqlError helper function; updated constraint violation checks to use libsql error objects.
Transaction Consolidation
packages/trpc/models/{listInvitations,backups}.ts, packages/trpc/routers/assets.ts, packages/trpc/routers/bookmarks.ts
Wrapped database operations in single transactions with explicit { behavior: "immediate" } option; consolidated related updates/deletes into transactional blocks.
Test Infrastructure
packages/trpc/testUtils.ts, packages/trpc/lib/__tests__/{ruleEngine,search}.test.ts, packages/db/...
Made getInMemoryDB async; updated getTestDB and buildTestContext to await async db initialization; updated test setup to handle async database factory.
Docker Configuration
docker/Dockerfile, docker/root/etc/s6-overlay/s6-rc.d/init-db-migration/run
Replaced ncc-based migrate.ts build with pnpm build of packages/db; added explicit copy of @libsql node_modules to final image; updated migration entry point from index.js to dist/migrate.js.
Benchmark and E2E Configuration
packages/benchmarks/docker-compose.yml, packages/e2e_tests/docker-compose.yml, packages/benchmarks/src/trpc.ts
Added DB_WAL_MODE: true environment variable; reduced CRAWLER_NUM_WORKERS to 1; added TRPC retry logic with 3 attempts and exponential backoff for fetch failures; added 60-second timeout to httpBatchLink via AbortController.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Database abstraction layer changes (packages/db/drizzle.ts, packages/db/index.ts) — Validate that LibSQL client initialization, pragma configuration, and migration execution work correctly in all environments (development, testing, production).
  • Transaction consolidation patterns (crawlerWorker.ts, bookmarks.ts) — Verify that moving operations into single transactions maintains data consistency and that the immediate behavior option doesn't introduce race conditions or atomicity issues.
  • Async/await cascading (getInMemoryDB, getTestDB) — Ensure all callers properly await the new async functions and that test setup completes before assertions run.
  • Error handling migration (lists.ts, tags.ts, users.ts) — Confirm that getLibsqlError correctly extracts LibsqlError from nested cause chains and that constraint violation detection remains reliable.
  • Docker build and runtime (Dockerfile, migration script) — Validate that @libsql native bindings are properly included and that the new db build/deployment step produces correct migration artifacts.
  • Result property renames — Large-scale property changes (.changes → .rowsAffected) across 15+ files; spot-check several models to ensure no property access was missed and behavior remains identical.

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to evaluate whether the description is related to the changeset. Add a pull request description explaining the rationale, scope, and impact of migrating from better-sqlite3 to libsql.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: migrate from better-sqlite3 to libsql' directly and clearly summarizes the main change in the changeset, which involves replacing better-sqlite3 with libsql throughout the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

migrate(db, { migrationsFolder: "./drizzle" });

P1 Badge Await libsql migrations

The switch to drizzle-orm/libsql/migrator makes migrate(...) async (usage in upstream docs is await migrate(db, …)), but the migration entrypoint now fires it without awaiting or catching the promise. If the migration fails, the rejection is unhandled and pnpm db:migrate can report success while the schema remains unapplied; on some runtimes the process may even exit before the async work completes.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/routers/assets.ts (1)

21-27: ensureAssetOwnership is querying by the wrong column/table

ensureAssetOwnership is doing:

const asset = await opts.ctx.db.query.assets.findFirst({
  where: eq(bookmarks.id, opts.assetId),
});

This filters on bookmarks.id while querying assets, which should fail at runtime and never find any asset. It likely needs to compare against assets.id instead.

Consider applying:

-  const asset = await opts.ctx.db.query.assets.findFirst({
-    where: eq(bookmarks.id, opts.assetId),
-  });
+  const asset = await opts.ctx.db.query.assets.findFirst({
+    where: eq(assets.id, opts.assetId),
+  });

This will unblock attachAsset, replaceAsset, and detachAsset flows that rely on this helper.

🧹 Nitpick comments (12)
packages/benchmarks/docker-compose.yml (1)

20-20: Remove the commented-out line.

The # WORKERS_ENABLED_WORKERS: video comment should be removed entirely rather than left as dead code.

Apply this diff to clean up the commented-out line:

       BROWSER_WEB_URL: http://chrome:9222
       CRAWLER_NUM_WORKERS: 1
       CRAWLER_ALLOWED_INTERNAL_HOSTNAMES: nginx
       DB_WAL_MODE: true
-      # WORKERS_ENABLED_WORKERS: video
-
packages/benchmarks/src/trpc.ts (2)

1-26: Retry predicate and attempts count may need tightening for retryLink

The retry logic looks reasonable, but a couple of details are worth double‑checking:

  • Relying on opts.error.message.includes("fetch failed") is brittle: the message differs between Node and browsers (e.g. "Failed to fetch" vs "fetch failed"), and may change across runtimes. Consider checking a more stable signal (e.g. error type or code) or at least handling both common message variants so you don’t silently skip retries in some environments.
  • Confirm whether opts.attempts is “number of retries so far” or “total attempts including the first one” for @trpc/client v11.4.3. As written, opts.attempts <= 3 could give 3 or 4 total tries depending on the API contract, so it’s worth verifying this matches your intended maximum.

You could, for example, normalize the predicate and make the max‑retries intent explicit once you confirm the API:

retry({ attempts, error }) {
  const msg = error.message.toLowerCase();
  const isNetworkFetchError =
    msg.includes("fetch failed") || msg.includes("failed to fetch");

  if (!isNetworkFetchError) return false;

  const MAX_RETRIES = 3;
  return attempts < MAX_RETRIES; // or <=, depending on @trpc/client semantics
}

35-44: Custom timeout overwrites upstream signal; consider composing abort signals

The 60s timeout wrapper is good, but the override unconditionally replaces any existing options.signal:

fetch(url, {
  ...options,
  signal: controller.signal,
} as RequestInit)

If httpBatchLink (now or in the future) passes its own signal, this drops upstream cancellation. You can safely preserve both by composing signals:

fetch(url, options) {
  const timeoutController = new AbortController();
  const timeoutId = setTimeout(() => timeoutController.abort(), 60_000);

  const originalSignal = options?.signal as AbortSignal | undefined;
  const controller =
    originalSignal == null
      ? timeoutController
      : (() => {
          const c = new AbortController();
          const abort = () => c.abort();
          originalSignal.addEventListener("abort", abort);
          timeoutController.signal.addEventListener("abort", abort);
          return c;
        })();

  return fetch(url, {
    ...options,
    signal: controller.signal,
  } as RequestInit).finally(() => clearTimeout(timeoutId));
}

This keeps your timeout behavior while still respecting any caller‑initiated aborts.

packages/trpc/models/backups.ts (1)

107-143: Use a local assetId snapshot in delete() to avoid drift and clarify intent

The new transactional delete block looks good overall (user‑scoped, single transaction, correct table/conditions). One small robustness tweak: you read this.backup.assetId in two places (before the transaction for deleteAsset, and again inside the transaction for the assets delete). If the DB row’s assetId can ever change between loading this Backup and calling delete() (e.g., via another process or direct DB access), this method could end up deleting the “wrong” asset row or skipping the DB delete for the current one.

Capturing the current assetId once and using it consistently also makes the intent clearer:

   async delete(): Promise<void> {
-    if (this.backup.assetId) {
-      // Delete asset
-      await deleteAsset({
-        userId: this.ctx.user.id,
-        assetId: this.backup.assetId,
-      });
-    }
+    const assetId = this.backup.assetId;
+
+    if (assetId) {
+      // Delete asset (storage / external system)
+      await deleteAsset({
+        userId: this.ctx.user.id,
+        assetId,
+      });
+    }
 
-    await this.ctx.db.transaction(
-      async (db) => {
-        // Delete asset first
-        if (this.backup.assetId) {
+    await this.ctx.db.transaction(
+      async (db) => {
+        // Delete asset row first
+        if (assetId) {
           await db
             .delete(assets)
             .where(
               and(
-                eq(assets.id, this.backup.assetId),
+                eq(assets.id, assetId),
                 eq(assets.userId, this.ctx.user.id),
               ),
             );
         }
 
         // Delete backup record
         await db
           .delete(backupsTable)
           .where(
             and(
               eq(backupsTable.id, this.backup.id),
               eq(backupsTable.userId, this.ctx.user.id),
             ),
           );
       },
       {
         behavior: "immediate",
       },
     );
   }

Separately (non‑blocking), consider whether you want the external deleteAsset call to happen after a successful DB transaction to avoid the case where storage deletion succeeds but the DB transaction fails and leaves a dangling reference. If current behavior is intentional, a brief comment explaining the ordering choice could help future readers.

apps/workers/workers/inference/tagging.ts (1)

323-353: Consider inlining the IIFE for improved readability.

The async IIFE wrapping the tag matching logic adds nesting without clear benefit since the variables matchedTagIds and notFoundTagNames could be declared directly in the transaction scope. This is a minor style preference.

-      // Attempt to match exiting tags with the new ones
-      const { matchedTagIds, notFoundTagNames } = await (async () => {
-        const { normalizeTag, sql: normalizedTagSql } = tagNormalizer(
-          bookmarkTags.name,
-        );
-        const normalizedInferredTags = inferredTags.map((t) => ({
-          originalTag: t,
-          normalizedTag: normalizeTag(t),
-        }));
-
-        const matchedTags = await tx.query.bookmarkTags.findMany({
-          where: and(
-            eq(bookmarkTags.userId, userId),
-            inArray(
-              normalizedTagSql,
-              normalizedInferredTags.map((t) => t.normalizedTag),
-            ),
-          ),
-        });
-
-        const matchedTagIds = matchedTags.map((r) => r.id);
-        const notFoundTagNames = normalizedInferredTags
-          .filter(
-            (t) =>
-              !matchedTags.some(
-                (mt) => normalizeTag(mt.name) === t.normalizedTag,
-              ),
-          )
-          .map((t) => t.originalTag);
-
-        return { matchedTagIds, notFoundTagNames };
-      })();
+      // Attempt to match existing tags with the new ones
+      const { normalizeTag, sql: normalizedTagSql } = tagNormalizer(
+        bookmarkTags.name,
+      );
+      const normalizedInferredTags = inferredTags.map((t) => ({
+        originalTag: t,
+        normalizedTag: normalizeTag(t),
+      }));
+
+      const matchedTags = await tx.query.bookmarkTags.findMany({
+        where: and(
+          eq(bookmarkTags.userId, userId),
+          inArray(
+            normalizedTagSql,
+            normalizedInferredTags.map((t) => t.normalizedTag),
+          ),
+        ),
+      });
+
+      const matchedTagIds = matchedTags.map((r) => r.id);
+      const notFoundTagNames = normalizedInferredTags
+        .filter(
+          (t) =>
+            !matchedTags.some(
+              (mt) => normalizeTag(mt.name) === t.normalizedTag,
+            ),
+        )
+        .map((t) => t.originalTag);

Also note: Line 322 has a typo - "exiting" should be "existing".

packages/db/drizzle.config.ts (1)

7-18: Confirm turso dialect URL format; consider file: prefix for local fallback

Switching the dialect to "turso" makes sense for libsql, but dbCredentials.url is still a bare filesystem path. Drizzle/libsql examples typically use a URL with a scheme (e.g. libsql://... or file:./db.db). If this config is used as a fallback for local dev/migrations, consider making it an explicit file URL:

-const databaseURL = serverConfig.dataDir
-  ? `${serverConfig.dataDir}/db.db`
-  : "./db.db";
+const databaseURL = serverConfig.dataDir
+  ? `file:${serverConfig.dataDir}/db.db`
+  : "file:./db.db";

Please double-check against your drizzle-kit/libsql setup to ensure this URL shape is what those tools expect in your environments.

packages/trpc/auth.ts (1)

36-47: Use strict equality for the rowsAffected check

rowsAffected is numeric, so using strict equality keeps things type-safe and consistent with the rest of the codebase:

-  if (res.rowsAffected == 0) {
+  if (res.rowsAffected === 0) {
packages/trpc/lib/__tests__/ruleEngine.test.ts (1)

77-82: Async DB initialization in tests is consistent; consider reusing getTestDB helper

Switching beforeEach to await getInMemoryDB(true) matches the new async DB setup and looks correct. For consistency with other tests and to keep one source of truth for test DB configuration, you might consider using getTestDB() from packages/trpc/testUtils.ts here instead of calling getInMemoryDB directly.

packages/trpc/models/users.ts (1)

7-8: LibSQL‑aware createRaw transaction looks correct; consider logging non‑constraint failures

Wrapping createRaw in db.transaction(..., { behavior: "immediate" }) correctly makes the “determine first user → assign role → insert” flow atomic, and using getLibsqlError to map SQLITE_CONSTRAINT_UNIQUE to a BAD_REQUEST (“Email is already taken”) keeps the previous semantics under libsql.

For all other DB errors you now throw a generic INTERNAL_SERVER_ERROR without logging the underlying cause. For easier production debugging, you may want to log e (or at least libsqlError) before throwing the generic TRPCError, while still keeping the client‑facing message sanitized.

Also applies to: 86-143

packages/trpc/models/tags.ts (1)

17-18: LibSQL error handling for tag create/update is sound; consider wrapping non‑constraint DB errors

Using getLibsqlError to detect SQLITE_CONSTRAINT_UNIQUE in both Tag.create and Tag.update correctly preserves the “duplicate tag name → BAD_REQUEST” behavior after moving to libsql. For non‑constraint cases, you currently rethrow the original error, which may surface DB details depending on your tRPC error formatting.

If you want stricter separation between internal DB errors and client‑visible messages, consider logging e and then throwing a generic TRPCError with code: "INTERNAL_SERVER_ERROR" for the fallback path, similar to the pattern in User.createRaw.

Also applies to: 59-83, 333-385

packages/trpc/models/lists.ts (1)

7-8: LibSQL primary‑key handling in addBookmark is correct; optional logging for other failures

ManualList.addBookmark now uses getLibsqlError and treats SQLITE_CONSTRAINT_PRIMARYKEY as a no‑op, which is the right way to make “already in list” idempotent under the libsql driver. For any other DB error, you throw a generic INTERNAL_SERVER_ERROR, which is fine behaviorally.

As in other modules, you might want to log e or libsqlError before throwing the generic TRPCError so that unexpected DB failures are easier to diagnose in production.

Also applies to: 913-941

packages/db/drizzle.ts (1)

89-92: Consider adding concurrency option for consistency with the main client.

The test client is created without the concurrency: 0 option that's set on the main client (Line 22). While this may be intentional for test isolation, inconsistent settings could lead to different behavior between tests and production.

   const memClient = createClient({
     url: `file:${tempFile}`,
+    concurrency: 0,
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 683083f and cc6e860.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • apps/workers/package.json (1 hunks)
  • apps/workers/tsdown.config.ts (1 hunks)
  • apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts (1 hunks)
  • apps/workers/workers/crawlerWorker.ts (3 hunks)
  • apps/workers/workers/inference/tagging.ts (1 hunks)
  • apps/workers/workers/videoWorker.ts (1 hunks)
  • docker/Dockerfile (2 hunks)
  • docker/root/etc/s6-overlay/s6-rc.d/init-db-migration/run (1 hunks)
  • packages/benchmarks/docker-compose.yml (1 hunks)
  • packages/benchmarks/src/trpc.ts (3 hunks)
  • packages/db/drizzle.config.ts (1 hunks)
  • packages/db/drizzle.ts (1 hunks)
  • packages/db/index.ts (2 hunks)
  • packages/db/migrate.ts (1 hunks)
  • packages/db/package.json (3 hunks)
  • packages/db/tsdown.config.ts (1 hunks)
  • packages/e2e_tests/docker-compose.yml (1 hunks)
  • packages/trpc/auth.ts (1 hunks)
  • packages/trpc/lib/__tests__/ruleEngine.test.ts (1 hunks)
  • packages/trpc/lib/__tests__/search.test.ts (1 hunks)
  • packages/trpc/models/backups.ts (1 hunks)
  • packages/trpc/models/bookmarks.ts (1 hunks)
  • packages/trpc/models/feeds.ts (1 hunks)
  • packages/trpc/models/importSessions.ts (1 hunks)
  • packages/trpc/models/listInvitations.ts (1 hunks)
  • packages/trpc/models/lists.ts (8 hunks)
  • packages/trpc/models/rules.ts (3 hunks)
  • packages/trpc/models/tags.ts (6 hunks)
  • packages/trpc/models/users.ts (4 hunks)
  • packages/trpc/models/webhooks.ts (1 hunks)
  • packages/trpc/routers/admin.ts (2 hunks)
  • packages/trpc/routers/assets.ts (2 hunks)
  • packages/trpc/routers/bookmarks.ts (4 hunks)
  • packages/trpc/routers/prompts.ts (1 hunks)
  • packages/trpc/testUtils.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript for type safety in all source files

Files:

  • packages/trpc/routers/prompts.ts
  • packages/trpc/models/listInvitations.ts
  • apps/workers/tsdown.config.ts
  • packages/trpc/models/webhooks.ts
  • packages/trpc/routers/admin.ts
  • packages/trpc/models/bookmarks.ts
  • packages/trpc/testUtils.ts
  • apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts
  • packages/db/drizzle.config.ts
  • apps/workers/workers/crawlerWorker.ts
  • packages/db/tsdown.config.ts
  • packages/trpc/models/feeds.ts
  • packages/trpc/auth.ts
  • packages/db/drizzle.ts
  • packages/trpc/models/rules.ts
  • apps/workers/workers/inference/tagging.ts
  • packages/trpc/routers/assets.ts
  • packages/trpc/models/importSessions.ts
  • packages/trpc/routers/bookmarks.ts
  • packages/db/index.ts
  • packages/trpc/lib/__tests__/search.test.ts
  • packages/trpc/models/tags.ts
  • packages/trpc/models/backups.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
  • apps/workers/workers/videoWorker.ts
  • packages/benchmarks/src/trpc.ts
  • packages/trpc/models/lists.ts
  • packages/db/migrate.ts
  • packages/trpc/models/users.ts
**/*.{ts,tsx,js,jsx,json,css,md}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier according to project standards

Files:

  • packages/trpc/routers/prompts.ts
  • packages/trpc/models/listInvitations.ts
  • apps/workers/tsdown.config.ts
  • packages/trpc/models/webhooks.ts
  • packages/trpc/routers/admin.ts
  • packages/trpc/models/bookmarks.ts
  • packages/trpc/testUtils.ts
  • apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts
  • packages/db/drizzle.config.ts
  • apps/workers/workers/crawlerWorker.ts
  • packages/db/tsdown.config.ts
  • packages/trpc/models/feeds.ts
  • packages/trpc/auth.ts
  • packages/db/drizzle.ts
  • packages/trpc/models/rules.ts
  • apps/workers/workers/inference/tagging.ts
  • packages/trpc/routers/assets.ts
  • packages/trpc/models/importSessions.ts
  • packages/trpc/routers/bookmarks.ts
  • packages/db/index.ts
  • packages/trpc/lib/__tests__/search.test.ts
  • packages/trpc/models/tags.ts
  • packages/trpc/models/backups.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
  • apps/workers/workers/videoWorker.ts
  • packages/benchmarks/src/trpc.ts
  • packages/db/package.json
  • packages/trpc/models/lists.ts
  • packages/db/migrate.ts
  • apps/workers/package.json
  • packages/trpc/models/users.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lint code using oxlint and fix issues with pnpm lint:fix

Files:

  • packages/trpc/routers/prompts.ts
  • packages/trpc/models/listInvitations.ts
  • apps/workers/tsdown.config.ts
  • packages/trpc/models/webhooks.ts
  • packages/trpc/routers/admin.ts
  • packages/trpc/models/bookmarks.ts
  • packages/trpc/testUtils.ts
  • apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts
  • packages/db/drizzle.config.ts
  • apps/workers/workers/crawlerWorker.ts
  • packages/db/tsdown.config.ts
  • packages/trpc/models/feeds.ts
  • packages/trpc/auth.ts
  • packages/db/drizzle.ts
  • packages/trpc/models/rules.ts
  • apps/workers/workers/inference/tagging.ts
  • packages/trpc/routers/assets.ts
  • packages/trpc/models/importSessions.ts
  • packages/trpc/routers/bookmarks.ts
  • packages/db/index.ts
  • packages/trpc/lib/__tests__/search.test.ts
  • packages/trpc/models/tags.ts
  • packages/trpc/models/backups.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
  • apps/workers/workers/videoWorker.ts
  • packages/benchmarks/src/trpc.ts
  • packages/trpc/models/lists.ts
  • packages/db/migrate.ts
  • packages/trpc/models/users.ts
packages/trpc/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Organize business logic in the tRPC router and procedures located in packages/trpc

Files:

  • packages/trpc/routers/prompts.ts
  • packages/trpc/models/listInvitations.ts
  • packages/trpc/models/webhooks.ts
  • packages/trpc/routers/admin.ts
  • packages/trpc/models/bookmarks.ts
  • packages/trpc/testUtils.ts
  • packages/trpc/models/feeds.ts
  • packages/trpc/auth.ts
  • packages/trpc/models/rules.ts
  • packages/trpc/routers/assets.ts
  • packages/trpc/models/importSessions.ts
  • packages/trpc/routers/bookmarks.ts
  • packages/trpc/lib/__tests__/search.test.ts
  • packages/trpc/models/tags.ts
  • packages/trpc/models/backups.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
  • packages/trpc/models/lists.ts
  • packages/trpc/models/users.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for database schema and migrations in the db package

Files:

  • packages/db/drizzle.config.ts
  • packages/db/tsdown.config.ts
  • packages/db/drizzle.ts
  • packages/db/index.ts
  • packages/db/migrate.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for writing and running tests

Files:

  • packages/trpc/lib/__tests__/search.test.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
🧠 Learnings (10)
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to packages/trpc/**/*.{ts,tsx} : Organize business logic in the tRPC router and procedures located in `packages/trpc`

Applied to files:

  • packages/trpc/routers/prompts.ts
  • packages/trpc/routers/admin.ts
  • packages/trpc/models/rules.ts
  • packages/trpc/routers/assets.ts
  • packages/trpc/models/tags.ts
  • packages/benchmarks/src/trpc.ts
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Create database migrations using `pnpm db:generate --name description_of_schema_change` after schema updates

Applied to files:

  • docker/root/etc/s6-overlay/s6-rc.d/init-db-migration/run
  • docker/Dockerfile
  • packages/db/migrate.ts
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to packages/db/**/*.{ts,tsx} : Use Drizzle ORM for database schema and migrations in the db package

Applied to files:

  • packages/trpc/testUtils.ts
  • packages/db/drizzle.config.ts
  • packages/db/tsdown.config.ts
  • packages/db/drizzle.ts
  • packages/db/index.ts
  • packages/trpc/lib/__tests__/ruleEngine.test.ts
  • packages/db/package.json
  • docker/Dockerfile
  • packages/db/migrate.ts
  • packages/trpc/models/users.ts
📚 Learning: 2025-11-16T13:02:08.919Z
Learnt from: MohamedBassem
Repo: karakeep-app/karakeep PR: 2141
File: packages/db/schema.ts:160-165
Timestamp: 2025-11-16T13:02:08.919Z
Learning: In this codebase using Drizzle ORM with SQLite, enum definitions (e.g., `text("status", { enum: ["value1", "value2"] })`) only provide TypeScript-level type safety and do not require database migrations when values are added or modified. SQLite stores these as TEXT columns without database-level CHECK constraints, so schema changes to enums don't need corresponding migration files.

Applied to files:

  • packages/db/drizzle.config.ts
  • packages/db/drizzle.ts
  • packages/db/index.ts
  • packages/trpc/models/tags.ts
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to packages/shared/**/*.{ts,tsx} : Organize shared code and types in the `packages/shared` directory for use across packages

Applied to files:

  • packages/db/tsdown.config.ts
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to packages/api/**/*.{ts,tsx} : Use Hono and tRPC for building the API backend

Applied to files:

  • packages/benchmarks/src/trpc.ts
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript for type safety in all source files

Applied to files:

  • packages/db/package.json
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to **/*.{ts,tsx,js,jsx,json,css,md} : Format code using Prettier according to project standards

Applied to files:

  • packages/db/package.json
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Run `pnpm typecheck` to verify TypeScript type safety across the codebase

Applied to files:

  • packages/db/package.json
📚 Learning: 2025-11-27T16:02:47.440Z
Learnt from: CR
Repo: karakeep-app/karakeep PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:02:47.440Z
Learning: Applies to packages/shared-server/**/*.{ts,tsx} : Place shared server-side logic in `packages/shared-server`

Applied to files:

  • apps/workers/package.json
🧬 Code graph analysis (11)
packages/trpc/models/listInvitations.ts (1)
packages/db/schema.ts (2)
  • listInvitations (488-519)
  • listCollaborators (462-486)
apps/workers/workers/crawlerWorker.ts (3)
packages/db/drizzle.ts (1)
  • db (41-41)
apps/workers/workerUtils.ts (1)
  • updateAsset (7-17)
packages/db/schema.ts (4)
  • bookmarkAssets (339-351)
  • bookmarks (154-213)
  • bookmarkLinks (215-243)
  • assets (259-295)
packages/db/drizzle.ts (1)
packages/db/index.ts (2)
  • db (8-8)
  • DB (9-9)
packages/trpc/models/rules.ts (1)
packages/db/schema.ts (2)
  • ruleEngineRulesTable (648-684)
  • ruleEngineActionsTable (686-720)
packages/trpc/routers/assets.ts (1)
packages/db/schema.ts (1)
  • assets (259-295)
packages/trpc/lib/__tests__/search.test.ts (1)
packages/db/drizzle.ts (2)
  • db (41-41)
  • getInMemoryDB (81-104)
packages/trpc/models/tags.ts (1)
packages/db/index.ts (1)
  • getLibsqlError (22-33)
packages/trpc/lib/__tests__/ruleEngine.test.ts (1)
packages/db/drizzle.ts (2)
  • db (41-41)
  • getInMemoryDB (81-104)
apps/workers/workers/videoWorker.ts (2)
packages/db/drizzle.ts (1)
  • db (41-41)
apps/workers/workerUtils.ts (1)
  • updateAsset (7-17)
packages/trpc/models/lists.ts (2)
packages/db/index.ts (1)
  • getLibsqlError (22-33)
packages/db/schema.ts (2)
  • bookmarksInLists (429-460)
  • bookmarkLists (398-427)
packages/trpc/models/users.ts (1)
packages/db/index.ts (2)
  • db (8-8)
  • getLibsqlError (22-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: typecheck
  • GitHub Check: tests
  • GitHub Check: format
🔇 Additional comments (43)
packages/benchmarks/docker-compose.yml (2)

17-17: Clarify the intent behind reducing CRAWLER_NUM_WORKERS from 6 to 1.

In a benchmarks environment, the worker count significantly affects load profile and benchmark validity. Reducing from 6 to 1 is a major change.

Is this intentional to isolate database performance from crawler concurrency, or should the original value be retained to measure libsql performance under concurrent load?


19-19: DB_WAL_MODE addition is appropriate for libsql.

Enabling WAL (Write-Ahead Logging) mode aligns with the libsql migration and follows modern SQLite best practices, improving concurrency and performance.

apps/workers/tsdown.config.ts (1)

13-17: [rewritten review comment]
[classification tag]

packages/trpc/models/listInvitations.ts (1)

122-141: Transaction refactor with behavior: "immediate" looks correct and preserves atomicity

The migration to await this.ctx.db.transaction(async (tx) => { ... }, { behavior: "immediate" }) keeps the accept flow logically identical: the invitation is deleted and the collaborator is inserted in a single atomic unit, and onConflictDoNothing() still makes this robust against repeats/races. The explicit behavior: "immediate" is consistent with using libsql-style transactions and should give you predictable write locking semantics.

I don’t see any new correctness or concurrency issues introduced by this change; assuming the types line up, this looks good.

Please double‑check that:

  • AuthedContext["db"]’s transaction API indeed expects (callback, { behavior }) in your libsql/drizzle setup, and
  • Existing tests around invitation acceptance (and any new libsql-backed transaction tests) pass, to confirm there are no driver-specific quirks with behavior: "immediate".
apps/workers/workers/inference/tagging.ts (3)

356-370: Potential edge case: conflicting inserts may result in missing tag attachments.

When using onConflictDoNothing().returning(), only successfully inserted rows are returned. If another transaction creates the same tag after the initial query (line 332-340) but before this insert, those tag IDs won't appear in either matchedTagIds or newTagIds, potentially leaving some tags unattached.

The immediate transaction behavior mitigates this significantly, but for robustness, consider using onConflictDoUpdate to return the existing row, or re-query to fetch IDs of all tags by name after insertion.

This is likely a low-probability edge case given the immediate transaction lock, but verify if this could occur under high concurrency scenarios.


372-396: LGTM!

The delete-then-attach pattern is correct. Using Set for deduplication and onConflictDoNothing on tagsOnBookmarks properly handles the case where a tag might already be attached by another source (e.g., manually by user). The returned values correctly capture only the actual changes for rule engine events.


398-412: Move triggerRuleEngineOnEvent outside the transaction.

triggerRuleEngineOnEvent enqueues a background job via RuleEngineQueue.enqueue() inside the transaction. If the transaction rolls back after the enqueue succeeds, the queued job will process with stale event data. Collect the event data within the transaction and trigger the rule engine after the transaction completes to ensure consistency.

packages/trpc/models/feeds.ts (1)

69-81: rowsAffected-based NOT_FOUND handling for delete looks correct

Using res.rowsAffected === 0 after the filtered delete is the right way to detect a missing feed and is consistent with the update path’s NOT_FOUND handling. No further changes needed here; just ensure your libsql/drizzle migration tests cover this branch.

packages/trpc/models/importSessions.ts (1)

164-180: Import session delete now correctly checks rowsAffected

Switching the not-found detection to result.rowsAffected === 0 after the scoped delete matches the expected libsql/drizzle behavior and aligns with other models’ NOT_FOUND semantics.

apps/workers/package.json (1)

7-59: New @libsql/client dependency: keep version aligned across the workspace

Adding "@libsql/client": "^0.15.15" to workers is fine if the workers use the client directly; just ensure this version stays in sync with the one used in @karakeep/db so you don’t end up with mismatched libsql client versions in different packages.

docker/root/etc/s6-overlay/s6-rc.d/init-db-migration/run (1)

4-6: Updated migration entrypoint matches compiled db build

Pointing the init-db-migration script at dist/migrate.js under /db_migrations is consistent with the new packages/db build/deploy flow. Just keep this path in sync with any future changes to the db package’s build output layout.

packages/trpc/models/webhooks.ts (1)

68-80: Webhook delete now correctly uses rowsAffected for NOT_FOUND

Using res.rowsAffected === 0 after the scoped delete is the right way to detect that no webhook was deleted and keeps error semantics consistent with other models.

docker/Dockerfile (1)

43-45: DB build/deploy flow and libsql bindings copy look consistent with the new migration setup

  • Building the db package then deploying @karakeep/db to /db_migrations lines up with the init-db-migration script running node dist/migrate.js from that directory.
  • Manually copying /app/node_modules/@libsql into the final image ensures libsql’s native bindings are available despite Next.js standalone omitting optional dependencies.

This looks coherent with the libsql migration; just verify that the deployed /db_migrations tree indeed contains dist/migrate.js and that @libsql resolves correctly at runtime in the standalone app.

Also applies to: 110-113

packages/db/package.json (2)

9-9: LGTM: Build script migration to tsdown.

The switch to tsdown is appropriate for bundling the migration script with its dependencies (including workspace packages and native libsql bindings).


22-22: Version ^0.15.15 is current and has no known vulnerabilities.

@libsql/client v0.15.15 is the latest stable version as of December 2025. No direct vulnerabilities have been reported for the npm @libsql/client package. While CVE-2025-47736 exists in the related libsql-sqlite3-parser Rust crate, it only affects versions ≤ 0.13.0 and does not impact this dependency.

packages/e2e_tests/docker-compose.yml (1)

19-19: LGTM: WAL mode enablement for e2e tests.

Enabling WAL (Write-Ahead Logging) mode is appropriate for the LibSQL migration, as it supports better concurrency and aligns with LibSQL's operational characteristics.

packages/db/tsdown.config.ts (1)

1-22: LGTM: Well-structured tsdown configuration.

The configuration properly:

  • Externalizes native bindings (@libsql/client, libsql) that cannot be bundled
  • Bundles workspace packages (/^@karakeep//) to create a self-contained migration script
  • Uses modern ESM output targeting Node.js 22
  • Includes sourcemaps for debugging
packages/db/migrate.ts (1)

1-5: LGTM: Correct migration to LibSQL async pattern.

The changes properly:

  • Update the import to use the libsql migrator
  • Add await to handle the async migration (libsql operations are async unlike better-sqlite3)
packages/trpc/lib/__tests__/search.test.ts (1)

27-27: LGTM: Test updated for async DB initialization.

The change correctly awaits the now-async getInMemoryDB() function, aligning with the LibSQL migration where database initialization requires asynchronous operations.

packages/trpc/models/bookmarks.ts (1)

820-820: LGTM: Updated to use standard rowsAffected property.

The change from changes (better-sqlite3 specific) to rowsAffected (standard property used by libsql) correctly maintains the conditional logic for asset cleanup.

apps/workers/workers/adminMaintenance/tasks/migrateLinkHtmlContent.ts (1)

117-117: LGTM: Updated to use standard rowsAffected property.

The change from changes to rowsAffected aligns with the LibSQL migration, maintaining the same error-throwing behavior when no rows are affected.

packages/trpc/routers/prompts.ts (1)

110-110: LGTM: Updated to use standard rowsAffected property.

The change from changes to rowsAffected correctly adapts to the LibSQL client's API while preserving the NOT_FOUND error handling logic.

packages/trpc/routers/admin.ts (1)

379-390: rowsAffected‑based NOT_FOUND checks align with libsql/drizzle semantics

Using result.rowsAffected instead of changes in updateUser and resetPassword preserves the intended behavior (throw NOT_FOUND when no row is updated) and matches the libsql/Drizzle result shape. No further issues from this change.

Also applies to: 402-413

packages/trpc/routers/assets.ts (2)

166-180: Explicit behavior: "immediate" on the replace transaction is appropriate

Adding { behavior: "immediate" } to the ctx.db.transaction call keeps the existing atomic delete+update behavior while making the locking semantics explicit and consistent with the rest of the libsql migration. No issues from this change.


212-221: rowsAffected check in detachAsset correctly drives NOT_FOUND

Switching the delete result check to result.rowsAffected == 0 preserves the intended behavior (throw NOT_FOUND when nothing was deleted) and matches other libsql/Drizzle usage in the PR. Note that with the fix to ensureAssetOwnership above, this will correctly distinguish “asset exists but doesn’t match bookmark” from the happy path.

apps/workers/workers/videoWorker.ts (1)

188-206: Immediate transaction around updateAsset is consistent and safe

Wrapping updateAsset in db.transaction(..., { behavior: "immediate" }) maintains atomicity for deleting the old asset row and inserting the new one, and aligns this worker with the libsql transaction pattern used elsewhere. No additional issues introduced here.

packages/trpc/testUtils.ts (1)

9-11: Async test DB helper and types are correctly updated

Making getTestDB async, updating TestDB to Awaited<ReturnType<typeof getTestDB>>, and awaiting it in buildTestContext keeps the test utilities aligned with the async getInMemoryDB(true) helper and preserves type safety for callers. This matches the Drizzle‑backed test DB pattern described for the @karakeep/db package.

Based on learnings, this stays consistent with the Drizzle ORM usage for the db package.

Also applies to: 13-14, 65-79

packages/trpc/models/users.ts (1)

218-229: rowsAffected checks in email verification and user deletion are correct

Switching from changes to result.rowsAffected / res.rowsAffected in verifyEmail and deleteInternal preserves the intended behavior of throwing NOT_FOUND when no user row is updated or deleted, and matches the libsql result shape used elsewhere in the PR. No issues detected here.

Also applies to: 363-371

packages/trpc/models/tags.ts (1)

181-196: rowsAffected usage and immediate merge transaction for tags are correct

  • deleteUnused now returning res.rowsAffected is the right adaptation from changes and gives a clear count of how many unused tags were removed.
  • The merge flow’s ctx.db.transaction(..., { behavior: "immediate" }) keeps the unlink → reinsert → delete‑tags sequence atomic and matches the new transaction pattern elsewhere.
  • In delete, checking res.rowsAffected === 0 before throwing NOT_FOUND correctly signals when the tag no longer exists or doesn’t belong to the current user.

All of these changes look consistent with the libsql migration.

Also applies to: 246-283, 311-323

packages/trpc/models/lists.ts (2)

464-477: rowsAffected‑driven NOT_FOUND / BAD_REQUEST logic for lists and collaborators looks good

Across List.delete, removeCollaborator, leaveList, updateCollaboratorRole, and ManualList.removeBookmark, switching from result.changes to result.rowsAffected preserves the intended semantics:

  • rowsAffected === 0NOT_FOUND (no such list/collaborator) or BAD_REQUEST (bookmark already not in list).
  • rowsAffected > 0 → success.

This matches the libsql result shape and keeps the API behavior unchanged.

Also applies to: 629-644, 651-675, 686-702, 943-961


979-1015: Merging lists inside an immediate transaction improves atomicity

Wrapping the bookmark move (insert with onConflictDoNothing) and optional source list deletion in a single ctx.db.transaction(..., { behavior: "immediate" }) ensures:

  • Target list entries are created before the source list is removed.
  • Deleting the source list correctly cascades its bookmarksInLists rows.
  • The whole merge is atomic even under concurrent access.

This change is aligned with the broader transaction refactor.

packages/db/drizzle.ts (2)

25-41: LGTM!

The pragma configuration is well-structured. The busy_timeout of 20 seconds provides adequate time for lock contention, and the conditional WAL mode based on config is a good pattern.


20-23: Set concurrency to undefined if unlimited concurrent connections are intended.

The concurrency: 0 setting does not enable unlimited concurrent connections. According to libsql documentation, concurrency sets the maximum number of simultaneous requests (default 20). To disable the concurrency limit entirely, set concurrency to undefined instead of 0.

Likely an incorrect or invalid review comment.

packages/trpc/models/rules.ts (2)

90-136: LGTM!

The transaction with { behavior: "immediate" } is correct for the libsql migration. The rule creation logic properly handles event-based field mapping and action insertion within a single atomic transaction.


212-225: LGTM!

The delete operation correctly uses rowsAffected for the NOT_FOUND check. Related actions will be deleted via the foreign key cascade defined in the schema.

apps/workers/workers/crawlerWorker.ts (3)

973-1006: LGTM!

The transaction consolidation in handleAsAssetBookmark properly groups the asset update, bookmark asset insertion, bookmark type change, and link deletion into a single atomic operation with immediate behavior. This ensures consistency when transforming a link bookmark to an asset bookmark.


1193-1263: LGTM!

The consolidated transaction correctly handles all metadata and asset updates atomically. The pattern of collecting deletion tasks during the transaction and executing them after commit (Line 1266) is appropriate—file deletions are idempotent and don't need transactional guarantees.


1288-1307: LGTM!

The archive update transaction follows the established pattern. The old asset cleanup after the transaction commit ensures data integrity.

packages/db/index.ts (2)

12-12: Good backward compatibility approach.

Re-exporting LibsqlError as SqliteError minimizes breaking changes for consumers of this package.


22-33: LGTM!

The getLibsqlError helper correctly handles both direct LibsqlError instances and errors with a nested cause. This is useful for extracting the underlying database error from Drizzle ORM's error wrapping.

packages/trpc/routers/bookmarks.ts (3)

147-279: LGTM!

The createBookmark mutation correctly consolidates all bookmark creation logic (base bookmark, link/text/asset content, and asset ownership) into a single atomic transaction with immediate behavior. The switch statement cleanly handles the three bookmark types.


521-549: LGTM!

The deprecated updateBookmarkText mutation correctly uses .returning() to verify the row exists, and properly updates modifiedAt within the same transaction.


729-850: LGTM!

The updateTags mutation correctly handles the complex tag attach/detach logic within a single transaction. The use of onConflictDoNothing() for both tag creation (Line 794) and relation insertion (Line 830) ensures idempotency without errors.

"@tsconfig/node22": "^22.0.0",
"@types/better-sqlite3": "^7.6.11",
"@vercel/ncc": "^0.38.4"
"tsdown": "^0.12.9"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

curl -s https://registry.npmjs.org/tsdown | jq '.["dist-tags"].latest'

Repository: karakeep-app/karakeep

Length of output: 73


🏁 Script executed:

cat -n packages/db/package.json

Repository: karakeep-app/karakeep

Length of output: 1391


Update tsdown to the latest version.

Current version 0.12.9 is outdated; the latest available version is 0.17.2. Update the dependency to "tsdown": "^0.17.2" to benefit from recent improvements and bug fixes.

🤖 Prompt for AI Agents
In packages/db/package.json around line 33, the tsdown dependency is pinned to
"^0.12.9"; update it to the current recommended version by changing the
dependency entry to "^0.17.2". After updating package.json, run the package
manager (npm/yarn/pnpm) to install and regenerate lockfile, and run the
test/build scripts to verify no breaking changes.

Comment on lines +181 to +200
if (input.actions.length > 0) {
await tx
.delete(ruleEngineActionsTable)
.where(eq(ruleEngineActionsTable.ruleId, input.id));
await tx.insert(ruleEngineActionsTable).values(
input.actions.map((action) => ({
ruleId: input.id,
userId: this.ctx.user.id,
action: JSON.stringify(action),
listId:
action.type === "addToList" || action.type === "removeFromList"
? action.listId
: null,
tagId:
action.type === "addTag" || action.type === "removeTag"
? action.tagId
: null,
})),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty actions array leaves orphaned action records.

When input.actions.length > 0, existing actions are deleted and new ones inserted. However, if input.actions.length === 0, the old actions are never deleted. If the intent is to clear actions when an empty array is provided, the delete should happen unconditionally.

-        if (input.actions.length > 0) {
-          await tx
-            .delete(ruleEngineActionsTable)
-            .where(eq(ruleEngineActionsTable.ruleId, input.id));
+        await tx
+          .delete(ruleEngineActionsTable)
+          .where(eq(ruleEngineActionsTable.ruleId, input.id));
+        if (input.actions.length > 0) {
           await tx.insert(ruleEngineActionsTable).values(
🤖 Prompt for AI Agents
In packages/trpc/models/rules.ts around lines 181 to 200, currently the code
only deletes existing action records when input.actions.length > 0, leaving
orphaned actions when an empty array is provided; change the flow to always run
the delete within the transaction (delete by ruleId unconditionally), then only
run the insert when input.actions.length > 0 so that providing an empty actions
array clears existing actions but still avoids unnecessary inserts.

Comment on lines +404 to 412
if (result.rowsAffected == 0) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Attempting to set link attributes for non-text type bookmark",
});
}
somethingChanged = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix incorrect error message for text bookmark update.

The error message says "Attempting to set link attributes" but this is for updating text content on a non-text bookmark.

             if (result.rowsAffected == 0) {
               throw new TRPCError({
                 code: "BAD_REQUEST",
                 message:
-                  "Attempting to set link attributes for non-text type bookmark",
+                  "Attempting to set text content for non-text type bookmark",
               });
             }
🤖 Prompt for AI Agents
In packages/trpc/routers/bookmarks.ts around lines 404 to 412, the thrown
TRPCError message incorrectly references "Attempting to set link attributes"
while the check is for updating text content on a non-text bookmark; change the
error message to accurately reflect the condition (for example: "Attempting to
set text content for non-text type bookmark" or similar clear wording) so the
error describes the actual operation that was attempted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants