Skip to content

refactor: deduplicate shared logic across monorepo#1472

Merged
Kitenite merged 18 commits into
mainfrom
kitenite/duplicate-logic
Feb 13, 2026
Merged

refactor: deduplicate shared logic across monorepo#1472
Kitenite merged 18 commits into
mainfrom
kitenite/duplicate-logic

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 13, 2026

Summary

Deduplicates shared logic across the monorepo:

  • Deduplicate org membership checks — Extracted findOrgMembership into @superset/db/utils, replacing 6 inline queries across tRPC routers and API routes.
  • Extract shared image upload utility — Consolidated duplicate upload logic from user and organization routers into packages/trpc/src/lib/upload.ts.

Test plan

  • Full typecheck passes (18/18 packages)
  • bun run lint:fix passes
  • bunx sherif passes
  • All tests pass (1213 pass, 0 fail)
  • Verify image upload flows (user avatar, org logo)
  • Verify integration connect flows (GitHub, Linear, Slack)

Summary by CodeRabbit

  • New Features

    • Added centralized content utilities for date formatting, slugification, and date normalization across marketing modules.
    • Added image upload utilities with validation and blob storage integration.
  • Refactor

    • Consolidated membership verification across API integration routes.
    • Standardized date handling in blog, changelog, and comparison content processing.
    • Simplified image handling in organization and user management flows.

…ient

Move duplicate tRPC client code (react.tsx, server.tsx, query-client.ts)
from apps/web and apps/admin into packages/trpc/src/client/ with factory
functions. Each app now uses thin wrappers that pass env-specific config.
Move the org membership query into @superset/db/utils so both tRPC
procedures and API route handlers share the same base query instead
of duplicating the drizzle query inline across 7+ files.
Deduplicate image upload logic (mime validation, base64 decoding, size
check, old image deletion, blob upload) from uploadAvatar and uploadLogo
into a shared uploadImage utility in packages/trpc/src/lib/upload.ts.
…lution, response formatting)

- Import taskPriorityValues from @superset/db/enums instead of redefining
- Extract resolveTaskId and taskIdCondition for UUID/slug resolution
- Extract formatMcpResponse helper for consistent response formatting
- Extract isPriority type guard to shared utils
Deduplicate the identical disconnect logic from linear and slack routers
into a shared disconnectIntegration function parameterized by provider.
Move the device online threshold constant (60s) from packages/trpc and
packages/mcp into @superset/shared/constants as the single source of truth.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

This PR refactors membership lookups and utility functions across the codebase. It introduces a new findOrgMembership helper to centralize organization membership queries, creates a content-utils module for shared date formatting and slug generation, and adds image upload utilities. Multiple API routes and TRPC routers are updated to use these new centralized functions, replacing direct database queries and inline implementations.

Changes

Cohort / File(s) Summary
Integration API Routes
apps/api/src/app/api/github/install/route.ts, apps/api/src/app/api/integrations/linear/connect/route.ts, apps/api/src/app/api/integrations/slack/connect/route.ts, apps/api/src/app/api/integrations/slack/link/route.ts
Replaced direct database membership queries with new findOrgMembership helper; updated imports and preserved authorization/validation logic.
Marketing Content Utilities
apps/marketing/src/lib/content-utils.ts
New module exporting formatContentDate, slugify, and normalizeContentDate functions for shared content processing.
Marketing Blog & Changelog Utils
apps/marketing/src/lib/blog-utils.ts, apps/marketing/src/lib/blog.ts, apps/marketing/src/lib/changelog-utils.ts, apps/marketing/src/lib/changelog.ts
Delegated date formatting and normalization to content-utils; re-exported slugify from shared module; updated ChangelogEntry interface with new fields.
Marketing Comparison Utils
apps/marketing/src/lib/compare-utils.ts, apps/marketing/src/lib/compare.ts
Expanded ComparisonPage interface with new fields; replaced manual date normalization with normalizeContentDate helper; updated date formatting to use formatContentDate.
Database Membership Utils
packages/db/src/utils/membership.ts, packages/db/src/utils/index.ts
Introduced new findOrgMembership function to centralize organization membership lookups; added re-export in utils barrel.
TRPC Upload Utilities
packages/trpc/src/lib/upload.ts
New module exporting uploadImage and generateImagePathname for centralized image handling with validation and blob storage management.
TRPC Routers
packages/trpc/src/router/integration/utils.ts, packages/trpc/src/router/organization/organization.ts, packages/trpc/src/router/user/user.ts
Updated to use findOrgMembership for membership verification; replaced inline image upload logic with centralized uploadImage and generateImagePathname utilities.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

