@tanstack/db-ivm 0.1.7 → 0.1.9

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.
Files changed (40) hide show
  1. package/dist/cjs/indexes.cjs +243 -85
  2. package/dist/cjs/indexes.cjs.map +1 -1
  3. package/dist/cjs/indexes.d.cts +58 -7
  4. package/dist/cjs/operators/distinct.cjs +2 -2
  5. package/dist/cjs/operators/distinct.cjs.map +1 -1
  6. package/dist/cjs/operators/distinct.d.cts +6 -4
  7. package/dist/cjs/operators/groupBy.cjs +12 -10
  8. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  9. package/dist/cjs/operators/groupBy.d.cts +7 -4
  10. package/dist/cjs/utils.cjs +0 -12
  11. package/dist/cjs/utils.cjs.map +1 -1
  12. package/dist/esm/indexes.d.ts +58 -7
  13. package/dist/esm/indexes.js +243 -85
  14. package/dist/esm/indexes.js.map +1 -1
  15. package/dist/esm/operators/distinct.d.ts +6 -4
  16. package/dist/esm/operators/distinct.js +2 -2
  17. package/dist/esm/operators/distinct.js.map +1 -1
  18. package/dist/esm/operators/groupBy.d.ts +7 -4
  19. package/dist/esm/operators/groupBy.js +12 -10
  20. package/dist/esm/operators/groupBy.js.map +1 -1
  21. package/dist/esm/utils.js +1 -13
  22. package/dist/esm/utils.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/indexes.ts +365 -90
  25. package/src/operators/distinct.ts +13 -8
  26. package/src/operators/groupBy.ts +38 -18
  27. package/dist/cjs/hashIndex.cjs +0 -83
  28. package/dist/cjs/hashIndex.cjs.map +0 -1
  29. package/dist/cjs/hashIndex.d.cts +0 -25
  30. package/dist/cjs/valueIndex.cjs +0 -74
  31. package/dist/cjs/valueIndex.cjs.map +0 -1
  32. package/dist/cjs/valueIndex.d.cts +0 -17
  33. package/dist/esm/hashIndex.d.ts +0 -25
  34. package/dist/esm/hashIndex.js +0 -83
  35. package/dist/esm/hashIndex.js.map +0 -1
  36. package/dist/esm/valueIndex.d.ts +0 -17
  37. package/dist/esm/valueIndex.js +0 -74
  38. package/dist/esm/valueIndex.js.map +0 -1
  39. package/src/hashIndex.ts +0 -94
  40. package/src/valueIndex.ts +0 -78
