@evolu/common 6.0.1-preview.3 → 6.0.1-preview.30

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 (173) hide show
  1. package/dist/src/Array.d.ts +58 -5
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +53 -5
  4. package/dist/src/Assert.d.ts +6 -16
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +6 -18
  7. package/dist/src/Brand.d.ts +75 -0
  8. package/dist/src/Brand.d.ts.map +1 -0
  9. package/dist/src/Brand.js +1 -0
  10. package/dist/src/Buffer.d.ts +1 -1
  11. package/dist/src/Buffer.d.ts.map +1 -1
  12. package/dist/src/Buffer.js +8 -7
  13. package/dist/src/Cache.d.ts +44 -0
  14. package/dist/src/Cache.d.ts.map +1 -0
  15. package/dist/src/Cache.js +52 -0
  16. package/dist/src/Callbacks.d.ts +45 -12
  17. package/dist/src/Callbacks.d.ts.map +1 -1
  18. package/dist/src/Callbacks.js +14 -7
  19. package/dist/src/Console.d.ts +31 -6
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +72 -9
  22. package/dist/src/Crypto.d.ts +61 -34
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +32 -45
  25. package/dist/src/Evolu/Db.d.ts +158 -65
  26. package/dist/src/Evolu/Db.d.ts.map +1 -1
  27. package/dist/src/Evolu/Db.js +286 -694
  28. package/dist/src/Evolu/Diff.d.ts +3 -3
  29. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  30. package/dist/src/Evolu/Diff.js +7 -5
  31. package/dist/src/Evolu/Evolu.d.ts +208 -133
  32. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  33. package/dist/src/Evolu/Evolu.js +188 -183
  34. package/dist/src/Evolu/Internal.d.ts +0 -2
  35. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  36. package/dist/src/Evolu/Internal.js +0 -2
  37. package/dist/src/Evolu/LocalAuth.d.ts +150 -0
  38. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  39. package/dist/src/Evolu/LocalAuth.js +174 -0
  40. package/dist/src/Evolu/Owner.d.ts +264 -120
  41. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  42. package/dist/src/Evolu/Owner.js +130 -104
  43. package/dist/src/Evolu/Platform.d.ts +9 -7
  44. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  45. package/dist/src/Evolu/Protocol.d.ts +277 -232
  46. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  47. package/dist/src/Evolu/Protocol.js +603 -378
  48. package/dist/src/Evolu/Public.d.ts +6 -8
  49. package/dist/src/Evolu/Public.d.ts.map +1 -1
  50. package/dist/src/Evolu/Public.js +2 -3
  51. package/dist/src/Evolu/PublicKysely.js +3 -3
  52. package/dist/src/Evolu/Query.d.ts +2 -1
  53. package/dist/src/Evolu/Query.d.ts.map +1 -1
  54. package/dist/src/Evolu/Relay.d.ts +92 -7
  55. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  56. package/dist/src/Evolu/Relay.js +243 -76
  57. package/dist/src/Evolu/Schema.d.ts +129 -73
  58. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  59. package/dist/src/Evolu/Schema.js +169 -89
  60. package/dist/src/Evolu/Storage.d.ts +212 -26
  61. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  62. package/dist/src/Evolu/Storage.js +137 -79
  63. package/dist/src/Evolu/Sync.d.ts +68 -13
  64. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  65. package/dist/src/Evolu/Sync.js +422 -20
  66. package/dist/src/Evolu/Timestamp.d.ts +85 -27
  67. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  68. package/dist/src/Evolu/Timestamp.js +77 -18
  69. package/dist/src/Identicon.d.ts +35 -0
  70. package/dist/src/Identicon.d.ts.map +1 -0
  71. package/dist/src/Identicon.js +143 -0
  72. package/dist/src/Instances.d.ts +34 -0
  73. package/dist/src/Instances.d.ts.map +1 -0
  74. package/dist/src/Instances.js +44 -0
  75. package/dist/src/ManyToManyMap.d.ts +71 -10
  76. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  77. package/dist/src/ManyToManyMap.js +41 -6
  78. package/dist/src/Number.d.ts +4 -3
  79. package/dist/src/Number.d.ts.map +1 -1
  80. package/dist/src/Number.js +5 -4
  81. package/dist/src/Platform.d.ts +20 -0
  82. package/dist/src/Platform.d.ts.map +1 -0
  83. package/dist/src/Platform.js +22 -0
  84. package/dist/src/Random.d.ts +3 -2
  85. package/dist/src/Random.d.ts.map +1 -1
  86. package/dist/src/Resources.d.ts +118 -0
  87. package/dist/src/Resources.d.ts.map +1 -0
  88. package/dist/src/Resources.js +197 -0
  89. package/dist/src/Result.d.ts +184 -52
  90. package/dist/src/Result.d.ts.map +1 -1
  91. package/dist/src/Result.js +30 -241
  92. package/dist/src/Skiplist.js +2 -1
  93. package/dist/src/Sqlite.d.ts +63 -5
  94. package/dist/src/Sqlite.d.ts.map +1 -1
  95. package/dist/src/Sqlite.js +110 -9
  96. package/dist/src/Task.d.ts +586 -0
  97. package/dist/src/Task.d.ts.map +1 -0
  98. package/dist/src/Task.js +469 -0
  99. package/dist/src/Time.d.ts +66 -1
  100. package/dist/src/Time.d.ts.map +1 -1
  101. package/dist/src/Time.js +99 -5
  102. package/dist/src/Type.d.ts +621 -340
  103. package/dist/src/Type.d.ts.map +1 -1
  104. package/dist/src/Type.js +665 -464
  105. package/dist/src/Types.d.ts +1 -75
  106. package/dist/src/Types.d.ts.map +1 -1
  107. package/dist/src/WebSocket.d.ts +5 -2
  108. package/dist/src/WebSocket.d.ts.map +1 -1
  109. package/dist/src/WebSocket.js +12 -18
  110. package/dist/src/Worker.d.ts +39 -11
  111. package/dist/src/Worker.d.ts.map +1 -1
  112. package/dist/src/Worker.js +22 -4
  113. package/dist/src/index.d.ts +7 -2
  114. package/dist/src/index.d.ts.map +1 -1
  115. package/dist/src/index.js +7 -2
  116. package/package.json +14 -13
  117. package/src/Array.ts +76 -11
  118. package/src/Assert.ts +6 -24
  119. package/src/Brand.ts +75 -0
  120. package/src/Buffer.ts +7 -7
  121. package/src/Cache.ts +85 -0
  122. package/src/Callbacks.ts +62 -22
  123. package/src/Console.ts +91 -11
  124. package/src/Crypto.ts +97 -82
  125. package/src/Evolu/Db.ts +514 -1020
  126. package/src/Evolu/Diff.ts +7 -5
  127. package/src/Evolu/Evolu.ts +464 -355
  128. package/src/Evolu/Internal.ts +0 -2
  129. package/src/Evolu/LocalAuth.ts +463 -0
  130. package/src/Evolu/Owner.ts +369 -228
  131. package/src/Evolu/Platform.ts +9 -9
  132. package/src/Evolu/Protocol.ts +859 -676
  133. package/src/Evolu/Public.ts +7 -14
  134. package/src/Evolu/PublicKysely.ts +3 -3
  135. package/src/Evolu/Query.ts +2 -1
  136. package/src/Evolu/Relay.ts +420 -92
  137. package/src/Evolu/Schema.ts +391 -191
  138. package/src/Evolu/Storage.ts +451 -118
  139. package/src/Evolu/Sync.ts +720 -37
  140. package/src/Evolu/Timestamp.ts +88 -35
  141. package/src/Identicon.ts +197 -0
  142. package/src/Instances.ts +90 -0
  143. package/src/ManyToManyMap.ts +124 -24
  144. package/src/Number.ts +6 -10
  145. package/src/Platform.ts +26 -0
  146. package/src/Random.ts +3 -2
  147. package/src/Resources.ts +367 -0
  148. package/src/Result.ts +191 -54
  149. package/src/Skiplist.ts +1 -1
  150. package/src/Sqlite.ts +122 -17
  151. package/src/Task.ts +901 -0
  152. package/src/Time.ts +180 -5
  153. package/src/Type.ts +1083 -727
  154. package/src/Types.ts +1 -77
  155. package/src/WebSocket.ts +27 -25
  156. package/src/Worker.ts +72 -23
  157. package/src/index.ts +7 -2
  158. package/dist/src/Evolu/Config.d.ts +0 -69
  159. package/dist/src/Evolu/Config.d.ts.map +0 -1
  160. package/dist/src/Evolu/Config.js +0 -9
  161. package/dist/src/Evolu/Kysely.d.ts +0 -6
  162. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  163. package/dist/src/Evolu/Kysely.js +0 -21
  164. package/dist/src/NanoId.d.ts +0 -27
  165. package/dist/src/NanoId.d.ts.map +0 -1
  166. package/dist/src/NanoId.js +0 -6
  167. package/dist/src/Promise.d.ts +0 -180
  168. package/dist/src/Promise.d.ts.map +0 -1
  169. package/dist/src/Promise.js +0 -176
  170. package/src/Evolu/Config.ts +0 -83
  171. package/src/Evolu/Kysely.ts +0 -38
  172. package/src/NanoId.ts +0 -39
  173. package/src/Promise.ts +0 -295
