@evolu/common 6.0.1-preview.2 → 6.0.1-preview.21

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 (154) hide show
  1. package/dist/src/Assert.d.ts +6 -3
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +7 -4
  4. package/dist/src/Brand.d.ts +75 -0
  5. package/dist/src/Brand.d.ts.map +1 -0
  6. package/dist/src/Brand.js +1 -0
  7. package/dist/src/Buffer.d.ts +1 -1
  8. package/dist/src/Buffer.d.ts.map +1 -1
  9. package/dist/src/Buffer.js +1 -1
  10. package/dist/src/CallbackRegistry.d.ts +53 -0
  11. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  12. package/dist/src/CallbackRegistry.js +25 -0
  13. package/dist/src/Console.d.ts +31 -6
  14. package/dist/src/Console.d.ts.map +1 -1
  15. package/dist/src/Console.js +72 -9
  16. package/dist/src/Crypto.d.ts +61 -34
  17. package/dist/src/Crypto.d.ts.map +1 -1
  18. package/dist/src/Crypto.js +29 -42
  19. package/dist/src/Evolu/Db.d.ts +159 -66
  20. package/dist/src/Evolu/Db.d.ts.map +1 -1
  21. package/dist/src/Evolu/Db.js +300 -705
  22. package/dist/src/Evolu/Diff.d.ts +3 -3
  23. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  24. package/dist/src/Evolu/Diff.js +7 -5
  25. package/dist/src/Evolu/Evolu.d.ts +200 -115
  26. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  27. package/dist/src/Evolu/Evolu.js +287 -157
  28. package/dist/src/Evolu/Internal.d.ts +0 -2
  29. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  30. package/dist/src/Evolu/Internal.js +0 -2
  31. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  32. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  33. package/dist/src/Evolu/LocalAuth.js +171 -0
  34. package/dist/src/Evolu/Owner.d.ts +209 -124
  35. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  36. package/dist/src/Evolu/Owner.js +118 -104
  37. package/dist/src/Evolu/Platform.d.ts +9 -7
  38. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  39. package/dist/src/Evolu/Protocol.d.ts +256 -211
  40. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  41. package/dist/src/Evolu/Protocol.js +558 -359
  42. package/dist/src/Evolu/Public.d.ts +6 -8
  43. package/dist/src/Evolu/Public.d.ts.map +1 -1
  44. package/dist/src/Evolu/Public.js +2 -3
  45. package/dist/src/Evolu/PublicKysely.js +3 -3
  46. package/dist/src/Evolu/Query.d.ts +2 -1
  47. package/dist/src/Evolu/Query.d.ts.map +1 -1
  48. package/dist/src/Evolu/Relay.d.ts +72 -4
  49. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  50. package/dist/src/Evolu/Relay.js +124 -13
  51. package/dist/src/Evolu/Schema.d.ts +129 -73
  52. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  53. package/dist/src/Evolu/Schema.js +169 -89
  54. package/dist/src/Evolu/Storage.d.ts +159 -14
  55. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  56. package/dist/src/Evolu/Storage.js +42 -32
  57. package/dist/src/Evolu/Sync.d.ts +68 -13
  58. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  59. package/dist/src/Evolu/Sync.js +414 -20
  60. package/dist/src/Evolu/Timestamp.d.ts +35 -27
  61. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  62. package/dist/src/Evolu/Timestamp.js +27 -18
  63. package/dist/src/ManyToManyMap.d.ts +74 -10
  64. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  65. package/dist/src/ManyToManyMap.js +41 -6
  66. package/dist/src/Number.d.ts +2 -1
  67. package/dist/src/Number.d.ts.map +1 -1
  68. package/dist/src/Random.d.ts +3 -2
  69. package/dist/src/Random.d.ts.map +1 -1
  70. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  71. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  72. package/dist/src/RefCountedResourceManager.js +197 -0
  73. package/dist/src/Result.d.ts +176 -51
  74. package/dist/src/Result.d.ts.map +1 -1
  75. package/dist/src/Result.js +30 -241
  76. package/dist/src/Sqlite.d.ts +21 -4
  77. package/dist/src/Sqlite.d.ts.map +1 -1
  78. package/dist/src/Sqlite.js +50 -8
  79. package/dist/src/Task.d.ts +511 -0
  80. package/dist/src/Task.d.ts.map +1 -0
  81. package/dist/src/Task.js +410 -0
  82. package/dist/src/Time.d.ts +59 -0
  83. package/dist/src/Time.d.ts.map +1 -1
  84. package/dist/src/Time.js +87 -4
  85. package/dist/src/Type.d.ts +474 -327
  86. package/dist/src/Type.d.ts.map +1 -1
  87. package/dist/src/Type.js +502 -436
  88. package/dist/src/Types.d.ts +0 -74
  89. package/dist/src/Types.d.ts.map +1 -1
  90. package/dist/src/WebSocket.d.ts +5 -2
  91. package/dist/src/WebSocket.d.ts.map +1 -1
  92. package/dist/src/WebSocket.js +12 -13
  93. package/dist/src/Worker.d.ts +39 -11
  94. package/dist/src/Worker.d.ts.map +1 -1
  95. package/dist/src/Worker.js +22 -4
  96. package/dist/src/index.d.ts +3 -3
  97. package/dist/src/index.d.ts.map +1 -1
  98. package/dist/src/index.js +3 -3
  99. package/package.json +14 -13
  100. package/src/Assert.ts +8 -7
  101. package/src/Brand.ts +75 -0
  102. package/src/Buffer.ts +1 -1
  103. package/src/CallbackRegistry.ts +84 -0
  104. package/src/Console.ts +91 -11
  105. package/src/Crypto.ts +94 -79
  106. package/src/Evolu/Db.ts +528 -1027
  107. package/src/Evolu/Diff.ts +7 -5
  108. package/src/Evolu/Evolu.ts +682 -329
  109. package/src/Evolu/Internal.ts +0 -2
  110. package/src/Evolu/LocalAuth.ts +422 -0
  111. package/src/Evolu/Owner.ts +282 -227
  112. package/src/Evolu/Platform.ts +9 -9
  113. package/src/Evolu/Protocol.ts +788 -640
  114. package/src/Evolu/Public.ts +7 -14
  115. package/src/Evolu/PublicKysely.ts +3 -3
  116. package/src/Evolu/Query.ts +2 -1
  117. package/src/Evolu/Relay.ts +238 -18
  118. package/src/Evolu/Schema.ts +391 -191
  119. package/src/Evolu/Storage.ts +275 -55
  120. package/src/Evolu/Sync.ts +708 -37
  121. package/src/Evolu/Timestamp.ts +38 -35
  122. package/src/ManyToManyMap.ts +127 -24
  123. package/src/Number.ts +2 -6
  124. package/src/Random.ts +3 -2
  125. package/src/RefCountedResourceManager.ts +368 -0
  126. package/src/Result.ts +183 -53
  127. package/src/Sqlite.ts +59 -23
  128. package/src/Task.ts +779 -0
  129. package/src/Time.ts +168 -4
  130. package/src/Type.ts +708 -660
  131. package/src/Types.ts +0 -76
  132. package/src/WebSocket.ts +23 -17
  133. package/src/Worker.ts +72 -23
  134. package/src/index.ts +3 -3
  135. package/dist/src/Callbacks.d.ts +0 -20
  136. package/dist/src/Callbacks.d.ts.map +0 -1
  137. package/dist/src/Callbacks.js +0 -18
  138. package/dist/src/Evolu/Config.d.ts +0 -69
  139. package/dist/src/Evolu/Config.d.ts.map +0 -1
  140. package/dist/src/Evolu/Config.js +0 -9
  141. package/dist/src/Evolu/Kysely.d.ts +0 -6
  142. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  143. package/dist/src/Evolu/Kysely.js +0 -21
  144. package/dist/src/NanoId.d.ts +0 -27
  145. package/dist/src/NanoId.d.ts.map +0 -1
  146. package/dist/src/NanoId.js +0 -6
  147. package/dist/src/Promise.d.ts +0 -180
  148. package/dist/src/Promise.d.ts.map +0 -1
  149. package/dist/src/Promise.js +0 -176
  150. package/src/Callbacks.ts +0 -43
  151. package/src/Evolu/Config.ts +0 -83
  152. package/src/Evolu/Kysely.ts +0 -38
  153. package/src/NanoId.ts +0 -39
  154. package/src/Promise.ts +0 -295