@@ -1 +1 @@
1
- {"version":3,"file":"indexes.js","sources":["../../src/indexes.ts"],"sourcesContent":["import { MultiSet } from \"./multiset.js\"\nimport { HashIndex } from \"./hashIndex.js\"\nimport { ValueIndex } from \"./valueIndex.js\"\nimport { concatIterable, mapIterable } from \"./utils.js\"\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class Index<K, V> {\n /*\n * This is a hybrid Index that composes a ValueIndex and a HashIndex.\n * Keys that have only one value are stored in the ValueIndex.\n * Keys that have multiple values are stored in the HashIndex, the hash distinguishes between the values.\n * This reduces the amount of hashes we need to compute since often times only a small portion of the keys are updated\n * so we don't have to hash the keys that are never updated.\n *\n * Note: The `valueIndex` and `hashIndex` have disjoint keys.\n * When a key that has only one value gets a new distinct value,\n * it is added to the `hashIndex` and removed from the `valueIndex` and vice versa.\n */\n #valueIndex: ValueIndex<K, V>\n #hashIndex: HashIndex<K, V>\n\n constructor() {\n this.#valueIndex = new ValueIndex<K, V>()\n this.#hashIndex = new HashIndex<K, V>()\n }\n\n toString(indent = false): string {\n return `Index(\\n ${this.#valueIndex.toString(indent)},\\n ${this.#hashIndex.toString(indent)}\\n)`\n }\n\n get(key: K): Array<[V, number]> {\n if (this.#valueIndex.has(key)) {\n return [this.#valueIndex.get(key)!]\n }\n return this.#hashIndex.get(key)\n }\n\n getMultiplicity(key: K, value: V): number {\n if (this.#valueIndex.has(key)) {\n return this.#valueIndex.getMultiplicity(key)\n }\n return this.#hashIndex.getMultiplicity(key, value)\n }\n\n /**\n * This returns an iterator that iterates over all key-value pairs.\n * @returns An iterable of all key-value pairs (and their multiplicities) in the index.\n */\n #entries(): Iterable<[K, [V, number]]> {\n return concatIterable(\n this.#valueIndex.entries(),\n this.#hashIndex.entriesIterator()\n )\n }\n\n /**\n * This method only iterates over the keys and not over the values.\n * Hence, it is more efficient than the `#entries` method.\n * It returns an iterator that you can use if you need to iterate over the values for a given key.\n * @returns An iterator of all *keys* in the index and their corresponding value iterator.\n */\n *#entriesIterators(): Iterable<[K, Iterable<[V, number]>]> {\n for (const [key, [value, multiplicity]] of this.#valueIndex.entries()) {\n yield [key, new Map<V, number>([[value, multiplicity]])]\n }\n for (const [key, valueMap] of this.#hashIndex.entries()) {\n yield [\n key,\n mapIterable(valueMap, ([_hash, [value, multiplicity]]) => [\n value,\n multiplicity,\n ]),\n ]\n }\n }\n\n has(key: K): boolean {\n return this.#valueIndex.has(key) || this.#hashIndex.has(key)\n }\n\n get size(): number {\n return this.#valueIndex.size + this.#hashIndex.size\n }\n\n addValue(key: K, value: [V, number]): void {\n const containedInValueIndex = this.#valueIndex.has(key)\n const containedInHashIndex = this.#hashIndex.has(key)\n\n if (containedInHashIndex && containedInValueIndex) {\n throw new Error(\n `Key ${key} is contained in both the value index and the hash index. This should never happen because they should have disjoint keysets.`\n )\n }\n\n if (!containedInValueIndex && !containedInHashIndex) {\n // This is the first time we see the key\n // Add it to the value index\n this.#valueIndex.addValue(key, value)\n return\n }\n\n if (containedInValueIndex) {\n // This key is already in the value index\n // It could be that it's the same value or a different one\n // If it's a different value we will need to remove the key from the value index\n // and add the key and its two values to the hash index\n try {\n this.#valueIndex.addValue(key, value)\n } catch {\n // This is a different value, need to move the key to the hash index\n const existingValue = this.#valueIndex.get(key)!\n this.#valueIndex.delete(key)\n this.#hashIndex.addValue(key, existingValue)\n this.#hashIndex.addValue(key, value)\n }\n return\n }\n\n if (containedInHashIndex) {\n // This key is already in the hash index so it already has two or more values.\n // However, this new value and multiplicity could cause an existing value to be removed\n // and lead to the key having only a single value in which case we need to move it back to the value index\n const singleRemainingValue = this.#hashIndex.addValue(key, value)\n if (singleRemainingValue) {\n // The key only has a single remaining value so we need to move it back to the value index\n this.#hashIndex.delete(key)\n this.#valueIndex.addValue(key, singleRemainingValue)\n }\n return\n }\n }\n\n append(other: Index<K, V>): void {\n for (const [key, value] of other.#entries()) {\n this.addValue(key, value)\n }\n }\n\n join<V2>(other: Index<K, V2>): MultiSet<[K, [V, V2]]> {\n const result: Array<[[K, [V, V2]], number]> = []\n\n // We want to iterate over the smaller of the two indexes to reduce the\n // number of operations we need to do.\n if (this.size <= other.size) {\n for (const [key, valueIt] of this.#entriesIterators()) {\n if (!other.has(key)) continue\n const otherValues = other.get(key)\n for (const [val1, mul1] of valueIt) {\n for (const [val2, mul2] of otherValues) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n } else {\n for (const [key, otherValueIt] of other.#entriesIterators()) {\n if (!this.has(key)) continue\n const values = this.get(key)\n for (const [val2, mul2] of otherValueIt) {\n for (const [val1, mul1] of values) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n }\n\n return new MultiSet(result)\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexes.js","sources":["../../src/indexes.ts"],"sourcesContent":["/**\n * # Optimized Index Data Structure\n *\n * Multi-level index that adapts storage strategy based on data patterns to minimize memory\n * usage, eliminate wasteful lookups, and avoid hashing whenever possible.\n *\n * ## Storage Strategy\n *\n * **Single value**: `IndexMap['key'] → [value, multiplicity]` (no hashing needed)\n *\n * **Multiple unprefixed values**: Direct ValueMap (avoids NO_PREFIX lookup)\n * ```\n * IndexMap['key'] → ValueMap { hash(value1) → [value1, mult1], ... }\n * ```\n *\n * **Values with prefixes**: PrefixMap uses prefix keys directly (no hashing)\n * ```\n * IndexMap['key'] → PrefixMap { 'prefix1' → [value1, mult1], NO_PREFIX → ValueMap{...} }\n * ```\n *\n * **Multiple values per prefix**: ValueMap within PrefixMap (hash only suffixes)\n * ```\n * PrefixMap['prefix'] → ValueMap { hash(suffix1) → [full_value1, mult1], ... }\n * ```\n *\n * ## Dynamic Evolution\n *\n * Structure automatically evolves as data is added:\n * - Single → ValueMap (when both values unprefixed)\n * - Single → PrefixMap (when at least one prefixed)\n * - ValueMap → PrefixMap (adding prefixed value to unprefixed)\n *\n * Prefixes extracted from array values: `['prefix', 'suffix']` → prefix='prefix'\n */\n\nimport { MultiSet } from \"./multiset.js\"\nimport { hash } from \"./hashing/index.js\"\nimport type { Hash } from \"./hashing/index.js\"\n\n// We use a symbol to represent the absence of a prefix, unprefixed values a stored\n// against this key.\nconst NO_PREFIX = Symbol(`NO_PREFIX`)\ntype NO_PREFIX = typeof NO_PREFIX\n\n// A single value is a tuple of the value and the multiplicity.\ntype SingleValue<TValue> = [TValue, number]\n\n// Base map type for the index. Stores single values, prefix maps, or value maps against a key.\ntype IndexMap<TKey, TValue, TPrefix> = Map<\n TKey,\n SingleValue<TValue> | PrefixMap<TValue, TPrefix> | ValueMap<TValue>\n>\n\n// Second level map type for the index, stores single values or value maps against a prefix.\nclass PrefixMap<TValue, TPrefix> extends Map<\n TPrefix | NO_PREFIX,\n SingleValue<TValue> | ValueMap<TValue>\n> {\n /**\n * Add a value to the PrefixMap. Returns true if the map becomes empty after the operation.\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const prefix = getPrefix<TValue, TPrefix>(value)\n const valueMapOrSingleValue = this.get(prefix)\n\n if (isSingleValue(valueMapOrSingleValue)) {\n const [currentValue, currentMultiplicity] = valueMapOrSingleValue\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n if (currentPrefix !== prefix) {\n throw new Error(`Mismatching prefixes, this should never happen`)\n }\n\n if (currentValue === value || hash(currentValue) === hash(value)) {\n // Same value, update multiplicity\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(prefix)\n } else {\n this.set(prefix, [value, newMultiplicity])\n }\n } else {\n // Different suffixes, need to create ValueMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), valueMapOrSingleValue)\n valueMap.set(hash(value), [value, multiplicity])\n this.set(prefix, valueMap)\n }\n } else if (valueMapOrSingleValue === undefined) {\n // No existing value for this prefix\n this.set(prefix, [value, multiplicity])\n } else {\n // Existing ValueMap\n const isEmpty = valueMapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.delete(prefix)\n }\n }\n\n return this.size === 0\n }\n}\n\n// Third level map type for the index, stores single values or value maps against a hash.\nclass ValueMap<TValue> extends Map<Hash, [TValue, number]> {\n /**\n * Add a value to the ValueMap. Returns true if the map becomes empty after the operation.\n * @param value - The full value to store\n * @param multiplicity - The multiplicity to add\n * @param hashKey - Optional hash key to use instead of hashing the full value (used when in PrefixMap context)\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const key = hash(value)\n const currentValue = this.get(key)\n\n if (currentValue) {\n const [, currentMultiplicity] = currentValue\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(key)\n } else {\n this.set(key, [value, newMultiplicity])\n }\n } else {\n this.set(key, [value, multiplicity])\n }\n\n return this.size === 0\n }\n}\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class Index<TKey, TValue, TPrefix = any> {\n /*\n * This index maintains a nested map of keys -> (value, multiplicities), where:\n * - initially the values are stored against the key as a single value tuple\n * - when a key gets additional values, the values are stored against the key in a\n * prefix map\n * - the prefix is extract where possible from values that are structured as\n * [rowPrimaryKey, rowValue], as they are in the Tanstack DB query pipeline.\n * - only when there are multiple values for a given prefix do we fall back to a\n * hash to identify identical values, storing them in a third level value map.\n */\n #inner: IndexMap<TKey, TValue, TPrefix>\n\n constructor() {\n this.#inner = new Map()\n }\n\n /**\n * This method returns a string representation of the index.\n * @param indent - Whether to indent the string representation.\n * @returns A string representation of the index.\n */\n toString(indent = false): string {\n return `Index(${JSON.stringify(\n [...this.entries()],\n undefined,\n indent ? 2 : undefined\n )})`\n }\n\n /**\n * The size of the index.\n */\n get size(): number {\n return this.#inner.size\n }\n\n /**\n * This method checks if the index has a given key.\n * @param key - The key to check.\n * @returns True if the index has the key, false otherwise.\n */\n has(key: TKey): boolean {\n return this.#inner.has(key)\n }\n\n /**\n * This method returns all values for a given key.\n * @param key - The key to get the values for.\n * @returns An array of value tuples [value, multiplicity].\n */\n get(key: TKey): Array<[TValue, number]> {\n return [...this.getIterator(key)]\n }\n\n /**\n * This method returns an iterator over all values for a given key.\n * @param key - The key to get the values for.\n * @returns An iterator of value tuples [value, multiplicity].\n */\n *getIterator(key: TKey): Iterable<[TValue, number]> {\n const mapOrSingleValue = this.#inner.get(key)\n if (isSingleValue(mapOrSingleValue)) {\n yield mapOrSingleValue\n } else if (mapOrSingleValue === undefined) {\n return\n } else if (mapOrSingleValue instanceof ValueMap) {\n // Direct ValueMap - all values have NO_PREFIX\n for (const valueTuple of mapOrSingleValue.values()) {\n yield valueTuple\n }\n } else {\n // PrefixMap - iterate through all prefixes\n for (const singleValueOrValueMap of mapOrSingleValue.values()) {\n if (isSingleValue(singleValueOrValueMap)) {\n yield singleValueOrValueMap\n } else {\n for (const valueTuple of singleValueOrValueMap.values()) {\n yield valueTuple\n }\n }\n }\n }\n }\n\n /**\n * This returns an iterator that iterates over all key-value pairs.\n * @returns An iterable of all key-value pairs (and their multiplicities) in the index.\n */\n *entries(): Iterable<[TKey, [TValue, number]]> {\n for (const key of this.#inner.keys()) {\n for (const valueTuple of this.getIterator(key)) {\n yield [key, valueTuple]\n }\n }\n }\n\n /**\n * This method only iterates over the keys and not over the values.\n * Hence, it is more efficient than the `#entries` method.\n * It returns an iterator that you can use if you need to iterate over the values for a given key.\n * @returns An iterator of all *keys* in the index and their corresponding value iterator.\n */\n *entriesIterators(): Iterable<[TKey, Iterable<[TValue, number]>]> {\n for (const key of this.#inner.keys()) {\n yield [key, this.getIterator(key)]\n }\n }\n\n /**\n * This method adds a value to the index.\n * @param key - The key to add the value to.\n * @param valueTuple - The value tuple [value, multiplicity] to add to the index.\n */\n addValue(key: TKey, valueTuple: SingleValue<TValue>) {\n const [value, multiplicity] = valueTuple\n // If the multiplicity is 0, do nothing\n if (multiplicity === 0) return\n\n const mapOrSingleValue = this.#inner.get(key)\n\n if (mapOrSingleValue === undefined) {\n // First value for this key\n this.#inner.set(key, valueTuple)\n return\n }\n\n if (isSingleValue(mapOrSingleValue)) {\n // Handle transition from single value to map\n this.#handleSingleValueTransition(\n key,\n mapOrSingleValue,\n value,\n multiplicity\n )\n return\n }\n\n if (mapOrSingleValue instanceof ValueMap) {\n // Handle existing ValueMap\n const prefix = getPrefix<TValue, TPrefix>(value)\n if (prefix !== NO_PREFIX) {\n // Convert ValueMap to PrefixMap since we have a prefixed value\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n prefixMap.set(NO_PREFIX, mapOrSingleValue)\n prefixMap.set(prefix, valueTuple)\n this.#inner.set(key, prefixMap)\n } else {\n // Add to existing ValueMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n } else {\n // Handle existing PrefixMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n }\n\n /**\n * Handle the transition from a single value to either a ValueMap or PrefixMap\n */\n #handleSingleValueTransition(\n key: TKey,\n currentSingleValue: SingleValue<TValue>,\n newValue: TValue,\n multiplicity: number\n ) {\n const [currentValue, currentMultiplicity] = currentSingleValue\n\n // Check for exact same value (reference equality)\n if (currentValue === newValue) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Get prefixes for both values\n const newPrefix = getPrefix<TValue, TPrefix>(newValue)\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n // Check if they're the same value by prefix/suffix comparison\n if (\n currentPrefix === newPrefix &&\n (currentValue === newValue || hash(currentValue) === hash(newValue))\n ) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Different values - choose appropriate map type\n if (currentPrefix === NO_PREFIX && newPrefix === NO_PREFIX) {\n // Both have NO_PREFIX, use ValueMap directly\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n this.#inner.set(key, valueMap)\n } else {\n // At least one has a prefix, use PrefixMap\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n\n if (currentPrefix === newPrefix) {\n // Same prefix, different suffixes - need ValueMap within PrefixMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n prefixMap.set(currentPrefix, valueMap)\n } else {\n // Different prefixes - store as separate single values\n prefixMap.set(currentPrefix, currentSingleValue)\n prefixMap.set(newPrefix, [newValue, multiplicity])\n }\n\n this.#inner.set(key, prefixMap)\n }\n }\n\n /**\n * This method appends another index to the current index.\n * @param other - The index to append to the current index.\n */\n append(other: Index<TKey, TValue>): void {\n for (const [key, value] of other.entries()) {\n this.addValue(key, value)\n }\n }\n\n /**\n * This method joins two indexes.\n * @param other - The index to join with the current index.\n * @returns A multiset of the joined values.\n */\n join<TValue2>(\n other: Index<TKey, TValue2>\n ): MultiSet<[TKey, [TValue, TValue2]]> {\n const result: Array<[[TKey, [TValue, TValue2]], number]> = []\n // We want to iterate over the smaller of the two indexes to reduce the\n // number of operations we need to do.\n if (this.size <= other.size) {\n for (const [key, valueIt] of this.entriesIterators()) {\n if (!other.has(key)) continue\n const otherValues = other.get(key)\n for (const [val1, mul1] of valueIt) {\n for (const [val2, mul2] of otherValues) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n } else {\n for (const [key, otherValueIt] of other.entriesIterators()) {\n if (!this.has(key)) continue\n const values = this.get(key)\n for (const [val2, mul2] of otherValueIt) {\n for (const [val1, mul1] of values) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n }\n\n return new MultiSet(result)\n }\n}\n\n/**\n * This function extracts the prefix from a value.\n * @param value - The value to extract the prefix from.\n * @returns The prefix and the suffix.\n */\nfunction getPrefix<TValue, TPrefix>(value: TValue): TPrefix | NO_PREFIX {\n // If the value is an array and the first element is a string or number, then the\n // first element is the prefix. This is used to distinguish between values without\n // the need for hashing unless there are multiple values for the same prefix.\n if (\n Array.isArray(value) &&\n (typeof value[0] === `string` ||\n typeof value[0] === `number` ||\n typeof value[0] === `bigint`)\n ) {\n return value[0] as TPrefix\n }\n return NO_PREFIX\n}\n\n/**\n * This function checks if a value is a single value.\n * @param value - The value to check.\n * @returns True if the value is a single value, false otherwise.\n */\nfunction isSingleValue<TValue>(\n value: SingleValue<TValue> | unknown\n): value is SingleValue<TValue> {\n return Array.isArray(value)\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAyCA,MAAM,YAAY,OAAO,WAAW;AAapC,MAAM,kBAAmC,IAGvC;AAAA;AAAA;AAAA;AAAA,EAIA,SAAS,OAAe,cAA+B;AACrD,QAAI,iBAAiB,EAAG,QAAO,KAAK,SAAS;AAE7C,UAAM,SAAS,UAA2B,KAAK;AAC/C,UAAM,wBAAwB,KAAK,IAAI,MAAM;AAE7C,QAAI,cAAc,qBAAqB,GAAG;AACxC,YAAM,CAAC,cAAc,mBAAmB,IAAI;AAC5C,YAAM,gBAAgB,UAA2B,YAAY;AAE7D,UAAI,kBAAkB,QAAQ;AAC5B,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE;AAEA,UAAI,iBAAiB,SAAS,KAAK,YAAY,MAAM,KAAK,KAAK,GAAG;AAEhE,cAAM,kBAAkB,sBAAsB;AAC9C,YAAI,oBAAoB,GAAG;AACzB,eAAK,OAAO,MAAM;AAAA,QACpB,OAAO;AACL,eAAK,IAAI,QAAQ,CAAC,OAAO,eAAe,CAAC;AAAA,QAC3C;AAAA,MACF,OAAO;AAEL,cAAM,WAAW,IAAI,SAAA;AACrB,iBAAS,IAAI,KAAK,YAAY,GAAG,qBAAqB;AACtD,iBAAS,IAAI,KAAK,KAAK,GAAG,CAAC,OAAO,YAAY,CAAC;AAC/C,aAAK,IAAI,QAAQ,QAAQ;AAAA,MAC3B;AAAA,IACF,WAAW,0BAA0B,QAAW;AAE9C,WAAK,IAAI,QAAQ,CAAC,OAAO,YAAY,CAAC;AAAA,IACxC,OAAO;AAEL,YAAM,UAAU,sBAAsB,SAAS,OAAO,YAAY;AAClE,UAAI,SAAS;AACX,aAAK,OAAO,MAAM;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAGA,MAAM,iBAAyB,IAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzD,SAAS,OAAe,cAA+B;AACrD,QAAI,iBAAiB,EAAG,QAAO,KAAK,SAAS;AAE7C,UAAM,MAAM,KAAK,KAAK;AACtB,UAAM,eAAe,KAAK,IAAI,GAAG;AAEjC,QAAI,cAAc;AAChB,YAAM,CAAA,EAAG,mBAAmB,IAAI;AAChC,YAAM,kBAAkB,sBAAsB;AAC9C,UAAI,oBAAoB,GAAG;AACzB,aAAK,OAAO,GAAG;AAAA,MACjB,OAAO;AACL,aAAK,IAAI,KAAK,CAAC,OAAO,eAAe,CAAC;AAAA,MACxC;AAAA,IACF,OAAO;AACL,WAAK,IAAI,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,IACrC;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAOO,MAAM,MAAmC;AAAA,EAa9C,cAAc;AAbT;AAWL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGE,uBAAK,4BAAa,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,SAAS,OAAe;AAC/B,WAAO,SAAS,KAAK;AAAA,MACnB,CAAC,GAAG,KAAK,SAAS;AAAA,MAClB;AAAA,MACA,SAAS,IAAI;AAAA,IAAA,CACd;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe;AACjB,WAAO,mBAAK,QAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAoB;AACtB,WAAO,mBAAK,QAAO,IAAI,GAAG;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAoC;AACtC,WAAO,CAAC,GAAG,KAAK,YAAY,GAAG,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,CAAC,YAAY,KAAuC;AAClD,UAAM,mBAAmB,mBAAK,QAAO,IAAI,GAAG;AAC5C,QAAI,cAAc,gBAAgB,GAAG;AACnC,YAAM;AAAA,IACR,WAAW,qBAAqB,QAAW;AACzC;AAAA,IACF,WAAW,4BAA4B,UAAU;AAE/C,iBAAW,cAAc,iBAAiB,UAAU;AAClD,cAAM;AAAA,MACR;AAAA,IACF,OAAO;AAEL,iBAAW,yBAAyB,iBAAiB,UAAU;AAC7D,YAAI,cAAc,qBAAqB,GAAG;AACxC,gBAAM;AAAA,QACR,OAAO;AACL,qBAAW,cAAc,sBAAsB,UAAU;AACvD,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,CAAC,UAA8C;AAC7C,eAAW,OAAO,mBAAK,QAAO,KAAA,GAAQ;AACpC,iBAAW,cAAc,KAAK,YAAY,GAAG,GAAG;AAC9C,cAAM,CAAC,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,CAAC,mBAAiE;AAChE,eAAW,OAAO,mBAAK,QAAO,KAAA,GAAQ;AACpC,YAAM,CAAC,KAAK,KAAK,YAAY,GAAG,CAAC;AAAA,IACnC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,KAAW,YAAiC;AACnD,UAAM,CAAC,OAAO,YAAY,IAAI;AAE9B,QAAI,iBAAiB,EAAG;AAExB,UAAM,mBAAmB,mBAAK,QAAO,IAAI,GAAG;AAE5C,QAAI,qBAAqB,QAAW;AAElC,yBAAK,QAAO,IAAI,KAAK,UAAU;AAC/B;AAAA,IACF;AAEA,QAAI,cAAc,gBAAgB,GAAG;AAEnC,4BAAK,kDAAL,WACE,KACA,kBACA,OACA;AAEF;AAAA,IACF;AAEA,QAAI,4BAA4B,UAAU;AAExC,YAAM,SAAS,UAA2B,KAAK;AAC/C,UAAI,WAAW,WAAW;AAExB,cAAM,YAAY,IAAI,UAAA;AACtB,kBAAU,IAAI,WAAW,gBAAgB;AACzC,kBAAU,IAAI,QAAQ,UAAU;AAChC,2BAAK,QAAO,IAAI,KAAK,SAAS;AAAA,MAChC,OAAO;AAEL,cAAM,UAAU,iBAAiB,SAAS,OAAO,YAAY;AAC7D,YAAI,SAAS;AACX,6BAAK,QAAO,OAAO,GAAG;AAAA,QACxB;AAAA,MACF;AAAA,IACF,OAAO;AAEL,YAAM,UAAU,iBAAiB,SAAS,OAAO,YAAY;AAC7D,UAAI,SAAS;AACX,2BAAK,QAAO,OAAO,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAyEA,OAAO,OAAkC;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM,WAAW;AAC1C,WAAK,SAAS,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KACE,OACqC;AACrC,UAAM,SAAqD,CAAA;AAG3D,QAAI,KAAK,QAAQ,MAAM,MAAM;AAC3B,iBAAW,CAAC,KAAK,OAAO,KAAK,KAAK,oBAAoB;AACpD,YAAI,CAAC,MAAM,IAAI,GAAG,EAAG;AACrB,cAAM,cAAc,MAAM,IAAI,GAAG;AACjC,mBAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,qBAAW,CAAC,MAAM,IAAI,KAAK,aAAa;AACtC,gBAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,qBAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,CAAC,KAAK,YAAY,KAAK,MAAM,oBAAoB;AAC1D,YAAI,CAAC,KAAK,IAAI,GAAG,EAAG;AACpB,cAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,mBAAW,CAAC,MAAM,IAAI,KAAK,cAAc;AACvC,qBAAW,CAAC,MAAM,IAAI,KAAK,QAAQ;AACjC,gBAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,qBAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,IAAI,SAAS,MAAM;AAAA,EAC5B;AACF;AA5QE;AAXK;AAAA;AAAA;AAAA;AAsKL,iCAAA,SACE,KACA,oBACA,UACA,cACA;AACA,QAAM,CAAC,cAAc,mBAAmB,IAAI;AAG5C,MAAI,iBAAiB,UAAU;AAC7B,UAAM,kBAAkB,sBAAsB;AAC9C,QAAI,oBAAoB,GAAG;AACzB,yBAAK,QAAO,OAAO,GAAG;AAAA,IACxB,OAAO;AACL,yBAAK,QAAO,IAAI,KAAK,CAAC,UAAU,eAAe,CAAC;AAAA,IAClD;AACA;AAAA,EACF;AAGA,QAAM,YAAY,UAA2B,QAAQ;AACrD,QAAM,gBAAgB,UAA2B,YAAY;AAG7D,MACE,kBAAkB,cACjB,iBAAiB,YAAY,KAAK,YAAY,MAAM,KAAK,QAAQ,IAClE;AACA,UAAM,kBAAkB,sBAAsB;AAC9C,QAAI,oBAAoB,GAAG;AACzB,yBAAK,QAAO,OAAO,GAAG;AAAA,IACxB,OAAO;AACL,yBAAK,QAAO,IAAI,KAAK,CAAC,UAAU,eAAe,CAAC;AAAA,IAClD;AACA;AAAA,EACF;AAGA,MAAI,kBAAkB,aAAa,cAAc,WAAW;AAE1D,UAAM,WAAW,IAAI,SAAA;AACrB,aAAS,IAAI,KAAK,YAAY,GAAG,kBAAkB;AACnD,aAAS,IAAI,KAAK,QAAQ,GAAG,CAAC,UAAU,YAAY,CAAC;AACrD,uBAAK,QAAO,IAAI,KAAK,QAAQ;AAAA,EAC/B,OAAO;AAEL,UAAM,YAAY,IAAI,UAAA;AAEtB,QAAI,kBAAkB,WAAW;AAE/B,YAAM,WAAW,IAAI,SAAA;AACrB,eAAS,IAAI,KAAK,YAAY,GAAG,kBAAkB;AACnD,eAAS,IAAI,KAAK,QAAQ,GAAG,CAAC,UAAU,YAAY,CAAC;AACrD,gBAAU,IAAI,eAAe,QAAQ;AAAA,IACvC,OAAO;AAEL,gBAAU,IAAI,eAAe,kBAAkB;AAC/C,gBAAU,IAAI,WAAW,CAAC,UAAU,YAAY,CAAC;AAAA,IACnD;AAEA,uBAAK,QAAO,IAAI,KAAK,SAAS;AAAA,EAChC;AACF;AA0DF,SAAS,UAA2B,OAAoC;AAItE,MACE,MAAM,QAAQ,KAAK,MAClB,OAAO,MAAM,CAAC,MAAM,YACnB,OAAO,MAAM,CAAC,MAAM,YACpB,OAAO,MAAM,CAAC,MAAM,WACtB;AACA,WAAO,MAAM,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAOA,SAAS,cACP,OAC8B;AAC9B,SAAO,MAAM,QAAQ,KAAK;AAC5B;"}
@@ -1,14 +1,16 @@
1
1
  import { DifferenceStreamWriter, UnaryOperator, DifferenceStreamReader } from '../graph.js';
2
- import { IStreamBuilder } from '../types.js';
2
+ import { IStreamBuilder, KeyValue } from '../types.js';
3
+ type GetValue<T> = T extends KeyValue<any, infer V> ? V : never;
3
4
  /**
4
5
  * Operator that removes duplicates
5
6
  */
6
- export declare class DistinctOperator<T> extends UnaryOperator<T> {
7
+ export declare class DistinctOperator<T extends KeyValue<any, any>> extends UnaryOperator<T, KeyValue<number, GetValue<T>>> {
7
8
  #private;
8
- constructor(id: number, input: DifferenceStreamReader<T>, output: DifferenceStreamWriter<T>, by?: (value: T) => any);
9
+ constructor(id: number, input: DifferenceStreamReader<T>, output: DifferenceStreamWriter<KeyValue<number, GetValue<T>>>, by?: (value: T) => any);
9
10
  run(): void;
10
11
  }
11
12
  /**
12
13
  * Removes duplicate values
13
14
  */
14
- export declare function distinct<T>(by?: (value: T) => any): (stream: IStreamBuilder<T>) => IStreamBuilder<T>;
15
+ export declare function distinct<T extends KeyValue<any, any>>(by?: (value: T) => any): (stream: IStreamBuilder<T>) => IStreamBuilder<T>;
16
+ export {};
@@ -42,9 +42,9 @@ class DistinctOperator extends UnaryOperator {
42
42
  __privateGet(this, _values).set(hashedValue, newMultiplicity);
43
43
  }
44
44
  if (oldMultiplicity <= 0 && newMultiplicity > 0) {
45
- result.push([value, 1]);
45
+ result.push([[hash(__privateGet(this, _by).call(this, value)), value[1]], 1]);
46
46
  } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
47
- result.push([value, -1]);
47
+ result.push([[hash(__privateGet(this, _by).call(this, value)), value[1]], -1]);
48
48
  }
49
49
  }
50
50
  if (result.length > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"distinct.js","sources":["../../../src/operators/distinct.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { hash } from \"../hashing/index.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport type { Hash } from \"../hashing/index.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder } from \"../types.js\"\n\ntype Multiplicity = number\n\n/**\n * Operator that removes duplicates\n */\nexport class DistinctOperator<T> extends UnaryOperator<T> {\n #by: (value: T) => any\n #values: Map<Hash, Multiplicity> // keeps track of the number of times each value has been seen\n\n constructor(\n id: number,\n input: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n by: (value: T) => any = (value: T) => value\n ) {\n super(id, input, output)\n this.#by = by\n this.#values = new Map()\n }\n\n run(): void {\n const updatedValues = new Map<Hash, [Multiplicity, T]>()\n\n // Compute the new multiplicity for each value\n for (const message of this.inputMessages()) {\n for (const [value, diff] of message.getInner()) {\n const hashedValue = hash(this.#by(value))\n\n const oldMultiplicity =\n updatedValues.get(hashedValue)?.[0] ??\n this.#values.get(hashedValue) ??\n 0\n const newMultiplicity = oldMultiplicity + diff\n\n updatedValues.set(hashedValue, [newMultiplicity, value])\n }\n }\n\n const result: Array<[T, number]> = []\n\n // Check which values became visible or disappeared\n for (const [\n hashedValue,\n [newMultiplicity, value],\n ] of updatedValues.entries()) {\n const oldMultiplicity = this.#values.get(hashedValue) ?? 0\n\n if (newMultiplicity === 0) {\n this.#values.delete(hashedValue)\n } else {\n this.#values.set(hashedValue, newMultiplicity)\n }\n\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value wasn't present in the stream\n // but with this change it is now present in the stream\n result.push([value, 1])\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was present in the stream\n // but with this change it is no longer present in the stream\n result.push([value, -1])\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n}\n\n/**\n * Removes duplicate values\n */\nexport function distinct<T>(by: (value: T) => any = (value: T) => value) {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>()\n )\n const operator = new DistinctOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n by\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAaO,MAAM,yBAA4B,cAAiB;AAAA;AAAA,EAIxD,YACE,IACA,OACA,QACA,KAAwB,CAAC,UAAa,OACtC;AACA,UAAM,IAAI,OAAO,MAAM;AATzB;AACA;AASE,uBAAK,KAAM;AACX,uBAAK,6BAAc,IAAA;AAAA,EACrB;AAAA,EAEA,MAAY;;AACV,UAAM,oCAAoB,IAAA;AAG1B,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,OAAO,IAAI,KAAK,QAAQ,YAAY;AAC9C,cAAM,cAAc,KAAK,mBAAK,KAAL,WAAS,MAAM;AAExC,cAAM,oBACJ,mBAAc,IAAI,WAAW,MAA7B,mBAAiC,OACjC,mBAAK,SAAQ,IAAI,WAAW,KAC5B;AACF,cAAM,kBAAkB,kBAAkB;AAE1C,sBAAc,IAAI,aAAa,CAAC,iBAAiB,KAAK,CAAC;AAAA,MACzD;AAAA,IACF;AAEA,UAAM,SAA6B,CAAA;AAGnC,eAAW;AAAA,MACT;AAAA,MACA,CAAC,iBAAiB,KAAK;AAAA,IAAA,KACpB,cAAc,WAAW;AAC5B,YAAM,kBAAkB,mBAAK,SAAQ,IAAI,WAAW,KAAK;AAEzD,UAAI,oBAAoB,GAAG;AACzB,2BAAK,SAAQ,OAAO,WAAW;AAAA,MACjC,OAAO;AACL,2BAAK,SAAQ,IAAI,aAAa,eAAe;AAAA,MAC/C;AAEA,UAAI,mBAAmB,KAAK,kBAAkB,GAAG;AAG/C,eAAO,KAAK,CAAC,OAAO,CAAC,CAAC;AAAA,MACxB,WAAW,kBAAkB,KAAK,mBAAmB,GAAG;AAGtD,eAAO,KAAK,CAAC,OAAO,EAAE,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAI,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AA9DE;AACA;AAkEK,SAAS,SAAY,KAAwB,CAAC,UAAa,OAAO;AACvE,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"distinct.js","sources":["../../../src/operators/distinct.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { hash } from \"../hashing/index.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport type { Hash } from \"../hashing/index.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, KeyValue } from \"../types.js\"\n\ntype Multiplicity = number\n\ntype GetValue<T> = T extends KeyValue<any, infer V> ? V : never\n\n/**\n * Operator that removes duplicates\n */\nexport class DistinctOperator<\n T extends KeyValue<any, any>,\n> extends UnaryOperator<T, KeyValue<number, GetValue<T>>> {\n #by: (value: T) => any\n #values: Map<Hash, Multiplicity> // keeps track of the number of times each value has been seen\n\n constructor(\n id: number,\n input: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<KeyValue<number, GetValue<T>>>,\n by: (value: T) => any = (value: T) => value\n ) {\n super(id, input, output)\n this.#by = by\n this.#values = new Map()\n }\n\n run(): void {\n const updatedValues = new Map<Hash, [Multiplicity, T]>()\n\n // Compute the new multiplicity for each value\n for (const message of this.inputMessages()) {\n for (const [value, diff] of message.getInner()) {\n const hashedValue = hash(this.#by(value))\n\n const oldMultiplicity =\n updatedValues.get(hashedValue)?.[0] ??\n this.#values.get(hashedValue) ??\n 0\n const newMultiplicity = oldMultiplicity + diff\n updatedValues.set(hashedValue, [newMultiplicity, value])\n }\n }\n\n const result: Array<[KeyValue<number, GetValue<T>>, number]> = []\n\n // Check which values became visible or disappeared\n for (const [\n hashedValue,\n [newMultiplicity, value],\n ] of updatedValues.entries()) {\n const oldMultiplicity = this.#values.get(hashedValue) ?? 0\n\n if (newMultiplicity === 0) {\n this.#values.delete(hashedValue)\n } else {\n this.#values.set(hashedValue, newMultiplicity)\n }\n\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value wasn't present in the stream\n // but with this change it is now present in the stream\n result.push([[hash(this.#by(value)), value[1]], 1])\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was present in the stream\n // but with this change it is no longer present in the stream\n result.push([[hash(this.#by(value)), value[1]], -1])\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n}\n\n/**\n * Removes duplicate values\n */\nexport function distinct<T extends KeyValue<any, any>>(\n by: (value: T) => any = (value: T) => value\n) {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>()\n )\n const operator = new DistinctOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n by\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAeO,MAAM,yBAEH,cAAgD;AAAA;AAAA,EAIxD,YACE,IACA,OACA,QACA,KAAwB,CAAC,UAAa,OACtC;AACA,UAAM,IAAI,OAAO,MAAM;AATzB;AACA;AASE,uBAAK,KAAM;AACX,uBAAK,6BAAc,IAAA;AAAA,EACrB;AAAA,EAEA,MAAY;;AACV,UAAM,oCAAoB,IAAA;AAG1B,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,OAAO,IAAI,KAAK,QAAQ,YAAY;AAC9C,cAAM,cAAc,KAAK,mBAAK,KAAL,WAAS,MAAM;AAExC,cAAM,oBACJ,mBAAc,IAAI,WAAW,MAA7B,mBAAiC,OACjC,mBAAK,SAAQ,IAAI,WAAW,KAC5B;AACF,cAAM,kBAAkB,kBAAkB;AAC1C,sBAAc,IAAI,aAAa,CAAC,iBAAiB,KAAK,CAAC;AAAA,MACzD;AAAA,IACF;AAEA,UAAM,SAAyD,CAAA;AAG/D,eAAW;AAAA,MACT;AAAA,MACA,CAAC,iBAAiB,KAAK;AAAA,IAAA,KACpB,cAAc,WAAW;AAC5B,YAAM,kBAAkB,mBAAK,SAAQ,IAAI,WAAW,KAAK;AAEzD,UAAI,oBAAoB,GAAG;AACzB,2BAAK,SAAQ,OAAO,WAAW;AAAA,MACjC,OAAO;AACL,2BAAK,SAAQ,IAAI,aAAa,eAAe;AAAA,MAC/C;AAEA,UAAI,mBAAmB,KAAK,kBAAkB,GAAG;AAG/C,eAAO,KAAK,CAAC,CAAC,KAAK,mBAAK,KAAL,WAAS,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAAA,MACpD,WAAW,kBAAkB,KAAK,mBAAmB,GAAG;AAGtD,eAAO,KAAK,CAAC,CAAC,KAAK,mBAAK,KAAL,WAAS,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAI,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AA7DE;AACA;AAiEK,SAAS,SACd,KAAwB,CAAC,UAAa,OACtC;AACA,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;"}
@@ -34,16 +34,19 @@ export declare function avg<T>(valueExtractor?: (value: T) => number): Aggregate
34
34
  sum: number;
35
35
  count: number;
36
36
  }>;
37
+ type CanMinMax = number | Date | bigint;
37
38
  /**
38
39
  * Creates a min aggregate function that computes the minimum value in a group
39
- * @param valueExtractor Function to extract a numeric value from each data entry
40
+ * @param valueExtractor Function to extract a comparable value from each data entry
40
41
  */
41
- export declare function min<T>(valueExtractor?: (value: T) => number): AggregateFunction<T, number, number>;
42
+ export declare function min<T extends CanMinMax>(): AggregateFunction<T, T | undefined, T | undefined>;
43
+ export declare function min<T, V extends CanMinMax>(valueExtractor: (value: T) => V): AggregateFunction<T, V | undefined, V | undefined>;
42
44
  /**
43
45
  * Creates a max aggregate function that computes the maximum value in a group
44
- * @param valueExtractor Function to extract a numeric value from each data entry
46
+ * @param valueExtractor Function to extract a comparable value from each data entry
45
47
  */
46
- export declare function max<T>(valueExtractor?: (value: T) => number): AggregateFunction<T, number, number>;
48
+ export declare function max<T extends CanMinMax>(): AggregateFunction<T, T | undefined, T | undefined>;
49
+ export declare function max<T, V extends CanMinMax>(valueExtractor: (value: T) => V): AggregateFunction<T, V | undefined, V | undefined>;
47
50
  /**
48
51
  * Creates a median aggregate function that computes the middle value in a sorted group
49
52
  * If there's an even number of values, returns the average of the two middle values
@@ -115,31 +115,33 @@ function avg(valueExtractor = (v) => v) {
115
115
  }
116
116
  };
117
117
  }
118
- function min(valueExtractor = (v) => v) {
118
+ function min(valueExtractor) {
119
+ const extractor = valueExtractor ?? ((v) => v);
119
120
  return {
120
- preMap: (data) => valueExtractor(data),
121
+ preMap: (data) => extractor(data),
121
122
  reduce: (values) => {
122
- let minValue = Number.POSITIVE_INFINITY;
123
+ let minValue;
123
124
  for (const [value, _multiplicity] of values) {
124
- if (value < minValue) {
125
+ if (!minValue || value && value < minValue) {
125
126
  minValue = value;
126
127
  }
127
128
  }
128
- return minValue === Number.POSITIVE_INFINITY ? 0 : minValue;
129
+ return minValue;
129
130
  }
130
131
  };
131
132
  }
132
- function max(valueExtractor = (v) => v) {
133
+ function max(valueExtractor) {
134
+ const extractor = valueExtractor ?? ((v) => v);
133
135
  return {
134
- preMap: (data) => valueExtractor(data),
136
+ preMap: (data) => extractor(data),
135
137
  reduce: (values) => {
136
- let maxValue = Number.NEGATIVE_INFINITY;
138
+ let maxValue;
137
139
  for (const [value, _multiplicity] of values) {
138
- if (value > maxValue) {
140
+ if (!maxValue || value && value > maxValue) {
139
141
  maxValue = value;
140
142
  }
141
143
  }
142
- return maxValue === Number.NEGATIVE_INFINITY ? 0 : maxValue;
144
+ return maxValue;
143
145
  }
144
146
  };
145
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"groupBy.js","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { map } from \"./map.js\"\nimport { reduce } from \"./reduce.js\"\nimport type { IStreamBuilder, KeyValue } from \"../types.js\"\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n | BasicAggregateFunction<T, R, V>\n | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = `__original_key__`\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = JSON.stringify(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<string, Record<string, unknown>>\n })\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<string, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number]\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n })\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n })\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(\n valueExtractor: (value: T) => any = (v) => v\n): AggregateFunction<T, number, number> {\n return {\n // Count only not-null values (the `== null` comparison gives true for both null and undefined)\n preMap: (data: T) => (valueExtractor(data) == null ? 0 : 1),\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [nullMultiplier, multiplicity] of values) {\n totalCount += nullMultiplier * multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function min<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let minValue = Number.POSITIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value < minValue) {\n minValue = value\n }\n }\n return minValue === Number.POSITIVE_INFINITY ? 0 : minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function max<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let maxValue = Number.NEGATIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value > maxValue) {\n maxValue = value\n }\n }\n return maxValue === Number.NEGATIVE_INFINITY ? 0 : maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"groupBy.js","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { map } from \"./map.js\"\nimport { reduce } from \"./reduce.js\"\nimport type { IStreamBuilder, KeyValue } from \"../types.js\"\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n | BasicAggregateFunction<T, R, V>\n | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = `__original_key__`\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = JSON.stringify(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<string, Record<string, unknown>>\n })\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<string, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number]\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n })\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n })\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(\n valueExtractor: (value: T) => any = (v) => v\n): AggregateFunction<T, number, number> {\n return {\n // Count only not-null values (the `== null` comparison gives true for both null and undefined)\n preMap: (data: T) => (valueExtractor(data) == null ? 0 : 1),\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [nullMultiplier, multiplicity] of values) {\n totalCount += nullMultiplier * multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\ntype CanMinMax = number | Date | bigint\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function min<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function min<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function min<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let minValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (!minValue || (value && value < minValue)) {\n minValue = value\n }\n }\n return minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function max<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function max<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function max<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let maxValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (!maxValue || (value && value > maxValue)) {\n maxValue = value\n }\n }\n return maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":[],"mappings":"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"}
package/dist/esm/utils.js CHANGED
@@ -83,23 +83,11 @@ class ObjectIdGenerator {
83
83
  }
84
84
  }
85
85
  const globalObjectIdGenerator = new ObjectIdGenerator();
86
- function* concatIterable(...iterables) {
87
- for (const iterable of iterables) {
88
- yield* iterable;
89
- }
90
- }
91
- function* mapIterable(it, fn) {
92
- for (const t of it) {
93
- yield fn(t);
94
- }
95
- }
96
86
  export {
97
87
  DefaultMap,
98
88
  ObjectIdGenerator,
99
89
  binarySearch,
100
90
  chunkedArrayPush,
101
- concatIterable,
102
- globalObjectIdGenerator,
103
- mapIterable
91
+ globalObjectIdGenerator
104
92
  };
105
93
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n"],"names":[],"mappings":"AAgBO,MAAM,mBAAyB,IAAU;AAAA,EAC9C,YACU,cACR,SACA;AACA,UAAM,OAAO;AAHL,SAAA,eAAA;AAAA,EAIV;AAAA,EAEA,IAAI,KAAW;AACb,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAElB,aAAO,KAAK,aAAA;AAAA,IACd;AACA,WAAO,MAAM,IAAI,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAQ,SAA6B;AAC1C,UAAM,QAAQ,KAAK,IAAI,GAAG;AAC1B,UAAM,WAAW,QAAQ,KAAK;AAC9B,SAAK,IAAI,KAAK,QAAQ;AACtB,WAAO;AAAA,EACT;AACF;AAMA,MAAM,YAAY;AACX,SAAS,iBAAiB,OAAuB,OAAuB;AAC7E,MAAI,MAAM,UAAU,WAAW;AAC7B,UAAM,KAAK,GAAG,KAAK;AAAA,EACrB,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,SAAS;AAC1C,YAAM,KAAK,GAAG,KAAK;AAAA,IACrB;AAAA,EACF;AACF;AAEO,SAAS,aACd,OACA,OACA,YACQ;AACR,MAAI,MAAM;AACV,MAAI,OAAO,MAAM;AACjB,SAAO,MAAM,MAAM;AACjB,UAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,UAAM,aAAa,WAAW,MAAM,GAAG,GAAI,KAAK;AAChD,QAAI,aAAa,GAAG;AAClB,YAAM,MAAM;AAAA,IACd,WAAW,aAAa,GAAG;AACzB,aAAO;AAAA,IACT,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMO,MAAM,kBAAkB;AAAA,EAAxB,cAAA;AACL,SAAQ,gCAAgB,QAAA;AACxB,SAAQ,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,MAAM,OAAoB;AAExB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,cAAM,OAAO,IAAI,WAAW,CAAC;AAC7B,qBAAa,aAAa,KAAK,YAAY;AAC3C,oBAAY,YAAY;AAAA,MAC1B;AACA,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,KAAK,QAAQ;AAAA,IACzC;AACA,WAAO,KAAK,UAAU,IAAI,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAoB;AAC9B,QAAI,UAAU,KAAM,QAAO;AAC3B,QAAI,UAAU,OAAW,QAAO;AAChC,QAAI,OAAO,UAAU,iBAAiB,OAAO,OAAO,KAAK,CAAC;AAE1D,WAAO,OAAO,KAAK,MAAM,KAAK,CAAC;AAAA,EACjC;AACF;AAKO,MAAM,0BAA0B,IAAI,kBAAA;AAEpC,UAAU,kBACZ,WACU;AACb,aAAW,YAAY,WAAW;AAChC,WAAO;AAAA,EACT;AACF;AAEO,UAAU,YACf,IACA,IACa;AACb,aAAW,KAAK,IAAI;AAClB,UAAM,GAAG,CAAC;AAAA,EACZ;AACF;"}
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n"],"names":[],"mappings":"AAgBO,MAAM,mBAAyB,IAAU;AAAA,EAC9C,YACU,cACR,SACA;AACA,UAAM,OAAO;AAHL,SAAA,eAAA;AAAA,EAIV;AAAA,EAEA,IAAI,KAAW;AACb,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAElB,aAAO,KAAK,aAAA;AAAA,IACd;AACA,WAAO,MAAM,IAAI,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAQ,SAA6B;AAC1C,UAAM,QAAQ,KAAK,IAAI,GAAG;AAC1B,UAAM,WAAW,QAAQ,KAAK;AAC9B,SAAK,IAAI,KAAK,QAAQ;AACtB,WAAO;AAAA,EACT;AACF;AAMA,MAAM,YAAY;AACX,SAAS,iBAAiB,OAAuB,OAAuB;AAC7E,MAAI,MAAM,UAAU,WAAW;AAC7B,UAAM,KAAK,GAAG,KAAK;AAAA,EACrB,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,SAAS;AAC1C,YAAM,KAAK,GAAG,KAAK;AAAA,IACrB;AAAA,EACF;AACF;AAEO,SAAS,aACd,OACA,OACA,YACQ;AACR,MAAI,MAAM;AACV,MAAI,OAAO,MAAM;AACjB,SAAO,MAAM,MAAM;AACjB,UAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,UAAM,aAAa,WAAW,MAAM,GAAG,GAAI,KAAK;AAChD,QAAI,aAAa,GAAG;AAClB,YAAM,MAAM;AAAA,IACd,WAAW,aAAa,GAAG;AACzB,aAAO;AAAA,IACT,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMO,MAAM,kBAAkB;AAAA,EAAxB,cAAA;AACL,SAAQ,gCAAgB,QAAA;AACxB,SAAQ,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,MAAM,OAAoB;AAExB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,cAAM,OAAO,IAAI,WAAW,CAAC;AAC7B,qBAAa,aAAa,KAAK,YAAY;AAC3C,oBAAY,YAAY;AAAA,MAC1B;AACA,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,KAAK,QAAQ;AAAA,IACzC;AACA,WAAO,KAAK,UAAU,IAAI,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAoB;AAC9B,QAAI,UAAU,KAAM,QAAO;AAC3B,QAAI,UAAU,OAAW,QAAO;AAChC,QAAI,OAAO,UAAU,iBAAiB,OAAO,OAAO,KAAK,CAAC;AAE1D,WAAO,OAAO,KAAK,MAAM,KAAK,CAAC;AAAA,EACjC;AACF;AAKO,MAAM,0BAA0B,IAAI,kBAAA;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/db-ivm",
3
3
  "description": "Incremental View Maintenance for TanStack DB based on Differential Dataflow",
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "dependencies": {
6
6
  "fractional-indexing": "^3.2.0",
7
7
  "sorted-btree": "^1.8.1"