@tanstack/db 0.2.2 → 0.2.4

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.
@@ -345,7 +345,7 @@ type NonNull<T> = T extends null ? never : T;
345
345
  * ```
346
346
  */
347
347
  export type Ref<T = any> = {
348
- [K in keyof T]: IsNonExactOptional<T[K]> extends true ? IsNonExactNullable<T[K]> extends true ? NonNullable<T[K]> extends Record<string, any> ? Ref<NonNullable<T[K]>> | undefined : RefLeaf<NonNullable<T[K]>> | undefined : NonUndefined<T[K]> extends Record<string, any> ? Ref<NonUndefined<T[K]>> | undefined : RefLeaf<NonUndefined<T[K]>> | undefined : IsNonExactNullable<T[K]> extends true ? NonNull<T[K]> extends Record<string, any> ? Ref<NonNull<T[K]>> | null : RefLeaf<NonNull<T[K]>> | null : T[K] extends Record<string, any> ? Ref<T[K]> : RefLeaf<T[K]>;
348
+ [K in keyof T]: IsNonExactOptional<T[K]> extends true ? IsNonExactNullable<T[K]> extends true ? IsPlainObject<NonNullable<T[K]>> extends true ? Ref<NonNullable<T[K]>> | undefined : RefLeaf<NonNullable<T[K]>> | undefined : IsPlainObject<NonUndefined<T[K]>> extends true ? Ref<NonUndefined<T[K]>> | undefined : RefLeaf<NonUndefined<T[K]>> | undefined : IsNonExactNullable<T[K]> extends true ? IsPlainObject<NonNull<T[K]>> extends true ? Ref<NonNull<T[K]>> | null : RefLeaf<NonNull<T[K]>> | null : IsPlainObject<T[K]> extends true ? Ref<T[K]> : RefLeaf<T[K]>;
349
349
  } & RefLeaf<T>;
350
350
  /**
351
351
  * Ref - The user-facing ref type with clean IDE display
@@ -564,4 +564,12 @@ export type WithResult<TContext extends Context, TResult> = Prettify<Omit<TConte
564
564
  export type Prettify<T> = {
565
565
  [K in keyof T]: T[K];
566
566
  } & {};
567
+ /**
568
+ * IsPlainObject - Utility type to check if T is a plain object
569
+ */
570
+ type IsPlainObject<T> = T extends unknown ? T extends object ? T extends ReadonlyArray<any> ? false : T extends JsBuiltIns ? false : true : false : false;
571
+ /**
572
+ * JsBuiltIns - List of JavaScript built-ins
573
+ */
574
+ type JsBuiltIns = ArrayBuffer | ArrayBufferLike | AsyncGenerator<any, any, any> | BigInt64Array | BigUint64Array | DataView | Date | Error | Float32Array | Float64Array | Function | Generator<any, any, any> | Int16Array | Int32Array | Int8Array | Map<any, any> | Promise<any> | RegExp | Set<any> | string | Uint16Array | Uint32Array | Uint8Array | Uint8ClampedArray | WeakMap<any, any> | WeakSet<any>;
567
575
  export {};
@@ -105,7 +105,7 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
105
105
  }
106
106
  let deoptimized = false;
107
107
  const activePipelineWithLoading = activePipeline.pipe(
108
- dbIvm.tap(([joinKey, _]) => {
108
+ dbIvm.tap((data) => {
109
109
  if (deoptimized) {
110
110
  return;
111
111
  }
@@ -120,8 +120,9 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
120
120
  );
121
121
  }
122
122
  const { loadKeys, loadInitialState } = collectionCallbacks;
123
- if (index$1 && index$1.supports(`eq`)) {
124
- const matchingKeys = index$1.lookup(`eq`, joinKey);
123
+ if (index$1 && index$1.supports(`in`)) {
124
+ const joinKeys = data.getInner().map(([[joinKey]]) => joinKey);
125
+ const matchingKeys = index$1.lookup(`in`, joinKeys);
125
126
  loadKeys(matchingKeys);
126
127
  } else {
127
128
  deoptimized = true;
@@ -1 +1 @@
1
- {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n tap,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftTableError,\n InvalidJoinConditionRightTableError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery, followRef } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n PropRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\nimport type { BaseIndex } from \"../../indexes/base-index.js\"\n\nexport type LoadKeysFn = (key: Set<string | number>) => void\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const {\n alias: joinedTableAlias,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n const mainCollection = collections[mainTableId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainTableId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeCollection, lazyCollection } = getActiveAndLazyCollections(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which table each expression refers to and swap if necessary\n const availableTableAliases = Object.keys(tables)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableTableAliases,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeCollection) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom =\n activeCollection === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n if (!limitedSubquery) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy collection as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which collections are lazy collections\n lazyCollections.add(lazyCollection.id)\n\n const activePipeline =\n activeCollection === `main` ? mainPipeline : joinedPipeline\n\n let index: BaseIndex<string | number> | undefined\n\n const lazyCollectionJoinExpr =\n activeCollection === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazyCollectionJoinExpr,\n lazyCollection\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection\n )\n }\n\n let deoptimized = false\n\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap(([joinKey, _]) => {\n if (deoptimized) {\n return\n }\n\n // Find the index for the path we join on\n // we need to find the index inside the map operator\n // because the indexes are only available after the initial sync\n // so we can't fetch it during compilation\n index ??= findIndexForField(\n followRefCollection.indexes,\n followRefResult.path\n )\n\n // The `callbacks` object is passed by the liveQueryCollection to the compiler.\n // It contains a function to lazy load keys for each lazy collection\n // as well as a function to switch back to a regular collection\n // (useful when there's no index for available for lazily loading the collection)\n const collectionCallbacks = callbacks[lazyCollection.id]\n if (!collectionCallbacks) {\n throw new Error(\n `Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`\n )\n }\n\n const { loadKeys, loadInitialState } = collectionCallbacks\n\n if (index && index.supports(`eq`)) {\n // Use the index to fetch the PKs of the rows in the lazy collection\n // that match this row from the active collection based on the value of the joinKey\n const matchingKeys = index.lookup(`eq`, joinKey)\n // Inform the lazy collection that those keys need to be loaded\n loadKeys(matchingKeys)\n } else {\n // We can't optimize the join because there is no index for the join key\n // on the lazy collection, so we load the initial state\n deoptimized = true\n loadInitialState()\n }\n })\n )\n\n if (activeCollection === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (available table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableTableAliases: Array<string>,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined table alias from the available table aliases\n const availableTableAliases = allAvailableTableAliases.filter(\n (alias) => alias !== joinedTableAlias\n )\n\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to an available table and right refers to joined table, keep as is\n if (\n leftTableAlias &&\n availableTableAliases.includes(leftTableAlias) &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to an available table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias &&\n availableTableAliases.includes(rightTableAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n // For backward compatibility, use the first available table alias in error message\n throw new InvalidJoinConditionTableMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias)\n }\n\n // Left side must refer to an available table\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable table, but just in case, but could happen with the IR\n if (!availableTableAliases.includes(leftTableAlias)) {\n throw new InvalidJoinConditionLeftTableError(leftTableAlias)\n }\n\n // Right side must refer to the joined table\n if (rightTableAlias !== joinedTableAlias) {\n throw new InvalidJoinConditionRightTableError(joinedTableAlias)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main table (i.e. left collection) or the joined table (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazyCollections(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeCollection: `main` | `joined`; lazyCollection: Collection }\n | { activeCollection: undefined; lazyCollection: undefined } {\n if (leftCollection.id === rightCollection.id) {\n // We can't apply this optimization if there's only one collection\n // because `liveQueryCollection` will detect that the collection is lazy\n // and treat it lazily (because the collection is shared)\n // and thus it will not load any keys because both sides of the join\n // will be handled lazily\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n\n switch (joinType) {\n case `left`:\n return { activeCollection: `main`, lazyCollection: rightCollection }\n case `right`:\n return { activeCollection: `joined`, lazyCollection: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeCollection: `main`, lazyCollection: rightCollection }\n : { activeCollection: `joined`, lazyCollection: leftCollection }\n default:\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","UnsupportedJoinTypeError","index","followRef","ensureIndexForField","tap","findIndexForField","joinOperator","consolidate","InvalidJoinConditionTableMismatchError","InvalidJoinConditionSameTableError","InvalidJoinConditionLeftTableError","InvalidJoinConditionRightTableError","InvalidJoinCondition","CollectionInputNotFoundError","compileQuery","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
1
+ {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n tap,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftTableError,\n InvalidJoinConditionRightTableError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery, followRef } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n PropRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\nimport type { BaseIndex } from \"../../indexes/base-index.js\"\n\nexport type LoadKeysFn = (key: Set<string | number>) => void\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const {\n alias: joinedTableAlias,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n const mainCollection = collections[mainTableId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainTableId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeCollection, lazyCollection } = getActiveAndLazyCollections(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which table each expression refers to and swap if necessary\n const availableTableAliases = Object.keys(tables)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableTableAliases,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeCollection) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom =\n activeCollection === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n if (!limitedSubquery) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy collection as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which collections are lazy collections\n lazyCollections.add(lazyCollection.id)\n\n const activePipeline =\n activeCollection === `main` ? mainPipeline : joinedPipeline\n\n let index: BaseIndex<string | number> | undefined\n\n const lazyCollectionJoinExpr =\n activeCollection === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazyCollectionJoinExpr,\n lazyCollection\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection\n )\n }\n\n let deoptimized = false\n\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n if (deoptimized) {\n return\n }\n\n // Find the index for the path we join on\n // we need to find the index inside the map operator\n // because the indexes are only available after the initial sync\n // so we can't fetch it during compilation\n index ??= findIndexForField(\n followRefCollection.indexes,\n followRefResult.path\n )\n\n // The `callbacks` object is passed by the liveQueryCollection to the compiler.\n // It contains a function to lazy load keys for each lazy collection\n // as well as a function to switch back to a regular collection\n // (useful when there's no index for available for lazily loading the collection)\n const collectionCallbacks = callbacks[lazyCollection.id]\n if (!collectionCallbacks) {\n throw new Error(\n `Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`\n )\n }\n\n const { loadKeys, loadInitialState } = collectionCallbacks\n\n if (index && index.supports(`in`)) {\n // Use the index to fetch the PKs of the rows in the lazy collection\n // that match this row from the active collection based on the value of the joinKey\n const joinKeys = data.getInner().map(([[joinKey]]) => joinKey)\n const matchingKeys = index.lookup(`in`, joinKeys)\n // Inform the lazy collection that those keys need to be loaded\n loadKeys(matchingKeys)\n } else {\n // We can't optimize the join because there is no index for the join key\n // on the lazy collection, so we load the initial state\n deoptimized = true\n loadInitialState()\n }\n })\n )\n\n if (activeCollection === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (available table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableTableAliases: Array<string>,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined table alias from the available table aliases\n const availableTableAliases = allAvailableTableAliases.filter(\n (alias) => alias !== joinedTableAlias\n )\n\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to an available table and right refers to joined table, keep as is\n if (\n leftTableAlias &&\n availableTableAliases.includes(leftTableAlias) &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to an available table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias &&\n availableTableAliases.includes(rightTableAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n // For backward compatibility, use the first available table alias in error message\n throw new InvalidJoinConditionTableMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias)\n }\n\n // Left side must refer to an available table\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable table, but just in case, but could happen with the IR\n if (!availableTableAliases.includes(leftTableAlias)) {\n throw new InvalidJoinConditionLeftTableError(leftTableAlias)\n }\n\n // Right side must refer to the joined table\n if (rightTableAlias !== joinedTableAlias) {\n throw new InvalidJoinConditionRightTableError(joinedTableAlias)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main table (i.e. left collection) or the joined table (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazyCollections(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeCollection: `main` | `joined`; lazyCollection: Collection }\n | { activeCollection: undefined; lazyCollection: undefined } {\n if (leftCollection.id === rightCollection.id) {\n // We can't apply this optimization if there's only one collection\n // because `liveQueryCollection` will detect that the collection is lazy\n // and treat it lazily (because the collection is shared)\n // and thus it will not load any keys because both sides of the join\n // will be handled lazily\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n\n switch (joinType) {\n case `left`:\n return { activeCollection: `main`, lazyCollection: rightCollection }\n case `right`:\n return { activeCollection: `joined`, lazyCollection: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeCollection: `main`, lazyCollection: rightCollection }\n : { activeCollection: `joined`, lazyCollection: leftCollection }\n default:\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","UnsupportedJoinTypeError","index","followRef","ensureIndexForField","tap","findIndexForField","joinOperator","consolidate","InvalidJoinConditionTableMismatchError","InvalidJoinConditionSameTableError","InvalidJoinConditionLeftTableError","InvalidJoinConditionRightTableError","InvalidJoinCondition","CollectionInputNotFoundError","compileQuery","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
@@ -83,14 +83,18 @@ class CollectionSubscriber {
83
83
  );
84
84
  }
85
85
  loadKeys(keys, filterFn) {
86
+ const changes = [];
86
87
  for (const key of keys) {
87
88
  if (this.sentKeys.has(key)) continue;
88
89
  const value = this.collection.get(key);
89
90
  if (value !== void 0 && filterFn(value)) {
90
91
  this.sentKeys.add(key);
91
- this.sendChangesToPipeline([{ type: `insert`, key, value }]);
92
+ changes.push({ type: `insert`, key, value });
92
93
  }
93
94
  }
95
+ if (changes.length > 0) {
96
+ this.sendChangesToPipeline(changes);
97
+ }
94
98
  }
95
99
  subscribeToAllChanges(whereExpression) {
96
100
  const sendChangesToPipeline = this.sendChangesToPipeline.bind(this);
@@ -1 +1 @@
1
- {"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import { MultiSet } from \"@tanstack/db-ivm\"\nimport { createFilterFunctionFromExpression } from \"../../change-events.js\"\nimport { convertToBasicExpression } from \"../compiler/expressions.js\"\nimport type { FullSyncState } from \"./types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type { ChangeMessage, SyncConfig } from \"../../types.js\"\nimport type { Context, GetResult } from \"../builder/types.js\"\nimport type { BasicExpression } from \"../ir.js\"\nimport type { CollectionConfigBuilder } from \"./collection-config-builder.js\"\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n // Keep track of the biggest value we've sent so far (needed for orderBy optimization)\n private biggest: any = undefined\n\n constructor(\n private collectionId: string,\n private collection: Collection,\n private config: Parameters<SyncConfig<TResult>[`sync`]>[0],\n private syncState: FullSyncState,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>\n ) {}\n\n subscribe() {\n const collectionAlias = findCollectionAlias(\n this.collectionId,\n this.collectionConfigBuilder.query\n )\n const whereClause = this.getWhereClauseFromAlias(collectionAlias)\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n this.subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${this.collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n this.subscribeToChanges()\n }\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n let unsubscribe: () => void\n if (this.collectionConfigBuilder.lazyCollections.has(this.collectionId)) {\n unsubscribe = this.subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(\n this.collectionConfigBuilder.optimizableOrderByCollections,\n this.collectionId\n )\n ) {\n unsubscribe = this.subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = this.subscribeToAllChanges(whereExpression)\n }\n this.syncState.unsubscribeCallbacks.add(unsubscribe)\n }\n\n private sendChangesToPipeline(\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) {\n const input = this.syncState.inputs[this.collectionId]!\n const sentChanges = sendChangesToInput(\n input,\n changes,\n this.collection.config.getKey\n )\n\n // Do not provide the callback that loads more data\n // if there's no more data to load\n // otherwise we end up in an infinite loop trying to load more data\n const dataLoader = sentChanges > 0 ? callback : undefined\n\n // We need to call `maybeRunGraph` even if there's no data to load\n // because we need to mark the collection as ready if it's not already\n // and that's only done in `maybeRunGraph`\n this.collectionConfigBuilder.maybeRunGraph(\n this.config,\n this.syncState,\n dataLoader\n )\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n private sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return this.sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n\n return this.sendChangesToPipeline(newChanges)\n }\n\n private loadKeys(\n keys: Iterable<string | number>,\n filterFn: (item: object) => boolean\n ) {\n for (const key of keys) {\n // Only load the key once\n if (this.sentKeys.has(key)) continue\n\n const value = this.collection.get(key)\n if (value !== undefined && filterFn(value)) {\n this.sentKeys.add(key)\n this.sendChangesToPipeline([{ type: `insert`, key, value }])\n }\n }\n }\n\n private subscribeToAllChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n const sendChangesToPipeline = this.sendChangesToPipeline.bind(this)\n const unsubscribe = this.collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n // Flag to indicate we have send to whole initial state of the collection\n // to the pipeline, this is set when there are no indexes that can be used\n // to filter the changes and so the whole state was requested from the collection\n let loadedInitialState = false\n\n // Flag to indicate that we have started sending changes to the pipeline.\n // This is set to true by either the first call to `loadKeys` or when the\n // query requests the whole initial state in `loadInitialState`.\n // Until that point we filter out all changes from subscription to the collection.\n let sendChanges = false\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n // We filter out changes when sendChanges is false to ensure that we don't send\n // any changes from the live subscription until the join operator requests either\n // the initial state or its first key. This is needed otherwise it could receive\n // changes which are then later subsumed by the initial state (and that would\n // lead to weird bugs due to the data being received twice).\n this.sendVisibleChangesToPipeline(\n sendChanges ? changes : [],\n loadedInitialState\n )\n }\n\n const unsubscribe = this.collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n sendChanges = true\n return this.loadKeys(keys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n this.collectionConfigBuilder.lazyCollectionsCallbacks[this.collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n sendChanges = true\n\n const changes = this.collection.currentStateAsChanges({\n whereExpression,\n })\n this.sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n private subscribeToOrderedChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n const { offset, limit, comparator, dataNeeded } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n this.loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n let filteredChanges = splittedChanges\n if (dataNeeded!() === 0) {\n // If the topK is full [..., maxSentValue] then we do not need to send changes > maxSentValue\n // because they can never make it into the topK.\n // However, if the topK isn't full yet, we need to also send changes > maxSentValue\n // because they will make it into the topK\n filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n this.biggest\n )\n }\n this.sendChangesToPipeline(\n filteredChanges,\n this.loadMoreIfNeeded.bind(this)\n )\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = this.collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n loadMoreIfNeeded() {\n const orderByInfo =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load, just return true\n return true\n }\n\n const { dataNeeded } = orderByInfo\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${this.collectionId}`\n )\n }\n\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n let noMoreNextItems = false\n if (n > 0) {\n const loadedItems = this.loadNextItems(n)\n noMoreNextItems = loadedItems === 0\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n // or there's no more data to load\n return n === 0 || noMoreNextItems\n }\n\n private sendChangesToPipelineWithTracking(\n changes: Iterable<ChangeMessage<any, string | number>>\n ) {\n const { comparator } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const trackedChanges = this.trackSentValues(changes, comparator)\n this.sendChangesToPipeline(trackedChanges, this.loadMoreIfNeeded.bind(this))\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n private loadNextItems(n: number) {\n const { valueExtractorForRawRow, index } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const biggestSentRow = this.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: this.collection.get(key) }\n })\n this.sendChangesToPipelineWithTracking(nextInserts)\n return nextInserts.length\n }\n\n private getWhereClauseFromAlias(\n collectionAlias: string | undefined\n ): BasicExpression<boolean> | undefined {\n const collectionWhereClausesCache =\n this.collectionConfigBuilder.collectionWhereClausesCache\n if (collectionAlias && collectionWhereClausesCache) {\n return collectionWhereClausesCache.get(collectionAlias)\n }\n return undefined\n }\n\n private *trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number\n ) {\n for (const change of changes) {\n this.sentKeys.add(change.key)\n\n if (!this.biggest) {\n this.biggest = change.value\n } else if (comparator(this.biggest, change.value) < 0) {\n this.biggest = change.value\n }\n\n yield change\n }\n }\n}\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["convertToBasicExpression","createFilterFunctionFromExpression","MultiSet"],"mappings":"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"}
1
+ {"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import { MultiSet } from \"@tanstack/db-ivm\"\nimport { createFilterFunctionFromExpression } from \"../../change-events.js\"\nimport { convertToBasicExpression } from \"../compiler/expressions.js\"\nimport type { FullSyncState } from \"./types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type { ChangeMessage, SyncConfig } from \"../../types.js\"\nimport type { Context, GetResult } from \"../builder/types.js\"\nimport type { BasicExpression } from \"../ir.js\"\nimport type { CollectionConfigBuilder } from \"./collection-config-builder.js\"\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n // Keep track of the biggest value we've sent so far (needed for orderBy optimization)\n private biggest: any = undefined\n\n constructor(\n private collectionId: string,\n private collection: Collection,\n private config: Parameters<SyncConfig<TResult>[`sync`]>[0],\n private syncState: FullSyncState,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>\n ) {}\n\n subscribe() {\n const collectionAlias = findCollectionAlias(\n this.collectionId,\n this.collectionConfigBuilder.query\n )\n const whereClause = this.getWhereClauseFromAlias(collectionAlias)\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n this.subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${this.collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n this.subscribeToChanges()\n }\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n let unsubscribe: () => void\n if (this.collectionConfigBuilder.lazyCollections.has(this.collectionId)) {\n unsubscribe = this.subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(\n this.collectionConfigBuilder.optimizableOrderByCollections,\n this.collectionId\n )\n ) {\n unsubscribe = this.subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = this.subscribeToAllChanges(whereExpression)\n }\n this.syncState.unsubscribeCallbacks.add(unsubscribe)\n }\n\n private sendChangesToPipeline(\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) {\n const input = this.syncState.inputs[this.collectionId]!\n const sentChanges = sendChangesToInput(\n input,\n changes,\n this.collection.config.getKey\n )\n\n // Do not provide the callback that loads more data\n // if there's no more data to load\n // otherwise we end up in an infinite loop trying to load more data\n const dataLoader = sentChanges > 0 ? callback : undefined\n\n // We need to call `maybeRunGraph` even if there's no data to load\n // because we need to mark the collection as ready if it's not already\n // and that's only done in `maybeRunGraph`\n this.collectionConfigBuilder.maybeRunGraph(\n this.config,\n this.syncState,\n dataLoader\n )\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n private sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return this.sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n\n return this.sendChangesToPipeline(newChanges)\n }\n\n private loadKeys(\n keys: Iterable<string | number>,\n filterFn: (item: object) => boolean\n ) {\n const changes: Array<ChangeMessage<any, string | number>> = []\n for (const key of keys) {\n // Only load the key once\n if (this.sentKeys.has(key)) continue\n\n const value = this.collection.get(key)\n if (value !== undefined && filterFn(value)) {\n this.sentKeys.add(key)\n changes.push({ type: `insert`, key, value })\n }\n }\n if (changes.length > 0) {\n this.sendChangesToPipeline(changes)\n }\n }\n\n private subscribeToAllChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n const sendChangesToPipeline = this.sendChangesToPipeline.bind(this)\n const unsubscribe = this.collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n // Flag to indicate we have send to whole initial state of the collection\n // to the pipeline, this is set when there are no indexes that can be used\n // to filter the changes and so the whole state was requested from the collection\n let loadedInitialState = false\n\n // Flag to indicate that we have started sending changes to the pipeline.\n // This is set to true by either the first call to `loadKeys` or when the\n // query requests the whole initial state in `loadInitialState`.\n // Until that point we filter out all changes from subscription to the collection.\n let sendChanges = false\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n // We filter out changes when sendChanges is false to ensure that we don't send\n // any changes from the live subscription until the join operator requests either\n // the initial state or its first key. This is needed otherwise it could receive\n // changes which are then later subsumed by the initial state (and that would\n // lead to weird bugs due to the data being received twice).\n this.sendVisibleChangesToPipeline(\n sendChanges ? changes : [],\n loadedInitialState\n )\n }\n\n const unsubscribe = this.collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n sendChanges = true\n return this.loadKeys(keys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n this.collectionConfigBuilder.lazyCollectionsCallbacks[this.collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n sendChanges = true\n\n const changes = this.collection.currentStateAsChanges({\n whereExpression,\n })\n this.sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n private subscribeToOrderedChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n const { offset, limit, comparator, dataNeeded } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n this.loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n let filteredChanges = splittedChanges\n if (dataNeeded!() === 0) {\n // If the topK is full [..., maxSentValue] then we do not need to send changes > maxSentValue\n // because they can never make it into the topK.\n // However, if the topK isn't full yet, we need to also send changes > maxSentValue\n // because they will make it into the topK\n filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n this.biggest\n )\n }\n this.sendChangesToPipeline(\n filteredChanges,\n this.loadMoreIfNeeded.bind(this)\n )\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = this.collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n loadMoreIfNeeded() {\n const orderByInfo =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load, just return true\n return true\n }\n\n const { dataNeeded } = orderByInfo\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${this.collectionId}`\n )\n }\n\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n let noMoreNextItems = false\n if (n > 0) {\n const loadedItems = this.loadNextItems(n)\n noMoreNextItems = loadedItems === 0\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n // or there's no more data to load\n return n === 0 || noMoreNextItems\n }\n\n private sendChangesToPipelineWithTracking(\n changes: Iterable<ChangeMessage<any, string | number>>\n ) {\n const { comparator } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const trackedChanges = this.trackSentValues(changes, comparator)\n this.sendChangesToPipeline(trackedChanges, this.loadMoreIfNeeded.bind(this))\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n private loadNextItems(n: number) {\n const { valueExtractorForRawRow, index } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const biggestSentRow = this.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: this.collection.get(key) }\n })\n this.sendChangesToPipelineWithTracking(nextInserts)\n return nextInserts.length\n }\n\n private getWhereClauseFromAlias(\n collectionAlias: string | undefined\n ): BasicExpression<boolean> | undefined {\n const collectionWhereClausesCache =\n this.collectionConfigBuilder.collectionWhereClausesCache\n if (collectionAlias && collectionWhereClausesCache) {\n return collectionWhereClausesCache.get(collectionAlias)\n }\n return undefined\n }\n\n private *trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number\n ) {\n for (const change of changes) {\n this.sentKeys.add(change.key)\n\n if (!this.biggest) {\n this.biggest = change.value\n } else if (comparator(this.biggest, change.value) < 0) {\n this.biggest = change.value\n }\n\n yield change\n }\n }\n}\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["convertToBasicExpression","createFilterFunctionFromExpression","MultiSet"],"mappings":"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"}
@@ -676,11 +676,11 @@ class BNodeInternal extends BNode {
676
676
  }
677
677
  const undefVals = [];
678
678
  const Delete = { delete: true }, DeleteRange = () => Delete;
679
- const EmptyLeaf = function() {
679
+ const EmptyLeaf = (function() {
680
680
  const n = new BNode();
681
681
  n.isShared = true;
682
682
  return n;
683
- }();
683
+ })();
684
684
  const ReusedArray = [];
685
685
  function check(fact, ...args) {
686
686
  {
@@ -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":";;AAiGO,MAAM,MAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmB5B,YACL,SACA,SACA,aACA;AAtBF,SAAQ,QAAqB;AAC7B,SAAA,QAAQ;AAsBN,SAAK,eAAe,eAAgB,IAAI,KAAK,IAAI,aAAc,GAAG,IAAI;AACtE,SAAK,WAAW;AAChB,QAAI,QAAS,MAAK,SAAS,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,OAAO;AACT,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAEA,IAAI,UAAU;AACZ,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,QAAQ;AACb,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,KAAQ,cAAiC;AAC3C,WAAO,KAAK,MAAM,IAAI,KAAK,cAAc,IAAI;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,IAAI,KAAQ,OAAU,WAA8B;AAClD,QAAI,KAAK,MAAM,eAAe,QAAQ,KAAK,MAAM,MAAA;AACjD,UAAM,SAAS,KAAK,MAAM,IAAI,KAAK,OAAO,WAAW,IAAI;AACzD,QAAI,WAAW,QAAQ,WAAW,MAAO,QAAO;AAEhD,SAAK,QAAQ,IAAI,cAAoB,CAAC,KAAK,OAAO,MAAM,CAAC;AACzD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,KAAiB;AACnB,WAAO,KAAK,SAAS,KAAK,KAAK,MAAM,MAAS,MAAM;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,KAAiB;AACtB,WAAO,KAAK,UAAU,KAAK,KAAK,MAAM,WAAW,MAAM;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,cAAc;AAChB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,SAAwB;AACtB,WAAO,KAAK,MAAM,OAAA;AAAA,EACpB;AAAA;AAAA,EAGA,SAAwB;AACtB,WAAO,KAAK,MAAM,OAAA;AAAA,EACpB;AAAA;AAAA,EAGA,YAAY;AACV,UAAM,UAAoB,CAAA;AAC1B,SAAK,MAAM;AAAA,MACT,KAAK,OAAA;AAAA,MACL,KAAK,OAAA;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAC,GAAG,OAAO;AACT,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IAAA;AAEF,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,KAAoB,aAA0C;AAC3E,kBAAc,eAAgB,CAAA;AAC9B,QAAI,QAAQ,QAAW;AACrB,aAAO,KAAK,MAAM,QAAQ,WAAW;AAAA,IACvC;AACA,WAAO,KAAK,MAAM;AAAA,MAChB;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,KAAmC;AAC/C,UAAM,IAAI,KAAK,eAAe,KAAK,WAAqB;AACxD,WAAO,KAAK,EAAE,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,KAAoB,aAA0C;AAC1E,kBAAc,eAAgB,CAAA;AAC9B,QAAI,QAAQ,QAAW;AACrB,aAAO,KAAK,MAAM,QAAQ,WAAW;AAAA,IACvC;AACA,WAAO,KAAK,MAAM,mBAAmB,KAAK,KAAK,UAAU,OAAO,WAAW;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,KAAmC;AAC9C,UAAM,IAAI,KAAK,cAAc,KAAK,WAAqB;AACvD,WAAO,KAAK,EAAE,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,OAAsB,WAA6B;AAC1D,QAAI,QAAQ;AACZ,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,SAAS,EAAG;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,SACE,KACA,MACA,aACA,SACA,gBACY;AACZ,UAAM,IAAI,KAAK,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,MAClB;AAAA,IAAA;AAEF,WAAO,OAAO,MAAM,WAAW,IAAI,EAAE;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,UACE,KACA,MACA,aACA,SACA,gBACY;AACZ,QAAI,OAAO,KAAK;AAChB,QAAI,KAAK,SAAU,MAAK,QAAQ,OAAO,KAAK,MAAA;AAC5C,QAAI;AACF,YAAM,IAAI,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB;AAAA,QAClB;AAAA,MAAA;AAEF,aAAO,OAAO,MAAM,WAAW,IAAI,EAAE;AAAA,IACvC,UAAA;AACE,UAAI;AACJ,aAAO,KAAK,KAAK,UAAU,KAAK,CAAC,KAAK,QAAQ;AAC5C,gCAAa,KAAK;AAClB,aAAK,QAAQ,OACX,KAAK,KAAK,WAAW,IACjB,YACC,KAAoC,SAAS,CAAC;AAAA,MACvD;AAEA,UAAI,UAAU;AACZ,aAAK,WAAW;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;AAGA,MAAM,MAAY;AAAA,EAShB,IAAI,SAAS;AACX,WAAQ,KAAa,aAAa;AAAA,EACpC;AAAA,EAEA,YAAY,OAAiB,CAAA,GAAI,QAAmB;AAClD,SAAK,OAAO;AACZ,SAAK,SAAS,UAAU;AACxB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA,EAKA,SAAS;AACP,WAAO,KAAK,KAAK,KAAK,KAAK,SAAS,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA,EAIA,QAAQ,KAAQ,SAAiB,KAAoC;AACnE,UAAM,OAAO,KAAK;AAClB,QAAI,KAAK,GACP,KAAK,KAAK,QACV,MAAM,MAAM;AACd,WAAO,KAAK,IAAI;AACd,YAAM,IAAI,IAAI,KAAK,GAAG,GAAI,GAAG;AAC7B,UAAI,IAAI,EAAG,MAAK,MAAM;AAAA,eACb,IAAI;AAEX,aAAK;AAAA,eACE,MAAM,EAAG,QAAO;AAAA,WACpB;AAEH,YAAI,QAAQ;AAEV,iBAAO,KAAK;AAAA,YACT,OAAM,IAAI,MAAM,8BAA8B;AAAA,MACrD;AACA,YAAO,KAAK,MAAO;AAAA,IACrB;AACA,WAAO,MAAM;AAAA,EACf;AAAA;AAAA;AAAA,EAKA,SAAwB;AACtB,WAAO,KAAK,KAAK,CAAC;AAAA,EACpB;AAAA,EAEA,QAAQ,aAAyC;AAC/C,QAAI,KAAK,KAAK,WAAW,EAAG,QAAO;AACnC,gBAAY,CAAC,IAAI,KAAK,KAAK,CAAC;AAC5B,gBAAY,CAAC,IAAI,KAAK,OAAO,CAAC;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,aAAyC;AAC/C,QAAI,KAAK,KAAK,WAAW,EAAG,QAAO;AACnC,UAAM,YAAY,KAAK,KAAK,SAAS;AACrC,gBAAY,CAAC,IAAI,KAAK,KAAK,SAAS;AACpC,gBAAY,CAAC,IAAI,KAAK,OAAO,SAAS;AACtC,WAAO;AAAA,EACT;AAAA,EAEA,QAAqB;AACnB,UAAM,IAAI,KAAK;AACf,WAAO,IAAI,MAAY,KAAK,KAAK,MAAM,CAAC,GAAG,MAAM,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC;AAAA,EAC7E;AAAA,EAEA,IAAI,KAAQ,cAA6B,MAAkC;AACzE,UAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ;AAC7C,WAAO,IAAI,IAAI,eAAe,KAAK,OAAO,CAAC;AAAA,EAC7C;AAAA,EAEA,mBACE,KACA,SACA,WACA,aACoB;AACpB,UAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,OAAO;AACvC,UAAM,eAAe,IAAI,IAAI,CAAC,IAAI,IAAI,YAAY,IAAI,IAAI;AAC1D,QAAI,gBAAgB,GAAG;AACrB,kBAAY,CAAC,IAAI,KAAK,KAAK,YAAY;AACvC,kBAAY,CAAC,IAAI,KAAK,OAAO,YAAY;AACzC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,oBACE,KACA,SACA,WACA,aACoB;AACpB,UAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,OAAO;AACvC,UAAM,eAAe,IAAI,IAAI,CAAC,IAAI,YAAY,IAAI,IAAI;AACtD,UAAM,OAAO,KAAK;AAClB,QAAI,eAAe,KAAK,QAAQ;AAC9B,kBAAY,CAAC,IAAI,KAAK,YAAY;AAClC,kBAAY,CAAC,IAAI,KAAK,OAAO,YAAY;AACzC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,IACE,KACA,OACA,WACA,MACuB;AACvB,QAAI,IAAI,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ;AAC3C,QAAI,IAAI,GAAG;AAET,UAAI,CAAC;AACL,WAAK;AAEL,UAAI,KAAK,KAAK,SAAS,KAAK,cAAc;AACxC,eAAO,KAAK,aAAa,GAAG,KAAK,OAAO,IAAI;AAAA,MAC9C,OAAO;AAEL,cAAM,kBAAkB,KAAK,kBAAA;AAC7B,YAAI,SAAsB;AAC1B,YAAI,IAAI,KAAK,KAAK,QAAQ;AACxB,eAAK,KAAK,KAAK;AACf,mBAAS;AAAA,QACX;AACA,eAAO,aAAa,GAAG,KAAK,OAAO,IAAI;AACvC,eAAO;AAAA,MACT;AAAA,IACF,OAAO;AAEL,UAAI,cAAc,OAAO;AACvB,YAAI,UAAU,OAAW,MAAK,YAAA;AAE9B,aAAK,KAAK,CAAC,IAAI;AACf,aAAK,OAAO,CAAC,IAAI;AAAA,MACnB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,WAAW;AAClB,aAAQ,KAAK,SAAS,KAAK,OAAO,MAAM,GAAG,KAAK,KAAK,MAAM;AAC7D,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aAAa,GAAU,KAAQ,OAAU,MAAmB;AAC1D,SAAK,KAAK,OAAO,GAAG,GAAG,GAAG;AAC1B,QAAI,KAAK,WAAW,WAAW;AAC7B,aAAO,UAAU,SAAS,KAAK,aAAc,WAAU,KAAK,MAAS;AACrE,UAAI,UAAU,QAAW;AACvB,eAAO;AAAA,MACT,OAAO;AACL,aAAK,SAAS,UAAU,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC;AAAA,MACvD;AAAA,IACF;AACA,SAAK,OAAO,OAAO,GAAG,GAAG,KAAK;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,cAAc,KAAkB;AAI9B,QAAI,IAAI,KAAK;AACb,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,MAAM,UAAW,GAAE,KAAK,MAAgB;AAAA,IAC9C,OAAO;AACL,UAAI,KAAK,YAAA;AACT,QAAE,KAAK,IAAI,OAAO,MAAA,CAAQ;AAAA,IAC5B;AACA,SAAK,KAAK,KAAK,IAAI,KAAK,OAAQ;AAAA,EAClC;AAAA,EAEA,aAAa,KAAkB;AAI7B,QAAI,IAAI,KAAK;AACb,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,MAAM,UAAW,GAAE,QAAQ,MAAgB;AAAA,IACjD,OAAO;AACL,UAAI,KAAK,YAAA;AACT,QAAE,QAAQ,IAAI,OAAO,IAAA,CAAM;AAAA,IAC7B;AACA,SAAK,KAAK,QAAQ,IAAI,KAAK,KAAM;AAAA,EACnC;AAAA,EAEA,oBAAiC;AAE/B,UAAM,OAAO,KAAK,KAAK,UAAU,GAC/B,OAAO,KAAK,KAAK,OAAO,IAAI;AAC9B,UAAM,SACJ,KAAK,WAAW,YAAY,YAAY,KAAK,OAAO,OAAO,IAAI;AACjE,WAAO,IAAI,MAAY,MAAM,MAAM;AAAA,EACrC;AAAA;AAAA;AAAA,EAKA,SACE,KACA,MACA,aACA,UACA,MACA,OACA,SACgC;AAChC,UAAM,MAAM,KAAK;AACjB,QAAI,MAAM;AACV,QAAI,SAAS,KAAK;AAChB,UAAI,CAAC,YAAa,QAAO;AACzB,eAAS,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK;AAC9C,UAAI,OAAO,EAAG,QAAO;AAAA,IACvB,OAAO;AACL,aAAO,KAAK,QAAQ,KAAK,GAAG,GAAG;AAC/B,cAAQ,KAAK,QAAQ,MAAM,IAAI,GAAG;AAClC,UAAI,QAAQ,EAAG,SAAQ,CAAC;AAAA,eACf,gBAAgB,KAAM;AAAA,IACjC;AACA,UAAM,OAAO,KAAK,MAChB,SAAS,KAAK;AAChB,QAAI,YAAY,QAAW;AACzB,eAAS,IAAI,MAAM,IAAI,OAAO,KAAK;AACjC,cAAM,MAAM,KAAK,CAAC;AAClB,cAAM,SAAS,QAAQ,KAAK,OAAO,CAAC,GAAI,OAAO;AAC/C,YAAI,WAAW,QAAW;AACxB,cAAI,aAAa,MAAM;AACrB,gBAAI,QAAQ,KAAK,CAAC,KAAK,KAAK,aAAa;AACvC,oBAAM,IAAI,MAAM,gDAAgD;AAClE,gBAAI,OAAO,QAAQ;AACjB,mBAAK,KAAK,OAAO,GAAG,CAAC;AACrB,kBAAI,KAAK,WAAW,gBAAgB,OAAO,OAAO,GAAG,CAAC;AACtD,mBAAK;AACL;AACA;AAAA,YACF,WAAW,OAAO,eAAe,OAAO,GAAG;AACzC,qBAAO,CAAC,IAAI,OAAO;AAAA,YACrB;AAAA,UACF;AACA,cAAI,OAAO,UAAU,OAAW,QAAO;AAAA,QACzC;AAAA,MACF;AAAA,IACF,gBAAgB,QAAQ;AACxB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,aAAa,KAAkB,GAAW;AACxC,SAAK,KAAK,KAAK,MAAM,KAAK,MAAM,IAAI,IAAI;AACxC,QAAI,KAAK,WAAW,WAAW;AAC7B,UAAI,IAAI,WAAW,UAAW;AAC9B,WAAK,SAAS,KAAK,OAAO,MAAM,GAAG,KAAK,KAAK,MAAM;AAAA,IACrD;AACA,SAAK,OAAO,KAAK,MAAM,KAAK,QAAQ,IAAI,aAAa;AAAA,EACvD;AACF;AAGA,MAAM,sBAA4B,MAAY;AAAA;AAAA;AAAA;AAAA;AAAA,EAU5C,YAAY,UAA8B,MAAiB;AACzD,QAAI,CAAC,MAAM;AACT,aAAO,CAAA;AACP,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,MAAK,CAAC,IAAI,SAAS,CAAC,EAAG,OAAA;AAAA,IACnE;AACA,UAAM,IAAI;AACV,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,SAAS;AACP,WAAO,KAAK,SAAS,CAAC,EAAG,OAAA;AAAA,EAC3B;AAAA,EAEA,QAAQ,aAAyC;AAC/C,WAAO,KAAK,SAAS,CAAC,EAAG,QAAQ,WAAW;AAAA,EAC9C;AAAA,EAEA,QAAQ,aAAyC;AAC/C,WAAO,KAAK,SAAS,KAAK,SAAS,SAAS,CAAC,EAAG,QAAQ,WAAW;AAAA,EACrE;AAAA,EAEA,IAAI,KAAQ,cAA6B,MAAkC;AACzE,UAAM,IAAI,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,GAC1C,WAAW,KAAK;AAClB,WAAO,IAAI,SAAS,SAChB,SAAS,CAAC,EAAG,IAAI,KAAK,cAAc,IAAI,IACxC;AAAA,EACN;AAAA,EAEA,mBACE,KACA,SACA,WACA,aACoB;AACpB,UAAM,IAAI,KAAK,QAAQ,KAAK,GAAG,OAAO,GACpC,WAAW,KAAK;AAClB,QAAI,KAAK,SAAS,OAAQ,QAAO,KAAK,QAAQ,WAAW;AACzD,UAAM,SAAS,SAAS,CAAC,EAAG;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,QAAI,WAAW,UAAa,IAAI,GAAG;AACjC,aAAO,SAAS,IAAI,CAAC,EAAG,QAAQ,WAAW;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,oBACE,KACA,SACA,WACA,aACoB;AACpB,UAAM,IAAI,KAAK,QAAQ,KAAK,GAAG,OAAO,GACpC,WAAW,KAAK,UAChB,SAAS,SAAS;AACpB,QAAI,KAAK,OAAQ,QAAO;AACxB,UAAM,SAAS,SAAS,CAAC,EAAG;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,QAAI,WAAW,UAAa,IAAI,SAAS,GAAG;AAC1C,aAAO,SAAS,IAAI,CAAC,EAAG,QAAQ,WAAW;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,IACE,KACA,OACA,WACA,MAC+B;AAC/B,UAAM,IAAI,KAAK,UACb,MAAM,KAAK,cACX,MAAM,KAAK;AACb,QAAI,IAAI,KAAK,IAAI,KAAK,QAAQ,KAAK,GAAG,GAAG,GAAG,EAAE,SAAS,CAAC,GACtD,QAAQ,EAAE,CAAC;AAEb,QAAI,MAAM,SAAU,GAAE,CAAC,IAAI,QAAQ,MAAM,MAAA;AACzC,QAAI,MAAM,KAAK,UAAU,KAAK;AAK5B,UAAI;AACJ,UACE,IAAI,MACH,QAAQ,EAAE,IAAI,CAAC,GAAI,KAAK,SAAS,OAClC,IAAI,MAAM,KAAK,CAAC,GAAI,GAAG,IAAI,GAC3B;AACA,YAAI,MAAM,SAAU,GAAE,IAAI,CAAC,IAAI,QAAQ,MAAM,MAAA;AAC7C,cAAM,cAAc,KAAK;AACzB,aAAK,KAAK,IAAI,CAAC,IAAI,MAAM,OAAA;AAAA,MAC3B,YACG,QAAQ,EAAE,IAAI,CAAC,OAAO,UACvB,MAAM,KAAK,SAAS,OACpB,IAAI,MAAM,UAAW,GAAG,IAAI,GAC5B;AACA,YAAI,MAAM,SAAU,GAAE,IAAI,CAAC,IAAI,QAAQ,MAAM,MAAA;AAC7C,cAAM,aAAa,KAAK;AACxB,aAAK,KAAK,CAAC,IAAI,EAAE,CAAC,EAAG,OAAA;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,IAAI,KAAK,OAAO,WAAW,IAAI;AACpD,QAAI,WAAW,MAAO,QAAO;AAC7B,SAAK,KAAK,CAAC,IAAI,MAAM,OAAA;AACrB,QAAI,WAAW,KAAM,QAAO;AAG5B,QAAI,KAAK,KAAK,SAAS,KAAK;AAE1B,WAAK,OAAO,IAAI,GAAG,MAAM;AACzB,aAAO;AAAA,IACT,OAAO;AAEL,YAAM,kBAAkB,KAAK,kBAAA;AAC7B,UAAI,SAA8B;AAClC,UAAI,IAAI,OAAO,OAAA,GAAW,KAAK,OAAA,CAAS,IAAI,GAAG;AAC7C,iBAAS;AACT,aAAK,KAAK,KAAK;AAAA,MACjB;AACA,aAAO,OAAO,IAAI,GAAG,MAAM;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,GAAU,OAAoB;AACnC,SAAK,SAAS,OAAO,GAAG,GAAG,KAAK;AAChC,SAAK,KAAK,OAAO,GAAG,GAAG,MAAM,QAAS;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAAoB;AAElB,UAAM,OAAO,KAAK,SAAS,UAAU;AACrC,WAAO,IAAI;AAAA,MACT,KAAK,SAAS,OAAO,IAAI;AAAA,MACzB,KAAK,KAAK,OAAO,IAAI;AAAA,IAAA;AAAA,EAEzB;AAAA,EAEA,cAAc,KAAkB;AAI9B,SAAK,KAAK,KAAK,IAAI,KAAK,OAAQ;AAChC,SAAK,SAAS,KAAM,IAA4B,SAAS,OAAQ;AAAA,EACnE;AAAA,EAEA,aAAa,KAAkB;AAI7B,SAAK,KAAK,QAAQ,IAAI,KAAK,KAAM;AACjC,SAAK,SAAS,QAAS,IAA4B,SAAS,KAAM;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SACE,KACA,MACA,aACA,UACA,MACA,OACA,SACgC;AAChC,UAAM,MAAM,KAAK;AACjB,UAAM,OAAO,KAAK,MAChB,WAAW,KAAK;AAClB,QAAI,OAAO,KAAK,QAAQ,KAAK,GAAG,GAAG,GACjC,IAAI;AACN,UAAM,QAAQ,KAAK;AAAA,MACjB,SAAS,MAAM,OAAO,KAAK,QAAQ,MAAM,GAAG,GAAG;AAAA,MAC/C,KAAK,SAAS;AAAA,IAAA;AAEhB,QAAI,CAAC,UAAU;AAEb,aAAO,KAAK,OAAO,KAAK;AACtB,cAAM,SAAS,SAAS,CAAC,EAAG;AAAA,UAC1B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAEF,YAAI,OAAO,WAAW,SAAU,QAAO;AACvC,gBAAQ;AAAA,MACV;AAAA,IACF,WAAW,KAAK,OAAO;AACrB,UAAI;AACF,eAAO,KAAK,OAAO,KAAK;AACtB,cAAI,SAAS,CAAC,EAAG,SAAU,UAAS,CAAC,IAAI,SAAS,CAAC,EAAG,MAAA;AACtD,gBAAM,SAAS,SAAS,CAAC,EAAG;AAAA,YAC1B;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAIF,eAAK,CAAC,IAAI,SAAS,CAAC,EAAG,OAAA;AACvB,cAAI,OAAO,WAAW,SAAU,QAAO;AACvC,kBAAQ;AAAA,QACV;AAAA,MACF,UAAA;AAEE,cAAM,OAAO,KAAK,gBAAgB;AAClC,YAAI,OAAO,EAAG;AACd,aAAK,IAAI,OAAO,KAAK,MAAM,KAAK;AAC9B,cAAI,SAAS,CAAC,EAAG,KAAK,UAAU,MAAM;AACpC,gBAAI,SAAS,CAAC,EAAG,KAAK,WAAW,GAAG;AAClC,mBAAK,SAAS,GAAG,KAAK,YAAY;AAAA,YACpC,OAAO;AAEL,mBAAK,OAAO,GAAG,CAAC;AAChB,uBAAS,OAAO,GAAG,CAAC;AAAA,YACtB;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,WAAW,KAAK,SAAS,CAAC,EAAG,KAAK,WAAW;AACxD,gBAAM,OAAO,eAAe;AAAA,MAChC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,SAAS,GAAU,SAA0B;AAC3C,UAAM,WAAW,KAAK;AACtB,QAAI,KAAK,KAAK,IAAI,IAAI,SAAS,QAAQ;AACrC,UAAI,SAAS,CAAC,EAAG,KAAK,SAAS,SAAS,IAAI,CAAC,EAAG,KAAK,UAAU,SAAS;AACtE,YAAI,SAAS,CAAC,EAAG;AAEf,mBAAS,CAAC,IAAI,SAAS,CAAC,EAAG,MAAA;AAC7B,iBAAS,CAAC,EAAG,aAAa,SAAS,IAAI,CAAC,GAAI,OAAO;AACnD,iBAAS,OAAO,IAAI,GAAG,CAAC;AACxB,aAAK,KAAK,OAAO,IAAI,GAAG,CAAC;AACzB,aAAK,KAAK,CAAC,IAAI,SAAS,CAAC,EAAG,OAAA;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,KAAkB,aAAqB;AAElD,UAAM,YAAY,KAAK,KAAK;AAC5B,SAAK,KAAK,KAAK,MAAM,KAAK,MAAM,IAAI,IAAI;AACxC,UAAM,cAAe,IAAmC;AACxD,SAAK,SAAS,KAAK,MAAM,KAAK,UAAU,WAAW;AAEnD,QAAI,IAAI,YAAY,CAAC,KAAK,UAAU;AAGlC,iBAAW,SAAS,YAAa,OAAM,WAAW;AAAA,IACpD;AAKA,SAAK,SAAS,YAAY,GAAG,WAAW;AAAA,EAC1C;AACF;AAaA,MAAM,YAAwB,CAAA;AAE9B,MAAM,SAAS,EAAE,QAAQ,KAAA,GACvB,cAAc,MAAM;AACtB,MAAM,aAAa,WAAY;AAC7B,QAAM,IAAI,IAAI,MAAA;AACd,IAAE,WAAW;AACb,SAAO;AACT,GAAA;AACA,MAAM,cAA0B,CAAA;AAEhC,SAAS,MAAM,SAAkB,MAAkB;AACtC;AACT,SAAK,QAAQ,SAAS;AACtB,UAAM,IAAI,MAAM,KAAK,KAAK,GAAG,CAAC;AAAA,EAChC;AACF;;"}