UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1,163 lines (1,056 loc) • 37.5 kB
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` } }