akanjs 3.0.0-beta.22 → 3.0.0-beta.23
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/fetch/client/wsClient.ts
CHANGED
|
@@ -107,7 +107,12 @@ export class WsClient {
|
|
|
107
107
|
#connect() {
|
|
108
108
|
if (this.#destroyed) return;
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
try {
|
|
111
|
+
this.#ws = new WebSocket(this.url);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.logger.error(`Cannot open a WebSocket to ${this.url}: ${error instanceof Error ? error.message : error}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
111
116
|
this.#ws.binaryType = "arraybuffer";
|
|
112
117
|
this.#ws.onopen = (e) => {
|
|
113
118
|
this.#reconnectAttempts = 0;
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Cls,
|
|
6
6
|
ENDPOINT_META,
|
|
7
7
|
FIELD_META,
|
|
8
|
+
Float,
|
|
8
9
|
getEnv,
|
|
9
10
|
ID,
|
|
10
11
|
INTERNAL_META,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
SLICE_META,
|
|
15
16
|
} from "akanjs/base";
|
|
16
17
|
import { capitalize, cookieHeaderHasAuthToken, Logger } from "akanjs/common";
|
|
17
|
-
import { deserialize, resolvePageLimit, resolvePageSkip, serialize } from "akanjs/constant";
|
|
18
|
+
import { type ConstantField, deserialize, resolvePageLimit, resolvePageSkip, serialize } from "akanjs/constant";
|
|
18
19
|
import { baseDocumentColumns, documentQueryHelper, getFilterSortByKey, type QueryFieldMap } from "akanjs/document";
|
|
19
20
|
import {
|
|
20
21
|
type AkanJob,
|
|
@@ -475,9 +476,11 @@ export class SignalResolver {
|
|
|
475
476
|
}) {}
|
|
476
477
|
return SliceEndpoint;
|
|
477
478
|
}
|
|
478
|
-
|
|
479
|
+
|
|
480
|
+
static readonly #clientOrderableTypes = new Set<unknown>([Int, Float, String, ID, Boolean, Date]);
|
|
479
481
|
static #assertLiveSort(refName: string, key: string, sliceInfo: SliceInfo) {
|
|
480
|
-
const lightFields =
|
|
482
|
+
const lightFields =
|
|
483
|
+
(sliceInfo.light as unknown as { [FIELD_META]?: Record<string, ConstantField> })[FIELD_META] ?? {};
|
|
481
484
|
for (const sortKey of sliceInfo.liveOption?.sort ?? []) {
|
|
482
485
|
const sort = getFilterSortByKey(sliceInfo.filter, sortKey);
|
|
483
486
|
if (!sort)
|
|
@@ -489,9 +492,12 @@ export class SignalResolver {
|
|
|
489
492
|
`Live slice "${refName}.${key}" declares sort "${sortKey}" on "${path}", which is not in ` +
|
|
490
493
|
`Light${capitalize(refName)}. A subscriber cannot order by a field it never receives.`,
|
|
491
494
|
);
|
|
495
|
+
const { modelRef, isArray, isMap } = lightFields[path].getProps();
|
|
496
|
+
if (!isArray && !isMap && SignalResolver.#clientOrderableTypes.has(modelRef)) continue;
|
|
492
497
|
SignalResolver.logger.warn(
|
|
493
|
-
`Live slice "${refName}.${key}" sorts by "${path}", which is
|
|
494
|
-
`
|
|
498
|
+
`Live slice "${refName}.${key}" sorts "${sortKey}" by "${path}", which is not an Int, Float, String, ID, ` +
|
|
499
|
+
`Boolean or Date field, so a client placing a new row may disagree with the server. Sort by one of ` +
|
|
500
|
+
`those, or leave "${sortKey}" out of .live({ sort }) so the list refetches when a row arrives.`,
|
|
495
501
|
);
|
|
496
502
|
}
|
|
497
503
|
}
|
|
@@ -37,9 +37,9 @@ export class QueryCompiler {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
orderBy(sort: Record<string, 1 | -1> = { createdAt: -1 }) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const terms = Object.entries(sort);
|
|
41
|
+
if (!terms.some(([path]) => path === "id")) terms.push(["id", terms.at(-1)?.[1] ?? -1]);
|
|
42
|
+
return terms.map(([path, direction]) => this.dialect.orderTerm(this.fieldExpr(path), direction)).join(", ");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
fieldExpr(path: string) {
|
package/store/liveInsert.ts
CHANGED
|
@@ -30,31 +30,53 @@ export const livePlacementIndex = ({
|
|
|
30
30
|
const sort = sorts?.[sortKey];
|
|
31
31
|
if (!sort || !Object.keys(sort).length) return null;
|
|
32
32
|
const paths = Object.entries(sort);
|
|
33
|
-
if (paths.some(([path]) => comparableOf(row[path]) ===
|
|
34
|
-
|
|
33
|
+
if (paths.some(([path]) => comparableOf(row[path]) === undefined)) return null;
|
|
34
|
+
let orderable = true;
|
|
35
|
+
const index = list.findIndex((item) => {
|
|
36
|
+
const order = compareRows(row, item, paths);
|
|
37
|
+
orderable = order !== null;
|
|
38
|
+
return order === null || order < 0;
|
|
39
|
+
});
|
|
40
|
+
if (!orderable) return null;
|
|
35
41
|
if (index !== -1) return index;
|
|
36
42
|
|
|
37
43
|
if (cumulative) return hasMore ? null : list.length;
|
|
38
44
|
return list.length < limit ? list.length : null;
|
|
39
45
|
};
|
|
40
46
|
|
|
41
|
-
const compareRows = (left: LiveSortableRow, right: LiveSortableRow, paths: [string, 1 | -1][]): number => {
|
|
47
|
+
const compareRows = (left: LiveSortableRow, right: LiveSortableRow, paths: [string, 1 | -1][]): number | null => {
|
|
42
48
|
for (const [path, direction] of paths) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
if (a === null || b === null) continue;
|
|
46
|
-
if (a === b) continue;
|
|
47
|
-
return (a < b ? -1 : 1) * direction;
|
|
49
|
+
const order = compareValues(comparableOf(left[path]), comparableOf(right[path]));
|
|
50
|
+
if (order !== 0) return order === null ? null : order * direction;
|
|
48
51
|
}
|
|
49
|
-
|
|
52
|
+
const order = compareValues(comparableOf(left.id), comparableOf(right.id));
|
|
53
|
+
return order === null ? null : order * (paths.at(-1)?.[1] ?? -1);
|
|
50
54
|
};
|
|
51
55
|
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
56
|
+
const compareValues = (a: Comparable | undefined, b: Comparable | undefined): number | null => {
|
|
57
|
+
if (a === undefined || b === undefined) return null;
|
|
58
|
+
if (a === b) return 0;
|
|
59
|
+
if (a === null) return -1;
|
|
60
|
+
if (b === null) return 1;
|
|
61
|
+
if (typeof a === "string" && typeof b === "string") return compareText(a, b);
|
|
62
|
+
if (typeof a === "number" && typeof b === "number") return a < b ? -1 : 1;
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const compareText = (a: string, b: string) => {
|
|
67
|
+
let idx = 0;
|
|
68
|
+
while (idx < a.length && idx < b.length && a[idx] === b[idx]) idx += 1;
|
|
69
|
+
return (a.codePointAt(idx) ?? -1) < (b.codePointAt(idx) ?? -1) ? -1 : 1;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type Comparable = number | string | null;
|
|
73
|
+
|
|
74
|
+
const comparableOf = (value: unknown): Comparable | undefined => {
|
|
75
|
+
if (value === null) return null;
|
|
54
76
|
if (typeof value === "number" || typeof value === "string") return value;
|
|
55
77
|
if (typeof value === "boolean") return value ? 1 : 0;
|
|
56
78
|
if (value instanceof Date) return value.getTime();
|
|
57
|
-
const valued: unknown = (value as { valueOf?: () => unknown })
|
|
79
|
+
const valued: unknown = (value as { valueOf?: () => unknown } | undefined)?.valueOf?.();
|
|
58
80
|
if (typeof valued === "number" || typeof valued === "string") return valued;
|
|
59
|
-
return
|
|
81
|
+
return undefined;
|
|
60
82
|
};
|