@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 16.9 kB
Source Map (JSON)
{"version":3,"file":"indexes.cjs","sources":["../../../src/collection/indexes.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from '../query/builder/ref-proxy'\nimport { CollectionConfigurationError } from '../errors'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { BaseIndex, IndexConstructor } from '../indexes/base-index'\nimport type { ChangeMessage } from '../types'\nimport type { IndexOptions } from '../indexes/index-options'\nimport type { SingleRowRefProxy } from '../query/builder/ref-proxy'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\nimport type { BasicExpression } from '../query/ir'\nimport type {\n CollectionEventsManager,\n CollectionIndexMetadata,\n CollectionIndexResolverMetadata,\n CollectionIndexSerializableValue,\n} from './events'\n\nconst INDEX_SIGNATURE_VERSION = 1 as const\n\nfunction compareStringsCodePoint(left: string, right: string): number {\n if (left === right) {\n return 0\n }\n\n return left < right ? -1 : 1\n}\n\nfunction resolveResolverMetadata<TKey extends string | number>(\n resolver: IndexConstructor<TKey>,\n): CollectionIndexResolverMetadata {\n return {\n kind: `constructor`,\n ...(resolver.name ? { name: resolver.name } : {}),\n }\n}\n\nfunction toSerializableIndexValue(\n value: unknown,\n): CollectionIndexSerializableValue | undefined {\n if (value == null) {\n return value\n }\n\n switch (typeof value) {\n case `string`:\n case `boolean`:\n return value\n case `number`:\n return Number.isFinite(value) ? value : null\n case `bigint`:\n return { __type: `bigint`, value: value.toString() }\n case `function`:\n case `symbol`:\n // Function and symbol identity are process-local and not stable across runtimes.\n // Dropping them keeps signatures deterministic; we may skip index reuse, which is acceptable.\n return undefined\n case `undefined`:\n return undefined\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toSerializableIndexValue(entry) ?? null)\n }\n\n if (value instanceof Date) {\n return {\n __type: `date`,\n value: value.toISOString(),\n }\n }\n\n if (value instanceof Set) {\n const serializedValues = Array.from(value)\n .map((entry) => toSerializableIndexValue(entry) ?? null)\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a),\n stableStringifyCollectionIndexValue(b),\n ),\n )\n return {\n __type: `set`,\n values: serializedValues,\n }\n }\n\n if (value instanceof Map) {\n const serializedEntries = Array.from(value.entries())\n .map(([mapKey, mapValue]) => ({\n key: toSerializableIndexValue(mapKey) ?? null,\n value: toSerializableIndexValue(mapValue) ?? null,\n }))\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a.key),\n stableStringifyCollectionIndexValue(b.key),\n ),\n )\n\n return {\n __type: `map`,\n entries: serializedEntries,\n }\n }\n\n if (value instanceof RegExp) {\n return {\n __type: `regexp`,\n value: value.toString(),\n }\n }\n\n const serializedObject: Record<string, CollectionIndexSerializableValue> = {}\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey),\n )\n\n for (const [key, entryValue] of entries) {\n const serializedEntry = toSerializableIndexValue(entryValue)\n if (serializedEntry !== undefined) {\n serializedObject[key] = serializedEntry\n }\n }\n\n return serializedObject\n}\n\nfunction stableStringifyCollectionIndexValue(\n value: CollectionIndexSerializableValue,\n): string {\n if (value === null) {\n return `null`\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`\n }\n\n if (typeof value !== `object`) {\n return JSON.stringify(value)\n }\n\n const sortedKeys = Object.keys(value).sort((left, right) =>\n compareStringsCodePoint(left, right),\n )\n const serializedEntries = sortedKeys.map(\n (key) =>\n `${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key]!)}`,\n )\n return `{${serializedEntries.join(`,`)}}`\n}\n\nfunction createCollectionIndexMetadata<TKey extends string | number>(\n indexId: number,\n expression: BasicExpression,\n name: string | undefined,\n resolver: IndexConstructor<TKey>,\n options: unknown,\n): CollectionIndexMetadata {\n const resolverMetadata = resolveResolverMetadata(resolver)\n const serializedExpression = toSerializableIndexValue(expression) ?? null\n const serializedOptions = toSerializableIndexValue(options)\n const signatureInput = toSerializableIndexValue({\n signatureVersion: INDEX_SIGNATURE_VERSION,\n expression: serializedExpression,\n options: serializedOptions ?? null,\n })\n const normalizedSignatureInput = signatureInput ?? null\n const signature = stableStringifyCollectionIndexValue(\n normalizedSignatureInput,\n )\n\n return {\n signatureVersion: INDEX_SIGNATURE_VERSION,\n signature,\n indexId,\n name,\n expression,\n resolver: resolverMetadata,\n ...(serializedOptions === undefined ? {} : { options: serializedOptions }),\n }\n}\n\nfunction cloneSerializableIndexValue(\n value: CollectionIndexSerializableValue,\n): CollectionIndexSerializableValue {\n if (value === null || typeof value !== `object`) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => cloneSerializableIndexValue(entry))\n }\n\n const cloned: Record<string, CollectionIndexSerializableValue> = {}\n for (const [key, entryValue] of Object.entries(value)) {\n cloned[key] = cloneSerializableIndexValue(entryValue)\n }\n return cloned\n}\n\nfunction cloneExpression(expression: BasicExpression): BasicExpression {\n return JSON.parse(JSON.stringify(expression)) as BasicExpression\n}\n\nexport class CollectionIndexesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private defaultIndexType: IndexConstructor<TKey> | undefined\n private events!: CollectionEventsManager\n\n public indexes = new Map<number, BaseIndex<TKey>>()\n public indexMetadata = new Map<number, CollectionIndexMetadata>()\n public indexCounter = 0\n\n constructor() {}\n\n setDeps(deps: {\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n defaultIndexType?: IndexConstructor<TKey>\n events: CollectionEventsManager\n }) {\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n this.defaultIndexType = deps.defaultIndexType\n this.events = deps.events\n }\n\n /**\n * Creates an index on a collection for faster queries.\n *\n * @example\n * ```ts\n * // With explicit index type (recommended for tree-shaking)\n * import { BasicIndex } from '@tanstack/db'\n * collection.createIndex((row) => row.userId, { indexType: BasicIndex })\n *\n * // With collection's default index type\n * collection.createIndex((row) => row.userId)\n * ```\n */\n public createIndex<TIndexType extends IndexConstructor<TKey>>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TIndexType> = {},\n ): BaseIndex<TKey> {\n this.lifecycle.validateCollectionUsable(`createIndex`)\n\n const indexId = ++this.indexCounter\n const singleRowRefProxy = createSingleRowRefProxy<TOutput>()\n const indexExpression = indexCallback(singleRowRefProxy)\n const expression = toExpression(indexExpression)\n\n // Use provided index type, or fall back to collection's default\n const IndexType = config.indexType ?? this.defaultIndexType\n if (!IndexType) {\n throw new CollectionConfigurationError(\n `No index type specified and no defaultIndexType set on collection. ` +\n `Either pass indexType in config, or set defaultIndexType on the collection:\\n` +\n ` import { BasicIndex } from '@tanstack/db'\\n` +\n ` createCollection({ defaultIndexType: BasicIndex, ... })`,\n )\n }\n\n // Create index synchronously\n const index = new IndexType(\n indexId,\n expression,\n config.name,\n config.options,\n )\n\n // Build with current data\n index.build(this.state.entries())\n\n this.indexes.set(indexId, index)\n\n // Track metadata and emit event\n const metadata = createCollectionIndexMetadata(\n indexId,\n expression,\n config.name,\n IndexType,\n config.options,\n )\n this.indexMetadata.set(indexId, metadata)\n this.events.emitIndexAdded(metadata)\n\n return index\n }\n\n /**\n * Removes an index from this collection.\n * Returns true when an index existed and was removed, false otherwise.\n */\n public removeIndex(indexOrId: BaseIndex<TKey> | number): boolean {\n this.lifecycle.validateCollectionUsable(`removeIndex`)\n\n const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id\n const index = this.indexes.get(indexId)\n if (!index) {\n return false\n }\n\n if (typeof indexOrId !== `number` && index !== indexOrId) {\n // Passed a different index instance with the same id — do not remove.\n return false\n }\n\n this.indexes.delete(indexId)\n\n const metadata = this.indexMetadata.get(indexId)\n this.indexMetadata.delete(indexId)\n if (metadata) {\n this.events.emitIndexRemoved(metadata)\n }\n\n return true\n }\n\n /**\n * Returns a sorted snapshot of index metadata.\n * This allows persisted wrappers to bootstrap from indexes that were created\n * before they attached lifecycle listeners.\n */\n public getIndexMetadataSnapshot(): Array<CollectionIndexMetadata> {\n return Array.from(this.indexMetadata.values())\n .sort((left, right) => left.indexId - right.indexId)\n .map((metadata) => ({\n ...metadata,\n expression: cloneExpression(metadata.expression),\n resolver: { ...metadata.resolver },\n ...(metadata.options === undefined\n ? {}\n : { options: cloneSerializableIndexValue(metadata.options) }),\n }))\n }\n\n /**\n * Updates all indexes when the collection changes\n */\n public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {\n for (const index of this.indexes.values()) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n index.add(change.key, change.value)\n break\n case `update`:\n if (change.previousValue) {\n index.update(change.key, change.previousValue, change.value)\n } else {\n index.add(change.key, change.value)\n }\n break\n case `delete`:\n index.remove(change.key, change.value)\n break\n }\n }\n }\n }\n\n /**\n * Clean up indexes\n */\n public cleanup(): void {\n this.indexes.clear()\n this.indexMetadata.clear()\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","CollectionConfigurationError"],"mappings":"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"}