@rebasepro/common 0.14.2-canary.gca521e9 → 0.15.0
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/common",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Rebase shared core — collection registry, data driver adapter and fluent query builder. No React dependency.",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"fast-equals": "6.0.2",
|
|
42
42
|
"json-logic-js": "^2.0.5",
|
|
43
|
-
"@rebasepro/types": "0.
|
|
44
|
-
"@rebasepro/utils": "0.
|
|
43
|
+
"@rebasepro/types": "0.15.0",
|
|
44
|
+
"@rebasepro/utils": "0.15.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@jest/globals": "^30.4.1",
|
package/src/data/sort-dialect.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { OrderBySpec, OrderByTuple } from "@rebasepro/types";
|
|
1
|
+
import type { OrderBySortTuple, OrderBySpec, OrderByTuple } from "@rebasepro/types";
|
|
2
|
+
import { isRelationAggregateSort, sortKeyToString } from "@rebasepro/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Sort-order wire codec.
|
|
@@ -20,16 +21,27 @@ import type { OrderBySpec, OrderByTuple } from "@rebasepro/types";
|
|
|
20
21
|
* the same value; the two are told apart by whether the first element is
|
|
21
22
|
* itself an array, which no field name ever is.
|
|
22
23
|
*
|
|
24
|
+
* This is also where a {@link RelationAggregateSort} object stops being an
|
|
25
|
+
* object. Above this function a sort key may be either spelling; below it,
|
|
26
|
+
* every key is a string — which is what `OrderByTuple`, the REST parameter, the
|
|
27
|
+
* driver contract and the cursor all already were. Doing it here means the one
|
|
28
|
+
* place that already collapses the two *shapes* of a sort also collapses the
|
|
29
|
+
* two *spellings* of a key, rather than every consumer learning about both.
|
|
30
|
+
*
|
|
23
31
|
* @returns The keys in order of significance, or `undefined` for no sort. An
|
|
24
32
|
* empty list also returns `undefined` — "sort by nothing" is no sort, and
|
|
25
33
|
* letting `[]` through would have every layer below re-deciding what it meant.
|
|
26
34
|
*/
|
|
27
35
|
export function normalizeOrderBy(orderBy?: OrderBySpec): OrderByTuple[] | undefined {
|
|
28
36
|
if (!orderBy || orderBy.length === 0) return undefined;
|
|
37
|
+
// An aggregate key is an object, so the first element being an array still
|
|
38
|
+
// tells the list form from the single-tuple one — no field name is an
|
|
39
|
+
// array, and neither is an aggregate key.
|
|
29
40
|
const list = Array.isArray(orderBy[0])
|
|
30
|
-
? orderBy as
|
|
31
|
-
: [orderBy as
|
|
32
|
-
|
|
41
|
+
? orderBy as OrderBySortTuple[]
|
|
42
|
+
: [orderBy as OrderBySortTuple];
|
|
43
|
+
if (list.length === 0) return undefined;
|
|
44
|
+
return list.map(([key, direction]) => [sortKeyToString(key), direction] as OrderByTuple);
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
/**
|
|
@@ -99,21 +111,30 @@ export function parseOrderBySpecStrict(raw: unknown, order?: "asc" | "desc"): Or
|
|
|
99
111
|
throw new OrderBySpecError(`${typeof raw} is not a field name or a list of sort keys`);
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
// The single-tuple spelling, `["created_at", "desc"]
|
|
103
|
-
|
|
114
|
+
// The single-tuple spelling, `["created_at", "desc"]` — or the same shape
|
|
115
|
+
// with an aggregate key in place of the field name.
|
|
116
|
+
if (typeof raw[0] === "string" || isRelationAggregateSort(raw[0])) return [toStrictTuple(raw, 0)];
|
|
104
117
|
|
|
105
118
|
return raw.map(toStrictTuple);
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
function toStrictTuple(raw: unknown, index: number): OrderByTuple {
|
|
109
|
-
if (!Array.isArray(raw)
|
|
122
|
+
if (!Array.isArray(raw)) {
|
|
123
|
+
throw new OrderBySpecError(`entry ${index} has no field name`);
|
|
124
|
+
}
|
|
125
|
+
// The object spelling of an aggregate key, from an untyped caller that did
|
|
126
|
+
// not go through `normalizeOrderBy`. Encoded rather than refused: it is a
|
|
127
|
+
// sort this understands, and rejecting the shape a typed caller writes
|
|
128
|
+
// would be a distinction between the two spellings that nothing else makes.
|
|
129
|
+
const key = isRelationAggregateSort(raw[0]) ? sortKeyToString(raw[0]) : raw[0];
|
|
130
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
110
131
|
throw new OrderBySpecError(`entry ${index} has no field name`);
|
|
111
132
|
}
|
|
112
133
|
const direction = raw[1];
|
|
113
134
|
if (direction !== undefined && direction !== "asc" && direction !== "desc") {
|
|
114
135
|
throw new OrderBySpecError(`entry ${index} has direction '${String(direction)}'`);
|
|
115
136
|
}
|
|
116
|
-
return [
|
|
137
|
+
return [key, direction ?? "asc"];
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
/**
|
|
@@ -143,6 +164,9 @@ export function serializeOrderBy(orderBy?: OrderBySpec | string): string | undef
|
|
|
143
164
|
if (!orderBy) return undefined;
|
|
144
165
|
// Runtime tolerance: pass through a pre-serialized wire string unchanged.
|
|
145
166
|
if (typeof orderBy === "string") return orderBy;
|
|
167
|
+
// `normalizeOrderBy` has already encoded any aggregate key to its string
|
|
168
|
+
// spelling, which is why the shorthand below can assume a string: neither
|
|
169
|
+
// `min(applications.created_at)` nor `count(applications)` contains a `:`.
|
|
146
170
|
const list = normalizeOrderBy(orderBy);
|
|
147
171
|
if (!list) return undefined;
|
|
148
172
|
if (list.length === 1) return `${list[0][0]}:${list[0][1]}`;
|