@rebasepro/client 0.14.0 → 0.14.1-canary.g7e666eb
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/index.es.js +37 -26
- package/dist/index.es.js.map +1 -1
- package/dist/offline-query.d.ts +3 -3
- package/dist/sdk_query_builder.d.ts +5 -0
- package/package.json +4 -4
- package/src/collection.ts +6 -3
- package/src/offline-query.ts +45 -27
- package/src/sdk_query_builder.ts +9 -1
package/dist/offline-query.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FilterValues, FindResult, LogicalCondition, FilterCondition,
|
|
1
|
+
import { FilterValues, FindResult, LogicalCondition, FilterCondition, OrderBySpec, WhereFilterOp } from "@rebasepro/types";
|
|
2
2
|
import { FindParams } from "./transport";
|
|
3
3
|
/**
|
|
4
4
|
* The server's page size when the caller does not ask for one.
|
|
@@ -39,7 +39,7 @@ export declare function matchesParams(row: Record<string, unknown>, params?: Fin
|
|
|
39
39
|
* the row id as a tiebreak so paging through an unsorted-but-equal run does
|
|
40
40
|
* not shuffle rows between pages.
|
|
41
41
|
*/
|
|
42
|
-
export declare function sortRows<M extends Record<string, unknown>>(rows: M[], orderBy?:
|
|
42
|
+
export declare function sortRows<M extends Record<string, unknown>>(rows: M[], orderBy?: OrderBySpec): M[];
|
|
43
43
|
/**
|
|
44
44
|
* Resolve `page`/`offset`/`limit` the way the server does.
|
|
45
45
|
*
|
|
@@ -102,6 +102,6 @@ export declare function isExactlyEvaluable(params?: FindParams): boolean;
|
|
|
102
102
|
* ascending, first descending) that matches Postgres and never reaches the
|
|
103
103
|
* comparator.
|
|
104
104
|
*/
|
|
105
|
-
export declare function isLocallySortable(rows: readonly Record<string, unknown>[], orderBy?:
|
|
105
|
+
export declare function isLocallySortable(rows: readonly Record<string, unknown>[], orderBy?: OrderBySpec): boolean;
|
|
106
106
|
/** Run a full query — filter, sort, paginate — over a set of rows. */
|
|
107
107
|
export declare function runLocalQuery<M extends Record<string, unknown>>(rows: M[], params?: FindParams): FindResult<M>;
|
|
@@ -25,6 +25,11 @@ export declare class SDKQueryBuilder<M extends Record<string, unknown> = Record<
|
|
|
25
25
|
where(logicalCondition: LogicalCondition): this;
|
|
26
26
|
/**
|
|
27
27
|
* Order the results by a specific column.
|
|
28
|
+
*
|
|
29
|
+
* Call it again to add a tie-breaker rather than replace the sort: keys
|
|
30
|
+
* apply in the order they were added, so
|
|
31
|
+
* `.orderBy("roles").orderBy("created_at", "desc")` sorts by role and
|
|
32
|
+
* shows the newest first within each one.
|
|
28
33
|
*/
|
|
29
34
|
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
|
|
30
35
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.1-canary.g7e666eb",
|
|
5
5
|
"description": "HTTP SDK client for the Rebase custom backend",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"./package.json": "./package.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@rebasepro/
|
|
33
|
-
"@rebasepro/common": "0.14.
|
|
34
|
-
"@rebasepro/
|
|
32
|
+
"@rebasepro/utils": "0.14.1-canary.g7e666eb",
|
|
33
|
+
"@rebasepro/common": "0.14.1-canary.g7e666eb",
|
|
34
|
+
"@rebasepro/types": "0.14.1-canary.g7e666eb"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@jest/globals": "^30.4.1",
|
package/src/collection.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
WriteOptions,
|
|
13
13
|
type ComputedSortField
|
|
14
14
|
} from "@rebasepro/types";
|
|
15
|
-
import { collectAllPages, paginateFind, resolveFindWindow } from "@rebasepro/common";
|
|
15
|
+
import { collectAllPages, normalizeOrderBy, paginateFind, resolveFindWindow } from "@rebasepro/common";
|
|
16
16
|
|
|
17
17
|
import { SDKQueryBuilder } from "./sdk_query_builder";
|
|
18
18
|
|
|
@@ -429,8 +429,11 @@ export function createCollectionClient<M extends Record<string, unknown> = Recor
|
|
|
429
429
|
// handed "20" where it expected a keyset value, and the
|
|
430
430
|
// offset it does understand never arrived at all.
|
|
431
431
|
offset: window.driverOffset,
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
// The list form, so a multi-key sort reaches the socket
|
|
433
|
+
// whole. Indexing `[0]`/`[1]` here read a tuple-of-tuples as
|
|
434
|
+
// a field name and a direction, and a live subscription came
|
|
435
|
+
// back in a different order from the same query fetched.
|
|
436
|
+
orderBy: normalizeOrderBy(params?.orderBy),
|
|
434
437
|
searchString: params?.searchString,
|
|
435
438
|
searchExplain: params?.searchExplain
|
|
436
439
|
},
|
package/src/offline-query.ts
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
FindResult,
|
|
5
5
|
LogicalCondition,
|
|
6
6
|
FilterCondition,
|
|
7
|
-
|
|
7
|
+
OrderBySpec,
|
|
8
8
|
WhereFilterOp,
|
|
9
9
|
toCanonicalOp
|
|
10
10
|
} from "@rebasepro/types";
|
|
11
11
|
import { FindParams } from "./transport";
|
|
12
|
-
import { resolveFindWindow } from "@rebasepro/common";
|
|
12
|
+
import { normalizeOrderBy, resolveFindWindow } from "@rebasepro/common";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* A local evaluator for `FindParams`, so cached rows can answer a query the
|
|
@@ -308,26 +308,43 @@ export function matchesParams(row: Record<string, unknown>, params?: FindParams)
|
|
|
308
308
|
* the row id as a tiebreak so paging through an unsorted-but-equal run does
|
|
309
309
|
* not shuffle rows between pages.
|
|
310
310
|
*/
|
|
311
|
-
export function sortRows<M extends Record<string, unknown>>(rows: M[], orderBy?:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const sign = direction === "desc" ? -1 : 1;
|
|
311
|
+
export function sortRows<M extends Record<string, unknown>>(rows: M[], orderBy?: OrderBySpec): M[] {
|
|
312
|
+
const keys = normalizeOrderBy(orderBy);
|
|
313
|
+
if (!keys) return rows;
|
|
315
314
|
return rows.sort((a, b) => {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return (aNull ? 1 : -1) * (direction === "desc" ? -1 : 1);
|
|
315
|
+
for (const [field, direction = "asc"] of keys) {
|
|
316
|
+
const cmp = compareOnKey(a, b, field, direction);
|
|
317
|
+
// Equal on this key — and equal is not a decision, so the next key
|
|
318
|
+
// gets to make one. Returning the tiebreak here instead is what a
|
|
319
|
+
// single-key sort does at the end, and doing it per key would order
|
|
320
|
+
// by the id the moment two rows shared a role.
|
|
321
|
+
if (cmp !== 0) return cmp;
|
|
324
322
|
}
|
|
325
|
-
|
|
326
|
-
if (cmp === undefined || cmp === 0) return tiebreak(a, b);
|
|
327
|
-
return cmp * sign;
|
|
323
|
+
return tiebreak(a, b);
|
|
328
324
|
});
|
|
329
325
|
}
|
|
330
326
|
|
|
327
|
+
/** One key's verdict: negative, positive, or 0 for "these two are equal here". */
|
|
328
|
+
function compareOnKey(
|
|
329
|
+
a: Record<string, unknown>,
|
|
330
|
+
b: Record<string, unknown>,
|
|
331
|
+
field: string,
|
|
332
|
+
direction: "asc" | "desc"
|
|
333
|
+
): number {
|
|
334
|
+
const av = a[field];
|
|
335
|
+
const bv = b[field];
|
|
336
|
+
const aNull = isNullish(toComparable(av));
|
|
337
|
+
const bNull = isNullish(toComparable(bv));
|
|
338
|
+
if (aNull || bNull) {
|
|
339
|
+
if (aNull && bNull) return 0;
|
|
340
|
+
// NULLS LAST ascending, NULLS FIRST descending.
|
|
341
|
+
return (aNull ? 1 : -1) * (direction === "desc" ? -1 : 1);
|
|
342
|
+
}
|
|
343
|
+
const cmp = compareValues(av, bv);
|
|
344
|
+
if (cmp === undefined || cmp === 0) return 0;
|
|
345
|
+
return cmp * (direction === "desc" ? -1 : 1);
|
|
346
|
+
}
|
|
347
|
+
|
|
331
348
|
function tiebreak(a: Record<string, unknown>, b: Record<string, unknown>): number {
|
|
332
349
|
const cmp = compareValues(a.id, b.id);
|
|
333
350
|
return cmp ?? 0;
|
|
@@ -433,21 +450,22 @@ export function isExactlyEvaluable(params?: FindParams): boolean {
|
|
|
433
450
|
*/
|
|
434
451
|
export function isLocallySortable(
|
|
435
452
|
rows: readonly Record<string, unknown>[],
|
|
436
|
-
orderBy?:
|
|
453
|
+
orderBy?: OrderBySpec
|
|
437
454
|
): boolean {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
455
|
+
const keys = normalizeOrderBy(orderBy);
|
|
456
|
+
if (!keys) return true;
|
|
457
|
+
// Every key has to be decidable, not just the first: a sort the local side
|
|
458
|
+
// can only agree with down to its second column is one it disagrees with.
|
|
459
|
+
return keys.every(([field]) => rows.every((row) => {
|
|
441
460
|
const value = toComparable(row[field]);
|
|
442
|
-
if (isNullish(value))
|
|
443
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
444
|
-
if (typeof value === "bigint")
|
|
461
|
+
if (isNullish(value)) return true;
|
|
462
|
+
if (typeof value === "number" || typeof value === "boolean") return true;
|
|
463
|
+
if (typeof value === "bigint") return true;
|
|
445
464
|
// A numeric string is compared as a number, so it is safe too — this is
|
|
446
465
|
// the wire's type erasure, which `compareValues` already undoes.
|
|
447
|
-
if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value)))
|
|
466
|
+
if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value))) return true;
|
|
448
467
|
return false;
|
|
449
|
-
}
|
|
450
|
-
return true;
|
|
468
|
+
}));
|
|
451
469
|
}
|
|
452
470
|
|
|
453
471
|
/** Run a full query — filter, sort, paginate — over a set of rows. */
|
package/src/sdk_query_builder.ts
CHANGED
|
@@ -2,12 +2,14 @@ import {
|
|
|
2
2
|
FindParams,
|
|
3
3
|
FindResult,
|
|
4
4
|
LogicalCondition,
|
|
5
|
+
OrderByTuple,
|
|
5
6
|
SDKCollectionClient,
|
|
6
7
|
SDKQueryBuilderInterface,
|
|
7
8
|
WhereFilterOp,
|
|
8
9
|
WhereValueFor,
|
|
9
10
|
type ComputedSortField
|
|
10
11
|
} from "@rebasepro/types";
|
|
12
|
+
import { normalizeOrderBy } from "@rebasepro/common";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* SDK Query Builder — returns flat rows (`FindResult<M>`) instead of
|
|
@@ -72,9 +74,15 @@ export class SDKQueryBuilder<M extends Record<string, unknown> = Record<string,
|
|
|
72
74
|
|
|
73
75
|
/**
|
|
74
76
|
* Order the results by a specific column.
|
|
77
|
+
*
|
|
78
|
+
* Call it again to add a tie-breaker rather than replace the sort: keys
|
|
79
|
+
* apply in the order they were added, so
|
|
80
|
+
* `.orderBy("roles").orderBy("created_at", "desc")` sorts by role and
|
|
81
|
+
* shows the newest first within each one.
|
|
75
82
|
*/
|
|
76
83
|
orderBy(column: (keyof M & string) | ComputedSortField, direction: "asc" | "desc" = "asc"): this {
|
|
77
|
-
this.params.orderBy
|
|
84
|
+
const existing = normalizeOrderBy(this.params.orderBy) ?? [];
|
|
85
|
+
this.params.orderBy = [...existing, [column, direction] as OrderByTuple];
|
|
78
86
|
return this;
|
|
79
87
|
}
|
|
80
88
|
|