@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1,244 lines (1,129 loc) • 40.9 kB
text/typescript
import { codedMessage, devBuild } from './error-message'
// Root error class for all TanStack DB errors
export class TanStackDBError extends Error {
constructor(message: string) {
super(message)
this.name = `TanStackDBError`
}
}
// Base error classes
export class NonRetriableError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `NonRetriableError`
}
}
// Schema validation error (exported from index for backward compatibility)
export class SchemaValidationError extends TanStackDBError {
type: `insert` | `update`
issues: ReadonlyArray<{
message: string
path?: ReadonlyArray<string | number | symbol>
}>
constructor(
type: `insert` | `update`,
issues: ReadonlyArray<{
message: string
path?: ReadonlyArray<string | number | symbol>
}>,
message?: string,
) {
const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues
.map((issue) => `\n- ${issue.message} - path: ${issue.path}`)
.join(``)}`
super(message || defaultMessage)
this.name = `SchemaValidationError`
this.type = type
this.issues = issues
}
}
// Module Instance Errors
export class DuplicateDbInstanceError extends TanStackDBError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Multiple instances of @tanstack/db detected!\n\n` +
`This causes transaction context to be lost because each instance maintains ` +
`its own transaction stack.\n\n` +
`Common causes:\n` +
`1. Different versions of @tanstack/db installed\n` +
`2. Incompatible peer dependency versions in packages\n` +
`3. Module resolution issues in bundler configuration\n\n` +
`To fix:\n` +
`1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)\n` +
`2. Force a single version using package manager overrides:\n` +
` - npm: "overrides" in package.json\n` +
` - pnpm: "pnpm.overrides" in package.json\n` +
` - yarn: "resolutions" in package.json\n` +
`3. Clear node_modules and lockfile, then reinstall\n\n` +
`To temporarily disable this check (not recommended):\n` +
`Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1\n\n` +
`See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`
: codedMessage(2),
)
this.name = `DuplicateDbInstanceError`
}
}
// Collection Configuration Errors
export class CollectionConfigurationError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `CollectionConfigurationError`
}
}
export class InvalidSyncPersistenceCapabilityError extends CollectionConfigurationError {
constructor(reason: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid sync persistence capability at metadata.persistence: ${reason}. ` +
`Custom sync wrappers must forward metadata.persistence unchanged.`
: codedMessage(3, { reason }),
)
this.name = `InvalidSyncPersistenceCapabilityError`
}
}
export class CollectionRequiresConfigError extends CollectionConfigurationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection requires a config`
: codedMessage(4),
)
}
}
export class CollectionRequiresSyncConfigError extends CollectionConfigurationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection requires a sync config`
: codedMessage(5),
)
}
}
export class InvalidSchemaError extends CollectionConfigurationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Schema must implement the standard-schema interface`
: codedMessage(6),
)
}
}
export class SchemaMustBeSynchronousError extends CollectionConfigurationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Schema validation must be synchronous`
: codedMessage(7),
)
}
}
// Collection State Errors
export class CollectionStateError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `CollectionStateError`
}
}
export class CollectionInErrorStateError extends CollectionStateError {
constructor(operation: string, collectionId: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot perform ${operation} on collection "${collectionId}" - collection is in error state. Try calling cleanup() and restarting the collection.`
: codedMessage(8, { operation, collectionId }),
)
}
}
export class CollectionHasSubscribersError extends CollectionStateError {
constructor(collectionId: string, dependents: number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot clean up collection "${collectionId}" while ${dependents} live query or Effect dependent(s) read it. Clean them up first, or call cleanup({ dependents: true }). collection.subscribers lists them.`
: codedMessage(254, { collectionId, dependents }),
)
}
}
export class CollectionReloadCrashedError extends CollectionStateError {
constructor(collectionId: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot reload collection "${collectionId}" because it crashed. A crash is not recovered in place; clean it up with { dependents: true } and start it again.`
: codedMessage(255, { collectionId }),
)
}
}
export class CollectionReloadDuringSyncEntryError extends CollectionStateError {
constructor(collectionId: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot reload collection "${collectionId}" from inside its sync function or a loadSubset call. Reload after it returns.`
: codedMessage(256, { collectionId }),
)
}
}
export class InvalidCollectionStatusTransitionError extends CollectionStateError {
constructor(from: string, to: string, collectionId: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid collection status transition from "${from}" to "${to}" for collection "${collectionId}"`
: codedMessage(9, { from, to, collectionId }),
)
}
}
export class CollectionIsInErrorStateError extends CollectionStateError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection is in error state`
: codedMessage(10),
)
}
}
export class NegativeActiveSubscribersError extends CollectionStateError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Active subscribers count is negative - this should never happen`
: codedMessage(11),
)
}
}
export class LiveQueryObserverDisposedError extends CollectionStateError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot subscribe to a disposed LiveQueryObserver`
: codedMessage(12),
)
}
}
export class LiveQueryWindowControllerDisposedError extends CollectionStateError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot subscribe to a disposed LiveQueryWindowController`
: codedMessage(13),
)
}
}
// Collection Operation Errors
export class CollectionOperationError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `CollectionOperationError`
}
}
// Development messages show a bigint key or field instead of failing to
// serialize it.
const showBigint = (_key: string, value: unknown) =>
typeof value === `bigint` ? `${value}n` : value
export class UndefinedKeyError extends CollectionOperationError {
constructor(item: any) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `An object was created without a defined key: ${JSON.stringify(item, showBigint)}`
: codedMessage(14, { item }),
)
}
}
export class InvalidKeyError extends CollectionOperationError {
constructor(key: unknown, item: unknown) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `getKey returned an invalid key type. Expected string or number, but got ${key === null ? `null` : typeof key}: ${JSON.stringify(key, showBigint)}. Item: ${JSON.stringify(item, showBigint)}`
: codedMessage(15, { key, type: key === null ? `null` : typeof key }),
)
}
}
export class DuplicateKeyError extends CollectionOperationError {
constructor(key: string | number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot insert document with ID "${key}" because it already exists in the collection`
: codedMessage(16, { key }),
)
}
}
export class DuplicateKeySyncError extends CollectionOperationError {
constructor(
key: string | number,
collectionId: string,
options?: {
hasCustomGetKey?: boolean
hasJoins?: boolean
hasDistinct?: boolean
},
) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? (() => {
const base = `Cannot insert document with key "${key}" from sync because it already exists in the collection "${collectionId}"`
// Provide enhanced guidance when custom getKey is used with distinct
if (options?.hasCustomGetKey && options.hasDistinct) {
return (
`${base}. ` +
`This collection uses a custom getKey with .distinct(). ` +
`The .distinct() operator deduplicates by the ENTIRE selected object (standard SQL behavior), ` +
`but your custom getKey extracts only a subset of fields. This causes multiple distinct rows ` +
`(with different values in non-key fields) to receive the same key. ` +
`To fix this, either: (1) ensure your SELECT only includes fields that uniquely identify each row, ` +
`(2) use .groupBy() with min()/max() aggregates to select one value per group, or ` +
`(3) remove the custom getKey to use the default key behavior.`
)
}
if (options?.hasCustomGetKey && options.hasJoins) {
// Provide enhanced guidance when custom getKey is used with joins
return (
`${base}. ` +
`This collection uses a custom getKey with joined queries. ` +
`Joined queries can produce multiple rows with the same key when relationships are not 1:1. ` +
`Consider: (1) using a composite key in your getKey function (e.g., \`\${item.key1}-\${item.key2}\`), ` +
`(2) ensuring your join produces unique rows per key, or (3) removing the custom getKey ` +
`to use the default composite key behavior.`
)
}
return base
})()
: codedMessage(17, { key, collectionId, ...options }),
)
}
}
export class MissingUpdateArgumentError extends CollectionOperationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `The first argument to update is missing`
: codedMessage(18),
)
}
}
export class NoKeysPassedToUpdateError extends CollectionOperationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `No keys were passed to update`
: codedMessage(19),
)
}
}
export class UpdateKeyNotFoundError extends CollectionOperationError {
constructor(key: string | number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `The key "${key}" was passed to update but an object for this key was not found in the collection`
: codedMessage(20, { key }),
)
}
}
export class KeyUpdateNotAllowedError extends CollectionOperationError {
constructor(originalKey: string | number, newKey: string | number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Updating the key of an item is not allowed. Original key: "${originalKey}", Attempted new key: "${newKey}". Please delete the old item and create a new one if a key change is necessary.`
: codedMessage(21, { originalKey, newKey }),
)
}
}
export class NoKeysPassedToDeleteError extends CollectionOperationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `No keys were passed to delete`
: codedMessage(22),
)
}
}
export class DeleteKeyNotFoundError extends CollectionOperationError {
constructor(key: string | number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection.delete was called with key '${key}' but there is no item in the collection with this key`
: codedMessage(23, { key }),
)
}
}
// Collection Handler Errors
export class MissingHandlerError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `MissingHandlerError`
}
}
export class MissingInsertHandlerError extends MissingHandlerError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`
: codedMessage(24),
)
}
}
export class MissingUpdateHandlerError extends MissingHandlerError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`
: codedMessage(25),
)
}
}
export class MissingDeleteHandlerError extends MissingHandlerError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`
: codedMessage(26),
)
}
}
// Transaction Errors
export class TransactionError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `TransactionError`
}
}
export class PacedTransactionManualCommitError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Paced mutations are committed by their strategy. Await the transaction receipt or roll it back instead.`
: codedMessage(86),
)
this.name = `PacedTransactionManualCommitError`
}
}
export class QueueCapacityExceededError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Queue capacity exceeded; the mutation was not admitted`
: codedMessage(27),
)
this.name = `QueueCapacityExceededError`
}
}
export class QueueDisposedError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Queue has been cleaned up; the mutation was not admitted`
: codedMessage(28),
)
this.name = `QueueDisposedError`
}
}
export class ThrottleCallDroppedError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Throttle call was dropped because trailing execution is disabled`
: codedMessage(29),
)
this.name = `ThrottleCallDroppedError`
}
}
export class DebounceCallDroppedError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Debounce call was dropped because trailing execution is disabled`
: codedMessage(30),
)
this.name = `DebounceCallDroppedError`
}
}
export class MissingMutationFunctionError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `mutationFn is required when creating a transaction`
: codedMessage(31),
)
}
}
export class OnMutateMustBeSynchronousError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`
: codedMessage(32),
)
this.name = `OnMutateMustBeSynchronousError`
}
}
export class TransactionNotPendingMutateError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `You can no longer call .mutate() as the transaction is no longer pending`
: codedMessage(33),
)
}
}
export class TransactionAlreadyCompletedRollbackError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `You can no longer call .rollback() as the transaction is already completed`
: codedMessage(34),
)
}
}
export class DuplicateTransactionIdError extends TransactionError {
constructor(id: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `A Collection already tracks an unsettled transaction with id "${id}". Give each transaction a unique id.`
: codedMessage(233, { id }),
)
}
}
export class TransactionNotPendingCommitError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `You can no longer call .commit() as the transaction is no longer pending`
: codedMessage(35),
)
}
}
export class NoPendingSyncTransactionWriteError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `No pending sync transaction to write to`
: codedMessage(36),
)
}
}
export class SyncTransactionAlreadyCommittedWriteError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `The pending sync transaction is already committed, you can't still write to it.`
: codedMessage(37),
)
}
}
export class SyncRowReusedWithoutPreviousValueError extends TransactionError {
constructor(key: string | number) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `A sync update for key "${key}" wrote a row object that changed in place since it was last written. ` +
`The change overwrote the row's previous value. ` +
`Write a new object, or pass the row's previous value as \`previousValue\`.`
: codedMessage(38, { key }),
)
}
}
export class NoPendingSyncTransactionCommitError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `No pending sync transaction to commit`
: codedMessage(39),
)
}
}
export class SyncTransactionAlreadyCommittedError extends TransactionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `The pending sync transaction is already committed, you can't commit it again.`
: codedMessage(40),
)
}
}
/**
* An internal sync-queue invariant failed: a cancel targeted a transaction
* that is not the open last one, or replaying the queue invalidated a
* transaction. No public path should reach this.
*/
export class SyncQueueInvariantError extends TransactionError {
constructor(detail: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Sync queue invariant failed: ${detail}`
: codedMessage(41, { detail }),
)
}
}
// Query Builder Errors
export class QueryBuilderError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `QueryBuilderError`
}
}
export class OnlyOneSourceAllowedError extends QueryBuilderError {
constructor(context: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Only one source is allowed in the ${context}`
: codedMessage(42, { context }),
)
}
}
export class SubQueryMustHaveFromClauseError extends QueryBuilderError {
constructor(context: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `A sub query passed to a ${context} must have a from clause itself`
: codedMessage(43, { context }),
)
}
}
export class InvalidSourceError extends QueryBuilderError {
constructor(alias: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid source for live query: The value provided for alias "${alias}" is not a Collection or subquery. Live queries only accept Collection instances or subqueries. Please ensure you're passing a valid Collection or QueryBuilder, not a plain array or other data type.`
: codedMessage(44, { alias }),
)
}
}
export class IncludeOperatorRequiredError extends QueryBuilderError {
constructor(field: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `A subquery in select() needs include(), toArray(), or materialize(). Wrap the ${field} field: ${field}: include(q.from({ i: issues }).where(...)). Import include from @tanstack/db. Query IR built by hand passes includesRuntime to each IR.IncludesSubquery.`
: codedMessage(248, { field }),
)
}
}
export type SourceClauseContext =
`from clause` | `unionAll clause` | `join clause`
export class InvalidSourceTypeError extends QueryBuilderError {
constructor(context: SourceClauseContext, type: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? (() => {
const expected =
context === `unionAll clause`
? `an object with one or more key-value pairs like { alias: collection }`
: `an object with a single key-value pair like { alias: collection }`
const example =
context === `unionAll clause`
? `.unionAll({ todos: todosCollection, events: eventsCollection })`
: context === `join clause`
? `.join({ todos: todosCollection }, ({ todo, todos }) => eq(todo.id, todos.id))`
: `.from({ todos: todosCollection })`
return (
`Invalid source for ${context}: Expected ${expected}. ` +
`For example: ${example}. Got: ${type}`
)
})()
: codedMessage(45, { context, type }),
)
}
}
export class JoinConditionMustBeEqualityError extends QueryBuilderError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Join condition must be an equality expression or a nonempty conjunction of equality expressions`
: codedMessage(46),
)
}
}
export class QueryMustHaveFromClauseError extends QueryBuilderError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Query must have a from clause`
: codedMessage(47),
)
}
}
export class InvalidWhereExpressionError extends QueryBuilderError {
constructor(valueType: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid where() expression: Expected a query expression, but received a ${valueType}. ` +
`This usually happens when using JavaScript's comparison operators (===, !==, <, >, etc.) directly. ` +
`Instead, use the query builder functions:\n\n` +
` ❌ .where(({ user }) => user.id === 'abc')\n` +
` ✅ .where(({ user }) => eq(user.id, 'abc'))\n\n` +
`Available comparison functions: eq, gt, gte, lt, lte, and, or, not, like, ilike, isNull, isUndefined`
: codedMessage(48, { valueType }),
)
}
}
// Query Compilation Errors
export class QueryCompilationError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `QueryCompilationError`
}
}
export class UnsafeAliasPathError extends QueryCompilationError {
constructor(segment: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unsafe alias path segment "${segment}" is not allowed in .select(). ` +
`Aliases must not contain "__proto__", "prototype", or "constructor".`
: codedMessage(49, { segment }),
)
this.name = `UnsafeAliasPathError`
}
}
export class DistinctRequiresSelectError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `DISTINCT requires a SELECT clause.`
: codedMessage(50),
)
}
}
export class FnSelectWithGroupByError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `fn.select() cannot be used with groupBy(). ` +
`groupBy requires the compiler to statically analyze aggregate functions (count, sum, max, etc.) in the SELECT clause, ` +
`which is not possible with fn.select() since it is an opaque function. ` +
`Use .select() instead of .fn.select() when combining with groupBy().`
: codedMessage(51),
)
}
}
export class GroupingOperatorRequiredError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `groupBy() without an aggregate needs groupKeys(), such as .groupBy(groupKeys(({ todo }) => todo.status)). Import groupKeys from @tanstack/db. A query with an aggregate such as count() needs nothing extra. Query IR built by hand passes groupingCompiler to each IR.Aggregate.`
: codedMessage(247),
)
}
}
export class UnsupportedFnSelectResultError extends QueryCompilationError {
constructor(valueDescription: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `fn.select() cannot return ${valueDescription}. ` +
`Child query builders, query expressions, and helpers such as eq(), toArray(), materialize(), concat(toArray()), and caseWhen() are query-construction values. ` +
`Use them as direct fields in .select() instead.`
: codedMessage(52, { valueDescription }),
)
}
}
export class UnsupportedRootScalarSelectError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Top-level scalar select() is not supported by createLiveQueryCollection() or queryOnce(). ` +
`Return an object from .select(), or use the scalar query inside toArray(...) or concat(toArray(...)).`
: codedMessage(53),
)
}
}
export class HavingRequiresGroupByError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `HAVING clause requires GROUP BY clause`
: codedMessage(54),
)
}
}
export class LimitOffsetRequireOrderByError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`
: codedMessage(55),
)
}
}
/**
* Error thrown when a collection input stream is not found during query compilation.
* In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream.
*/
export class CollectionInputNotFoundError extends QueryCompilationError {
constructor(
alias: string,
collectionId?: string,
availableKeys?: Array<string>,
) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? (() => {
const details = collectionId
? `alias "${alias}" (collection "${collectionId}")`
: `collection "${alias}"`
const availableKeysMsg = availableKeys?.length
? `. Available keys: ${availableKeys.join(`, `)}`
: ``
return `Input for ${details} not found in inputs map${availableKeysMsg}`
})()
: codedMessage(56, {
alias,
collectionId,
availableKeys,
}),
)
}
}
export class UnsupportedFromTypeError extends QueryCompilationError {
constructor(type: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unsupported FROM type: ${type}`
: codedMessage(58, { type }),
)
}
}
export class UnknownExpressionTypeError extends QueryCompilationError {
constructor(type: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unknown expression type: ${type}`
: codedMessage(59, { type }),
)
}
}
export class EmptyReferencePathError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Reference path cannot be empty`
: codedMessage(60),
)
}
}
export class UnknownFunctionError extends QueryCompilationError {
constructor(functionName: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unknown function: ${functionName}`
: codedMessage(61, { functionName }),
)
}
}
export class JoinCollectionNotFoundError extends QueryCompilationError {
constructor(collectionId: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection "${collectionId}" not found during compilation of join`
: codedMessage(62, { collectionId }),
)
}
}
// JOIN Operation Errors
export class JoinError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `JoinError`
}
}
export class UnsupportedJoinTypeError extends JoinError {
constructor(joinType: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unsupported join type: ${joinType}`
: codedMessage(63, { joinType }),
)
}
}
export class InvalidJoinConditionSameSourceError extends JoinError {
constructor(sourceAlias: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid join condition: both expressions refer to the same source "${sourceAlias}"`
: codedMessage(64, { sourceAlias }),
)
}
}
export class InvalidJoinConditionSourceMismatchError extends JoinError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid join condition: expressions must reference source aliases`
: codedMessage(65),
)
}
}
export class InvalidJoinConditionLeftSourceError extends JoinError {
constructor(sourceAlias: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid join condition: left expression refers to an unavailable source "${sourceAlias}"`
: codedMessage(66, { sourceAlias }),
)
}
}
export class InvalidJoinConditionRightSourceError extends JoinError {
constructor(sourceAlias: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid join condition: right expression does not refer to the joined source "${sourceAlias}"`
: codedMessage(67, { sourceAlias }),
)
}
}
export class InvalidJoinCondition extends JoinError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Invalid join condition`
: codedMessage(68),
)
}
}
export class UnsupportedJoinSourceTypeError extends JoinError {
constructor(type: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unsupported join source type: ${type}`
: codedMessage(69, { type }),
)
}
}
// GROUP BY and Aggregation Errors
export class GroupByError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `GroupByError`
}
}
export class NonAggregateExpressionNotInGroupByError extends GroupByError {
constructor(alias: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Non-aggregate expression '${alias}' must also appear in GROUP BY clause`
: codedMessage(70, { alias }),
)
}
}
export class UnsupportedAggregateFunctionError extends GroupByError {
constructor(functionName: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unsupported aggregate function: ${functionName}`
: codedMessage(71, { functionName }),
)
}
}
export class AggregateFunctionNotInSelectError extends GroupByError {
constructor(functionName: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Aggregate function in HAVING clause must also be in SELECT clause: ${functionName}`
: codedMessage(72, { functionName }),
)
}
}
export class UnknownHavingExpressionTypeError extends GroupByError {
constructor(type: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Unknown expression type in HAVING clause: ${type}`
: codedMessage(73, { type }),
)
}
}
// Storage Errors
export class StorageError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `StorageError`
}
}
export class SerializationError extends StorageError {
constructor(operation: string, originalError: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot ${operation} item because its storage cannot represent it: ${originalError}`
: codedMessage(74, { operation, originalError }),
)
}
}
// LocalStorage Collection Errors
export class LocalStorageCollectionError extends StorageError {
constructor(message: string) {
super(message)
this.name = `LocalStorageCollectionError`
}
}
export class StorageKeyRequiredError extends LocalStorageCollectionError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `[LocalStorageCollection] storageKey must be provided.`
: codedMessage(75),
)
}
}
export class InvalidStorageDataFormatError extends LocalStorageCollectionError {
constructor(storageKey: string, key: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `[LocalStorageCollection] Invalid data format in storage key "${storageKey}" for key "${key}".`
: codedMessage(76, { storageKey, key }),
)
}
}
export class InvalidStorageObjectFormatError extends LocalStorageCollectionError {
constructor(storageKey: string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `[LocalStorageCollection] Invalid data format in storage key "${storageKey}". Expected object format.`
: codedMessage(77, { storageKey }),
)
}
}
// Sync Cleanup Errors
export class SyncCleanupError extends TanStackDBError {
constructor(collectionId: string, error: Error | string) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection "${collectionId}" sync cleanup function threw an error: ${error instanceof Error ? error.message : String(error)}`
: codedMessage(78, { collectionId, error }),
)
this.name = `SyncCleanupError`
}
}
/** A sync transaction was canceled before its writes became visible. */
export class SyncTransactionAbortedError extends Error {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Sync transaction was aborted before application`
: codedMessage(79),
)
this.name = `AbortError`
}
}
/** A collection was cleaned up before its initial preload became ready. */
export class CollectionPreloadAbortedError extends Error {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Collection preload was abandoned during cleanup`
: codedMessage(80),
)
this.name = `AbortError`
}
}
/** A subset operation was canceled before its result became visible. */
export class LoadSubsetOperationAbortedError extends Error {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Load subset operation was aborted before its result became visible`
: codedMessage(81),
)
this.name = `AbortError`
}
}
// Query Optimizer Errors
export class QueryOptimizerError extends TanStackDBError {
constructor(message: string) {
super(message)
this.name = `QueryOptimizerError`
}
}
export class CannotCombineEmptyExpressionListError extends QueryOptimizerError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Cannot combine empty expression list`
: codedMessage(82),
)
}
}
/**
* Internal error when the compiler returns aliases that don't have corresponding input streams.
* This should never happen since all aliases come from user declarations.
*/
export class MissingAliasInputsError extends QueryCompilationError {
constructor(missingAliases: Array<string>) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Internal error: compiler returned aliases without inputs: ${missingAliases.join(`, `)}. ` +
`This indicates a bug in query compilation. Please report this issue.`
: codedMessage(83, { missingAliases }),
)
}
}
/**
* Error thrown when setWindow is called on a collection without an ORDER BY clause.
*/
export class SetWindowRequiresOrderByError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `setWindow() can only move a window on a query with an ORDER BY clause. ` +
`Add .orderBy() to your query to enable window movement.`
: codedMessage(84),
)
}
}
/**
* Error thrown when setWindow would change the limit of a findOne() query.
*/
export class SetWindowFindOneLimitError extends QueryCompilationError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `setWindow() cannot change the limit of a findOne() query.`
: codedMessage(244),
)
}
}
/** Error thrown when setWindow is called from inside another setWindow call. */
export class SetWindowReentrancyError extends TanStackDBError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `setWindow() cannot run reentrantly. Wait for the current window operation to return before starting another one.`
: codedMessage(85),
)
this.name = `SetWindowReentrancyError`
}
}
export class UnhashableQueryIRError extends Error {
constructor(
public readonly path: string,
public readonly reason: string,
) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Query IR is not stably hashable at ${path}: ${reason}`
: codedMessage(87, { path, reason }),
)
this.name = `UnhashableQueryIRError`
}
}
/**
* Error thrown when a mounted framework binding that compares query identity
* reads a different source Collection under a Collection ID it has already
* read, in an earlier query or the same one. The identity names each source by
* its ID, so the binding could otherwise keep a live query that reads the old
* source.
*/
export class SourceCollectionIdReuseError extends TanStackDBError {
constructor(
public readonly bindingName: string,
public readonly collectionId: string,
) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `[${bindingName}] Collection ID "${collectionId}" named a different source Collection than one this live query already read. While the hook is mounted, each Collection ID must keep naming one source Collection. Unmount the hook and clean up its previous source and client scope before reusing the ID.`
: codedMessage(245, { bindingName, collectionId }),
)
this.name = `SourceCollectionIdReuseError`
}
}
export class InvalidPoolOptionError extends TanStackDBError {
constructor() {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `A live query's pool option takes pooledLiveQueries from @tanstack/db, or nothing. Pooling is opt-in: pass pool: pooledLiveQueries to share one partition among equality-filtered queries.`
: codedMessage(251),
)
this.name = `InvalidPoolOptionError`
}
}