@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.
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +1 -1
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +1 -1
- package/dist/src/CallbackRegistry.d.ts +53 -0
- package/dist/src/CallbackRegistry.d.ts.map +1 -0
- package/dist/src/CallbackRegistry.js +25 -0
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +48 -37
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +27 -50
- package/dist/src/Evolu/Db.d.ts +138 -66
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +248 -645
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +79 -116
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +275 -132
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +144 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +171 -0
- package/dist/src/Evolu/Owner.d.ts +129 -83
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +80 -89
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +114 -191
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +409 -416
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.js +3 -3
- package/dist/src/Evolu/Relay.d.ts +1 -2
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +11 -9
- package/dist/src/Evolu/Schema.d.ts +88 -27
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +141 -24
- package/dist/src/Evolu/Storage.d.ts +158 -14
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +32 -32
- package/dist/src/Evolu/Sync.d.ts +77 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +453 -20
- package/dist/src/Evolu/Timestamp.d.ts +29 -27
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +20 -18
- package/dist/src/ManyToManyMap.d.ts +74 -10
- package/dist/src/ManyToManyMap.d.ts.map +1 -1
- package/dist/src/ManyToManyMap.js +41 -6
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/RefCountedResourceManager.d.ts +119 -0
- package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
- package/dist/src/RefCountedResourceManager.js +197 -0
- package/dist/src/Result.d.ts +144 -22
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +5 -2
- package/dist/src/Sqlite.d.ts +20 -4
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +50 -8
- package/dist/src/Task.d.ts +511 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +410 -0
- package/dist/src/Time.d.ts +59 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +87 -4
- package/dist/src/Type.d.ts +431 -341
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +458 -466
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -13
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +2 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -3
- package/package.json +7 -7
- package/src/Assert.ts +2 -4
- package/src/Buffer.ts +1 -1
- package/src/CallbackRegistry.ts +84 -0
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +78 -91
- package/src/Evolu/Db.ts +455 -947
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +545 -307
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +422 -0
- package/src/Evolu/Owner.ts +191 -131
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +536 -653
- package/src/Evolu/Public.ts +7 -9
- package/src/Evolu/PublicKysely.ts +3 -3
- package/src/Evolu/Relay.ts +17 -12
- package/src/Evolu/Schema.ts +271 -66
- package/src/Evolu/Storage.ts +263 -55
- package/src/Evolu/Sync.ts +758 -37
- package/src/Evolu/Timestamp.ts +30 -35
- package/src/ManyToManyMap.ts +127 -24
- package/src/Random.ts +3 -2
- package/src/RefCountedResourceManager.ts +368 -0
- package/src/Result.ts +149 -23
- package/src/Sqlite.ts +59 -24
- package/src/Task.ts +779 -0
- package/src/Time.ts +168 -4
- package/src/Type.ts +657 -695
- package/src/WebSocket.ts +23 -17
- package/src/Worker.ts +72 -23
- package/src/index.ts +2 -3
- package/dist/src/Callbacks.d.ts +0 -20
- package/dist/src/Callbacks.d.ts.map +0 -1
- package/dist/src/Callbacks.js +0 -18
- package/dist/src/Evolu/Config.d.ts +0 -82
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Callbacks.ts +0 -43
- package/src/Evolu/Config.ts +0 -97
- package/src/Evolu/Kysely.ts +0 -38
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/dist/src/Evolu/Evolu.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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,
|
|
67
|
-
const
|
|
68
|
-
let evolu = evoluInstances.get(
|
|
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(
|
|
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,
|
|
80
|
-
deps.console.enabled =
|
|
81
|
-
|
|
82
|
-
const {
|
|
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
|
|
86
|
-
|
|
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
|
|
90
|
-
const
|
|
91
|
-
const dbWorker = deps.createDbWorker(
|
|
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 "
|
|
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.
|
|
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.
|
|
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
|
-
|
|
173
|
+
onCompleteRegistry.execute(id);
|
|
134
174
|
}
|
|
135
175
|
break;
|
|
136
176
|
}
|
|
137
|
-
case "
|
|
177
|
+
case "refreshQueries": {
|
|
138
178
|
if (message.tabId && message.tabId === getTabId())
|
|
139
179
|
return;
|
|
140
|
-
loadingPromises.
|
|
141
|
-
|
|
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
|
-
|
|
193
|
+
deps.reloadApp(reloadUrl);
|
|
150
194
|
}
|
|
151
195
|
else {
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
|
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
|
-
//
|
|
196
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
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
|
-
|
|
300
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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,
|
|
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";
|