@@ -1,38 +1,30 @@
1
+ import { pack } from "msgpackr";
1
2
  import { isNonEmptyArray, isNonEmptyReadonlyArray } from "../Array.js";
2
- import { assertNonEmptyArray } from "../Assert.js";
3
+ import { assert, assertNonEmptyArray } from "../Assert.js";
3
4
  import { createCallbacks } from "../Callbacks.js";
5
+ import { eqArrayNumber } from "../Eq.js";
4
6
  import { exhaustiveCheck } from "../Function.js";
7
+ import { createInstances } from "../Instances.js";
5
8
  import { err, ok } from "../Result.js";
6
9
  import { isSqlMutation } from "../Sqlite.js";
7
10
  import { createStore } from "../Store.js";
8
11
  import { createId, } from "../Type.js";
9
- import { defaultConfig } from "./Config.js";
12
+ import { defaultDbConfig } from "./Db.js";
10
13
  import { applyPatches } from "./Diff.js";
11
- import { kysely } from "./Kysely.js";
12
14
  import { createSubscribedQueries, emptyRows, serializeQuery, } from "./Query.js";
13
- import { assertValidEvoluSchema, insertable, updateable, upsertable, validEvoluSchemaToDbSchema, } from "./Schema.js";
15
+ import { evoluSchemaToDbSchema, insertable, kysely, updateable, upsertable, } from "./Schema.js";
16
+ import { DbChange } from "./Storage.js";
14
17
  import { initialSyncState } from "./Sync.js";
