@tanstack/db 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/collection/change-events.cjs +1 -1
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs +7 -3
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs +3 -6
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/index.cjs +4 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +16 -4
- package/dist/cjs/collection/mutations.cjs +13 -20
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +14 -6
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/subscription.cjs +3 -4
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +2 -2
- package/dist/cjs/collection/sync.cjs +10 -2
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +4 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +2 -1
- package/dist/cjs/indexes/base-index.cjs +26 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +47 -2
- package/dist/cjs/indexes/btree-index.cjs +45 -9
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +15 -0
- package/dist/cjs/indexes/lazy-index.cjs +3 -6
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs +78 -0
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -0
- package/dist/cjs/indexes/reverse-index.d.cts +30 -0
- package/dist/cjs/proxy.cjs +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs +21 -0
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +16 -1
- package/dist/cjs/query/builder/types.d.cts +7 -0
- package/dist/cjs/query/compiler/evaluators.cjs +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +2 -10
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs +6 -6
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +4 -5
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +2 -2
- package/dist/cjs/query/compiler/select.cjs +1 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +1 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +3 -2
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +2 -2
- package/dist/cjs/query/live/collection-subscriber.cjs +2 -3
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/types.d.cts +4 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +7 -4
- package/dist/cjs/query/optimizer.cjs +2 -4
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +2 -3
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +13 -0
- package/dist/cjs/utils/btree.cjs +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +7 -2
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.d.cts +3 -2
- package/dist/cjs/utils.cjs +6 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -3
- package/dist/esm/collection/change-events.js +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js +7 -3
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.js +3 -6
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +16 -4
- package/dist/esm/collection/index.js +4 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/mutations.js +13 -20
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.js +14 -6
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +2 -2
- package/dist/esm/collection/subscription.js +3 -4
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js +10 -2
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/indexes/auto-index.d.ts +2 -1
- package/dist/esm/indexes/auto-index.js +4 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +47 -2
- package/dist/esm/indexes/base-index.js +26 -0
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +15 -0
- package/dist/esm/indexes/btree-index.js +45 -9
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js +3 -6
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.d.ts +30 -0
- package/dist/esm/indexes/reverse-index.js +78 -0
- package/dist/esm/indexes/reverse-index.js.map +1 -0
- package/dist/esm/proxy.js +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +16 -1
- package/dist/esm/query/builder/index.js +21 -0
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +7 -0
- package/dist/esm/query/compiler/evaluators.js +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +3 -11
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +6 -6
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +2 -2
- package/dist/esm/query/compiler/order-by.js +4 -5
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.d.ts +1 -0
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +2 -2
- package/dist/esm/query/live/collection-config-builder.js +3 -2
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +2 -3
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +4 -0
- package/dist/esm/query/live-query-collection.d.ts +7 -4
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js +2 -4
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/transactions.js +2 -3
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +13 -0
- package/dist/esm/utils/btree.js +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/index-optimization.d.ts +3 -2
- package/dist/esm/utils/index-optimization.js +7 -2
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +2 -3
- package/dist/esm/utils.js +6 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/changes.ts +10 -4
- package/src/collection/index.ts +38 -5
- package/src/collection/state.ts +28 -11
- package/src/collection/subscription.ts +3 -3
- package/src/collection/sync.ts +17 -2
- package/src/indexes/auto-index.ts +8 -3
- package/src/indexes/base-index.ts +94 -4
- package/src/indexes/btree-index.ts +58 -7
- package/src/indexes/reverse-index.ts +120 -0
- package/src/query/builder/index.ts +30 -2
- package/src/query/builder/types.ts +12 -0
- package/src/query/compiler/group-by.ts +1 -10
- package/src/query/compiler/order-by.ts +15 -18
- package/src/query/index.ts +1 -0
- package/src/query/ir.ts +1 -0
- package/src/query/live/collection-config-builder.ts +3 -2
- package/src/query/live/types.ts +5 -0
- package/src/query/live-query-collection.ts +34 -8
- package/src/types.ts +22 -0
- package/src/utils/index-optimization.ts +19 -5
- package/src/utils.ts +8 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport {\n MissingMutationFunctionError,\n TransactionAlreadyCompletedRollbackError,\n TransactionNotPendingCommitError,\n TransactionNotPendingMutateError,\n} from \"./errors\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n MutationFn,\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\n\nlet sequenceNumber = 0\n\n/**\n * Merges two pending mutations for the same item within a transaction\n *\n * Merge behavior truth table:\n * - (insert, update) → insert (merge changes, keep empty original)\n * - (insert, delete) → null (cancel both mutations)\n * - (update, delete) → delete (delete dominates)\n * - (update, update) → update (replace with latest, union changes)\n * - (delete, delete) → delete (replace with latest)\n * - (insert, insert) → insert (replace with latest)\n *\n * Note: (delete, update) and (delete, insert) should never occur as the collection\n * layer prevents operations on deleted items within the same transaction.\n *\n * @param existing - The existing mutation in the transaction\n * @param incoming - The new mutation being applied\n * @returns The merged mutation, or null if both should be removed\n */\nfunction mergePendingMutations<T extends object>(\n existing: PendingMutation<T>,\n incoming: PendingMutation<T>\n): PendingMutation<T> | null {\n // Truth table implementation\n switch (`${existing.type}-${incoming.type}` as const) {\n case `insert-update`: {\n // Update after insert: keep as insert but merge changes\n // For insert-update, the key should remain the same since collections don't allow key changes\n return {\n ...existing,\n type: `insert` as const,\n original: {},\n modified: incoming.modified,\n changes: { ...existing.changes, ...incoming.changes },\n // Keep existing keys (key changes not allowed in updates)\n key: existing.key,\n globalKey: existing.globalKey,\n // Merge metadata (last-write-wins)\n metadata: incoming.metadata ?? existing.metadata,\n syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },\n // Update tracking info\n mutationId: incoming.mutationId,\n updatedAt: incoming.updatedAt,\n }\n }\n\n case `insert-delete`:\n // Delete after insert: cancel both mutations\n return null\n\n case `update-delete`:\n // Delete after update: delete dominates\n return incoming\n\n case `update-update`: {\n // Update after update: replace with latest, union changes\n return {\n ...incoming,\n // Keep original from first update\n original: existing.original,\n // Union the changes from both updates\n changes: { ...existing.changes, ...incoming.changes },\n // Merge metadata\n metadata: incoming.metadata ?? existing.metadata,\n syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },\n }\n }\n\n case `delete-delete`:\n case `insert-insert`:\n // Same type: replace with latest\n return incoming\n\n default: {\n // Exhaustiveness check\n const _exhaustive: never = `${existing.type}-${incoming.type}` as never\n throw new Error(`Unhandled mutation combination: ${_exhaustive}`)\n }\n }\n}\n\n/**\n * Creates a new transaction for grouping multiple collection operations\n * @param config - Transaction configuration with mutation function\n * @returns A new Transaction instance\n * @example\n * // Basic transaction usage\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Send all mutations to API\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * collection.update(\"2\", draft => { draft.completed = true })\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle transaction errors\n * try {\n * const tx = createTransaction({\n * mutationFn: async () => { throw new Error(\"API failed\") }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"New item\" })\n * })\n *\n * await tx.isPersisted.promise\n * } catch (error) {\n * console.log('Transaction failed:', error)\n * }\n *\n * @example\n * // Manual commit control\n * const tx = createTransaction({\n * autoCommit: false,\n * mutationFn: async () => {\n * // API call\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * // Commit later\n * await tx.commit()\n */\nexport function createTransaction<T extends object = Record<string, unknown>>(\n config: TransactionConfig<T>\n): Transaction<T> {\n const newTransaction = new Transaction<T>(config)\n transactions.push(newTransaction)\n return newTransaction\n}\n\n/**\n * Gets the currently active ambient transaction, if any\n * Used internally by collection operations to join existing transactions\n * @returns The active transaction or undefined if none is active\n * @example\n * // Check if operations will join an ambient transaction\n * const ambientTx = getActiveTransaction()\n * if (ambientTx) {\n * console.log('Operations will join transaction:', ambientTx.id)\n * }\n */\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction<any>) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction<any>) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction<any>) {\n const index = transactions.findIndex((t) => t.id === tx.id)\n if (index !== -1) {\n transactions.splice(index, 1)\n }\n}\n\nclass Transaction<T extends object = Record<string, unknown>> {\n public id: string\n public state: TransactionState\n public mutationFn: MutationFn<T>\n public mutations: Array<PendingMutation<T>>\n public isPersisted: Deferred<Transaction<T>>\n public autoCommit: boolean\n public createdAt: Date\n public sequenceNumber: number\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig<T>) {\n if (typeof config.mutationFn === `undefined`) {\n throw new MissingMutationFunctionError()\n }\n this.id = config.id ?? crypto.randomUUID()\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred<Transaction<T>>()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.sequenceNumber = sequenceNumber++\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n\n if (newState === `completed` || newState === `failed`) {\n removeFromPendingList(this)\n }\n }\n\n /**\n * Execute collection operations within this transaction\n * @param callback - Function containing collection operations to group together\n * @returns This transaction for chaining\n * @example\n * // Group multiple operations\n * const tx = createTransaction({ mutationFn: async () => {\n * // Send to API\n * }})\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * collection.update(\"2\", draft => { draft.completed = true })\n * collection.delete(\"3\")\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle mutate errors\n * try {\n * tx.mutate(() => {\n * collection.insert({ id: \"invalid\" }) // This might throw\n * })\n * } catch (error) {\n * console.log('Mutation failed:', error)\n * }\n *\n * @example\n * // Manual commit control\n * const tx = createTransaction({ autoCommit: false, mutationFn: async () => {} })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * // Commit later when ready\n * await tx.commit()\n */\n mutate(callback: () => void): Transaction<T> {\n if (this.state !== `pending`) {\n throw new TransactionNotPendingMutateError()\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit().catch(() => {\n // Errors from autoCommit are handled via isPersisted.promise\n // This catch prevents unhandled promise rejections\n })\n }\n\n return this\n }\n\n /**\n * Apply new mutations to this transaction, intelligently merging with existing mutations\n *\n * When mutations operate on the same item (same globalKey), they are merged according to\n * the following rules:\n *\n * - **insert + update** → insert (merge changes, keep empty original)\n * - **insert + delete** → removed (mutations cancel each other out)\n * - **update + delete** → delete (delete dominates)\n * - **update + update** → update (union changes, keep first original)\n * - **same type** → replace with latest\n *\n * This merging reduces over-the-wire churn and keeps the optimistic local view\n * aligned with user intent.\n *\n * @param mutations - Array of new mutations to apply\n */\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.globalKey === newMutation.globalKey\n )\n\n if (existingIndex >= 0) {\n const existingMutation = this.mutations[existingIndex]!\n const mergeResult = mergePendingMutations(existingMutation, newMutation)\n\n if (mergeResult === null) {\n // Remove the mutation (e.g., delete after insert cancels both)\n this.mutations.splice(existingIndex, 1)\n } else {\n // Replace with merged mutation\n this.mutations[existingIndex] = mergeResult\n }\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n /**\n * Rollback the transaction and any conflicting transactions\n * @param config - Configuration for rollback behavior\n * @returns This transaction for chaining\n * @example\n * // Manual rollback\n * const tx = createTransaction({ mutationFn: async () => {\n * // Send to API\n * }})\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * })\n *\n * // Rollback if needed\n * if (shouldCancel) {\n * tx.rollback()\n * }\n *\n * @example\n * // Handle rollback cascade (automatic)\n * const tx1 = createTransaction({ mutationFn: async () => {} })\n * const tx2 = createTransaction({ mutationFn: async () => {} })\n *\n * tx1.mutate(() => collection.update(\"1\", draft => { draft.value = \"A\" }))\n * tx2.mutate(() => collection.update(\"1\", draft => { draft.value = \"B\" })) // Same item\n *\n * tx1.rollback() // This will also rollback tx2 due to conflict\n *\n * @example\n * // Handle rollback in error scenarios\n * try {\n * await tx.isPersisted.promise\n * } catch (error) {\n * console.log('Transaction was rolled back:', error)\n * // Transaction automatically rolled back on mutation function failure\n * }\n */\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction<T> {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw new TransactionAlreadyCompletedRollbackError()\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same ids\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationIds = new Set()\n this.mutations.forEach((m) => mutationIds.add(m.globalKey))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.globalKey)) &&\n t.rollback({ isSecondaryRollback: true })\n }\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n for (const mutation of this.mutations) {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection._state.onTransactionStateChange()\n\n // Only call commitPendingTransactions if there are pending sync transactions\n if (mutation.collection._state.pendingSyncedTransactions.length > 0) {\n mutation.collection._state.commitPendingTransactions()\n }\n\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n /**\n * Commit the transaction and execute the mutation function\n * @returns Promise that resolves to this transaction when complete\n * @example\n * // Manual commit (when autoCommit is false)\n * const tx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * })\n *\n * await tx.commit() // Manually commit\n *\n * @example\n * // Handle commit errors\n * try {\n * const tx = createTransaction({\n * mutationFn: async () => { throw new Error(\"API failed\") }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * await tx.commit()\n * } catch (error) {\n * console.log('Commit failed, transaction rolled back:', error)\n * }\n *\n * @example\n * // Check transaction state after commit\n * await tx.commit()\n * console.log(tx.state) // \"completed\" or \"failed\"\n */\n async commit(): Promise<Transaction<T>> {\n if (this.state !== `pending`) {\n throw new TransactionNotPendingCommitError()\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n this.isPersisted.resolve(this)\n\n return this\n }\n\n // Run mutationFn\n try {\n // At this point we know there's at least one mutation\n // We've already verified mutations is non-empty, so this cast is safe\n // Use a direct type assertion instead of object spreading to preserve the original type\n await this.mutationFn({\n transaction: this as unknown as TransactionWithMutations<T>,\n })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Preserve the original error for rethrowing\n const originalError =\n error instanceof Error ? error : new Error(String(error))\n\n // Update transaction with error information\n this.error = {\n message: originalError.message,\n error: originalError,\n }\n\n // rollback the transaction\n this.rollback()\n\n // Re-throw the original error to preserve identity and stack\n throw originalError\n }\n\n return this\n }\n\n /**\n * Compare two transactions by their createdAt time and sequence number in order\n * to sort them in the order they were created.\n * @param other - The other transaction to compare to\n * @returns -1 if this transaction was created before the other, 1 if it was created after, 0 if they were created at the same time\n */\n compareCreatedAt(other: Transaction<any>): number {\n const createdAtComparison =\n this.createdAt.getTime() - other.createdAt.getTime()\n if (createdAtComparison !== 0) {\n return createdAtComparison\n }\n return this.sequenceNumber - other.sequenceNumber\n }\n}\n\nexport type { Transaction }\n"],"names":["MissingMutationFunctionError","createDeferred","TransactionNotPendingMutateError","TransactionAlreadyCompletedRollbackError","TransactionNotPendingCommitError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport {\n MissingMutationFunctionError,\n TransactionAlreadyCompletedRollbackError,\n TransactionNotPendingCommitError,\n TransactionNotPendingMutateError,\n} from \"./errors\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n MutationFn,\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\n\nlet sequenceNumber = 0\n\n/**\n * Merges two pending mutations for the same item within a transaction\n *\n * Merge behavior truth table:\n * - (insert, update) → insert (merge changes, keep empty original)\n * - (insert, delete) → null (cancel both mutations)\n * - (update, delete) → delete (delete dominates)\n * - (update, update) → update (replace with latest, union changes)\n * - (delete, delete) → delete (replace with latest)\n * - (insert, insert) → insert (replace with latest)\n *\n * Note: (delete, update) and (delete, insert) should never occur as the collection\n * layer prevents operations on deleted items within the same transaction.\n *\n * @param existing - The existing mutation in the transaction\n * @param incoming - The new mutation being applied\n * @returns The merged mutation, or null if both should be removed\n */\nfunction mergePendingMutations<T extends object>(\n existing: PendingMutation<T>,\n incoming: PendingMutation<T>\n): PendingMutation<T> | null {\n // Truth table implementation\n switch (`${existing.type}-${incoming.type}` as const) {\n case `insert-update`: {\n // Update after insert: keep as insert but merge changes\n // For insert-update, the key should remain the same since collections don't allow key changes\n return {\n ...existing,\n type: `insert` as const,\n original: {},\n modified: incoming.modified,\n changes: { ...existing.changes, ...incoming.changes },\n // Keep existing keys (key changes not allowed in updates)\n key: existing.key,\n globalKey: existing.globalKey,\n // Merge metadata (last-write-wins)\n metadata: incoming.metadata ?? existing.metadata,\n syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },\n // Update tracking info\n mutationId: incoming.mutationId,\n updatedAt: incoming.updatedAt,\n }\n }\n\n case `insert-delete`:\n // Delete after insert: cancel both mutations\n return null\n\n case `update-delete`:\n // Delete after update: delete dominates\n return incoming\n\n case `update-update`: {\n // Update after update: replace with latest, union changes\n return {\n ...incoming,\n // Keep original from first update\n original: existing.original,\n // Union the changes from both updates\n changes: { ...existing.changes, ...incoming.changes },\n // Merge metadata\n metadata: incoming.metadata ?? existing.metadata,\n syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },\n }\n }\n\n case `delete-delete`:\n case `insert-insert`:\n // Same type: replace with latest\n return incoming\n\n default: {\n // Exhaustiveness check\n const _exhaustive: never = `${existing.type}-${incoming.type}` as never\n throw new Error(`Unhandled mutation combination: ${_exhaustive}`)\n }\n }\n}\n\n/**\n * Creates a new transaction for grouping multiple collection operations\n * @param config - Transaction configuration with mutation function\n * @returns A new Transaction instance\n * @example\n * // Basic transaction usage\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Send all mutations to API\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * collection.update(\"2\", draft => { draft.completed = true })\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle transaction errors\n * try {\n * const tx = createTransaction({\n * mutationFn: async () => { throw new Error(\"API failed\") }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"New item\" })\n * })\n *\n * await tx.isPersisted.promise\n * } catch (error) {\n * console.log('Transaction failed:', error)\n * }\n *\n * @example\n * // Manual commit control\n * const tx = createTransaction({\n * autoCommit: false,\n * mutationFn: async () => {\n * // API call\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * // Commit later\n * await tx.commit()\n */\nexport function createTransaction<T extends object = Record<string, unknown>>(\n config: TransactionConfig<T>\n): Transaction<T> {\n const newTransaction = new Transaction<T>(config)\n transactions.push(newTransaction)\n return newTransaction\n}\n\n/**\n * Gets the currently active ambient transaction, if any\n * Used internally by collection operations to join existing transactions\n * @returns The active transaction or undefined if none is active\n * @example\n * // Check if operations will join an ambient transaction\n * const ambientTx = getActiveTransaction()\n * if (ambientTx) {\n * console.log('Operations will join transaction:', ambientTx.id)\n * }\n */\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction<any>) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction<any>) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction<any>) {\n const index = transactions.findIndex((t) => t.id === tx.id)\n if (index !== -1) {\n transactions.splice(index, 1)\n }\n}\n\nclass Transaction<T extends object = Record<string, unknown>> {\n public id: string\n public state: TransactionState\n public mutationFn: MutationFn<T>\n public mutations: Array<PendingMutation<T>>\n public isPersisted: Deferred<Transaction<T>>\n public autoCommit: boolean\n public createdAt: Date\n public sequenceNumber: number\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig<T>) {\n if (typeof config.mutationFn === `undefined`) {\n throw new MissingMutationFunctionError()\n }\n this.id = config.id ?? crypto.randomUUID()\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred<Transaction<T>>()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.sequenceNumber = sequenceNumber++\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n\n if (newState === `completed` || newState === `failed`) {\n removeFromPendingList(this)\n }\n }\n\n /**\n * Execute collection operations within this transaction\n * @param callback - Function containing collection operations to group together\n * @returns This transaction for chaining\n * @example\n * // Group multiple operations\n * const tx = createTransaction({ mutationFn: async () => {\n * // Send to API\n * }})\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * collection.update(\"2\", draft => { draft.completed = true })\n * collection.delete(\"3\")\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle mutate errors\n * try {\n * tx.mutate(() => {\n * collection.insert({ id: \"invalid\" }) // This might throw\n * })\n * } catch (error) {\n * console.log('Mutation failed:', error)\n * }\n *\n * @example\n * // Manual commit control\n * const tx = createTransaction({ autoCommit: false, mutationFn: async () => {} })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * // Commit later when ready\n * await tx.commit()\n */\n mutate(callback: () => void): Transaction<T> {\n if (this.state !== `pending`) {\n throw new TransactionNotPendingMutateError()\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit().catch(() => {\n // Errors from autoCommit are handled via isPersisted.promise\n // This catch prevents unhandled promise rejections\n })\n }\n\n return this\n }\n\n /**\n * Apply new mutations to this transaction, intelligently merging with existing mutations\n *\n * When mutations operate on the same item (same globalKey), they are merged according to\n * the following rules:\n *\n * - **insert + update** → insert (merge changes, keep empty original)\n * - **insert + delete** → removed (mutations cancel each other out)\n * - **update + delete** → delete (delete dominates)\n * - **update + update** → update (union changes, keep first original)\n * - **same type** → replace with latest\n *\n * This merging reduces over-the-wire churn and keeps the optimistic local view\n * aligned with user intent.\n *\n * @param mutations - Array of new mutations to apply\n */\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.globalKey === newMutation.globalKey\n )\n\n if (existingIndex >= 0) {\n const existingMutation = this.mutations[existingIndex]!\n const mergeResult = mergePendingMutations(existingMutation, newMutation)\n\n if (mergeResult === null) {\n // Remove the mutation (e.g., delete after insert cancels both)\n this.mutations.splice(existingIndex, 1)\n } else {\n // Replace with merged mutation\n this.mutations[existingIndex] = mergeResult\n }\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n /**\n * Rollback the transaction and any conflicting transactions\n * @param config - Configuration for rollback behavior\n * @returns This transaction for chaining\n * @example\n * // Manual rollback\n * const tx = createTransaction({ mutationFn: async () => {\n * // Send to API\n * }})\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * })\n *\n * // Rollback if needed\n * if (shouldCancel) {\n * tx.rollback()\n * }\n *\n * @example\n * // Handle rollback cascade (automatic)\n * const tx1 = createTransaction({ mutationFn: async () => {} })\n * const tx2 = createTransaction({ mutationFn: async () => {} })\n *\n * tx1.mutate(() => collection.update(\"1\", draft => { draft.value = \"A\" }))\n * tx2.mutate(() => collection.update(\"1\", draft => { draft.value = \"B\" })) // Same item\n *\n * tx1.rollback() // This will also rollback tx2 due to conflict\n *\n * @example\n * // Handle rollback in error scenarios\n * try {\n * await tx.isPersisted.promise\n * } catch (error) {\n * console.log('Transaction was rolled back:', error)\n * // Transaction automatically rolled back on mutation function failure\n * }\n */\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction<T> {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw new TransactionAlreadyCompletedRollbackError()\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same ids\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationIds = new Set()\n this.mutations.forEach((m) => mutationIds.add(m.globalKey))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.globalKey)) &&\n t.rollback({ isSecondaryRollback: true })\n }\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n for (const mutation of this.mutations) {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection._state.onTransactionStateChange()\n\n // Only call commitPendingTransactions if there are pending sync transactions\n if (mutation.collection._state.pendingSyncedTransactions.length > 0) {\n mutation.collection._state.commitPendingTransactions()\n }\n\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n /**\n * Commit the transaction and execute the mutation function\n * @returns Promise that resolves to this transaction when complete\n * @example\n * // Manual commit (when autoCommit is false)\n * const tx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Buy milk\" })\n * })\n *\n * await tx.commit() // Manually commit\n *\n * @example\n * // Handle commit errors\n * try {\n * const tx = createTransaction({\n * mutationFn: async () => { throw new Error(\"API failed\") }\n * })\n *\n * tx.mutate(() => {\n * collection.insert({ id: \"1\", text: \"Item\" })\n * })\n *\n * await tx.commit()\n * } catch (error) {\n * console.log('Commit failed, transaction rolled back:', error)\n * }\n *\n * @example\n * // Check transaction state after commit\n * await tx.commit()\n * console.log(tx.state) // \"completed\" or \"failed\"\n */\n async commit(): Promise<Transaction<T>> {\n if (this.state !== `pending`) {\n throw new TransactionNotPendingCommitError()\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n this.isPersisted.resolve(this)\n\n return this\n }\n\n // Run mutationFn\n try {\n // At this point we know there's at least one mutation\n // We've already verified mutations is non-empty, so this cast is safe\n // Use a direct type assertion instead of object spreading to preserve the original type\n await this.mutationFn({\n transaction: this as unknown as TransactionWithMutations<T>,\n })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Preserve the original error for rethrowing\n const originalError =\n error instanceof Error ? error : new Error(String(error))\n\n // Update transaction with error information\n this.error = {\n message: originalError.message,\n error: originalError,\n }\n\n // rollback the transaction\n this.rollback()\n\n // Re-throw the original error to preserve identity and stack\n throw originalError\n }\n\n return this\n }\n\n /**\n * Compare two transactions by their createdAt time and sequence number in order\n * to sort them in the order they were created.\n * @param other - The other transaction to compare to\n * @returns -1 if this transaction was created before the other, 1 if it was created after, 0 if they were created at the same time\n */\n compareCreatedAt(other: Transaction<any>): number {\n const createdAtComparison =\n this.createdAt.getTime() - other.createdAt.getTime()\n if (createdAtComparison !== 0) {\n return createdAtComparison\n }\n return this.sequenceNumber - other.sequenceNumber\n }\n}\n\nexport type { Transaction }\n"],"names":["MissingMutationFunctionError","createDeferred","TransactionNotPendingMutateError","TransactionAlreadyCompletedRollbackError","TransactionNotPendingCommitError"],"mappings":"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"}
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -375,6 +375,19 @@ export interface BaseCollectionConfig<T extends object = Record<string, unknown>
|
|
|
375
375
|
export interface CollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
376
376
|
sync: SyncConfig<T, TKey>;
|
|
377
377
|
}
|
|
378
|
+
export type SingleResult = {
|
|
379
|
+
singleResult: true;
|
|
380
|
+
};
|
|
381
|
+
export type NonSingleResult = {
|
|
382
|
+
singleResult?: never;
|
|
383
|
+
};
|
|
384
|
+
export type MaybeSingleResult = {
|
|
385
|
+
/**
|
|
386
|
+
* If enabled the collection will return a single object instead of an array
|
|
387
|
+
*/
|
|
388
|
+
singleResult?: true;
|
|
389
|
+
};
|
|
390
|
+
export type CollectionConfigSingleRowOption<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> = CollectionConfig<T, TKey, TSchema> & MaybeSingleResult;
|
|
378
391
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
|
|
379
392
|
/**
|
|
380
393
|
* An input row from a collection
|
package/dist/cjs/utils/btree.cjs
CHANGED
|
@@ -250,7 +250,7 @@ class BTree {
|
|
|
250
250
|
} finally {
|
|
251
251
|
let isShared;
|
|
252
252
|
while (root.keys.length <= 1 && !root.isLeaf) {
|
|
253
|
-
isShared
|
|
253
|
+
isShared ||= root.isShared;
|
|
254
254
|
this._root = root = root.keys.length === 0 ? EmptyLeaf : root.children[0];
|
|
255
255
|
}
|
|
256
256
|
if (isShared) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btree.cjs","sources":["../../../src/utils/btree.ts"],"sourcesContent":["// This file was copied from https://github.com/qwertie/btree-typescript/tree/master and adapted to our needs.\n// We removed methods that we don't need.\n\n// B+ tree by David Piepgrass. License: MIT\ntype EditRangeResult<V, R = number> = {\n value?: V\n break?: R\n delete?: boolean\n}\n\ntype index = number\n\n// Informative microbenchmarks & stuff:\n// http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation (very educational)\n// https://blog.mozilla.org/luke/2012/10/02/optimizing-javascript-variable-access/ (local vars are faster than properties)\n// http://benediktmeurer.de/2017/12/13/an-introduction-to-speculative-optimization-in-v8/ (other stuff)\n// https://jsperf.com/js-in-operator-vs-alternatives (avoid 'in' operator; `.p!==undefined` faster than `hasOwnProperty('p')` in all browsers)\n// https://jsperf.com/instanceof-vs-typeof-vs-constructor-vs-member (speed of type tests varies wildly across browsers)\n// https://jsperf.com/detecting-arrays-new (a.constructor===Array is best across browsers, assuming a is an object)\n// https://jsperf.com/shallow-cloning-methods (a constructor is faster than Object.create; hand-written clone faster than Object.assign)\n// https://jsperf.com/ways-to-fill-an-array (slice-and-replace is fastest)\n// https://jsperf.com/math-min-max-vs-ternary-vs-if (Math.min/max is slow on Edge)\n// https://jsperf.com/array-vs-property-access-speed (v.x/v.y is faster than a[0]/a[1] in major browsers IF hidden class is constant)\n// https://jsperf.com/detect-not-null-or-undefined (`x==null` slightly slower than `x===null||x===undefined` on all browsers)\n// Overall, microbenchmarks suggest Firefox is the fastest browser for JavaScript and Edge is the slowest.\n// Lessons from https://v8project.blogspot.com/2017/09/elements-kinds-in-v8.html:\n// - Avoid holes in arrays. Avoid `new Array(N)`, it will be \"holey\" permanently.\n// - Don't read outside bounds of an array (it scans prototype chain).\n// - Small integer arrays are stored differently from doubles\n// - Adding non-numbers to an array deoptimizes it permanently into a general array\n// - Objects can be used like arrays (e.g. have length property) but are slower\n// - V8 source (NewElementsCapacity in src/objects.h): arrays grow by 50% + 16 elements\n\n/**\n * A reasonably fast collection of key-value pairs with a powerful API.\n * Largely compatible with the standard Map. BTree is a B+ tree data structure,\n * so the collection is sorted by key.\n *\n * B+ trees tend to use memory more efficiently than hashtables such as the\n * standard Map, especially when the collection contains a large number of\n * items. However, maintaining the sort order makes them modestly slower:\n * O(log size) rather than O(1). This B+ tree implementation supports O(1)\n * fast cloning. It also supports freeze(), which can be used to ensure that\n * a BTree is not changed accidentally.\n *\n * Confusingly, the ES6 Map.forEach(c) method calls c(value,key) instead of\n * c(key,value), in contrast to other methods such as set() and entries()\n * which put the key first. I can only assume that the order was reversed on\n * the theory that users would usually want to examine values and ignore keys.\n * BTree's forEach() therefore works the same way, but a second method\n * `.forEachPair((key,value)=>{...})` is provided which sends you the key\n * first and the value second; this method is slightly faster because it is\n * the \"native\" for-each method for this class.\n *\n * Out of the box, BTree supports keys that are numbers, strings, arrays of\n * numbers/strings, Date, and objects that have a valueOf() method returning a\n * number or string. Other data types, such as arrays of Date or custom\n * objects, require a custom comparator, which you must pass as the second\n * argument to the constructor (the first argument is an optional list of\n * initial items). Symbols cannot be used as keys because they are unordered\n * (one Symbol is never \"greater\" or \"less\" than another).\n *\n * @example\n * Given a {name: string, age: number} object, you can create a tree sorted by\n * name and then by age like this:\n *\n * var tree = new BTree(undefined, (a, b) => {\n * if (a.name > b.name)\n * return 1; // Return a number >0 when a > b\n * else if (a.name < b.name)\n * return -1; // Return a number <0 when a < b\n * else // names are equal (or incomparable)\n * return a.age - b.age; // Return >0 when a.age > b.age\n * });\n *\n * tree.set({name:\"Bill\", age:17}, \"happy\");\n * tree.set({name:\"Fran\", age:40}, \"busy & stressed\");\n * tree.set({name:\"Bill\", age:55}, \"recently laid off\");\n * tree.forEachPair((k, v) => {\n * console.log(`Name: ${k.name} Age: ${k.age} Status: ${v}`);\n * });\n *\n * @description\n * The \"range\" methods (`forEach, forRange, editRange`) will return the number\n * of elements that were scanned. In addition, the callback can return {break:R}\n * to stop early and return R from the outer function.\n *\n * - TODO: Test performance of preallocating values array at max size\n * - TODO: Add fast initialization when a sorted array is provided to constructor\n *\n * For more documentation see https://github.com/qwertie/btree-typescript\n *\n * Are you a C# developer? You might like the similar data structures I made for C#:\n * BDictionary, BList, etc. See http://core.loyc.net/collections/\n *\n * @author David Piepgrass\n */\nexport class BTree<K = any, V = any> {\n private _root: BNode<K, V> = EmptyLeaf as BNode<K, V>\n _size = 0\n _maxNodeSize: number\n\n /**\n * provides a total order over keys (and a strict partial order over the type K)\n * @returns a negative value if a < b, 0 if a === b and a positive value if a > b\n */\n _compare: (a: K, b: K) => number\n\n /**\n * Initializes an empty B+ tree.\n * @param compare Custom function to compare pairs of elements in the tree.\n * If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.\n * @param entries A set of key-value pairs to initialize the tree\n * @param maxNodeSize Branching factor (maximum items or children per node)\n * Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.\n */\n public constructor(\n compare: (a: K, b: K) => number,\n entries?: Array<[K, V]>,\n maxNodeSize?: number\n ) {\n this._maxNodeSize = maxNodeSize! >= 4 ? Math.min(maxNodeSize!, 256) : 32\n this._compare = compare\n if (entries) this.setPairs(entries)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // ES6 Map<K,V> methods /////////////////////////////////////////////////////\n\n /** Gets the number of key-value pairs in the tree. */\n get size() {\n return this._size\n }\n /** Gets the number of key-value pairs in the tree. */\n get length() {\n return this._size\n }\n /** Returns true iff the tree contains no key-value pairs. */\n get isEmpty() {\n return this._size === 0\n }\n\n /** Releases the tree so that its size is 0. */\n clear() {\n this._root = EmptyLeaf as BNode<K, V>\n this._size = 0\n }\n\n /**\n * Finds a pair in the tree and returns the associated value.\n * @param defaultValue a value to return if the key was not found.\n * @returns the value, or defaultValue if the key was not found.\n * @description Computational complexity: O(log size)\n */\n get(key: K, defaultValue?: V): V | undefined {\n return this._root.get(key, defaultValue, this)\n }\n\n /**\n * Adds or overwrites a key-value pair in the B+ tree.\n * @param key the key is used to determine the sort order of\n * data in the tree.\n * @param value data to associate with the key (optional)\n * @param overwrite Whether to overwrite an existing key-value pair\n * (default: true). If this is false and there is an existing\n * key-value pair then this method has no effect.\n * @returns true if a new key-value pair was added.\n * @description Computational complexity: O(log size)\n * Note: when overwriting a previous entry, the key is updated\n * as well as the value. This has no effect unless the new key\n * has data that does not affect its sort order.\n */\n set(key: K, value: V, overwrite?: boolean): boolean {\n if (this._root.isShared) this._root = this._root.clone()\n const result = this._root.set(key, value, overwrite, this)\n if (result === true || result === false) return result\n // Root node has split, so create a new root node.\n this._root = new BNodeInternal<K, V>([this._root, result])\n return true\n }\n\n /**\n * Returns true if the key exists in the B+ tree, false if not.\n * Use get() for best performance; use has() if you need to\n * distinguish between \"undefined value\" and \"key not present\".\n * @param key Key to detect\n * @description Computational complexity: O(log size)\n */\n has(key: K): boolean {\n return this.forRange(key, key, true, undefined) !== 0\n }\n\n /**\n * Removes a single key-value pair from the B+ tree.\n * @param key Key to find\n * @returns true if a pair was found and removed, false otherwise.\n * @description Computational complexity: O(log size)\n */\n delete(key: K): boolean {\n return this.editRange(key, key, true, DeleteRange) !== 0\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Additional methods ///////////////////////////////////////////////////////\n\n /** Returns the maximum number of children/values before nodes will split. */\n get maxNodeSize() {\n return this._maxNodeSize\n }\n\n /** Gets the lowest key in the tree. Complexity: O(log size) */\n minKey(): K | undefined {\n return this._root.minKey()\n }\n\n /** Gets the highest key in the tree. Complexity: O(1) */\n maxKey(): K | undefined {\n return this._root.maxKey()\n }\n\n /** Gets an array of all keys, sorted */\n keysArray() {\n const results: Array<K> = []\n this._root.forRange(\n this.minKey()!,\n this.maxKey()!,\n true,\n false,\n this,\n 0,\n (k, _v) => {\n results.push(k)\n }\n )\n return results\n }\n\n /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the lowest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array on every iteration.\n */\n nextHigherPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.minPair(reusedArray)\n }\n return this._root.getPairOrNextHigher(\n key,\n this._compare,\n false,\n reusedArray\n )\n }\n\n /** Returns the next key larger than the specified key, or undefined if there is none.\n * Also, nextHigherKey(undefined) returns the lowest key.\n */\n nextHigherKey(key: K | undefined): K | undefined {\n const p = this.nextHigherPair(key, ReusedArray as [K, V])\n return p && p[0]\n }\n\n /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the highest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array each time you call this method.\n */\n nextLowerPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.maxPair(reusedArray)\n }\n return this._root.getPairOrNextLower(key, this._compare, false, reusedArray)\n }\n\n /** Returns the next key smaller than the specified key, or undefined if there is none.\n * Also, nextLowerKey(undefined) returns the highest key.\n */\n nextLowerKey(key: K | undefined): K | undefined {\n const p = this.nextLowerPair(key, ReusedArray as [K, V])\n return p && p[0]\n }\n\n /** Adds all pairs from a list of key-value pairs.\n * @param pairs Pairs to add to this tree. If there are duplicate keys,\n * later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]\n * associates 0 with 7.)\n * @param overwrite Whether to overwrite pairs that already exist (if false,\n * pairs[i] is ignored when the key pairs[i][0] already exists.)\n * @returns The number of pairs added to the collection.\n * @description Computational complexity: O(pairs.length * log(size + pairs.length))\n */\n setPairs(pairs: Array<[K, V]>, overwrite?: boolean): number {\n let added = 0\n for (const pair of pairs) {\n if (this.set(pair[0], pair[1], overwrite)) added++\n }\n return added\n }\n\n forRange(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => void,\n initialCounter?: number\n ): number\n\n /**\n * Scans the specified range of keys, in ascending order by key.\n * Note: the callback `onFound` must not insert or remove items in the\n * collection. Doing so may cause incorrect data to be sent to the\n * callback afterward.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return {break:R} to stop early with result R.\n * @param initialCounter Initial third argument of onFound. This value\n * increases by one each time `onFound` is called. Default: 0\n * @returns The number of values found, or R if the callback returned\n * `{break:R}` to stop early.\n * @description Computational complexity: O(number of items scanned + log size)\n */\n forRange<R = number>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => { break?: R } | void,\n initialCounter?: number\n ): R | number {\n const r = this._root.forRange(\n low,\n high,\n includeHigh,\n false,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n }\n\n /**\n * Scans and potentially modifies values for a subsequence of keys.\n * Note: the callback `onFound` should ideally be a pure function.\n * Specfically, it must not insert items, call clone(), or change\n * the collection except via return value; out-of-band editing may\n * cause an exception or may cause incorrect data to be sent to\n * the callback (duplicate or missed items). It must not cause a\n * clone() of the collection, otherwise the clone could be modified\n * by changes requested by the callback.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return `{value:v}` to change the value associated\n * with the current key, `{delete:true}` to delete the current pair,\n * `{break:R}` to stop early with result R, or it can return nothing\n * (undefined or {}) to cause no effect and continue iterating.\n * `{break:R}` can be combined with one of the other two commands.\n * The third argument `counter` is the number of items iterated\n * previously; it equals 0 when `onFound` is called the first time.\n * @returns The number of values scanned, or R if the callback returned\n * `{break:R}` to stop early.\n * @description\n * Computational complexity: O(number of items scanned + log size)\n * Note: if the tree has been cloned with clone(), any shared\n * nodes are copied before `onFound` is called. This takes O(n) time\n * where n is proportional to the amount of shared data scanned.\n */\n editRange<R = V>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,\n initialCounter?: number\n ): R | number {\n let root = this._root\n if (root.isShared) this._root = root = root.clone()\n try {\n const r = root.forRange(\n low,\n high,\n includeHigh,\n true,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n } finally {\n let isShared\n while (root.keys.length <= 1 && !root.isLeaf) {\n isShared ||= root.isShared\n this._root = root =\n root.keys.length === 0\n ? EmptyLeaf\n : (root as any as BNodeInternal<K, V>).children[0]!\n }\n // If any ancestor of the new root was shared, the new root must also be shared\n if (isShared) {\n root.isShared = true\n }\n }\n }\n}\n\n/** Leaf node / base class. **************************************************/\nclass BNode<K, V> {\n // If this is an internal node, _keys[i] is the highest key in children[i].\n keys: Array<K>\n values: Array<V>\n // True if this node might be within multiple `BTree`s (or have multiple parents).\n // If so, it must be cloned before being mutated to avoid changing an unrelated tree.\n // This is transitive: if it's true, children are also shared even if `isShared!=true`\n // in those children. (Certain operations will propagate isShared=true to children.)\n isShared: true | undefined\n get isLeaf() {\n return (this as any).children === undefined\n }\n\n constructor(keys: Array<K> = [], values?: Array<V>) {\n this.keys = keys\n this.values = values || undefVals\n this.isShared = undefined\n }\n\n // /////////////////////////////////////////////////////////////////////////\n // Shared methods /////////////////////////////////////////////////////////\n\n maxKey() {\n return this.keys[this.keys.length - 1]\n }\n\n // If key not found, returns i^failXor where i is the insertion index.\n // Callers that don't care whether there was a match will set failXor=0.\n indexOf(key: K, failXor: number, cmp: (a: K, b: K) => number): index {\n const keys = this.keys\n let lo = 0,\n hi = keys.length,\n mid = hi >> 1\n while (lo < hi) {\n const c = cmp(keys[mid]!, key)\n if (c < 0) lo = mid + 1\n else if (c > 0)\n // key < keys[mid]\n hi = mid\n else if (c === 0) return mid\n else {\n // c is NaN or otherwise invalid\n if (key === key)\n // at least the search key is not NaN\n return keys.length\n else throw new Error(`BTree: NaN was used as a key`)\n }\n mid = (lo + hi) >> 1\n }\n return mid ^ failXor\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: misc //////////////////////////////////////////////////////////\n\n minKey(): K | undefined {\n return this.keys[0]\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n reusedArray[0] = this.keys[0]!\n reusedArray[1] = this.values[0]!\n return reusedArray\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n const lastIndex = this.keys.length - 1\n reusedArray[0] = this.keys[lastIndex]!\n reusedArray[1] = this.values[lastIndex]!\n return reusedArray\n }\n\n clone(): BNode<K, V> {\n const v = this.values\n return new BNode<K, V>(this.keys.slice(0), v === undefVals ? v : v.slice(0))\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, -1, tree._compare)\n return i < 0 ? defaultValue : this.values[i]\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1\n if (indexOrLower >= 0) {\n reusedArray[0] = this.keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1\n const keys = this.keys\n if (indexOrLower < keys.length) {\n reusedArray[0] = keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: set & node splitting //////////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNode<K, V> {\n let i = this.indexOf(key, -1, tree._compare)\n if (i < 0) {\n // key does not exist yet\n i = ~i\n tree._size++\n\n if (this.keys.length < tree._maxNodeSize) {\n return this.insertInLeaf(i, key, value, tree)\n } else {\n // This leaf node is full and must split\n const newRightSibling = this.splitOffRightSide()\n let target: BNode<K, V> = this\n if (i > this.keys.length) {\n i -= this.keys.length\n target = newRightSibling\n }\n target.insertInLeaf(i, key, value, tree)\n return newRightSibling\n }\n } else {\n // Key already exists\n if (overwrite !== false) {\n if (value !== undefined) this.reifyValues()\n // usually this is a no-op, but some users may wish to edit the key\n this.keys[i] = key\n this.values[i] = value\n }\n return false\n }\n }\n\n reifyValues() {\n if (this.values === undefVals)\n return (this.values = this.values.slice(0, this.keys.length))\n return this.values\n }\n\n insertInLeaf(i: index, key: K, value: V, tree: BTree<K, V>) {\n this.keys.splice(i, 0, key)\n if (this.values === undefVals) {\n while (undefVals.length < tree._maxNodeSize) undefVals.push(undefined)\n if (value === undefined) {\n return true\n } else {\n this.values = undefVals.slice(0, this.keys.length - 1)\n }\n }\n this.values.splice(i, 0, value)\n return true\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (rhs.values === undefVals) {\n if (v !== undefVals) v.push(undefined as any)\n } else {\n v = this.reifyValues()\n v.push(rhs.values.shift()!)\n }\n this.keys.push(rhs.keys.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (lhs.values === undefVals) {\n if (v !== undefVals) v.unshift(undefined as any)\n } else {\n v = this.reifyValues()\n v.unshift(lhs.values.pop()!)\n }\n this.keys.unshift(lhs.keys.pop()!)\n }\n\n splitOffRightSide(): BNode<K, V> {\n // Reminder: parent node must update its copy of key for this node\n const half = this.keys.length >> 1,\n keys = this.keys.splice(half)\n const values =\n this.values === undefVals ? undefVals : this.values.splice(half)\n return new BNode<K, V>(keys, values)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: scanning & deletions //////////////////////////////////////////\n\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n let iLow, iHigh\n if (high === low) {\n if (!includeHigh) return count\n iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1\n if (iLow < 0) return count\n } else {\n iLow = this.indexOf(low, 0, cmp)\n iHigh = this.indexOf(high, -1, cmp)\n if (iHigh < 0) iHigh = ~iHigh\n else if (includeHigh === true) iHigh++\n }\n const keys = this.keys,\n values = this.values\n if (onFound !== undefined) {\n for (let i = iLow; i < iHigh; i++) {\n const key = keys[i]!\n const result = onFound(key, values[i]!, count++)\n if (result !== undefined) {\n if (editMode === true) {\n if (key !== keys[i] || this.isShared === true)\n throw new Error(`BTree illegally changed or cloned in editRange`)\n if (result.delete) {\n this.keys.splice(i, 1)\n if (this.values !== undefVals) this.values.splice(i, 1)\n tree._size--\n i--\n iHigh--\n } else if (result.hasOwnProperty(`value`)) {\n values[i] = result.value!\n }\n }\n if (result.break !== undefined) return result\n }\n }\n } else count += iHigh - iLow\n return count\n }\n\n /** Adds entire contents of right-hand sibling (rhs is left unchanged) */\n mergeSibling(rhs: BNode<K, V>, _: number) {\n this.keys.push.apply(this.keys, rhs.keys)\n if (this.values === undefVals) {\n if (rhs.values === undefVals) return\n this.values = this.values.slice(0, this.keys.length)\n }\n this.values.push.apply(this.values, rhs.reifyValues())\n }\n}\n\n/** Internal node (non-leaf node) ********************************************/\nclass BNodeInternal<K, V> extends BNode<K, V> {\n // Note: conventionally B+ trees have one fewer key than the number of\n // children, but I find it easier to keep the array lengths equal: each\n // keys[i] caches the value of children[i].maxKey().\n children: Array<BNode<K, V>>\n\n /**\n * This does not mark `children` as shared, so it is the responsibility of the caller\n * to ensure children are either marked shared, or aren't included in another tree.\n */\n constructor(children: Array<BNode<K, V>>, keys?: Array<K>) {\n if (!keys) {\n keys = []\n for (let i = 0; i < children.length; i++) keys[i] = children[i]!.maxKey()!\n }\n super(keys)\n this.children = children\n }\n\n minKey() {\n return this.children[0]!.minKey()\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[0]!.minPair(reusedArray)\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[this.children.length - 1]!.maxPair(reusedArray)\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, 0, tree._compare),\n children = this.children\n return i < children.length\n ? children[i]!.get(key, defaultValue, tree)\n : undefined\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children\n if (i >= children.length) return this.maxPair(reusedArray)\n const result = children[i]!.getPairOrNextLower(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i > 0) {\n return children[i - 1]!.maxPair(reusedArray)\n }\n return result\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children,\n length = children.length\n if (i >= length) return undefined\n const result = children[i]!.getPairOrNextHigher(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i < length - 1) {\n return children[i + 1]!.minPair(reusedArray)\n }\n return result\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: set & node splitting //////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNodeInternal<K, V> {\n const c = this.children,\n max = tree._maxNodeSize,\n cmp = tree._compare\n let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1),\n child = c[i]!\n\n if (child.isShared) c[i] = child = child.clone()\n if (child.keys.length >= max) {\n // child is full; inserting anything else will cause a split.\n // Shifting an item to the left or right sibling may avoid a split.\n // We can do a shift if the adjacent node is not full and if the\n // current key can still be placed in the same node after the shift.\n let other: BNode<K, V> | undefined\n if (\n i > 0 &&\n (other = c[i - 1]!).keys.length < max &&\n cmp(child.keys[0]!, key) < 0\n ) {\n if (other.isShared) c[i - 1] = other = other.clone()\n other.takeFromRight(child)\n this.keys[i - 1] = other.maxKey()!\n } else if (\n (other = c[i + 1]) !== undefined &&\n other.keys.length < max &&\n cmp(child.maxKey()!, key) < 0\n ) {\n if (other.isShared) c[i + 1] = other = other.clone()\n other.takeFromLeft(child)\n this.keys[i] = c[i]!.maxKey()!\n }\n }\n\n const result = child.set(key, value, overwrite, tree)\n if (result === false) return false\n this.keys[i] = child.maxKey()!\n if (result === true) return true\n\n // The child has split and `result` is a new right child... does it fit?\n if (this.keys.length < max) {\n // yes\n this.insert(i + 1, result)\n return true\n } else {\n // no, we must split also\n const newRightSibling = this.splitOffRightSide()\n let target: BNodeInternal<K, V> = this\n if (cmp(result.maxKey()!, this.maxKey()!) > 0) {\n target = newRightSibling\n i -= this.keys.length\n }\n target.insert(i + 1, result)\n return newRightSibling\n }\n }\n\n /**\n * Inserts `child` at index `i`.\n * This does not mark `child` as shared, so it is the responsibility of the caller\n * to ensure that either child is marked shared, or it is not included in another tree.\n */\n insert(i: index, child: BNode<K, V>) {\n this.children.splice(i, 0, child)\n this.keys.splice(i, 0, child.maxKey()!)\n }\n\n /**\n * Split this node.\n * Modifies this to remove the second half of the items, returning a separate node containing them.\n */\n splitOffRightSide() {\n // assert !this.isShared;\n const half = this.children.length >> 1\n return new BNodeInternal<K, V>(\n this.children.splice(half),\n this.keys.splice(half)\n )\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.push(rhs.keys.shift()!)\n this.children.push((rhs as BNodeInternal<K, V>).children.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.unshift(lhs.keys.pop()!)\n this.children.unshift((lhs as BNodeInternal<K, V>).children.pop()!)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: scanning & deletions //////////////////////////////////////\n\n // Note: `count` is the next value of the third argument to `onFound`.\n // A leaf node's `forRange` function returns a new value for this counter,\n // unless the operation is to stop early.\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n const keys = this.keys,\n children = this.children\n let iLow = this.indexOf(low, 0, cmp),\n i = iLow\n const iHigh = Math.min(\n high === low ? iLow : this.indexOf(high, 0, cmp),\n keys.length - 1\n )\n if (!editMode) {\n // Simple case\n for (; i <= iHigh; i++) {\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n if (typeof result !== `number`) return result\n count = result\n }\n } else if (i <= iHigh) {\n try {\n for (; i <= iHigh; i++) {\n if (children[i]!.isShared) children[i] = children[i]!.clone()\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n // Note: if children[i] is empty then keys[i]=undefined.\n // This is an invalid state, but it is fixed below.\n keys[i] = children[i]!.maxKey()!\n if (typeof result !== `number`) return result\n count = result\n }\n } finally {\n // Deletions may have occurred, so look for opportunities to merge nodes.\n const half = tree._maxNodeSize >> 1\n if (iLow > 0) iLow--\n for (i = iHigh; i >= iLow; i--) {\n if (children[i]!.keys.length <= half) {\n if (children[i]!.keys.length !== 0) {\n this.tryMerge(i, tree._maxNodeSize)\n } else {\n // child is empty! delete it!\n keys.splice(i, 1)\n children.splice(i, 1)\n }\n }\n }\n if (children.length !== 0 && children[0]!.keys.length === 0)\n check(false, `emptiness bug`)\n }\n }\n return count\n }\n\n /** Merges child i with child i+1 if their combined size is not too large */\n tryMerge(i: index, maxSize: number): boolean {\n const children = this.children\n if (i >= 0 && i + 1 < children.length) {\n if (children[i]!.keys.length + children[i + 1]!.keys.length <= maxSize) {\n if (children[i]!.isShared)\n // cloned already UNLESS i is outside scan range\n children[i] = children[i]!.clone()\n children[i]!.mergeSibling(children[i + 1]!, maxSize)\n children.splice(i + 1, 1)\n this.keys.splice(i + 1, 1)\n this.keys[i] = children[i]!.maxKey()!\n return true\n }\n }\n return false\n }\n\n /**\n * Move children from `rhs` into this.\n * `rhs` must be part of this tree, and be removed from it after this call\n * (otherwise isShared for its children could be incorrect).\n */\n mergeSibling(rhs: BNode<K, V>, maxNodeSize: number) {\n // assert !this.isShared;\n const oldLength = this.keys.length\n this.keys.push.apply(this.keys, rhs.keys)\n const rhsChildren = (rhs as any as BNodeInternal<K, V>).children\n this.children.push.apply(this.children, rhsChildren)\n\n if (rhs.isShared && !this.isShared) {\n // All children of a shared node are implicitly shared, and since their new\n // parent is not shared, they must now be explicitly marked as shared.\n for (const child of rhsChildren) child.isShared = true\n }\n\n // If our children are themselves almost empty due to a mass-delete,\n // they may need to be merged too (but only the oldLength-1 and its\n // right sibling should need this).\n this.tryMerge(oldLength - 1, maxNodeSize)\n }\n}\n\n// Optimization: this array of `undefined`s is used instead of a normal\n// array of values in nodes where `undefined` is the only value.\n// Its length is extended to max node size on first use; since it can\n// be shared between trees with different maximums, its length can only\n// increase, never decrease. Its type should be undefined[] but strangely\n// TypeScript won't allow the comparison V[] === undefined[]. To prevent\n// users from making this array too large, BTree has a maximum node size.\n//\n// FAQ: undefVals[i] is already undefined, so why increase the array size?\n// Reading outside the bounds of an array is relatively slow because it\n// has the side effect of scanning the prototype chain.\nconst undefVals: Array<any> = []\n\nconst Delete = { delete: true },\n DeleteRange = () => Delete\nconst EmptyLeaf = (function () {\n const n = new BNode<any, any>()\n n.isShared = true\n return n\n})()\nconst ReusedArray: Array<any> = [] // assumed thread-local\n\nfunction check(fact: boolean, ...args: Array<any>) {\n if (!fact) {\n args.unshift(`B+ tree`) // at beginning of message\n throw new Error(args.join(` `))\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"btree.cjs","sources":["../../../src/utils/btree.ts"],"sourcesContent":["// This file was copied from https://github.com/qwertie/btree-typescript/tree/master and adapted to our needs.\n// We removed methods that we don't need.\n\n// B+ tree by David Piepgrass. License: MIT\ntype EditRangeResult<V, R = number> = {\n value?: V\n break?: R\n delete?: boolean\n}\n\ntype index = number\n\n// Informative microbenchmarks & stuff:\n// http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation (very educational)\n// https://blog.mozilla.org/luke/2012/10/02/optimizing-javascript-variable-access/ (local vars are faster than properties)\n// http://benediktmeurer.de/2017/12/13/an-introduction-to-speculative-optimization-in-v8/ (other stuff)\n// https://jsperf.com/js-in-operator-vs-alternatives (avoid 'in' operator; `.p!==undefined` faster than `hasOwnProperty('p')` in all browsers)\n// https://jsperf.com/instanceof-vs-typeof-vs-constructor-vs-member (speed of type tests varies wildly across browsers)\n// https://jsperf.com/detecting-arrays-new (a.constructor===Array is best across browsers, assuming a is an object)\n// https://jsperf.com/shallow-cloning-methods (a constructor is faster than Object.create; hand-written clone faster than Object.assign)\n// https://jsperf.com/ways-to-fill-an-array (slice-and-replace is fastest)\n// https://jsperf.com/math-min-max-vs-ternary-vs-if (Math.min/max is slow on Edge)\n// https://jsperf.com/array-vs-property-access-speed (v.x/v.y is faster than a[0]/a[1] in major browsers IF hidden class is constant)\n// https://jsperf.com/detect-not-null-or-undefined (`x==null` slightly slower than `x===null||x===undefined` on all browsers)\n// Overall, microbenchmarks suggest Firefox is the fastest browser for JavaScript and Edge is the slowest.\n// Lessons from https://v8project.blogspot.com/2017/09/elements-kinds-in-v8.html:\n// - Avoid holes in arrays. Avoid `new Array(N)`, it will be \"holey\" permanently.\n// - Don't read outside bounds of an array (it scans prototype chain).\n// - Small integer arrays are stored differently from doubles\n// - Adding non-numbers to an array deoptimizes it permanently into a general array\n// - Objects can be used like arrays (e.g. have length property) but are slower\n// - V8 source (NewElementsCapacity in src/objects.h): arrays grow by 50% + 16 elements\n\n/**\n * A reasonably fast collection of key-value pairs with a powerful API.\n * Largely compatible with the standard Map. BTree is a B+ tree data structure,\n * so the collection is sorted by key.\n *\n * B+ trees tend to use memory more efficiently than hashtables such as the\n * standard Map, especially when the collection contains a large number of\n * items. However, maintaining the sort order makes them modestly slower:\n * O(log size) rather than O(1). This B+ tree implementation supports O(1)\n * fast cloning. It also supports freeze(), which can be used to ensure that\n * a BTree is not changed accidentally.\n *\n * Confusingly, the ES6 Map.forEach(c) method calls c(value,key) instead of\n * c(key,value), in contrast to other methods such as set() and entries()\n * which put the key first. I can only assume that the order was reversed on\n * the theory that users would usually want to examine values and ignore keys.\n * BTree's forEach() therefore works the same way, but a second method\n * `.forEachPair((key,value)=>{...})` is provided which sends you the key\n * first and the value second; this method is slightly faster because it is\n * the \"native\" for-each method for this class.\n *\n * Out of the box, BTree supports keys that are numbers, strings, arrays of\n * numbers/strings, Date, and objects that have a valueOf() method returning a\n * number or string. Other data types, such as arrays of Date or custom\n * objects, require a custom comparator, which you must pass as the second\n * argument to the constructor (the first argument is an optional list of\n * initial items). Symbols cannot be used as keys because they are unordered\n * (one Symbol is never \"greater\" or \"less\" than another).\n *\n * @example\n * Given a {name: string, age: number} object, you can create a tree sorted by\n * name and then by age like this:\n *\n * var tree = new BTree(undefined, (a, b) => {\n * if (a.name > b.name)\n * return 1; // Return a number >0 when a > b\n * else if (a.name < b.name)\n * return -1; // Return a number <0 when a < b\n * else // names are equal (or incomparable)\n * return a.age - b.age; // Return >0 when a.age > b.age\n * });\n *\n * tree.set({name:\"Bill\", age:17}, \"happy\");\n * tree.set({name:\"Fran\", age:40}, \"busy & stressed\");\n * tree.set({name:\"Bill\", age:55}, \"recently laid off\");\n * tree.forEachPair((k, v) => {\n * console.log(`Name: ${k.name} Age: ${k.age} Status: ${v}`);\n * });\n *\n * @description\n * The \"range\" methods (`forEach, forRange, editRange`) will return the number\n * of elements that were scanned. In addition, the callback can return {break:R}\n * to stop early and return R from the outer function.\n *\n * - TODO: Test performance of preallocating values array at max size\n * - TODO: Add fast initialization when a sorted array is provided to constructor\n *\n * For more documentation see https://github.com/qwertie/btree-typescript\n *\n * Are you a C# developer? You might like the similar data structures I made for C#:\n * BDictionary, BList, etc. See http://core.loyc.net/collections/\n *\n * @author David Piepgrass\n */\nexport class BTree<K = any, V = any> {\n private _root: BNode<K, V> = EmptyLeaf as BNode<K, V>\n _size = 0\n _maxNodeSize: number\n\n /**\n * provides a total order over keys (and a strict partial order over the type K)\n * @returns a negative value if a < b, 0 if a === b and a positive value if a > b\n */\n _compare: (a: K, b: K) => number\n\n /**\n * Initializes an empty B+ tree.\n * @param compare Custom function to compare pairs of elements in the tree.\n * If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.\n * @param entries A set of key-value pairs to initialize the tree\n * @param maxNodeSize Branching factor (maximum items or children per node)\n * Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.\n */\n public constructor(\n compare: (a: K, b: K) => number,\n entries?: Array<[K, V]>,\n maxNodeSize?: number\n ) {\n this._maxNodeSize = maxNodeSize! >= 4 ? Math.min(maxNodeSize!, 256) : 32\n this._compare = compare\n if (entries) this.setPairs(entries)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // ES6 Map<K,V> methods /////////////////////////////////////////////////////\n\n /** Gets the number of key-value pairs in the tree. */\n get size() {\n return this._size\n }\n /** Gets the number of key-value pairs in the tree. */\n get length() {\n return this._size\n }\n /** Returns true iff the tree contains no key-value pairs. */\n get isEmpty() {\n return this._size === 0\n }\n\n /** Releases the tree so that its size is 0. */\n clear() {\n this._root = EmptyLeaf as BNode<K, V>\n this._size = 0\n }\n\n /**\n * Finds a pair in the tree and returns the associated value.\n * @param defaultValue a value to return if the key was not found.\n * @returns the value, or defaultValue if the key was not found.\n * @description Computational complexity: O(log size)\n */\n get(key: K, defaultValue?: V): V | undefined {\n return this._root.get(key, defaultValue, this)\n }\n\n /**\n * Adds or overwrites a key-value pair in the B+ tree.\n * @param key the key is used to determine the sort order of\n * data in the tree.\n * @param value data to associate with the key (optional)\n * @param overwrite Whether to overwrite an existing key-value pair\n * (default: true). If this is false and there is an existing\n * key-value pair then this method has no effect.\n * @returns true if a new key-value pair was added.\n * @description Computational complexity: O(log size)\n * Note: when overwriting a previous entry, the key is updated\n * as well as the value. This has no effect unless the new key\n * has data that does not affect its sort order.\n */\n set(key: K, value: V, overwrite?: boolean): boolean {\n if (this._root.isShared) this._root = this._root.clone()\n const result = this._root.set(key, value, overwrite, this)\n if (result === true || result === false) return result\n // Root node has split, so create a new root node.\n this._root = new BNodeInternal<K, V>([this._root, result])\n return true\n }\n\n /**\n * Returns true if the key exists in the B+ tree, false if not.\n * Use get() for best performance; use has() if you need to\n * distinguish between \"undefined value\" and \"key not present\".\n * @param key Key to detect\n * @description Computational complexity: O(log size)\n */\n has(key: K): boolean {\n return this.forRange(key, key, true, undefined) !== 0\n }\n\n /**\n * Removes a single key-value pair from the B+ tree.\n * @param key Key to find\n * @returns true if a pair was found and removed, false otherwise.\n * @description Computational complexity: O(log size)\n */\n delete(key: K): boolean {\n return this.editRange(key, key, true, DeleteRange) !== 0\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Additional methods ///////////////////////////////////////////////////////\n\n /** Returns the maximum number of children/values before nodes will split. */\n get maxNodeSize() {\n return this._maxNodeSize\n }\n\n /** Gets the lowest key in the tree. Complexity: O(log size) */\n minKey(): K | undefined {\n return this._root.minKey()\n }\n\n /** Gets the highest key in the tree. Complexity: O(1) */\n maxKey(): K | undefined {\n return this._root.maxKey()\n }\n\n /** Gets an array of all keys, sorted */\n keysArray() {\n const results: Array<K> = []\n this._root.forRange(\n this.minKey()!,\n this.maxKey()!,\n true,\n false,\n this,\n 0,\n (k, _v) => {\n results.push(k)\n }\n )\n return results\n }\n\n /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the lowest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array on every iteration.\n */\n nextHigherPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.minPair(reusedArray)\n }\n return this._root.getPairOrNextHigher(\n key,\n this._compare,\n false,\n reusedArray\n )\n }\n\n /** Returns the next key larger than the specified key, or undefined if there is none.\n * Also, nextHigherKey(undefined) returns the lowest key.\n */\n nextHigherKey(key: K | undefined): K | undefined {\n const p = this.nextHigherPair(key, ReusedArray as [K, V])\n return p && p[0]\n }\n\n /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the highest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array each time you call this method.\n */\n nextLowerPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.maxPair(reusedArray)\n }\n return this._root.getPairOrNextLower(key, this._compare, false, reusedArray)\n }\n\n /** Returns the next key smaller than the specified key, or undefined if there is none.\n * Also, nextLowerKey(undefined) returns the highest key.\n */\n nextLowerKey(key: K | undefined): K | undefined {\n const p = this.nextLowerPair(key, ReusedArray as [K, V])\n return p && p[0]\n }\n\n /** Adds all pairs from a list of key-value pairs.\n * @param pairs Pairs to add to this tree. If there are duplicate keys,\n * later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]\n * associates 0 with 7.)\n * @param overwrite Whether to overwrite pairs that already exist (if false,\n * pairs[i] is ignored when the key pairs[i][0] already exists.)\n * @returns The number of pairs added to the collection.\n * @description Computational complexity: O(pairs.length * log(size + pairs.length))\n */\n setPairs(pairs: Array<[K, V]>, overwrite?: boolean): number {\n let added = 0\n for (const pair of pairs) {\n if (this.set(pair[0], pair[1], overwrite)) added++\n }\n return added\n }\n\n forRange(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => void,\n initialCounter?: number\n ): number\n\n /**\n * Scans the specified range of keys, in ascending order by key.\n * Note: the callback `onFound` must not insert or remove items in the\n * collection. Doing so may cause incorrect data to be sent to the\n * callback afterward.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return {break:R} to stop early with result R.\n * @param initialCounter Initial third argument of onFound. This value\n * increases by one each time `onFound` is called. Default: 0\n * @returns The number of values found, or R if the callback returned\n * `{break:R}` to stop early.\n * @description Computational complexity: O(number of items scanned + log size)\n */\n forRange<R = number>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => { break?: R } | void,\n initialCounter?: number\n ): R | number {\n const r = this._root.forRange(\n low,\n high,\n includeHigh,\n false,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n }\n\n /**\n * Scans and potentially modifies values for a subsequence of keys.\n * Note: the callback `onFound` should ideally be a pure function.\n * Specfically, it must not insert items, call clone(), or change\n * the collection except via return value; out-of-band editing may\n * cause an exception or may cause incorrect data to be sent to\n * the callback (duplicate or missed items). It must not cause a\n * clone() of the collection, otherwise the clone could be modified\n * by changes requested by the callback.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return `{value:v}` to change the value associated\n * with the current key, `{delete:true}` to delete the current pair,\n * `{break:R}` to stop early with result R, or it can return nothing\n * (undefined or {}) to cause no effect and continue iterating.\n * `{break:R}` can be combined with one of the other two commands.\n * The third argument `counter` is the number of items iterated\n * previously; it equals 0 when `onFound` is called the first time.\n * @returns The number of values scanned, or R if the callback returned\n * `{break:R}` to stop early.\n * @description\n * Computational complexity: O(number of items scanned + log size)\n * Note: if the tree has been cloned with clone(), any shared\n * nodes are copied before `onFound` is called. This takes O(n) time\n * where n is proportional to the amount of shared data scanned.\n */\n editRange<R = V>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,\n initialCounter?: number\n ): R | number {\n let root = this._root\n if (root.isShared) this._root = root = root.clone()\n try {\n const r = root.forRange(\n low,\n high,\n includeHigh,\n true,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n } finally {\n let isShared\n while (root.keys.length <= 1 && !root.isLeaf) {\n isShared ||= root.isShared\n this._root = root =\n root.keys.length === 0\n ? EmptyLeaf\n : (root as any as BNodeInternal<K, V>).children[0]!\n }\n // If any ancestor of the new root was shared, the new root must also be shared\n if (isShared) {\n root.isShared = true\n }\n }\n }\n}\n\n/** Leaf node / base class. **************************************************/\nclass BNode<K, V> {\n // If this is an internal node, _keys[i] is the highest key in children[i].\n keys: Array<K>\n values: Array<V>\n // True if this node might be within multiple `BTree`s (or have multiple parents).\n // If so, it must be cloned before being mutated to avoid changing an unrelated tree.\n // This is transitive: if it's true, children are also shared even if `isShared!=true`\n // in those children. (Certain operations will propagate isShared=true to children.)\n isShared: true | undefined\n get isLeaf() {\n return (this as any).children === undefined\n }\n\n constructor(keys: Array<K> = [], values?: Array<V>) {\n this.keys = keys\n this.values = values || undefVals\n this.isShared = undefined\n }\n\n // /////////////////////////////////////////////////////////////////////////\n // Shared methods /////////////////////////////////////////////////////////\n\n maxKey() {\n return this.keys[this.keys.length - 1]\n }\n\n // If key not found, returns i^failXor where i is the insertion index.\n // Callers that don't care whether there was a match will set failXor=0.\n indexOf(key: K, failXor: number, cmp: (a: K, b: K) => number): index {\n const keys = this.keys\n let lo = 0,\n hi = keys.length,\n mid = hi >> 1\n while (lo < hi) {\n const c = cmp(keys[mid]!, key)\n if (c < 0) lo = mid + 1\n else if (c > 0)\n // key < keys[mid]\n hi = mid\n else if (c === 0) return mid\n else {\n // c is NaN or otherwise invalid\n if (key === key)\n // at least the search key is not NaN\n return keys.length\n else throw new Error(`BTree: NaN was used as a key`)\n }\n mid = (lo + hi) >> 1\n }\n return mid ^ failXor\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: misc //////////////////////////////////////////////////////////\n\n minKey(): K | undefined {\n return this.keys[0]\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n reusedArray[0] = this.keys[0]!\n reusedArray[1] = this.values[0]!\n return reusedArray\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n const lastIndex = this.keys.length - 1\n reusedArray[0] = this.keys[lastIndex]!\n reusedArray[1] = this.values[lastIndex]!\n return reusedArray\n }\n\n clone(): BNode<K, V> {\n const v = this.values\n return new BNode<K, V>(this.keys.slice(0), v === undefVals ? v : v.slice(0))\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, -1, tree._compare)\n return i < 0 ? defaultValue : this.values[i]\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1\n if (indexOrLower >= 0) {\n reusedArray[0] = this.keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1\n const keys = this.keys\n if (indexOrLower < keys.length) {\n reusedArray[0] = keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: set & node splitting //////////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNode<K, V> {\n let i = this.indexOf(key, -1, tree._compare)\n if (i < 0) {\n // key does not exist yet\n i = ~i\n tree._size++\n\n if (this.keys.length < tree._maxNodeSize) {\n return this.insertInLeaf(i, key, value, tree)\n } else {\n // This leaf node is full and must split\n const newRightSibling = this.splitOffRightSide()\n let target: BNode<K, V> = this\n if (i > this.keys.length) {\n i -= this.keys.length\n target = newRightSibling\n }\n target.insertInLeaf(i, key, value, tree)\n return newRightSibling\n }\n } else {\n // Key already exists\n if (overwrite !== false) {\n if (value !== undefined) this.reifyValues()\n // usually this is a no-op, but some users may wish to edit the key\n this.keys[i] = key\n this.values[i] = value\n }\n return false\n }\n }\n\n reifyValues() {\n if (this.values === undefVals)\n return (this.values = this.values.slice(0, this.keys.length))\n return this.values\n }\n\n insertInLeaf(i: index, key: K, value: V, tree: BTree<K, V>) {\n this.keys.splice(i, 0, key)\n if (this.values === undefVals) {\n while (undefVals.length < tree._maxNodeSize) undefVals.push(undefined)\n if (value === undefined) {\n return true\n } else {\n this.values = undefVals.slice(0, this.keys.length - 1)\n }\n }\n this.values.splice(i, 0, value)\n return true\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (rhs.values === undefVals) {\n if (v !== undefVals) v.push(undefined as any)\n } else {\n v = this.reifyValues()\n v.push(rhs.values.shift()!)\n }\n this.keys.push(rhs.keys.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (lhs.values === undefVals) {\n if (v !== undefVals) v.unshift(undefined as any)\n } else {\n v = this.reifyValues()\n v.unshift(lhs.values.pop()!)\n }\n this.keys.unshift(lhs.keys.pop()!)\n }\n\n splitOffRightSide(): BNode<K, V> {\n // Reminder: parent node must update its copy of key for this node\n const half = this.keys.length >> 1,\n keys = this.keys.splice(half)\n const values =\n this.values === undefVals ? undefVals : this.values.splice(half)\n return new BNode<K, V>(keys, values)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: scanning & deletions //////////////////////////////////////////\n\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n let iLow, iHigh\n if (high === low) {\n if (!includeHigh) return count\n iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1\n if (iLow < 0) return count\n } else {\n iLow = this.indexOf(low, 0, cmp)\n iHigh = this.indexOf(high, -1, cmp)\n if (iHigh < 0) iHigh = ~iHigh\n else if (includeHigh === true) iHigh++\n }\n const keys = this.keys,\n values = this.values\n if (onFound !== undefined) {\n for (let i = iLow; i < iHigh; i++) {\n const key = keys[i]!\n const result = onFound(key, values[i]!, count++)\n if (result !== undefined) {\n if (editMode === true) {\n if (key !== keys[i] || this.isShared === true)\n throw new Error(`BTree illegally changed or cloned in editRange`)\n if (result.delete) {\n this.keys.splice(i, 1)\n if (this.values !== undefVals) this.values.splice(i, 1)\n tree._size--\n i--\n iHigh--\n } else if (result.hasOwnProperty(`value`)) {\n values[i] = result.value!\n }\n }\n if (result.break !== undefined) return result\n }\n }\n } else count += iHigh - iLow\n return count\n }\n\n /** Adds entire contents of right-hand sibling (rhs is left unchanged) */\n mergeSibling(rhs: BNode<K, V>, _: number) {\n this.keys.push.apply(this.keys, rhs.keys)\n if (this.values === undefVals) {\n if (rhs.values === undefVals) return\n this.values = this.values.slice(0, this.keys.length)\n }\n this.values.push.apply(this.values, rhs.reifyValues())\n }\n}\n\n/** Internal node (non-leaf node) ********************************************/\nclass BNodeInternal<K, V> extends BNode<K, V> {\n // Note: conventionally B+ trees have one fewer key than the number of\n // children, but I find it easier to keep the array lengths equal: each\n // keys[i] caches the value of children[i].maxKey().\n children: Array<BNode<K, V>>\n\n /**\n * This does not mark `children` as shared, so it is the responsibility of the caller\n * to ensure children are either marked shared, or aren't included in another tree.\n */\n constructor(children: Array<BNode<K, V>>, keys?: Array<K>) {\n if (!keys) {\n keys = []\n for (let i = 0; i < children.length; i++) keys[i] = children[i]!.maxKey()!\n }\n super(keys)\n this.children = children\n }\n\n minKey() {\n return this.children[0]!.minKey()\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[0]!.minPair(reusedArray)\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[this.children.length - 1]!.maxPair(reusedArray)\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, 0, tree._compare),\n children = this.children\n return i < children.length\n ? children[i]!.get(key, defaultValue, tree)\n : undefined\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children\n if (i >= children.length) return this.maxPair(reusedArray)\n const result = children[i]!.getPairOrNextLower(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i > 0) {\n return children[i - 1]!.maxPair(reusedArray)\n }\n return result\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children,\n length = children.length\n if (i >= length) return undefined\n const result = children[i]!.getPairOrNextHigher(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i < length - 1) {\n return children[i + 1]!.minPair(reusedArray)\n }\n return result\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: set & node splitting //////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNodeInternal<K, V> {\n const c = this.children,\n max = tree._maxNodeSize,\n cmp = tree._compare\n let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1),\n child = c[i]!\n\n if (child.isShared) c[i] = child = child.clone()\n if (child.keys.length >= max) {\n // child is full; inserting anything else will cause a split.\n // Shifting an item to the left or right sibling may avoid a split.\n // We can do a shift if the adjacent node is not full and if the\n // current key can still be placed in the same node after the shift.\n let other: BNode<K, V> | undefined\n if (\n i > 0 &&\n (other = c[i - 1]!).keys.length < max &&\n cmp(child.keys[0]!, key) < 0\n ) {\n if (other.isShared) c[i - 1] = other = other.clone()\n other.takeFromRight(child)\n this.keys[i - 1] = other.maxKey()!\n } else if (\n (other = c[i + 1]) !== undefined &&\n other.keys.length < max &&\n cmp(child.maxKey()!, key) < 0\n ) {\n if (other.isShared) c[i + 1] = other = other.clone()\n other.takeFromLeft(child)\n this.keys[i] = c[i]!.maxKey()!\n }\n }\n\n const result = child.set(key, value, overwrite, tree)\n if (result === false) return false\n this.keys[i] = child.maxKey()!\n if (result === true) return true\n\n // The child has split and `result` is a new right child... does it fit?\n if (this.keys.length < max) {\n // yes\n this.insert(i + 1, result)\n return true\n } else {\n // no, we must split also\n const newRightSibling = this.splitOffRightSide()\n let target: BNodeInternal<K, V> = this\n if (cmp(result.maxKey()!, this.maxKey()!) > 0) {\n target = newRightSibling\n i -= this.keys.length\n }\n target.insert(i + 1, result)\n return newRightSibling\n }\n }\n\n /**\n * Inserts `child` at index `i`.\n * This does not mark `child` as shared, so it is the responsibility of the caller\n * to ensure that either child is marked shared, or it is not included in another tree.\n */\n insert(i: index, child: BNode<K, V>) {\n this.children.splice(i, 0, child)\n this.keys.splice(i, 0, child.maxKey()!)\n }\n\n /**\n * Split this node.\n * Modifies this to remove the second half of the items, returning a separate node containing them.\n */\n splitOffRightSide() {\n // assert !this.isShared;\n const half = this.children.length >> 1\n return new BNodeInternal<K, V>(\n this.children.splice(half),\n this.keys.splice(half)\n )\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.push(rhs.keys.shift()!)\n this.children.push((rhs as BNodeInternal<K, V>).children.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.unshift(lhs.keys.pop()!)\n this.children.unshift((lhs as BNodeInternal<K, V>).children.pop()!)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: scanning & deletions //////////////////////////////////////\n\n // Note: `count` is the next value of the third argument to `onFound`.\n // A leaf node's `forRange` function returns a new value for this counter,\n // unless the operation is to stop early.\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n const keys = this.keys,\n children = this.children\n let iLow = this.indexOf(low, 0, cmp),\n i = iLow\n const iHigh = Math.min(\n high === low ? iLow : this.indexOf(high, 0, cmp),\n keys.length - 1\n )\n if (!editMode) {\n // Simple case\n for (; i <= iHigh; i++) {\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n if (typeof result !== `number`) return result\n count = result\n }\n } else if (i <= iHigh) {\n try {\n for (; i <= iHigh; i++) {\n if (children[i]!.isShared) children[i] = children[i]!.clone()\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n // Note: if children[i] is empty then keys[i]=undefined.\n // This is an invalid state, but it is fixed below.\n keys[i] = children[i]!.maxKey()!\n if (typeof result !== `number`) return result\n count = result\n }\n } finally {\n // Deletions may have occurred, so look for opportunities to merge nodes.\n const half = tree._maxNodeSize >> 1\n if (iLow > 0) iLow--\n for (i = iHigh; i >= iLow; i--) {\n if (children[i]!.keys.length <= half) {\n if (children[i]!.keys.length !== 0) {\n this.tryMerge(i, tree._maxNodeSize)\n } else {\n // child is empty! delete it!\n keys.splice(i, 1)\n children.splice(i, 1)\n }\n }\n }\n if (children.length !== 0 && children[0]!.keys.length === 0)\n check(false, `emptiness bug`)\n }\n }\n return count\n }\n\n /** Merges child i with child i+1 if their combined size is not too large */\n tryMerge(i: index, maxSize: number): boolean {\n const children = this.children\n if (i >= 0 && i + 1 < children.length) {\n if (children[i]!.keys.length + children[i + 1]!.keys.length <= maxSize) {\n if (children[i]!.isShared)\n // cloned already UNLESS i is outside scan range\n children[i] = children[i]!.clone()\n children[i]!.mergeSibling(children[i + 1]!, maxSize)\n children.splice(i + 1, 1)\n this.keys.splice(i + 1, 1)\n this.keys[i] = children[i]!.maxKey()!\n return true\n }\n }\n return false\n }\n\n /**\n * Move children from `rhs` into this.\n * `rhs` must be part of this tree, and be removed from it after this call\n * (otherwise isShared for its children could be incorrect).\n */\n mergeSibling(rhs: BNode<K, V>, maxNodeSize: number) {\n // assert !this.isShared;\n const oldLength = this.keys.length\n this.keys.push.apply(this.keys, rhs.keys)\n const rhsChildren = (rhs as any as BNodeInternal<K, V>).children\n this.children.push.apply(this.children, rhsChildren)\n\n if (rhs.isShared && !this.isShared) {\n // All children of a shared node are implicitly shared, and since their new\n // parent is not shared, they must now be explicitly marked as shared.\n for (const child of rhsChildren) child.isShared = true\n }\n\n // If our children are themselves almost empty due to a mass-delete,\n // they may need to be merged too (but only the oldLength-1 and its\n // right sibling should need this).\n this.tryMerge(oldLength - 1, maxNodeSize)\n }\n}\n\n// Optimization: this array of `undefined`s is used instead of a normal\n// array of values in nodes where `undefined` is the only value.\n// Its length is extended to max node size on first use; since it can\n// be shared between trees with different maximums, its length can only\n// increase, never decrease. Its type should be undefined[] but strangely\n// TypeScript won't allow the comparison V[] === undefined[]. To prevent\n// users from making this array too large, BTree has a maximum node size.\n//\n// FAQ: undefVals[i] is already undefined, so why increase the array size?\n// Reading outside the bounds of an array is relatively slow because it\n// has the side effect of scanning the prototype chain.\nconst undefVals: Array<any> = []\n\nconst Delete = { delete: true },\n DeleteRange = () => Delete\nconst EmptyLeaf = (function () {\n const n = new BNode<any, any>()\n n.isShared = true\n return n\n})()\nconst ReusedArray: Array<any> = [] // assumed thread-local\n\nfunction check(fact: boolean, ...args: Array<any>) {\n if (!fact) {\n args.unshift(`B+ tree`) // at beginning of message\n throw new Error(args.join(` `))\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
|
|
3
|
+
const utils = require("../utils.cjs");
|
|
4
|
+
const reverseIndex = require("../indexes/reverse-index.cjs");
|
|
5
|
+
function findIndexForField(indexes, fieldPath, compareOptions = utils.DEFAULT_COMPARE_OPTIONS) {
|
|
4
6
|
for (const index of indexes.values()) {
|
|
5
|
-
if (index.matchesField(fieldPath)) {
|
|
7
|
+
if (index.matchesField(fieldPath) && index.matchesCompareOptions(compareOptions)) {
|
|
8
|
+
if (!index.matchesDirection(compareOptions.direction)) {
|
|
9
|
+
return new reverseIndex.ReverseIndex(index);
|
|
10
|
+
}
|
|
6
11
|
return index;
|
|
7
12
|
}
|
|
8
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-optimization.cjs","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport type { BaseIndex, IndexOperation } from \"../indexes/base-index.js\"\nimport type { BasicExpression } from \"../query/ir.js\"\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n indexes: Map<number, BaseIndex<TKey>>,\n fieldPath: Array<string>\n): BaseIndex<TKey> | undefined {\n for (const index of indexes.values()) {\n if (index.matchesField(fieldPath)) {\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, indexes)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return optimizeAndExpression(expression, indexes)\n\n case `or`:\n return optimizeOrExpression(expression, indexes)\n\n case `in`:\n return optimizeInArrayExpression(expression, indexes)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return canOptimizeAndExpression(expression, indexes)\n\n case `or`:\n return canOptimizeOrExpression(expression, indexes)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, indexes)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(indexes, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index-optimization.cjs","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from \"../utils.js\"\nimport { ReverseIndex } from \"../indexes/reverse-index.js\"\nimport type { CompareOptions } from \"../query/builder/types.js\"\nimport type {\n BaseIndex,\n IndexInterface,\n IndexOperation,\n} from \"../indexes/base-index.js\"\nimport type { BasicExpression } from \"../query/ir.js\"\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n indexes: Map<number, IndexInterface<TKey>>,\n fieldPath: Array<string>,\n compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS\n): IndexInterface<TKey> | undefined {\n for (const index of indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOptions)\n ) {\n if (!index.matchesDirection(compareOptions.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, indexes)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return optimizeAndExpression(expression, indexes)\n\n case `or`:\n return optimizeOrExpression(expression, indexes)\n\n case `in`:\n return optimizeInArrayExpression(expression, indexes)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return canOptimizeAndExpression(expression, indexes)\n\n case `or`:\n return canOptimizeOrExpression(expression, indexes)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, indexes)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(indexes, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":["DEFAULT_COMPARE_OPTIONS","ReverseIndex"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CompareOptions } from '../query/builder/types.js';
|
|
2
|
+
import { BaseIndex, IndexInterface } from '../indexes/base-index.js';
|
|
2
3
|
import { BasicExpression } from '../query/ir.js';
|
|
3
4
|
/**
|
|
4
5
|
* Result of index-based query optimization
|
|
@@ -10,7 +11,7 @@ export interface OptimizationResult<TKey> {
|
|
|
10
11
|
/**
|
|
11
12
|
* Finds an index that matches a given field path
|
|
12
13
|
*/
|
|
13
|
-
export declare function findIndexForField<TKey extends string | number>(indexes: Map<number,
|
|
14
|
+
export declare function findIndexForField<TKey extends string | number>(indexes: Map<number, IndexInterface<TKey>>, fieldPath: Array<string>, compareOptions?: CompareOptions): IndexInterface<TKey> | undefined;
|
|
14
15
|
/**
|
|
15
16
|
* Intersects multiple sets (AND logic)
|
|
16
17
|
*/
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -112,6 +112,12 @@ function isTemporal(a) {
|
|
|
112
112
|
const tag = getStringTag(a);
|
|
113
113
|
return typeof tag === `string` && temporalTypes.includes(tag);
|
|
114
114
|
}
|
|
115
|
+
const DEFAULT_COMPARE_OPTIONS = {
|
|
116
|
+
direction: `asc`,
|
|
117
|
+
nulls: `first`,
|
|
118
|
+
stringSort: `locale`
|
|
119
|
+
};
|
|
120
|
+
exports.DEFAULT_COMPARE_OPTIONS = DEFAULT_COMPARE_OPTIONS;
|
|
115
121
|
exports.deepEquals = deepEquals;
|
|
116
122
|
exports.isTemporal = isTemporal;
|
|
117
123
|
//# sourceMappingURL=utils.cjs.map
|