@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.
Files changed (250) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +162 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +200 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +29 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +329 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2274 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1499 -348
  107. package/dist/src/Test.d.ts +150 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +97 -0
  110. package/dist/src/Time.d.ts +202 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +199 -88
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +587 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +370 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +130 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +274 -113
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +621 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +34 -49
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -177
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +54 -58
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +127 -179
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +37 -24
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +429 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +520 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3696 -740
  206. package/src/Test.ts +202 -0
  207. package/src/Time.ts +357 -147
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +866 -430
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +404 -167
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +867 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -824
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +441 -307
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +119 -273
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +211 -283
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/dist/src/Instances.d.ts +0 -34
  228. package/dist/src/Instances.d.ts.map +0 -1
  229. package/dist/src/Instances.js +0 -44
  230. package/dist/src/Resources.d.ts +0 -118
  231. package/dist/src/Resources.d.ts.map +0 -1
  232. package/dist/src/Resources.js +0 -197
  233. package/dist/src/local-first/Platform.d.ts +0 -25
  234. package/dist/src/local-first/Platform.d.ts.map +0 -1
  235. package/dist/src/local-first/Platform.js +0 -1
  236. package/dist/src/local-first/Public.d.ts +0 -22
  237. package/dist/src/local-first/Public.d.ts.map +0 -1
  238. package/dist/src/local-first/Public.js +0 -15
  239. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  240. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  241. package/dist/src/local-first/PublicKysely.js +0 -184
  242. package/dist/src/local-first/Sync.d.ts +0 -112
  243. package/dist/src/local-first/Sync.d.ts.map +0 -1
  244. package/dist/src/local-first/Sync.js +0 -526
  245. package/src/Instances.ts +0 -92
  246. package/src/Resources.ts +0 -367
  247. package/src/local-first/Platform.ts +0 -27
  248. package/src/local-first/Public.ts +0 -42
  249. package/src/local-first/PublicKysely.ts +0 -239
  250. package/src/local-first/Sync.ts +0 -949
@@ -1,27 +1,38 @@
1
- import { Brand } from "../Brand.js";
2
- import { bytesToHex, hexToBytes } from "../Buffer.js";
3
- import { createRandomBytes } from "../Crypto.js";
4
- import {
5
- createRecord,
6
- isPlainObject,
7
- objectToEntries,
8
- ReadonlyRecord,
9
- } from "../Object.js";
10
- import { ok, Result } from "../Result.js";
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
- eqSqliteValue,
13
- explainSqliteQueryPlan,
14
- SafeSql,
15
- SqliteDep,
16
- SqliteError,
17
- SqliteQuery,
18
- SqliteQueryOptions,
19
- SqliteRow,
20
- SqliteValue,
21
- } from "../Sqlite.js";
22
- import { Store, StoreSubscribe } from "../Store.js";
23
- import { createId, Id, String } from "../Type.js";
24
- import { Simplify } from "../Types.js";
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 allTodos = evolu.createQuery((db) =>
33
- * db.selectFrom("todo").selectAll(),
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<R extends Row = Row> = string &
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 allTodos = evolu.createQuery((db) =>
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
- * Evolu serializes {@link SqliteQuery} into a string to be easily used as a key
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 jsonObjectFrom from kysely/helpers/sqlite
103
- | ReadonlyArray<Row>; // for jsonArrayFrom from kysely/helpers/sqlite
78
+ | Row // for evoluJsonObjectFrom
79
+ | ReadonlyArray<Row>; // for evoluJsonArrayFrom
104
80
  }
105
81
 
106
- // To preserve identity.
107
- export const emptyRows: ReadonlyArray<Row> = [];
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<R extends Row = Row> = ReadonlyArray<Query<R>>;
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> ? Promise<QueryRows<R>> : never;
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 QueryRowsMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
125
-
126
- export interface SubscribedQueries {
127
- subscribe: (query: Query) => StoreSubscribe;
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
+ );