15
- // For hot reloading and Evolu multitenancy.
16
- const evoluInstances = new Map();
18
+ const evoluInstances = createInstances();
19
+ /**
20
+ * Unique identifier for the current browser tab or app instance, lazily
21
+ * initialized on first use to distinguish between multiple tabs.
22
+ */
17
23
  let tabId = null;
18
24
  /**
19
- * Creates an {@link Evolu} instance configured with the specified
20
- * {@link EvoluSchema} and optional configuration.
21
- *
22
- * This function returns a configured Evolu instance, providing a typed
23
- * interface for querying, mutating, and syncing your application's data. The
24
- * returned instance includes:
25
- *
26
- * - Subscription methods for receiving updates on queries, the owner, errors, and
27
- * sync state.
28
- * - Methods for creating, updating, or deleting rows in a type-safe manner.
29
- * - Methods for querying data using Evolu's typed SQL queries, leveraging Kysely
30
- * under the hood.
31
- * - Built-in support for local-first and offline-first data with automatic sync
32
- * and merging.
33
- * - Automatic schema evolution that updates the underlying database with new
34
- * columns or tables.
35
- * - Managing owner data with resetAppOwner and restoreAppOwner.
25
+ * Creates an {@link Evolu} instance for a platform configured with the specified
26
+ * {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
27
+ * interface for querying, mutating, and syncing your application's data.
36
28
  *
37
29
  * ### Example
38
30
  *
@@ -61,62 +53,66 @@ let tabId = null;
61
53
  *
62
54
  * const evolu = createEvolu(evoluReactDeps)(Schema);
63
55
  * ```
56
+ *
57
+ * ### Instance Caching
58
+ *
59
+ * `createEvolu` caches instances using {@link Instances} by {@link EvoluConfig}
60
+ * name to enable hot reloading and prevent database corruption from multiple
61
+ * connections. For testing, use unique instance names to ensure proper
62
+ * isolation.
64
63
  */
