@tanstack/db-ivm 0.1.25 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/dist/cjs/d2.d.cts +3 -3
- package/dist/cjs/graph.d.cts +2 -2
- package/dist/cjs/hashing/hash.cjs +58 -3
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/hashing/hash.d.cts +9 -0
- package/dist/cjs/hashing/index.d.cts +2 -2
- package/dist/cjs/hashing/murmur.cjs +26 -15
- package/dist/cjs/hashing/murmur.cjs.map +1 -1
- package/dist/cjs/hashing/murmur.d.cts +2 -0
- package/dist/cjs/index.cjs +25 -51
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +6 -6
- package/dist/cjs/indexes.cjs +52 -16
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/indexes.d.cts +42 -9
- package/dist/cjs/multiset.d.cts +8 -2
- package/dist/cjs/operators/concat.d.cts +2 -2
- package/dist/cjs/operators/consolidate.d.cts +2 -2
- package/dist/cjs/operators/distinct.d.cts +2 -2
- package/dist/cjs/operators/filter.d.cts +3 -3
- package/dist/cjs/operators/groupBy.cjs +33 -36
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.d.cts +6 -7
- package/dist/cjs/operators/groupedOrderBy.d.cts +8 -2
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +3 -9
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +24 -11
- package/dist/cjs/operators/index.d.cts +15 -21
- package/dist/cjs/operators/join.cjs +0 -20
- package/dist/cjs/operators/join.cjs.map +1 -1
- package/dist/cjs/operators/join.d.cts +29 -30
- package/dist/cjs/operators/map.d.cts +3 -3
- package/dist/cjs/operators/orderBy.cjs +2 -77
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +10 -24
- package/dist/cjs/operators/output.d.cts +3 -3
- package/dist/cjs/operators/pipe.d.cts +1 -1
- package/dist/cjs/operators/reduce.cjs +36 -5
- package/dist/cjs/operators/reduce.cjs.map +1 -1
- package/dist/cjs/operators/reduce.d.cts +38 -5
- package/dist/cjs/operators/tap.d.cts +3 -3
- package/dist/cjs/operators/topKArray.cjs +9 -0
- package/dist/cjs/operators/topKArray.cjs.map +1 -1
- package/dist/cjs/operators/topKArray.d.cts +17 -2
- package/dist/cjs/operators/topKState.cjs +3 -0
- package/dist/cjs/operators/topKState.cjs.map +1 -1
- package/dist/cjs/operators/topKState.d.cts +45 -8
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +5 -9
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +37 -9
- package/dist/cjs/types.d.cts +6 -3
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +4 -8
- package/dist/esm/hashing/hash.d.ts +9 -0
- package/dist/esm/hashing/hash.js +60 -5
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/hashing/murmur.d.ts +2 -0
- package/dist/esm/hashing/murmur.js +26 -15
- package/dist/esm/hashing/murmur.js.map +1 -1
- package/dist/esm/index.js +11 -37
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.d.ts +4 -1
- package/dist/esm/indexes.js +52 -16
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/operators/groupBy.d.ts +1 -5
- package/dist/esm/operators/groupBy.js +33 -36
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/groupedTopKWithFractionalIndex.d.ts +1 -6
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js +3 -9
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/index.d.ts +0 -6
- package/dist/esm/operators/join.d.ts +1 -26
- package/dist/esm/operators/join.js +1 -21
- package/dist/esm/operators/join.js.map +1 -1
- package/dist/esm/operators/orderBy.d.ts +2 -22
- package/dist/esm/operators/orderBy.js +3 -78
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/reduce.d.ts +11 -2
- package/dist/esm/operators/reduce.js +36 -5
- package/dist/esm/operators/reduce.js.map +1 -1
- package/dist/esm/operators/topKArray.d.ts +6 -0
- package/dist/esm/operators/topKArray.js +10 -1
- package/dist/esm/operators/topKArray.js.map +1 -1
- package/dist/esm/operators/topKState.d.ts +1 -0
- package/dist/esm/operators/topKState.js +3 -0
- package/dist/esm/operators/topKState.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +7 -3
- package/dist/esm/operators/topKWithFractionalIndex.js +6 -10
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/utils.d.ts +0 -7
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -5
- package/src/hashing/hash.ts +87 -5
- package/src/hashing/murmur.ts +31 -17
- package/src/indexes.ts +65 -16
- package/src/operators/groupBy.ts +48 -59
- package/src/operators/groupedTopKWithFractionalIndex.ts +4 -15
- package/src/operators/index.ts +0 -6
- package/src/operators/join.ts +1 -91
- package/src/operators/orderBy.ts +6 -141
- package/src/operators/reduce.ts +62 -5
- package/src/operators/topKArray.ts +15 -0
- package/src/operators/topKState.ts +7 -0
- package/src/operators/topKWithFractionalIndex.ts +15 -16
- package/src/utils.ts +0 -30
- package/dist/cjs/operators/count.cjs +0 -35
- package/dist/cjs/operators/count.cjs.map +0 -1
- package/dist/cjs/operators/count.d.cts +0 -13
- package/dist/cjs/operators/debug.cjs +0 -39
- package/dist/cjs/operators/debug.cjs.map +0 -1
- package/dist/cjs/operators/debug.d.cts +0 -16
- package/dist/cjs/operators/filterBy.cjs +0 -19
- package/dist/cjs/operators/filterBy.cjs.map +0 -1
- package/dist/cjs/operators/filterBy.d.cts +0 -9
- package/dist/cjs/operators/keying.cjs +0 -16
- package/dist/cjs/operators/keying.cjs.map +0 -1
- package/dist/cjs/operators/keying.d.cts +0 -19
- package/dist/cjs/operators/negate.cjs +0 -27
- package/dist/cjs/operators/negate.cjs.map +0 -1
- package/dist/cjs/operators/negate.d.cts +0 -13
- package/dist/cjs/operators/topK.cjs +0 -39
- package/dist/cjs/operators/topK.cjs.map +0 -1
- package/dist/cjs/operators/topK.d.cts +0 -31
- package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +0 -28
- package/dist/esm/operators/count.d.ts +0 -13
- package/dist/esm/operators/count.js +0 -35
- package/dist/esm/operators/count.js.map +0 -1
- package/dist/esm/operators/debug.d.ts +0 -16
- package/dist/esm/operators/debug.js +0 -39
- package/dist/esm/operators/debug.js.map +0 -1
- package/dist/esm/operators/filterBy.d.ts +0 -9
- package/dist/esm/operators/filterBy.js +0 -19
- package/dist/esm/operators/filterBy.js.map +0 -1
- package/dist/esm/operators/keying.d.ts +0 -19
- package/dist/esm/operators/keying.js +0 -16
- package/dist/esm/operators/keying.js.map +0 -1
- package/dist/esm/operators/negate.d.ts +0 -13
- package/dist/esm/operators/negate.js +0 -27
- package/dist/esm/operators/negate.js.map +0 -1
- package/dist/esm/operators/topK.d.ts +0 -31
- package/dist/esm/operators/topK.js +0 -39
- package/dist/esm/operators/topK.js.map +0 -1
- package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +0 -28
- package/src/operators/count.ts +0 -51
- package/src/operators/debug.ts +0 -57
- package/src/operators/filterBy.ts +0 -30
- package/src/operators/keying.ts +0 -34
- package/src/operators/negate.ts +0 -32
- package/src/operators/topK.ts +0 -91
- package/src/operators/topKWithFractionalIndexBTree.ts +0 -297
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.js","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, getSymbolIdentity, randomHash } from './murmur.js'\nimport { isTemporal } from './temporal.js'\nimport type { Hasher } from './murmur.js'\n\n/*\n * Implementation of structural hashing based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst TRUE = randomHash()\nconst FALSE = randomHash()\nconst NULL = randomHash()\nconst UNDEFINED = randomHash()\nconst KEY = randomHash()\nconst FUNCTIONS = randomHash()\n// Identity is distinct even if two random hash values happen to collide.\nconst DATE_MARKER = { hash: randomHash() }\nconst REGEXP_MARKER = { hash: randomHash() }\nconst OBJECT_MARKER = { hash: randomHash() }\nconst ARRAY_MARKER = { hash: randomHash() }\nconst MAP_MARKER = { hash: randomHash() }\nconst SET_MARKER = { hash: randomHash() }\nconst UINT8ARRAY_MARKER = { hash: randomHash() }\nconst TEMPORAL_MARKER = { hash: randomHash() }\n// Bound structural recursion and value visits. Shared acyclic subtrees are\n// cached; cycles are rejected rather than given context-dependent hashes.\nconst MAX_STRUCTURAL_HASH_WORK = 1_000_000\nconst MAX_STRUCTURAL_HASH_DEPTH = 768\n\n// Maximum byte length for Uint8Arrays to hash by content instead of reference\n// Arrays smaller than this will be hashed by content, allowing proper equality comparisons\n// for small arrays like ULIDs (16 bytes) while still avoiding performance costs for large arrays\nconst UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128\n\nconst hashCache = new WeakMap<object, number>()\nconst referenceValues = new WeakSet<object>()\n\n/** @internal Register a mutable handle before it enters a structural value. */\nexport function registerOpaqueHash(value: object): void {\n cachedReferenceHash(value)\n}\n\ntype HashContext = {\n activeObjects: Set<object>\n work: number\n pendingHashes: Map<object, number>\n}\n\nexport function hash(input: any): number {\n const hasher = new MurmurHashStream()\n updateHasher(hasher, input)\n return hasher.digest()\n}\n\nfunction hashObject(input: object, context: HashContext): number {\n if (context.activeObjects.size >= MAX_STRUCTURAL_HASH_DEPTH) {\n throw new RangeError(\n `Value is too complex to hash safely: structural depth`,\n )\n }\n\n context.activeObjects.add(input)\n\n let valueHash: number | undefined\n try {\n const [marker, header, body] = objectParts(input)\n const hasher = new MurmurHashStream()\n hasher.update(marker.hash)\n // Header values of a type with a body (array length, RegExp fields)\n // count toward the work cap; Date, binary, and Temporal headers do not.\n for (const value of header)\n if (body) updateHasher(hasher, value, context)\n else hasher.update(value as string | number)\n if (body) {\n // Sorted string keys, then enumerable symbol keys by identity.\n const keys: Array<string | symbol> = Object.keys(body).sort(keySort)\n keys.push(\n ...Object.getOwnPropertySymbols(body)\n .filter((key) =>\n Object.prototype.propertyIsEnumerable.call(body, key),\n )\n .sort(\n (left, right) => getSymbolIdentity(left) - getSymbolIdentity(right),\n ),\n )\n for (const key of keys) {\n hasher.update(KEY)\n hasher.update(key)\n updateHasher(hasher, body[key as keyof typeof body], context)\n }\n }\n valueHash = hasher.digest()\n } finally {\n context.activeObjects.delete(input)\n }\n\n context.pendingHashes.set(input, valueHash)\n return valueHash\n}\n\n/**\n * The one type dispatch shared by structural hashing and equality: a type\n * marker, primitive header values, and an optional object whose enumerable\n * own properties are compared structurally.\n */\ntype ObjectParts = [\n marker: Readonly<{ hash: number }>,\n // A RegExp `lastIndex` can hold any value; the other headers are strings\n // or numbers.\n header: ReadonlyArray<unknown>,\n body?: object,\n]\n\nfunction objectParts(input: object): ObjectParts {\n if (input instanceof Date) {\n const time = input.getTime()\n // Invalid dates are equal to each other; headers compare with `===`.\n return [DATE_MARKER, [Number.isNaN(time) ? `invalid` : time]]\n }\n if (isBinaryValue(input))\n return [\n UINT8ARRAY_MARKER,\n [String.fromCharCode.apply(null, input as unknown as Array<number>)],\n ]\n if (isTemporal(input))\n return [TEMPORAL_MARKER, [input[Symbol.toStringTag], input.toString()]]\n if (input instanceof RegExp)\n return [REGEXP_MARKER, [input.source, input.flags, input.lastIndex], input]\n if (input instanceof Map) return [MAP_MARKER, [], [...input.entries()]]\n if (input instanceof Set) return [SET_MARKER, [], [...input.values()]]\n return input instanceof Array\n ? [ARRAY_MARKER, [input.length], input]\n : [OBJECT_MARKER, [], input]\n}\n\nfunction updateHasher(\n hasher: Hasher,\n input: unknown,\n context?: HashContext,\n): void {\n if (context && ++context.work > MAX_STRUCTURAL_HASH_WORK) {\n throw new RangeError(`Value is too complex to hash safely: structural work`)\n }\n if (input === null) {\n hasher.update(NULL)\n return\n }\n switch (typeof input) {\n case `undefined`:\n hasher.update(UNDEFINED)\n return\n case `boolean`:\n hasher.update(input ? TRUE : FALSE)\n return\n case `number`:\n // Normalize NaNs and -0\n hasher.update(isNaN(input) ? NaN : input === 0 ? 0 : input)\n return\n case `bigint`:\n case `string`:\n case `symbol`:\n hasher.update(input)\n return\n case `object`:\n hasher.update(getCachedHash(input, context))\n return\n case `function`:\n // Functions are assigned a globally unique ID\n // and that ID is cached in the weak map\n hasher.update(cachedReferenceHash(input))\n }\n}\n\nfunction getCachedHash(input: object, context?: HashContext): number {\n // Active objects are neither cached nor pending, so the cycle check below\n // still sees every back-reference.\n const cached = hashCache.get(input) ?? context?.pendingHashes.get(input)\n if (cached !== undefined) return cached\n\n // Opaque leaves cannot contain structural back-references. Resolve them\n // before entering structural recursion, even when they have user properties.\n if (isReferenceHashedObject(input)) return cachedReferenceHash(input)\n\n if (context) {\n if (context.activeObjects.has(input)) {\n throw new TypeError(`Cannot hash cyclic structural values`)\n }\n return hashObject(input, context)\n }\n\n // Only an uncached structural root needs graph traversal state. Commit its\n // cache entries after success so a failed traversal cannot poison retries.\n const root: HashContext = {\n activeObjects: new Set(),\n work: 0,\n pendingHashes: new Map(),\n }\n const result = hashObject(input, root)\n for (const [object, valueHash] of root.pendingHashes) {\n hashCache.set(object, valueHash)\n }\n return result\n}\n\nfunction isReferenceHashedObject(input: object): boolean {\n return (\n referenceValues.has(input) ||\n (typeof File !== `undefined` && input instanceof File) ||\n (isBinaryValue(input) &&\n input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD)\n )\n}\n\n// Buffer can come from another realm than Uint8Array (for example under jsdom).\nfunction isBinaryValue(input: object): input is Uint8Array {\n return (\n (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n input instanceof Uint8Array\n )\n}\n\n/** @internal Compare immutable structural values without computing a digest.\n * Pair memoization also permits cyclic values that structural hashing rejects.\n * Reference-valued leaves remain opaque, including registered mutable handles.\n */\nexport function equalHashValues(left: unknown, right: unknown): boolean {\n const compared = new Map<object, Set<object>>()\n const keys = (value: object) =>\n Reflect.ownKeys(value).filter((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key),\n )\n function equal(a: unknown, b: unknown): boolean {\n if (a === b || (Number.isNaN(a) && Number.isNaN(b))) return true\n if (\n a === null ||\n b === null ||\n typeof a !== `object` ||\n typeof b !== `object` ||\n isReferenceHashedObject(a) ||\n isReferenceHashedObject(b)\n )\n return false\n // Arrays from another realm take the object marker but keep this check.\n if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length)\n return false\n\n // Revisited pairs close cycles and avoid expanding shared subtrees. Check\n // before objectParts copies Map or Set entries; record only pairs with a\n // body, after their markers and headers match.\n const peers = compared.get(a)\n if (peers?.has(b)) return true\n const [aMarker, aHeader, aBody] = objectParts(a)\n const [bMarker, bHeader, bBody] = objectParts(b)\n if (\n aMarker !== bMarker ||\n !aHeader.every((value, index) => value === bHeader[index])\n )\n return false\n if (!aBody || !bBody) return true\n if (peers) peers.add(b)\n else compared.set(a, new Set([b]))\n const aKeys = keys(aBody)\n return (\n aKeys.length === keys(bBody).length &&\n aKeys.every(\n (key) =>\n Object.prototype.propertyIsEnumerable.call(bBody, key) &&\n equal(aBody[key as keyof object], bBody[key as keyof object]),\n )\n )\n }\n return equal(left, right)\n}\n\nlet nextRefId = 1\nfunction cachedReferenceHash(fn: object): number {\n let valueHash = hashCache.get(fn)\n if (valueHash === undefined) {\n valueHash = nextRefId ^ FUNCTIONS\n nextRefId++\n hashCache.set(fn, valueHash)\n referenceValues.add(fn)\n }\n return valueHash\n}\n\n/**\n * Strings sorted lexicographically.\n */\nfunction keySort(a: string, b: string): number {\n return a.localeCompare(b)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hash.js","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, getSymbolIdentity, randomHash } from './murmur.js'\nimport { isTemporal } from './temporal.js'\nimport type { Hasher } from './murmur.js'\n\n/*\n * Implementation of structural hashing based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst TRUE = randomHash()\nconst FALSE = randomHash()\nconst NULL = randomHash()\nconst UNDEFINED = randomHash()\nconst KEY = randomHash()\nconst FUNCTIONS = randomHash()\n// Identity is distinct even if two random hash values happen to collide.\nconst DATE_MARKER = { hash: randomHash() }\nconst REGEXP_MARKER = { hash: randomHash() }\nconst OBJECT_MARKER = { hash: randomHash() }\nconst ARRAY_MARKER = { hash: randomHash() }\nconst MAP_MARKER = { hash: randomHash() }\nconst SET_MARKER = { hash: randomHash() }\nconst UINT8ARRAY_MARKER = { hash: randomHash() }\nconst TEMPORAL_MARKER = { hash: randomHash() }\n// Bound structural recursion and value visits. Shared acyclic subtrees are\n// cached; cycles are rejected rather than given context-dependent hashes.\nconst MAX_STRUCTURAL_HASH_WORK = 1_000_000\nconst MAX_STRUCTURAL_HASH_DEPTH = 768\n\n// Maximum byte length for Uint8Arrays to hash by content instead of reference\n// Arrays smaller than this will be hashed by content, allowing proper equality comparisons\n// for small arrays like ULIDs (16 bytes) while still avoiding performance costs for large arrays\nconst UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128\n\nconst hashCache = new WeakMap<object, number>()\nconst referenceValues = new WeakSet<object>()\n\n/** @internal Register a mutable handle before it enters a structural value. */\nexport function registerOpaqueHash(value: object): void {\n cachedReferenceHash(value)\n}\n\ntype HashContext = {\n activeObjects: Set<object>\n work: number\n pendingHashes: Map<object, number>\n}\n\nexport function hash(input: any): number {\n const hasher = new MurmurHashStream()\n updateHasher(hasher, input)\n return hasher.digest()\n}\n\nfunction hashObject(input: object, context: HashContext): number {\n if (context.activeObjects.size >= MAX_STRUCTURAL_HASH_DEPTH) {\n throw new RangeError(\n `Value is too complex to hash safely: structural depth`,\n )\n }\n\n context.activeObjects.add(input)\n\n let valueHash: number | undefined\n try {\n const [marker, header, body] = objectParts(input)\n const hasher = new MurmurHashStream()\n hasher.update(marker.hash)\n // Header values of a type with a body (array length, RegExp fields)\n // count toward the work cap; Date, binary, and Temporal headers do not.\n for (const value of header)\n if (body) updateHasher(hasher, value, context)\n else hasher.update(value as string | number)\n if (body) {\n // String keys in UTF-16 code-unit order, then enumerable symbol keys by\n // identity. Code-unit order is total; locale collation can rank two\n // distinct keys as equal and leave their insertion order in the hash.\n const keys: Array<string | symbol> = Object.keys(body)\n // A dense array with no other keys lists its indices in one order for\n // every equal array: integer keys come first, so the last key is the\n // last index. Anything else sorts.\n const denseArray =\n Array.isArray(body) &&\n keys.length === body.length &&\n (keys.length === 0 || keys[keys.length - 1] === String(body.length - 1))\n if (!denseArray) keys.sort()\n keys.push(\n ...Object.getOwnPropertySymbols(body)\n .filter((key) =>\n Object.prototype.propertyIsEnumerable.call(body, key),\n )\n .sort(\n (left, right) => getSymbolIdentity(left) - getSymbolIdentity(right),\n ),\n )\n for (const key of keys) {\n hasher.update(KEY)\n hasher.update(key)\n updateHasher(hasher, body[key as keyof typeof body], context)\n }\n }\n valueHash = hasher.digest()\n } finally {\n context.activeObjects.delete(input)\n }\n\n context.pendingHashes.set(input, valueHash)\n return valueHash\n}\n\n/**\n * The one type dispatch shared by structural hashing and equality: a type\n * marker, primitive header values, and an optional object whose enumerable\n * own properties are compared structurally.\n */\ntype ObjectParts = [\n marker: Readonly<{ hash: number }>,\n // A RegExp `lastIndex` can hold any value; the other headers are strings\n // or numbers.\n header: ReadonlyArray<unknown>,\n body?: object,\n]\n\nfunction objectParts(input: object): ObjectParts {\n if (input instanceof Date) {\n const time = input.getTime()\n // Invalid dates are equal to each other; headers compare with `===`.\n return [DATE_MARKER, [Number.isNaN(time) ? `invalid` : time]]\n }\n if (isBinaryValue(input))\n return [\n UINT8ARRAY_MARKER,\n [String.fromCharCode.apply(null, input as unknown as Array<number>)],\n ]\n if (isTemporal(input))\n return [TEMPORAL_MARKER, [input[Symbol.toStringTag], input.toString()]]\n if (input instanceof RegExp)\n return [REGEXP_MARKER, [input.source, input.flags, input.lastIndex], input]\n if (input instanceof Map) return [MAP_MARKER, [], [...input.entries()]]\n if (input instanceof Set) return [SET_MARKER, [], [...input.values()]]\n return input instanceof Array\n ? [ARRAY_MARKER, [input.length], input]\n : [OBJECT_MARKER, [], input]\n}\n\nfunction updateHasher(\n hasher: Hasher,\n input: unknown,\n context?: HashContext,\n): void {\n if (context && ++context.work > MAX_STRUCTURAL_HASH_WORK) {\n throw new RangeError(`Value is too complex to hash safely: structural work`)\n }\n if (input === null) {\n hasher.update(NULL)\n return\n }\n switch (typeof input) {\n case `undefined`:\n hasher.update(UNDEFINED)\n return\n case `boolean`:\n hasher.update(input ? TRUE : FALSE)\n return\n case `number`:\n // Normalize NaNs and -0\n hasher.update(isNaN(input) ? NaN : input === 0 ? 0 : input)\n return\n case `bigint`:\n case `string`:\n case `symbol`:\n hasher.update(input)\n return\n case `object`:\n hasher.update(getCachedHash(input, context))\n return\n case `function`:\n // Functions are assigned a globally unique ID\n // and that ID is cached in the weak map\n hasher.update(cachedReferenceHash(input))\n }\n}\n\nfunction getCachedHash(input: object, context?: HashContext): number {\n // Active objects are neither cached nor pending, so the cycle check below\n // still sees every back-reference.\n const cached = hashCache.get(input) ?? context?.pendingHashes.get(input)\n if (cached !== undefined) return cached\n\n // Opaque leaves cannot contain structural back-references. Resolve them\n // before entering structural recursion, even when they have user properties.\n if (isReferenceHashedObject(input)) return cachedReferenceHash(input)\n\n if (context) {\n if (context.activeObjects.has(input)) {\n throw new TypeError(`Cannot hash cyclic structural values`)\n }\n return hashObject(input, context)\n }\n\n // Only an uncached structural root needs graph traversal state. Commit its\n // cache entries after success so a failed traversal cannot poison retries.\n const root: HashContext = {\n activeObjects: new Set(),\n work: 0,\n pendingHashes: new Map(),\n }\n const result = hashObject(input, root)\n for (const [object, valueHash] of root.pendingHashes) {\n hashCache.set(object, valueHash)\n }\n return result\n}\n\nfunction isReferenceHashedObject(input: object): boolean {\n return (\n referenceValues.has(input) ||\n (typeof File !== `undefined` && input instanceof File) ||\n (isBinaryValue(input) &&\n input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD)\n )\n}\n\n// Buffer can come from another realm than Uint8Array (for example under jsdom).\nfunction isBinaryValue(input: object): input is Uint8Array {\n return (\n (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n input instanceof Uint8Array\n )\n}\n\n/** @internal Compare immutable structural values without computing a digest.\n * Pair memoization also permits cyclic values that structural hashing rejects.\n * Reference-valued leaves remain opaque, including registered mutable handles.\n */\nexport function equalHashValues(left: unknown, right: unknown): boolean {\n const compared = new Map<object, Set<object>>()\n const keys = (value: object) =>\n Reflect.ownKeys(value).filter((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key),\n )\n function equal(a: unknown, b: unknown): boolean {\n if (a === b || (Number.isNaN(a) && Number.isNaN(b))) return true\n if (\n a === null ||\n b === null ||\n typeof a !== `object` ||\n typeof b !== `object` ||\n isReferenceHashedObject(a) ||\n isReferenceHashedObject(b)\n )\n return false\n // Arrays from another realm take the object marker but keep this check.\n if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length)\n return false\n\n // Revisited pairs close cycles and avoid expanding shared subtrees. Check\n // before objectParts copies Map or Set entries; record only pairs with a\n // body, after their markers and headers match.\n const peers = compared.get(a)\n if (peers?.has(b)) return true\n const [aMarker, aHeader, aBody] = objectParts(a)\n const [bMarker, bHeader, bBody] = objectParts(b)\n if (\n aMarker !== bMarker ||\n !aHeader.every((value, index) => value === bHeader[index])\n )\n return false\n if (!aBody || !bBody) return true\n if (peers) peers.add(b)\n else compared.set(a, new Set([b]))\n const aKeys = keys(aBody)\n return (\n aKeys.length === keys(bBody).length &&\n aKeys.every(\n (key) =>\n Object.prototype.propertyIsEnumerable.call(bBody, key) &&\n equal(aBody[key as keyof object], bBody[key as keyof object]),\n )\n )\n }\n return equal(left, right)\n}\n\nlet nextRefId = 1\nfunction cachedReferenceHash(fn: object): number {\n let valueHash = hashCache.get(fn)\n if (valueHash === undefined) {\n valueHash = nextRefId ^ FUNCTIONS\n nextRefId++\n hashCache.set(fn, valueHash)\n referenceValues.add(fn)\n }\n return valueHash\n}\n\n// One punctuation tag per objectParts marker, so values of different kinds\n// never share an identity (and no tag reads as an identifier in this source).\nconst MARKER_TAGS = new Map<{ hash: number }, string>([\n [ARRAY_MARKER, `[`],\n [OBJECT_MARKER, `{`],\n [DATE_MARKER, `<`],\n [REGEXP_MARKER, `/`],\n [MAP_MARKER, `&`],\n [SET_MARKER, `*`],\n [UINT8ARRAY_MARKER, `^`],\n [TEMPORAL_MARKER, `?`],\n])\n\n// Nesting a structural identity follows: a min or max input is an array of\n// exact keys, and an exact key can hold a reference tuple.\nconst MAX_IDENTITY_DEPTH = 4\n\n/**\n * @internal An exact identity string for a value, or undefined when it has\n * no cheap one. Two values that `equalHashValues` treats as equal get the\n * same result, both undefined or one string; two that it treats as different\n * never share a string. The decision uses only what equalHashValues reads:\n * the type, the `objectParts` marker and headers, and enumerable own keys. groupBy uses it\n * to merge equal contributions without hashing them.\n */\nexport function structuralIdentity(\n value: unknown,\n depth = 0,\n): string | undefined {\n switch (typeof value) {\n case `number`:\n // The hash treats -0 as 0 and every NaN as one value.\n return `#${value === 0 ? 0 : value}`\n case `string`:\n return `\"${value.length}:${value}`\n case `boolean`:\n return value ? `+` : `-`\n case `undefined`:\n return `~`\n case `bigint`:\n return `%${value}`\n case `symbol`:\n return `$${getSymbolIdentity(value)}`\n case `object`: {\n if (value === null) return `!`\n if (depth >= MAX_IDENTITY_DEPTH || isReferenceHashedObject(value))\n return undefined\n const [marker, header, body] = objectParts(value)\n // equalHashValues compares headers with ===, so a NaN header is never\n // equal to another value's, and gets no identity.\n let head = MARKER_TAGS.get(marker)!\n for (const part of header) {\n if (Number.isNaN(part)) return undefined\n head += structuralIdentity(part, depth + 1)\n }\n if (!body) return head\n if (\n Object.getOwnPropertySymbols(body).some((key) =>\n Object.prototype.propertyIsEnumerable.call(body, key),\n )\n )\n return undefined\n let parts = ``\n for (const key of Object.keys(body).sort()) {\n const part = structuralIdentity(\n (body as Record<string, unknown>)[key],\n depth + 1,\n )\n if (part === undefined) return undefined\n parts += `${key.length}:${key}=${part}|`\n }\n // An array's header is its length, so a hole differs from undefined.\n return `${head}:${parts.length}:${parts}`\n }\n default:\n return undefined\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,8 @@ export declare class MurmurHashStream implements Hasher {
|
|
|
22
22
|
private carryBytes;
|
|
23
23
|
private _mix;
|
|
24
24
|
writeByte(byte: number): void;
|
|
25
|
+
/** Write four bytes, low byte first, as four `writeByte` calls would. */
|
|
26
|
+
private writeWord;
|
|
25
27
|
update(chunk: symbol | string | number | bigint): void;
|
|
26
28
|
digest(): number;
|
|
27
29
|
}
|
|
@@ -36,9 +36,7 @@ function createSymbolIdStore() {
|
|
|
36
36
|
function randomHash() {
|
|
37
37
|
return Math.random() * (2 ** 31 - 1) >>> 0;
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
const dv = new DataView(buf);
|
|
41
|
-
const u8 = new Uint8Array(buf);
|
|
39
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
42
40
|
class MurmurHashStream {
|
|
43
41
|
constructor() {
|
|
44
42
|
this.hash = RANDOM_SEED;
|
|
@@ -64,6 +62,17 @@ class MurmurHashStream {
|
|
|
64
62
|
this.carryBytes = 0;
|
|
65
63
|
}
|
|
66
64
|
}
|
|
65
|
+
/** Write four bytes, low byte first, as four `writeByte` calls would. */
|
|
66
|
+
writeWord(word) {
|
|
67
|
+
this.length += 4;
|
|
68
|
+
if (this.carryBytes === 0) {
|
|
69
|
+
this._mix(word >>> 0);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const shift = 8 * this.carryBytes;
|
|
73
|
+
this._mix((this.carry | word << shift) >>> 0);
|
|
74
|
+
this.carry = word >>> 32 - shift;
|
|
75
|
+
}
|
|
67
76
|
update(chunk) {
|
|
68
77
|
switch (typeof chunk) {
|
|
69
78
|
case `symbol`: {
|
|
@@ -73,22 +82,24 @@ class MurmurHashStream {
|
|
|
73
82
|
}
|
|
74
83
|
case `string`:
|
|
75
84
|
this.update(STRING_MARKER);
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
{
|
|
86
|
+
const pairs = chunk.length - chunk.length % 2;
|
|
87
|
+
for (let i = 0; i < pairs; i += 2) {
|
|
88
|
+
this.writeWord(
|
|
89
|
+
chunk.charCodeAt(i) | chunk.charCodeAt(i + 1) << 16
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (pairs < chunk.length) {
|
|
93
|
+
const code = chunk.charCodeAt(pairs);
|
|
94
|
+
this.writeByte(code & 255);
|
|
95
|
+
this.writeByte(code >>> 8 & 255);
|
|
96
|
+
}
|
|
80
97
|
}
|
|
81
98
|
return;
|
|
82
99
|
case `number`:
|
|
83
100
|
dv.setFloat64(0, chunk, true);
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
86
|
-
this.writeByte(u8[2]);
|
|
87
|
-
this.writeByte(u8[3]);
|
|
88
|
-
this.writeByte(u8[4]);
|
|
89
|
-
this.writeByte(u8[5]);
|
|
90
|
-
this.writeByte(u8[6]);
|
|
91
|
-
this.writeByte(u8[7]);
|
|
101
|
+
this.writeWord(dv.getUint32(0, true));
|
|
102
|
+
this.writeWord(dv.getUint32(4, true));
|
|
92
103
|
return;
|
|
93
104
|
case `bigint`: {
|
|
94
105
|
let value = chunk;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"murmur.js","sources":["../../../src/hashing/murmur.ts"],"sourcesContent":["/*\n * Implementation of murmur hash based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst RANDOM_SEED = randomHash()\nconst STRING_MARKER = randomHash()\nconst BIG_INT_MARKER = randomHash()\nconst NEG_BIG_INT_MARKER = randomHash()\nconst SYMBOL_MARKER = randomHash()\n\ntype SymbolIdStore = {\n get: (key: symbol) => number | undefined\n set: (key: symbol, value: number) => unknown\n}\n\nconst symbolIds = createSymbolIdStore()\nconst registeredSymbolIds = new Map<string, number>()\nlet nextSymbolId = 0\n\nexport function getSymbolIdentity(symbol: symbol): number {\n const registeredKey = Symbol.keyFor(symbol)\n if (registeredKey !== undefined) {\n let id = registeredSymbolIds.get(registeredKey)\n if (id === undefined) {\n id = ++nextSymbolId\n registeredSymbolIds.set(registeredKey, id)\n }\n return id\n }\n\n let id = symbolIds.get(symbol)\n if (id === undefined) {\n id = ++nextSymbolId\n symbolIds.set(symbol, id)\n }\n return id\n}\n\nfunction createSymbolIdStore(): SymbolIdStore {\n const weakIds = new WeakMap<object, number>() as unknown as SymbolIdStore\n const probe = Symbol()\n\n try {\n weakIds.set(probe, 0)\n if (weakIds.get(probe) === 0) return weakIds\n } catch {\n // Older runtimes reject symbols as weak keys. Retain them rather than\n // merge distinct symbols and corrupt differential state.\n }\n\n return new Map<symbol, number>()\n}\n\nexport type Hash = number\n\nexport function randomHash() {\n return (Math.random() * (2 ** 31 - 1)) >>> 0\n}\n\nexport interface Hasher {\n update: (val: symbol | string | number | bigint) => void\n digest: () => number\n}\n\n// Allocate
|
|
1
|
+
{"version":3,"file":"murmur.js","sources":["../../../src/hashing/murmur.ts"],"sourcesContent":["/*\n * Implementation of murmur hash based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst RANDOM_SEED = randomHash()\nconst STRING_MARKER = randomHash()\nconst BIG_INT_MARKER = randomHash()\nconst NEG_BIG_INT_MARKER = randomHash()\nconst SYMBOL_MARKER = randomHash()\n\ntype SymbolIdStore = {\n get: (key: symbol) => number | undefined\n set: (key: symbol, value: number) => unknown\n}\n\nconst symbolIds = createSymbolIdStore()\nconst registeredSymbolIds = new Map<string, number>()\nlet nextSymbolId = 0\n\nexport function getSymbolIdentity(symbol: symbol): number {\n const registeredKey = Symbol.keyFor(symbol)\n if (registeredKey !== undefined) {\n let id = registeredSymbolIds.get(registeredKey)\n if (id === undefined) {\n id = ++nextSymbolId\n registeredSymbolIds.set(registeredKey, id)\n }\n return id\n }\n\n let id = symbolIds.get(symbol)\n if (id === undefined) {\n id = ++nextSymbolId\n symbolIds.set(symbol, id)\n }\n return id\n}\n\nfunction createSymbolIdStore(): SymbolIdStore {\n const weakIds = new WeakMap<object, number>() as unknown as SymbolIdStore\n const probe = Symbol()\n\n try {\n weakIds.set(probe, 0)\n if (weakIds.get(probe) === 0) return weakIds\n } catch {\n // Older runtimes reject symbols as weak keys. Retain them rather than\n // merge distinct symbols and corrupt differential state.\n }\n\n return new Map<symbol, number>()\n}\n\nexport type Hash = number\n\nexport function randomHash() {\n return (Math.random() * (2 ** 31 - 1)) >>> 0\n}\n\nexport interface Hasher {\n update: (val: symbol | string | number | bigint) => void\n digest: () => number\n}\n\n// Allocate this once, outside the hot path\nconst dv = new DataView(new ArrayBuffer(8))\n\n/**\n * This implementation of Murmur hash uses a random initial seed and random markers.\n * This means that hashes aren't deterministic across app restarts.\n * This is intentional in the composites polyfill to be resistent to hash-flooding attacks\n * where malicious users would precompute lots of different objects whose hashes collide with each other.\n *\n * Currently, for ts/db-ivm this is fine because we don't persist client state.\n * However, when we will introduce persistence we will either need to store the seeds or remove the randomness\n * to ensure deterministic hashes across app restarts.\n */\nexport class MurmurHashStream implements Hasher {\n private hash: number = RANDOM_SEED\n private length = 0\n private carry = 0\n private carryBytes = 0\n\n private _mix(k1: number): void {\n k1 = Math.imul(k1, 0xcc9e2d51)\n k1 = (k1 << 15) | (k1 >>> 17)\n k1 = Math.imul(k1, 0x1b873593)\n this.hash ^= k1\n this.hash = (this.hash << 13) | (this.hash >>> 19)\n this.hash = Math.imul(this.hash, 5) + 0xe6546b64\n }\n\n writeByte(byte: number): void {\n this.carry |= (byte & 0xff) << (8 * this.carryBytes)\n this.carryBytes++\n this.length++\n\n if (this.carryBytes === 4) {\n this._mix(this.carry >>> 0)\n this.carry = 0\n this.carryBytes = 0\n }\n }\n\n /** Write four bytes, low byte first, as four `writeByte` calls would. */\n private writeWord(word: number): void {\n this.length += 4\n if (this.carryBytes === 0) {\n this._mix(word >>> 0)\n return\n }\n // The carry's bytes come first. The word's low bytes complete that block,\n // and its high bytes become the new carry, which keeps its byte count.\n const shift = 8 * this.carryBytes\n this._mix((this.carry | (word << shift)) >>> 0)\n this.carry = word >>> (32 - shift)\n }\n\n update(chunk: symbol | string | number | bigint): void {\n switch (typeof chunk) {\n case `symbol`: {\n this.update(SYMBOL_MARKER)\n this.update(getSymbolIdentity(chunk))\n return\n }\n case `string`:\n this.update(STRING_MARKER)\n {\n // Each code unit is two bytes, so a pair of them is one word.\n const pairs = chunk.length - (chunk.length % 2)\n for (let i = 0; i < pairs; i += 2) {\n this.writeWord(\n chunk.charCodeAt(i) | (chunk.charCodeAt(i + 1) << 16),\n )\n }\n if (pairs < chunk.length) {\n const code = chunk.charCodeAt(pairs)\n this.writeByte(code & 0xff)\n this.writeByte((code >>> 8) & 0xff)\n }\n }\n return\n case `number`:\n dv.setFloat64(0, chunk, true) // fixed little-endian\n this.writeWord(dv.getUint32(0, true))\n this.writeWord(dv.getUint32(4, true))\n return\n case `bigint`: {\n let value = chunk\n if (value < 0n) {\n value = -value\n this.update(NEG_BIG_INT_MARKER)\n } else {\n this.update(BIG_INT_MARKER)\n }\n while (value > 0n) {\n this.writeByte(Number(value & 0xffn))\n value >>= 8n\n }\n if (chunk === 0n) this.writeByte(0)\n return\n }\n default:\n throw new TypeError(`Unsupported input type: ${typeof chunk}`)\n }\n }\n\n digest(): number {\n if (this.carryBytes > 0) {\n let k1 = this.carry >>> 0\n k1 = Math.imul(k1, 0xcc9e2d51)\n k1 = (k1 << 15) | (k1 >>> 17)\n k1 = Math.imul(k1, 0x1b873593)\n this.hash ^= k1\n }\n\n this.hash ^= this.length\n this.hash ^= this.hash >>> 16\n this.hash = Math.imul(this.hash, 0x85ebca6b)\n this.hash ^= this.hash >>> 13\n this.hash = Math.imul(this.hash, 0xc2b2ae35)\n this.hash ^= this.hash >>> 16\n\n return this.hash >>> 0\n }\n}\n"],"names":["id"],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,81 +2,55 @@ import { D2, RootStreamBuilder, StreamBuilder } from "./d2.js";
|
|
|
2
2
|
import { MultiSet } from "./multiset.js";
|
|
3
3
|
import { compareKeys, serializeValue } from "./utils.js";
|
|
4
4
|
import { registerOpaqueHash } from "./hashing/hash.js";
|
|
5
|
-
import { groupBy, groupByOperators } from "./operators/groupBy.js";
|
|
6
|
-
import { pipe } from "./operators/pipe.js";
|
|
7
|
-
import { MapOperator, map } from "./operators/map.js";
|
|
8
|
-
import { TapOperator, tap } from "./operators/tap.js";
|
|
9
|
-
import { FilterOperator, filter } from "./operators/filter.js";
|
|
10
|
-
import { NegateOperator, negate } from "./operators/negate.js";
|
|
11
5
|
import { ConcatOperator, concat } from "./operators/concat.js";
|
|
12
|
-
import { DebugOperator, debug } from "./operators/debug.js";
|
|
13
|
-
import { OutputOperator, output } from "./operators/output.js";
|
|
14
6
|
import { ConsolidateOperator, consolidate } from "./operators/consolidate.js";
|
|
15
|
-
import { JoinOperator, antiJoin, fullJoin, innerJoin, join, leftJoin, rightJoin } from "./operators/join.js";
|
|
16
|
-
import { ReduceOperator, reduce } from "./operators/reduce.js";
|
|
17
|
-
import { CountOperator, count } from "./operators/count.js";
|
|
18
7
|
import { DistinctOperator, distinct } from "./operators/distinct.js";
|
|
19
|
-
import {
|
|
20
|
-
import { topK, topKWithIndex } from "./operators/topK.js";
|
|
21
|
-
import { TopKWithFractionalIndexOperator, topKWithFractionalIndex } from "./operators/topKWithFractionalIndex.js";
|
|
8
|
+
import { FilterOperator, filter } from "./operators/filter.js";
|
|
22
9
|
import { GroupedTopKWithFractionalIndexOperator, groupedTopKWithFractionalIndex } from "./operators/groupedTopKWithFractionalIndex.js";
|
|
23
|
-
import {
|
|
10
|
+
import { JoinOperator, join } from "./operators/join.js";
|
|
11
|
+
import { MapOperator, map } from "./operators/map.js";
|
|
12
|
+
import { OutputOperator, output } from "./operators/output.js";
|
|
13
|
+
import { ReduceOperator, reduce } from "./operators/reduce.js";
|
|
14
|
+
import { TapOperator, tap } from "./operators/tap.js";
|
|
15
|
+
import { TopKWithFractionalIndexOperator, topKWithFractionalIndex } from "./operators/topKWithFractionalIndex.js";
|
|
16
|
+
import { groupBy, groupByOperators } from "./operators/groupBy.js";
|
|
24
17
|
import { groupedOrderByWithFractionalIndex } from "./operators/groupedOrderBy.js";
|
|
25
|
-
import {
|
|
18
|
+
import { orderByWithFractionalIndex } from "./operators/orderBy.js";
|
|
19
|
+
import { pipe } from "./operators/pipe.js";
|
|
26
20
|
export {
|
|
27
21
|
ConcatOperator,
|
|
28
22
|
ConsolidateOperator,
|
|
29
|
-
CountOperator,
|
|
30
23
|
D2,
|
|
31
|
-
DebugOperator,
|
|
32
24
|
DistinctOperator,
|
|
33
25
|
FilterOperator,
|
|
34
26
|
GroupedTopKWithFractionalIndexOperator,
|
|
35
27
|
JoinOperator,
|
|
36
28
|
MapOperator,
|
|
37
29
|
MultiSet,
|
|
38
|
-
NegateOperator,
|
|
39
30
|
OutputOperator,
|
|
40
31
|
ReduceOperator,
|
|
41
32
|
RootStreamBuilder,
|
|
42
33
|
StreamBuilder,
|
|
43
34
|
TapOperator,
|
|
44
35
|
TopKWithFractionalIndexOperator,
|
|
45
|
-
antiJoin,
|
|
46
36
|
compareKeys,
|
|
47
37
|
concat,
|
|
48
38
|
consolidate,
|
|
49
|
-
count,
|
|
50
|
-
debug,
|
|
51
39
|
distinct,
|
|
52
40
|
filter,
|
|
53
|
-
filterBy,
|
|
54
|
-
fullJoin,
|
|
55
41
|
groupBy,
|
|
56
42
|
groupByOperators,
|
|
57
43
|
groupedOrderByWithFractionalIndex,
|
|
58
44
|
groupedTopKWithFractionalIndex,
|
|
59
|
-
innerJoin,
|
|
60
45
|
join,
|
|
61
|
-
keyBy,
|
|
62
|
-
leftJoin,
|
|
63
46
|
map,
|
|
64
|
-
negate,
|
|
65
|
-
orderBy,
|
|
66
47
|
orderByWithFractionalIndex,
|
|
67
|
-
orderByWithFractionalIndexBase,
|
|
68
|
-
orderByWithIndex,
|
|
69
48
|
output,
|
|
70
49
|
pipe,
|
|
71
50
|
reduce,
|
|
72
51
|
registerOpaqueHash,
|
|
73
|
-
rekey,
|
|
74
|
-
rightJoin,
|
|
75
52
|
serializeValue,
|
|
76
53
|
tap,
|
|
77
|
-
|
|
78
|
-
topKWithFractionalIndex,
|
|
79
|
-
topKWithIndex,
|
|
80
|
-
unkey
|
|
54
|
+
topKWithFractionalIndex
|
|
81
55
|
};
|
|
82
56
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/indexes.d.ts
CHANGED
|
@@ -7,7 +7,10 @@ type SingleValue<TValue> = [TValue, number];
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class Index<TKey, TValue, TPrefix = any> {
|
|
9
9
|
#private;
|
|
10
|
-
constructor(
|
|
10
|
+
constructor(options?: {
|
|
11
|
+
prefixIdentity?: boolean;
|
|
12
|
+
trackPresence?: boolean;
|
|
13
|
+
});
|
|
11
14
|
/**
|
|
12
15
|
* Create an Index from multiple MultiSet messages.
|
|
13
16
|
* @param messages - Array of MultiSet messages to build the index from.
|
package/dist/esm/indexes.js
CHANGED
|
@@ -4,8 +4,9 @@ const NO_PREFIX = /* @__PURE__ */ Symbol(`NO_PREFIX`);
|
|
|
4
4
|
class PrefixMap extends Map {
|
|
5
5
|
/**
|
|
6
6
|
* Add a value to the PrefixMap. Returns true if the map becomes empty after the operation.
|
|
7
|
+
* With `prefixIdentity`, values that share a prefix are the same value.
|
|
7
8
|
*/
|
|
8
|
-
addValue(value, multiplicity) {
|
|
9
|
+
addValue(value, multiplicity, prefixIdentity = false) {
|
|
9
10
|
if (multiplicity === 0) return this.size === 0;
|
|
10
11
|
const prefix = getPrefix(value);
|
|
11
12
|
const valueMapOrSingleValue = this.get(prefix);
|
|
@@ -15,7 +16,7 @@ class PrefixMap extends Map {
|
|
|
15
16
|
if (!isSamePrefix(currentPrefix, prefix)) {
|
|
16
17
|
throw new Error(`Mismatching prefixes, this should never happen`);
|
|
17
18
|
}
|
|
18
|
-
if (currentValue === value || hash(currentValue) === hash(value)) {
|
|
19
|
+
if (currentValue === value || prefixIdentity && prefix !== NO_PREFIX || hash(currentValue) === hash(value)) {
|
|
19
20
|
const newMultiplicity = currentMultiplicity + multiplicity;
|
|
20
21
|
if (newMultiplicity === 0) {
|
|
21
22
|
this.delete(prefix);
|
|
@@ -76,10 +77,20 @@ class Index {
|
|
|
76
77
|
* hash to identify identical values, storing them in a third level value map.
|
|
77
78
|
*/
|
|
78
79
|
#inner;
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
80
|
+
// Sum of multiplicities per key, for presence. Only joins read presence; a
|
|
81
|
+
// reduce index is built without it.
|
|
82
|
+
#consolidatedMultiplicity;
|
|
83
|
+
/**
|
|
84
|
+
* When true, the producer guarantees that a value's string or number
|
|
85
|
+
* prefix determines the value: two values under one key with an equal
|
|
86
|
+
* prefix are equal, so they merge without a structural hash.
|
|
87
|
+
*/
|
|
88
|
+
#prefixIdentity;
|
|
89
|
+
constructor(options) {
|
|
82
90
|
this.#inner = /* @__PURE__ */ new Map();
|
|
91
|
+
if (options?.trackPresence ?? true)
|
|
92
|
+
this.#consolidatedMultiplicity = /* @__PURE__ */ new Map();
|
|
93
|
+
this.#prefixIdentity = options?.prefixIdentity ?? false;
|
|
83
94
|
}
|
|
84
95
|
/**
|
|
85
96
|
* Create an Index from multiple MultiSet messages.
|
|
@@ -114,6 +125,11 @@ class Index {
|
|
|
114
125
|
get size() {
|
|
115
126
|
return this.#inner.size;
|
|
116
127
|
}
|
|
128
|
+
#presence() {
|
|
129
|
+
if (this.#consolidatedMultiplicity === void 0)
|
|
130
|
+
throw new Error(`This Index does not track presence`);
|
|
131
|
+
return this.#consolidatedMultiplicity;
|
|
132
|
+
}
|
|
117
133
|
/**
|
|
118
134
|
* This method checks if the index has a given key.
|
|
119
135
|
* @param key - The key to check.
|
|
@@ -128,7 +144,7 @@ class Index {
|
|
|
128
144
|
* @returns True if the key has non-zero consolidated multiplicity, false otherwise.
|
|
129
145
|
*/
|
|
130
146
|
hasPresence(key) {
|
|
131
|
-
return (this.#
|
|
147
|
+
return (this.#presence().get(key) || 0) !== 0;
|
|
132
148
|
}
|
|
133
149
|
/**
|
|
134
150
|
* Get the consolidated multiplicity (sum of multiplicities) for a key.
|
|
@@ -136,14 +152,14 @@ class Index {
|
|
|
136
152
|
* @returns The consolidated multiplicity for the key.
|
|
137
153
|
*/
|
|
138
154
|
getConsolidatedMultiplicity(key) {
|
|
139
|
-
return this.#
|
|
155
|
+
return this.#presence().get(key) || 0;
|
|
140
156
|
}
|
|
141
157
|
/**
|
|
142
158
|
* Get all keys that have presence (non-zero consolidated multiplicity).
|
|
143
159
|
* @returns An iterator of keys with non-zero consolidated multiplicity.
|
|
144
160
|
*/
|
|
145
161
|
getPresenceKeys() {
|
|
146
|
-
return this.#
|
|
162
|
+
return this.#presence().keys();
|
|
147
163
|
}
|
|
148
164
|
/**
|
|
149
165
|
* This method returns all values for a given key.
|
|
@@ -151,7 +167,20 @@ class Index {
|
|
|
151
167
|
* @returns An array of value tuples [value, multiplicity].
|
|
152
168
|
*/
|
|
153
169
|
get(key) {
|
|
154
|
-
|
|
170
|
+
const mapOrSingleValue = this.#inner.get(key);
|
|
171
|
+
if (mapOrSingleValue === void 0) return [];
|
|
172
|
+
if (isSingleValue(mapOrSingleValue)) return [mapOrSingleValue];
|
|
173
|
+
if (mapOrSingleValue instanceof ValueMap)
|
|
174
|
+
return [...mapOrSingleValue.values()];
|
|
175
|
+
const result = [];
|
|
176
|
+
for (const singleValueOrValueMap of mapOrSingleValue.values()) {
|
|
177
|
+
if (isSingleValue(singleValueOrValueMap))
|
|
178
|
+
result.push(singleValueOrValueMap);
|
|
179
|
+
else
|
|
180
|
+
for (const valueTuple of singleValueOrValueMap.values())
|
|
181
|
+
result.push(valueTuple);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
155
184
|
}
|
|
156
185
|
/**
|
|
157
186
|
* This method returns an iterator over all values for a given key.
|
|
@@ -210,11 +239,14 @@ class Index {
|
|
|
210
239
|
addValue(key, valueTuple) {
|
|
211
240
|
const [value, multiplicity] = valueTuple;
|
|
212
241
|
if (multiplicity === 0) return;
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
const presence = this.#consolidatedMultiplicity;
|
|
243
|
+
if (presence) {
|
|
244
|
+
const newConsolidatedMultiplicity = (presence.get(key) || 0) + multiplicity;
|
|
245
|
+
if (newConsolidatedMultiplicity === 0) {
|
|
246
|
+
presence.delete(key);
|
|
247
|
+
} else {
|
|
248
|
+
presence.set(key, newConsolidatedMultiplicity);
|
|
249
|
+
}
|
|
218
250
|
}
|
|
219
251
|
const mapOrSingleValue = this.#inner.get(key);
|
|
220
252
|
if (mapOrSingleValue === void 0) {
|
|
@@ -244,7 +276,11 @@ class Index {
|
|
|
244
276
|
}
|
|
245
277
|
}
|
|
246
278
|
} else {
|
|
247
|
-
const isEmpty = mapOrSingleValue.addValue(
|
|
279
|
+
const isEmpty = mapOrSingleValue.addValue(
|
|
280
|
+
value,
|
|
281
|
+
multiplicity,
|
|
282
|
+
this.#prefixIdentity
|
|
283
|
+
);
|
|
248
284
|
if (isEmpty) {
|
|
249
285
|
this.#inner.delete(key);
|
|
250
286
|
}
|
|
@@ -266,7 +302,7 @@ class Index {
|
|
|
266
302
|
}
|
|
267
303
|
const newPrefix = getPrefix(newValue);
|
|
268
304
|
const currentPrefix = getPrefix(currentValue);
|
|
269
|
-
if (isSamePrefix(currentPrefix, newPrefix) && (
|
|
305
|
+
if (isSamePrefix(currentPrefix, newPrefix) && (this.#prefixIdentity && newPrefix !== NO_PREFIX || hash(currentValue) === hash(newValue))) {
|
|
270
306
|
const newMultiplicity = currentMultiplicity + multiplicity;
|
|
271
307
|
if (newMultiplicity === 0) {
|
|
272
308
|
this.#inner.delete(key);
|
package/dist/esm/indexes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 (!isSamePrefix(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 #consolidatedMultiplicity: Map<TKey, number> = new Map() // sum of multiplicities per key\n\n constructor() {\n this.#inner = new Map()\n }\n\n /**\n * Create an Index from multiple MultiSet messages.\n * @param messages - Array of MultiSet messages to build the index from.\n * @returns A new Index containing all the data from the messages.\n */\n static fromMultiSets<K, V>(messages: Array<MultiSet<[K, V]>>): Index<K, V> {\n const index = new Index<K, V>()\n\n for (const message of messages) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n index.addValue(key, [value, multiplicity])\n }\n }\n\n return index\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 * Check if a key has presence (non-zero consolidated multiplicity).\n * @param key - The key to check.\n * @returns True if the key has non-zero consolidated multiplicity, false otherwise.\n */\n hasPresence(key: TKey): boolean {\n return (this.#consolidatedMultiplicity.get(key) || 0) !== 0\n }\n\n /**\n * Get the consolidated multiplicity (sum of multiplicities) for a key.\n * @param key - The key to get the consolidated multiplicity for.\n * @returns The consolidated multiplicity for the key.\n */\n getConsolidatedMultiplicity(key: TKey): number {\n return this.#consolidatedMultiplicity.get(key) || 0\n }\n\n /**\n * Get all keys that have presence (non-zero consolidated multiplicity).\n * @returns An iterator of keys with non-zero consolidated multiplicity.\n */\n getPresenceKeys(): Iterable<TKey> {\n return this.#consolidatedMultiplicity.keys()\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 // Update consolidated multiplicity tracking\n const newConsolidatedMultiplicity =\n (this.#consolidatedMultiplicity.get(key) || 0) + multiplicity\n if (newConsolidatedMultiplicity === 0) {\n this.#consolidatedMultiplicity.delete(key)\n } else {\n this.#consolidatedMultiplicity.set(key, newConsolidatedMultiplicity)\n }\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 isSamePrefix(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 (isSamePrefix(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 */\n// Prefixes are Map keys, so they compare as a Map does: NaN equals NaN.\nfunction isSamePrefix(a: unknown, b: unknown): boolean {\n return a === b || (Number.isNaN(a) && Number.isNaN(b))\n}\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":"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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 * With `prefixIdentity`, values that share a prefix are the same value.\n */\n addValue(\n value: TValue,\n multiplicity: number,\n prefixIdentity = false,\n ): 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 (!isSamePrefix(currentPrefix, prefix)) {\n throw new Error(`Mismatching prefixes, this should never happen`)\n }\n\n if (\n currentValue === value ||\n (prefixIdentity && prefix !== NO_PREFIX) ||\n hash(currentValue) === hash(value)\n ) {\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 // Sum of multiplicities per key, for presence. Only joins read presence; a\n // reduce index is built without it.\n #consolidatedMultiplicity: Map<TKey, number> | undefined\n /**\n * When true, the producer guarantees that a value's string or number\n * prefix determines the value: two values under one key with an equal\n * prefix are equal, so they merge without a structural hash.\n */\n #prefixIdentity: boolean\n\n constructor(options?: { prefixIdentity?: boolean; trackPresence?: boolean }) {\n this.#inner = new Map()\n if (options?.trackPresence ?? true)\n this.#consolidatedMultiplicity = new Map()\n this.#prefixIdentity = options?.prefixIdentity ?? false\n }\n\n /**\n * Create an Index from multiple MultiSet messages.\n * @param messages - Array of MultiSet messages to build the index from.\n * @returns A new Index containing all the data from the messages.\n */\n static fromMultiSets<K, V>(messages: Array<MultiSet<[K, V]>>): Index<K, V> {\n const index = new Index<K, V>()\n\n for (const message of messages) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n index.addValue(key, [value, multiplicity])\n }\n }\n\n return index\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 #presence(): Map<TKey, number> {\n if (this.#consolidatedMultiplicity === undefined)\n throw new Error(`This Index does not track presence`)\n return this.#consolidatedMultiplicity\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 * Check if a key has presence (non-zero consolidated multiplicity).\n * @param key - The key to check.\n * @returns True if the key has non-zero consolidated multiplicity, false otherwise.\n */\n hasPresence(key: TKey): boolean {\n return (this.#presence().get(key) || 0) !== 0\n }\n\n /**\n * Get the consolidated multiplicity (sum of multiplicities) for a key.\n * @param key - The key to get the consolidated multiplicity for.\n * @returns The consolidated multiplicity for the key.\n */\n getConsolidatedMultiplicity(key: TKey): number {\n return this.#presence().get(key) || 0\n }\n\n /**\n * Get all keys that have presence (non-zero consolidated multiplicity).\n * @returns An iterator of keys with non-zero consolidated multiplicity.\n */\n getPresenceKeys(): Iterable<TKey> {\n return this.#presence().keys()\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 // Built directly, in getIterator's order: join and reduce call this for\n // every changed key.\n const mapOrSingleValue = this.#inner.get(key)\n if (mapOrSingleValue === undefined) return []\n if (isSingleValue(mapOrSingleValue)) return [mapOrSingleValue]\n if (mapOrSingleValue instanceof ValueMap)\n return [...mapOrSingleValue.values()]\n const result: Array<[TValue, number]> = []\n for (const singleValueOrValueMap of mapOrSingleValue.values()) {\n if (isSingleValue(singleValueOrValueMap))\n result.push(singleValueOrValueMap)\n else\n for (const valueTuple of singleValueOrValueMap.values())\n result.push(valueTuple)\n }\n return result\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 // Update consolidated multiplicity tracking\n const presence = this.#consolidatedMultiplicity\n if (presence) {\n const newConsolidatedMultiplicity =\n (presence.get(key) || 0) + multiplicity\n if (newConsolidatedMultiplicity === 0) {\n presence.delete(key)\n } else {\n presence.set(key, newConsolidatedMultiplicity)\n }\n }\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(\n value,\n multiplicity,\n this.#prefixIdentity,\n )\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 isSamePrefix(currentPrefix, newPrefix) &&\n ((this.#prefixIdentity && newPrefix !== NO_PREFIX) ||\n 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 (isSamePrefix(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 */\n// Prefixes are Map keys, so they compare as a Map does: NaN equals NaN.\nfunction isSamePrefix(a: unknown, b: unknown): boolean {\n return a === b || (Number.isNaN(a) && Number.isNaN(b))\n}\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":"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"}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { IStreamBuilder, KeyValue } from '../types.js';
|
|
2
2
|
type GroupKey = Record<string, unknown>;
|
|
3
|
-
type
|
|
3
|
+
type AggregateFunction<T, R, V = unknown> = {
|
|
4
4
|
preMap: (data: T) => V;
|
|
5
5
|
reduce: (values: Array<[V, number]>) => V;
|
|
6
6
|
postMap?: (result: V) => R;
|
|
7
7
|
};
|
|
8
|
-
type PipedAggregateFunction<T, R> = {
|
|
9
|
-
pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>;
|
|
10
|
-
};
|
|
11
|
-
type AggregateFunction<T, R, V = unknown> = BasicAggregateFunction<T, R, V> | PipedAggregateFunction<T, R>;
|
|
12
8
|
type ExtractAggregateReturnType<T, A> = A extends AggregateFunction<T, infer R, any> ? R : never;
|
|
13
9
|
type AggregatesReturnType<T, A> = {
|
|
14
10
|
[K in keyof A]: ExtractAggregateReturnType<T, A[K]>;
|