@evolu/common 6.0.1-preview.19 → 6.0.1-preview.20

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 (140) hide show
  1. package/dist/src/Assert.d.ts.map +1 -1
  2. package/dist/src/Assert.js +1 -1
  3. package/dist/src/Buffer.d.ts +1 -1
  4. package/dist/src/Buffer.d.ts.map +1 -1
  5. package/dist/src/Buffer.js +1 -1
  6. package/dist/src/CallbackRegistry.d.ts +53 -0
  7. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  8. package/dist/src/CallbackRegistry.js +25 -0
  9. package/dist/src/Console.d.ts +31 -6
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +72 -9
  12. package/dist/src/Crypto.d.ts +48 -37
  13. package/dist/src/Crypto.d.ts.map +1 -1
  14. package/dist/src/Crypto.js +27 -50
  15. package/dist/src/Evolu/Db.d.ts +138 -66
  16. package/dist/src/Evolu/Db.d.ts.map +1 -1
  17. package/dist/src/Evolu/Db.js +248 -645
  18. package/dist/src/Evolu/Diff.d.ts +3 -3
  19. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  20. package/dist/src/Evolu/Diff.js +7 -5
  21. package/dist/src/Evolu/Evolu.d.ts +79 -116
  22. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  23. package/dist/src/Evolu/Evolu.js +275 -132
  24. package/dist/src/Evolu/Internal.d.ts +0 -2
  25. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  26. package/dist/src/Evolu/Internal.js +0 -2
  27. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  28. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  29. package/dist/src/Evolu/LocalAuth.js +171 -0
  30. package/dist/src/Evolu/Owner.d.ts +129 -83
  31. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  32. package/dist/src/Evolu/Owner.js +80 -89
  33. package/dist/src/Evolu/Platform.d.ts +9 -7
  34. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  35. package/dist/src/Evolu/Protocol.d.ts +114 -191
  36. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  37. package/dist/src/Evolu/Protocol.js +409 -416
  38. package/dist/src/Evolu/Public.d.ts +6 -8
  39. package/dist/src/Evolu/Public.d.ts.map +1 -1
  40. package/dist/src/Evolu/Public.js +2 -3
  41. package/dist/src/Evolu/PublicKysely.js +3 -3
  42. package/dist/src/Evolu/Relay.d.ts +1 -2
  43. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  44. package/dist/src/Evolu/Relay.js +11 -9
  45. package/dist/src/Evolu/Schema.d.ts +88 -27
  46. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  47. package/dist/src/Evolu/Schema.js +141 -24
  48. package/dist/src/Evolu/Storage.d.ts +158 -14
  49. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  50. package/dist/src/Evolu/Storage.js +32 -32
  51. package/dist/src/Evolu/Sync.d.ts +77 -13
  52. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  53. package/dist/src/Evolu/Sync.js +453 -20
  54. package/dist/src/Evolu/Timestamp.d.ts +29 -27
  55. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  56. package/dist/src/Evolu/Timestamp.js +20 -18
  57. package/dist/src/ManyToManyMap.d.ts +74 -10
  58. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  59. package/dist/src/ManyToManyMap.js +41 -6
  60. package/dist/src/Random.d.ts +3 -2
  61. package/dist/src/Random.d.ts.map +1 -1
  62. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  63. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  64. package/dist/src/RefCountedResourceManager.js +197 -0
  65. package/dist/src/Result.d.ts +144 -22
  66. package/dist/src/Result.d.ts.map +1 -1
  67. package/dist/src/Result.js +5 -2
  68. package/dist/src/Sqlite.d.ts +20 -4
  69. package/dist/src/Sqlite.d.ts.map +1 -1
  70. package/dist/src/Sqlite.js +50 -8
  71. package/dist/src/Task.d.ts +511 -0
  72. package/dist/src/Task.d.ts.map +1 -0
  73. package/dist/src/Task.js +410 -0
  74. package/dist/src/Time.d.ts +59 -0
  75. package/dist/src/Time.d.ts.map +1 -1
  76. package/dist/src/Time.js +87 -4
  77. package/dist/src/Type.d.ts +431 -341
  78. package/dist/src/Type.d.ts.map +1 -1
  79. package/dist/src/Type.js +458 -466
  80. package/dist/src/WebSocket.d.ts +5 -2
  81. package/dist/src/WebSocket.d.ts.map +1 -1
  82. package/dist/src/WebSocket.js +12 -13
  83. package/dist/src/Worker.d.ts +39 -11
  84. package/dist/src/Worker.d.ts.map +1 -1
  85. package/dist/src/Worker.js +22 -4
  86. package/dist/src/index.d.ts +2 -3
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +2 -3
  89. package/package.json +7 -7
  90. package/src/Assert.ts +2 -4
  91. package/src/Buffer.ts +1 -1
  92. package/src/CallbackRegistry.ts +84 -0
  93. package/src/Console.ts +91 -11
  94. package/src/Crypto.ts +78 -91
  95. package/src/Evolu/Db.ts +455 -947
  96. package/src/Evolu/Diff.ts +7 -5
  97. package/src/Evolu/Evolu.ts +545 -307
  98. package/src/Evolu/Internal.ts +0 -2
  99. package/src/Evolu/LocalAuth.ts +422 -0
  100. package/src/Evolu/Owner.ts +191 -131
  101. package/src/Evolu/Platform.ts +9 -9
  102. package/src/Evolu/Protocol.ts +536 -653
  103. package/src/Evolu/Public.ts +7 -9
  104. package/src/Evolu/PublicKysely.ts +3 -3
  105. package/src/Evolu/Relay.ts +17 -12
  106. package/src/Evolu/Schema.ts +271 -66
  107. package/src/Evolu/Storage.ts +263 -55
  108. package/src/Evolu/Sync.ts +758 -37
  109. package/src/Evolu/Timestamp.ts +30 -35
  110. package/src/ManyToManyMap.ts +127 -24
  111. package/src/Random.ts +3 -2
  112. package/src/RefCountedResourceManager.ts +368 -0
  113. package/src/Result.ts +149 -23
  114. package/src/Sqlite.ts +59 -24
  115. package/src/Task.ts +779 -0
  116. package/src/Time.ts +168 -4
  117. package/src/Type.ts +657 -695
  118. package/src/WebSocket.ts +23 -17
  119. package/src/Worker.ts +72 -23
  120. package/src/index.ts +2 -3
  121. package/dist/src/Callbacks.d.ts +0 -20
  122. package/dist/src/Callbacks.d.ts.map +0 -1
  123. package/dist/src/Callbacks.js +0 -18
  124. package/dist/src/Evolu/Config.d.ts +0 -82
  125. package/dist/src/Evolu/Config.d.ts.map +0 -1
  126. package/dist/src/Evolu/Config.js +0 -9
  127. package/dist/src/Evolu/Kysely.d.ts +0 -6
  128. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  129. package/dist/src/Evolu/Kysely.js +0 -21
  130. package/dist/src/NanoId.d.ts +0 -27
  131. package/dist/src/NanoId.d.ts.map +0 -1
  132. package/dist/src/NanoId.js +0 -6
  133. package/dist/src/Promise.d.ts +0 -180
  134. package/dist/src/Promise.d.ts.map +0 -1
  135. package/dist/src/Promise.js +0 -176
  136. package/src/Callbacks.ts +0 -43
  137. package/src/Evolu/Config.ts +0 -97
  138. package/src/Evolu/Kysely.ts +0 -38
  139. package/src/NanoId.ts +0 -39
  140. package/src/Promise.ts +0 -295
