@tanstack/db-ivm
Version:
Incremental View Maintenance for TanStack DB based on Differential Dataflow
214 lines (202 loc) • 6.47 kB
text/typescript
import { topK, topKWithIndex } from "./topK.js"
import { topKWithFractionalIndex } from "./topKWithFractionalIndex.js"
import { map } from "./map.js"
import { innerJoin } from "./join.js"
import { consolidate } from "./consolidate.js"
import type { KeyValue } from "../types.js"
import type { IStreamBuilder } from "../types"
export interface OrderByOptions<Ve> {
comparator?: (a: Ve, b: Ve) => number
limit?: number
offset?: number
}
/**
* Orders the elements and limits the number of results, with optional offset
* This requires a keyed stream, and uses the `topK` operator to order all the elements.
*
* @param valueExtractor - A function that extracts the value to order by from the element
* @param options - An optional object containing comparator, limit and offset properties
* @returns A piped operator that orders the elements and limits the number of results
*/
export function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(
valueExtractor: (
value: T extends KeyValue<unknown, infer V> ? V : never
) => Ve,
options?: OrderByOptions<Ve>
) {
const limit = options?.limit ?? Infinity
const offset = options?.offset ?? 0
const comparator =
options?.comparator ??
((a, b) => {
// Default to JS like ordering
if (a === b) return 0
if (a < b) return -1
return 1
})
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
return stream.pipe(
map(
([key, value]) =>
[
null,
[
key,
valueExtractor(
value as T extends KeyValue<unknown, infer V> ? V : never
),
],
] as KeyValue<null, [KeyType, Ve]>
),
topK((a, b) => comparator(a[1], b[1]), { limit, offset }),
map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),
innerJoin(stream),
map(([key, value]) => {
return [key, value[1]] as T
}),
consolidate()
)
}
}
/**
* Orders the elements and limits the number of results, with optional offset and
* annotates the value with the index.
* This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.
*
* @param valueExtractor - A function that extracts the value to order by from the element
* @param options - An optional object containing comparator, limit and offset properties
* @returns A piped operator that orders the elements and limits the number of results
*/
export function orderByWithIndex<
T extends KeyValue<unknown, unknown>,
Ve = unknown,
>(
valueExtractor: (
value: T extends KeyValue<unknown, infer V> ? V : never
) => Ve,
options?: OrderByOptions<Ve>
) {
const limit = options?.limit ?? Infinity
const offset = options?.offset ?? 0
const comparator =
options?.comparator ??
((a, b) => {
// Default to JS like ordering
if (a === b) return 0
if (a < b) return -1
return 1
})
return (
stream: IStreamBuilder<T>
): IStreamBuilder<
KeyValue<
T extends KeyValue<infer K, unknown> ? K : never,
[T extends KeyValue<unknown, infer V> ? V : never, number]
>
> => {
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
return stream.pipe(
map(
([key, value]) =>
[
null,
[
key,
valueExtractor(
value as T extends KeyValue<unknown, infer V> ? V : never
),
],
] as KeyValue<null, [KeyType, Ve]>
),
topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),
map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),
innerJoin(stream),
map(([key, [index, value]]) => {
return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>
}),
consolidate()
)
}
}
export function orderByWithFractionalIndexBase<
T extends KeyValue<unknown, unknown>,
Ve = unknown,
>(
topKFunction: typeof topKWithFractionalIndex,
valueExtractor: (
value: T extends KeyValue<unknown, infer V> ? V : never
) => Ve,
options?: OrderByOptions<Ve>
) {
const limit = options?.limit ?? Infinity
const offset = options?.offset ?? 0
const comparator =
options?.comparator ??
((a, b) => {
// Default to JS like ordering
if (a === b) return 0
if (a < b) return -1
return 1
})
return (
stream: IStreamBuilder<T>
): IStreamBuilder<
KeyValue<
T extends KeyValue<infer K, unknown> ? K : never,
[T extends KeyValue<unknown, infer V> ? V : never, string]
>
> => {
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
return stream.pipe(
map(
([key, value]) =>
[
null,
[
key,
valueExtractor(
value as T extends KeyValue<unknown, infer V> ? V : never
),
],
] as KeyValue<null, [KeyType, Ve]>
),
topKFunction((a, b) => comparator(a[1], b[1]), {
limit,
offset,
}),
map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, string>),
innerJoin(stream),
map(([key, [index, value]]) => {
return [key, [value, index]] as KeyValue<KeyType, [ValueType, string]>
}),
consolidate()
)
}
}
/**
* Orders the elements and limits the number of results, with optional offset and
* annotates the value with a fractional index.
* This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.
*
* @param valueExtractor - A function that extracts the value to order by from the element
* @param options - An optional object containing comparator, limit and offset properties
* @returns A piped operator that orders the elements and limits the number of results
*/
export function orderByWithFractionalIndex<
T extends KeyValue<unknown, unknown>,
Ve = unknown,
>(
valueExtractor: (
value: T extends KeyValue<unknown, infer V> ? V : never
) => Ve,
options?: OrderByOptions<Ve>
) {
return orderByWithFractionalIndexBase(
topKWithFractionalIndex,
valueExtractor,
options
)
}