@evolu/common 7.4.0 → 8.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +1064 -630
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -823
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +209 -282
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -529
- package/src/Instances.ts +0 -90
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -960
|
@@ -1,440 +1,510 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Local-first platform.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
7
|
+
if (value !== null && value !== void 0) {
|
|
8
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
9
|
+
var dispose, inner;
|
|
10
|
+
if (async) {
|
|
11
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.asyncDispose];
|
|
13
|
+
}
|
|
14
|
+
if (dispose === void 0) {
|
|
15
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
16
|
+
dispose = value[Symbol.dispose];
|
|
17
|
+
if (async) inner = dispose;
|
|
18
|
+
}
|
|
19
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
20
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
21
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
22
|
+
}
|
|
23
|
+
else if (async) {
|
|
24
|
+
env.stack.push({ async: true });
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
29
|
+
return function (env) {
|
|
30
|
+
function fail(e) {
|
|
31
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
32
|
+
env.hasError = true;
|
|
33
|
+
}
|
|
34
|
+
var r, s = 0;
|
|
35
|
+
function next() {
|
|
36
|
+
while (r = env.stack.pop()) {
|
|
37
|
+
try {
|
|
38
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
39
|
+
if (r.dispose) {
|
|
40
|
+
var result = r.dispose.call(r.value);
|
|
41
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
42
|
+
}
|
|
43
|
+
else s |= 1;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
fail(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
50
|
+
if (env.hasError) throw env.error;
|
|
51
|
+
}
|
|
52
|
+
return next();
|
|
53
|
+
};
|
|
54
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
55
|
+
var e = new Error(message);
|
|
56
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
|
+
});
|
|
58
|
+
import { emptyArray, isNonEmptyArray, mapArray, } from "../Array.js";
|
|
59
|
+
import { assert, assertNonEmptyReadonlyArray, assertNotDisposed, } from "../Assert.js";
|
|
4
60
|
import { createCallbacks } from "../Callbacks.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
61
|
+
import { createConsole } from "../Console.js";
|
|
62
|
+
import { createUnknownError } from "../Error.js";
|
|
63
|
+
import { exhaustiveCheck, todo } from "../Function.js";
|
|
64
|
+
import { createMicrotaskBatch } from "../Microtask.js";
|
|
65
|
+
import { createRefCountByKey } from "../RefCount.js";
|
|
8
66
|
import { err, ok } from "../Result.js";
|
|
9
|
-
import {
|
|
67
|
+
import { isNonEmptySet } from "../Set.js";
|
|
68
|
+
import { SqliteBoolean, sqliteBooleanToBoolean } from "../Sqlite.js";
|
|
10
69
|
import { createStore } from "../Store.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
70
|
+
import {} from "../Task.js";
|
|
71
|
+
import { brand, createId, createIdFromString, Name, UrlSafeString, } from "../Type.js";
|
|
72
|
+
import { createOwnerWebSocketTransport } from "./Owner.js";
|
|
73
|
+
import { applyPatches } from "./Query.js";
|
|
74
|
+
import { evoluSchemaToSqliteSchema } from "./Schema.js";
|
|
15
75
|
import { DbChange } from "./Storage.js";
|
|
16
|
-
import { initialSyncState } from "./Sync.js";
|
|
17
|
-
const evoluInstances = createInstances();
|
|
18
76
|
/**
|
|
19
|
-
*
|
|
20
|
-
* initialized on first use to distinguish between multiple tabs.
|
|
21
|
-
*/
|
|
22
|
-
let tabId = null;
|
|
23
|
-
/**
|
|
24
|
-
* Creates an {@link Evolu} instance for a platform configured with the specified
|
|
25
|
-
* {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
|
|
26
|
-
* interface for querying, mutating, and syncing your application's data.
|
|
27
|
-
*
|
|
28
|
-
* ### Example
|
|
29
|
-
*
|
|
30
|
-
* ```ts
|
|
31
|
-
* const TodoId = id("Todo");
|
|
32
|
-
* type TodoId = InferType<typeof TodoId>;
|
|
77
|
+
* Application name.
|
|
33
78
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
79
|
+
* Evolu uses AppName as the base prefix for {@link Evolu.name}. The final
|
|
80
|
+
* instance name is derived per {@link AppOwner} as
|
|
81
|
+
* `${appName}-${createIdFromString(appOwner.id)}`.
|
|
36
82
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* todoCategory: {
|
|
48
|
-
* id: TodoCategoryId,
|
|
49
|
-
* name: NonEmptyString50,
|
|
50
|
-
* },
|
|
51
|
-
* };
|
|
52
|
-
*
|
|
53
|
-
* const evolu = createEvolu(evoluReactDeps)(Schema);
|
|
54
|
-
* ```
|
|
83
|
+
* Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
|
|
84
|
+
* `_`) and must be between 1 and 41 characters.
|
|
85
|
+
*/
|
|
86
|
+
export const AppName = /*#__PURE__*/ brand("AppName", UrlSafeString, (value) => value.length >= 1 && value.length <= 41
|
|
87
|
+
? ok(value)
|
|
88
|
+
: err({ type: "AppName", value }));
|
|
89
|
+
export const testAppName = /*#__PURE__*/ AppName.orThrow("AppName");
|
|
90
|
+
/**
|
|
91
|
+
* Creates shared dependencies used by all {@link createEvolu} instances on a
|
|
92
|
+
* platform.
|
|
55
93
|
*
|
|
56
|
-
*
|
|
94
|
+
* Call this once per platform and reuse the returned deps when creating
|
|
95
|
+
* multiple Evolu instances. The returned deps object owns long-lived resources
|
|
96
|
+
* such as worker channels and the shared {@link EvoluErrorDep.evoluError}
|
|
97
|
+
* store.
|
|
57
98
|
*
|
|
58
|
-
*
|
|
59
|
-
* name to enable hot reloading and prevent database corruption from multiple
|
|
60
|
-
* connections. For testing, use unique instance names to ensure proper
|
|
61
|
-
* isolation.
|
|
99
|
+
* Dispose it only during app shutdown.
|
|
62
100
|
*/
|
|
63
|
-
export const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
deps.console.log("[evolu]", "createEvoluInstance", {
|
|
72
|
-
name: dbConfig.name,
|
|
73
|
-
});
|
|
74
|
-
const errorStore = createStore(null);
|
|
75
|
-
const rowsStore = createStore(new Map());
|
|
76
|
-
const { promise: appOwner, resolve: resolveAppOwner } = Promise.withResolvers();
|
|
77
|
-
if (config?.externalAppOwner) {
|
|
78
|
-
resolveAppOwner(config.externalAppOwner);
|
|
79
|
-
}
|
|
80
|
-
// TODO: Update it for the owner-api
|
|
81
|
-
const _syncStore = createStore(initialSyncState);
|
|
82
|
-
const subscribedQueries = createSubscribedQueries(rowsStore);
|
|
83
|
-
const loadingPromises = createLoadingPromises(subscribedQueries);
|
|
84
|
-
const onCompleteCallbacks = createCallbacks(deps);
|
|
85
|
-
const exportCallbacks = createCallbacks(deps);
|
|
86
|
-
const dbWorker = deps.createDbWorker(dbConfig.name);
|
|
87
|
-
const getTabId = () => {
|
|
88
|
-
tabId ??= createId(deps);
|
|
89
|
-
return tabId;
|
|
90
|
-
};
|
|
91
|
-
// Worker responses are delivered to all tabs. Each case must handle this
|
|
92
|
-
// properly (e.g., AppOwner promise resolves only once, tabId filtering).
|
|
93
|
-
dbWorker.onMessage((message) => {
|
|
101
|
+
export const createEvoluDeps = (deps) => {
|
|
102
|
+
const { createMessageChannel, sharedWorker } = deps;
|
|
103
|
+
const console = deps.console ?? createConsole();
|
|
104
|
+
const stack = new DisposableStack();
|
|
105
|
+
stack.use(sharedWorker);
|
|
106
|
+
const evoluError = stack.use(createStore(null));
|
|
107
|
+
const tabChannel = stack.use(createMessageChannel());
|
|
108
|
+
tabChannel.port2.onMessage = (message) => {
|
|
94
109
|
switch (message.type) {
|
|
95
|
-
case "
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
resolveAppOwner(message.appOwner);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
case "onQueryPatches": {
|
|
104
|
-
if (message.tabId !== getTabId())
|
|
105
|
-
return;
|
|
106
|
-
const state = rowsStore.get();
|
|
107
|
-
const nextState = new Map([
|
|
108
|
-
...state,
|
|
109
|
-
...message.queryPatches.map(({ query, patches }) => [
|
|
110
|
-
query,
|
|
111
|
-
applyPatches(patches, state.get(query) ?? emptyRows),
|
|
112
|
-
]),
|
|
113
|
-
]);
|
|
114
|
-
for (const { query } of message.queryPatches) {
|
|
115
|
-
loadingPromises.resolve(query, nextState.get(query) ?? emptyRows);
|
|
116
|
-
}
|
|
117
|
-
if (deps.flushSync && message.onCompleteIds.length > 0) {
|
|
118
|
-
deps.flushSync(() => {
|
|
119
|
-
rowsStore.set(nextState);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
rowsStore.set(nextState);
|
|
124
|
-
}
|
|
125
|
-
for (const id of message.onCompleteIds) {
|
|
126
|
-
onCompleteCallbacks.execute(id);
|
|
110
|
+
case "OnConsoleEntry":
|
|
111
|
+
console.write(message.entry);
|
|
112
|
+
// Fallback channel for unexpected errors without EvoluError typing.
|
|
113
|
+
if (message.entry.method === "error") {
|
|
114
|
+
evoluError.set(createUnknownError(message.entry.args));
|
|
127
115
|
}
|
|
128
116
|
break;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const loadingPromisesQueries = loadingPromises.getQueries();
|
|
134
|
-
loadingPromises.releaseUnsubscribedOnMutation();
|
|
135
|
-
const queries = dedupeArray([
|
|
136
|
-
...loadingPromisesQueries,
|
|
137
|
-
...subscribedQueries.get(),
|
|
138
|
-
]);
|
|
139
|
-
if (isNonEmptyReadonlyArray(queries)) {
|
|
140
|
-
dbWorker.postMessage({ type: "query", tabId: getTabId(), queries });
|
|
141
|
-
}
|
|
117
|
+
case "OnError":
|
|
118
|
+
evoluError.set(message.error);
|
|
119
|
+
// Keep typed errors visible in logs as operational failures.
|
|
120
|
+
console.error(message.error);
|
|
142
121
|
break;
|
|
143
|
-
}
|
|
144
|
-
case "onReset": {
|
|
145
|
-
if (message.reload) {
|
|
146
|
-
deps.reloadApp(reloadUrl);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
onCompleteCallbacks.execute(message.onCompleteId);
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
case "onExport": {
|
|
154
|
-
exportCallbacks.execute(message.onCompleteId, message.file);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
122
|
default:
|
|
158
123
|
exhaustiveCheck(message);
|
|
159
124
|
}
|
|
160
|
-
});
|
|
161
|
-
const dbSchema = evoluSchemaToDbSchema(schema, indexes);
|
|
162
|
-
const mutationTypesCache = new Map();
|
|
163
|
-
// Lazy create mutation Types like this: `insertable(Schema.todo)`
|
|
164
|
-
const getMutationType = (table, kind) => {
|
|
165
|
-
let types = mutationTypesCache.get(kind);
|
|
166
|
-
if (!types) {
|
|
167
|
-
types = new Map();
|
|
168
|
-
mutationTypesCache.set(kind, types);
|
|
169
|
-
}
|
|
170
|
-
let type = types.get(table);
|
|
171
|
-
if (!type) {
|
|
172
|
-
type = { insert: insertable, update: updateable, upsert: upsertable }[kind](schema[table]);
|
|
173
|
-
types.set(table, type);
|
|
174
|
-
}
|
|
175
|
-
return type;
|
|
176
|
-
};
|
|
177
|
-
dbWorker.postMessage({ type: "init", config: dbConfig, dbSchema });
|
|
178
|
-
// We can't use `init` to get AppOwner because `init` runs only once per n tabs.
|
|
179
|
-
dbWorker.postMessage({ type: "getAppOwner" });
|
|
180
|
-
const loadQueryMicrotaskQueue = [];
|
|
181
|
-
const mutateMicrotaskQueue = [];
|
|
182
|
-
const useOwnerMicrotaskQueue = [];
|
|
183
|
-
const createMutation = (kind) => (table, props, options) => {
|
|
184
|
-
const result = getMutationType(table, kind).fromUnknown(props);
|
|
185
|
-
const id = kind === "insert"
|
|
186
|
-
? createId(deps)
|
|
187
|
-
: props.id;
|
|
188
|
-
if (options?.onlyValidate !== true) {
|
|
189
|
-
if (!result.ok) {
|
|
190
|
-
// Mark the transaction as invalid by pushing null
|
|
191
|
-
mutateMicrotaskQueue.push([null, undefined]);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
const { id: _, isDeleted, ...values } = result.value;
|
|
195
|
-
const dbChange = {
|
|
196
|
-
table,
|
|
197
|
-
id,
|
|
198
|
-
values,
|
|
199
|
-
isInsert: kind === "insert" || kind === "upsert",
|
|
200
|
-
isDelete: SqliteBoolean.is(isDeleted)
|
|
201
|
-
? sqliteBooleanToBoolean(isDeleted)
|
|
202
|
-
: null,
|
|
203
|
-
};
|
|
204
|
-
assert(DbChange.is(dbChange), `Invalid DbChange for table '${table}': Please check schema type errors.`);
|
|
205
|
-
mutateMicrotaskQueue.push([
|
|
206
|
-
{ ...dbChange, ownerId: options?.ownerId },
|
|
207
|
-
options?.onComplete,
|
|
208
|
-
]);
|
|
209
|
-
}
|
|
210
|
-
if (mutateMicrotaskQueue.length === 1) {
|
|
211
|
-
queueMicrotask(processMutationQueue);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (result.ok)
|
|
215
|
-
return ok({ id });
|
|
216
|
-
return err(result.error);
|
|
217
125
|
};
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
mutateMicrotaskQueue.length = 0;
|
|
229
|
-
// Don't process any mutations if there was a validation error.
|
|
230
|
-
// All mutations within a queue run as a single transaction.
|
|
231
|
-
if (changes.length !== queueLength) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const onCompleteIds = onCompletes.map(onCompleteCallbacks.register);
|
|
235
|
-
loadingPromises.releaseUnsubscribedOnMutation();
|
|
236
|
-
if (!isNonEmptyArray(changes))
|
|
237
|
-
return;
|
|
238
|
-
dbWorker.postMessage({
|
|
239
|
-
type: "mutate",
|
|
240
|
-
tabId: getTabId(),
|
|
241
|
-
changes,
|
|
242
|
-
onCompleteIds,
|
|
243
|
-
subscribedQueries: subscribedQueries.get(),
|
|
244
|
-
});
|
|
245
|
-
};
|
|
246
|
-
const evolu = {
|
|
247
|
-
subscribeError: errorStore.subscribe,
|
|
248
|
-
getError: errorStore.get,
|
|
249
|
-
createQuery,
|
|
250
|
-
loadQuery: (query) => {
|
|
251
|
-
const { promise, isNew } = loadingPromises.get(query);
|
|
252
|
-
if (isNew) {
|
|
253
|
-
loadQueryMicrotaskQueue.push(query);
|
|
254
|
-
if (loadQueryMicrotaskQueue.length === 1) {
|
|
255
|
-
queueMicrotask(() => {
|
|
256
|
-
const queries = dedupeArray(loadQueryMicrotaskQueue);
|
|
257
|
-
loadQueryMicrotaskQueue.length = 0;
|
|
258
|
-
assertNonEmptyReadonlyArray(queries);
|
|
259
|
-
deps.console.log("[evolu]", "loadQuery", { queries });
|
|
260
|
-
dbWorker.postMessage({
|
|
261
|
-
type: "query",
|
|
262
|
-
tabId: getTabId(),
|
|
263
|
-
queries,
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return promise;
|
|
269
|
-
},
|
|
270
|
-
loadQueries: (queries) => queries.map(evolu.loadQuery),
|
|
271
|
-
subscribeQuery: (query) => (listener) => {
|
|
272
|
-
// Call the listener only if the result has been changed.
|
|
273
|
-
let previousRows = null;
|
|
274
|
-
const unsubscribe = subscribedQueries.subscribe(query)(() => {
|
|
275
|
-
const rows = evolu.getQueryRows(query);
|
|
276
|
-
if (previousRows === rows)
|
|
277
|
-
return;
|
|
278
|
-
previousRows = rows;
|
|
279
|
-
listener();
|
|
280
|
-
});
|
|
281
|
-
return () => {
|
|
282
|
-
previousRows = null;
|
|
283
|
-
unsubscribe();
|
|
284
|
-
};
|
|
285
|
-
},
|
|
286
|
-
getQueryRows: (query) => (rowsStore.get().get(query) ?? emptyRows),
|
|
287
|
-
appOwner,
|
|
288
|
-
// TODO: Update it for the owner-api
|
|
289
|
-
// subscribeSyncState: syncStore.subscribe,
|
|
290
|
-
// getSyncState: syncStore.get,
|
|
291
|
-
insert: createMutation("insert"),
|
|
292
|
-
update: createMutation("update"),
|
|
293
|
-
upsert: createMutation("upsert"),
|
|
294
|
-
resetAppOwner: (options) => {
|
|
295
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
296
|
-
const onCompleteId = onCompleteCallbacks.register(resolve);
|
|
297
|
-
dbWorker.postMessage({
|
|
298
|
-
type: "reset",
|
|
299
|
-
onCompleteId,
|
|
300
|
-
reload: options?.reload ?? true,
|
|
301
|
-
});
|
|
302
|
-
return promise;
|
|
303
|
-
},
|
|
304
|
-
restoreAppOwner: (mnemonic, options) => {
|
|
305
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
306
|
-
const onCompleteId = onCompleteCallbacks.register(resolve);
|
|
307
|
-
dbWorker.postMessage({
|
|
308
|
-
type: "reset",
|
|
309
|
-
onCompleteId,
|
|
310
|
-
reload: options?.reload ?? true,
|
|
311
|
-
restore: { mnemonic, dbSchema },
|
|
312
|
-
});
|
|
313
|
-
return promise;
|
|
314
|
-
},
|
|
315
|
-
reloadApp: () => {
|
|
316
|
-
deps.reloadApp(reloadUrl);
|
|
317
|
-
},
|
|
318
|
-
ensureSchema: (schema) => {
|
|
319
|
-
mutationTypesCache.clear();
|
|
320
|
-
const dbSchema = evoluSchemaToDbSchema(schema);
|
|
321
|
-
dbWorker.postMessage({ type: "ensureDbSchema", dbSchema });
|
|
322
|
-
},
|
|
323
|
-
exportDatabase: () => {
|
|
324
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
325
|
-
const onCompleteId = exportCallbacks.register(resolve);
|
|
326
|
-
dbWorker.postMessage({ type: "export", onCompleteId });
|
|
327
|
-
return promise;
|
|
328
|
-
},
|
|
329
|
-
useOwner: (owner) => {
|
|
330
|
-
const scheduleOwnerQueueProcessing = () => {
|
|
331
|
-
if (useOwnerMicrotaskQueue.length !== 1)
|
|
332
|
-
return;
|
|
333
|
-
queueMicrotask(() => {
|
|
334
|
-
const queue = [...useOwnerMicrotaskQueue];
|
|
335
|
-
useOwnerMicrotaskQueue.length = 0;
|
|
336
|
-
const result = [];
|
|
337
|
-
const skipIndices = new Set();
|
|
338
|
-
for (let i = 0; i < queue.length; i++) {
|
|
339
|
-
if (skipIndices.has(i))
|
|
340
|
-
continue;
|
|
341
|
-
const [currentOwner, currentUse, currentOwnerSerialized] = queue[i];
|
|
342
|
-
// Look for opposite action with same owner
|
|
343
|
-
for (let j = i + 1; j < queue.length; j++) {
|
|
344
|
-
if (skipIndices.has(j))
|
|
345
|
-
continue;
|
|
346
|
-
const [, otherUse, otherOwnerSerialized] = queue[j];
|
|
347
|
-
if (currentUse !== otherUse &&
|
|
348
|
-
eqArrayNumber(currentOwnerSerialized, otherOwnerSerialized)) {
|
|
349
|
-
// Found cancel-out pair, skip both
|
|
350
|
-
skipIndices.add(i).add(j);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
if (!skipIndices.has(i)) {
|
|
355
|
-
result.push([currentOwner, currentUse, currentOwnerSerialized]);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
for (const [owner, use] of result) {
|
|
359
|
-
dbWorker.postMessage({ type: "useOwner", owner, use });
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
};
|
|
363
|
-
useOwnerMicrotaskQueue.push([owner, true, pack(owner)]);
|
|
364
|
-
scheduleOwnerQueueProcessing();
|
|
365
|
-
const unuse = () => {
|
|
366
|
-
useOwnerMicrotaskQueue.push([owner, false, pack(owner)]);
|
|
367
|
-
scheduleOwnerQueueProcessing();
|
|
368
|
-
};
|
|
369
|
-
return unuse;
|
|
370
|
-
},
|
|
371
|
-
/** Disposal is not implemented yet. */
|
|
372
|
-
[Symbol.dispose]: () => {
|
|
373
|
-
throw new Error("Evolu instance disposal is not yet implemented");
|
|
374
|
-
},
|
|
126
|
+
sharedWorker.port.postMessage({
|
|
127
|
+
type: "InitTab",
|
|
128
|
+
consoleLevel: console.getLevel(),
|
|
129
|
+
port: tabChannel.port1.native,
|
|
130
|
+
}, [tabChannel.port1.native]);
|
|
131
|
+
const moved = stack.move();
|
|
132
|
+
return {
|
|
133
|
+
...deps,
|
|
134
|
+
evoluError,
|
|
135
|
+
[Symbol.dispose]: () => moved.dispose(),
|
|
375
136
|
};
|
|
376
|
-
return evolu;
|
|
377
|
-
};
|
|
378
|
-
export const createQuery = (queryCallback, options) => {
|
|
379
|
-
const compiledQuery = queryCallback(kysely).compile();
|
|
380
|
-
if (isSqlMutation(compiledQuery.sql))
|
|
381
|
-
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.");
|
|
382
|
-
return serializeQuery({
|
|
383
|
-
sql: compiledQuery.sql,
|
|
384
|
-
parameters: compiledQuery.parameters,
|
|
385
|
-
...(options && { options }),
|
|
386
|
-
});
|
|
387
137
|
};
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Creates an {@link Evolu} instance from {@link EvoluSchema} and
|
|
140
|
+
* {@link EvoluConfig}.
|
|
141
|
+
*/
|
|
142
|
+
export const createEvolu = (schema, config) => async (run) => {
|
|
143
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
144
|
+
try {
|
|
145
|
+
const { appName, appOwner, memoryOnly = false, transports = [{ type: "WebSocket", url: "wss://free.evoluhq.com" }], } = config;
|
|
146
|
+
const name = Name.orThrow(`${appName}-${createIdFromString(appOwner.id)}`);
|
|
147
|
+
const console = run.deps.console.child(name).child("Evolu");
|
|
148
|
+
console.info("createEvolu");
|
|
149
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
150
|
+
const rowsByQueryMapStore = stack.use(createStore(new Map()));
|
|
151
|
+
const subscribedQueriesRefCount = stack.use(createRefCountByKey());
|
|
152
|
+
/**
|
|
153
|
+
* Settle pending query loads during disposal so awaiting callers and React
|
|
154
|
+
* `use` thenables do not hang forever during teardown.
|
|
155
|
+
*/
|
|
156
|
+
const loadingPromisesByQuery = stack.adopt(new Map(), (loadingPromisesByQuery) => {
|
|
157
|
+
for (const loadingPromise of loadingPromisesByQuery.values()) {
|
|
158
|
+
if (loadingPromise.promise.status === "fulfilled")
|
|
159
|
+
continue;
|
|
160
|
+
fulfillLoadingPromise(loadingPromise, emptyArray);
|
|
398
161
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return;
|
|
162
|
+
loadingPromisesByQuery.clear();
|
|
163
|
+
});
|
|
164
|
+
const fulfillLoadingPromise = (loadingPromise, rows) => {
|
|
165
|
+
/**
|
|
166
|
+
* Pending promises must be resolved in place to preserve identity for
|
|
167
|
+
* current awaiters. Fulfilled promises are replaced with a new resolved
|
|
168
|
+
* promise so future loads see the latest rows.
|
|
169
|
+
*/
|
|
408
170
|
if (loadingPromise.promise.status !== "fulfilled") {
|
|
409
171
|
loadingPromise.resolve(rows);
|
|
410
172
|
}
|
|
411
173
|
else {
|
|
412
174
|
loadingPromise.promise = Promise.resolve(rows);
|
|
413
175
|
}
|
|
414
|
-
|
|
415
|
-
// While undocumented in React docs, React still uses these properties internally,
|
|
416
|
-
// and Evolu's own promise caching logic depends on checking `promise.status`.
|
|
417
|
-
// https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
|
|
176
|
+
/** See {@link LoadingPromise.promise}. */
|
|
418
177
|
void Object.assign(loadingPromise.promise, {
|
|
419
178
|
status: "fulfilled",
|
|
420
179
|
value: rows,
|
|
421
180
|
});
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Mutations and refreshes invalidate query snapshots. Keep loading promises
|
|
184
|
+
* only for actively subscribed queries and release unsubscribed ones.
|
|
185
|
+
*
|
|
186
|
+
* Fulfilled promises can be dropped immediately because no awaiter is
|
|
187
|
+
* waiting on them. Pending promises must stay alive until they resolve so
|
|
188
|
+
* current awaiters keep the same promise identity.
|
|
189
|
+
*/
|
|
190
|
+
const releaseUnsubscribedLoadingPromises = () => {
|
|
191
|
+
for (const [query, loadingPromise] of loadingPromisesByQuery) {
|
|
192
|
+
if (subscribedQueriesRefCount.has(query))
|
|
193
|
+
continue;
|
|
430
194
|
if (loadingPromise.promise.status === "fulfilled") {
|
|
431
|
-
|
|
195
|
+
loadingPromisesByQuery.delete(query);
|
|
432
196
|
}
|
|
433
197
|
else {
|
|
434
198
|
loadingPromise.releaseOnResolve = true;
|
|
435
199
|
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const onMutateCompleteCallbacks = stack.use(createCallbacks(run.deps));
|
|
203
|
+
let exportDatabasePending = null;
|
|
204
|
+
stack.defer(() => {
|
|
205
|
+
exportDatabasePending?.reject({ type: "EvoluDisposedError" });
|
|
206
|
+
exportDatabasePending = null;
|
|
207
|
+
});
|
|
208
|
+
const mutateBatch = stack.use(createMicrotaskBatch((items) => {
|
|
209
|
+
console.debug("mutateBatch", { changeCount: items.length });
|
|
210
|
+
releaseUnsubscribedLoadingPromises();
|
|
211
|
+
postMessage({
|
|
212
|
+
type: "Mutate",
|
|
213
|
+
changes: mapArray(items, (item) => item.change),
|
|
214
|
+
onCompleteIds: items.flatMap((item) => item.onComplete
|
|
215
|
+
? [onMutateCompleteCallbacks.register(item.onComplete)]
|
|
216
|
+
: []),
|
|
217
|
+
subscribedQueries: subscribedQueriesRefCount.keys(),
|
|
436
218
|
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
219
|
+
}));
|
|
220
|
+
const queryBatch = stack.use(createMicrotaskBatch((queries) => {
|
|
221
|
+
const dedupedQueries = new Set(queries);
|
|
222
|
+
assert(isNonEmptySet(dedupedQueries), "Expected non-empty query batch.");
|
|
223
|
+
console.debug("queryBatch", { queryCount: dedupedQueries.size });
|
|
224
|
+
postMessage({ type: "Query", queries: dedupedQueries });
|
|
225
|
+
}));
|
|
226
|
+
const useOwnerBatch = stack.use(createMicrotaskBatch((actions) => postMessage({ type: "UseOwner", actions })));
|
|
227
|
+
let postMessage;
|
|
228
|
+
// Scope worker/channel wiring and keep only postMessage outside.
|
|
229
|
+
{
|
|
230
|
+
const { createDbWorker, createMessageChannel, sharedWorker } = run.deps;
|
|
231
|
+
const dbWorkerChannel = stack.use(createMessageChannel());
|
|
232
|
+
const evoluChannel = stack.use(createMessageChannel());
|
|
233
|
+
evoluChannel.port1.onMessage = (message) => {
|
|
234
|
+
switch (message.type) {
|
|
235
|
+
case "OnPatchesByQuery": {
|
|
236
|
+
console.debug("onPatchesByQuery", {
|
|
237
|
+
queryCount: message.patchesByQuery.size,
|
|
238
|
+
onCompleteCount: message.onCompleteIds.length,
|
|
239
|
+
});
|
|
240
|
+
const state = rowsByQueryMapStore.get();
|
|
241
|
+
const nextRowsByQueryMap = new Map(state);
|
|
242
|
+
for (const [query, patches] of message.patchesByQuery) {
|
|
243
|
+
nextRowsByQueryMap.set(query, applyPatches(patches, state.get(query) ?? emptyArray));
|
|
244
|
+
}
|
|
245
|
+
for (const query of message.patchesByQuery.keys()) {
|
|
246
|
+
const loadingPromise = loadingPromisesByQuery.get(query);
|
|
247
|
+
if (!loadingPromise)
|
|
248
|
+
continue;
|
|
249
|
+
const rows = nextRowsByQueryMap.get(query);
|
|
250
|
+
assert(rows, "Expected patched query rows to exist.");
|
|
251
|
+
fulfillLoadingPromise(loadingPromise, rows);
|
|
252
|
+
/**
|
|
253
|
+
* Release promises flagged during mutation when they finish
|
|
254
|
+
* resolving. This keeps in-flight promise identity stable and
|
|
255
|
+
* prevents stale cache entries after completion.
|
|
256
|
+
*/
|
|
257
|
+
if (loadingPromise.releaseOnResolve) {
|
|
258
|
+
loadingPromisesByQuery.delete(query);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (run.deps.flushSync && message.onCompleteIds.length > 0) {
|
|
262
|
+
run.deps.flushSync(() => {
|
|
263
|
+
rowsByQueryMapStore.set(nextRowsByQueryMap);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
rowsByQueryMapStore.set(nextRowsByQueryMap);
|
|
268
|
+
}
|
|
269
|
+
for (const onCompleteId of message.onCompleteIds) {
|
|
270
|
+
onMutateCompleteCallbacks.execute(onCompleteId);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "RefreshQueries": {
|
|
275
|
+
releaseUnsubscribedLoadingPromises();
|
|
276
|
+
const queries = new Set([
|
|
277
|
+
...loadingPromisesByQuery.keys(),
|
|
278
|
+
...subscribedQueriesRefCount.keys(),
|
|
279
|
+
]);
|
|
280
|
+
if (isNonEmptySet(queries))
|
|
281
|
+
postMessage({ type: "Query", queries });
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case "OnExport": {
|
|
285
|
+
assert(exportDatabasePending, "OnExport received without pending export.");
|
|
286
|
+
exportDatabasePending.resolve(message.file);
|
|
287
|
+
exportDatabasePending = null;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
default:
|
|
291
|
+
exhaustiveCheck(message);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
sharedWorker.port.postMessage({
|
|
295
|
+
type: "CreateEvolu",
|
|
296
|
+
name,
|
|
297
|
+
evoluPort: evoluChannel.port2.native,
|
|
298
|
+
dbWorkerPort: dbWorkerChannel.port2.native,
|
|
299
|
+
}, [evoluChannel.port2.native, dbWorkerChannel.port2.native]);
|
|
300
|
+
/**
|
|
301
|
+
* No stack.use because Evolu instances don't dispose DbWorker because
|
|
302
|
+
* it's SharedWorder responsibility. DbWorker can be used by another tab.
|
|
303
|
+
* That's required because SQLite WASM needs a single web worker.
|
|
304
|
+
*/
|
|
305
|
+
createDbWorker().postMessage({
|
|
306
|
+
type: "Init",
|
|
307
|
+
name,
|
|
308
|
+
consoleLevel: console.getLevel(),
|
|
309
|
+
sqliteSchema: evoluSchemaToSqliteSchema(schema, config.indexes),
|
|
310
|
+
encryptionKey: appOwner.encryptionKey,
|
|
311
|
+
memoryOnly,
|
|
312
|
+
port: dbWorkerChannel.port1.native,
|
|
313
|
+
}, [dbWorkerChannel.port1.native]);
|
|
314
|
+
postMessage = evoluChannel.port1.postMessage;
|
|
315
|
+
stack.defer(() => {
|
|
316
|
+
postMessage({ type: "Dispose" });
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const createMutation = (kind) => (table, values, options) => {
|
|
320
|
+
assertNotDisposed(moved);
|
|
321
|
+
const { id = createId(run.deps), isDeleted, ...changeValues } = values;
|
|
322
|
+
const dbChange = {
|
|
323
|
+
table,
|
|
324
|
+
id,
|
|
325
|
+
values: changeValues,
|
|
326
|
+
isInsert: kind === "insert" || kind === "upsert",
|
|
327
|
+
isDelete: SqliteBoolean.is(isDeleted)
|
|
328
|
+
? sqliteBooleanToBoolean(isDeleted)
|
|
329
|
+
: null,
|
|
330
|
+
};
|
|
331
|
+
assert(DbChange.is(dbChange), `Invalid DbChange for table '${String(table)}'.`);
|
|
332
|
+
mutateBatch.push({
|
|
333
|
+
change: { ...dbChange, ownerId: options?.ownerId ?? appOwner.id },
|
|
334
|
+
onComplete: options?.onComplete,
|
|
335
|
+
});
|
|
336
|
+
return { id };
|
|
337
|
+
};
|
|
338
|
+
const loadQuery = (query) => {
|
|
339
|
+
assertNotDisposed(moved);
|
|
340
|
+
const loadingPromise = loadingPromisesByQuery.get(query);
|
|
341
|
+
if (loadingPromise) {
|
|
342
|
+
return loadingPromise.promise;
|
|
343
|
+
}
|
|
344
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
345
|
+
const typedPromise = promise;
|
|
346
|
+
typedPromise.status = "pending";
|
|
347
|
+
loadingPromisesByQuery.set(query, {
|
|
348
|
+
promise: typedPromise,
|
|
349
|
+
resolve,
|
|
350
|
+
releaseOnResolve: false,
|
|
351
|
+
});
|
|
352
|
+
queryBatch.push(query);
|
|
353
|
+
return typedPromise;
|
|
354
|
+
};
|
|
355
|
+
const getQueryRows = (query) => {
|
|
356
|
+
assertNotDisposed(moved);
|
|
357
|
+
return (rowsByQueryMapStore.get().get(query) ??
|
|
358
|
+
emptyArray);
|
|
359
|
+
};
|
|
360
|
+
const useOwner = (owner, ownerTransports) => {
|
|
361
|
+
assertNotDisposed(moved);
|
|
362
|
+
const effectiveTransports = ownerTransports ?? transports;
|
|
363
|
+
assertNonEmptyReadonlyArray(effectiveTransports, "useOwner requires explicit non-empty transports when config.transports is empty.");
|
|
364
|
+
const syncOwner = {
|
|
365
|
+
owner,
|
|
366
|
+
transports: effectiveTransports,
|
|
367
|
+
};
|
|
368
|
+
useOwnerBatch.push({
|
|
369
|
+
owner: syncOwner,
|
|
370
|
+
action: "add",
|
|
371
|
+
});
|
|
372
|
+
let isUsed = true;
|
|
373
|
+
return () => {
|
|
374
|
+
assert(isUsed, "UnuseOwner can be called only once.");
|
|
375
|
+
isUsed = false;
|
|
376
|
+
useOwnerBatch.push({
|
|
377
|
+
owner: syncOwner,
|
|
378
|
+
action: "remove",
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
const moved = stack.move();
|
|
383
|
+
if (isNonEmptyArray(transports))
|
|
384
|
+
useOwner(appOwner);
|
|
385
|
+
return ok({
|
|
386
|
+
name,
|
|
387
|
+
appOwner,
|
|
388
|
+
insert: createMutation("insert"),
|
|
389
|
+
update: createMutation("update"),
|
|
390
|
+
upsert: createMutation("upsert"),
|
|
391
|
+
loadQuery,
|
|
392
|
+
loadQueries: (queries) => queries.map((query) => loadQuery(query)),
|
|
393
|
+
subscribeQuery: (query) => (listener) => {
|
|
394
|
+
assertNotDisposed(moved);
|
|
395
|
+
subscribedQueriesRefCount.increment(query);
|
|
396
|
+
let isSubscribed = true;
|
|
397
|
+
let previousRows = null;
|
|
398
|
+
const unsubscribe = rowsByQueryMapStore.subscribe(() => {
|
|
399
|
+
const rows = getQueryRows(query);
|
|
400
|
+
if (previousRows === rows)
|
|
401
|
+
return;
|
|
402
|
+
previousRows = rows;
|
|
403
|
+
listener();
|
|
404
|
+
});
|
|
405
|
+
return () => {
|
|
406
|
+
assert(isSubscribed, "subscribeQuery unsubscribe can be called only once.");
|
|
407
|
+
isSubscribed = false;
|
|
408
|
+
previousRows = null;
|
|
409
|
+
unsubscribe();
|
|
410
|
+
if (moved.disposed)
|
|
411
|
+
return;
|
|
412
|
+
subscribedQueriesRefCount.decrement(query);
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
getQueryRows,
|
|
416
|
+
exportDatabase: () => {
|
|
417
|
+
assertNotDisposed(moved);
|
|
418
|
+
if (!exportDatabasePending) {
|
|
419
|
+
exportDatabasePending =
|
|
420
|
+
Promise.withResolvers();
|
|
421
|
+
postMessage({ type: "Export" });
|
|
422
|
+
}
|
|
423
|
+
return exportDatabasePending.promise;
|
|
424
|
+
},
|
|
425
|
+
deleteDatabase: () => {
|
|
426
|
+
assertNotDisposed(moved);
|
|
427
|
+
todo();
|
|
428
|
+
},
|
|
429
|
+
deleteOwner: (owner) => {
|
|
430
|
+
assertNotDisposed(moved);
|
|
431
|
+
void owner;
|
|
432
|
+
todo();
|
|
433
|
+
},
|
|
434
|
+
useOwner,
|
|
435
|
+
[Symbol.asyncDispose]: () => {
|
|
436
|
+
console.info("dispose");
|
|
437
|
+
return moved.disposeAsync();
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch (e_1) {
|
|
442
|
+
env_1.error = e_1;
|
|
443
|
+
env_1.hasError = true;
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
const result_1 = __disposeResources(env_1);
|
|
447
|
+
if (result_1)
|
|
448
|
+
await result_1;
|
|
449
|
+
}
|
|
440
450
|
};
|
|
451
|
+
// case "onReset": {
|
|
452
|
+
// if (message.reload) {
|
|
453
|
+
// deps.reloadApp(reloadUrl);
|
|
454
|
+
// } else {
|
|
455
|
+
// onCompleteCallbacks.execute(message.onCompleteId);
|
|
456
|
+
// }
|
|
457
|
+
// break;
|
|
458
|
+
// }
|
|
459
|
+
// resetAppOwner: (_options) => {
|
|
460
|
+
// const { promise, resolve } = Promise.withResolvers<undefined>();
|
|
461
|
+
// const _onCompleteId = onCompleteCallbacks.register(resolve);
|
|
462
|
+
// // dbWorker.postMessage({
|
|
463
|
+
// // type: "reset",
|
|
464
|
+
// // onCompleteId,
|
|
465
|
+
// // reload: options?.reload ?? true,
|
|
466
|
+
// // });
|
|
467
|
+
// return promise;
|
|
468
|
+
// },
|
|
469
|
+
// restoreAppOwner: (_mnemonic, _options) => {
|
|
470
|
+
// const { promise, resolve } = Promise.withResolvers<undefined>();
|
|
471
|
+
// const _onCompleteId = onCompleteCallbacks.register(resolve);
|
|
472
|
+
// // dbWorker.postMessage({
|
|
473
|
+
// // type: "reset",
|
|
474
|
+
// // onCompleteId,
|
|
475
|
+
// // reload: options?.reload ?? true,
|
|
476
|
+
// // restore: { mnemonic, sqliteSchema },
|
|
477
|
+
// // });
|
|
478
|
+
// return promise;
|
|
479
|
+
// },
|
|
480
|
+
// reloadApp: () => {
|
|
481
|
+
// // TODO:
|
|
482
|
+
// // deps.reloadApp(reloadUrl);
|
|
483
|
+
// },
|
|
484
|
+
// ensureSchema: (schema) => {
|
|
485
|
+
// mutationTypesCache.clear();
|
|
486
|
+
// const sqliteSchema = evoluSchemaToSqliteSchema(schema);
|
|
487
|
+
// dbWorker.postMessage({ type: "ensureSqliteSchema", sqliteSchema });
|
|
488
|
+
// },
|
|
489
|
+
// /**
|
|
490
|
+
// * Delete {@link AppOwner} and all their data from the current device. After
|
|
491
|
+
// * the deletion, Evolu will purge the application state. For browsers, this
|
|
492
|
+
// * will reload all tabs using Evolu. For native apps, it will restart the
|
|
493
|
+
// * app.
|
|
494
|
+
// *
|
|
495
|
+
// * Reloading can be turned off via options if you want to provide a different
|
|
496
|
+
// * UX.
|
|
497
|
+
// */
|
|
498
|
+
// readonly resetAppOwner: (options?: {
|
|
499
|
+
// readonly reload?: boolean;
|
|
500
|
+
// }) => Promise<void>;
|
|
501
|
+
// /**
|
|
502
|
+
// * Restore {@link AppOwner} with all their synced data. It uses
|
|
503
|
+
// * {@link Evolu.resetAppOwner}, so be careful.
|
|
504
|
+
// */
|
|
505
|
+
// readonly restoreAppOwner: (
|
|
506
|
+
// mnemonic: Mnemonic,
|
|
507
|
+
// options?: {
|
|
508
|
+
// readonly reload?: boolean;
|
|
509
|
+
// },
|
|
510
|
+
// ) => Promise<void>;
|