@@ -1,17 +1,18 @@
1
+ import { pack } from "msgpackr";
1
2
  import { isNonEmptyArray, isNonEmptyReadonlyArray } from "../Array.js";
2
3
  import { assert, assertNonEmptyArray } from "../Assert.js";
3
- import { createCallbacks } from "../Callbacks.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
- import { DbChange, } from "./Protocol.js";
13
13
  import { createSubscribedQueries, emptyRows, serializeQuery, } from "./Query.js";
14
- import { evoluSchemaToDbSchema, insertable, updateable, upsertable, } from "./Schema.js";
14
+ import { evoluSchemaToDbSchema, insertable, kysely, updateable, upsertable, } from "./Schema.js";
15
+ import { DbChange } from "./Storage.js";
15
16
  import { initialSyncState } from "./Sync.js";
16
17
  // For hot reloading and Evolu multitenancy.
17
18
  const evoluInstances = new Map();
@@ -33,7 +34,8 @@ let tabId = null;
33
34
  * and merging.
34
35
  * - Automatic schema evolution that updates the underlying database with new
35
36
  * columns or tables.
36
- * - Managing owner data with resetAppOwner and restoreAppOwner.
37
+ * - Managing owner data with {@link Evolu#resetAppOwner} and
38
+ * {@link Evolu#restoreAppOwner}.
37
39
  *
38
40
  * ### Example
39
41
  *
@@ -63,12 +65,12 @@ let tabId = null;
63
65
  * const evolu = createEvolu(evoluReactDeps)(Schema);
64
66
  * ```
65
67
  */
66
- export const createEvolu = (deps) => (schema, partialConfig = {}) => {
67
- const config = { ...defaultConfig, ...partialConfig };
68
- 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);
69
71
  if (evolu == null) {
70
72
  evolu = createEvoluInstance(deps)(schema, config);
71
- evoluInstances.set(config.name, evolu);
73
+ evoluInstances.set(name, evolu);
72
74
  }
73
75
  else {
74
76
  // Hot reloading. Note that indexes are intentionally omitted.
@@ -76,49 +78,87 @@ export const createEvolu = (deps) => (schema, partialConfig = {}) => {
76
78
  }
77
79
  return evolu;
78
80
  };
79
- const createEvoluInstance = (deps) => (schema, evoluConfig) => {
80
- deps.console.enabled = evoluConfig.enableLogging ?? false;
81
- deps.console.log("[evolu]", "createEvoluInstance");
82
- const { onInit, 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
+ });
83
88
  const errorStore = createStore(null);
84
89
  const rowsStore = createStore(new Map());
85
- const appOwnerStore = createStore(null);
86
- 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);
87
96
  const subscribedQueries = createSubscribedQueries(rowsStore);
88
97
  const loadingPromises = createLoadingPromises(subscribedQueries);
89
- const callbacks = createCallbacks(deps);
90
- const appState = deps.createAppState(config);
91
- const dbWorker = deps.createDbWorker(config.name);
98
+ const onCompleteRegistry = createCallbackRegistry(deps);
99
+ const exportRegistry = createCallbackRegistry(deps);
100
+ const dbWorker = deps.createDbWorker(dbConfig.name);
92
101
  const getTabId = () => {
93
102
  tabId ??= createId(deps);
94
103
  return tabId;
95
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).
96
140
  dbWorker.onMessage((message) => {
97
141
  switch (message.type) {
98
- case "onInit": {
99
- appOwnerStore.set(message.appOwner);
100
- onInit?.({
101
- appOwner: message.appOwner,
102
- isFirst: message.isFirst,
103
- });
104
- break;
105
- }
106
142
  case "onError": {
107
143
  errorStore.set(message.error);
108
144
  break;
109
145
  }
110
- case "onChange": {
146
+ case "onGetAppOwner": {
147
+ resolveAppOwner(message.appOwner);
148
+ break;
149
+ }
150
+ case "onQueryPatches": {
111
151
  if (message.tabId !== getTabId())
112
152
  return;
113
153
  const state = rowsStore.get();
114
154
  const nextState = new Map([
115
155
  ...state,
116
- ...message.patches.map(({ query, patches }) => [
156
+ ...message.queryPatches.map(({ query, patches }) => [
117
157
  query,
118
158
  applyPatches(patches, state.get(query) ?? emptyRows),
119
159
  ]),
120
160
  ]);
121
- for (const { query } of message.patches) {
161
+ for (const { query } of message.queryPatches) {
122
162
  loadingPromises.resolve(query, nextState.get(query) ?? emptyRows);
123
163
  }
124
164
  if (deps.flushSync && message.onCompleteIds.length > 0) {
@@ -130,15 +170,19 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
130
170
  rowsStore.set(nextState);
131
171
  }
132
172
  for (const id of message.onCompleteIds) {
133
- callbacks.execute(id);
173
+ onCompleteRegistry.execute(id);
134
174
  }
135
175
  break;
136
176
  }
137
- case "onReceive": {
177
+ case "refreshQueries": {
138
178
  if (message.tabId && message.tabId === getTabId())
139
179
  return;
140
- loadingPromises.releaseUnsubscribed();
141
- const queries = subscribedQueries.get();
180
+ const loadingPromisesQueries = loadingPromises.getQueries();
181
+ loadingPromises.releaseUnsubscribedOnMutation();
182
+ const queries = [
183
+ // Dedupe
184
+ ...new Set([...loadingPromisesQueries, ...subscribedQueries.get()]),
185
+ ];
142
186
  if (isNonEmptyReadonlyArray(queries)) {
143
187
  dbWorker.postMessage({ type: "query", tabId: getTabId(), queries });
144
188
  }
@@ -146,15 +190,75 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
146
190
  }
147
191
  case "onReset": {
148
192
  if (message.reload) {
149
- appState.reset();
193
+ deps.reloadApp(reloadUrl);
150
194
  }
151
195
  else {
152
- callbacks.execute(message.onCompleteId);
196
+ onCompleteRegistry.execute(message.onCompleteId);
153
197
  }
154
198
  break;
155
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
+ // }
156
260
  case "onExport": {
157
- callbacks.execute(message.onCompleteId, message.file);
261
+ exportRegistry.execute(message.onCompleteId, message.file);
158
262
  break;
159
263
  }
160
264
  default:
@@ -177,24 +281,21 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
177
281
  }
178
282
  return type;
179
283
  };
180
- dbWorker.postMessage({
181
- type: "init",
182
- config,
183
- dbSchema,
184
- });
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" });
185
287
  const loadQueryMicrotaskQueue = [];
186
288
  const mutateMicrotaskQueue = [];
289
+ const useOwnerMicrotaskQueue = [];
187
290
  const createMutation = (kind) => (table, props, options) => {
188
- const Type = getMutationType(table, kind);
189
- const result = Type.fromUnknown(props);
291
+ const result = getMutationType(table, kind).fromUnknown(props);
190
292
  const id = kind === "insert"
191
293
  ? createId(deps)
192
294
  : props.id;
193
295
  if (options?.onlyValidate !== true) {
194
296
  if (!result.ok) {
195
- // One error must invalidate the whole queue.
196
- // We insert `undefined` to detect such a situation.
197
- mutateMicrotaskQueue.push([undefined, undefined]);
297
+ // Mark the transaction as invalid by pushing null
298
+ mutateMicrotaskQueue.push([null, undefined]);
198
299
  }
199
300
  else {
200
301
  const values = { ...result.value };
@@ -205,60 +306,74 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
205
306
  values.createdAt = new Date(deps.time.now()).toISOString();
206
307
  }
207
308
  }
208
- const dbChange = {
209
- table,
210
- id,
211
- values,
212
- };
213
- assertValidDbChange(dbChange);
214
- mutateMicrotaskQueue.push([dbChange, options?.onComplete]);
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);
215
316
  }
216
- if (mutateMicrotaskQueue.length === 1)
217
- queueMicrotask(() => {
218
- const changes = [];
219
- const onCompletes = [];
220
- for (const [change, onComplete] of mutateMicrotaskQueue) {
221
- if (change)
222
- changes.push(change);
223
- if (onComplete)
224
- onCompletes.push(onComplete);
225
- }
226
- const mutateMicrotaskQueueLength = mutateMicrotaskQueue.length;
227
- mutateMicrotaskQueue.length = 0;
228
- // Don't mutate anything if there was a validation error.
229
- // All mutations within a queue are considered to be a transaction.
230
- if (changes.length !== mutateMicrotaskQueueLength) {
231
- return;
232
- }
233
- const onCompleteIds = onCompletes.map((onComplete) => callbacks.register(onComplete));
234
- loadingPromises.releaseUnsubscribed();
235
- if (isNonEmptyArray(changes))
236
- dbWorker.postMessage({
237
- type: "mutate",
238
- tabId: getTabId(),
239
- changes,
240
- onCompleteIds,
241
- subscribedQueries: subscribedQueries.get(),
242
- });
243
- });
244
317
  }
245
318
  if (result.ok)
246
319
  return ok({ id });
247
320
  return err(result.error);
248
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
+ };
249
373
  const evolu = {
250
374
  subscribeError: errorStore.subscribe,
251
375
  getError: errorStore.get,
252
- createQuery: (queryCallback, options) => {
253
- const compiledQuery = queryCallback(kysely).compile();
254
- if (isSqlMutation(compiledQuery.sql))
255
- 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.");
256
- return serializeQuery({
257
- sql: compiledQuery.sql,
258
- parameters: compiledQuery.parameters,
259
- ...(options && { options }),
260
- });
261
- },
376
+ createQuery,
262
377
  loadQuery: (query) => {
263
378
  const { promise, isNew } = loadingPromises.get(query);
264
379
  if (isNew) {
@@ -269,6 +384,7 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
269
384
  const queries = [...new Set(loadQueryMicrotaskQueue)];
270
385
  loadQueryMicrotaskQueue.length = 0;
271
386
  assertNonEmptyArray(queries);
387
+ deps.console.log("[evolu]", "loadQuery", { queries });
272
388
  dbWorker.postMessage({
273
389
  type: "query",
274
390
  tabId: getTabId(),
@@ -296,20 +412,16 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
296
412
  };
297
413
  },
298
414
  getQueryRows: (query) => (rowsStore.get().get(query) ?? emptyRows),
299
- subscribeAppOwner: appOwnerStore.subscribe,
300
- getAppOwner: appOwnerStore.get,
301
- subscribeSyncState: syncStore.subscribe,
302
- getSyncState: syncStore.get,
415
+ appOwner,
416
+ // TODO: Update it for the owner-api
417
+ // subscribeSyncState: syncStore.subscribe,
418
+ // getSyncState: syncStore.get,
303
419
  insert: createMutation("insert"),
304
420
  update: createMutation("update"),
305
421
  upsert: createMutation("upsert"),
306
422
  resetAppOwner: (options) => {
307
- // Eslint bug, Promise<void> is correct by docs.
308
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
309
423
  const { promise, resolve } = Promise.withResolvers();
310
- const onCompleteId = callbacks.register(() => {
311
- resolve();
312
- });
424
+ const onCompleteId = onCompleteRegistry.register(resolve);
313
425
  dbWorker.postMessage({
314
426
  type: "reset",
315
427
  onCompleteId,
@@ -318,12 +430,8 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
318
430
  return promise;
319
431
  },
320
432
  restoreAppOwner: (mnemonic, options) => {
321
- // Eslint bug, Promise<void> is correct by docs.
322
- // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
323
433
  const { promise, resolve } = Promise.withResolvers();
324
- const onCompleteId = callbacks.register(() => {
325
- resolve();
326
- });
434
+ const onCompleteId = onCompleteRegistry.register(resolve);
327
435
  dbWorker.postMessage({
328
436
  type: "reset",
329
437
  onCompleteId,
@@ -333,7 +441,7 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
333
441
  return promise;
334
442
  },
335
443
  reloadApp: () => {
336
- appState.reset();
444
+ deps.reloadApp(reloadUrl);
337
445
  },
338
446
  ensureSchema: (schema) => {
339
447
  mutationTypesCache.clear();
@@ -342,20 +450,68 @@ const createEvoluInstance = (deps) => (schema, evoluConfig) => {
342
450
  },
343
451
  exportDatabase: () => {
344
452
  const { promise, resolve } = Promise.withResolvers();
345
- const onCompleteId = callbacks.register((arg) => {
346
- if (arg instanceof Uint8Array)
347
- resolve(arg);
348
- });
453
+ const onCompleteId = exportRegistry.register(resolve);
349
454
  dbWorker.postMessage({ type: "export", onCompleteId });
350
455
  return promise;
351
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
+ },
352
499
  };
353
500
  return evolu;
354
501
  };
355
- 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
+ };
356
512
  const createLoadingPromises = (subscribedQueries) => {
357
513
  const loadingPromiseMap = new Map();
358
- const loadingPromises = {
514
+ return {
359
515
  get: (query) => {
360
516
  let loadingPromise = loadingPromiseMap.get(query);
361
517
  const isNew = !loadingPromise;
@@ -377,15 +533,12 @@ const createLoadingPromises = (subscribedQueries) => {
377
533
  loadingPromise.resolve(rows);
378
534
  }
379
535
  else {
380
- // A promise can't be fulfilled 2x, so we need a new one.
381
536
  loadingPromise.promise = Promise.resolve(rows);
382
537
  }
383
- /**
384
- * "For example, a data framework can set the status and value fields on a
385
- * promise preemptively, before passing to React, so that React can unwrap
386
- * it without waiting a microtask."
387
- * https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
388
- */
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
389
542
  void Object.assign(loadingPromise.promise, {
390
543
  status: "fulfilled",
391
544
  value: rows,
@@ -394,14 +547,7 @@ const createLoadingPromises = (subscribedQueries) => {
394
547
  loadingPromiseMap.delete(query);
395
548
  }
396
549
  },
397
- /**
398
- * We can't delete loading promises in `resolveLoadingPromises` because they
399
- * must be cached, so repeated calls to `loadQuery` will always return the
400
- * same promise until the data changes, and we also can't cache them forever
401
- * because only subscribed queries are automatically updated (reactivity is
402
- * expensive) hence this function must be called manually on any mutation.
403
- */
404
- releaseUnsubscribed: () => {
550
+ releaseUnsubscribedOnMutation: () => {
405
551
  [...loadingPromiseMap.entries()]
406
552
  .filter(([query]) => !subscribedQueries.has(query))
407
553
  .forEach(([query, loadingPromise]) => {
@@ -413,9 +559,6 @@ const createLoadingPromises = (subscribedQueries) => {
413
559
  }
414
560
  });
415
561
  },
562
+ getQueries: () => Array.from(loadingPromiseMap.keys()),
416
563
  };
417
- return loadingPromises;
418
- };
419
- const assertValidDbChange = (dbChange) => {
420
- assert(DbChange.is(dbChange), `Failed to create DbChange for table "${dbChange.table}". If you see this message, you either disabled EvoluSchema validation or Evolu has a bug - please report it.`);
421
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";