@evolu/common 7.4.1 → 8.0.0-next.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 (251) 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 +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -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 +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -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 +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  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 +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -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 +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  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 +622 -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 +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  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 +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  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 +36 -26
  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 +415 -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 +465 -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 +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +851 -430
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -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 +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  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 +206 -277
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -526
  246. package/src/Instances.ts +0 -92
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -949
package/src/Resources.ts DELETED
@@ -1,367 +0,0 @@
1
- import { err, ok, Result } from "./Result.js";
2
- import { PositiveInt } from "./Type.js";
3
-
4
- /**
5
- * A generic resource manager that handles reference counting and delayed
6
- * disposal of shared resources. Useful for managing expensive resources like
7
- * WebSocket connections that need to be shared among multiple consumers.
8
- */
9
- export interface Resources<
10
- TResource extends Disposable,
11
- TResourceKey extends string,
12
- TResourceConfig,
13
- TConsumer,
14
- TConsumerId extends string,
15
- > extends Disposable {
16
- /**
17
- * Adds a consumer to resources, creating them if necessary. Increments
18
- * reference counts for existing consumer-resource pairs.
19
- */
20
- readonly addConsumer: (
21
- consumer: TConsumer,
22
- resourceConfigs: ReadonlyArray<TResourceConfig>,
23
- ) => void;
24
-
25
- /**
26
- * Removes a consumer from resources. Decrements reference counts and
27
- * schedules disposal when no consumers remain.
28
- *
29
- * Returns an error if the resource doesn't exist or if the consumer wasn't
30
- * added to the resource.
31
- */
32
- readonly removeConsumer: (
33
- consumer: TConsumer,
34
- resourceConfigs: ReadonlyArray<TResourceConfig>,
35
- ) => Result<
36
- void,
37
- | ResourceNotFoundError<TResourceKey>
38
- | ConsumerNotFoundError<TConsumerId, TResourceKey>
39
- >;
40
-
41
- /** Gets the resource for the specified key, or null if it doesn't exist. */
42
- readonly getResource: (key: TResourceKey) => TResource | null;
43
-
44
- /** Gets all consumer IDs currently using the specified resource key. */
45
- readonly getConsumersForResource: (
46
- key: TResourceKey,
47
- ) => ReadonlyArray<TConsumerId>;
48
-
49
- /** Checks if a consumer is currently using any resources. */
50
- readonly hasConsumerAnyResource: (consumer: TConsumer) => boolean;
51
-
52
- /**
53
- * Gets the consumer for the specified consumer ID, or null if not found or
54
- * not using any resources.
55
- */
56
- readonly getConsumer: (consumerId: TConsumerId) => TConsumer | null;
57
- }
58
-
59
- /** Error when trying to remove a consumer from a resource that doesn't exist. */
60
- export interface ResourceNotFoundError<TResourceKey extends string = string> {
61
- readonly type: "ResourceNotFoundError";
62
- readonly resourceKey: TResourceKey;
63
- }
64
-
65
- /** Error when trying to remove a consumer that wasn't added to a resource. */
66
- export interface ConsumerNotFoundError<
67
- TConsumerId extends string = string,
68
- TResourceKey extends string = string,
69
- > {
70
- readonly type: "ConsumerNotFoundError";
71
- readonly consumerId: TConsumerId;
72
- readonly resourceKey: TResourceKey;
73
- }
74
-
75
- export interface ResourcesConfig<
76
- TResource extends Disposable,
77
- TResourceKey extends string,
78
- TResourceConfig,
79
- TConsumer,
80
- TConsumerId extends string,
81
- > {
82
- /** Creates a new resource for the given config. */
83
- readonly createResource: (config: TResourceConfig) => TResource;
84
-
85
- /** Extracts a unique key from a resource config for deduplication. */
86
- readonly getResourceKey: (config: TResourceConfig) => TResourceKey;
87
-
88
- /** Extracts a unique identifier from a consumer for reference counting. */
89
- readonly getConsumerId: (consumer: TConsumer) => TConsumerId;
90
-
91
- /**
92
- * Delay in milliseconds before disposing unused resources. Helps avoid
93
- * resource churn during rapid add/remove cycles. Defaults to 100ms.
94
- */
95
- readonly disposalDelay?: number;
96
-
97
- /**
98
- * Called when a consumer is added to a resource for the first time. This
99
- * happens when the consumer's reference count goes from 0 to 1 for this
100
- * resource.
101
- */
102
- readonly onConsumerAdded?: (
103
- consumer: TConsumer,
104
- resource: TResource,
105
- resourceKey: TResourceKey,
106
- ) => void;
107
-
108
- /**
109
- * Called when a consumer is completely removed from a resource. This happens
110
- * when the consumer's reference count goes from 1 to 0 for this resource.
111
- */
112
- readonly onConsumerRemoved?: (
113
- consumer: TConsumer,
114
- resource: TResource,
115
- resourceKey: TResourceKey,
116
- ) => void;
117
- }
118
-
119
- /**
120
- * Creates {@link Resources}.
121
- *
122
- * This tracks which consumers are using which resources and maintains reference
123
- * counts to know when it's safe to dispose resources. Resources are created
124
- * on-demand and disposed with a configurable delay to avoid churn.
125
- *
126
- * ### Example Usage
127
- *
128
- * ```ts
129
- * // WebSocket connections
130
- * interface WebSocketConfig {
131
- * readonly url: WebSocketUrl;
132
- * }
133
- *
134
- * type WebSocketUrl = string & Brand<"WebSocketUrl">;
135
- * type UserId = string & Brand<"UserId">;
136
- *
137
- * const webSockets = createResources<
138
- * WebSocket,
139
- * WebSocketUrl,
140
- * WebSocketConfig,
141
- * User,
142
- * UserId
143
- * >({
144
- * createResource: (config) => new WebSocket(config.url),
145
- * getResourceKey: (config) => config.url,
146
- * getConsumerId: (user) => user.id,
147
- * disposalDelay: 1000,
148
- * });
149
- *
150
- * // Add users to WebSocket connections
151
- * webSockets.addConsumer(user1, [
152
- * { url: "ws://server1.com" as WebSocketUrl },
153
- * { url: "ws://server2.com" as WebSocketUrl },
154
- * ]);
155
- * webSockets.addConsumer(user2, [
156
- * { url: "ws://server1.com" as WebSocketUrl },
157
- * ]);
158
- *
159
- * // Remove users - server1 stays alive (user2 still using it)
160
- * webSockets.removeConsumer(user1, [
161
- * { url: "ws://server1.com" as WebSocketUrl },
162
- * { url: "ws://server2.com" as WebSocketUrl },
163
- * ]);
164
- *
165
- * // server2 gets disposed after delay, server1 stays alive
166
- * ```
167
- */
168
- export const createResources = <
169
- TResource extends Disposable,
170
- TResourceKey extends string,
171
- TResourceConfig,
172
- TConsumer,
173
- TConsumerId extends string,
174
- >(
175
- config: ResourcesConfig<
176
- TResource,
177
- TResourceKey,
178
- TResourceConfig,
179
- TConsumer,
180
- TConsumerId
181
- >,
182
- ): Resources<
183
- TResource,
184
- TResourceKey,
185
- TResourceConfig,
186
- TConsumer,
187
- TConsumerId
188
- > => {
189
- let isDisposed = false;
190
-
191
- const resourcesMap = new Map<TResourceKey, TResource>();
192
- const consumerCounts = new Map<TResourceKey, Map<TConsumerId, PositiveInt>>();
193
- const consumers = new Map<TConsumerId, TConsumer>();
194
- const disposalTimeouts = new Map<
195
- TResourceKey,
196
- ReturnType<typeof setTimeout>
197
- >();
198
-
199
- const disposalDelay = config.disposalDelay ?? 100;
200
-
201
- const ensureResource = (resourceConfig: TResourceConfig) => {
202
- const key = config.getResourceKey(resourceConfig);
203
- const timeout = disposalTimeouts.get(key);
204
- if (timeout) {
205
- clearTimeout(timeout);
206
- disposalTimeouts.delete(key);
207
- }
208
-
209
- if (!resourcesMap.has(key)) {
210
- const resource = config.createResource(resourceConfig);
211
- resourcesMap.set(key, resource);
212
- }
213
- };
214
-
215
- const scheduleDisposal = (key: TResourceKey): void => {
216
- const timeout = setTimeout(() => {
217
- const resource = resourcesMap.get(key);
218
- if (resource) {
219
- resource[Symbol.dispose]();
220
- resourcesMap.delete(key);
221
- }
222
- disposalTimeouts.delete(key);
223
- }, disposalDelay);
224
-
225
- disposalTimeouts.set(key, timeout);
226
- };
227
-
228
- const resources: Resources<
229
- TResource,
230
- TResourceKey,
231
- TResourceConfig,
232
- TConsumer,
233
- TConsumerId
234
- > = {
235
- addConsumer: (consumer, resourceConfigs) => {
236
- if (isDisposed) return;
237
-
238
- const consumerId = config.getConsumerId(consumer);
239
-
240
- // Store consumer (last added consumer for this ID)
241
- consumers.set(consumerId, consumer);
242
-
243
- for (const resourceConfig of resourceConfigs) {
244
- ensureResource(resourceConfig);
245
- const resourceKey = config.getResourceKey(resourceConfig);
246
-
247
- let counts = consumerCounts.get(resourceKey);
248
- if (!counts) {
249
- counts = new Map<TConsumerId, PositiveInt>();
250
- consumerCounts.set(resourceKey, counts);
251
- }
252
-
253
- const currentCount = counts.get(consumerId) ?? 0;
254
- const newCount = currentCount + 1;
255
- counts.set(consumerId, PositiveInt.orThrow(newCount));
256
-
257
- // Call onConsumerAdded callback only when consumer is added for the first time (0 -> 1)
258
- if (currentCount === 0 && config.onConsumerAdded) {
259
- const resource = resourcesMap.get(resourceKey);
260
- if (resource) {
261
- config.onConsumerAdded(consumer, resource, resourceKey);
262
- }
263
- }
264
- }
265
- },
266
-
267
- removeConsumer: (consumer, resourceConfigs) => {
268
- if (isDisposed) return ok();
269
-
270
- const consumerId = config.getConsumerId(consumer);
271
-
272
- for (const resourceConfig of resourceConfigs) {
273
- const key = config.getResourceKey(resourceConfig);
274
- const counts = consumerCounts.get(key);
275
- if (!counts) {
276
- return err({ type: "ResourceNotFoundError", resourceKey: key });
277
- }
278
-
279
- const currentCount = counts.get(consumerId);
280
- if (currentCount == null) {
281
- return err({
282
- type: "ConsumerNotFoundError",
283
- consumerId: consumerId,
284
- resourceKey: key,
285
- });
286
- }
287
-
288
- if (currentCount === 1) {
289
- counts.delete(consumerId);
290
-
291
- // Call onConsumerRemoved callback only when consumer is completely removed (1 -> 0)
292
- if (config.onConsumerRemoved) {
293
- const resource = resourcesMap.get(key);
294
- if (resource) {
295
- config.onConsumerRemoved(consumer, resource, key);
296
- }
297
- }
298
-
299
- if (counts.size === 0) {
300
- consumerCounts.delete(key);
301
- scheduleDisposal(key);
302
- }
303
- } else {
304
- counts.set(consumerId, PositiveInt.orThrow(currentCount - 1));
305
- }
306
- }
307
-
308
- if (!resources.hasConsumerAnyResource(consumer)) {
309
- consumers.delete(consumerId);
310
- }
311
-
312
- return ok();
313
- },
314
-
315
- getResource: (key) => {
316
- if (isDisposed) return null;
317
- return resourcesMap.get(key) ?? null;
318
- },
319
-
320
- getConsumersForResource: (key) => {
321
- if (isDisposed) return [];
322
- const counts = consumerCounts.get(key);
323
- return counts ? Array.from(counts.keys()) : [];
324
- },
325
-
326
- hasConsumerAnyResource: (consumer) => {
327
- if (isDisposed) return false;
328
- const consumerId = config.getConsumerId(consumer);
329
- // If slow, can be optimized with reverse index
330
- return Array.from(consumerCounts.values()).some((counts) =>
331
- counts.has(consumerId),
332
- );
333
- },
334
-
335
- getConsumer: (consumerId) => {
336
- if (isDisposed) return null;
337
- const consumer = consumers.get(consumerId);
338
- if (!consumer) return null;
339
-
340
- // Only return consumer if it's currently using any resources
341
- if (!resources.hasConsumerAnyResource(consumer)) {
342
- return null;
343
- }
344
-
345
- return consumer;
346
- },
347
-
348
- [Symbol.dispose]: () => {
349
- if (isDisposed) return;
350
- isDisposed = true;
351
-
352
- for (const timeout of disposalTimeouts.values()) {
353
- clearTimeout(timeout);
354
- }
355
- disposalTimeouts.clear();
356
-
357
- for (const resource of resourcesMap.values()) {
358
- resource[Symbol.dispose]();
359
- }
360
- resourcesMap.clear();
361
- consumerCounts.clear();
362
- consumers.clear();
363
- },
364
- };
365
-
366
- return resources;
367
- };
@@ -1,27 +0,0 @@
1
- /**
2
- * FlushSync is for libraries like React to flush updates synchronously inside
3
- * the provided callback to ensure the DOM is updated immediately.
4
- *
5
- * For example, with React, when we want to focus on an element rendered as a
6
- * result of a mutation, Evolu ensures all DOM changes are flushed synchronously
7
- * if an onComplete callback is used.
8
- *
9
- * https://react.dev/reference/react-dom/flushSync
10
- */
11
- export type FlushSync = (callback: () => void) => void;
12
-
13
- export interface FlushSyncDep {
14
- readonly flushSync: FlushSync;
15
- }
16
-
17
- /**
18
- * Reload the app in a platform-specific way.
19
- *
20
- * - **Web**: Redirects to the specified URL
21
- * - **React Native**: Restarts the app (URL parameter ignored)
22
- */
23
- export type ReloadApp = (url: string) => void;
24
-
25
- export interface ReloadAppDep {
26
- readonly reloadApp: ReloadApp;
27
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * Public local-first API exported from "@evolu/common"
3
- *
4
- * This module provides the supported consumer-facing API for Evolu's
5
- * local-first system: prefer importing from `@evolu/common` (this module) when
6
- * using Evolu in applications. If you require access to lower level internals,
7
- * consider importing from `@evolu/common/local-first`.
8
- *
9
- * @module
10
- */
11
-
12
- export { createEvolu } from "./Evolu.js";
13
- export type { Evolu, EvoluConfig, EvoluDeps, EvoluError } from "./Evolu.js";
14
- export type { UnuseOwner } from "./Evolu.js";
15
- export * from "./LocalAuth.js";
16
- export * from "./Owner.js";
17
- export * as kysely from "./PublicKysely.js";
18
- export type { InferRow, Query, QueryRows, Row } from "./Query.js";
19
- export type { EvoluSchema } from "./Schema.js";
20
- export type {
21
- NetworkError,
22
- PaymentRequiredError,
23
- ServerError,
24
- SyncOwner,
25
- SyncState,
26
- SyncStateInitial,
27
- SyncStateIsNotSynced,
28
- SyncStateIsSynced,
29
- SyncStateIsSyncing,
30
- } from "./Sync.js";
31
- export {
32
- Timestamp,
33
- timestampBytesToTimestamp,
34
- timestampToTimestampBytes,
35
- } from "./Timestamp.js";
36
- export type {
37
- TimestampBytes,
38
- TimestampCounterOverflowError,
39
- TimestampDriftError,
40
- TimestampError,
41
- TimestampTimeOutOfRangeError,
42
- } from "./Timestamp.js";
@@ -1,239 +0,0 @@
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 "./Query.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
- // Kysely expects strict AST.
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
- // Kysely expects strict AST.
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
- // Kysely expects strict AST.
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
-
213
- for (const { selection: s } of node.selections ?? []) {
214
- if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
215
- args.push(
216
- colName(s.column.column.name),
217
- colRef(table, s.column.column.name),
218
- );
219
- } else if (ColumnNode.is(s)) {
220
- args.push(colName(s.column.name), colRef(table, s.column.name));
221
- } else if (AliasNode.is(s) && IdentifierNode.is(s.alias)) {
222
- args.push(colName(s.alias.name), colRef(table, s.alias.name));
223
- } else {
224
- throw new Error(`can't extract column names from the select query node`);
225
- }
226
- }
227
-
228
- return args;
229
- }
230
-
231
- function colName(col: string): Expression<unknown> {
232
- return new ExpressionWrapper(ValueNode.createImmediate(col));
233
- }
234
-
235
- function colRef(table: string, col: string): Expression<unknown> {
236
- return new ExpressionWrapper(
237
- ReferenceNode.create(ColumnNode.create(col), TableNode.create(table)),
238
- );
239
- }