@evolu/common 7.4.1 → 8.0.0-next.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.
- package/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +162 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +200 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +29 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +329 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2274 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1499 -348
- package/dist/src/Test.d.ts +150 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +97 -0
- package/dist/src/Time.d.ts +202 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +199 -88
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +587 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +370 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +130 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +274 -113
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +621 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +34 -49
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -177
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +54 -58
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +127 -179
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +37 -24
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +429 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +520 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3696 -740
- package/src/Test.ts +202 -0
- package/src/Time.ts +357 -147
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +866 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +404 -167
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +867 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -824
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +441 -307
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +119 -273
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +211 -283
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -526
- package/src/Instances.ts +0 -92
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -949
package/src/local-first/Query.ts
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Query helpers, execution, and caching.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
AliasableExpression,
|
|
9
|
+
Expression,
|
|
10
|
+
Simplify as KyselySimplify,
|
|
11
|
+
RawBuilder,
|
|
12
|
+
SelectQueryNode,
|
|
13
|
+
} from "kysely";
|
|
11
14
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "../
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
15
|
+
AliasNode,
|
|
16
|
+
ColumnNode,
|
|
17
|
+
ExpressionWrapper,
|
|
18
|
+
IdentifierNode,
|
|
19
|
+
sql as kyselySqlBuilder,
|
|
20
|
+
ReferenceNode,
|
|
21
|
+
TableNode,
|
|
22
|
+
ValueNode,
|
|
23
|
+
} from "kysely";
|
|
24
|
+
import type { Brand } from "../Brand.js";
|
|
25
|
+
import { createRandomBytes } from "../Crypto.js";
|
|
26
|
+
import type { ReadonlyRecord } from "../Object.js";
|
|
27
|
+
import { createRecord, isPlainObject } from "../Object.js";
|
|
28
|
+
import type { SqliteQueryString } from "../Sqlite.js";
|
|
29
|
+
import { eqSqliteValue, SqliteValue } from "../Sqlite.js";
|
|
30
|
+
import { createId, String } from "../Type.js";
|
|
31
|
+
import type { Simplify } from "../Types.js";
|
|
32
|
+
import type { EvoluSchema } from "./Schema.js";
|
|
33
|
+
|
|
34
|
+
export { sql as kyselySql } from "kysely";
|
|
35
|
+
export type { NotNull as KyselyNotNull } from "kysely";
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
38
|
* A type-safe SQL query.
|
|
@@ -29,21 +40,27 @@ import { Simplify } from "../Types.js";
|
|
|
29
40
|
* ### Example
|
|
30
41
|
*
|
|
31
42
|
* ```ts
|
|
32
|
-
* const
|
|
33
|
-
*
|
|
34
|
-
* );
|
|
43
|
+
* const createQuery = createQueryBuilder(Schema);
|
|
44
|
+
* const allTodos = createQuery((db) => db.selectFrom("todo").selectAll());
|
|
35
45
|
* type AllTodosRow = typeof allTodos.Row;
|
|
36
46
|
* ```
|
|
37
47
|
*/
|
|
38
|
-
export type Query<
|
|
48
|
+
export type Query<
|
|
49
|
+
S extends EvoluSchema = EvoluSchema,
|
|
50
|
+
R extends Row = Row,
|
|
51
|
+
> = SqliteQueryString &
|
|
39
52
|
Brand<"Query"> & {
|
|
53
|
+
/** A shorthand for the query schema. */
|
|
54
|
+
Schema: S;
|
|
55
|
+
|
|
40
56
|
/**
|
|
41
57
|
* A shorthand for {@link InferRow}.
|
|
42
58
|
*
|
|
43
59
|
* ### Example
|
|
44
60
|
*
|
|
45
61
|
* ```ts
|
|
46
|
-
* const
|
|
62
|
+
* const createQuery = createQueryBuilder(Schema);
|
|
63
|
+
* const allTodos = createQuery((db) =>
|
|
47
64
|
* db.selectFrom("todo").selectAll(),
|
|
48
65
|
* );
|
|
49
66
|
* type AllTodosRow = typeof allTodos.Row;
|
|
@@ -52,187 +69,191 @@ export type Query<R extends Row = Row> = string &
|
|
|
52
69
|
Row: R;
|
|
53
70
|
};
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* and for comparison.
|
|
58
|
-
*/
|
|
59
|
-
export const serializeQuery = <R extends Row>(query: SqliteQuery): Query<R> => {
|
|
60
|
-
const params = query.parameters.map((v) =>
|
|
61
|
-
v instanceof Uint8Array
|
|
62
|
-
? (["b", bytesToHex(v)] as const)
|
|
63
|
-
: (["j", v] as const),
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const options = query.options
|
|
67
|
-
? objectToEntries(query.options).toSorted(([a], [b]) => a.localeCompare(b))
|
|
68
|
-
: [];
|
|
69
|
-
|
|
70
|
-
return JSON.stringify([query.sql, params, options]) as Query<R>;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const deserializeQuery = <R extends Row>(
|
|
74
|
-
query: Query<R>,
|
|
75
|
-
): SqliteQuery => {
|
|
76
|
-
const [sql, paramsArr, optionsArr] = JSON.parse(query) as [
|
|
77
|
-
SafeSql,
|
|
78
|
-
Array<readonly ["b", string] | readonly ["j", string | number | null]>,
|
|
79
|
-
Array<Array<string | number | null>>,
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const parameters = paramsArr.map(([type, value]) =>
|
|
83
|
-
type === "b" ? hexToBytes(value) : value,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const options = optionsArr.length
|
|
87
|
-
? (Object.fromEntries(optionsArr) as SqliteQueryOptions)
|
|
88
|
-
: undefined;
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
sql,
|
|
92
|
-
parameters,
|
|
93
|
-
...(options !== undefined && { options }),
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export type InferRow<T extends Query> = T extends Query<infer R> ? R : never;
|
|
72
|
+
export type InferRow<T extends Query> =
|
|
73
|
+
T extends Query<any, infer R> ? R : never;
|
|
98
74
|
|
|
99
75
|
export interface Row {
|
|
100
76
|
readonly [key: string]:
|
|
101
77
|
| SqliteValue
|
|
102
|
-
| Row // for
|
|
103
|
-
| ReadonlyArray<Row>; // for
|
|
78
|
+
| Row // for evoluJsonObjectFrom
|
|
79
|
+
| ReadonlyArray<Row>; // for evoluJsonArrayFrom
|
|
104
80
|
}
|
|
105
81
|
|
|
106
|
-
|
|
107
|
-
|
|
82
|
+
/**
|
|
83
|
+
* An improved Evolu version of Kysely's SQLite `jsonArrayFrom` helper.
|
|
84
|
+
*
|
|
85
|
+
* Kysely's `ParseJSONResultsPlugin` heuristically parses any result string that
|
|
86
|
+
* looks like JSON. Evolu instead prefixes JSON produced by these helpers with a
|
|
87
|
+
* per-runtime identifier and only parses values carrying that prefix, avoiding
|
|
88
|
+
* accidental parsing of ordinary string columns that merely happen to start
|
|
89
|
+
* with `{` or `[`.
|
|
90
|
+
*
|
|
91
|
+
* ### Example
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { evoluJsonArrayFrom } from "@evolu/common";
|
|
95
|
+
*
|
|
96
|
+
* const result = await db
|
|
97
|
+
* .selectFrom("person")
|
|
98
|
+
* .select((eb) => [
|
|
99
|
+
* "id",
|
|
100
|
+
* evoluJsonArrayFrom(
|
|
101
|
+
* eb
|
|
102
|
+
* .selectFrom("pet")
|
|
103
|
+
* .select(["pet.id as pet_id", "pet.name"])
|
|
104
|
+
* .whereRef("pet.owner_id", "=", "person.id")
|
|
105
|
+
* .orderBy("pet.name"),
|
|
106
|
+
* ).as("pets"),
|
|
107
|
+
* ])
|
|
108
|
+
* .execute();
|
|
109
|
+
*
|
|
110
|
+
* result[0]?.id;
|
|
111
|
+
* result[0]?.pets[0].pet_id;
|
|
112
|
+
* result[0]?.pets[0].name;
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const evoluJsonArrayFrom = <O>(
|
|
116
|
+
expr: SelectQueryBuilderExpression<O>,
|
|
117
|
+
): RawBuilder<Array<KyselySimplify<O>>> =>
|
|
118
|
+
kyselySqlBuilder`(select ${kyselySqlBuilder.lit(kyselyJsonIdentifier)} || coalesce(json_group_array(json_object(${kyselySqlBuilder.join(
|
|
119
|
+
getSqliteJsonObjectArgs(expr.toOperationNode(), "agg"),
|
|
120
|
+
)})), '[]') from ${expr} as agg)`;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* An improved Evolu version of Kysely's SQLite `jsonObjectFrom` helper.
|
|
124
|
+
*
|
|
125
|
+
* Kysely's `ParseJSONResultsPlugin` heuristically parses any result string that
|
|
126
|
+
* looks like JSON. Evolu instead prefixes JSON produced by these helpers with a
|
|
127
|
+
* per-runtime identifier and only parses values carrying that prefix, avoiding
|
|
128
|
+
* accidental parsing of ordinary string columns that merely happen to start
|
|
129
|
+
* with `{` or `[`.
|
|
130
|
+
*
|
|
131
|
+
* The subquery must only return one row.
|
|
132
|
+
*
|
|
133
|
+
* ### Example
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { evoluJsonObjectFrom } from "@evolu/common";
|
|
137
|
+
*
|
|
138
|
+
* const result = await db
|
|
139
|
+
* .selectFrom("person")
|
|
140
|
+
* .select((eb) => [
|
|
141
|
+
* "id",
|
|
142
|
+
* evoluJsonObjectFrom(
|
|
143
|
+
* eb
|
|
144
|
+
* .selectFrom("pet")
|
|
145
|
+
* .select(["pet.id as pet_id", "pet.name"])
|
|
146
|
+
* .whereRef("pet.owner_id", "=", "person.id")
|
|
147
|
+
* .where("pet.is_favorite", "=", true),
|
|
148
|
+
* ).as("favorite_pet"),
|
|
149
|
+
* ])
|
|
150
|
+
* .execute();
|
|
151
|
+
*
|
|
152
|
+
* result[0]?.id;
|
|
153
|
+
* result[0]?.favorite_pet?.pet_id;
|
|
154
|
+
* result[0]?.favorite_pet?.name;
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export const evoluJsonObjectFrom = <O>(
|
|
158
|
+
expr: SelectQueryBuilderExpression<O>,
|
|
159
|
+
): RawBuilder<KyselySimplify<O> | null> =>
|
|
160
|
+
kyselySqlBuilder`(select ${kyselySqlBuilder.lit(kyselyJsonIdentifier)} || json_object(${kyselySqlBuilder.join(
|
|
161
|
+
getSqliteJsonObjectArgs(expr.toOperationNode(), "obj"),
|
|
162
|
+
)}) from ${expr} as obj)`;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* An improved Evolu version of Kysely's SQLite `jsonBuildObject` helper.
|
|
166
|
+
*
|
|
167
|
+
* Kysely's `ParseJSONResultsPlugin` heuristically parses any result string that
|
|
168
|
+
* looks like JSON. Evolu instead prefixes JSON produced by these helpers with a
|
|
169
|
+
* per-runtime identifier and only parses values carrying that prefix, avoiding
|
|
170
|
+
* accidental parsing of ordinary string columns that merely happen to start
|
|
171
|
+
* with `{` or `[`.
|
|
172
|
+
*
|
|
173
|
+
* ### Example
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { evoluJsonBuildObject, kyselySql } from "@evolu/common";
|
|
177
|
+
*
|
|
178
|
+
* const result = await db
|
|
179
|
+
* .selectFrom("person")
|
|
180
|
+
* .select((eb) => [
|
|
181
|
+
* "id",
|
|
182
|
+
* evoluJsonBuildObject({
|
|
183
|
+
* first: eb.ref("first_name"),
|
|
184
|
+
* last: eb.ref("last_name"),
|
|
185
|
+
* full: kyselySql<string>`first_name || ' ' || last_name`,
|
|
186
|
+
* }).as("name"),
|
|
187
|
+
* ])
|
|
188
|
+
* .execute();
|
|
189
|
+
*
|
|
190
|
+
* result[0]?.id;
|
|
191
|
+
* result[0]?.name.first;
|
|
192
|
+
* result[0]?.name.last;
|
|
193
|
+
* result[0]?.name.full;
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export const evoluJsonBuildObject = <
|
|
197
|
+
O extends Record<string, Expression<unknown>>,
|
|
198
|
+
>(
|
|
199
|
+
obj: O,
|
|
200
|
+
): RawBuilder<
|
|
201
|
+
KyselySimplify<{
|
|
202
|
+
[K in keyof O]: O[K] extends Expression<infer V> ? V : never;
|
|
203
|
+
}>
|
|
204
|
+
> =>
|
|
205
|
+
kyselySqlBuilder`${kyselySqlBuilder.lit(kyselyJsonIdentifier)} || json_object(${kyselySqlBuilder.join(
|
|
206
|
+
Object.keys(obj).flatMap((k) => [kyselySqlBuilder.lit(k), obj[k]]),
|
|
207
|
+
)})`;
|
|
208
|
+
|
|
209
|
+
export const getJsonObjectArgs = (
|
|
210
|
+
node: SelectQueryNode,
|
|
211
|
+
table: string,
|
|
212
|
+
): Array<Expression<unknown> | string> => {
|
|
213
|
+
const args: Array<Expression<unknown> | string> = [];
|
|
214
|
+
|
|
215
|
+
for (const { selection: s } of node.selections ?? []) {
|
|
216
|
+
if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
|
|
217
|
+
args.push(
|
|
218
|
+
colName(s.column.column.name),
|
|
219
|
+
colRef(table, s.column.column.name),
|
|
220
|
+
);
|
|
221
|
+
} else if (ColumnNode.is(s)) {
|
|
222
|
+
args.push(colName(s.column.name), colRef(table, s.column.name));
|
|
223
|
+
} else if (AliasNode.is(s) && IdentifierNode.is(s.alias)) {
|
|
224
|
+
args.push(colName(s.alias.name), colRef(table, s.alias.name));
|
|
225
|
+
} else {
|
|
226
|
+
throw new Error(`can't extract column names from the select query node`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return args;
|
|
231
|
+
};
|
|
108
232
|
|
|
109
233
|
/** Rows returned by a query. */
|
|
110
234
|
export type QueryRows<R extends Row = Row> = ReadonlyArray<
|
|
111
235
|
Readonly<Simplify<R>>
|
|
112
236
|
>;
|
|
113
237
|
|
|
114
|
-
export type Queries<
|
|
238
|
+
export type Queries<
|
|
239
|
+
S extends EvoluSchema = EvoluSchema,
|
|
240
|
+
R extends Row = Row,
|
|
241
|
+
> = ReadonlyArray<Query<S, R>>;
|
|
115
242
|
|
|
116
243
|
export type QueriesToQueryRows<Q extends Queries> = {
|
|
117
|
-
[P in keyof Q]: Q[P] extends Query<infer R> ? QueryRows<R> : never;
|
|
244
|
+
[P in keyof Q]: Q[P] extends Query<any, infer R> ? QueryRows<R> : never;
|
|
118
245
|
};
|
|
119
246
|
|
|
120
247
|
export type QueriesToQueryRowsPromises<Q extends Queries> = {
|
|
121
|
-
[P in keyof Q]: Q[P] extends Query<infer R>
|
|
248
|
+
[P in keyof Q]: Q[P] extends Query<any, infer R>
|
|
249
|
+
? Promise<QueryRows<R>>
|
|
250
|
+
: never;
|
|
122
251
|
};
|
|
123
252
|
|
|
124
|
-
export type
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
has: (query: Query) => boolean;
|
|
130
|
-
|
|
131
|
-
get: () => ReadonlyArray<Query>;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export const createSubscribedQueries = (
|
|
135
|
-
rowsStore: Store<QueryRowsMap>,
|
|
136
|
-
): SubscribedQueries => {
|
|
137
|
-
const subscribedQueryMap = new Map<Query, number>();
|
|
138
|
-
|
|
139
|
-
const subscribedQueries: SubscribedQueries = {
|
|
140
|
-
subscribe: (query) => (listener) => {
|
|
141
|
-
subscribedQueryMap.set(query, (subscribedQueryMap.get(query) ?? 0) + 1);
|
|
142
|
-
const unsubscribe = rowsStore.subscribe(listener);
|
|
143
|
-
return () => {
|
|
144
|
-
const count = subscribedQueryMap.get(query);
|
|
145
|
-
if (count != null && count > 1) {
|
|
146
|
-
subscribedQueryMap.set(query, count - 1);
|
|
147
|
-
} else {
|
|
148
|
-
subscribedQueryMap.delete(query);
|
|
149
|
-
}
|
|
150
|
-
unsubscribe();
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
get: () => [...subscribedQueryMap.keys()],
|
|
155
|
-
|
|
156
|
-
has: (query) => subscribedQueryMap.has(query),
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return subscribedQueries;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
export interface GetQueryRowsCacheDep {
|
|
163
|
-
readonly getQueryRowsCache: GetQueryRowsCache;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export type GetQueryRowsCache = (tabId: Id) => QueryRowsCache;
|
|
167
|
-
|
|
168
|
-
export interface QueryRowsCache {
|
|
169
|
-
readonly set: (
|
|
170
|
-
queriesRows: ReadonlyArray<readonly [Query, ReadonlyArray<SqliteRow>]>,
|
|
171
|
-
) => void;
|
|
172
|
-
readonly get: () => QueryRowsMap;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export const createGetQueryRowsCache = (): GetQueryRowsCache => {
|
|
176
|
-
const tabQueryRowsCacheMap = new Map<Id, QueryRowsCache>();
|
|
177
|
-
|
|
178
|
-
return (tabId: Id) => {
|
|
179
|
-
let cache = tabQueryRowsCacheMap.get(tabId);
|
|
180
|
-
if (!cache) {
|
|
181
|
-
let queryRowsCache: QueryRowsMap = new Map();
|
|
182
|
-
cache = {
|
|
183
|
-
set: (queriesRows) => {
|
|
184
|
-
queryRowsCache = new Map([...queryRowsCache, ...queriesRows]);
|
|
185
|
-
},
|
|
186
|
-
get: () => queryRowsCache,
|
|
187
|
-
};
|
|
188
|
-
tabQueryRowsCacheMap.set(tabId, cache);
|
|
189
|
-
}
|
|
190
|
-
return cache;
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export const loadQueries =
|
|
195
|
-
(deps: GetQueryRowsCacheDep & SqliteDep) =>
|
|
196
|
-
(
|
|
197
|
-
tabId: Id,
|
|
198
|
-
queries: ReadonlyArray<Query>,
|
|
199
|
-
): Result<ReadonlyArray<QueryPatches>, SqliteError> => {
|
|
200
|
-
const queriesRows = [];
|
|
201
|
-
|
|
202
|
-
for (const query of queries) {
|
|
203
|
-
const sqlQuery = deserializeQuery(query);
|
|
204
|
-
const result = deps.sqlite.exec(sqlQuery);
|
|
205
|
-
if (!result.ok) return result;
|
|
206
|
-
|
|
207
|
-
queriesRows.push([query, result.value.rows] as const);
|
|
208
|
-
if (sqlQuery.options?.logExplainQueryPlan) {
|
|
209
|
-
explainSqliteQueryPlan(deps)(sqlQuery);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const queryRowsCache = deps.getQueryRowsCache(tabId);
|
|
214
|
-
|
|
215
|
-
const previousState = queryRowsCache.get();
|
|
216
|
-
queryRowsCache.set(queriesRows);
|
|
217
|
-
|
|
218
|
-
const currentState = queryRowsCache.get();
|
|
219
|
-
|
|
220
|
-
const queryPatchesArray = queries.map(
|
|
221
|
-
(query): QueryPatches => ({
|
|
222
|
-
query,
|
|
223
|
-
patches: makePatches(
|
|
224
|
-
previousState.get(query),
|
|
225
|
-
currentState.get(query) ?? emptyRows,
|
|
226
|
-
),
|
|
227
|
-
}),
|
|
228
|
-
);
|
|
229
|
-
return ok(queryPatchesArray);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
export interface QueryPatches {
|
|
233
|
-
readonly query: Query;
|
|
234
|
-
readonly patches: ReadonlyArray<Patch>;
|
|
235
|
-
}
|
|
253
|
+
export type RowsByQueryMap<S extends EvoluSchema = EvoluSchema> = ReadonlyMap<
|
|
254
|
+
Query<S>,
|
|
255
|
+
ReadonlyArray<Row>
|
|
256
|
+
>;
|
|
236
257
|
|
|
237
258
|
export type Patch = ReplaceAllPatch | ReplaceAtPatch;
|
|
238
259
|
|
|
@@ -258,8 +279,10 @@ export const makePatches = (
|
|
|
258
279
|
previousRows: ReadonlyArray<Row> | undefined,
|
|
259
280
|
nextRows: ReadonlyArray<Row>,
|
|
260
281
|
): ReadonlyArray<Patch> => {
|
|
261
|
-
if (previousRows === undefined)
|
|
282
|
+
if (previousRows === undefined) {
|
|
262
283
|
return [{ op: "replaceAll", value: nextRows }];
|
|
284
|
+
}
|
|
285
|
+
|
|
263
286
|
// TODO: Detect prepend and append, it's cheap.
|
|
264
287
|
if (previousRows.length !== nextRows.length) {
|
|
265
288
|
return [{ op: "replaceAll", value: nextRows }];
|
|
@@ -322,10 +345,28 @@ export const applyPatches = (
|
|
|
322
345
|
*
|
|
323
346
|
* See: https://github.com/kysely-org/kysely/issues/1372#issuecomment-2702773948
|
|
324
347
|
*/
|
|
325
|
-
export const kyselyJsonIdentifier = createId({
|
|
326
|
-
randomBytes: createRandomBytes(),
|
|
348
|
+
export const kyselyJsonIdentifier = /*#__PURE__*/ createId({
|
|
349
|
+
randomBytes: /*#__PURE__*/ createRandomBytes(),
|
|
327
350
|
});
|
|
328
351
|
|
|
352
|
+
interface SelectQueryBuilderExpression<O> extends AliasableExpression<O> {
|
|
353
|
+
get isSelectQueryBuilder(): true;
|
|
354
|
+
toOperationNode(): SelectQueryNode;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const getSqliteJsonObjectArgs = (
|
|
358
|
+
node: SelectQueryNode,
|
|
359
|
+
table: string,
|
|
360
|
+
): Array<Expression<unknown> | string> => {
|
|
361
|
+
try {
|
|
362
|
+
return getJsonObjectArgs(node, table);
|
|
363
|
+
} catch {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"SQLite evoluJsonArrayFrom and evoluJsonObjectFrom can only handle explicit selections due to limitations of the json_object function. selectAll() is not allowed in the subquery.",
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
329
370
|
export const parseSqliteJsonArray = <T>(
|
|
330
371
|
arr: ReadonlyArray<T>,
|
|
331
372
|
): ReadonlyArray<T> => {
|
|
@@ -338,7 +379,7 @@ export const parseSqliteJsonArray = <T>(
|
|
|
338
379
|
|
|
339
380
|
const parse = (obj: unknown): unknown => {
|
|
340
381
|
if (String.is(obj) && obj.startsWith(kyselyJsonIdentifier)) {
|
|
341
|
-
return JSON.parse(obj.slice(kyselyJsonIdentifier.length));
|
|
382
|
+
return parse(JSON.parse(obj.slice(kyselyJsonIdentifier.length)));
|
|
342
383
|
}
|
|
343
384
|
|
|
344
385
|
if (Array.isArray(obj)) {
|
|
@@ -361,3 +402,11 @@ const parseObject = (
|
|
|
361
402
|
}
|
|
362
403
|
return result as ReadonlyRecord<string, unknown>;
|
|
363
404
|
};
|
|
405
|
+
|
|
406
|
+
const colName = (col: string): Expression<unknown> =>
|
|
407
|
+
new ExpressionWrapper(ValueNode.createImmediate(col));
|
|
408
|
+
|
|
409
|
+
const colRef = (table: string, col: string): Expression<unknown> =>
|
|
410
|
+
new ExpressionWrapper(
|
|
411
|
+
ReferenceNode.create(ColumnNode.create(col), TableNode.create(table)),
|
|
412
|
+
);
|