@michaelstewart/convex-tanstack-db-collection 0.0.1

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.
@@ -0,0 +1,679 @@
1
+ function isTypeMarker(value) {
2
+ return typeof value === `object` && value !== null && `__type` in value && typeof value.__type === `string`;
3
+ }
4
+ function serializeValue(value) {
5
+ if (value === void 0) {
6
+ return { __type: `undefined` };
7
+ }
8
+ if (typeof value === `number`) {
9
+ if (Number.isNaN(value)) {
10
+ return { __type: `nan` };
11
+ }
12
+ if (value === Number.POSITIVE_INFINITY) {
13
+ return { __type: `infinity`, sign: 1 };
14
+ }
15
+ if (value === Number.NEGATIVE_INFINITY) {
16
+ return { __type: `infinity`, sign: -1 };
17
+ }
18
+ }
19
+ if (value === null || typeof value === `string` || typeof value === `number` || typeof value === `boolean`) {
20
+ return value;
21
+ }
22
+ if (value instanceof Date) {
23
+ return { __type: `date`, value: value.toJSON() };
24
+ }
25
+ if (Array.isArray(value)) {
26
+ return value.map((item) => serializeValue(item));
27
+ }
28
+ if (typeof value === `object`) {
29
+ return Object.fromEntries(
30
+ Object.entries(value).map(([key, val]) => [
31
+ key,
32
+ serializeValue(val)
33
+ ])
34
+ );
35
+ }
36
+ return value;
37
+ }
38
+ function deserializeValue(value) {
39
+ if (isTypeMarker(value)) {
40
+ switch (value.__type) {
41
+ case `undefined`:
42
+ return void 0;
43
+ case `nan`:
44
+ return NaN;
45
+ case `infinity`:
46
+ return value.sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
47
+ case `date`:
48
+ return new Date(value.value);
49
+ default:
50
+ return value;
51
+ }
52
+ }
53
+ if (value === null || typeof value === `string` || typeof value === `number` || typeof value === `boolean`) {
54
+ return value;
55
+ }
56
+ if (Array.isArray(value)) {
57
+ return value.map((item) => deserializeValue(item));
58
+ }
59
+ if (typeof value === `object`) {
60
+ return Object.fromEntries(
61
+ Object.entries(value).map(([key, val]) => [
62
+ key,
63
+ deserializeValue(val)
64
+ ])
65
+ );
66
+ }
67
+ return value;
68
+ }
69
+ function toKey(value) {
70
+ return JSON.stringify(serializeValue(value));
71
+ }
72
+ function fromKey(key) {
73
+ return deserializeValue(JSON.parse(key));
74
+ }
75
+ class ConvexSyncManager {
76
+ constructor(options) {
77
+ this.activeDimensions = /* @__PURE__ */ new Map();
78
+ this.refCounts = /* @__PURE__ */ new Map();
79
+ this.pendingFilters = {};
80
+ this.globalCursor = 0;
81
+ this.currentSubscription = null;
82
+ this.debounceTimer = null;
83
+ this.isProcessing = false;
84
+ this.markedReady = false;
85
+ this.hasRequestedGlobal = false;
86
+ this.globalRefCount = 0;
87
+ this.messagesSinceSubscription = 0;
88
+ this.callbacks = null;
89
+ this.client = options.client;
90
+ this.query = options.query;
91
+ this.filterDimensions = options.filterDimensions;
92
+ this.updatedAtFieldName = options.updatedAtFieldName;
93
+ this.debounceMs = options.debounceMs;
94
+ this.tailOverlapMs = options.tailOverlapMs;
95
+ this.resubscribeThreshold = options.resubscribeThreshold;
96
+ this.getKey = options.getKey;
97
+ for (const dim of this.filterDimensions) {
98
+ this.activeDimensions.set(dim.convexArg, /* @__PURE__ */ new Set());
99
+ }
100
+ }
101
+ /**
102
+ * Initialize the sync manager with callbacks from TanStack DB
103
+ */
104
+ setCallbacks(callbacks) {
105
+ this.callbacks = callbacks;
106
+ }
107
+ /**
108
+ * Create a composite key for ref counting multi-filter combinations.
109
+ * Uses serialized values for deterministic keys.
110
+ */
111
+ createCompositeKey(filters) {
112
+ const sorted = Object.keys(filters).sort().reduce(
113
+ (acc, key) => {
114
+ const values = filters[key];
115
+ if (values) {
116
+ acc[key] = values.map((v) => toKey(v)).sort();
117
+ }
118
+ return acc;
119
+ },
120
+ {}
121
+ );
122
+ return JSON.stringify(sorted);
123
+ }
124
+ /**
125
+ * Request filters to be synced (called by loadSubset)
126
+ * Filters are batched via debouncing for efficiency
127
+ */
128
+ requestFilters(filters) {
129
+ var _a;
130
+ if (this.filterDimensions.length === 0) {
131
+ this.globalRefCount++;
132
+ if (!this.hasRequestedGlobal) {
133
+ this.hasRequestedGlobal = true;
134
+ return this.scheduleProcessing();
135
+ }
136
+ return Promise.resolve();
137
+ }
138
+ const compositeKey = this.createCompositeKey(filters);
139
+ const count = this.refCounts.get(compositeKey) || 0;
140
+ this.refCounts.set(compositeKey, count + 1);
141
+ let hasNewValues = false;
142
+ for (const [convexArg, values] of Object.entries(filters)) {
143
+ const activeSet = this.activeDimensions.get(convexArg);
144
+ if (!activeSet) continue;
145
+ const dim = this.filterDimensions.find((d) => d.convexArg === convexArg);
146
+ if (dim == null ? void 0 : dim.single) {
147
+ const existingCount = activeSet.size;
148
+ const pendingCount = ((_a = this.pendingFilters[convexArg]) == null ? void 0 : _a.length) ?? 0;
149
+ const newValues = values.filter((v) => {
150
+ var _a2;
151
+ const serialized = toKey(v);
152
+ const alreadyActive = activeSet.has(serialized);
153
+ const alreadyPending = (_a2 = this.pendingFilters[convexArg]) == null ? void 0 : _a2.some(
154
+ (pv) => toKey(pv) === serialized
155
+ );
156
+ return !alreadyActive && !alreadyPending;
157
+ });
158
+ if (existingCount + pendingCount + newValues.length > 1) {
159
+ throw new Error(
160
+ `Filter '${dim.filterField}' is configured as single but multiple values were requested. Active: ${existingCount}, Pending: ${pendingCount}, New: ${newValues.length}. Use single: false if you need to sync multiple values.`
161
+ );
162
+ }
163
+ }
164
+ for (const value of values) {
165
+ const serialized = toKey(value);
166
+ if (!activeSet.has(serialized)) {
167
+ if (!this.pendingFilters[convexArg]) {
168
+ this.pendingFilters[convexArg] = [];
169
+ }
170
+ const alreadyPending = this.pendingFilters[convexArg].some(
171
+ (v) => toKey(v) === serialized
172
+ );
173
+ if (!alreadyPending) {
174
+ this.pendingFilters[convexArg].push(value);
175
+ hasNewValues = true;
176
+ }
177
+ }
178
+ }
179
+ }
180
+ if (hasNewValues) {
181
+ return this.scheduleProcessing();
182
+ }
183
+ return Promise.resolve();
184
+ }
185
+ /**
186
+ * Release filters when no longer needed (called by unloadSubset)
187
+ */
188
+ releaseFilters(filters) {
189
+ if (this.filterDimensions.length === 0) {
190
+ this.globalRefCount = Math.max(0, this.globalRefCount - 1);
191
+ if (this.globalRefCount === 0 && this.hasRequestedGlobal) {
192
+ this.hasRequestedGlobal = false;
193
+ this.updateSubscription();
194
+ }
195
+ return;
196
+ }
197
+ const compositeKey = this.createCompositeKey(filters);
198
+ const count = (this.refCounts.get(compositeKey) || 0) - 1;
199
+ if (count <= 0) {
200
+ this.refCounts.delete(compositeKey);
201
+ } else {
202
+ this.refCounts.set(compositeKey, count);
203
+ }
204
+ this.cleanupUnreferencedValues();
205
+ }
206
+ /**
207
+ * Remove values from activeDimensions that are no longer referenced
208
+ * by any composite key in refCounts
209
+ */
210
+ cleanupUnreferencedValues() {
211
+ const referencedValues = /* @__PURE__ */ new Map();
212
+ for (const dim of this.filterDimensions) {
213
+ referencedValues.set(dim.convexArg, /* @__PURE__ */ new Set());
214
+ }
215
+ for (const compositeKey of this.refCounts.keys()) {
216
+ try {
217
+ const filters = JSON.parse(compositeKey);
218
+ for (const [convexArg, serializedValues] of Object.entries(filters)) {
219
+ const refSet = referencedValues.get(convexArg);
220
+ if (refSet) {
221
+ for (const serialized of serializedValues) {
222
+ refSet.add(serialized);
223
+ }
224
+ }
225
+ }
226
+ } catch {
227
+ }
228
+ }
229
+ let needsSubscriptionUpdate = false;
230
+ for (const [convexArg, activeSet] of this.activeDimensions) {
231
+ const refSet = referencedValues.get(convexArg);
232
+ for (const serialized of activeSet) {
233
+ if (!refSet.has(serialized)) {
234
+ activeSet.delete(serialized);
235
+ needsSubscriptionUpdate = true;
236
+ }
237
+ }
238
+ }
239
+ if (needsSubscriptionUpdate) {
240
+ this.updateSubscription();
241
+ }
242
+ }
243
+ /**
244
+ * Schedule debounced processing of pending filters
245
+ */
246
+ scheduleProcessing() {
247
+ return new Promise((resolve, reject) => {
248
+ if (this.debounceTimer) {
249
+ clearTimeout(this.debounceTimer);
250
+ }
251
+ this.debounceTimer = setTimeout(async () => {
252
+ try {
253
+ await this.processFilterBatch();
254
+ resolve();
255
+ } catch (error) {
256
+ reject(error);
257
+ }
258
+ }, this.debounceMs);
259
+ });
260
+ }
261
+ /**
262
+ * Process the current batch of pending filters
263
+ */
264
+ async processFilterBatch() {
265
+ if (this.isProcessing) {
266
+ return;
267
+ }
268
+ const hasPendingFilters = Object.keys(this.pendingFilters).length > 0;
269
+ const needsGlobalSync = this.filterDimensions.length === 0 && this.hasRequestedGlobal;
270
+ if (!hasPendingFilters && !needsGlobalSync) {
271
+ return;
272
+ }
273
+ this.isProcessing = true;
274
+ try {
275
+ if (this.filterDimensions.length === 0) {
276
+ await this.runGlobalBackfill();
277
+ } else {
278
+ const newFilters = { ...this.pendingFilters };
279
+ this.pendingFilters = {};
280
+ for (const [convexArg, values] of Object.entries(newFilters)) {
281
+ const activeSet = this.activeDimensions.get(convexArg);
282
+ if (activeSet) {
283
+ for (const value of values) {
284
+ activeSet.add(toKey(value));
285
+ }
286
+ }
287
+ }
288
+ await this.runBackfill(newFilters);
289
+ }
290
+ this.updateSubscription();
291
+ if (!this.markedReady && this.callbacks) {
292
+ this.callbacks.markReady();
293
+ this.markedReady = true;
294
+ }
295
+ } finally {
296
+ this.isProcessing = false;
297
+ }
298
+ }
299
+ /**
300
+ * Run global backfill for 0-filter case
301
+ */
302
+ async runGlobalBackfill() {
303
+ try {
304
+ const args = { after: 0 };
305
+ const items = await this.client.query(this.query, args);
306
+ if (Array.isArray(items)) {
307
+ this.handleIncomingData(items);
308
+ }
309
+ } catch (error) {
310
+ console.error("[ConvexSyncManager] Global backfill error:", error);
311
+ throw error;
312
+ }
313
+ }
314
+ /**
315
+ * Run backfill query for new filter values to get their full history
316
+ */
317
+ async runBackfill(newFilters) {
318
+ if (Object.keys(newFilters).length === 0) return;
319
+ try {
320
+ const args = {
321
+ ...newFilters,
322
+ after: 0
323
+ };
324
+ const items = await this.client.query(this.query, args);
325
+ if (Array.isArray(items)) {
326
+ this.handleIncomingData(items);
327
+ }
328
+ } catch (error) {
329
+ console.error("[ConvexSyncManager] Backfill error:", error);
330
+ throw error;
331
+ }
332
+ }
333
+ /**
334
+ * Build query args from all active dimensions
335
+ */
336
+ buildQueryArgs(after) {
337
+ const args = { after };
338
+ for (const [convexArg, serializedValues] of this.activeDimensions) {
339
+ const values = [...serializedValues].map((s) => fromKey(s));
340
+ const dim = this.filterDimensions.find((d) => d.convexArg === convexArg);
341
+ args[convexArg] = (dim == null ? void 0 : dim.single) ? values[0] : values;
342
+ }
343
+ return args;
344
+ }
345
+ /**
346
+ * Update the live subscription to cover all active filter values
347
+ */
348
+ updateSubscription() {
349
+ if (this.currentSubscription) {
350
+ this.currentSubscription();
351
+ this.currentSubscription = null;
352
+ }
353
+ this.messagesSinceSubscription = 0;
354
+ if (this.filterDimensions.length === 0) {
355
+ if (!this.hasRequestedGlobal) {
356
+ return;
357
+ }
358
+ } else {
359
+ let hasActiveValues = false;
360
+ for (const activeSet of this.activeDimensions.values()) {
361
+ if (activeSet.size > 0) {
362
+ hasActiveValues = true;
363
+ break;
364
+ }
365
+ }
366
+ if (!hasActiveValues) {
367
+ return;
368
+ }
369
+ }
370
+ const cursor = Math.max(0, this.globalCursor - this.tailOverlapMs);
371
+ const args = this.buildQueryArgs(cursor);
372
+ if ("onUpdate" in this.client) {
373
+ const subscription = this.client.onUpdate(
374
+ this.query,
375
+ args,
376
+ (result) => {
377
+ if (result !== void 0) {
378
+ const items = result;
379
+ if (Array.isArray(items)) {
380
+ this.handleIncomingData(items);
381
+ }
382
+ }
383
+ },
384
+ (error) => {
385
+ console.error(`[ConvexSyncManager] Subscription error:`, error);
386
+ }
387
+ );
388
+ this.currentSubscription = () => subscription.unsubscribe();
389
+ } else {
390
+ const watch = this.client.watchQuery(this.query, args);
391
+ this.currentSubscription = watch.onUpdate(() => {
392
+ const result = watch.localQueryResult();
393
+ if (result !== void 0) {
394
+ const items = result;
395
+ if (Array.isArray(items)) {
396
+ this.handleIncomingData(items);
397
+ }
398
+ }
399
+ });
400
+ }
401
+ }
402
+ /**
403
+ * Handle incoming data from backfill or subscription
404
+ * Uses LWW (Last-Write-Wins) to resolve conflicts
405
+ */
406
+ handleIncomingData(items) {
407
+ if (!this.callbacks || items.length === 0) return;
408
+ const { collection, begin, write, commit } = this.callbacks;
409
+ const previousCursor = this.globalCursor;
410
+ let newItemCount = 0;
411
+ begin();
412
+ for (const item of items) {
413
+ const key = this.getKey(item);
414
+ const incomingTs = item[this.updatedAtFieldName];
415
+ if (incomingTs !== void 0 && incomingTs > this.globalCursor) {
416
+ this.globalCursor = incomingTs;
417
+ }
418
+ const existing = collection.get(key);
419
+ if (!existing) {
420
+ write({ type: `insert`, value: item });
421
+ if (incomingTs !== void 0 && incomingTs > previousCursor) {
422
+ newItemCount++;
423
+ }
424
+ } else {
425
+ const existingTs = existing[this.updatedAtFieldName];
426
+ if (incomingTs !== void 0 && existingTs !== void 0) {
427
+ if (incomingTs > existingTs) {
428
+ write({ type: `update`, value: item });
429
+ if (incomingTs > previousCursor) {
430
+ newItemCount++;
431
+ }
432
+ }
433
+ } else if (incomingTs !== void 0) {
434
+ write({ type: `update`, value: item });
435
+ }
436
+ }
437
+ }
438
+ commit();
439
+ this.messagesSinceSubscription += newItemCount;
440
+ if (this.resubscribeThreshold > 0 && this.messagesSinceSubscription >= this.resubscribeThreshold && this.currentSubscription !== null) {
441
+ this.updateSubscription();
442
+ }
443
+ }
444
+ /**
445
+ * Clean up all resources
446
+ */
447
+ cleanup() {
448
+ if (this.debounceTimer) {
449
+ clearTimeout(this.debounceTimer);
450
+ this.debounceTimer = null;
451
+ }
452
+ if (this.currentSubscription) {
453
+ this.currentSubscription();
454
+ this.currentSubscription = null;
455
+ }
456
+ for (const activeSet of this.activeDimensions.values()) {
457
+ activeSet.clear();
458
+ }
459
+ this.refCounts.clear();
460
+ this.pendingFilters = {};
461
+ this.globalCursor = 0;
462
+ this.markedReady = false;
463
+ this.hasRequestedGlobal = false;
464
+ this.globalRefCount = 0;
465
+ this.messagesSinceSubscription = 0;
466
+ this.callbacks = null;
467
+ }
468
+ /**
469
+ * Get debug info about current state
470
+ */
471
+ getDebugInfo() {
472
+ const activeDimensions = {};
473
+ for (const [convexArg, serializedValues] of this.activeDimensions) {
474
+ activeDimensions[convexArg] = [...serializedValues].map((s) => fromKey(s));
475
+ }
476
+ return {
477
+ activeDimensions,
478
+ globalCursor: this.globalCursor,
479
+ pendingFilters: { ...this.pendingFilters },
480
+ hasSubscription: this.currentSubscription !== null,
481
+ markedReady: this.markedReady,
482
+ hasRequestedGlobal: this.hasRequestedGlobal,
483
+ messagesSinceSubscription: this.messagesSinceSubscription
484
+ };
485
+ }
486
+ }
487
+ function isPropRef(expr) {
488
+ return typeof expr === `object` && expr !== null && `type` in expr && expr.type === `ref` && `path` in expr && Array.isArray(expr.path);
489
+ }
490
+ function isValue(expr) {
491
+ return typeof expr === `object` && expr !== null && `type` in expr && expr.type === `val` && `value` in expr;
492
+ }
493
+ function isFunc(expr) {
494
+ return typeof expr === `object` && expr !== null && `type` in expr && expr.type === `func` && `name` in expr && `args` in expr && Array.isArray(expr.args);
495
+ }
496
+ function propRefMatchesField(propRef, fieldName) {
497
+ const { path } = propRef;
498
+ if (path.length === 1 && path[0] === fieldName) {
499
+ return true;
500
+ }
501
+ if (path.length === 2 && path[1] === fieldName) {
502
+ return true;
503
+ }
504
+ return false;
505
+ }
506
+ function extractFromEq(func, filterField) {
507
+ if (func.args.length !== 2) return [];
508
+ const [left, right] = func.args;
509
+ if (isPropRef(left) && propRefMatchesField(left, filterField) && isValue(right)) {
510
+ return [right.value];
511
+ }
512
+ if (isValue(left) && isPropRef(right) && propRefMatchesField(right, filterField)) {
513
+ return [left.value];
514
+ }
515
+ return [];
516
+ }
517
+ function extractFromIn(func, filterField) {
518
+ if (func.args.length !== 2) return [];
519
+ const [left, right] = func.args;
520
+ if (isPropRef(left) && propRefMatchesField(left, filterField) && isValue(right)) {
521
+ const val = right.value;
522
+ if (Array.isArray(val)) {
523
+ return val;
524
+ }
525
+ }
526
+ return [];
527
+ }
528
+ function walkExpression(expr, filterField) {
529
+ if (!isFunc(expr)) return [];
530
+ const { name, args } = expr;
531
+ const results = [];
532
+ switch (name) {
533
+ case `eq`:
534
+ results.push(...extractFromEq(expr, filterField));
535
+ break;
536
+ case `in`:
537
+ results.push(...extractFromIn(expr, filterField));
538
+ break;
539
+ case `and`:
540
+ case `or`:
541
+ for (const arg of args) {
542
+ results.push(...walkExpression(arg, filterField));
543
+ }
544
+ break;
545
+ // For other functions, recursively check their arguments
546
+ // (in case of nested expressions)
547
+ default:
548
+ for (const arg of args) {
549
+ results.push(...walkExpression(arg, filterField));
550
+ }
551
+ break;
552
+ }
553
+ return results;
554
+ }
555
+ function extractFilterValues(options, filterField) {
556
+ const { where } = options;
557
+ if (!where) {
558
+ return [];
559
+ }
560
+ const values = walkExpression(where, filterField);
561
+ const seen = /* @__PURE__ */ new Set();
562
+ const unique = [];
563
+ for (const value of values) {
564
+ const key = toKey(value);
565
+ if (!seen.has(key)) {
566
+ seen.add(key);
567
+ unique.push(value);
568
+ }
569
+ }
570
+ return unique;
571
+ }
572
+ function hasFilterField(options, filterField) {
573
+ return extractFilterValues(options, filterField).length > 0;
574
+ }
575
+ function extractMultipleFilterValues(options, filterDimensions) {
576
+ const result = {};
577
+ for (const dim of filterDimensions) {
578
+ const values = extractFilterValues(options, dim.filterField);
579
+ if (values.length > 0) {
580
+ result[dim.convexArg] = values;
581
+ }
582
+ }
583
+ return result;
584
+ }
585
+ const DEFAULT_UPDATED_AT_FIELD = `updatedAt`;
586
+ const DEFAULT_DEBOUNCE_MS = 50;
587
+ const DEFAULT_TAIL_OVERLAP_MS = 1e4;
588
+ const DEFAULT_RESUBSCRIBE_THRESHOLD = 10;
589
+ function normalizeFilterConfig(filters) {
590
+ if (filters === void 0) return [];
591
+ return Array.isArray(filters) ? filters : [filters];
592
+ }
593
+ function convexCollectionOptions(config) {
594
+ const {
595
+ client,
596
+ query,
597
+ filters,
598
+ updatedAtFieldName = DEFAULT_UPDATED_AT_FIELD,
599
+ debounceMs = DEFAULT_DEBOUNCE_MS,
600
+ tailOverlapMs = DEFAULT_TAIL_OVERLAP_MS,
601
+ resubscribeThreshold = DEFAULT_RESUBSCRIBE_THRESHOLD,
602
+ getKey,
603
+ onInsert,
604
+ onUpdate,
605
+ ...baseConfig
606
+ } = config;
607
+ const filterDimensions = normalizeFilterConfig(filters);
608
+ const syncManager = new ConvexSyncManager({
609
+ client,
610
+ query,
611
+ filterDimensions,
612
+ updatedAtFieldName,
613
+ debounceMs,
614
+ tailOverlapMs,
615
+ resubscribeThreshold,
616
+ getKey
617
+ });
618
+ const syncConfig = {
619
+ sync: (params) => {
620
+ const { collection, begin, write, commit, markReady } = params;
621
+ syncManager.setCallbacks({
622
+ collection: {
623
+ get: (key) => collection.get(key),
624
+ has: (key) => collection.has(key)
625
+ },
626
+ begin,
627
+ write,
628
+ commit,
629
+ markReady
630
+ });
631
+ return {
632
+ loadSubset: (options) => {
633
+ if (filterDimensions.length === 0) {
634
+ return syncManager.requestFilters({});
635
+ }
636
+ const extracted = extractMultipleFilterValues(options, filterDimensions);
637
+ if (Object.keys(extracted).length === 0) {
638
+ return Promise.resolve();
639
+ }
640
+ return syncManager.requestFilters(extracted);
641
+ },
642
+ unloadSubset: (options) => {
643
+ if (filterDimensions.length === 0) {
644
+ syncManager.releaseFilters({});
645
+ return;
646
+ }
647
+ const extracted = extractMultipleFilterValues(options, filterDimensions);
648
+ if (Object.keys(extracted).length > 0) {
649
+ syncManager.releaseFilters(extracted);
650
+ }
651
+ },
652
+ cleanup: () => {
653
+ syncManager.cleanup();
654
+ }
655
+ };
656
+ }
657
+ };
658
+ return {
659
+ ...baseConfig,
660
+ getKey,
661
+ syncMode: `on-demand`,
662
+ // Always on-demand since we sync based on query predicates
663
+ sync: syncConfig,
664
+ onInsert,
665
+ onUpdate
666
+ };
667
+ }
668
+ export {
669
+ ConvexSyncManager,
670
+ convexCollectionOptions,
671
+ deserializeValue,
672
+ extractFilterValues,
673
+ extractMultipleFilterValues,
674
+ fromKey,
675
+ hasFilterField,
676
+ serializeValue,
677
+ toKey
678
+ };
679
+ //# sourceMappingURL=index.js.map