65
- export const createEvolu = (deps) => (
66
- // TODO: Validate missing Id, unsupported types, used default types via TS types
67
- // with type errors messages as we had it in the old Evolu.
68
- schema, partialConfig = {}) => {
69
- const config = { ...defaultConfig, ...partialConfig };
70
- let evolu = evoluInstances.get(config.name);
71
- if (evolu == null) {
72
- evolu = createEvoluInstance(deps)(schema, config);
73
- evoluInstances.set(config.name, evolu);
74
- }
75
- else {
76
- // Hot reloading. Note that indexes are intentionally omitted.
77
- evolu.ensureSchema(schema);
78
- }
79
- return evolu;
80
- };
81
- const createEvoluInstance = (deps) => (schema, evoluConfig) => {
82
- deps.console.enabled = evoluConfig.enableLogging ?? false;
83
- deps.console.log("[evolu]", "createEvoluInstance");
84
- const { initialData, indexes, ...config } = evoluConfig;
64
+ export const createEvolu = (deps) => (schema, config) => evoluInstances.ensure(config?.name ?? defaultDbConfig.name, () => createEvoluInstance(deps)(schema, config), (evolu) => {
65
+ // Hot reloading. Note that indexes are intentionally omitted.
66
+ evolu.ensureSchema(schema);
67
+ });
68
+ const createEvoluInstance = (deps) => (schema, config) => {
69
+ deps.console.enabled = config?.enableLogging ?? false;
70
+ const { indexes, reloadUrl = "/", ...partialDbConfig } = config ?? {};
71
+ const dbConfig = { ...defaultDbConfig, ...partialDbConfig };
72
+ deps.console.log("[evolu]", "createEvoluInstance", {
73
+ name: dbConfig.name,
74
+ });
85
75
  const errorStore = createStore(null);
86
76
  const rowsStore = createStore(new Map());
87
- const ownerStore = createStore(null);
88
- const syncStore = createStore(initialSyncState);
77
+ const { promise: appOwner, resolve: resolveAppOwner } = Promise.withResolvers();
78
+ if (config?.externalAppOwner) {
79
+ resolveAppOwner(config.externalAppOwner);
80
+ }
81
+ // TODO: Update it for the owner-api
82
+ const _syncStore = createStore(initialSyncState);
89
83
  const subscribedQueries = createSubscribedQueries(rowsStore);
90
84
  const loadingPromises = createLoadingPromises(subscribedQueries);
91
- const callbacks = createCallbacks(deps);
92
- const appState = deps.createAppState(config);
93
- const dbWorker = deps.createDbWorker(config.name);
85
+ const onCompleteCallbacks = createCallbacks(deps);
86
+ const exportCallbacks = createCallbacks(deps);
87
+ const dbWorker = deps.createDbWorker(dbConfig.name);
94
88
  const getTabId = () => {
95
89
  tabId ??= createId(deps);
96
90
  return tabId;
97
91
  };
92
+ // Worker responses are delivered to all tabs. Each case must handle this
93
+ // properly (e.g., AppOwner promise resolves only once, tabId filtering).
98
94
  dbWorker.onMessage((message) => {
99
95
  switch (message.type) {
100
- case "onInit": {
101
- ownerStore.set(message.owner);
102
- break;
103
- }
104
96
  case "onError": {
105
97
  errorStore.set(message.error);
106
98
  break;
107
99
  }
108
- case "onChange": {
100
+ case "onGetAppOwner": {
101
+ resolveAppOwner(message.appOwner);
102
+ break;
103
+ }
104
+ case "onQueryPatches": {
109
105
  if (message.tabId !== getTabId())
110
106
  return;
111
107
  const state = rowsStore.get();
112
108
  const nextState = new Map([
113
109
  ...state,
114
- ...message.patches.map(({ query, patches }) => [
110
+ ...message.queryPatches.map(({ query, patches }) => [
115
111
  query,
116
112
  applyPatches(patches, state.get(query) ?? emptyRows),
117
113
  ]),
118
114
  ]);
119
- for (const { query } of message.patches) {
115
+ for (const { query } of message.queryPatches) {
120
116
  loadingPromises.resolve(query, nextState.get(query) ?? emptyRows);
121
117
  }
122
118
  if (deps.flushSync && message.onCompleteIds.length > 0) {
@@ -128,15 +124,19 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
128
124
  rowsStore.set(nextState);
129
125
  }
130
126
  for (const id of message.onCompleteIds) {
131
- callbacks.execute(id);
127
+ onCompleteCallbacks.execute(id);
132
128
  }
133
129
  break;
134
130
  }
135
- case "onReceive": {
131
+ case "refreshQueries": {
136
132
  if (message.tabId && message.tabId === getTabId())
137
133
  return;
138
- loadingPromises.releaseUnsubscribed();
139
- const queries = subscribedQueries.get();
134
+ const loadingPromisesQueries = loadingPromises.getQueries();
135
+ loadingPromises.releaseUnsubscribedOnMutation();
136
+ const queries = [
137
+ // Dedupe
138
+ ...new Set([...loadingPromisesQueries, ...subscribedQueries.get()]),
139
+ ];
140
140
  if (isNonEmptyReadonlyArray(queries)) {
141
141
  dbWorker.postMessage({ type: "query", tabId: getTabId(), queries });
142
142
  }
@@ -144,22 +144,22 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
144
144
  }
145
145
  case "onReset": {
146
146
  if (message.reload) {
147
- appState.reset();
147
+ deps.reloadApp(reloadUrl);
148
148
  }
149
149
  else {
150
- callbacks.execute(message.onCompleteId);
150
+ onCompleteCallbacks.execute(message.onCompleteId);
151
151
  }
152
152
  break;
153
153
  }
154
154
  case "onExport": {
155
- callbacks.execute(message.onCompleteId, message.file);
155
+ exportCallbacks.execute(message.onCompleteId, message.file);
156
156
  break;
157
157
  }
158
158
  default:
159
159
  exhaustiveCheck(message);
160
160
  }
161
161
  });
162
- const dbSchema = validEvoluSchemaToDbSchema(assertValidEvoluSchema(schema), indexes);
162
+ const dbSchema = evoluSchemaToDbSchema(schema, indexes);
163
163
  const mutationTypesCache = new Map();
164
164
  // Lazy create mutation Types like this: `insertable(Schema.todo)`
165
165
  const getMutationType = (table, kind) => {
@@ -175,103 +175,76 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
175
175
  }
176
176
  return type;
177
177
  };
178
- const initialDataDbChanges = [];
179
- /**
180
- * Note that the initial data function is called even if it is unnecessary
181
- * (initial data are already in the DB) because we don't want to wait for
182
- * SQLite's response. Initial data should be small (because they are inlined
183
- * in the code), so it's ok.
184
- */
185
- if (initialData)
186
- initialData({
187
- insert: (table, props) => {
188
- const Type = getMutationType(table, "insert");
189
- const id = createId(deps);
190
- const result = Type.fromUnknown(props);
191
- if (result.ok) {
192
- initialDataDbChanges.push({
193
- id,
194
- table,
195
- values: result.value,
196
- });
197
- return ok({ id });
198
- }
199
- return result;
200
- },
201
- });
202
- dbWorker.postMessage({
203
- type: "init",
204
- config,
205
- dbSchema,
206
- initialData: initialDataDbChanges,
207
- });
178
+ dbWorker.postMessage({ type: "init", config: dbConfig, dbSchema });
179
+ // We can't use `init` to get AppOwner because `init` runs only once per n tabs.
180
+ dbWorker.postMessage({ type: "getAppOwner" });
208
181
  const loadQueryMicrotaskQueue = [];
209
182
  const mutateMicrotaskQueue = [];
183
+ const useOwnerMicrotaskQueue = [];
210
184
  const createMutation = (kind) => (table, props, options) => {
211
- const Type = getMutationType(table, kind);
212
- const result = Type.fromUnknown(props);
185
+ const result = getMutationType(table, kind).fromUnknown(props);
213
186
  const id = kind === "insert"
214
187
  ? createId(deps)
215
188
  : props.id;
216
189
  if (options?.onlyValidate !== true) {
217
190
  if (!result.ok) {
218
- // One error must invalidate the whole queue.
219
- // We insert `undefined` to detect such a situation.
220
- mutateMicrotaskQueue.push([undefined, undefined]);
191
+ // Mark the transaction as invalid by pushing null
192
+ mutateMicrotaskQueue.push([null, undefined]);
221
193
  }
222
194
  else {
223
- // Remove `id` from values.
224
- const { id: _id, ...values } = result.value;
225
- // EvoluSchema Types ensure valid types.
226
- const change = { table, id, values };
227
- mutateMicrotaskQueue.push([change, options?.onComplete]);
228
- }
229
- if (mutateMicrotaskQueue.length === 1)
230
- queueMicrotask(() => {
231
- const changes = [];
232
- const onCompletes = [];
233
- for (const [change, onComplete] of mutateMicrotaskQueue) {
234
- if (change)
235
- changes.push(change);
236
- if (onComplete)
237
- onCompletes.push(onComplete);
238
- }
239
- const mutateMicrotaskQueueLength = mutateMicrotaskQueue.length;
240
- mutateMicrotaskQueue.length = 0;
241
- // Don't mutate anything if there was a validation error.
242
- // All mutations within a queue are considered to be a transaction.
243
- if (changes.length !== mutateMicrotaskQueueLength) {
244
- return;
195
+ const values = { ...result.value };
196
+ delete values.id;
197
+ if (kind === "insert" || kind === "upsert") {
198
+ // Only set createdAt if not provided by user
199
+ if (!("createdAt" in values)) {
200
+ values.createdAt = new Date(deps.time.now()).toISOString();
245
201
  }
246
- const onCompleteIds = onCompletes.map((onComplete) => callbacks.register(onComplete));
247
- loadingPromises.releaseUnsubscribed();
248
- if (isNonEmptyArray(changes))
249
- dbWorker.postMessage({
250
- type: "mutate",
251
- tabId: getTabId(),
252
- changes,
253
- onCompleteIds,
254
- subscribedQueries: subscribedQueries.get(),
255
- });
256
- });
202
+ }
203
+ const dbChange = { table, id, values };
204
+ assert(DbChange.is(dbChange), `Failed to create DbChange for table "${dbChange.table}"`);
205
+ const mutationChange = { ...dbChange, ownerId: options?.ownerId };
206
+ mutateMicrotaskQueue.push([mutationChange, options?.onComplete]);
207
+ }
208
+ if (mutateMicrotaskQueue.length === 1) {
209
+ queueMicrotask(processMutationQueue);
210
+ }
257
211
  }
258
212
  if (result.ok)
259
213
  return ok({ id });
260
214
  return err(result.error);
261
215
  };
216
+ const processMutationQueue = () => {
217
+ const changes = [];
218
+ const onCompletes = [];
219
+ for (const [change, onComplete] of mutateMicrotaskQueue) {
220
+ if (change !== null)
221
+ changes.push(change);
222
+ if (onComplete)
223
+ onCompletes.push(onComplete);
224
+ }
225
+ const queueLength = mutateMicrotaskQueue.length;
226
+ mutateMicrotaskQueue.length = 0;
227
+ // Don't process any mutations if there was a validation error.
228
+ // All mutations within a queue run as a single transaction.
229
+ if (changes.length !== queueLength) {
230
+ return;
231
+ }
232
+ const onCompleteIds = onCompletes.map(onCompleteCallbacks.register);
233
+ loadingPromises.releaseUnsubscribedOnMutation();
234
+ if (!isNonEmptyArray(changes))
235
+ return;
236
+ dbWorker.postMessage({
237
+ type: "mutate",
238
+ tabId: getTabId(),
239
+ changes,
240
+ onCompleteIds,
241
+ subscribedQueries: subscribedQueries.get(),
242
+ });
243
+ };
262
244
  const evolu = {
263
245
  subscribeError: errorStore.subscribe,
264
246
  getError: errorStore.get,
265
- createQuery: (queryCallback, options) => {
266
- const compiledQuery = queryCallback(kysely).compile();
267
- if (isSqlMutation(compiledQuery.sql))
268
- throw new Error("SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu Mutation API.");
269
- return serializeQuery({
270
- sql: compiledQuery.sql,
271
- parameters: compiledQuery.parameters,
272
- ...(options && { options }),
273
- });
274
- },
247
+ createQuery,
275
248
  loadQuery: (query) => {
276
249
  const { promise, isNew } = loadingPromises.get(query);
277
250
  if (isNew) {
@@ -282,6 +255,7 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
282
255
  const queries = [...new Set(loadQueryMicrotaskQueue)];
283
256
  loadQueryMicrotaskQueue.length = 0;
284
257
  assertNonEmptyArray(queries);
258
+ deps.console.log("[evolu]", "loadQuery", { queries });
285
259
  dbWorker.postMessage({
286
260
  type: "query",
287
261
  tabId: getTabId(),
@@ -309,20 +283,16 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
309
283
  };
310
284
  },
311
285
  getQueryRows: (query) => (rowsStore.get().get(query) ?? emptyRows),
312
- subscribeAppOwner: ownerStore.subscribe,
313
- getAppOwner: ownerStore.get,
314
- subscribeSyncState: syncStore.subscribe,
315
- getSyncState: syncStore.get,
286
+ appOwner,
287
+ // TODO: Update it for the owner-api
288
+ // subscribeSyncState: syncStore.subscribe,
289
+ // getSyncState: syncStore.get,
316
290
  insert: createMutation("insert"),
317
291
  update: createMutation("update"),
318
292
  upsert: createMutation("upsert"),
319
293
  resetAppOwner: (options) => {
320
- // Eslint bug, Promise<void> is correct by docs.
321
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
322
294
  const { promise, resolve } = Promise.withResolvers();
323
- const onCompleteId = callbacks.register(() => {
324
- resolve();
325
- });
295
+ const onCompleteId = onCompleteCallbacks.register(resolve);
326
296
  dbWorker.postMessage({
327
297
  type: "reset",
328
298
  onCompleteId,
@@ -331,12 +301,8 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
331
301
  return promise;
332
302
  },
333
303
  restoreAppOwner: (mnemonic, options) => {
334
- // Eslint bug, Promise<void> is correct by docs.
335
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
336
304
  const { promise, resolve } = Promise.withResolvers();
337
- const onCompleteId = callbacks.register(() => {
338
- resolve();
339
- });
305
+ const onCompleteId = onCompleteCallbacks.register(resolve);
340
306
  dbWorker.postMessage({
341
307
  type: "reset",
342
308
  onCompleteId,
@@ -346,32 +312,81 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
346
312
  return promise;
347
313
  },
348
314
  reloadApp: () => {
349
- appState.reset();
315
+ deps.reloadApp(reloadUrl);
350
316
  },
351
317
  ensureSchema: (schema) => {
352
318
  mutationTypesCache.clear();
353
- const validSchema = assertValidEvoluSchema(schema);
354
- dbWorker.postMessage({
355
- type: "ensureDbSchema",
356
- dbSchema: validEvoluSchemaToDbSchema(validSchema),
357
- });
319
+ const dbSchema = evoluSchemaToDbSchema(schema);
320
+ dbWorker.postMessage({ type: "ensureDbSchema", dbSchema });
358
321
  },
359
322
  exportDatabase: () => {
360
323
  const { promise, resolve } = Promise.withResolvers();
361
- const onCompleteId = callbacks.register((arg) => {
362
- if (arg instanceof Uint8Array)
363
- resolve(arg);
364
- });
324
+ const onCompleteId = exportCallbacks.register(resolve);
365
325
  dbWorker.postMessage({ type: "export", onCompleteId });
366
326
  return promise;
367
327
  },
328
+ useOwner: (owner) => {
329
+ const scheduleOwnerQueueProcessing = () => {
330
+ if (useOwnerMicrotaskQueue.length !== 1)
331
+ return;
332
+ queueMicrotask(() => {
333
+ const queue = [...useOwnerMicrotaskQueue];
334
+ useOwnerMicrotaskQueue.length = 0;
335
+ const result = [];
336
+ const skipIndices = new Set();
337
+ for (let i = 0; i < queue.length; i++) {
338
+ if (skipIndices.has(i))
339
+ continue;
340
+ const [currentOwner, currentUse, currentOwnerSerialized] = queue[i];
341
+ // Look for opposite action with same owner
342
+ for (let j = i + 1; j < queue.length; j++) {
343
+ if (skipIndices.has(j))
344
+ continue;
345
+ const [, otherUse, otherOwnerSerialized] = queue[j];
346
+ if (currentUse !== otherUse &&
347
+ eqArrayNumber(currentOwnerSerialized, otherOwnerSerialized)) {
348
+ // Found cancel-out pair, skip both
349
+ skipIndices.add(i).add(j);
350
+ break;
351
+ }
352
+ }
353
+ if (!skipIndices.has(i)) {
354
+ result.push([currentOwner, currentUse, currentOwnerSerialized]);
355
+ }
356
+ }
357
+ for (const [owner, use] of result) {
358
+ dbWorker.postMessage({ type: "useOwner", owner, use });
359
+ }
360
+ });
361
+ };
362
+ useOwnerMicrotaskQueue.push([owner, true, pack(owner)]);
363
+ scheduleOwnerQueueProcessing();
364
+ const unuse = () => {
365
+ useOwnerMicrotaskQueue.push([owner, false, pack(owner)]);
366
+ scheduleOwnerQueueProcessing();
367
+ };
368
+ return unuse;
369
+ },
370
+ /** Disposal is not implemented yet. */
371
+ [Symbol.dispose]: () => {
372
+ throw new Error("Evolu instance disposal is not yet implemented");
373
+ },
368
374
  };
369
375
  return evolu;
370
376
  };
371
- export const createNamespaceName = (config) => (name) => `evolu:${config.name}:${name}`;
377
+ export const createQuery = (queryCallback, options) => {
378
+ const compiledQuery = queryCallback(kysely).compile();
379
+ if (isSqlMutation(compiledQuery.sql))
380
+ throw new Error("SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu Mutation API.");
381
+ return serializeQuery({
382
+ sql: compiledQuery.sql,
383
+ parameters: compiledQuery.parameters,
384
+ ...(options && { options }),
385
+ });
386
+ };
372
387
  const createLoadingPromises = (subscribedQueries) => {
373
388
  const loadingPromiseMap = new Map();
374
- const loadingPromises = {
389
+ return {
375
390
  get: (query) => {
376
391
  let loadingPromise = loadingPromiseMap.get(query);
377
392
  const isNew = !loadingPromise;
@@ -393,15 +408,12 @@ const createLoadingPromises = (subscribedQueries) => {
393
408
  loadingPromise.resolve(rows);
394
409
  }
395
410
  else {
396
- // A promise can't be fulfilled 2x, so we need a new one.
397
411
  loadingPromise.promise = Promise.resolve(rows);
398
412
  }
399
- /**
400
- * "For example, a data framework can set the status and value fields on a
401
- * promise preemptively, before passing to React, so that React can unwrap
402
- * it without waiting a microtask."
403
- * https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
404
- */
413
+ // Set status and value fields for React's `use` Hook to unwrap synchronously.
414
+ // While undocumented in React docs, React still uses these properties internally,
415
+ // and Evolu's own promise caching logic depends on checking `promise.status`.
416
+ // https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
405
417
  void Object.assign(loadingPromise.promise, {
406
418
  status: "fulfilled",
407
419
  value: rows,
@@ -410,14 +422,7 @@ const createLoadingPromises = (subscribedQueries) => {
410
422
  loadingPromiseMap.delete(query);
411
423
  }
412
424
  },
413
- /**
414
- * We can't delete loading promises in `resolveLoadingPromises` because they
415
- * must be cached, so repeated calls to `loadQuery` will always return the
416
- * same promise until the data changes, and we also can't cache them forever
417
- * because only subscribed queries are automatically updated (reactivity is
418
- * expensive) hence this function must be called manually on any mutation.
419
- */
420
- releaseUnsubscribed: () => {
425
+ releaseUnsubscribedOnMutation: () => {
421
426
  [...loadingPromiseMap.entries()]
422
427
  .filter(([query]) => !subscribedQueries.has(query))
423
428
  .forEach(([query, loadingPromise]) => {
@@ -429,6 +434,6 @@ const createLoadingPromises = (subscribedQueries) => {
429
434
  }
430
435
  });
431
436
  },
437
+ getQueries: () => Array.from(loadingPromiseMap.keys()),
432
438
  };
433
- return loadingPromises;
434
439
  };
@@ -9,11 +9,9 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- export * from "./Config.js";
13
12
  export * from "./Db.js";
14
13
  export * from "./Diff.js";
15
14
  export * from "./Evolu.js";
16
- export * from "./Kysely.js";
17
15
  export * from "./Owner.js";
18
16
  export * from "./Platform.js";
19
17
  export * from "./Protocol.js";
@@ -1 +1 @@
1
- {"version":3,"file":"Internal.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"Internal.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
@@ -9,11 +9,9 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- export * from "./Config.js";
13
12
  export * from "./Db.js";
14
13
  export * from "./Diff.js";
15
14
  export * from "./Evolu.js";
16
- export * from "./Kysely.js";
17
15
  export * from "./Owner.js";
18
16
  export * from "./Platform.js";
19
17
  export * from "./Protocol.js";