@tanstack/db-ivm 0.1.18 → 0.1.19
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/index.cjs +42 -42
- package/dist/cjs/utils.cjs +123 -6
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +7 -3
- package/dist/esm/index.js +15 -15
- package/dist/esm/operators/topKArray.js +1 -1
- package/dist/esm/utils.d.ts +7 -3
- package/dist/esm/utils.js +123 -6
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/utils.ts +204 -9
package/dist/cjs/index.cjs
CHANGED
|
@@ -3,47 +3,52 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const d2 = require("./d2.cjs");
|
|
4
4
|
const multiset = require("./multiset.cjs");
|
|
5
5
|
const utils = require("./utils.cjs");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const count = require("./operators/count.cjs");
|
|
9
|
-
const debug = require("./operators/debug.cjs");
|
|
10
|
-
const distinct = require("./operators/distinct.cjs");
|
|
11
|
-
const filter = require("./operators/filter.cjs");
|
|
12
|
-
const groupedTopKWithFractionalIndex = require("./operators/groupedTopKWithFractionalIndex.cjs");
|
|
13
|
-
const join = require("./operators/join.cjs");
|
|
6
|
+
const groupBy = require("./operators/groupBy.cjs");
|
|
7
|
+
const pipe = require("./operators/pipe.cjs");
|
|
14
8
|
const map = require("./operators/map.cjs");
|
|
9
|
+
const tap = require("./operators/tap.cjs");
|
|
10
|
+
const filter = require("./operators/filter.cjs");
|
|
15
11
|
const negate = require("./operators/negate.cjs");
|
|
12
|
+
const concat = require("./operators/concat.cjs");
|
|
13
|
+
const debug = require("./operators/debug.cjs");
|
|
16
14
|
const output = require("./operators/output.cjs");
|
|
15
|
+
const consolidate = require("./operators/consolidate.cjs");
|
|
16
|
+
const join = require("./operators/join.cjs");
|
|
17
17
|
const reduce = require("./operators/reduce.cjs");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const filterBy = require("./operators/filterBy.cjs");
|
|
21
|
-
const groupBy = require("./operators/groupBy.cjs");
|
|
22
|
-
const groupedOrderBy = require("./operators/groupedOrderBy.cjs");
|
|
18
|
+
const count = require("./operators/count.cjs");
|
|
19
|
+
const distinct = require("./operators/distinct.cjs");
|
|
23
20
|
const keying = require("./operators/keying.cjs");
|
|
24
|
-
const orderBy = require("./operators/orderBy.cjs");
|
|
25
|
-
const pipe = require("./operators/pipe.cjs");
|
|
26
21
|
const topK = require("./operators/topK.cjs");
|
|
22
|
+
const topKWithFractionalIndex = require("./operators/topKWithFractionalIndex.cjs");
|
|
23
|
+
const groupedTopKWithFractionalIndex = require("./operators/groupedTopKWithFractionalIndex.cjs");
|
|
24
|
+
const orderBy = require("./operators/orderBy.cjs");
|
|
25
|
+
const groupedOrderBy = require("./operators/groupedOrderBy.cjs");
|
|
26
|
+
const filterBy = require("./operators/filterBy.cjs");
|
|
27
27
|
exports.D2 = d2.D2;
|
|
28
28
|
exports.RootStreamBuilder = d2.RootStreamBuilder;
|
|
29
29
|
exports.StreamBuilder = d2.StreamBuilder;
|
|
30
30
|
exports.MultiSet = multiset.MultiSet;
|
|
31
31
|
exports.compareKeys = utils.compareKeys;
|
|
32
32
|
exports.serializeValue = utils.serializeValue;
|
|
33
|
+
exports.groupBy = groupBy.groupBy;
|
|
34
|
+
exports.groupByOperators = groupBy.groupByOperators;
|
|
35
|
+
exports.pipe = pipe.pipe;
|
|
36
|
+
exports.MapOperator = map.MapOperator;
|
|
37
|
+
exports.map = map.map;
|
|
38
|
+
exports.TapOperator = tap.TapOperator;
|
|
39
|
+
exports.tap = tap.tap;
|
|
40
|
+
exports.FilterOperator = filter.FilterOperator;
|
|
41
|
+
exports.filter = filter.filter;
|
|
42
|
+
exports.NegateOperator = negate.NegateOperator;
|
|
43
|
+
exports.negate = negate.negate;
|
|
33
44
|
exports.ConcatOperator = concat.ConcatOperator;
|
|
34
45
|
exports.concat = concat.concat;
|
|
35
|
-
exports.ConsolidateOperator = consolidate.ConsolidateOperator;
|
|
36
|
-
exports.consolidate = consolidate.consolidate;
|
|
37
|
-
exports.CountOperator = count.CountOperator;
|
|
38
|
-
exports.count = count.count;
|
|
39
46
|
exports.DebugOperator = debug.DebugOperator;
|
|
40
47
|
exports.debug = debug.debug;
|
|
41
|
-
exports.
|
|
42
|
-
exports.
|
|
43
|
-
exports.
|
|
44
|
-
exports.
|
|
45
|
-
exports.GroupedTopKWithFractionalIndexOperator = groupedTopKWithFractionalIndex.GroupedTopKWithFractionalIndexOperator;
|
|
46
|
-
exports.groupedTopKWithFractionalIndex = groupedTopKWithFractionalIndex.groupedTopKWithFractionalIndex;
|
|
48
|
+
exports.OutputOperator = output.OutputOperator;
|
|
49
|
+
exports.output = output.output;
|
|
50
|
+
exports.ConsolidateOperator = consolidate.ConsolidateOperator;
|
|
51
|
+
exports.consolidate = consolidate.consolidate;
|
|
47
52
|
exports.JoinOperator = join.JoinOperator;
|
|
48
53
|
exports.antiJoin = join.antiJoin;
|
|
49
54
|
exports.fullJoin = join.fullJoin;
|
|
@@ -51,30 +56,25 @@ exports.innerJoin = join.innerJoin;
|
|
|
51
56
|
exports.join = join.join;
|
|
52
57
|
exports.leftJoin = join.leftJoin;
|
|
53
58
|
exports.rightJoin = join.rightJoin;
|
|
54
|
-
exports.MapOperator = map.MapOperator;
|
|
55
|
-
exports.map = map.map;
|
|
56
|
-
exports.NegateOperator = negate.NegateOperator;
|
|
57
|
-
exports.negate = negate.negate;
|
|
58
|
-
exports.OutputOperator = output.OutputOperator;
|
|
59
|
-
exports.output = output.output;
|
|
60
59
|
exports.ReduceOperator = reduce.ReduceOperator;
|
|
61
60
|
exports.reduce = reduce.reduce;
|
|
62
|
-
exports.
|
|
63
|
-
exports.
|
|
64
|
-
exports.
|
|
65
|
-
exports.
|
|
66
|
-
exports.filterBy = filterBy.filterBy;
|
|
67
|
-
exports.groupBy = groupBy.groupBy;
|
|
68
|
-
exports.groupByOperators = groupBy.groupByOperators;
|
|
69
|
-
exports.groupedOrderByWithFractionalIndex = groupedOrderBy.groupedOrderByWithFractionalIndex;
|
|
61
|
+
exports.CountOperator = count.CountOperator;
|
|
62
|
+
exports.count = count.count;
|
|
63
|
+
exports.DistinctOperator = distinct.DistinctOperator;
|
|
64
|
+
exports.distinct = distinct.distinct;
|
|
70
65
|
exports.keyBy = keying.keyBy;
|
|
71
66
|
exports.rekey = keying.rekey;
|
|
72
67
|
exports.unkey = keying.unkey;
|
|
68
|
+
exports.topK = topK.topK;
|
|
69
|
+
exports.topKWithIndex = topK.topKWithIndex;
|
|
70
|
+
exports.TopKWithFractionalIndexOperator = topKWithFractionalIndex.TopKWithFractionalIndexOperator;
|
|
71
|
+
exports.topKWithFractionalIndex = topKWithFractionalIndex.topKWithFractionalIndex;
|
|
72
|
+
exports.GroupedTopKWithFractionalIndexOperator = groupedTopKWithFractionalIndex.GroupedTopKWithFractionalIndexOperator;
|
|
73
|
+
exports.groupedTopKWithFractionalIndex = groupedTopKWithFractionalIndex.groupedTopKWithFractionalIndex;
|
|
73
74
|
exports.orderBy = orderBy.orderBy;
|
|
74
75
|
exports.orderByWithFractionalIndex = orderBy.orderByWithFractionalIndex;
|
|
75
76
|
exports.orderByWithFractionalIndexBase = orderBy.orderByWithFractionalIndexBase;
|
|
76
77
|
exports.orderByWithIndex = orderBy.orderByWithIndex;
|
|
77
|
-
exports.
|
|
78
|
-
exports.
|
|
79
|
-
exports.topKWithIndex = topK.topKWithIndex;
|
|
78
|
+
exports.groupedOrderByWithFractionalIndex = groupedOrderBy.groupedOrderByWithFractionalIndex;
|
|
79
|
+
exports.filterBy = filterBy.filterBy;
|
|
80
80
|
//# sourceMappingURL=index.cjs.map
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -114,16 +114,133 @@ function compareKeys(a, b) {
|
|
|
114
114
|
return typeof a === `string` ? -1 : 1;
|
|
115
115
|
}
|
|
116
116
|
function serializeValue(value) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
if (isJsonSafeStructuralValue(value, /* @__PURE__ */ new Set())) {
|
|
118
|
+
return JSON.stringify(toStableJsonValue(value));
|
|
119
|
+
}
|
|
120
|
+
return `~${JSON.stringify(toCanonicalValue(value, /* @__PURE__ */ new Set()))}`;
|
|
121
|
+
}
|
|
122
|
+
function isJsonSafeStructuralValue(value, ancestors) {
|
|
123
|
+
if (value === null) return true;
|
|
124
|
+
switch (typeof value) {
|
|
125
|
+
case `boolean`:
|
|
126
|
+
case `string`:
|
|
127
|
+
return true;
|
|
128
|
+
case `number`:
|
|
129
|
+
return Number.isFinite(value);
|
|
130
|
+
case `undefined`:
|
|
131
|
+
case `bigint`:
|
|
132
|
+
case `symbol`:
|
|
133
|
+
case `function`:
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
137
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Uint8Array || value instanceof Map || value instanceof Set) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return Array.isArray(value) ? value.every((item) => isJsonSafeStructuralValue(item, ancestors)) : Object.keys(value).every(
|
|
141
|
+
(key) => isJsonSafeStructuralValue(
|
|
142
|
+
value[key],
|
|
143
|
+
ancestors
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function toStableJsonValue(value) {
|
|
149
|
+
if (value === null || typeof value === `boolean` || typeof value === `number` || typeof value === `string`) {
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
return value.map(toStableJsonValue);
|
|
154
|
+
}
|
|
155
|
+
return Object.fromEntries(
|
|
156
|
+
Object.keys(value).sort().map((key) => [
|
|
157
|
+
key,
|
|
158
|
+
toStableJsonValue(value[key])
|
|
159
|
+
])
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function toCanonicalValue(value, ancestors) {
|
|
163
|
+
if (value === void 0) return [`undefined`];
|
|
164
|
+
if (value === null) return [`null`];
|
|
165
|
+
switch (typeof value) {
|
|
166
|
+
case `boolean`:
|
|
167
|
+
return [`boolean`, value];
|
|
168
|
+
case `number`:
|
|
169
|
+
if (Number.isNaN(value)) return [`number`, `NaN`];
|
|
170
|
+
if (value === Infinity) return [`number`, `Infinity`];
|
|
171
|
+
if (value === -Infinity) return [`number`, `-Infinity`];
|
|
172
|
+
return [`number`, value === 0 ? 0 : value];
|
|
173
|
+
case `bigint`:
|
|
174
|
+
return [`bigint`, value.toString()];
|
|
175
|
+
case `string`:
|
|
176
|
+
return [`string`, value];
|
|
177
|
+
case `symbol`:
|
|
178
|
+
case `function`:
|
|
179
|
+
throw new TypeError(
|
|
180
|
+
`Cannot serialize ${typeof value} as a structural relation key`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
184
|
+
if (value instanceof Date) {
|
|
185
|
+
const timestamp = value.getTime();
|
|
186
|
+
return Number.isNaN(timestamp) ? [`date`, `Invalid`] : [`date`, timestamp];
|
|
187
|
+
}
|
|
188
|
+
if (value instanceof RegExp) {
|
|
189
|
+
return [`regexp`, value.source, value.flags];
|
|
190
|
+
}
|
|
191
|
+
if (value instanceof Uint8Array) {
|
|
192
|
+
return [`bytes`, Array.from(value)];
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(value)) {
|
|
195
|
+
return [`array`, value.map((item) => toCanonicalValue(item, ancestors))];
|
|
196
|
+
}
|
|
197
|
+
if (value instanceof Map) {
|
|
198
|
+
const entries2 = [...value.entries()].map(
|
|
199
|
+
([key, entryValue]) => [
|
|
200
|
+
toCanonicalValue(key, ancestors),
|
|
201
|
+
toCanonicalValue(entryValue, ancestors)
|
|
202
|
+
]
|
|
203
|
+
);
|
|
204
|
+
entries2.sort(
|
|
205
|
+
(left, right) => compareSerializedValues(JSON.stringify(left), JSON.stringify(right))
|
|
206
|
+
);
|
|
207
|
+
return [`map`, entries2];
|
|
120
208
|
}
|
|
121
|
-
if (
|
|
122
|
-
|
|
209
|
+
if (value instanceof Set) {
|
|
210
|
+
const entries2 = [...value].map(
|
|
211
|
+
(entry) => toCanonicalValue(entry, ancestors)
|
|
212
|
+
);
|
|
213
|
+
entries2.sort(
|
|
214
|
+
(left, right) => compareSerializedValues(JSON.stringify(left), JSON.stringify(right))
|
|
215
|
+
);
|
|
216
|
+
return [`set`, entries2];
|
|
123
217
|
}
|
|
124
|
-
|
|
218
|
+
const entries = Object.keys(value).sort().map(
|
|
219
|
+
(key) => [
|
|
220
|
+
key,
|
|
221
|
+
toCanonicalValue(
|
|
222
|
+
value[key],
|
|
223
|
+
ancestors
|
|
224
|
+
)
|
|
225
|
+
]
|
|
226
|
+
);
|
|
227
|
+
return [`object`, entries];
|
|
125
228
|
});
|
|
126
229
|
}
|
|
230
|
+
function compareSerializedValues(left, right) {
|
|
231
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
232
|
+
}
|
|
233
|
+
function withAcyclicValue(value, ancestors, encode) {
|
|
234
|
+
if (ancestors.has(value)) {
|
|
235
|
+
throw new TypeError(`Cannot serialize a cyclic structural relation key`);
|
|
236
|
+
}
|
|
237
|
+
ancestors.add(value);
|
|
238
|
+
try {
|
|
239
|
+
return encode();
|
|
240
|
+
} finally {
|
|
241
|
+
ancestors.delete(value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
127
244
|
exports.DefaultMap = DefaultMap;
|
|
128
245
|
exports.ObjectIdGenerator = ObjectIdGenerator;
|
|
129
246
|
exports.binarySearch = binarySearch;
|
package/dist/cjs/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string,\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>,\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number,\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U,\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n\nexport type HRange = [number, number] // half-open [start, end[ i.e. end is exclusive\n\n/**\n * Computes the difference between two half-open ranges.\n * @param a - The first half-open range\n * @param b - The second half-open range\n * @returns The difference between the two ranges\n */\nexport function diffHalfOpen(a: HRange, b: HRange) {\n const [a1, a2] = a\n const [b1, b2] = b\n\n // A \\ B can be up to two segments (left and right of the overlap)\n const onlyInA: Array<number> = [\n ...range(a1, Math.min(a2, b1)), // left side of A outside B\n ...range(Math.max(a1, b2), a2), // right side of A outside B\n ]\n\n // B \\ A similarly\n const onlyInB: Array<number> = [\n ...range(b1, Math.min(b2, a1)),\n ...range(Math.max(b1, a2), b2),\n ]\n\n return { onlyInA, onlyInB }\n}\n\nfunction range(start: number, end: number): Array<number> {\n const out: Array<number> = []\n for (let i = start; i < end; i++) out.push(i)\n return out\n}\n\n/**\n * Compares two keys (string | number) in a consistent, deterministic way.\n * Handles mixed types by ordering strings before numbers.\n */\nexport function compareKeys(a: string | number, b: string | number): number {\n // Same type: compare directly\n if (typeof a === typeof b) {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n // Different types: strings come before numbers\n return typeof a === `string` ? -1 : 1\n}\n\n/**\n * Serializes a value for use as a key, handling BigInt and Date values that JSON.stringify cannot handle.\n * Uses JSON.stringify with a replacer function to convert BigInt values to strings and Date values to ISO strings.\n * This is used for creating string keys in groupBy operations.\n */\nexport function serializeValue(value: unknown): string {\n return JSON.stringify(value, (_, val) => {\n if (typeof val === 'bigint') {\n return val.toString()\n }\n if (val instanceof Date) {\n return val.toISOString()\n }\n return val\n })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string,\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>,\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number,\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U,\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n\nexport type HRange = [number, number] // half-open [start, end[ i.e. end is exclusive\n\n/**\n * Computes the difference between two half-open ranges.\n * @param a - The first half-open range\n * @param b - The second half-open range\n * @returns The difference between the two ranges\n */\nexport function diffHalfOpen(a: HRange, b: HRange) {\n const [a1, a2] = a\n const [b1, b2] = b\n\n // A \\ B can be up to two segments (left and right of the overlap)\n const onlyInA: Array<number> = [\n ...range(a1, Math.min(a2, b1)), // left side of A outside B\n ...range(Math.max(a1, b2), a2), // right side of A outside B\n ]\n\n // B \\ A similarly\n const onlyInB: Array<number> = [\n ...range(b1, Math.min(b2, a1)),\n ...range(Math.max(b1, a2), b2),\n ]\n\n return { onlyInA, onlyInB }\n}\n\nfunction range(start: number, end: number): Array<number> {\n const out: Array<number> = []\n for (let i = start; i < end; i++) out.push(i)\n return out\n}\n\n/**\n * Compares two keys (string | number) in a consistent, deterministic way.\n * Handles mixed types by ordering strings before numbers.\n */\nexport function compareKeys(a: string | number, b: string | number): number {\n // Same type: compare directly\n if (typeof a === typeof b) {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n // Different types: strings come before numbers\n return typeof a === `string` ? -1 : 1\n}\n\ntype CanonicalValue =\n | readonly [`undefined`]\n | readonly [`null`]\n | readonly [`boolean`, boolean]\n | readonly [`number`, number | `NaN` | `Infinity` | `-Infinity`]\n | readonly [`bigint`, string]\n | readonly [`string`, string]\n | readonly [`date`, number | `Invalid`]\n | readonly [`regexp`, string, string]\n | readonly [`bytes`, Array<number>]\n | readonly [`array`, Array<CanonicalValue>]\n | readonly [`map`, Array<readonly [CanonicalValue, CanonicalValue]>]\n | readonly [`set`, Array<CanonicalValue>]\n | readonly [`object`, Array<readonly [string, CanonicalValue]>]\n\n/**\n * Serializes a supported query value into one canonical key.\n *\n * JSON's native encoding is not suitable for relation keys: it merges BigInt\n * with strings when a replacer is used, merges NaN with null, drops undefined,\n * and depends on object insertion order. Ordinary JSON values keep their\n * established wire form. Values that need richer types use a reserved prefix\n * plus a structural, type-tagged encoding.\n */\nexport function serializeValue(value: unknown): string {\n if (isJsonSafeStructuralValue(value, new Set())) {\n return JSON.stringify(toStableJsonValue(value))\n }\n\n return `~${JSON.stringify(toCanonicalValue(value, new Set()))}`\n}\n\ntype JsonValue =\n | null\n | boolean\n | number\n | string\n | Array<JsonValue>\n | { [key: string]: JsonValue }\n\nfunction isJsonSafeStructuralValue(\n value: unknown,\n ancestors: Set<object>,\n): boolean {\n if (value === null) return true\n\n switch (typeof value) {\n case `boolean`:\n case `string`:\n return true\n case `number`:\n return Number.isFinite(value)\n case `undefined`:\n case `bigint`:\n case `symbol`:\n case `function`:\n return false\n }\n\n return withAcyclicValue(value, ancestors, () => {\n if (\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Uint8Array ||\n value instanceof Map ||\n value instanceof Set\n ) {\n return false\n }\n\n return Array.isArray(value)\n ? value.every((item) => isJsonSafeStructuralValue(item, ancestors))\n : Object.keys(value).every((key) =>\n isJsonSafeStructuralValue(\n (value as Record<string, unknown>)[key],\n ancestors,\n ),\n )\n })\n}\n\nfunction toStableJsonValue(value: unknown): JsonValue {\n if (\n value === null ||\n typeof value === `boolean` ||\n typeof value === `number` ||\n typeof value === `string`\n ) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map(toStableJsonValue)\n }\n\n return Object.fromEntries(\n Object.keys(value as object)\n .sort()\n .map((key) => [\n key,\n toStableJsonValue((value as Record<string, unknown>)[key]),\n ]),\n )\n}\n\nfunction toCanonicalValue(\n value: unknown,\n ancestors: Set<object>,\n): CanonicalValue {\n if (value === undefined) return [`undefined`]\n if (value === null) return [`null`]\n\n switch (typeof value) {\n case `boolean`:\n return [`boolean`, value]\n case `number`:\n if (Number.isNaN(value)) return [`number`, `NaN`]\n if (value === Infinity) return [`number`, `Infinity`]\n if (value === -Infinity) return [`number`, `-Infinity`]\n return [`number`, value === 0 ? 0 : value]\n case `bigint`:\n return [`bigint`, value.toString()]\n case `string`:\n return [`string`, value]\n case `symbol`:\n case `function`:\n throw new TypeError(\n `Cannot serialize ${typeof value} as a structural relation key`,\n )\n }\n\n return withAcyclicValue(value, ancestors, () => {\n if (value instanceof Date) {\n const timestamp = value.getTime()\n return Number.isNaN(timestamp)\n ? ([`date`, `Invalid`] as const)\n : ([`date`, timestamp] as const)\n }\n\n if (value instanceof RegExp) {\n return [`regexp`, value.source, value.flags]\n }\n\n if (value instanceof Uint8Array) {\n return [`bytes`, Array.from(value)]\n }\n\n if (Array.isArray(value)) {\n return [`array`, value.map((item) => toCanonicalValue(item, ancestors))]\n }\n\n if (value instanceof Map) {\n const entries = [...value.entries()].map(\n ([key, entryValue]) =>\n [\n toCanonicalValue(key, ancestors),\n toCanonicalValue(entryValue, ancestors),\n ] as const,\n )\n entries.sort((left, right) =>\n compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),\n )\n return [`map`, entries]\n }\n\n if (value instanceof Set) {\n const entries = [...value].map((entry) =>\n toCanonicalValue(entry, ancestors),\n )\n entries.sort((left, right) =>\n compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),\n )\n return [`set`, entries]\n }\n\n const entries = Object.keys(value)\n .sort()\n .map(\n (key) =>\n [\n key,\n toCanonicalValue(\n (value as Record<string, unknown>)[key],\n ancestors,\n ),\n ] as const,\n )\n return [`object`, entries]\n })\n}\n\nfunction compareSerializedValues(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nfunction withAcyclicValue<T>(\n value: object,\n ancestors: Set<object>,\n encode: () => T,\n): T {\n if (ancestors.has(value)) {\n throw new TypeError(`Cannot serialize a cyclic structural relation key`)\n }\n\n ancestors.add(value)\n try {\n return encode()\n } finally {\n ancestors.delete(value)\n }\n}\n"],"names":["entries"],"mappings":"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"}
|
package/dist/cjs/utils.d.cts
CHANGED
|
@@ -58,8 +58,12 @@ export declare function diffHalfOpen(a: HRange, b: HRange): {
|
|
|
58
58
|
*/
|
|
59
59
|
export declare function compareKeys(a: string | number, b: string | number): number;
|
|
60
60
|
/**
|
|
61
|
-
* Serializes a value
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Serializes a supported query value into one canonical key.
|
|
62
|
+
*
|
|
63
|
+
* JSON's native encoding is not suitable for relation keys: it merges BigInt
|
|
64
|
+
* with strings when a replacer is used, merges NaN with null, drops undefined,
|
|
65
|
+
* and depends on object insertion order. Ordinary JSON values keep their
|
|
66
|
+
* established wire form. Values that need richer types use a reserved prefix
|
|
67
|
+
* plus a structural, type-tagged encoding.
|
|
64
68
|
*/
|
|
65
69
|
export declare function serializeValue(value: unknown): string;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { D2, RootStreamBuilder, StreamBuilder } from "./d2.js";
|
|
2
2
|
import { MultiSet } from "./multiset.js";
|
|
3
3
|
import { compareKeys, serializeValue } from "./utils.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { CountOperator, count } from "./operators/count.js";
|
|
7
|
-
import { DebugOperator, debug } from "./operators/debug.js";
|
|
8
|
-
import { DistinctOperator, distinct } from "./operators/distinct.js";
|
|
9
|
-
import { FilterOperator, filter } from "./operators/filter.js";
|
|
10
|
-
import { GroupedTopKWithFractionalIndexOperator, groupedTopKWithFractionalIndex } from "./operators/groupedTopKWithFractionalIndex.js";
|
|
11
|
-
import { JoinOperator, antiJoin, fullJoin, innerJoin, join, leftJoin, rightJoin } from "./operators/join.js";
|
|
4
|
+
import { groupBy, groupByOperators } from "./operators/groupBy.js";
|
|
5
|
+
import { pipe } from "./operators/pipe.js";
|
|
12
6
|
import { MapOperator, map } from "./operators/map.js";
|
|
7
|
+
import { TapOperator, tap } from "./operators/tap.js";
|
|
8
|
+
import { FilterOperator, filter } from "./operators/filter.js";
|
|
13
9
|
import { NegateOperator, negate } from "./operators/negate.js";
|
|
10
|
+
import { ConcatOperator, concat } from "./operators/concat.js";
|
|
11
|
+
import { DebugOperator, debug } from "./operators/debug.js";
|
|
14
12
|
import { OutputOperator, output } from "./operators/output.js";
|
|
13
|
+
import { ConsolidateOperator, consolidate } from "./operators/consolidate.js";
|
|
14
|
+
import { JoinOperator, antiJoin, fullJoin, innerJoin, join, leftJoin, rightJoin } from "./operators/join.js";
|
|
15
15
|
import { ReduceOperator, reduce } from "./operators/reduce.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { filterBy } from "./operators/filterBy.js";
|
|
19
|
-
import { groupBy, groupByOperators } from "./operators/groupBy.js";
|
|
20
|
-
import { groupedOrderByWithFractionalIndex } from "./operators/groupedOrderBy.js";
|
|
16
|
+
import { CountOperator, count } from "./operators/count.js";
|
|
17
|
+
import { DistinctOperator, distinct } from "./operators/distinct.js";
|
|
21
18
|
import { keyBy, rekey, unkey } from "./operators/keying.js";
|
|
22
|
-
import { orderBy, orderByWithFractionalIndex, orderByWithFractionalIndexBase, orderByWithIndex } from "./operators/orderBy.js";
|
|
23
|
-
import { pipe } from "./operators/pipe.js";
|
|
24
19
|
import { topK, topKWithIndex } from "./operators/topK.js";
|
|
20
|
+
import { TopKWithFractionalIndexOperator, topKWithFractionalIndex } from "./operators/topKWithFractionalIndex.js";
|
|
21
|
+
import { GroupedTopKWithFractionalIndexOperator, groupedTopKWithFractionalIndex } from "./operators/groupedTopKWithFractionalIndex.js";
|
|
22
|
+
import { orderBy, orderByWithFractionalIndex, orderByWithFractionalIndexBase, orderByWithIndex } from "./operators/orderBy.js";
|
|
23
|
+
import { groupedOrderByWithFractionalIndex } from "./operators/groupedOrderBy.js";
|
|
24
|
+
import { filterBy } from "./operators/filterBy.js";
|
|
25
25
|
export {
|
|
26
26
|
ConcatOperator,
|
|
27
27
|
ConsolidateOperator,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateKeyBetween } from "fractional-indexing";
|
|
2
|
-
import { compareKeys,
|
|
2
|
+
import { compareKeys, diffHalfOpen, binarySearch } from "../utils.js";
|
|
3
3
|
function indexedValue(value, index) {
|
|
4
4
|
return [value, index];
|
|
5
5
|
}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -58,8 +58,12 @@ export declare function diffHalfOpen(a: HRange, b: HRange): {
|
|
|
58
58
|
*/
|
|
59
59
|
export declare function compareKeys(a: string | number, b: string | number): number;
|
|
60
60
|
/**
|
|
61
|
-
* Serializes a value
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Serializes a supported query value into one canonical key.
|
|
62
|
+
*
|
|
63
|
+
* JSON's native encoding is not suitable for relation keys: it merges BigInt
|
|
64
|
+
* with strings when a replacer is used, merges NaN with null, drops undefined,
|
|
65
|
+
* and depends on object insertion order. Ordinary JSON values keep their
|
|
66
|
+
* established wire form. Values that need richer types use a reserved prefix
|
|
67
|
+
* plus a structural, type-tagged encoding.
|
|
64
68
|
*/
|
|
65
69
|
export declare function serializeValue(value: unknown): string;
|
package/dist/esm/utils.js
CHANGED
|
@@ -112,16 +112,133 @@ function compareKeys(a, b) {
|
|
|
112
112
|
return typeof a === `string` ? -1 : 1;
|
|
113
113
|
}
|
|
114
114
|
function serializeValue(value) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
if (isJsonSafeStructuralValue(value, /* @__PURE__ */ new Set())) {
|
|
116
|
+
return JSON.stringify(toStableJsonValue(value));
|
|
117
|
+
}
|
|
118
|
+
return `~${JSON.stringify(toCanonicalValue(value, /* @__PURE__ */ new Set()))}`;
|
|
119
|
+
}
|
|
120
|
+
function isJsonSafeStructuralValue(value, ancestors) {
|
|
121
|
+
if (value === null) return true;
|
|
122
|
+
switch (typeof value) {
|
|
123
|
+
case `boolean`:
|
|
124
|
+
case `string`:
|
|
125
|
+
return true;
|
|
126
|
+
case `number`:
|
|
127
|
+
return Number.isFinite(value);
|
|
128
|
+
case `undefined`:
|
|
129
|
+
case `bigint`:
|
|
130
|
+
case `symbol`:
|
|
131
|
+
case `function`:
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
135
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Uint8Array || value instanceof Map || value instanceof Set) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return Array.isArray(value) ? value.every((item) => isJsonSafeStructuralValue(item, ancestors)) : Object.keys(value).every(
|
|
139
|
+
(key) => isJsonSafeStructuralValue(
|
|
140
|
+
value[key],
|
|
141
|
+
ancestors
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function toStableJsonValue(value) {
|
|
147
|
+
if (value === null || typeof value === `boolean` || typeof value === `number` || typeof value === `string`) {
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
return value.map(toStableJsonValue);
|
|
152
|
+
}
|
|
153
|
+
return Object.fromEntries(
|
|
154
|
+
Object.keys(value).sort().map((key) => [
|
|
155
|
+
key,
|
|
156
|
+
toStableJsonValue(value[key])
|
|
157
|
+
])
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function toCanonicalValue(value, ancestors) {
|
|
161
|
+
if (value === void 0) return [`undefined`];
|
|
162
|
+
if (value === null) return [`null`];
|
|
163
|
+
switch (typeof value) {
|
|
164
|
+
case `boolean`:
|
|
165
|
+
return [`boolean`, value];
|
|
166
|
+
case `number`:
|
|
167
|
+
if (Number.isNaN(value)) return [`number`, `NaN`];
|
|
168
|
+
if (value === Infinity) return [`number`, `Infinity`];
|
|
169
|
+
if (value === -Infinity) return [`number`, `-Infinity`];
|
|
170
|
+
return [`number`, value === 0 ? 0 : value];
|
|
171
|
+
case `bigint`:
|
|
172
|
+
return [`bigint`, value.toString()];
|
|
173
|
+
case `string`:
|
|
174
|
+
return [`string`, value];
|
|
175
|
+
case `symbol`:
|
|
176
|
+
case `function`:
|
|
177
|
+
throw new TypeError(
|
|
178
|
+
`Cannot serialize ${typeof value} as a structural relation key`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
182
|
+
if (value instanceof Date) {
|
|
183
|
+
const timestamp = value.getTime();
|
|
184
|
+
return Number.isNaN(timestamp) ? [`date`, `Invalid`] : [`date`, timestamp];
|
|
185
|
+
}
|
|
186
|
+
if (value instanceof RegExp) {
|
|
187
|
+
return [`regexp`, value.source, value.flags];
|
|
188
|
+
}
|
|
189
|
+
if (value instanceof Uint8Array) {
|
|
190
|
+
return [`bytes`, Array.from(value)];
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
return [`array`, value.map((item) => toCanonicalValue(item, ancestors))];
|
|
194
|
+
}
|
|
195
|
+
if (value instanceof Map) {
|
|
196
|
+
const entries2 = [...value.entries()].map(
|
|
197
|
+
([key, entryValue]) => [
|
|
198
|
+
toCanonicalValue(key, ancestors),
|
|
199
|
+
toCanonicalValue(entryValue, ancestors)
|
|
200
|
+
]
|
|
201
|
+
);
|
|
202
|
+
entries2.sort(
|
|
203
|
+
(left, right) => compareSerializedValues(JSON.stringify(left), JSON.stringify(right))
|
|
204
|
+
);
|
|
205
|
+
return [`map`, entries2];
|
|
118
206
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
207
|
+
if (value instanceof Set) {
|
|
208
|
+
const entries2 = [...value].map(
|
|
209
|
+
(entry) => toCanonicalValue(entry, ancestors)
|
|
210
|
+
);
|
|
211
|
+
entries2.sort(
|
|
212
|
+
(left, right) => compareSerializedValues(JSON.stringify(left), JSON.stringify(right))
|
|
213
|
+
);
|
|
214
|
+
return [`set`, entries2];
|
|
121
215
|
}
|
|
122
|
-
|
|
216
|
+
const entries = Object.keys(value).sort().map(
|
|
217
|
+
(key) => [
|
|
218
|
+
key,
|
|
219
|
+
toCanonicalValue(
|
|
220
|
+
value[key],
|
|
221
|
+
ancestors
|
|
222
|
+
)
|
|
223
|
+
]
|
|
224
|
+
);
|
|
225
|
+
return [`object`, entries];
|
|
123
226
|
});
|
|
124
227
|
}
|
|
228
|
+
function compareSerializedValues(left, right) {
|
|
229
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
230
|
+
}
|
|
231
|
+
function withAcyclicValue(value, ancestors, encode) {
|
|
232
|
+
if (ancestors.has(value)) {
|
|
233
|
+
throw new TypeError(`Cannot serialize a cyclic structural relation key`);
|
|
234
|
+
}
|
|
235
|
+
ancestors.add(value);
|
|
236
|
+
try {
|
|
237
|
+
return encode();
|
|
238
|
+
} finally {
|
|
239
|
+
ancestors.delete(value);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
125
242
|
export {
|
|
126
243
|
DefaultMap,
|
|
127
244
|
ObjectIdGenerator,
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string,\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>,\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number,\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U,\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n\nexport type HRange = [number, number] // half-open [start, end[ i.e. end is exclusive\n\n/**\n * Computes the difference between two half-open ranges.\n * @param a - The first half-open range\n * @param b - The second half-open range\n * @returns The difference between the two ranges\n */\nexport function diffHalfOpen(a: HRange, b: HRange) {\n const [a1, a2] = a\n const [b1, b2] = b\n\n // A \\ B can be up to two segments (left and right of the overlap)\n const onlyInA: Array<number> = [\n ...range(a1, Math.min(a2, b1)), // left side of A outside B\n ...range(Math.max(a1, b2), a2), // right side of A outside B\n ]\n\n // B \\ A similarly\n const onlyInB: Array<number> = [\n ...range(b1, Math.min(b2, a1)),\n ...range(Math.max(b1, a2), b2),\n ]\n\n return { onlyInA, onlyInB }\n}\n\nfunction range(start: number, end: number): Array<number> {\n const out: Array<number> = []\n for (let i = start; i < end; i++) out.push(i)\n return out\n}\n\n/**\n * Compares two keys (string | number) in a consistent, deterministic way.\n * Handles mixed types by ordering strings before numbers.\n */\nexport function compareKeys(a: string | number, b: string | number): number {\n // Same type: compare directly\n if (typeof a === typeof b) {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n // Different types: strings come before numbers\n return typeof a === `string` ? -1 : 1\n}\n\n/**\n * Serializes a value for use as a key, handling BigInt and Date values that JSON.stringify cannot handle.\n * Uses JSON.stringify with a replacer function to convert BigInt values to strings and Date values to ISO strings.\n * This is used for creating string keys in groupBy operations.\n */\nexport function serializeValue(value: unknown): string {\n return JSON.stringify(value, (_, val) => {\n if (typeof val === 'bigint') {\n return val.toString()\n }\n if (val instanceof Date) {\n return val.toISOString()\n }\n return val\n })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string,\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>,\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number,\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U,\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n\nexport type HRange = [number, number] // half-open [start, end[ i.e. end is exclusive\n\n/**\n * Computes the difference between two half-open ranges.\n * @param a - The first half-open range\n * @param b - The second half-open range\n * @returns The difference between the two ranges\n */\nexport function diffHalfOpen(a: HRange, b: HRange) {\n const [a1, a2] = a\n const [b1, b2] = b\n\n // A \\ B can be up to two segments (left and right of the overlap)\n const onlyInA: Array<number> = [\n ...range(a1, Math.min(a2, b1)), // left side of A outside B\n ...range(Math.max(a1, b2), a2), // right side of A outside B\n ]\n\n // B \\ A similarly\n const onlyInB: Array<number> = [\n ...range(b1, Math.min(b2, a1)),\n ...range(Math.max(b1, a2), b2),\n ]\n\n return { onlyInA, onlyInB }\n}\n\nfunction range(start: number, end: number): Array<number> {\n const out: Array<number> = []\n for (let i = start; i < end; i++) out.push(i)\n return out\n}\n\n/**\n * Compares two keys (string | number) in a consistent, deterministic way.\n * Handles mixed types by ordering strings before numbers.\n */\nexport function compareKeys(a: string | number, b: string | number): number {\n // Same type: compare directly\n if (typeof a === typeof b) {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n // Different types: strings come before numbers\n return typeof a === `string` ? -1 : 1\n}\n\ntype CanonicalValue =\n | readonly [`undefined`]\n | readonly [`null`]\n | readonly [`boolean`, boolean]\n | readonly [`number`, number | `NaN` | `Infinity` | `-Infinity`]\n | readonly [`bigint`, string]\n | readonly [`string`, string]\n | readonly [`date`, number | `Invalid`]\n | readonly [`regexp`, string, string]\n | readonly [`bytes`, Array<number>]\n | readonly [`array`, Array<CanonicalValue>]\n | readonly [`map`, Array<readonly [CanonicalValue, CanonicalValue]>]\n | readonly [`set`, Array<CanonicalValue>]\n | readonly [`object`, Array<readonly [string, CanonicalValue]>]\n\n/**\n * Serializes a supported query value into one canonical key.\n *\n * JSON's native encoding is not suitable for relation keys: it merges BigInt\n * with strings when a replacer is used, merges NaN with null, drops undefined,\n * and depends on object insertion order. Ordinary JSON values keep their\n * established wire form. Values that need richer types use a reserved prefix\n * plus a structural, type-tagged encoding.\n */\nexport function serializeValue(value: unknown): string {\n if (isJsonSafeStructuralValue(value, new Set())) {\n return JSON.stringify(toStableJsonValue(value))\n }\n\n return `~${JSON.stringify(toCanonicalValue(value, new Set()))}`\n}\n\ntype JsonValue =\n | null\n | boolean\n | number\n | string\n | Array<JsonValue>\n | { [key: string]: JsonValue }\n\nfunction isJsonSafeStructuralValue(\n value: unknown,\n ancestors: Set<object>,\n): boolean {\n if (value === null) return true\n\n switch (typeof value) {\n case `boolean`:\n case `string`:\n return true\n case `number`:\n return Number.isFinite(value)\n case `undefined`:\n case `bigint`:\n case `symbol`:\n case `function`:\n return false\n }\n\n return withAcyclicValue(value, ancestors, () => {\n if (\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Uint8Array ||\n value instanceof Map ||\n value instanceof Set\n ) {\n return false\n }\n\n return Array.isArray(value)\n ? value.every((item) => isJsonSafeStructuralValue(item, ancestors))\n : Object.keys(value).every((key) =>\n isJsonSafeStructuralValue(\n (value as Record<string, unknown>)[key],\n ancestors,\n ),\n )\n })\n}\n\nfunction toStableJsonValue(value: unknown): JsonValue {\n if (\n value === null ||\n typeof value === `boolean` ||\n typeof value === `number` ||\n typeof value === `string`\n ) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map(toStableJsonValue)\n }\n\n return Object.fromEntries(\n Object.keys(value as object)\n .sort()\n .map((key) => [\n key,\n toStableJsonValue((value as Record<string, unknown>)[key]),\n ]),\n )\n}\n\nfunction toCanonicalValue(\n value: unknown,\n ancestors: Set<object>,\n): CanonicalValue {\n if (value === undefined) return [`undefined`]\n if (value === null) return [`null`]\n\n switch (typeof value) {\n case `boolean`:\n return [`boolean`, value]\n case `number`:\n if (Number.isNaN(value)) return [`number`, `NaN`]\n if (value === Infinity) return [`number`, `Infinity`]\n if (value === -Infinity) return [`number`, `-Infinity`]\n return [`number`, value === 0 ? 0 : value]\n case `bigint`:\n return [`bigint`, value.toString()]\n case `string`:\n return [`string`, value]\n case `symbol`:\n case `function`:\n throw new TypeError(\n `Cannot serialize ${typeof value} as a structural relation key`,\n )\n }\n\n return withAcyclicValue(value, ancestors, () => {\n if (value instanceof Date) {\n const timestamp = value.getTime()\n return Number.isNaN(timestamp)\n ? ([`date`, `Invalid`] as const)\n : ([`date`, timestamp] as const)\n }\n\n if (value instanceof RegExp) {\n return [`regexp`, value.source, value.flags]\n }\n\n if (value instanceof Uint8Array) {\n return [`bytes`, Array.from(value)]\n }\n\n if (Array.isArray(value)) {\n return [`array`, value.map((item) => toCanonicalValue(item, ancestors))]\n }\n\n if (value instanceof Map) {\n const entries = [...value.entries()].map(\n ([key, entryValue]) =>\n [\n toCanonicalValue(key, ancestors),\n toCanonicalValue(entryValue, ancestors),\n ] as const,\n )\n entries.sort((left, right) =>\n compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),\n )\n return [`map`, entries]\n }\n\n if (value instanceof Set) {\n const entries = [...value].map((entry) =>\n toCanonicalValue(entry, ancestors),\n )\n entries.sort((left, right) =>\n compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),\n )\n return [`set`, entries]\n }\n\n const entries = Object.keys(value)\n .sort()\n .map(\n (key) =>\n [\n key,\n toCanonicalValue(\n (value as Record<string, unknown>)[key],\n ancestors,\n ),\n ] as const,\n )\n return [`object`, entries]\n })\n}\n\nfunction compareSerializedValues(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nfunction withAcyclicValue<T>(\n value: object,\n ancestors: Set<object>,\n encode: () => T,\n): T {\n if (ancestors.has(value)) {\n throw new TypeError(`Cannot serialize a cyclic structural relation key`)\n }\n\n ancestors.add(value)\n try {\n return encode()\n } finally {\n ancestors.delete(value)\n }\n}\n"],"names":["entries"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/utils.ts
CHANGED
|
@@ -193,19 +193,214 @@ export function compareKeys(a: string | number, b: string | number): number {
|
|
|
193
193
|
return typeof a === `string` ? -1 : 1
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
type CanonicalValue =
|
|
197
|
+
| readonly [`undefined`]
|
|
198
|
+
| readonly [`null`]
|
|
199
|
+
| readonly [`boolean`, boolean]
|
|
200
|
+
| readonly [`number`, number | `NaN` | `Infinity` | `-Infinity`]
|
|
201
|
+
| readonly [`bigint`, string]
|
|
202
|
+
| readonly [`string`, string]
|
|
203
|
+
| readonly [`date`, number | `Invalid`]
|
|
204
|
+
| readonly [`regexp`, string, string]
|
|
205
|
+
| readonly [`bytes`, Array<number>]
|
|
206
|
+
| readonly [`array`, Array<CanonicalValue>]
|
|
207
|
+
| readonly [`map`, Array<readonly [CanonicalValue, CanonicalValue]>]
|
|
208
|
+
| readonly [`set`, Array<CanonicalValue>]
|
|
209
|
+
| readonly [`object`, Array<readonly [string, CanonicalValue]>]
|
|
210
|
+
|
|
196
211
|
/**
|
|
197
|
-
* Serializes a value
|
|
198
|
-
*
|
|
199
|
-
*
|
|
212
|
+
* Serializes a supported query value into one canonical key.
|
|
213
|
+
*
|
|
214
|
+
* JSON's native encoding is not suitable for relation keys: it merges BigInt
|
|
215
|
+
* with strings when a replacer is used, merges NaN with null, drops undefined,
|
|
216
|
+
* and depends on object insertion order. Ordinary JSON values keep their
|
|
217
|
+
* established wire form. Values that need richer types use a reserved prefix
|
|
218
|
+
* plus a structural, type-tagged encoding.
|
|
200
219
|
*/
|
|
201
220
|
export function serializeValue(value: unknown): string {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
221
|
+
if (isJsonSafeStructuralValue(value, new Set())) {
|
|
222
|
+
return JSON.stringify(toStableJsonValue(value))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return `~${JSON.stringify(toCanonicalValue(value, new Set()))}`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
type JsonValue =
|
|
229
|
+
| null
|
|
230
|
+
| boolean
|
|
231
|
+
| number
|
|
232
|
+
| string
|
|
233
|
+
| Array<JsonValue>
|
|
234
|
+
| { [key: string]: JsonValue }
|
|
235
|
+
|
|
236
|
+
function isJsonSafeStructuralValue(
|
|
237
|
+
value: unknown,
|
|
238
|
+
ancestors: Set<object>,
|
|
239
|
+
): boolean {
|
|
240
|
+
if (value === null) return true
|
|
241
|
+
|
|
242
|
+
switch (typeof value) {
|
|
243
|
+
case `boolean`:
|
|
244
|
+
case `string`:
|
|
245
|
+
return true
|
|
246
|
+
case `number`:
|
|
247
|
+
return Number.isFinite(value)
|
|
248
|
+
case `undefined`:
|
|
249
|
+
case `bigint`:
|
|
250
|
+
case `symbol`:
|
|
251
|
+
case `function`:
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
256
|
+
if (
|
|
257
|
+
value instanceof Date ||
|
|
258
|
+
value instanceof RegExp ||
|
|
259
|
+
value instanceof Uint8Array ||
|
|
260
|
+
value instanceof Map ||
|
|
261
|
+
value instanceof Set
|
|
262
|
+
) {
|
|
263
|
+
return false
|
|
205
264
|
}
|
|
206
|
-
|
|
207
|
-
|
|
265
|
+
|
|
266
|
+
return Array.isArray(value)
|
|
267
|
+
? value.every((item) => isJsonSafeStructuralValue(item, ancestors))
|
|
268
|
+
: Object.keys(value).every((key) =>
|
|
269
|
+
isJsonSafeStructuralValue(
|
|
270
|
+
(value as Record<string, unknown>)[key],
|
|
271
|
+
ancestors,
|
|
272
|
+
),
|
|
273
|
+
)
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function toStableJsonValue(value: unknown): JsonValue {
|
|
278
|
+
if (
|
|
279
|
+
value === null ||
|
|
280
|
+
typeof value === `boolean` ||
|
|
281
|
+
typeof value === `number` ||
|
|
282
|
+
typeof value === `string`
|
|
283
|
+
) {
|
|
284
|
+
return value
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (Array.isArray(value)) {
|
|
288
|
+
return value.map(toStableJsonValue)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return Object.fromEntries(
|
|
292
|
+
Object.keys(value as object)
|
|
293
|
+
.sort()
|
|
294
|
+
.map((key) => [
|
|
295
|
+
key,
|
|
296
|
+
toStableJsonValue((value as Record<string, unknown>)[key]),
|
|
297
|
+
]),
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function toCanonicalValue(
|
|
302
|
+
value: unknown,
|
|
303
|
+
ancestors: Set<object>,
|
|
304
|
+
): CanonicalValue {
|
|
305
|
+
if (value === undefined) return [`undefined`]
|
|
306
|
+
if (value === null) return [`null`]
|
|
307
|
+
|
|
308
|
+
switch (typeof value) {
|
|
309
|
+
case `boolean`:
|
|
310
|
+
return [`boolean`, value]
|
|
311
|
+
case `number`:
|
|
312
|
+
if (Number.isNaN(value)) return [`number`, `NaN`]
|
|
313
|
+
if (value === Infinity) return [`number`, `Infinity`]
|
|
314
|
+
if (value === -Infinity) return [`number`, `-Infinity`]
|
|
315
|
+
return [`number`, value === 0 ? 0 : value]
|
|
316
|
+
case `bigint`:
|
|
317
|
+
return [`bigint`, value.toString()]
|
|
318
|
+
case `string`:
|
|
319
|
+
return [`string`, value]
|
|
320
|
+
case `symbol`:
|
|
321
|
+
case `function`:
|
|
322
|
+
throw new TypeError(
|
|
323
|
+
`Cannot serialize ${typeof value} as a structural relation key`,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return withAcyclicValue(value, ancestors, () => {
|
|
328
|
+
if (value instanceof Date) {
|
|
329
|
+
const timestamp = value.getTime()
|
|
330
|
+
return Number.isNaN(timestamp)
|
|
331
|
+
? ([`date`, `Invalid`] as const)
|
|
332
|
+
: ([`date`, timestamp] as const)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (value instanceof RegExp) {
|
|
336
|
+
return [`regexp`, value.source, value.flags]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (value instanceof Uint8Array) {
|
|
340
|
+
return [`bytes`, Array.from(value)]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (Array.isArray(value)) {
|
|
344
|
+
return [`array`, value.map((item) => toCanonicalValue(item, ancestors))]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (value instanceof Map) {
|
|
348
|
+
const entries = [...value.entries()].map(
|
|
349
|
+
([key, entryValue]) =>
|
|
350
|
+
[
|
|
351
|
+
toCanonicalValue(key, ancestors),
|
|
352
|
+
toCanonicalValue(entryValue, ancestors),
|
|
353
|
+
] as const,
|
|
354
|
+
)
|
|
355
|
+
entries.sort((left, right) =>
|
|
356
|
+
compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),
|
|
357
|
+
)
|
|
358
|
+
return [`map`, entries]
|
|
208
359
|
}
|
|
209
|
-
|
|
360
|
+
|
|
361
|
+
if (value instanceof Set) {
|
|
362
|
+
const entries = [...value].map((entry) =>
|
|
363
|
+
toCanonicalValue(entry, ancestors),
|
|
364
|
+
)
|
|
365
|
+
entries.sort((left, right) =>
|
|
366
|
+
compareSerializedValues(JSON.stringify(left), JSON.stringify(right)),
|
|
367
|
+
)
|
|
368
|
+
return [`set`, entries]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const entries = Object.keys(value)
|
|
372
|
+
.sort()
|
|
373
|
+
.map(
|
|
374
|
+
(key) =>
|
|
375
|
+
[
|
|
376
|
+
key,
|
|
377
|
+
toCanonicalValue(
|
|
378
|
+
(value as Record<string, unknown>)[key],
|
|
379
|
+
ancestors,
|
|
380
|
+
),
|
|
381
|
+
] as const,
|
|
382
|
+
)
|
|
383
|
+
return [`object`, entries]
|
|
210
384
|
})
|
|
211
385
|
}
|
|
386
|
+
|
|
387
|
+
function compareSerializedValues(left: string, right: string): number {
|
|
388
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function withAcyclicValue<T>(
|
|
392
|
+
value: object,
|
|
393
|
+
ancestors: Set<object>,
|
|
394
|
+
encode: () => T,
|
|
395
|
+
): T {
|
|
396
|
+
if (ancestors.has(value)) {
|
|
397
|
+
throw new TypeError(`Cannot serialize a cyclic structural relation key`)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
ancestors.add(value)
|
|
401
|
+
try {
|
|
402
|
+
return encode()
|
|
403
|
+
} finally {
|
|
404
|
+
ancestors.delete(value)
|
|
405
|
+
}
|
|
406
|
+
}
|