@@ -1,16 +1,18 @@
1
+ import { pack } from "msgpackr";
1
2
  import { isNonEmptyArray, isNonEmptyReadonlyArray } from "../Array.js";
2
- import { assertNonEmptyArray } from "../Assert.js";
3
- import { createCallbacks } from "../Callbacks.js";
3
+ import { assert, assertNonEmptyArray } from "../Assert.js";
4
+ import { createCallbackRegistry } from "../CallbackRegistry.js";
5
+ import { eqArrayNumber } from "../Eq.js";
4
6
  import { exhaustiveCheck } from "../Function.js";
5
7
  import { err, ok } from "../Result.js";
6
8
  import { isSqlMutation } from "../Sqlite.js";
7
9
  import { createStore } from "../Store.js";
8
10
  import { createId, } from "../Type.js";
9
- import { defaultConfig } from "./Config.js";
11
+ import { defaultDbConfig } from "./Db.js";
10
12
  import { applyPatches } from "./Diff.js";
11
- import { kysely } from "./Kysely.js";
12
13
  import { createSubscribedQueries, emptyRows, serializeQuery, } from "./Query.js";
13
- import { assertValidEvoluSchema, insertable, updateable, upsertable, validEvoluSchemaToDbSchema, } from "./Schema.js";
14
+ import { evoluSchemaToDbSchema, insertable, kysely, updateable, upsertable, } from "./Schema.js";
15
+ import { DbChange } from "./Storage.js";
14
16
  import { initialSyncState } from "./Sync.js";