Poem

🐰 The utils hop and consolidate,
From scattered queries to one clean gate,
Date dances with slugs so neat,
Images upload, membership's fleet,
Refactored code makes review sweet! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: deduplicate shared logic across monorepo' clearly and concisely summarizes the main objective of the changeset—extracting and consolidating duplicated logic.
Description check ✅ Passed The PR description covers all essential sections: a clear summary explaining the deduplication efforts with specific examples, related issues context, type of change (Refactor), and a detailed test plan with verification items.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kitenite/duplicate-logic

No actionable comments were generated in the recent review. 🎉


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
Copy Markdown
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: 2

🤖 Fix all issues with AI agents
In `@packages/trpc/src/lib/upload.ts`:
- Around line 25-27: The base64 extraction logic using
fileData.split("base64,")[1] falls back to the full data URI when the payload is
empty, causing Buffer.from(fullDataUri, "base64") to produce garbage; update the
code that computes base64Data (the base64Data variable derived from fileData) to
explicitly detect an empty payload after "base64," and treat it as an error
(e.g., throw or return a validation error) instead of returning fileData, so
downstream Buffer.from(base64Data, "base64") only receives a non-empty base64
payload; ensure the new guard is applied before any size checks or Buffer.from
calls.
- Around line 38-49: Reorder the blob operations so the new upload is performed
before deleting the old image: call put(pathname, buffer, { access: "public",
contentType: mimeType }) first and only if it succeeds attempt del(existingUrl).
Wrap the del(existingUrl) call in a try/catch and log any deletion error with
context (include existingUrl and the error) instead of silently swallowing it;
do not delete the old blob until put has completed successfully, and if del
fails after a successful put, log the failure but do not revert the successful
put.
🧹 Nitpick comments (9)
apps/marketing/src/lib/content-utils.ts (2)

1-2: Unused constant _MONTH_LONG.

The underscore prefix signals intentional non-use, but if it's truly not needed, consider removing it to avoid dead code.


36-42: Redundant truthy-value branch and inconsistent numeric handling.

Lines 40-42 catch any truthy non-string/non-number/non-Date value (e.g., objects, booleans), producing potentially meaningless strings like "[object Object]" or "true". Also, numeric values (e.g., Unix timestamps) are returned as raw strings via String(value) rather than going through toDateInput, which could produce unexpected date representations downstream.

Consider either:

  1. Removing the catch-all truthy branch (lines 40-42) since it overlaps with the string/number check and could mask bad data.
  2. Routing numbers through toDateInput for consistency with the Date branch.
Suggested simplification
 	if (typeof value === "string" || typeof value === "number") {
-		return String(value);
-	}
-
-	if (value) {
-		return String(value);
+		return typeof value === "number" ? toDateInput(value) : value;
 	}
packages/trpc/src/router/integration/utils.ts (2)

8-11: Consider using object parameters for verifyOrgMembership and verifyOrgAdmin.

These functions take 2 positional string arguments (userId, organizationId) which are easy to swap accidentally. The new disconnectIntegration correctly uses object parameters — consider aligning these older helpers for consistency.

As per coding guidelines: "Use object parameters for functions with 2 or more parameters instead of positional arguments."


37-59: disconnectIntegration silently returns success: false when no connection exists.

