@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1,163 lines (1,056 loc) • 37.5 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 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`
}
}
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)}`
: 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)}. Item: ${JSON.stringify(item)}`
: 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 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 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,
}),
)
}
}
/**
* Retained for callers that imported this formerly thrown error.
*
* @deprecated Nested queries may now shadow ancestor aliases. Duplicate aliases
* are rejected only within one lexical scope or union branch namespace.
*/
export class DuplicateAliasInSubqueryError extends QueryCompilationError {
constructor(alias: string, parentAliases: Array<string>) {
super(
devBuild() && process.env.NODE_ENV !== `production`
? `Subquery uses alias "${alias}" which is already used in the parent query. ` +
`Each alias must be unique across parent and subquery contexts. ` +
`Parent query aliases: ${parentAliases.join(`, `)}. ` +
`Please rename "${alias}" in either the parent query or subquery to avoid conflicts.`
: codedMessage(57, { alias, parentAliases }),
)
}
}
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}' in SELECT 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 it cannot be JSON serialized: ${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 be called on collections with an ORDER BY clause. ` +
`Add .orderBy() to your query to enable window movement.`
: codedMessage(84),
)
}
}
/** 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`
}
}