@tanstack/db-ivm 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/hashing/hash.cjs +76 -41
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/hashing/hash.d.cts +2 -0
- package/dist/cjs/hashing/murmur.cjs +32 -9
- package/dist/cjs/hashing/murmur.cjs.map +1 -1
- package/dist/cjs/hashing/murmur.d.cts +1 -0
- package/dist/cjs/index.cjs +44 -42
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/operators/groupBy.cjs +1 -1
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/utils.cjs +131 -6
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +7 -3
- package/dist/esm/hashing/hash.d.ts +2 -0
- package/dist/esm/hashing/hash.js +78 -43
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/hashing/murmur.d.ts +1 -0
- package/dist/esm/hashing/murmur.js +32 -9
- package/dist/esm/hashing/murmur.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +17 -15
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operators/groupBy.js +1 -1
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/topKArray.js +1 -1
- package/dist/esm/utils.d.ts +7 -3
- package/dist/esm/utils.js +131 -6
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -2
- package/src/hashing/hash.ts +117 -51
- package/src/hashing/murmur.ts +44 -10
- package/src/index.ts +1 -0
- package/src/operators/groupBy.ts +7 -4
- package/src/utils.ts +212 -9
- package/dist/cjs/operators/orderByBTree.d.cts +0 -3
- package/dist/esm/operators/orderByBTree.d.ts +0 -3
- package/src/operators/orderByBTree.ts +0 -20
package/src/operators/groupBy.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function groupBy<
|
|
|
62
62
|
stream: IStreamBuilder<T>,
|
|
63
63
|
): IStreamBuilder<KeyValue<string, ResultType>> => {
|
|
64
64
|
// Special key to store the original key object
|
|
65
|
-
const KEY_SENTINEL = `
|
|
65
|
+
const KEY_SENTINEL = Symbol(`original_group_key`)
|
|
66
66
|
|
|
67
67
|
// First map to extract keys and pre-aggregate values
|
|
68
68
|
const withKeysAndValues = stream.pipe(
|
|
@@ -71,7 +71,7 @@ export function groupBy<
|
|
|
71
71
|
const keyString = serializeValue(key)
|
|
72
72
|
|
|
73
73
|
// Create values object with pre-aggregated values
|
|
74
|
-
const values: Record<string, unknown> = {}
|
|
74
|
+
const values: Record<string | symbol, unknown> = {}
|
|
75
75
|
|
|
76
76
|
// Store the original key object
|
|
77
77
|
values[KEY_SENTINEL] = key
|
|
@@ -81,7 +81,10 @@ export function groupBy<
|
|
|
81
81
|
values[name] = aggregate.preMap(data)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
return [keyString, values] as KeyValue<
|
|
84
|
+
return [keyString, values] as KeyValue<
|
|
85
|
+
string,
|
|
86
|
+
Record<string | symbol, unknown>
|
|
87
|
+
>
|
|
85
88
|
}),
|
|
86
89
|
)
|
|
87
90
|
|
|
@@ -99,7 +102,7 @@ export function groupBy<
|
|
|
99
102
|
return []
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
const result: Record<
|
|
105
|
+
const result: Record<PropertyKey, unknown> = {}
|
|
103
106
|
|
|
104
107
|
// Get the original key from first value in group
|
|
105
108
|
const originalKey = values[0]?.[0]?.[KEY_SENTINEL]
|
package/src/utils.ts
CHANGED
|
@@ -185,6 +185,14 @@ function range(start: number, end: number): Array<number> {
|
|
|
185
185
|
export function compareKeys(a: string | number, b: string | number): number {
|
|
186
186
|
// Same type: compare directly
|
|
187
187
|
if (typeof a === typeof b) {
|
|
188
|
+
if (typeof a === `number` && typeof b === `number`) {
|
|
189
|
+
const aIsNaN = Number.isNaN(a)
|
|
190
|
+
const bIsNaN = Number.isNaN(b)
|
|
191
|
+
if (aIsNaN || bIsNaN) {
|
|
192
|
+
if (aIsNaN && bIsNaN) return 0
|
|
193
|
+
return aIsNaN ? 1 : -1
|
|
194
|
+
}
|
|
195
|
+
}
|
|
188
196
|
if (a < b) return -1
|
|
189
197
|
if (a > b) return 1
|
|
190
198
|
return 0
|
|
@@ -193,19 +201,214 @@ export function compareKeys(a: string | number, b: string | number): number {
|
|
|
193
201
|
return typeof a === `string` ? -1 : 1
|
|
194
202
|
}
|
|
195
203
|
|
|
204
|
+
type CanonicalValue =
|
|
205
|
+
| readonly [`undefined`]
|
|
206
|
+
| readonly [`null`]
|
|
207
|
+
| readonly [`boolean`, boolean]
|
|
208
|
+
| readonly [`number`, number | `NaN` | `Infinity` | `-Infinity`]
|
|
209
|
+
| readonly [`bigint`, string]
|
|
210
|
+
| readonly [`string`, string]
|
|
211
|
+
| readonly [`date`, number | `Invalid`]
|
|
212
|
+
| readonly [`regexp`, string, string]
|
|
213
|
+
| readonly [`bytes`, Array<number>]
|
|
214
|
+
| readonly [`array`, Array<CanonicalValue>]
|
|
215
|
+
| readonly [`map`, Array<readonly [CanonicalValue, CanonicalValue]>]
|
|
216
|
+
| readonly [`set`, Array<CanonicalValue>]
|
|
217
|
+
| readonly [`object`, Array<readonly [string, CanonicalValue]>]
|
|
218
|
+
|
|
196
219
|
/**
|
|
197
|
-
* Serializes a value
|
|
198
|
-
*
|
|
199
|
-
*
|
|
220
|
+
* Serializes a supported query value into one canonical key.
|
|
221
|
+
*
|
|
222
|
+
* JSON's native encoding is not suitable for relation keys: it merges BigInt
|
|
223
|
+
* with strings when a replacer is used, merges NaN with null, drops undefined,
|
|
224
|
+
* and depends on object insertion order. Ordinary JSON values keep their
|
|
225
|
+
* established wire form. Values that need richer types use a reserved prefix
|
|
226
|
+
* plus a structural, type-tagged encoding.
|
|
200
227
|
*/
|
|
201
228
|
export function serializeValue(value: unknown): string {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
229
|
+
if (isJsonSafeStructuralValue(value, new Set())) {
|
|
230
|
+
return JSON.stringify(toStableJsonValue(value))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return `~${JSON.stringify(toCanonicalValue(value, new Set()))}`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
type JsonValue =
|
|
237
|
+
| null
|
|
238
|
+
| boolean
|
|
239
|
+
| number
|
|
240
|
+
| string
|
|
241
|
+
| Array<JsonValue>
|
|
242
|
+
| { [key: string]: JsonValue }
|
|
243
|
+
|
|
244
|
+
function isJsonSafeStructuralValue(
|
|
245
|
+
value: unknown,
|
|
246
|
+
ancestors: Set<object>,
|
|
247
|
+
): boolean {
|
|
248
|
+
if (value === null) return true
|
|
249
|
+
|
|
250
|
+
switch (typeof value) {
|
|
251
|
+
case `boolean`:
|
|
252
|
+
case `string`:
|
|
253
|
+
return true
|
|
254
|
+
case `number`:
|
|
255
|
+
return Number.isFinite(value)
|
|
256
|
+
case `undefined`:
|
|
257
|
+
case `bigint`:
|
|
258
|
+
case `symbol`:
|
|
259
|
+
case `function`:
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
264
|
+
if (
|
|
265
|
+
value instanceof Date ||
|
|
266
|
+
value instanceof RegExp ||
|
|
267
|
+
value instanceof Uint8Array ||
|
|
268
|
+
value instanceof Map ||
|
|
269
|
+
value instanceof Set
|
|
270
|
+
) {
|
|
271
|
+
return false
|
|
205
272
|
}
|
|
206
|
-
|
|
207
|
-
|
|
273
|
+
|
|
274
|
+
return Array.isArray(value)
|
|
275
|
+
? value.every((item) => isJsonSafeStructuralValue(item, ancestors))
|
|
276
|
+
: Object.keys(value).every((key) =>
|
|
277
|
+
isJsonSafeStructuralValue(
|
|
278
|
+
(value as Record<string, unknown>)[key],
|
|
279
|
+
ancestors,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function toStableJsonValue(value: unknown): JsonValue {
|
|
286
|
+
if (
|
|
287
|
+
value === null ||
|
|
288
|
+
typeof value === `boolean` ||
|
|
289
|
+
typeof value === `number` ||
|
|
290
|
+
typeof value === `string`
|
|
291
|
+
) {
|
|
292
|
+
return value
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
return value.map(toStableJsonValue)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return Object.fromEntries(
|
|
300
|
+
Object.keys(value as object)
|
|
301
|
+
.sort()
|
|
302
|
+
.map((key) => [
|
|
303
|
+
key,
|
|
304
|
+
toStableJsonValue((value as Record<string, unknown>)[key]),
|
|
305
|
+
]),
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function toCanonicalValue(
|
|
310
|
+
value: unknown,
|
|
311
|
+
ancestors: Set<object>,
|
|
312
|
+
): CanonicalValue {
|
|
313
|
+
if (value === undefined) return [`undefined`]
|
|
314
|
+
if (value === null) return [`null`]
|
|
315
|
+
|
|
316
|
+
switch (typeof value) {
|
|
317
|
+
case `boolean`:
|
|
318
|
+
return [`boolean`, value]
|
|
319
|
+
case `number`:
|
|
320
|
+
if (Number.isNaN(value)) return [`number`, `NaN`]
|
|
321
|
+
if (value === Infinity) return [`number`, `Infinity`]
|
|
322
|
+
if (value === -Infinity) return [`number`, `-Infinity`]
|
|
323
|
+
return [`number`, value === 0 ? 0 : value]
|
|
324
|
+
case `bigint`:
|
|
325
|
+
return [`bigint`, value.toString()]
|
|
326
|
+
case `string`:
|
|
327
|
+
return [`string`, value]
|
|
328
|
+
case `symbol`:
|
|
329
|
+
case `function`:
|
|
330
|
+
throw new TypeError(
|
|
331
|
+
`Cannot serialize ${typeof value} as a structural relation key`,
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
336
|
+
if (value instanceof Date) {
|
|
337
|
+
const timestamp = value.getTime()
|
|
338
|
+
return Number.isNaN(timestamp)
|
|
339
|
+
? ([`date`, `Invalid`] as const)
|
|
340
|
+
: ([`date`, timestamp] as const)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (value instanceof RegExp) {
|
|
344
|
+
return [`regexp`, value.source, value.flags]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (value instanceof Uint8Array) {
|
|
348
|
+
return [`bytes`, Array.from(value)]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (Array.isArray(value)) {
|
|
352
|
+
return [`array`, value.map((item) => toCanonicalValue(item, ancestors))]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (value instanceof Map) {
|
|
356
|
+
const entries = [...value.entries()].map(
|
|
357
|
+
([key, entryValue]) =>
|
|
358
|
+
[
|
|
359
|
+
toCanonicalValue(key, ancestors),
|
|
360
|
+
toCanonicalValue(entryValue, ancestors),
|
|
361
|
+
] as const,
|
|
362
|
+
)
|
|
363
|
+
entries.sort((left, right) =>
|
|
364
|
+
compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),
|
|
365
|
+
)
|
|
366
|
+
return [`map`, entries]
|
|
208
367
|
}
|
|
209
|
-
|
|
368
|
+
|
|
369
|
+
if (value instanceof Set) {
|
|
370
|
+
const entries = [...value].map((entry) =>
|
|
371
|
+
toCanonicalValue(entry, ancestors),
|
|
372
|
+
)
|
|
373
|
+
entries.sort((left, right) =>
|
|
374
|
+
compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),
|
|
375
|
+
)
|
|
376
|
+
return [`set`, entries]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const entries = Object.keys(value)
|
|
380
|
+
.sort()
|
|
381
|
+
.map(
|
|
382
|
+
(key) =>
|
|
383
|
+
[
|
|
384
|
+
key,
|
|
385
|
+
toCanonicalValue(
|
|
386
|
+
(value as Record<string, unknown>)[key],
|
|
387
|
+
ancestors,
|
|
388
|
+
),
|
|
389
|
+
] as const,
|
|
390
|
+
)
|
|
391
|
+
return [`object`, entries]
|
|
210
392
|
})
|
|
211
393
|
}
|
|
394
|
+
|
|
395
|
+
function compareSerializedValues(left: string, right: string): number {
|
|
396
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function withAcyclicValue<T>(
|
|
400
|
+
value: object,
|
|
401
|
+
ancestors: Set<object>,
|
|
402
|
+
encode: () => T,
|
|
403
|
+
): T {
|
|
404
|
+
if (ancestors.has(value)) {
|
|
405
|
+
throw new TypeError(`Cannot serialize a cyclic structural relation key`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
ancestors.add(value)
|
|
409
|
+
try {
|
|
410
|
+
return encode()
|
|
411
|
+
} finally {
|
|
412
|
+
ancestors.delete(value)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { KeyValue } from '../types.js';
|
|
2
|
-
import { OrderByOptions } from './orderBy.js';
|
|
3
|
-
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { KeyValue } from '../types.js';
|
|
2
|
-
import { OrderByOptions } from './orderBy.js';
|
|
3
|
-
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { orderByWithFractionalIndexBase } from './orderBy.js'
|
|
2
|
-
import { topKWithFractionalIndexBTree } from './topKWithFractionalIndexBTree.js'
|
|
3
|
-
import type { KeyValue } from '../types.js'
|
|
4
|
-
import type { OrderByOptions } from './orderBy.js'
|
|
5
|
-
|
|
6
|
-
export function orderByWithFractionalIndexBTree<
|
|
7
|
-
T extends KeyValue<unknown, unknown>,
|
|
8
|
-
Ve = unknown,
|
|
9
|
-
>(
|
|
10
|
-
valueExtractor: (
|
|
11
|
-
value: T extends KeyValue<unknown, infer V> ? V : never,
|
|
12
|
-
) => Ve,
|
|
13
|
-
options?: OrderByOptions<Ve>,
|
|
14
|
-
) {
|
|
15
|
-
return orderByWithFractionalIndexBase(
|
|
16
|
-
topKWithFractionalIndexBTree,
|
|
17
|
-
valueExtractor,
|
|
18
|
-
options,
|
|
19
|
-
)
|
|
20
|
-
}
|