@evolu/common 7.2.2 → 7.3.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/dist/src/Function.d.ts +61 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +19 -0
- package/dist/src/Object.d.ts +32 -1
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +32 -1
- package/dist/src/Order.d.ts +2 -1
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +1 -0
- package/dist/src/Result.d.ts +36 -29
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Sqlite.d.ts +9 -7
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +8 -0
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +6 -4
- package/dist/src/Type.d.ts +30 -2
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +82 -0
- package/dist/src/local-first/Db.d.ts +1 -5
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +112 -117
- package/dist/src/local-first/Evolu.d.ts +3 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +14 -12
- package/dist/src/local-first/LocalAuth.d.ts +2 -2
- package/dist/src/local-first/Owner.d.ts +3 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.d.ts +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +12 -12
- package/dist/src/local-first/PublicKysely.js +1 -1
- package/dist/src/local-first/Query.d.ts +54 -6
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +130 -11
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +17 -18
- package/dist/src/local-first/Schema.d.ts +15 -14
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +43 -51
- package/dist/src/local-first/Storage.d.ts +2 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +2 -6
- package/dist/src/local-first/Sync.d.ts +10 -7
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +154 -103
- package/dist/src/local-first/Timestamp.d.ts +13 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +14 -1
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Function.ts +74 -0
- package/src/Object.ts +40 -1
- package/src/Order.ts +2 -1
- package/src/Result.ts +38 -29
- package/src/Sqlite.ts +18 -7
- package/src/Task.ts +6 -4
- package/src/Type.ts +134 -1
- package/src/local-first/Db.ts +182 -217
- package/src/local-first/Evolu.ts +21 -10
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Protocol.ts +13 -19
- package/src/local-first/PublicKysely.ts +1 -1
- package/src/local-first/Query.ts +216 -21
- package/src/local-first/Relay.ts +20 -24
- package/src/local-first/Schema.ts +83 -70
- package/src/local-first/Storage.ts +3 -8
- package/src/local-first/Sync.ts +215 -135
- package/src/local-first/Timestamp.ts +14 -1
- package/src/local-first/index.ts +0 -1
- package/dist/src/local-first/Diff.d.ts +0 -43
- package/dist/src/local-first/Diff.d.ts.map +0 -1
- package/dist/src/local-first/Diff.js +0 -97
- package/src/local-first/Diff.ts +0 -144
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { createRandomBytes } from "../Crypto.js";
|
|
2
|
-
import { isPlainObject } from "../Object.js";
|
|
3
|
-
import { orderUint8Array } from "../Order.js";
|
|
4
|
-
import { createId, String } from "../Type.js";
|
|
5
|
-
export const applyPatches = (patches, current) => patches.reduce((next, patch) => {
|
|
6
|
-
switch (patch.op) {
|
|
7
|
-
case "replaceAll":
|
|
8
|
-
return parseSqliteJsonArray(patch.value);
|
|
9
|
-
case "replaceAt": {
|
|
10
|
-
const parsedRow = parseSqliteJsonArray([patch.value])[0];
|
|
11
|
-
return next.toSpliced(patch.index, 1, parsedRow);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}, current);
|
|
15
|
-
/**
|
|
16
|
-
* We detect only changes in the whole result and in-place edits. In the future,
|
|
17
|
-
* we will add more heuristics. We will probably not implement the Myers diff
|
|
18
|
-
* algorithm because it's faster to rerender all than to compute many detailed
|
|
19
|
-
* patches. We will only implement logic a developer would implement manually,
|
|
20
|
-
* if necessary.
|
|
21
|
-
*/
|
|
22
|
-
export const makePatches = (previousRows, nextRows) => {
|
|
23
|
-
if (previousRows === undefined)
|
|
24
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
25
|
-
// TODO: Detect prepend and append, it's cheap.
|
|
26
|
-
if (previousRows.length !== nextRows.length) {
|
|
27
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
28
|
-
}
|
|
29
|
-
const length = previousRows.length;
|
|
30
|
-
const replaceAtPatches = [];
|
|
31
|
-
for (let i = 0; i < length; i++) {
|
|
32
|
-
const previousRow = previousRows[i];
|
|
33
|
-
const nextRow = nextRows[i];
|
|
34
|
-
// We expect the same shape for both rows.
|
|
35
|
-
for (const key in previousRow)
|
|
36
|
-
if (!areEqual(previousRow[key], nextRow[key])) {
|
|
37
|
-
replaceAtPatches.push({ op: "replaceAt", value: nextRow, index: i });
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (length > 0 && replaceAtPatches.length === length) {
|
|
42
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
43
|
-
}
|
|
44
|
-
return replaceAtPatches;
|
|
45
|
-
};
|
|
46
|
-
// TODO: Replace with eqSqliteValue.
|
|
47
|
-
const areEqual = (a, b) => {
|
|
48
|
-
if (a instanceof Uint8Array && b instanceof Uint8Array) {
|
|
49
|
-
return orderUint8Array(a, b) === 0;
|
|
50
|
-
}
|
|
51
|
-
return a === b;
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* A unique identifier prepended to JSON-encoded strings. This allows safe
|
|
55
|
-
* detection and parsing of only those columns that require JSON.parse.
|
|
56
|
-
*
|
|
57
|
-
* The identifier is a cryptographically random Evolu Id, ensuring uniqueness
|
|
58
|
-
* and preventing malicious actors from inserting fake data that could be
|
|
59
|
-
* misinterpreted as JSON by the application.
|
|
60
|
-
*
|
|
61
|
-
* Note: The same queries created by different browser tabs will have different
|
|
62
|
-
* identifiers and thus be considered different and cached separately. This is
|
|
63
|
-
* usually not a big deal, but if needed, the DB cache can be optimized by
|
|
64
|
-
* passing the kyselyJsonIdentifier into the DB worker during initialization,
|
|
65
|
-
* allowing queries to be grouped and recognized across tabs or sessions.
|
|
66
|
-
*
|
|
67
|
-
* See: https://github.com/kysely-org/kysely/issues/1372#issuecomment-2702773948
|
|
68
|
-
*/
|
|
69
|
-
export const kyselyJsonIdentifier = createId({
|
|
70
|
-
randomBytes: createRandomBytes(),
|
|
71
|
-
});
|
|
72
|
-
export const parseSqliteJsonArray = (arr) => {
|
|
73
|
-
const result = new Array(arr.length);
|
|
74
|
-
for (let i = 0; i < arr.length; ++i) {
|
|
75
|
-
result[i] = parse(arr[i]);
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
|
-
const parse = (obj) => {
|
|
80
|
-
if (String.is(obj) && obj.startsWith(kyselyJsonIdentifier)) {
|
|
81
|
-
return JSON.parse(obj.slice(kyselyJsonIdentifier.length));
|
|
82
|
-
}
|
|
83
|
-
if (Array.isArray(obj)) {
|
|
84
|
-
return parseSqliteJsonArray(obj);
|
|
85
|
-
}
|
|
86
|
-
if (isPlainObject(obj)) {
|
|
87
|
-
return parseObject(obj);
|
|
88
|
-
}
|
|
89
|
-
return obj;
|
|
90
|
-
};
|
|
91
|
-
const parseObject = (obj) => {
|
|
92
|
-
const result = Object.create(null);
|
|
93
|
-
for (const key in obj) {
|
|
94
|
-
result[key] = parse(obj[key]);
|
|
95
|
-
}
|
|
96
|
-
return result;
|
|
97
|
-
};
|
package/src/local-first/Diff.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { createRandomBytes } from "../Crypto.js";
|
|
2
|
-
import { isPlainObject, ReadonlyRecord } from "../Object.js";
|
|
3
|
-
import { orderUint8Array } from "../Order.js";
|
|
4
|
-
import { SqliteValue } from "../Sqlite.js";
|
|
5
|
-
import { createId, String } from "../Type.js";
|
|
6
|
-
import { Query, Row } from "./Query.js";
|
|
7
|
-
|
|
8
|
-
export interface QueryPatches {
|
|
9
|
-
readonly query: Query;
|
|
10
|
-
readonly patches: ReadonlyArray<Patch>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type Patch = ReplaceAllPatch | ReplaceAtPatch;
|
|
14
|
-
|
|
15
|
-
export interface ReplaceAllPatch {
|
|
16
|
-
readonly op: "replaceAll";
|
|
17
|
-
readonly value: ReadonlyArray<Row>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ReplaceAtPatch {
|
|
21
|
-
readonly op: "replaceAt";
|
|
22
|
-
readonly index: number;
|
|
23
|
-
readonly value: Row;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const applyPatches = (
|
|
27
|
-
patches: ReadonlyArray<Patch>,
|
|
28
|
-
current: ReadonlyArray<Row>,
|
|
29
|
-
): ReadonlyArray<Row> =>
|
|
30
|
-
patches.reduce((next, patch) => {
|
|
31
|
-
switch (patch.op) {
|
|
32
|
-
case "replaceAll":
|
|
33
|
-
return parseSqliteJsonArray(patch.value);
|
|
34
|
-
case "replaceAt": {
|
|
35
|
-
const parsedRow = parseSqliteJsonArray([patch.value])[0];
|
|
36
|
-
return next.toSpliced(patch.index, 1, parsedRow);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}, current);
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* We detect only changes in the whole result and in-place edits. In the future,
|
|
43
|
-
* we will add more heuristics. We will probably not implement the Myers diff
|
|
44
|
-
* algorithm because it's faster to rerender all than to compute many detailed
|
|
45
|
-
* patches. We will only implement logic a developer would implement manually,
|
|
46
|
-
* if necessary.
|
|
47
|
-
*/
|
|
48
|
-
export const makePatches = (
|
|
49
|
-
previousRows: ReadonlyArray<Row> | undefined,
|
|
50
|
-
nextRows: ReadonlyArray<Row>,
|
|
51
|
-
): ReadonlyArray<Patch> => {
|
|
52
|
-
if (previousRows === undefined)
|
|
53
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
54
|
-
// TODO: Detect prepend and append, it's cheap.
|
|
55
|
-
if (previousRows.length !== nextRows.length) {
|
|
56
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const length = previousRows.length;
|
|
60
|
-
const replaceAtPatches: Array<ReplaceAtPatch> = [];
|
|
61
|
-
|
|
62
|
-
for (let i = 0; i < length; i++) {
|
|
63
|
-
const previousRow = previousRows[i];
|
|
64
|
-
const nextRow = nextRows[i];
|
|
65
|
-
|
|
66
|
-
// We expect the same shape for both rows.
|
|
67
|
-
for (const key in previousRow)
|
|
68
|
-
if (
|
|
69
|
-
!areEqual(previousRow[key] as SqliteValue, nextRow[key] as SqliteValue)
|
|
70
|
-
) {
|
|
71
|
-
replaceAtPatches.push({ op: "replaceAt", value: nextRow, index: i });
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (length > 0 && replaceAtPatches.length === length) {
|
|
77
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
78
|
-
}
|
|
79
|
-
return replaceAtPatches;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// TODO: Replace with eqSqliteValue.
|
|
83
|
-
const areEqual = (a: SqliteValue, b: SqliteValue): boolean => {
|
|
84
|
-
if (a instanceof Uint8Array && b instanceof Uint8Array) {
|
|
85
|
-
return orderUint8Array(a, b) === 0;
|
|
86
|
-
}
|
|
87
|
-
return a === b;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* A unique identifier prepended to JSON-encoded strings. This allows safe
|
|
92
|
-
* detection and parsing of only those columns that require JSON.parse.
|
|
93
|
-
*
|
|
94
|
-
* The identifier is a cryptographically random Evolu Id, ensuring uniqueness
|
|
95
|
-
* and preventing malicious actors from inserting fake data that could be
|
|
96
|
-
* misinterpreted as JSON by the application.
|
|
97
|
-
*
|
|
98
|
-
* Note: The same queries created by different browser tabs will have different
|
|
99
|
-
* identifiers and thus be considered different and cached separately. This is
|
|
100
|
-
* usually not a big deal, but if needed, the DB cache can be optimized by
|
|
101
|
-
* passing the kyselyJsonIdentifier into the DB worker during initialization,
|
|
102
|
-
* allowing queries to be grouped and recognized across tabs or sessions.
|
|
103
|
-
*
|
|
104
|
-
* See: https://github.com/kysely-org/kysely/issues/1372#issuecomment-2702773948
|
|
105
|
-
*/
|
|
106
|
-
export const kyselyJsonIdentifier = createId({
|
|
107
|
-
randomBytes: createRandomBytes(),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
export const parseSqliteJsonArray = <T>(
|
|
111
|
-
arr: ReadonlyArray<T>,
|
|
112
|
-
): ReadonlyArray<T> => {
|
|
113
|
-
const result = new Array<T>(arr.length);
|
|
114
|
-
for (let i = 0; i < arr.length; ++i) {
|
|
115
|
-
result[i] = parse(arr[i]) as T;
|
|
116
|
-
}
|
|
117
|
-
return result;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const parse = (obj: unknown): unknown => {
|
|
121
|
-
if (String.is(obj) && obj.startsWith(kyselyJsonIdentifier)) {
|
|
122
|
-
return JSON.parse(obj.slice(kyselyJsonIdentifier.length));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (Array.isArray(obj)) {
|
|
126
|
-
return parseSqliteJsonArray(obj);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (isPlainObject(obj)) {
|
|
130
|
-
return parseObject(obj);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return obj;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const parseObject = (
|
|
137
|
-
obj: ReadonlyRecord<string, unknown>,
|
|
138
|
-
): ReadonlyRecord<string, unknown> => {
|
|
139
|
-
const result = Object.create(null) as Record<string, unknown>;
|
|
140
|
-
for (const key in obj) {
|
|
141
|
-
result[key] = parse(obj[key]);
|
|
142
|
-
}
|
|
143
|
-
return result as ReadonlyRecord<string, unknown>;
|
|
144
|
-
};
|