The callers (Linear/Slack disconnect mutations) return this result directly to the client. A missing connection during disconnect could indicate a bug or race condition. Consider throwing a TRPCError with NOT_FOUND instead, which would be more consistent with verifyOrgMembership's throw-on-missing pattern and the coding guideline on TRPCError codes.

That said, if the intent is idempotent disconnects (no-op when already disconnected), the current approach is fine — just worth confirming the design intent.

packages/trpc/src/lib/upload.ts (1)

61-63: Math.random() for pathname generation is fine for uniqueness but could collide under high concurrency.

Not a blocking issue, but crypto.randomUUID() would be more robust and already imported (node:crypto is used elsewhere in this PR). Worth considering for a shared utility that may see wider use.

packages/mcp/src/tools/tasks/utils.ts (1)

14-41: Consider reusing taskIdCondition inside resolveTaskId to DRY the UUID-detection logic.

Both resolveTaskId and taskIdCondition independently instantiate z.string().uuid() and branch on UUID vs slug. resolveTaskId could delegate to taskIdCondition to avoid duplicating this pattern.

♻️ Suggested refactor
 export async function resolveTaskId({
 	taskId,
 	organizationId,
 }: {
 	taskId: string;
 	organizationId: string;
 }) {
-	const isUuid = z.string().uuid().safeParse(taskId).success;
-
 	const [task] = await db
 		.select({ id: tasks.id })
 		.from(tasks)
 		.where(
 			and(
-				isUuid ? eq(tasks.id, taskId) : eq(tasks.slug, taskId),
+				taskIdCondition(taskId),
 				eq(tasks.organizationId, organizationId),
 				isNull(tasks.deletedAt),
 			),
 		)
 		.limit(1);
 
 	return task ?? null;
 }
packages/mcp/src/tools/tasks/create-task/create-task.ts (1)

150-152: isPriority check is redundant here — Zod already validates the value.

input.priority is validated by z.enum(taskPriorityValues).default("none") in taskInputSchema, so it is always a valid TaskPriority by the time you reach this line. The guard adds no safety and could be removed for clarity.

♻️ Simplified assignment
-			const priority: TaskPriority = isPriority(input.priority)
-				? input.priority
-				: "none";
+			const priority: TaskPriority = input.priority;
packages/mcp/src/tools/tasks/delete-task/delete-task.ts (1)

31-46: Sequential resolveTaskId calls create N+1 queries — consider batching if this becomes a concern.

Each task ID triggers a separate DB query (up to 25). Given the max-25 cap and MCP tool context, this is acceptable, but a batch resolver could be a future optimization.

packages/mcp/src/tools/tasks/update-task/update-task.ts (1)

117-131: Sequential per-task updates are not transaction-wrapped — partial updates possible on failure.

Unlike create-task (which uses dbWs.transaction), this handler issues individual UPDATE statements in a loop. If a mid-batch update fails, earlier updates are already committed. Consider wrapping the loop in a dbWs.transaction for atomicity, consistent with the create-task pattern.

♻️ Wrap updates in a transaction
-		const updatedTasks: { id: string; slug: string; title: string }[] = [];
-
-		for (const { taskId, updateData } of resolvedUpdates) {
-			const [task] = await dbWs
-				.update(tasks)
-				.set(updateData)
-				.where(eq(tasks.id, taskId))
-				.returning({
-					id: tasks.id,
-					slug: tasks.slug,
-					title: tasks.title,
-				});
-
-			if (task) {
-				updatedTasks.push(task);
-			}
-		}
+		const updatedTasks = await dbWs.transaction(async (tx) => {
+			const results: { id: string; slug: string; title: string }[] = [];
+			for (const { taskId, updateData } of resolvedUpdates) {
+				const [task] = await tx
+					.update(tasks)
+					.set(updateData)
+					.where(eq(tasks.id, taskId))
+					.returning({
+						id: tasks.id,
+						slug: tasks.slug,
+						title: tasks.title,
+					});
+				if (task) {
+					results.push(task);
+				}
+			}
+			return results;
+		});

