diff --git a/.changeset/fix-dependency-bundling-issues.md b/.changeset/fix-dependency-bundling-issues.md new file mode 100644 index 000000000..d8580d4cf --- /dev/null +++ b/.changeset/fix-dependency-bundling-issues.md @@ -0,0 +1,23 @@ +--- +"@tanstack/offline-transactions": patch +"@tanstack/query-db-collection": patch +--- + +Fix dependency bundling issues by moving @tanstack/db to peerDependencies + +**What Changed:** + +Moved `@tanstack/db` from regular dependencies to peerDependencies in: + +- `@tanstack/offline-transactions` +- `@tanstack/query-db-collection` + +Removed `@opentelemetry/api` dependency from `@tanstack/offline-transactions`. + +**Why:** + +These extension packages incorrectly declared `@tanstack/db` as both a regular dependency AND a peerDependency simultaneously. This caused lock files to develop conflicting versions, resulting in multiple instances of `@tanstack/db` being installed in consuming applications. + +The fix removes `@tanstack/db` from regular dependencies and keeps it only as a peerDependency. This ensures only one version of `@tanstack/db` is installed in the dependency tree, preventing version conflicts. + +For local development, `@tanstack/db` remains in devDependencies so the packages can be built and tested independently. diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index f5f53ba1c..c505927fc 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -50,12 +50,10 @@ "typecheck": "tsc --noEmit", "lint": "eslint src" }, - "dependencies": { - "@opentelemetry/api": "^1.9.0", - "@tanstack/db": "workspace:*" - }, + "dependencies": {}, "devDependencies": { "@types/node": "^20.0.0", + "@tanstack/db": "workspace:*", "eslint": "^8.57.0", "typescript": "^5.5.4", "vitest": "^3.2.4" diff --git a/packages/offline-transactions/src/api/OfflineAction.ts b/packages/offline-transactions/src/api/OfflineAction.ts index 9c8915b70..c81efd7da 100644 --- a/packages/offline-transactions/src/api/OfflineAction.ts +++ b/packages/offline-transactions/src/api/OfflineAction.ts @@ -1,4 +1,3 @@ -import { SpanStatusCode, context, trace } from "@opentelemetry/api" import { OnMutateMustBeSynchronousError } from "@tanstack/db" import { OfflineTransaction } from "./OfflineTransaction" import type { Transaction } from "@tanstack/db" @@ -45,30 +44,15 @@ export function createOfflineAction( } }) - // Immediately commit with span instrumentation - const tracer = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`) - const span = tracer.startSpan(`offlineAction.${mutationFnName}`) - const ctx = trace.setSpan(context.active(), span) - console.log(`starting offlineAction span`, { tracer, span, ctx }) - - // Execute the commit within the span context - // The key is to return the promise synchronously from context.with() so context binds to it - const commitPromise = context.with(ctx, () => { - // Return the promise synchronously - this is critical for context propagation in browsers - return (async () => { - try { - await transaction.commit() - span.setStatus({ code: SpanStatusCode.OK }) - span.end() - console.log(`ended offlineAction span - success`) - } catch (error) { - span.recordException(error as Error) - span.setStatus({ code: SpanStatusCode.ERROR }) - span.end() - console.log(`ended offlineAction span - error`) - } - })() - }) + // Immediately commit + const commitPromise = (async () => { + try { + await transaction.commit() + console.log(`offlineAction committed - success`) + } catch { + console.log(`offlineAction commit failed - error`) + } + })() // Don't await - this is fire-and-forget for optimistic actions // But catch to prevent unhandled rejection diff --git a/packages/offline-transactions/src/api/OfflineTransaction.ts b/packages/offline-transactions/src/api/OfflineTransaction.ts index 25f4ca18a..d8be88d33 100644 --- a/packages/offline-transactions/src/api/OfflineTransaction.ts +++ b/packages/offline-transactions/src/api/OfflineTransaction.ts @@ -1,4 +1,3 @@ -import { context, trace } from "@opentelemetry/api" import { createTransaction } from "@tanstack/db" import { NonRetriableError } from "../types" import type { PendingMutation, Transaction } from "@tanstack/db" @@ -40,9 +39,6 @@ export class OfflineTransaction { mutationFn: async () => { // This is the blocking mutationFn that waits for the executor // First persist the transaction to the outbox - const activeSpan = trace.getSpan(context.active()) - const spanContext = activeSpan?.spanContext() - const offlineTransaction: OfflineTransactionType = { id: this.offlineId, mutationFnName: this.mutationFnName, @@ -53,14 +49,7 @@ export class OfflineTransaction { retryCount: 0, nextAttemptAt: Date.now(), metadata: this.metadata, - spanContext: spanContext - ? { - traceId: spanContext.traceId, - spanId: spanContext.spanId, - traceFlags: spanContext.traceFlags, - traceState: spanContext.traceState?.serialize(), - } - : undefined, + spanContext: undefined, version: 1, } diff --git a/packages/offline-transactions/src/executor/TransactionExecutor.ts b/packages/offline-transactions/src/executor/TransactionExecutor.ts index 5d8cde116..437c26802 100644 --- a/packages/offline-transactions/src/executor/TransactionExecutor.ts +++ b/packages/offline-transactions/src/executor/TransactionExecutor.ts @@ -1,35 +1,12 @@ -import { createTraceState } from "@opentelemetry/api" import { DefaultRetryPolicy } from "../retry/RetryPolicy" import { NonRetriableError } from "../types" import { withNestedSpan } from "../telemetry/tracer" -import type { SpanContext } from "@opentelemetry/api" import type { KeyScheduler } from "./KeyScheduler" import type { OutboxManager } from "../outbox/OutboxManager" -import type { - OfflineConfig, - OfflineTransaction, - SerializedSpanContext, -} from "../types" +import type { OfflineConfig, OfflineTransaction } from "../types" const HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`) -function toSpanContext( - serialized?: SerializedSpanContext -): SpanContext | undefined { - if (!serialized) { - return undefined - } - - return { - traceId: serialized.traceId, - spanId: serialized.spanId, - traceFlags: serialized.traceFlags, - traceState: serialized.traceState - ? createTraceState(serialized.traceState) - : undefined, - } -} - export class TransactionExecutor { private scheduler: KeyScheduler private outbox: OutboxManager @@ -131,9 +108,6 @@ export class TransactionExecutor { ;(err as any)[HANDLED_EXECUTION_ERROR] = true throw err } - }, - { - parentContext: toSpanContext(transaction.spanContext), } ) } catch (error) { diff --git a/packages/offline-transactions/src/telemetry/tracer.ts b/packages/offline-transactions/src/telemetry/tracer.ts index d12acda2e..f2469c289 100644 --- a/packages/offline-transactions/src/telemetry/tracer.ts +++ b/packages/offline-transactions/src/telemetry/tracer.ts @@ -1,28 +1,23 @@ -import { SpanStatusCode, context, trace } from "@opentelemetry/api" -import type { Span, SpanContext } from "@opentelemetry/api" - -const TRACER = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`) - export interface SpanAttrs { [key: string]: string | number | boolean | undefined } interface WithSpanOptions { - parentContext?: SpanContext + parentContext?: any } -function getParentContext(options?: WithSpanOptions) { - if (options?.parentContext) { - const parentSpan = trace.wrapSpanContext(options.parentContext) - return trace.setSpan(context.active(), parentSpan) - } - - return context.active() +// No-op span implementation +const noopSpan = { + setAttribute: () => {}, + setAttributes: () => {}, + setStatus: () => {}, + recordException: () => {}, + end: () => {}, } /** * Lightweight span wrapper with error handling. - * Uses OpenTelemetry API which is no-op when tracing is disabled. + * No-op implementation - telemetry has been removed. * * By default, creates spans at the current context level (siblings). * Use withNestedSpan if you want parent-child relationships. @@ -30,122 +25,42 @@ function getParentContext(options?: WithSpanOptions) { export async function withSpan( name: string, attrs: SpanAttrs, - fn: (span: Span) => Promise, - options?: WithSpanOptions + fn: (span: any) => Promise, + _options?: WithSpanOptions ): Promise { - const parentCtx = getParentContext(options) - const span = TRACER.startSpan(name, undefined, parentCtx) - - // Filter out undefined attributes - const filteredAttrs: Record = {} - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined) { - filteredAttrs[key] = value - } - } - - span.setAttributes(filteredAttrs) - - try { - const result = await fn(span) - span.setStatus({ code: SpanStatusCode.OK }) - return result - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }) - span.recordException(error as Error) - throw error - } finally { - span.end() - } + return await fn(noopSpan) } /** * Like withSpan but propagates context so child spans nest properly. - * Use this when you want operations inside fn to be child spans. + * No-op implementation - telemetry has been removed. */ export async function withNestedSpan( name: string, attrs: SpanAttrs, - fn: (span: Span) => Promise, - options?: WithSpanOptions + fn: (span: any) => Promise, + _options?: WithSpanOptions ): Promise { - const parentCtx = getParentContext(options) - const span = TRACER.startSpan(name, undefined, parentCtx) - - // Filter out undefined attributes - const filteredAttrs: Record = {} - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined) { - filteredAttrs[key] = value - } - } - - span.setAttributes(filteredAttrs) - - // Set the span as active context so child spans nest properly - const ctx = trace.setSpan(parentCtx, span) - - try { - // Execute the function within the span's context - const result = await context.with(ctx, () => fn(span)) - span.setStatus({ code: SpanStatusCode.OK }) - return result - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }) - span.recordException(error as Error) - throw error - } finally { - span.end() - } + return await fn(noopSpan) } /** * Creates a synchronous span for non-async operations + * No-op implementation - telemetry has been removed. */ export function withSyncSpan( name: string, attrs: SpanAttrs, - fn: (span: Span) => T, - options?: WithSpanOptions + fn: (span: any) => T, + _options?: WithSpanOptions ): T { - const parentCtx = getParentContext(options) - const span = TRACER.startSpan(name, undefined, parentCtx) - - // Filter out undefined attributes - const filteredAttrs: Record = {} - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined) { - filteredAttrs[key] = value - } - } - - span.setAttributes(filteredAttrs) - - try { - const result = fn(span) - span.setStatus({ code: SpanStatusCode.OK }) - return result - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }) - span.recordException(error as Error) - throw error - } finally { - span.end() - } + return fn(noopSpan) } /** * Get the current tracer instance + * No-op implementation - telemetry has been removed. */ export function getTracer() { - return TRACER + return null } diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index 9a798985a..f81fc14e5 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -3,10 +3,10 @@ "description": "TanStack Query collection for TanStack DB", "version": "0.2.42", "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@tanstack/db": "workspace:*" + "@standard-schema/spec": "^1.0.0" }, "devDependencies": { + "@tanstack/db": "workspace:*", "@tanstack/query-core": "^5.90.5", "@vitest/coverage-istanbul": "^3.2.4" }, @@ -31,6 +31,7 @@ "module": "dist/esm/index.js", "packageManager": "pnpm@10.19.0", "peerDependencies": { + "@tanstack/db": "*", "@tanstack/query-core": "^5.0.0", "typescript": ">=4.7" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 722476984..5894cd9c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -594,7 +594,7 @@ importers: version: 0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) drizzle-zod: specifier: ^0.8.3 - version: 0.8.3(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76) + version: 0.8.3(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.11) express: specifier: ^4.21.2 version: 4.21.2 @@ -773,14 +773,10 @@ importers: version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.7.0)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) packages/offline-transactions: - dependencies: - '@opentelemetry/api': - specifier: ^1.9.0 - version: 1.9.0 + devDependencies: '@tanstack/db': specifier: workspace:* version: link:../db - devDependencies: '@types/node': specifier: ^20.0.0 version: 20.19.24 @@ -830,13 +826,13 @@ importers: '@standard-schema/spec': specifier: ^1.0.0 version: 1.0.0 - '@tanstack/db': - specifier: workspace:* - version: link:../db typescript: specifier: '>=4.7' version: 5.9.3 devDependencies: + '@tanstack/db': + specifier: workspace:* + version: link:../db '@tanstack/query-core': specifier: ^5.90.5 version: 5.90.5 @@ -10878,7 +10874,8 @@ snapshots: '@oozcitak/util@8.3.8': {} - '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.0': + optional: true '@oxc-resolver/binding-android-arm-eabi@11.8.4': optional: true @@ -13724,11 +13721,6 @@ snapshots: pg: 8.16.3 postgres: 3.4.7 - drizzle-zod@0.8.3(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.76): - dependencies: - drizzle-orm: 0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) - zod: 3.25.76 - drizzle-zod@0.8.3(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.11): dependencies: drizzle-orm: 0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(gel@2.1.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)