15
17
  // For hot reloading and Evolu multitenancy.
16
18
  const evoluInstances = new Map();
@@ -32,7 +34,8 @@ let tabId = null;
32
34
  * and merging.
33
35
  * - Automatic schema evolution that updates the underlying database with new
34
36
  * columns or tables.
35
- * - Managing owner data with resetAppOwner and restoreAppOwner.
37
+ * - Managing owner data with {@link Evolu#resetAppOwner} and
38
+ * {@link Evolu#restoreAppOwner}.
36
39
  *
37
40
  * ### Example
38
41
  *
@@ -62,15 +65,12 @@ let tabId = null;
62
65
  * const evolu = createEvolu(evoluReactDeps)(Schema);
63
66
  * ```
64
67
  */
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);
68
+ export const createEvolu = (deps) => (schema, config) => {
69
+ const name = config?.name ?? defaultDbConfig.name;
70
+ let evolu = evoluInstances.get(name);
71
71
  if (evolu == null) {
72
72
  evolu = createEvoluInstance(deps)(schema, config);
73
- evoluInstances.set(config.name, evolu);
73
+ evoluInstances.set(name, evolu);
74
74
  }
75
75
  else {
76
76
  // Hot reloading. Note that indexes are intentionally omitted.
@@ -78,45 +78,87 @@ schema, partialConfig = {}) => {
78
78
  }
79
79
  return evolu;
80
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;
81
+ const createEvoluInstance = (deps) => (schema, config) => {
82
+ deps.console.enabled = config?.enableLogging ?? false;
83
+ const { indexes, reloadUrl = "/", ...partialDbConfig } = config ?? {};
84
+ const dbConfig = { ...defaultDbConfig, ...partialDbConfig };
85
+ deps.console.log("[evolu]", "createEvoluInstance", {
86
+ name: dbConfig.name,
87
+ });
85
88
  const errorStore = createStore(null);
86
89
  const rowsStore = createStore(new Map());
87
- const ownerStore = createStore(null);
88
- const syncStore = createStore(initialSyncState);
90
+ const { promise: appOwner, resolve: resolveAppOwner } = Promise.withResolvers();
91
+ if (config?.externalAppOwner) {
92
+ resolveAppOwner(config.externalAppOwner);
93
+ }
94
+ // TODO: Update it for the owner-api
95
+ const _syncStore = createStore(initialSyncState);
89
96
  const subscribedQueries = createSubscribedQueries(rowsStore);
90
97
  const loadingPromises = createLoadingPromises(subscribedQueries);
91
- const callbacks = createCallbacks(deps);
92
- const appState = deps.createAppState(config);
93
- const dbWorker = deps.createDbWorker(config.name);
98
+ const onCompleteRegistry = createCallbackRegistry(deps);
99
+ const exportRegistry = createCallbackRegistry(deps);
100
+ const dbWorker = deps.createDbWorker(dbConfig.name);
94
101
  const getTabId = () => {
95
102
  tabId ??= createId(deps);
96
103
  return tabId;
97
104
  };
105
+ // const createLocalOnly = (
106
+ // localMutations: Array<MutationChange>,
107
+ // defaultOwnerId: OwnerId | undefined,
108
+ // ): LocalOnly<EvoluSchema> => ({
109
+ // insert: (table, values) => {
110
+ // const id = createId(deps);
111
+ // localMutations.push({
112
+ // table,
113
+ // id,
114
+ // values,
115
+ // ownerId: defaultOwnerId,
116
+ // });
117
+ // return id;
118
+ // },
119
+ // update: (table, values) => {
120
+ // const { id, ...rest } = values;
121
+ // localMutations.push({
122
+ // table,
123
+ // id: id as Id,
124
+ // values: rest,
125
+ // ownerId: defaultOwnerId,
126
+ // });
127
+ // },
128
+ // upsert: (table, values) => {
129
+ // const { id, ...rest } = values as Record<string, unknown> & { id: Id };
130
+ // localMutations.push({
131
+ // table,
132
+ // id: id,
133
+ // values: rest as MutationChange["values"],
134
+ // ownerId: defaultOwnerId,
135
+ // });
136
+ // },
137
+ // });
138
+ // Worker responses are delivered to all tabs. Each case must handle this
139
+ // properly (e.g., AppOwner promise resolves only once, tabId filtering).
98
140
  dbWorker.onMessage((message) => {
99
141
  switch (message.type) {
100
- case "onInit": {
101
- ownerStore.set(message.owner);
102
- break;
103
- }
104
142
  case "onError": {
105
143
  errorStore.set(message.error);
106
144
  break;
107
145
  }
108
- case "onChange": {
146
+ case "onGetAppOwner": {
147
+ resolveAppOwner(message.appOwner);
148
+ break;
149
+ }
150
+ case "onQueryPatches": {
109
151
  if (message.tabId !== getTabId())
110
152
  return;
111
153
  const state = rowsStore.get();
112
154
  const nextState = new Map([
113
155
  ...state,
114
- ...message.patches.map(({ query, patches }) => [
156
+ ...message.queryPatches.map(({ query, patches }) => [
115
157
  query,
116
158
  applyPatches(patches, state.get(query) ?? emptyRows),
117
159
  ]),
118
160
  ]);
119
- for (const { query } of message.patches) {
161
+ for (const { query } of message.queryPatches) {
120
162
  loadingPromises.resolve(query, nextState.get(query) ?? emptyRows);
121
163
  }
122
164
  if (deps.flushSync && message.onCompleteIds.length > 0) {
@@ -128,15 +170,19 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
128
170
  rowsStore.set(nextState);
129
171
  }
130
172
  for (const id of message.onCompleteIds) {
131
- callbacks.execute(id);
173
+ onCompleteRegistry.execute(id);
132
174
  }
133
175
  break;
134
176
  }
135
- case "onReceive": {
177
+ case "refreshQueries": {
136
178
  if (message.tabId && message.tabId === getTabId())
137
179
  return;
138
- loadingPromises.releaseUnsubscribed();
139
- const queries = subscribedQueries.get();
180
+ const loadingPromisesQueries = loadingPromises.getQueries();
181
+ loadingPromises.releaseUnsubscribedOnMutation();
182
+ const queries = [
183
+ // Dedupe
184
+ ...new Set([...loadingPromisesQueries, ...subscribedQueries.get()]),
185
+ ];
140
186
  if (isNonEmptyReadonlyArray(queries)) {
141
187
  dbWorker.postMessage({ type: "query", tabId: getTabId(), queries });
142
188
  }
@@ -144,22 +190,82 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
144
190
  }
145
191
  case "onReset": {
146
192
  if (message.reload) {
147
- appState.reset();
193
+ deps.reloadApp(reloadUrl);
148
194
  }
149
195
  else {
150
- callbacks.execute(message.onCompleteId);
196
+ onCompleteRegistry.execute(message.onCompleteId);
151
197
  }
152
198
  break;
153
199
  }
200
+ // case "processNewMessages": {
201
+ // void requestIdleTask(
202
+ // toTask(async () => {
203
+ // const approved: Array<Timestamp> = [];
204
+ // const invalidChanges: Array<DbChange> = [];
205
+ // const rejectedChanges: Array<DbChange> = [];
206
+ // const localMutations: Array<MutationChange> = [];
207
+ // for (const crdtMessage of message.messages) {
208
+ // let isApproved = true;
209
+ // let isValid = true;
210
+ // const table = crdtMessage.change.table;
211
+ // if (table in schema) {
212
+ // const { createdAt, ...values } = crdtMessage.change.values;
213
+ // isValid =
214
+ // (createdAt ? DateIso.is(createdAt) : true) &&
215
+ // getMutationType(table, "update").is({
216
+ // id: crdtMessage.change.id,
217
+ // ...values,
218
+ // });
219
+ // } else {
220
+ // isValid = false;
221
+ // }
222
+ // if (!isValid) {
223
+ // isApproved = false;
224
+ // invalidChanges.push(crdtMessage.change);
225
+ // } else if (onMessage) {
226
+ // // At this point, we've validated that the message conforms to the
227
+ // // schema, so the typed callback can safely process it.
228
+ // isApproved = await onMessage(crdtMessage.change, {
229
+ // ownerId: message.ownerId,
230
+ // localOnly: createLocalOnly(localMutations, message.ownerId),
231
+ // });
232
+ // if (!isApproved) {
233
+ // rejectedChanges.push(crdtMessage.change);
234
+ // }
235
+ // }
236
+ // if (isApproved) {
237
+ // approved.push(crdtMessage.timestamp);
238
+ // }
239
+ // }
240
+ // // Report OnMessageError if there were any invalid or rejected changes
241
+ // if (invalidChanges.length > 0 || rejectedChanges.length > 0) {
242
+ // const onMessageError: OnMessageError = {
243
+ // type: "OnMessageError",
244
+ // invalidChanges,
245
+ // rejectedChanges,
246
+ // };
247
+ // errorStore.set(onMessageError);
248
+ // }
249
+ // dbWorker.postMessage({
250
+ // type: "onProcessNewMessages",
251
+ // onCompleteId: message.onCompleteId,
252
+ // approved,
253
+ // localMutations,
254
+ // });
255
+ // return ok();
256
+ // }),
257
+ // )();
258
+ // break;
259
+ // }
154
260
  case "onExport": {
155
- callbacks.execute(message.onCompleteId, message.file);
261
+ exportRegistry.execute(message.onCompleteId, message.file);
156
262
  break;
157
263
  }
158
264
  default:
159
265
  exhaustiveCheck(message);
160
266
  }
161
267
  });
162
- const dbSchema = validEvoluSchemaToDbSchema(assertValidEvoluSchema(schema), indexes);
268
+ const dbSchema = evoluSchemaToDbSchema(schema, indexes);
163
269
  const mutationTypesCache = new Map();
164
270
  // Lazy create mutation Types like this: `insertable(Schema.todo)`
165
271
  const getMutationType = (table, kind) => {
@@ -175,103 +281,99 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
175
281
  }
176
282
  return type;
177
283
  };
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
- });
284
+ dbWorker.postMessage({ type: "init", config: dbConfig, dbSchema });
285
+ // We can't use `init` to get AppOwner because `init` runs only once per n tabs.
286
+ dbWorker.postMessage({ type: "getAppOwner" });
208
287
  const loadQueryMicrotaskQueue = [];
209
288
  const mutateMicrotaskQueue = [];
289
+ const useOwnerMicrotaskQueue = [];
210
290
  const createMutation = (kind) => (table, props, options) => {
211
- const Type = getMutationType(table, kind);
212
- const result = Type.fromUnknown(props);
291
+ const result = getMutationType(table, kind).fromUnknown(props);
213
292
  const id = kind === "insert"
214
293
  ? createId(deps)
215
294
  : props.id;
216
295
  if (options?.onlyValidate !== true) {
217
296
  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]);
297
+ // Mark the transaction as invalid by pushing null
298
+ mutateMicrotaskQueue.push([null, undefined]);
221
299
  }
222
300
  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;
301
+ const values = { ...result.value };
302
+ delete values.id;
303
+ if (kind === "insert" || kind === "upsert") {
304
+ // Only set createdAt if not provided by user
305
+ if (!("createdAt" in values)) {
306
+ values.createdAt = new Date(deps.time.now()).toISOString();
245
307
  }
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
- });
308
+ }
309
+ const dbChange = { table, id, values };
310
+ assert(DbChange.is(dbChange), `Failed to create DbChange for table "${dbChange.table}"`);
311
+ const mutationChange = { ...dbChange, ownerId: options?.ownerId };
312
+ mutateMicrotaskQueue.push([mutationChange, options?.onComplete]);
313
+ }
314
+ if (mutateMicrotaskQueue.length === 1) {
315
+ queueMicrotask(processMutationQueue);
316
+ }
257
317
  }
258
318
  if (result.ok)
259
319
  return ok({ id });
260
320
  return err(result.error);
261
321
  };
322
+ const processMutationQueue = () => {
323
+ const changes = [];
324
+ const onCompleteCallbacks = [];
325
+ for (const [change, onComplete] of mutateMicrotaskQueue) {
326
+ if (change !== null)
327
+ changes.push(change);
328
+ if (onComplete)
329
+ onCompleteCallbacks.push(onComplete);
330
+ }
331
+ const queueLength = mutateMicrotaskQueue.length;
332
+ mutateMicrotaskQueue.length = 0;
333
+ // Don't process any mutations if there was a validation error.
334
+ // All mutations within a queue run as a single transaction.
335
+ if (changes.length !== queueLength) {
336
+ return;
337
+ }
338
+ const onCompleteIds = onCompleteCallbacks.map(onCompleteRegistry.register);
339
+ loadingPromises.releaseUnsubscribedOnMutation();
340
+ if (!isNonEmptyArray(changes))
341
+ return;
342
+ // if (onMessage) {
343
+ // const rejectedChanges: Array<DbChange> = [];
344
+ // const localMutations: Array<MutationChange> = [];
345
+ // for (const change of changes) {
346
+ // const localOnly = createLocalOnly(localMutations, change.ownerId);
347
+ // const isApproved = await onMessage(change, {
348
+ // ownerId: change.ownerId,
349
+ // localOnly,
350
+ // });
351
+ // if (!isApproved) {
352
+ // rejectedChanges.push(change);
353
+ // }
354
+ // }
355
+ // if (rejectedChanges.length > 0) {
356
+ // errorStore.set({
357
+ // type: "OnMessageError",
358
+ // invalidChanges: [],
359
+ // rejectedChanges,
360
+ // });
361
+ // return;
362
+ // }
363
+ // changes.push(...localMutations);
364
+ // }
365
+ dbWorker.postMessage({
366
+ type: "mutate",
367
+ tabId: getTabId(),
368
+ changes,
369
+ onCompleteIds,
370
+ subscribedQueries: subscribedQueries.get(),
371
+ });
372
+ };
262
373
  const evolu = {
263
374
  subscribeError: errorStore.subscribe,
264
375
  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
- },
376
+ createQuery,
275
377
  loadQuery: (query) => {
276
378
  const { promise, isNew } = loadingPromises.get(query);
277
379
  if (isNew) {
@@ -282,6 +384,7 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
282
384
  const queries = [...new Set(loadQueryMicrotaskQueue)];
283
385
  loadQueryMicrotaskQueue.length = 0;
284
386
  assertNonEmptyArray(queries);
387
+ deps.console.log("[evolu]", "loadQuery", { queries });
285
388
  dbWorker.postMessage({
286
389
  type: "query",
287
390
  tabId: getTabId(),
@@ -309,20 +412,16 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
309
412
  };
310
413
  },
311
414
  getQueryRows: (query) => (rowsStore.get().get(query) ?? emptyRows),
312
- subscribeAppOwner: ownerStore.subscribe,
313
- getAppOwner: ownerStore.get,
314
- subscribeSyncState: syncStore.subscribe,
315
- getSyncState: syncStore.get,
415
+ appOwner,
416
+ // TODO: Update it for the owner-api
417
+ // subscribeSyncState: syncStore.subscribe,
418
+ // getSyncState: syncStore.get,
316
419
  insert: createMutation("insert"),
317
420
  update: createMutation("update"),
318
421
  upsert: createMutation("upsert"),
319
422
  resetAppOwner: (options) => {
320
- // Eslint bug, Promise<void> is correct by docs.
321
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
322
423
  const { promise, resolve } = Promise.withResolvers();
323
- const onCompleteId = callbacks.register(() => {
324
- resolve();
325
- });
424
+ const onCompleteId = onCompleteRegistry.register(resolve);
326
425
  dbWorker.postMessage({
327
426
  type: "reset",
328
427
  onCompleteId,
@@ -331,12 +430,8 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
331
430
  return promise;
332
431
  },
333
432
  restoreAppOwner: (mnemonic, options) => {
334
- // Eslint bug, Promise<void> is correct by docs.
335
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
336
433
  const { promise, resolve } = Promise.withResolvers();
337
- const onCompleteId = callbacks.register(() => {
338
- resolve();
339
- });
434
+ const onCompleteId = onCompleteRegistry.register(resolve);
340
435
  dbWorker.postMessage({
341
436
  type: "reset",
342
437
  onCompleteId,
@@ -346,32 +441,77 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
346
441
  return promise;
347
442
  },
348
443
  reloadApp: () => {
349
- appState.reset();
444
+ deps.reloadApp(reloadUrl);
350
445
  },
351
446
  ensureSchema: (schema) => {
352
447
  mutationTypesCache.clear();
353
- const validSchema = assertValidEvoluSchema(schema);
354
- dbWorker.postMessage({
355
- type: "ensureDbSchema",
356
- dbSchema: validEvoluSchemaToDbSchema(validSchema),
357
- });
448
+ const dbSchema = evoluSchemaToDbSchema(schema);
449
+ dbWorker.postMessage({ type: "ensureDbSchema", dbSchema });
358
450
  },
359
451
  exportDatabase: () => {
360
452
  const { promise, resolve } = Promise.withResolvers();
361
- const onCompleteId = callbacks.register((arg) => {
362
- if (arg instanceof Uint8Array)
363
- resolve(arg);
364
- });
453
+ const onCompleteId = exportRegistry.register(resolve);
365
454
  dbWorker.postMessage({ type: "export", onCompleteId });
366
455
  return promise;
367
456
  },
457
+ useOwner: (owner) => {
458
+ const scheduleOwnerQueueProcessing = () => {
459
+ if (useOwnerMicrotaskQueue.length !== 1)
460
+ return;
461
+ queueMicrotask(() => {
462
+ const queue = [...useOwnerMicrotaskQueue];
463
+ useOwnerMicrotaskQueue.length = 0;
464
+ const result = [];
465
+ const skipIndices = new Set();
466
+ for (let i = 0; i < queue.length; i++) {
467
+ if (skipIndices.has(i))
468
+ continue;
469
+ const [currentOwner, currentUse, currentOwnerSerialized] = queue[i];
470
+ // Look for opposite action with same owner
471
+ for (let j = i + 1; j < queue.length; j++) {
472
+ if (skipIndices.has(j))
473
+ continue;
474
+ const [, otherUse, otherOwnerSerialized] = queue[j];
475
+ if (currentUse !== otherUse &&
476
+ eqArrayNumber(currentOwnerSerialized, otherOwnerSerialized)) {
477
+ // Found cancel-out pair, skip both
478
+ skipIndices.add(i).add(j);
479
+ break;
480
+ }
481
+ }
482
+ if (!skipIndices.has(i)) {
483
+ result.push([currentOwner, currentUse, currentOwnerSerialized]);
484
+ }
485
+ }
486
+ for (const [owner, use] of result) {
487
+ dbWorker.postMessage({ type: "useOwner", owner, use });
488
+ }
489
+ });
490
+ };
491
+ useOwnerMicrotaskQueue.push([owner, true, pack(owner)]);
492
+ scheduleOwnerQueueProcessing();
493
+ const unuse = () => {
494
+ useOwnerMicrotaskQueue.push([owner, false, pack(owner)]);
495
+ scheduleOwnerQueueProcessing();
496
+ };
497
+ return unuse;
498
+ },
368
499
  };
369
500
  return evolu;
370
501
  };
371
- export const createNamespaceName = (config) => (name) => `evolu:${config.name}:${name}`;
502
+ export const createQuery = (queryCallback, options) => {
503
+ const compiledQuery = queryCallback(kysely).compile();
504
+ if (isSqlMutation(compiledQuery.sql))
505
+ 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.");
506
+ return serializeQuery({
507
+ sql: compiledQuery.sql,
508
+ parameters: compiledQuery.parameters,
509
+ ...(options && { options }),
510
+ });
511
+ };
372
512
  const createLoadingPromises = (subscribedQueries) => {
373
513
  const loadingPromiseMap = new Map();
374
- const loadingPromises = {
514
+ return {
375
515
  get: (query) => {
376
516
  let loadingPromise = loadingPromiseMap.get(query);
377
517
  const isNew = !loadingPromise;
@@ -393,15 +533,12 @@ const createLoadingPromises = (subscribedQueries) => {
393
533
  loadingPromise.resolve(rows);
394
534
  }
395
535
  else {
396
- // A promise can't be fulfilled 2x, so we need a new one.
397
536
  loadingPromise.promise = Promise.resolve(rows);
398
537
  }
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
- */
538
+ // Set status and value fields for React's `use` Hook to unwrap synchronously.
539
+ // While undocumented in React docs, React still uses these properties internally,
540
+ // and Evolu's own promise caching logic depends on checking `promise.status`.
541
+ // https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
405
542
  void Object.assign(loadingPromise.promise, {
406
543
  status: "fulfilled",
407
544
  value: rows,
@@ -410,14 +547,7 @@ const createLoadingPromises = (subscribedQueries) => {
410
547
  loadingPromiseMap.delete(query);
411
548
  }
412
549
  },
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: () => {
550
+ releaseUnsubscribedOnMutation: () => {
421
551
  [...loadingPromiseMap.entries()]
422
552
  .filter(([query]) => !subscribedQueries.has(query))
423
553
  .forEach(([query, loadingPromise]) => {
@@ -429,6 +559,6 @@ const createLoadingPromises = (subscribedQueries) => {
429
559
  }
430
560
  });
431
561
  },
562
+ getQueries: () => Array.from(loadingPromiseMap.keys()),
432
563
  };
433
- return loadingPromises;
434
564
  };
@@ -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";