Comment on lines +25 to +27
const base64Data = fileData.includes("base64,")
? fileData.split("base64,")[1] || fileData
: fileData;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Edge case in base64 extraction when data after "base64," is empty.

If fileData is "data:image/png;base64," (no payload after the comma), split("base64,")[1] yields "", which is falsy, so the fallback returns the entire data URI string. Buffer.from(fullDataUri, "base64") will silently produce garbage bytes rather than failing. This would result in a corrupted upload that passes the size check.

Consider a stricter guard:

Proposed fix
 	const base64Data = fileData.includes("base64,")
-		? fileData.split("base64,")[1] || fileData
+		? fileData.split("base64,")[1] ?? fileData
 		: fileData;
+
+	if (!base64Data) {
+		throw new TRPCError({
+			code: "BAD_REQUEST",
+			message: "Invalid file data: empty base64 payload",
+		});
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const base64Data = fileData.includes("base64,")
? fileData.split("base64,")[1] || fileData
: fileData;
const base64Data = fileData.includes("base64,")
? fileData.split("base64,")[1] ?? fileData
: fileData;
if (!base64Data) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid file data: empty base64 payload",
});
}
🤖 Prompt for AI Agents
In `@packages/trpc/src/lib/upload.ts` around lines 25 - 27, The base64 extraction
logic using fileData.split("base64,")[1] falls back to the full data URI when
the payload is empty, causing Buffer.from(fullDataUri, "base64") to produce
garbage; update the code that computes base64Data (the base64Data variable
derived from fileData) to explicitly detect an empty payload after "base64," and
treat it as an error (e.g., throw or return a validation error) instead of
returning fileData, so downstream Buffer.from(base64Data, "base64") only
receives a non-empty base64 payload; ensure the new guard is applied before any
size checks or Buffer.from calls.

Comment on lines +38 to +49
if (existingUrl) {
try {
await del(existingUrl);
} catch {
// Old image doesn't exist or isn't in blob storage
}
}

