@evolu/common 5.4.7 → 6.0.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.
Files changed (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 💾
3
+ *
4
+ * @module
5
+ */
6
+
7
+ export { createEvolu } from "./Evolu.js";
8
+ export type {
9
+ Evolu,
10
+ EvoluConfigWithInitialData,
11
+ EvoluDeps,
12
+ EvoluError,
13
+ } from "./Evolu.js";
14
+ export * from "./Owner.js";
15
+ export { binaryIdToId, idToBinaryId } from "./Protocol.js";
16
+ export type { BinaryId } from "./Protocol.js";
17
+ export * as kysely from "./PublicKysely.js";
18
+ export type { InferRow, Query, QueryRows, Row } from "./Query.js";
19
+ export { formatValidMutationSizeError } from "./Schema.js";
20
+ export type { EvoluSchema, ValidMutationSizeError } from "./Schema.js";
21
+ export type {
22
+ NetworkError,
23
+ PaymentRequiredError,
24
+ ServerError,
25
+ SyncState,
26
+ SyncStateInitial,
27
+ SyncStateIsNotSynced,
28
+ SyncStateIsSynced,
29
+ SyncStateIsSyncing,
30
+ } from "./Sync.js";
31
+ export {
32
+ binaryTimestampToTimestamp,
33
+ Timestamp,
34
+ timestampToBinaryTimestamp,
35
+ } from "./Timestamp.js";
36
+ export type {
37
+ BinaryTimestamp,
38
+ TimestampCounterOverflowError,
39
+ TimestampDriftError,
40
+ TimestampDuplicateNodeError,
41
+ TimestampError,
42
+ TimestampTimeOutOfRangeError,
43
+ } from "./Timestamp.js";
@@ -0,0 +1,240 @@
1
+ import {
2
+ AliasableExpression,
3
+ AliasNode,
4
+ ColumnNode,
5
+ Expression,
6
+ ExpressionWrapper,
7
+ IdentifierNode,
8
+ RawBuilder,
9
+ ReferenceNode,
10
+ SelectQueryNode,
11
+ Simplify,
12
+ sql,
13
+ TableNode,
14
+ ValueNode,
15
+ } from "kysely";
16
+ import { kyselyJsonIdentifier } from "./Diff.js";
17
+
18
+ export { sql } from "kysely";
19
+ export type { NotNull } from "kysely";
20
+
21
+ /**
22
+ * A SQLite helper for aggregating a subquery into a JSON array.
23
+ *
24
+ * ### Examples
25
+ *
26
+ * ```ts
27
+ * import { kysely } from "@evolu/common";
28
+ *
29
+ * // TODO: Update for Evolu
30
+ * const result = await db
31
+ * .selectFrom("person")
32
+ * .select((eb) => [
33
+ * "id",
34
+ * kysely
35
+ * .jsonArrayFrom(
36
+ * eb
37
+ * .selectFrom("pet")
38
+ * .select(["pet.id as pet_id", "pet.name"])
39
+ * .whereRef("pet.owner_id", "=", "person.id")
40
+ * .orderBy("pet.name"),
41
+ * )
42
+ * .as("pets"),
43
+ * ])
44
+ * .execute();
45
+ *
46
+ * result[0]?.id;
47
+ * result[0]?.pets[0].pet_id;
48
+ * result[0]?.pets[0].name;
49
+ * ```
50
+ *
51
+ * The generated SQL (SQLite):
52
+ *
53
+ * ```sql
54
+ * select "id", (
55
+ * select coalesce(json_group_array(json_object(
56
+ * 'pet_id', "agg"."pet_id",
57
+ * 'name', "agg"."name"
58
+ * )), '[]') from (
59
+ * select "pet"."id" as "pet_id", "pet"."name"
60
+ * from "pet"
61
+ * where "pet"."owner_id" = "person"."id"
62
+ * order by "pet"."name"
63
+ * ) as "agg"
64
+ * ) as "pets"
65
+ * from "person"
66
+ * ```
67
+ */
68
+ // SQL formatting breaks generated SQL for some reason.
69
+ // prettier-ignore
70
+ export function jsonArrayFrom<O>(
71
+ expr: SelectQueryBuilderExpression<O>,
72
+ ): RawBuilder<Array<Simplify<O>>> {
73
+ return sql`(select ${sql.lit(kyselyJsonIdentifier)} || coalesce(json_group_array(json_object(${sql.join(
74
+ getSqliteJsonObjectArgs(expr.toOperationNode(), 'agg'),
75
+ )})), '[]') from ${expr} as agg)`
76
+ }
77
+
78
+ /**
79
+ * A SQLite helper for turning a subquery into a JSON object.
80
+ *
81
+ * The subquery must only return one row.
82
+ *
83
+ * ### Examples
84
+ *
85
+ * ```ts
86
+ * import { kysely } from "@evolu/common";
87
+ *
88
+ * // TODO: Update for Evolu
89
+ * const result = await db
90
+ * .selectFrom("person")
91
+ * .select((eb) => [
92
+ * "id",
93
+ * jsonObjectFrom(
94
+ * eb
95
+ * .selectFrom("pet")
96
+ * .select(["pet.id as pet_id", "pet.name"])
97
+ * .whereRef("pet.owner_id", "=", "person.id")
98
+ * .where("pet.is_favorite", "=", true),
99
+ * ).as("favorite_pet"),
100
+ * ])
101
+ * .execute();
102
+ *
103
+ * result[0]?.id;
104
+ * result[0]?.favorite_pet?.pet_id;
105
+ * result[0]?.favorite_pet?.name;
106
+ * ```
107
+ *
108
+ * The generated SQL (SQLite):
109
+ *
110
+ * ```sql
111
+ * select "id", (
112
+ * select json_object(
113
+ * 'pet_id', "obj"."pet_id",
114
+ * 'name', "obj"."name"
115
+ * ) from (
116
+ * select "pet"."id" as "pet_id", "pet"."name"
117
+ * from "pet"
118
+ * where "pet"."owner_id" = "person"."id"
119
+ * and "pet"."is_favorite" = ?
120
+ * ) as obj
121
+ * ) as "favorite_pet"
122
+ * from "person";
123
+ * ```
124
+ */
125
+ // SQL formatting breaks generated SQL for some reason.
126
+ // prettier-ignore
127
+ export function jsonObjectFrom<O>(
128
+ expr: SelectQueryBuilderExpression<O>,
129
+ ): RawBuilder<Simplify<O> | null> {
130
+ return sql`(select ${sql.lit(kyselyJsonIdentifier)} || json_object(${sql.join(
131
+ getSqliteJsonObjectArgs(expr.toOperationNode(), 'obj'),
132
+ )}) from ${expr} as obj)`
133
+ }
134
+
135
+ /**
136
+ * The SQLite `json_object` function.
137
+ *
138
+ * ### Examples
139
+ *
140
+ * ```ts
141
+ * import { kysely } from "@evolu/common";
142
+ *
143
+ * // TODO: Update for Evolu
144
+ * const result = await db
145
+ * .selectFrom("person")
146
+ * .select((eb) => [
147
+ * "id",
148
+ * kysely
149
+ * .jsonBuildObject({
150
+ * first: eb.ref("first_name"),
151
+ * last: eb.ref("last_name"),
152
+ * full: kysely.sql<string>`first_name || ' ' || last_name`,
153
+ * })
154
+ * .as("name"),
155
+ * ])
156
+ * .execute();
157
+ *
158
+ * result[0]?.id;
159
+ * result[0]?.name.first;
160
+ * result[0]?.name.last;
161
+ * result[0]?.name.full;
162
+ * ```
163
+ *
164
+ * The generated SQL (SQLite):
165
+ *
166
+ * ```sql
167
+ * select "id", json_object(
168
+ * 'first', first_name,
169
+ * 'last', last_name,
170
+ * 'full', "first_name" || ' ' || "last_name"
171
+ * ) as "name"
172
+ * from "person"
173
+ * ```
174
+ */
175
+ // SQL formatting breaks generated SQL for some reason.
176
+ // prettier-ignore
177
+ export function jsonBuildObject<O extends Record<string, Expression<unknown>>>(
178
+ obj: O,
179
+ ): RawBuilder<
180
+ Simplify<{
181
+ [K in keyof O]: O[K] extends Expression<infer V> ? V : never
182
+ }>
183
+ > {
184
+ return sql`${sql.lit(kyselyJsonIdentifier)} || json_object(${sql.join(
185
+ Object.keys(obj).flatMap((k) => [sql.lit(k), obj[k]]),
186
+ )})`
187
+ }
188
+
189
+ interface SelectQueryBuilderExpression<O> extends AliasableExpression<O> {
190
+ get isSelectQueryBuilder(): true;
191
+ toOperationNode(): SelectQueryNode;
192
+ }
193
+
194
+ function getSqliteJsonObjectArgs(
195
+ node: SelectQueryNode,
196
+ table: string,
197
+ ): Array<Expression<unknown> | string> {
198
+ try {
199
+ return getJsonObjectArgs(node, table);
200
+ } catch {
201
+ throw new Error(
202
+ "SQLite jsonArrayFrom and jsonObjectFrom functions can only handle explicit selections due to limitations of the json_object function. selectAll() is not allowed in the subquery.",
203
+ );
204
+ }
205
+ }
206
+
207
+ export function getJsonObjectArgs(
208
+ node: SelectQueryNode,
209
+ table: string,
210
+ ): Array<Expression<unknown> | string> {
211
+ const args: Array<Expression<unknown> | string> = [];
212
+ args.push(kyselyJsonIdentifier, kyselyJsonIdentifier);
213
+
214
+ for (const { selection: s } of node.selections ?? []) {
215
+ if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
216
+ args.push(
217
+ colName(s.column.column.name),
218
+ colRef(table, s.column.column.name),
219
+ );
220
+ } else if (ColumnNode.is(s)) {
221
+ args.push(colName(s.column.name), colRef(table, s.column.name));
222
+ } else if (AliasNode.is(s) && IdentifierNode.is(s.alias)) {
223
+ args.push(colName(s.alias.name), colRef(table, s.alias.name));
224
+ } else {
225
+ throw new Error(`can't extract column names from the select query node`);
226
+ }
227
+ }
228
+
229
+ return args;
230
+ }
231
+
232
+ function colName(col: string): Expression<unknown> {
233
+ return new ExpressionWrapper(ValueNode.createImmediate(col));
234
+ }
235
+
236
+ function colRef(table: string, col: string): Expression<unknown> {
237
+ return new ExpressionWrapper(
238
+ ReferenceNode.create(ColumnNode.create(col), TableNode.create(table)),
239
+ );
240
+ }
@@ -0,0 +1,167 @@
1
+ import { bytesToHex, hexToBytes } from "../Buffer.js";
2
+ import { objectToEntries } from "../Object.js";
3
+ import {
4
+ SafeSql,
5
+ SqliteQuery,
6
+ SqliteQueryOptions,
7
+ SqliteRow,
8
+ SqliteValue,
9
+ } from "../Sqlite.js";
10
+ import { Store, StoreSubscribe } from "../Store.js";
11
+ import { Brand, Simplify } from "../Types.js";
12
+
13
+ /**
14
+ * A type-safe SQL query.
15
+ *
16
+ * ### Example
17
+ *
18
+ * ```ts
19
+ * const allTodos = evolu.createQuery((db) =>
20
+ * db.selectFrom("todo").selectAll(),
21
+ * );
22
+ * type AllTodosRow = typeof allTodos.Row;
23
+ * ```
24
+ */
25
+ export type Query<R extends Row = Row> = string &
26
+ Brand<"Query"> & {
27
+ /**
28
+ * A shorthand for {@link InferRow}.
29
+ *
30
+ * ### Example
31
+ *
32
+ * ```ts
33
+ * const allTodos = evolu.createQuery((db) =>
34
+ * db.selectFrom("todo").selectAll(),
35
+ * );
36
+ * type AllTodosRow = typeof allTodos.Row;
37
+ * ```
38
+ */
39
+ Row: R;
40
+ };
41
+
42
+ /**
43
+ * Evolu serializes {@link SqliteQuery} into a string to be easily used as a key
44
+ * and for comparison.
45
+ */
46
+ export const serializeQuery = <R extends Row>(query: SqliteQuery): Query<R> => {
47
+ const params = query.parameters.map((v) =>
48
+ v instanceof Uint8Array
49
+ ? (["b", bytesToHex(v)] as const)
50
+ : (["j", v] as const),
51
+ );
52
+
53
+ const options = query.options
54
+ ? objectToEntries(query.options).sort(([a], [b]) => a.localeCompare(b))
55
+ : [];
56
+
57
+ return JSON.stringify([query.sql, params, options]) as Query<R>;
58
+ };
59
+
60
+ export const deserializeQuery = <R extends Row>(
61
+ query: Query<R>,
62
+ ): SqliteQuery => {
63
+ const [sql, paramsArr, optionsArr] = JSON.parse(query) as [
64
+ SafeSql,
65
+ Array<readonly ["b", string] | readonly ["j", string | number | null]>,
66
+ Array<Array<string | number | null>>,
67
+ ];
68
+
69
+ const parameters = paramsArr.map(([type, value]) =>
70
+ type === "b" ? hexToBytes(value) : value,
71
+ );
72
+
73
+ const options = optionsArr.length
74
+ ? (Object.fromEntries(optionsArr) as SqliteQueryOptions)
75
+ : undefined;
76
+
77
+ return {
78
+ sql,
79
+ parameters,
80
+ ...(options !== undefined && { options }),
81
+ };
82
+ };
83
+
84
+ export type InferRow<T extends Query> = T extends Query<infer R> ? R : never;
85
+
86
+ export interface Row {
87
+ readonly [key: string]:
88
+ | SqliteValue
89
+ | Row // for jsonObjectFrom from kysely/helpers/sqlite
90
+ | ReadonlyArray<Row>; // for jsonArrayFrom from kysely/helpers/sqlite
91
+ }
92
+
93
+ // To preserve identity.
94
+ export const emptyRows: ReadonlyArray<Row> = [];
95
+
96
+ /** Rows returned by a query. */
97
+ export type QueryRows<R extends Row = Row> = ReadonlyArray<
98
+ Readonly<Simplify<R>>
99
+ >;
100
+
101
+ export type Queries<R extends Row = Row> = ReadonlyArray<Query<R>>;
102
+
103
+ export type QueriesToQueryRows<Q extends Queries> = {
104
+ [P in keyof Q]: Q[P] extends Query<infer R> ? QueryRows<R> : never;
105
+ };
106
+
107
+ export type QueriesToQueryRowsPromises<Q extends Queries> = {
108
+ [P in keyof Q]: Q[P] extends Query<infer R> ? Promise<QueryRows<R>> : never;
109
+ };
110
+
111
+ export type QueryRowsMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
112
+
113
+ export interface QueryRowsCache {
114
+ readonly set: (
115
+ queriesRows: ReadonlyArray<readonly [Query, ReadonlyArray<SqliteRow>]>,
116
+ ) => void;
117
+ readonly get: () => QueryRowsMap;
118
+ }
119
+
120
+ export const createQueryRowsCache = (): QueryRowsCache => {
121
+ let queryRowsCache: QueryRowsMap = new Map();
122
+
123
+ const cache: QueryRowsCache = {
124
+ set: (queriesRows) => {
125
+ queryRowsCache = new Map([...queryRowsCache, ...queriesRows]);
126
+ },
127
+ get: () => queryRowsCache,
128
+ };
129
+
130
+ return cache;
131
+ };
132
+
133
+ export interface SubscribedQueries {
134
+ subscribe: (query: Query) => StoreSubscribe;
135
+
136
+ has: (query: Query) => boolean;
137
+
138
+ get: () => ReadonlyArray<Query>;
139
+ }
140
+
141
+ export const createSubscribedQueries = (
142
+ rowsStore: Store<QueryRowsMap>,
143
+ ): SubscribedQueries => {
144
+ const subscribedQueryMap = new Map<Query, number>();
145
+
146
+ const subscribedQueries: SubscribedQueries = {
147
+ subscribe: (query) => (listener) => {
148
+ subscribedQueryMap.set(query, (subscribedQueryMap.get(query) ?? 0) + 1);
149
+ const unsubscribe = rowsStore.subscribe(listener);
150
+ return () => {
151
+ const count = subscribedQueryMap.get(query);
152
+ if (count != null && count > 1) {
153
+ subscribedQueryMap.set(query, count - 1);
154
+ } else {
155
+ subscribedQueryMap.delete(query);
156
+ }
157
+ unsubscribe();
158
+ };
159
+ },
160
+
161
+ get: () => [...subscribedQueryMap.keys()],
162
+
163
+ has: (query) => subscribedQueryMap.has(query),
164
+ };
165
+
166
+ return subscribedQueries;
167
+ };
@@ -0,0 +1,142 @@
1
+ import { isNonEmptyReadonlyArray } from "../Array.js";
2
+ import { ConsoleConfig } from "../Console.js";
3
+ import { eqArrayNumber } from "../Eq.js";
4
+ import { ok, Result } from "../Result.js";
5
+ import { sql, SqliteError } from "../Sqlite.js";
6
+ import { SimpleName } from "../Type.js";
7
+ import { OwnerId, WriteKey } from "./Owner.js";
8
+ import { EncryptedDbChange, Storage } from "./Protocol.js";
9
+ import {
10
+ createSqliteStorageBase,
11
+ CreateSqliteStorageBaseOptions,
12
+ SqliteStorageDeps,
13
+ } from "./Storage.js";
14
+ import { timestampToBinaryTimestamp } from "./Timestamp.js";
15
+
16
+ export interface Relay extends Disposable {}
17
+
18
+ export interface RelayConfig extends ConsoleConfig {
19
+ readonly name?: SimpleName;
20
+ }
21
+
22
+ export const createRelayStorage =
23
+ (deps: SqliteStorageDeps) =>
24
+ (options: CreateSqliteStorageBaseOptions): Result<Storage, SqliteError> => {
25
+ const sqliteStorageBase = createSqliteStorageBase(deps)(options);
26
+ if (!sqliteStorageBase.ok) return sqliteStorageBase;
27
+
28
+ for (const query of [
29
+ sql`
30
+ create table if not exists evolu_writeKey (
31
+ "ownerId" blob not null,
32
+ "writeKey" blob not null,
33
+ primary key ("ownerId")
34
+ )
35
+ strict;
36
+ `,
37
+
38
+ sql`
39
+ create table if not exists evolu_message (
40
+ "ownerId" blob not null,
41
+ "timestamp" blob not null,
42
+ "change" blob not null,
43
+ primary key ("ownerId", "timestamp")
44
+ )
45
+ strict;
46
+ `,
47
+ ]) {
48
+ const result = deps.sqlite.exec(query);
49
+ if (!result.ok) return result;
50
+ }
51
+
52
+ return ok({
53
+ ...sqliteStorageBase.value,
54
+
55
+ /**
56
+ * Lazily authorizes the initiator's {@link WriteKey} for the given
57
+ * {@link OwnerId}.
58
+ *
59
+ * - If the {@link OwnerId} does not exist, it is created and associated with
60
+ * the provided write key.
61
+ * - If the {@link OwnerId} exists, the provided write key is compared to the
62
+ * stored key.
63
+ */
64
+ validateWriteKey: (ownerId, writeKey) => {
65
+ const selectWriteKey = deps.sqlite.exec<{ writeKey: WriteKey }>(sql`
66
+ select writeKey
67
+ from evolu_writeKey
68
+ where ownerId = ${ownerId};
69
+ `);
70
+ if (!selectWriteKey.ok) {
71
+ options.onStorageError(selectWriteKey.error);
72
+ return false;
73
+ }
74
+
75
+ const { rows } = selectWriteKey.value;
76
+
77
+ if (!isNonEmptyReadonlyArray(rows)) {
78
+ const insertWriteKey = deps.sqlite.exec(sql`
79
+ insert into evolu_writeKey (ownerId, writeKey)
80
+ values (${ownerId}, ${writeKey});
81
+ `);
82
+ if (!insertWriteKey.ok) {
83
+ options.onStorageError(insertWriteKey.error);
84
+ return false;
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ return eqArrayNumber(rows[0].writeKey, writeKey);
91
+ },
92
+
93
+ writeMessages: (ownerId, messages) => {
94
+ const result = deps.sqlite.transaction(() => {
95
+ for (const message of messages) {
96
+ const insertTimestampResult =
97
+ sqliteStorageBase.value.insertTimestamp(
98
+ ownerId,
99
+ timestampToBinaryTimestamp(message.timestamp),
100
+ );
101
+ if (!insertTimestampResult.ok) return insertTimestampResult;
102
+
103
+ const insertMessage = deps.sqlite.exec(sql`
104
+ insert into evolu_message ("ownerId", "timestamp", "change")
105
+ values
106
+ (
107
+ ${ownerId},
108
+ ${timestampToBinaryTimestamp(message.timestamp)},
109
+ ${message.change}
110
+ )
111
+ on conflict do nothing;
112
+ `);
113
+ if (!insertMessage.ok) return insertMessage;
114
+ }
115
+ return ok();
116
+ });
117
+
118
+ if (!result.ok) {
119
+ options.onStorageError(result.error);
120
+ return false;
121
+ }
122
+
123
+ return true;
124
+ },
125
+
126
+ readDbChange: (ownerId, timestamp) => {
127
+ const result = deps.sqlite.exec<{
128
+ change: EncryptedDbChange;
129
+ }>(sql`
130
+ select "change"
131
+ from evolu_message
132
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
133
+ `);
134
+ if (!result.ok) {
135
+ options.onStorageError(result.error);
136
+ return null;
137
+ }
138
+
139
+ return result.value.rows[0]?.change;
140
+ },
141
+ });
142
+ };