const blob = await put(pathname, buffer, {
access: "public",
contentType: mimeType,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Old image is deleted before the new upload succeeds — data loss on upload failure.

If del(existingUrl) succeeds but put(pathname, ...) subsequently throws, the old image is gone and no new image was stored. Consider reordering: upload first, then delete the old blob only after the new one is confirmed.

Also, the catch block on Line 41 silently swallows the deletion error. Per coding guidelines, errors should at minimum be logged with context.

Proposed fix: reorder operations and log deletion errors
-	if (existingUrl) {
-		try {
-			await del(existingUrl);
-		} catch {
-			// Old image doesn't exist or isn't in blob storage
-		}
-	}
-
 	const blob = await put(pathname, buffer, {
 		access: "public",
 		contentType: mimeType,
 	});
 
+	if (existingUrl) {
+		try {
+			await del(existingUrl);
+		} catch (error) {
+			console.error("[upload/uploadImage] Failed to delete old image:", error);
+		}
+	}
+
 	return blob.url;

As per coding guidelines, "Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (existingUrl) {
try {
await del(existingUrl);
} catch {
// Old image doesn't exist or isn't in blob storage
}
}
const blob = await put(pathname, buffer, {
access: "public",
contentType: mimeType,
});
const blob = await put(pathname, buffer, {
access: "public",
contentType: mimeType,
});
if (existingUrl) {
try {
await del(existingUrl);
} catch (error) {
console.error("[upload/uploadImage] Failed to delete old image:", error);
}
}
return blob.url;
🤖 Prompt for AI Agents
In `@packages/trpc/src/lib/upload.ts` around lines 38 - 49, Reorder the blob
operations so the new upload is performed before deleting the old image: call
put(pathname, buffer, { access: "public", contentType: mimeType }) first and
only if it succeeds attempt del(existingUrl). Wrap the del(existingUrl) call in
a try/catch and log any deletion error with context (include existingUrl and the
error) instead of silently swallowing it; do not delete the old blob until put
has completed successfully, and if del fails after a successful put, log the
failure but do not revert the successful put.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 13, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app
  • ✅ Streams Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
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: 1

🤖 Fix all issues with AI agents
In `@packages/trpc/package.json`:
- Around line 39-47: Remove "react" and "@tanstack/react-query" from
dependencies in package.json and add them under peerDependencies with the same
semver ranges (e.g., "react": "19.2.0", "@tanstack/react-query": "^5.90.19"),
while keeping them in devDependencies for local typechecking; this package
exports hooks (useTRPC, TRPCProvider, createTRPCReactProvider) so moving these
two to peerDependencies prevents duplicate React/React-Query instances and
broken hook/cache behavior.

Comment thread packages/trpc/package.json Outdated
Comment on lines +39 to +47
"@tanstack/react-query": "^5.90.19",
"@trpc/client": "^11.7.1",
"@trpc/server": "^11.7.1",
"@trpc/tanstack-react-query": "^11.7.1",
"@upstash/qstash": "^2.8.4",
"@vercel/blob": "^2.0.0",
"@vercel/kv": "^3.0.0",
"drizzle-orm": "0.45.1",
"react": "19.2.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, confirm the current state of packages/trpc/package.json
echo "=== Current state of packages/trpc/package.json (lines 35-55) ==="
sed -n '35,55p' packages/trpc/package.json

echo ""
echo "=== Full dependencies section of packages/trpc/package.json ==="
jq '.dependencies, .peerDependencies, .devDependencies | keys' packages/trpc/package.json

Repository: superset-sh/superset

Length of output: 1186


🏁 Script executed:

#!/bin/bash
# Check how packages/trpc is consumed - find imports across apps
echo "=== Imports of `@trpc` in web app ==="
fd -t f '\.ts$' '\.tsx$' apps/web --max-depth 3 -x rg '@trpc' {} + 2>/dev/null | head -20

echo ""
echo "=== Imports of `@trpc` in admin app ==="
fd -t f '\.ts$' '\.tsx$' apps/admin --max-depth 3 -x rg '@trpc' {} + 2>/dev/null | head -10

Repository: superset-sh/superset

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Compare how other shared packages declare react dependencies
echo "=== Checking react declaration in other packages/ ==="
fd package.json 'packages' --max-depth 2 --exec sh -c '
  path="$1"
  echo "--- $path ---"
  if jq -e ".dependencies.react" "$path" >/dev/null 2>&1; then
    echo "dependencies.react: $(jq -r ".dependencies.react" "$path")"
  fi
  if jq -e ".peerDependencies.react" "$path" >/dev/null 2>&1; then
    echo "peerDependencies.react: $(jq -r ".peerDependencies.react" "$path")"
  fi
  if ! jq -e ".dependencies.react, .peerDependencies.react" "$path" >/dev/null 2>&1; then
    echo "react: not declared"
  fi
' _ {}

Repository: superset-sh/superset

Length of output: 827


🏁 Script executed:

#!/bin/bash
# Check if web and admin apps have react in their dependencies
echo "=== apps/web/package.json react declaration ==="
jq '.dependencies.react, .peerDependencies.react' apps/web/package.json 2>/dev/null

echo ""
echo "=== apps/admin/package.json react declaration ==="
jq '.dependencies.react, .peerDependencies.react' apps/admin/package.json 2>/dev/null

Repository: superset-sh/superset

Length of output: 190


🏁 Script executed:

#!/bin/bash
# Double-check if admin app might use `@trpc` indirectly or through a different import pattern
echo "=== Checking admin app directory structure ==="
ls -la apps/admin/src/ 2>/dev/null | head -20

echo ""
echo "=== Searching for 'trpc' (case-insensitive) in admin ==="
rg -i 'trpc' apps/admin/ --type ts --type tsx 2>/dev/null | head -10

Repository: superset-sh/superset

Length of output: 704


🏁 Script executed:

#!/bin/bash
# Check what packages/trpc actually exports - is it frontend-only or mixed?
echo "=== packages/trpc exports and main entry ==="
head -30 packages/trpc/package.json | grep -A 2 '"exports"\|"main"'

echo ""
echo "=== List of files in packages/trpc/src ==="
find packages/trpc/src -type f -name '*.ts' -o -name '*.tsx' 2>/dev/null | head -20

Repository: superset-sh/superset

Length of output: 994


🏁 Script executed:

#!/bin/bash
# Check what's in apps/admin/src/trpc directory
echo "=== Contents of apps/admin/src/trpc ==="
find apps/admin/src/trpc -type f 2>/dev/null | head -20

echo ""
echo "=== Sample from apps/admin/src/trpc/client.ts ==="
head -20 apps/admin/src/trpc/client.ts 2>/dev/null

echo ""
echo "=== How does admin app import from packages/trpc ==="
rg 'from.*trpc|import.*trpc' apps/admin/src/ --type ts --type tsx 2>/dev/null | head -10

Repository: superset-sh/superset

Length of output: 269


🏁 Script executed:

#!/bin/bash
# Check the actual purpose - look at react.tsx exports
echo "=== packages/trpc/src/client/react.tsx first 50 lines ==="
head -50 packages/trpc/src/client/react.tsx

echo ""
echo "=== Check if react.tsx exports React hooks ==="
rg 'export.*useQuery|export.*useMutation|export.*Provider|export function' packages/trpc/src/client/react.tsx | head -15

Repository: superset-sh/superset

Length of output: 1686


Move react and @tanstack/react-query to peerDependencies.

This package exports React hooks (useTRPC, TRPCProvider, createTRPCReactProvider) and is consumed by multiple apps (web, admin). Declaring react and @tanstack/react-query as direct dependencies risks bundling separate instances, causing hook errors and broken cache sharing. The monorepo already follows this pattern in packages/ui and packages/durable-session.

Move both to peerDependencies and keep in devDependencies for local typechecking:

Suggested diff
 	"dependencies": {
 		...
-		"@tanstack/react-query": "^5.90.19",
 		"@trpc/client": "^11.7.1",
 		"@trpc/server": "^11.7.1",
 		"@trpc/tanstack-react-query": "^11.7.1",
 		...
-		"react": "19.2.0",
 		...
 	},
+	"peerDependencies": {
+		"react": "^19.0.0",
+		"@tanstack/react-query": "^5.0.0"
+	},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@tanstack/react-query": "^5.90.19",
"@trpc/client": "^11.7.1",
"@trpc/server": "^11.7.1",
"@trpc/tanstack-react-query": "^11.7.1",
"@upstash/qstash": "^2.8.4",
"@vercel/blob": "^2.0.0",
"@vercel/kv": "^3.0.0",
"drizzle-orm": "0.45.1",
"react": "19.2.0",
"dependencies": {
"@trpc/client": "^11.7.1",
"@trpc/server": "^11.7.1",
"@trpc/tanstack-react-query": "^11.7.1",
"@upstash/qstash": "^2.8.4",
"@vercel/blob": "^2.0.0",
"@vercel/kv": "^3.0.0",
"drizzle-orm": "0.45.1"
},
"peerDependencies": {
"react": "^19.0.0",
"@tanstack/react-query": "^5.0.0"
},
"devDependencies": {
"react": "^19.0.0",
"@tanstack/react-query": "^5.0.0"
}
🤖 Prompt for AI Agents
In `@packages/trpc/package.json` around lines 39 - 47, Remove "react" and
"@tanstack/react-query" from dependencies in package.json and add them under
peerDependencies with the same semver ranges (e.g., "react": "19.2.0",
"@tanstack/react-query": "^5.90.19"), while keeping them in devDependencies for
local typechecking; this package exports hooks (useTRPC, TRPCProvider,
createTRPCReactProvider) so moving these two to peerDependencies prevents
duplicate React/React-Query instances and broken hook/cache behavior.

@Kitenite Kitenite merged commit 63c68d2 into main Feb 13, 2026
15 checks passed
@Kitenite Kitenite deleted the kitenite/duplicate-logic branch February 13, 2026 23:59
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.

1 participant