@evolu/common 7.4.1 → 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 +851 -430
- 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 -824
- 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 +206 -277
- 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 -526
- package/src/Instances.ts +0 -92
- 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 -949
package/src/local-first/Evolu.ts
CHANGED
|
@@ -1,80 +1,199 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Local-first platform.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
2
7
|
import {
|
|
3
|
-
|
|
8
|
+
emptyArray,
|
|
4
9
|
isNonEmptyArray,
|
|
5
|
-
|
|
10
|
+
mapArray,
|
|
11
|
+
type NonEmptyReadonlyArray,
|
|
6
12
|
} from "../Array.js";
|
|
7
|
-
import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
8
|
-
import { createCallbacks } from "../Callbacks.js";
|
|
9
|
-
import { ConsoleDep } from "../Console.js";
|
|
10
|
-
import { RandomBytesDep, SymmetricCryptoDecryptError } from "../Crypto.js";
|
|
11
|
-
import { eqArrayNumber } from "../Eq.js";
|
|
12
|
-
import { TransferableError } from "../Error.js";
|
|
13
|
-
import { exhaustiveCheck } from "../Function.js";
|
|
14
|
-
import { createInstances, Instances } from "../Instances.js";
|
|
15
|
-
import { err, ok, Result } from "../Result.js";
|
|
16
13
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "../
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
14
|
+
assert,
|
|
15
|
+
assertNonEmptyReadonlyArray,
|
|
16
|
+
assertNotDisposed,
|
|
17
|
+
} from "../Assert.js";
|
|
18
|
+
import { createCallbacks } from "../Callbacks.js";
|
|
19
|
+
import type { ConsoleDep } from "../Console.js";
|
|
20
|
+
import { createConsole } from "../Console.js";
|
|
21
|
+
import { createUnknownError } from "../Error.js";
|
|
22
|
+
import { exhaustiveCheck, todo } from "../Function.js";
|
|
23
|
+
import { createMicrotaskBatch } from "../Microtask.js";
|
|
24
|
+
import type { FlushSyncDep, ReloadAppDep } from "../Platform.js";
|
|
25
|
+
import { createRefCountByKey } from "../RefCount.js";
|
|
26
|
+
import { err, ok } from "../Result.js";
|
|
27
|
+
import { isNonEmptySet } from "../Set.js";
|
|
28
|
+
import { SqliteBoolean, sqliteBooleanToBoolean } from "../Sqlite.js";
|
|
29
|
+
import type { Listener, ReadonlyStore, Unsubscribe } from "../Store.js";
|
|
30
|
+
import { createStore } from "../Store.js";
|
|
31
|
+
import { type createRun, type Task } from "../Task.js";
|
|
32
|
+
import type { Id, TypeError } from "../Type.js";
|
|
26
33
|
import {
|
|
34
|
+
brand,
|
|
27
35
|
createId,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
InferType,
|
|
32
|
-
Mnemonic,
|
|
33
|
-
ObjectType,
|
|
34
|
-
SimpleName,
|
|
35
|
-
ValidMutationSize,
|
|
36
|
-
ValidMutationSizeError,
|
|
36
|
+
createIdFromString,
|
|
37
|
+
Name,
|
|
38
|
+
UrlSafeString,
|
|
37
39
|
} from "../Type.js";
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
import type { ExtractType } from "../Types.js";
|
|
41
|
+
import type { CreateMessageChannelDep } from "../Worker.js";
|
|
42
|
+
import type { CreateDbWorkerDep } from "./Db.js";
|
|
43
|
+
import type { EvoluError } from "./Error.js";
|
|
44
|
+
import type {
|
|
45
|
+
AppOwner,
|
|
46
|
+
Owner,
|
|
47
|
+
OwnerId,
|
|
48
|
+
OwnerTransport,
|
|
49
|
+
ReadonlyOwner,
|
|
50
|
+
SyncOwner,
|
|
51
|
+
} from "./Owner.js";
|
|
52
|
+
import { createOwnerWebSocketTransport } from "./Owner.js";
|
|
53
|
+
import type {
|
|
47
54
|
Queries,
|
|
48
55
|
QueriesToQueryRowsPromises,
|
|
49
56
|
Query,
|
|
50
57
|
QueryRows,
|
|
51
|
-
QueryRowsMap,
|
|
52
58
|
Row,
|
|
53
|
-
|
|
54
|
-
SubscribedQueries,
|
|
59
|
+
RowsByQueryMap,
|
|
55
60
|
} from "./Query.js";
|
|
56
|
-
import {
|
|
57
|
-
|
|
61
|
+
import { applyPatches } from "./Query.js";
|
|
62
|
+
import type {
|
|
58
63
|
EvoluSchema,
|
|
59
|
-
evoluSchemaToDbSchema,
|
|
60
64
|
IndexesConfig,
|
|
61
|
-
insertable,
|
|
62
|
-
kysely,
|
|
63
65
|
Mutation,
|
|
64
66
|
MutationChange,
|
|
65
|
-
MutationKind,
|
|
66
|
-
MutationMapping,
|
|
67
|
-
MutationOptions,
|
|
68
|
-
SystemColumns,
|
|
69
|
-
updateable,
|
|
70
|
-
upsertable,
|
|
71
67
|
ValidateSchema,
|
|
72
68
|
} from "./Schema.js";
|
|
69
|
+
import { evoluSchemaToSqliteSchema } from "./Schema.js";
|
|
70
|
+
import type {
|
|
71
|
+
DbWorkerInput,
|
|
72
|
+
DbWorkerOutput,
|
|
73
|
+
EvoluInput,
|
|
74
|
+
EvoluOutput,
|
|
75
|
+
EvoluTabOutput,
|
|
76
|
+
SharedWorkerDep,
|
|
77
|
+
} from "./Shared.js";
|
|
73
78
|
import { DbChange } from "./Storage.js";
|
|
74
|
-
import {
|
|
75
|
-
|
|
79
|
+
import type { Timestamp } from "./Timestamp.js";
|
|
80
|
+
|
|
81
|
+
export interface EvoluConfig {
|
|
82
|
+
/**
|
|
83
|
+
* The app name. Evolu is multitenant - it can run multiple instances
|
|
84
|
+
* concurrently. The same app can have multiple instances for different
|
|
85
|
+
* accounts.
|
|
86
|
+
*
|
|
87
|
+
* Evolu derives the final instance name from `appName` and `appOwner` in
|
|
88
|
+
* {@link EvoluConfig}. The derived instance name is used as the SQLite
|
|
89
|
+
* database filename and as the log prefix. This ensures that each
|
|
90
|
+
* {@link Owner} gets a separate local database while preserving a readable app
|
|
91
|
+
* prefix.
|
|
92
|
+
*
|
|
93
|
+
* ### Example
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* // appName: AppName.orThrow("MyApp")
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
readonly appName: AppName;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* {@link AppOwner} used to create this {@link Evolu} instance.
|
|
103
|
+
*
|
|
104
|
+
* Exposed as {@link Evolu.appOwner}. If `appOwner` is not passed, Evolu
|
|
105
|
+
* creates one.
|
|
106
|
+
*
|
|
107
|
+
* AppOwner controls access to the encrypted local SQLite database. If its
|
|
108
|
+
* secret material (Owner secret / Mnemonic) is not stored safely, data
|
|
109
|
+
* written by that instance is permanently inaccessible.
|
|
110
|
+
*
|
|
111
|
+
* Best onboarding UX is accountless first use: let users try a ready-to-use
|
|
112
|
+
* app, then prompt backup of `evolu.appOwner`.
|
|
113
|
+
*
|
|
114
|
+
* Recommended usage:
|
|
115
|
+
*
|
|
116
|
+
* - Omit `appOwner` for first run, then persist `evolu.appOwner` after user
|
|
117
|
+
* activity and guide the user to back it up.
|
|
118
|
+
* - Pass `appOwner` restored from secure storage (for example, Expo
|
|
119
|
+
* SecureStore, WebAuthn-backed storage, or app-managed account recovery
|
|
120
|
+
* flow).
|
|
121
|
+
*/
|
|
122
|
+
readonly appOwner: AppOwner;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Transport configuration for sync and backup.
|
|
126
|
+
*
|
|
127
|
+
* If not specified, Evolu uses the default Evolu relay. Pass one or more
|
|
128
|
+
* transports to override it with your own relays. Pass an empty array to
|
|
129
|
+
* disable sync, which is useful when sync should be configured later.
|
|
130
|
+
*
|
|
131
|
+
* Empty transports start the instance without sync. In that case,
|
|
132
|
+
* {@link Evolu.useOwner} must be called with explicit non-empty transports to
|
|
133
|
+
* enable sync for any Owner, including the AppOwner.
|
|
134
|
+
*
|
|
135
|
+
* **Redundancy:** The ideal setup uses at least two completely independent
|
|
136
|
+
* relays - for example, a home relay and a geographically separate relay.
|
|
137
|
+
* Data is sent to both relays simultaneously, providing true redundancy
|
|
138
|
+
* similar to using two independent clouds. This eliminates vendor lock-in and
|
|
139
|
+
* ensures your app continues working regardless of circumstances - whether
|
|
140
|
+
* home relay hardware fails or disappears, or a remote relay provider shuts
|
|
141
|
+
* down.
|
|
142
|
+
*
|
|
143
|
+
* Currently supports:
|
|
144
|
+
*
|
|
145
|
+
* - WebSocket: Real-time bidirectional communication with relay servers
|
|
146
|
+
*
|
|
147
|
+
* Use {@link createOwnerWebSocketTransport} to create WebSocket transport
|
|
148
|
+
* configurations with proper URL formatting and {@link OwnerId} inclusion. The
|
|
149
|
+
* {@link OwnerId} in the URL enables relay authentication, allowing relay
|
|
150
|
+
* servers to control access (e.g., for paid tiers or private instances).
|
|
151
|
+
*
|
|
152
|
+
* The default value is:
|
|
153
|
+
*
|
|
154
|
+
* `{ type: "WebSocket", url: "wss://free.evoluhq.com" }`.
|
|
155
|
+
*
|
|
156
|
+
* ### Example
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* // Single WebSocket relay
|
|
160
|
+
* transports: [{ type: "WebSocket", url: "wss://relay1.example.com" }];
|
|
161
|
+
*
|
|
162
|
+
* // Multiple WebSocket relays for redundancy
|
|
163
|
+
* transports: [
|
|
164
|
+
* { type: "WebSocket", url: "wss://relay1.example.com" },
|
|
165
|
+
* { type: "WebSocket", url: "wss://relay2.example.com" },
|
|
166
|
+
* { type: "WebSocket", url: "wss://relay3.example.com" },
|
|
167
|
+
* ];
|
|
168
|
+
*
|
|
169
|
+
* // Local-only instance (no sync) - useful for device settings or when relay
|
|
170
|
+
* // URL will be provided later (e.g., after authentication), allowing users
|
|
171
|
+
* // to work offline before the app connects
|
|
172
|
+
* transports: [];
|
|
173
|
+
*
|
|
174
|
+
* // Using createOwnerWebSocketTransport helper for relay authentication
|
|
175
|
+
* transports: [
|
|
176
|
+
* createOwnerWebSocketTransport({
|
|
177
|
+
* url: "ws://localhost:4000",
|
|
178
|
+
* ownerId,
|
|
179
|
+
* }),
|
|
180
|
+
* ];
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
readonly transports?: ReadonlyArray<OwnerTransport>;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Keep local data only in memory instead of persisting it on this device.
|
|
187
|
+
* Useful for testing, temporary data, or sensitive data that should not be
|
|
188
|
+
* recoverable from local storage after the process ends.
|
|
189
|
+
*
|
|
190
|
+
* Local data stored in memory is completely destroyed when the process ends.
|
|
191
|
+
* Sync can still persist data remotely when transports are enabled.
|
|
192
|
+
*
|
|
193
|
+
* The default value is: `false`.
|
|
194
|
+
*/
|
|
195
|
+
readonly memoryOnly?: boolean;
|
|
76
196
|
|
|
77
|
-
export interface EvoluConfig extends Partial<DbConfig> {
|
|
78
197
|
/**
|
|
79
198
|
* Use the `indexes` option to define SQLite indexes.
|
|
80
199
|
*
|
|
@@ -98,79 +217,153 @@ export interface EvoluConfig extends Partial<DbConfig> {
|
|
|
98
217
|
readonly indexes?: IndexesConfig;
|
|
99
218
|
|
|
100
219
|
/**
|
|
101
|
-
*
|
|
220
|
+
* Called when this instance's local database is deleted.
|
|
102
221
|
*
|
|
103
|
-
*
|
|
222
|
+
* Apps can use this to update UI immediately because the corresponding
|
|
223
|
+
* {@link Evolu} instance becomes unusable after local database deletion.
|
|
224
|
+
*/
|
|
225
|
+
readonly onDatabaseDeleted?: () => void;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Called when local data for an {@link Owner} is deleted.
|
|
104
229
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
230
|
+
* Apps can use this to update UI immediately because that owner stops being
|
|
231
|
+
* used across tabs and instances.
|
|
107
232
|
*/
|
|
108
|
-
readonly
|
|
233
|
+
readonly onOwnerDeleted?: (owner: Owner) => void;
|
|
109
234
|
}
|
|
110
235
|
|
|
111
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Application name.
|
|
238
|
+
*
|
|
239
|
+
* Evolu uses AppName as the base prefix for {@link Evolu.name}. The final
|
|
240
|
+
* instance name is derived per {@link AppOwner} as
|
|
241
|
+
* `${appName}-${createIdFromString(appOwner.id)}`.
|
|
242
|
+
*
|
|
243
|
+
* Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
|
|
244
|
+
* `_`) and must be between 1 and 41 characters.
|
|
245
|
+
*/
|
|
246
|
+
export const AppName = /*#__PURE__*/ brand("AppName", UrlSafeString, (value) =>
|
|
247
|
+
value.length >= 1 && value.length <= 41
|
|
248
|
+
? ok(value)
|
|
249
|
+
: err<AppNameError>({ type: "AppName", value }),
|
|
250
|
+
);
|
|
251
|
+
export type AppName = typeof AppName.Type;
|
|
252
|
+
export interface AppNameError extends TypeError<"AppName"> {}
|
|
253
|
+
|
|
254
|
+
export const testAppName = /*#__PURE__*/ AppName.orThrow("AppName");
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Local-first SQL database with typed queries, mutations, and sync.
|
|
258
|
+
*
|
|
259
|
+
* TODO: Better docs.
|
|
260
|
+
*/
|
|
261
|
+
export interface Evolu<
|
|
262
|
+
S extends EvoluSchema = EvoluSchema,
|
|
263
|
+
> extends AsyncDisposable {
|
|
264
|
+
/**
|
|
265
|
+
* Evolu instance name is derived from {@link EvoluConfig.appName} and
|
|
266
|
+
* {@link AppOwner}'s hash.
|
|
267
|
+
*/
|
|
268
|
+
readonly name: Name;
|
|
269
|
+
|
|
270
|
+
/** {@link AppOwner}. */
|
|
271
|
+
readonly appOwner: AppOwner;
|
|
272
|
+
|
|
112
273
|
/**
|
|
113
|
-
*
|
|
274
|
+
* Inserts a row and returns the generated {@link Id}.
|
|
275
|
+
*
|
|
276
|
+
* All non-nullable columns are required, nullable columns are optional, and
|
|
277
|
+
* `id` is omitted because Evolu generates it automatically. This ensures
|
|
278
|
+
* every row has a globally unique, conflict-free identifier without
|
|
279
|
+
* coordination.
|
|
114
280
|
*
|
|
115
281
|
* ### Example
|
|
116
282
|
*
|
|
117
283
|
* ```ts
|
|
118
|
-
* const
|
|
119
|
-
*
|
|
120
|
-
* console.log(error);
|
|
284
|
+
* const { id } = evolu.insert("todo", {
|
|
285
|
+
* title: NonEmptyString100.orThrow("Learn Evolu"),
|
|
121
286
|
* });
|
|
287
|
+
*
|
|
288
|
+
* // With onComplete callback
|
|
289
|
+
* evolu.insert(
|
|
290
|
+
* "todo",
|
|
291
|
+
* { title: NonEmptyString100.orThrow("Another todo") },
|
|
292
|
+
* { onComplete: () => console.log("Insert completed") },
|
|
293
|
+
* );
|
|
122
294
|
* ```
|
|
295
|
+
*
|
|
296
|
+
* @see {@link Mutation}
|
|
123
297
|
*/
|
|
124
|
-
readonly
|
|
125
|
-
|
|
126
|
-
/** Get {@link EvoluError}. */
|
|
127
|
-
readonly getError: () => EvoluError | null;
|
|
298
|
+
readonly insert: Mutation<S, "insert">;
|
|
128
299
|
|
|
129
300
|
/**
|
|
130
|
-
*
|
|
301
|
+
* Updates a row and returns the {@link Id}.
|
|
131
302
|
*
|
|
132
|
-
*
|
|
133
|
-
* https://kysely.dev.
|
|
303
|
+
* Only `id` is required, all other columns are optional.
|
|
134
304
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
305
|
+
* ### Example
|
|
306
|
+
*
|
|
307
|
+
* ```ts
|
|
308
|
+
* const { id } = evolu.update("todo", {
|
|
309
|
+
* id: todoId,
|
|
310
|
+
* title: NonEmptyString100.orThrow("Updated title"),
|
|
311
|
+
* });
|
|
137
312
|
*
|
|
138
|
-
*
|
|
139
|
-
* {
|
|
313
|
+
* // Soft delete
|
|
314
|
+
* evolu.update("todo", { id: todoId, isDeleted: sqliteTrue });
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @see {@link Mutation}
|
|
318
|
+
*/
|
|
319
|
+
readonly update: Mutation<S, "update">;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Upserts a row and returns the {@link Id}.
|
|
323
|
+
*
|
|
324
|
+
* Like insert, but requires an `id`. Useful for rows with external ID via
|
|
325
|
+
* {@link createIdFromString}. All other non-nullable columns are required,
|
|
326
|
+
* nullable columns are optional.
|
|
327
|
+
*
|
|
328
|
+
* `createdAt` and `updatedAt` cannot be set manually. Evolu derives both from
|
|
329
|
+
* the CRDT {@link Timestamp} — they share the same value, encoded once to
|
|
330
|
+
* avoid redundancy. Timestamps are always generated by Evolu to preserve CRDT
|
|
331
|
+
* consistency guarantees.
|
|
140
332
|
*
|
|
141
333
|
* ### Example
|
|
142
334
|
*
|
|
143
335
|
* ```ts
|
|
144
|
-
* const
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* evolu.createQuery((db) =>
|
|
150
|
-
* db.selectFrom("todo").selectAll().where("id", "=", id),
|
|
151
|
-
* );
|
|
336
|
+
* const stableId = createIdFromString("my-todo-1");
|
|
337
|
+
* const { id } = evolu.upsert("todo", {
|
|
338
|
+
* id: stableId,
|
|
339
|
+
* title: NonEmptyString100.orThrow("Learn Evolu"),
|
|
340
|
+
* });
|
|
152
341
|
* ```
|
|
342
|
+
*
|
|
343
|
+
* @see {@link Mutation}
|
|
153
344
|
*/
|
|
154
|
-
readonly
|
|
345
|
+
readonly upsert: Mutation<S, "upsert">;
|
|
155
346
|
|
|
156
347
|
/**
|
|
157
348
|
* Load {@link Query} and return a promise with {@link QueryRows}.
|
|
158
349
|
*
|
|
159
350
|
* The returned promise always resolves successfully because there is no
|
|
160
|
-
* reason why loading should fail. All data are local, and the query is
|
|
161
|
-
*
|
|
351
|
+
* reason why loading should fail. All data are local, and the query is
|
|
352
|
+
* typed.
|
|
162
353
|
*
|
|
163
|
-
* Loading is batched
|
|
164
|
-
*
|
|
165
|
-
*
|
|
354
|
+
* Loading is batched. Returned promises are cached while pending and can be
|
|
355
|
+
* reused after fulfillment until mutation-driven invalidation, which prevents
|
|
356
|
+
* redundant database queries and supports React Suspense (stable references
|
|
357
|
+
* while pending).
|
|
166
358
|
*
|
|
167
359
|
* To subscribe a query for automatic updates, use
|
|
168
|
-
* {@link Evolu
|
|
360
|
+
* {@link Evolu.subscribeQuery}.
|
|
169
361
|
*
|
|
170
362
|
* ### Example
|
|
171
363
|
*
|
|
172
364
|
* ```ts
|
|
173
|
-
* const
|
|
365
|
+
* const createQuery = createQueryBuilder(Schema);
|
|
366
|
+
* const allTodos = createQuery((db) =>
|
|
174
367
|
* db.selectFrom("todo").selectAll(),
|
|
175
368
|
* );
|
|
176
369
|
* evolu.loadQuery(allTodos).then((rows) => {
|
|
@@ -178,7 +371,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
178
371
|
* });
|
|
179
372
|
* ```
|
|
180
373
|
*/
|
|
181
|
-
readonly loadQuery: <R extends Row>(
|
|
374
|
+
readonly loadQuery: <R extends Row>(
|
|
375
|
+
query: Query<S, R>,
|
|
376
|
+
) => Promise<QueryRows<R>>;
|
|
182
377
|
|
|
183
378
|
/**
|
|
184
379
|
* Load an array of {@link Query} queries and return an array of
|
|
@@ -191,7 +386,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
191
386
|
* evolu.loadQueries([allTodos, todoById(1)]);
|
|
192
387
|
* ```
|
|
193
388
|
*/
|
|
194
|
-
readonly loadQueries: <
|
|
389
|
+
readonly loadQueries: <Q extends Queries<S>>(
|
|
195
390
|
queries: [...Q],
|
|
196
391
|
) => [...QueriesToQueryRowsPromises<Q>];
|
|
197
392
|
|
|
@@ -206,7 +401,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
206
401
|
* });
|
|
207
402
|
* ```
|
|
208
403
|
*/
|
|
209
|
-
readonly subscribeQuery: (
|
|
404
|
+
readonly subscribeQuery: (
|
|
405
|
+
query: Query<S>,
|
|
406
|
+
) => (listener: Listener) => Unsubscribe;
|
|
210
407
|
|
|
211
408
|
/**
|
|
212
409
|
* Get {@link QueryRows}.
|
|
@@ -219,864 +416,727 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
219
416
|
* });
|
|
220
417
|
* ```
|
|
221
418
|
*/
|
|
222
|
-
readonly getQueryRows: <R extends Row>(query: Query<R>) => QueryRows<R>;
|
|
419
|
+
readonly getQueryRows: <R extends Row>(query: Query<S, R>) => QueryRows<R>;
|
|
223
420
|
|
|
224
421
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* Note: With web-only deps, this promise will not resolve during SSR because
|
|
228
|
-
* there is no AppOwner on the server.
|
|
422
|
+
* Exports the SQLite database file.
|
|
229
423
|
*
|
|
230
|
-
*
|
|
424
|
+
* Exports are sequential: concurrent calls share one pending export instead
|
|
425
|
+
* of starting parallel exports.
|
|
231
426
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* ```
|
|
427
|
+
* The pending promise rejects if this {@link Evolu} instance is disposed
|
|
428
|
+
* before export completion.
|
|
235
429
|
*/
|
|
236
|
-
readonly
|
|
430
|
+
readonly exportDatabase: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
431
|
+
|
|
432
|
+
// TODO: Add exportHistory.
|
|
237
433
|
|
|
238
434
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* The first argument is the table name, and the second is an object
|
|
243
|
-
* containing the row data. An optional third argument provides mutation
|
|
244
|
-
* options including an `onComplete` callback and `onlyValidate` flag.
|
|
435
|
+
* Delete the local SQLite database for this {@link Evolu} instance on the
|
|
436
|
+
* current device.
|
|
245
437
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* Evolu does not use SQL for mutations to ensure data can be safely and
|
|
251
|
-
* predictably merged without conflicts. Explicit mutations also allow Evolu
|
|
252
|
-
* to automatically update {@link SystemColumns}.
|
|
253
|
-
*
|
|
254
|
-
* ### Example
|
|
255
|
-
*
|
|
256
|
-
* ```ts
|
|
257
|
-
* const result = evolu.insert("todo", {
|
|
258
|
-
* title: "Learn Evolu",
|
|
259
|
-
* isCompleted: false,
|
|
260
|
-
* });
|
|
438
|
+
* **Warning**: This first drops all tables. “The dropped table is completely
|
|
439
|
+
* removed from the database schema and the disk file. The table can not be
|
|
440
|
+
* recovered. All indices and triggers associated with the table are also
|
|
441
|
+
* deleted.” https://sqlite.org/lang_droptable.html
|
|
261
442
|
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* } else {
|
|
265
|
-
* console.error("Validation error:", result.error);
|
|
266
|
-
* }
|
|
443
|
+
* After that, it deletes the SQLite file identified by {@link Evolu.name},
|
|
444
|
+
* permanently forgetting all local data for that instance on this device.
|
|
267
445
|
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* "todo",
|
|
271
|
-
* { title: "Another todo" },
|
|
272
|
-
* {
|
|
273
|
-
* onComplete: () => {
|
|
274
|
-
* console.log("Insert completed");
|
|
275
|
-
* },
|
|
276
|
-
* },
|
|
277
|
-
* );
|
|
278
|
-
* ```
|
|
446
|
+
* All instances identified by {@link Evolu.name} will be self-disposed. Use
|
|
447
|
+
* {@link EvoluConfig.onDatabaseDeleted} to update app UI when that happens.
|
|
279
448
|
*/
|
|
280
|
-
|
|
449
|
+
readonly deleteDatabase: () => void;
|
|
281
450
|
|
|
282
451
|
/**
|
|
283
|
-
*
|
|
284
|
-
* {@link Id}.
|
|
285
|
-
*
|
|
286
|
-
* The first argument is the table name, and the second is an object
|
|
287
|
-
* containing the row data including the required `id` field. An optional
|
|
288
|
-
* third argument provides mutation options including an `onComplete` callback
|
|
289
|
-
* and `onlyValidate` flag.
|
|
452
|
+
* Delete all local data for a specific {@link Owner}.
|
|
290
453
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* errors.
|
|
454
|
+
* This deletes all rows whose `ownerId` matches the provided owner and
|
|
455
|
+
* deletes that owner's local history as well.
|
|
294
456
|
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* to automatically update {@link SystemColumns}.
|
|
298
|
-
*
|
|
299
|
-
* ### Example
|
|
300
|
-
*
|
|
301
|
-
* ```ts
|
|
302
|
-
* const result = evolu.update("todo", {
|
|
303
|
-
* id: todoId,
|
|
304
|
-
* title: "Updated title",
|
|
305
|
-
* isCompleted: true,
|
|
306
|
-
* });
|
|
307
|
-
*
|
|
308
|
-
* if (result.ok) {
|
|
309
|
-
* console.log("Todo updated with ID:", result.value.id);
|
|
310
|
-
* } else {
|
|
311
|
-
* console.error("Validation error:", result.error);
|
|
312
|
-
* }
|
|
313
|
-
*
|
|
314
|
-
* // To delete a row, set isDeleted to true
|
|
315
|
-
* evolu.update("todo", { id: todoId, isDeleted: true });
|
|
316
|
-
*
|
|
317
|
-
* // With onComplete callback
|
|
318
|
-
* evolu.update(
|
|
319
|
-
* "todo",
|
|
320
|
-
* { id: todoId, title: "New title" },
|
|
321
|
-
* {
|
|
322
|
-
* onComplete: () => {
|
|
323
|
-
* console.log("Update completed");
|
|
324
|
-
* },
|
|
325
|
-
* },
|
|
326
|
-
* );
|
|
327
|
-
* ```
|
|
457
|
+
* It also stops using that owner across all tabs and instances. Use
|
|
458
|
+
* {@link EvoluConfig.onOwnerDeleted} to update app UI when that happens.
|
|
328
459
|
*/
|
|
329
|
-
|
|
460
|
+
readonly deleteOwner: (owner: Owner) => void;
|
|
330
461
|
|
|
331
462
|
/**
|
|
332
|
-
*
|
|
333
|
-
* {@link Id}.
|
|
334
|
-
*
|
|
335
|
-
* The first argument is the table name, and the second is an object
|
|
336
|
-
* containing the row data including the required `id` field. An optional
|
|
337
|
-
* third argument provides mutation options including an `onComplete` callback
|
|
338
|
-
* and `onlyValidate` flag.
|
|
463
|
+
* Use an Owner for sync. Returns a {@link UnuseOwner}.
|
|
339
464
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
465
|
+
* Using an Owner means syncing it with the provided transports, or with the
|
|
466
|
+
* default transports defined in {@link EvoluConfig} when transports are
|
|
467
|
+
* omitted.
|
|
343
468
|
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* errors.
|
|
469
|
+
* If {@link EvoluConfig.transports} is an empty array, this method must be
|
|
470
|
+
* called with explicit non-empty transports.
|
|
347
471
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* to automatically update {@link SystemColumns}.
|
|
472
|
+
* Transports are automatically deduplicated and reference-counted, so
|
|
473
|
+
* multiple Owners using the same transport will share a single connection.
|
|
351
474
|
*
|
|
352
475
|
* ### Example
|
|
353
476
|
*
|
|
354
477
|
* ```ts
|
|
355
|
-
* // Use
|
|
356
|
-
* const
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* });
|
|
478
|
+
* // Use an Owner (starts syncing).
|
|
479
|
+
* const unuseOwner = evolu.useOwner(shardOwner, [
|
|
480
|
+
* createOwnerWebSocketTransport({
|
|
481
|
+
* url: "ws://localhost:4000",
|
|
482
|
+
* ownerId: shardOwner.id,
|
|
483
|
+
* }),
|
|
484
|
+
* ]);
|
|
363
485
|
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* } else {
|
|
367
|
-
* console.error("Validation error:", result.error);
|
|
368
|
-
* }
|
|
369
|
-
*
|
|
370
|
-
* // Data migration with custom createdAt
|
|
371
|
-
* evolu.upsert("todo", {
|
|
372
|
-
* id: externalId,
|
|
373
|
-
* title: "Migrated todo",
|
|
374
|
-
* createdAt: new Date("2023-01-01"), // Preserve original timestamp
|
|
375
|
-
* });
|
|
486
|
+
* // Later, stop using the Owner.
|
|
487
|
+
* unuseOwner();
|
|
376
488
|
*
|
|
377
|
-
* //
|
|
378
|
-
* evolu.
|
|
379
|
-
*
|
|
380
|
-
* { id: stableId, title: "Updated title" },
|
|
381
|
-
* {
|
|
382
|
-
* onComplete: () => {
|
|
383
|
-
* console.log("Upsert completed");
|
|
384
|
-
* },
|
|
385
|
-
* },
|
|
386
|
-
* );
|
|
489
|
+
* // Bulk operations.
|
|
490
|
+
* const unuseOwners = owners.map(evolu.useOwner);
|
|
491
|
+
* // Later: for (const unuse of unuseOwners) unuse();
|
|
387
492
|
* ```
|
|
388
493
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
* will reload all tabs using Evolu. For native apps, it will restart the
|
|
395
|
-
* app.
|
|
396
|
-
*
|
|
397
|
-
* Reloading can be turned off via options if you want to provide a different
|
|
398
|
-
* UX.
|
|
399
|
-
*/
|
|
400
|
-
readonly resetAppOwner: (options?: {
|
|
401
|
-
readonly reload?: boolean;
|
|
402
|
-
}) => Promise<void>;
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Restore {@link AppOwner} with all their synced data. It uses
|
|
406
|
-
* {@link Evolu#resetAppOwner}, so be careful.
|
|
407
|
-
*/
|
|
408
|
-
readonly restoreAppOwner: (
|
|
409
|
-
mnemonic: Mnemonic,
|
|
410
|
-
options?: {
|
|
411
|
-
readonly reload?: boolean;
|
|
412
|
-
},
|
|
413
|
-
) => Promise<void>;
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Reload the app in a platform-specific way. For browsers, this will reload
|
|
417
|
-
* all tabs using Evolu. For native apps, it will restart the app.
|
|
418
|
-
*/
|
|
419
|
-
readonly reloadApp: () => void;
|
|
494
|
+
readonly useOwner: (
|
|
495
|
+
owner: ReadonlyOwner | Owner,
|
|
496
|
+
transports?: NonEmptyReadonlyArray<OwnerTransport>,
|
|
497
|
+
) => UnuseOwner;
|
|
498
|
+
}
|
|
420
499
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
*
|
|
424
|
-
* In the future, it will be possible to import a database and export/import
|
|
425
|
-
* history for 1:1 migrations across owners.
|
|
426
|
-
*/
|
|
427
|
-
readonly exportDatabase: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
500
|
+
/** Function returned by {@link Evolu.useOwner} to stop using an Owner for sync. */
|
|
501
|
+
export type UnuseOwner = () => void;
|
|
428
502
|
|
|
503
|
+
export interface EvoluErrorDep {
|
|
429
504
|
/**
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
* Using an owner means syncing it with its transports, or the transports
|
|
433
|
-
* defined in Evolu config if the owner has no transports defined.
|
|
505
|
+
* {@link ReadonlyStore} of {@link EvoluError} shared by all {@link Evolu}
|
|
506
|
+
* instances created from the same {@link createEvoluDeps} result.
|
|
434
507
|
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
508
|
+
* Subscribe once to show user-facing messages across all instances. Logging
|
|
509
|
+
* is handled by platform {@link createRun} global error handlers.
|
|
437
510
|
*
|
|
438
511
|
* ### Example
|
|
439
512
|
*
|
|
440
513
|
* ```ts
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* // Later, stop using the owner.
|
|
445
|
-
* unuseOwner();
|
|
514
|
+
* deps.evoluError.subscribe(() => {
|
|
515
|
+
* const error = deps.evoluError.get();
|
|
516
|
+
* if (!error) return;
|
|
446
517
|
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
518
|
+
* switch (error.type) {
|
|
519
|
+
* case "InvalidComputerClock":
|
|
520
|
+
* // Show guidance specific to the detected error.
|
|
521
|
+
* showMessage(
|
|
522
|
+
* "Your system clock appears incorrect. Please fix it.",
|
|
523
|
+
* );
|
|
524
|
+
* break;
|
|
525
|
+
* default:
|
|
526
|
+
* // Show a generic user message for other operational errors.
|
|
527
|
+
* showMessage("Something went wrong. Please try again.");
|
|
528
|
+
* }
|
|
529
|
+
* });
|
|
450
530
|
* ```
|
|
451
|
-
*
|
|
452
|
-
* @experimental
|
|
453
531
|
*/
|
|
454
|
-
readonly
|
|
532
|
+
readonly evoluError: ReadonlyStore<EvoluError | null>;
|
|
455
533
|
}
|
|
456
534
|
|
|
457
|
-
/** Function returned by {@link Evolu#useOwner} to stop using an {@link SyncOwner}. */
|
|
458
|
-
export type UnuseOwner = () => void;
|
|
459
|
-
|
|
460
|
-
/** Represents errors that can occur in Evolu. */
|
|
461
|
-
export type EvoluError =
|
|
462
|
-
| ProtocolError
|
|
463
|
-
| SqliteError
|
|
464
|
-
| SymmetricCryptoDecryptError
|
|
465
|
-
| TimestampError
|
|
466
|
-
| TransferableError;
|
|
467
|
-
|
|
468
|
-
interface InternalEvoluInstance<
|
|
469
|
-
S extends EvoluSchema = EvoluSchema,
|
|
470
|
-
> extends Evolu<S> {
|
|
471
|
-
/**
|
|
472
|
-
* Ensure tables and columns defined in {@link EvoluSchema} exist in the
|
|
473
|
-
* database. This function is for hot reloading.
|
|
474
|
-
*/
|
|
475
|
-
readonly ensureSchema: (schema: EvoluSchema) => void;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
export type EvoluDeps = ConsoleDep &
|
|
479
|
-
CreateDbWorkerDep &
|
|
480
|
-
Partial<FlushSyncDep> &
|
|
481
|
-
RandomBytesDep &
|
|
482
|
-
ReloadAppDep &
|
|
483
|
-
TimeDep;
|
|
484
|
-
|
|
485
|
-
const evoluInstances = createInstances<SimpleName, InternalEvoluInstance>();
|
|
486
|
-
|
|
487
535
|
/**
|
|
488
|
-
*
|
|
489
|
-
*
|
|
536
|
+
* Shared platform dependencies for creating {@link Evolu} instances.
|
|
537
|
+
*
|
|
538
|
+
* Includes platform adapters, the shared {@link EvoluErrorDep.evoluError} store,
|
|
539
|
+
* and disposal for owned resources.
|
|
490
540
|
*/
|
|
491
|
-
|
|
541
|
+
export type EvoluDeps = EvoluPlatformDeps & EvoluErrorDep & Disposable;
|
|
492
542
|
|
|
493
543
|
/**
|
|
494
|
-
*
|
|
495
|
-
* {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
|
|
496
|
-
* interface for querying, mutating, and syncing your application's data.
|
|
497
|
-
*
|
|
498
|
-
* ### Example
|
|
499
|
-
*
|
|
500
|
-
* ```ts
|
|
501
|
-
* const TodoId = id("Todo");
|
|
502
|
-
* type TodoId = InferType<typeof TodoId>;
|
|
503
|
-
*
|
|
504
|
-
* const TodoCategoryId = id("TodoCategory");
|
|
505
|
-
* type TodoCategoryId = InferType<typeof TodoCategoryId>;
|
|
506
|
-
*
|
|
507
|
-
* const NonEmptyString50 = maxLength(50, NonEmptyString);
|
|
508
|
-
* type NonEmptyString50 = InferType<typeof NonEmptyString50>;
|
|
544
|
+
* Platform-specific dependencies required to create {@link EvoluDeps}.
|
|
509
545
|
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
* }
|
|
522
|
-
*
|
|
523
|
-
* const evolu = createEvolu(evoluReactDeps)(Schema);
|
|
524
|
-
* ```
|
|
546
|
+
* Provides worker and channel adapters plus optional platform integrations for
|
|
547
|
+
* logging and synchronous UI flush.
|
|
548
|
+
*/
|
|
549
|
+
export type EvoluPlatformDeps = CreateDbWorkerDep &
|
|
550
|
+
CreateMessageChannelDep &
|
|
551
|
+
ReloadAppDep &
|
|
552
|
+
SharedWorkerDep &
|
|
553
|
+
Partial<ConsoleDep> &
|
|
554
|
+
Partial<FlushSyncDep>;
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Creates shared dependencies used by all {@link createEvolu} instances on a
|
|
558
|
+
* platform.
|
|
525
559
|
*
|
|
526
|
-
*
|
|
560
|
+
* Call this once per platform and reuse the returned deps when creating
|
|
561
|
+
* multiple Evolu instances. The returned deps object owns long-lived resources
|
|
562
|
+
* such as worker channels and the shared {@link EvoluErrorDep.evoluError}
|
|
563
|
+
* store.
|
|
527
564
|
*
|
|
528
|
-
*
|
|
529
|
-
* name to enable hot reloading and prevent database corruption from multiple
|
|
530
|
-
* connections. For testing, use unique instance names to ensure proper
|
|
531
|
-
* isolation.
|
|
565
|
+
* Dispose it only during app shutdown.
|
|
532
566
|
*/
|
|
533
|
-
export const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
deps.console.enabled = config?.enableLogging ?? false;
|
|
552
|
-
|
|
553
|
-
const { indexes, reloadUrl = "/", ...partialDbConfig } = config ?? {};
|
|
554
|
-
|
|
555
|
-
const dbConfig: DbConfig = { ...defaultDbConfig, ...partialDbConfig };
|
|
556
|
-
|
|
557
|
-
deps.console.log("[evolu]", "createEvoluInstance", {
|
|
558
|
-
name: dbConfig.name,
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
const errorStore = createStore<EvoluError | null>(null);
|
|
562
|
-
const rowsStore = createStore<QueryRowsMap>(new Map());
|
|
567
|
+
export const createEvoluDeps = (deps: EvoluPlatformDeps): EvoluDeps => {
|
|
568
|
+
const { createMessageChannel, sharedWorker } = deps;
|
|
569
|
+
const console = deps.console ?? createConsole();
|
|
570
|
+
|
|
571
|
+
const stack = new DisposableStack();
|
|
572
|
+
stack.use(sharedWorker);
|
|
573
|
+
const evoluError = stack.use(createStore<EvoluError | null>(null));
|
|
574
|
+
|
|
575
|
+
const tabChannel = stack.use(createMessageChannel<EvoluTabOutput>());
|
|
576
|
+
tabChannel.port2.onMessage = (message) => {
|
|
577
|
+
switch (message.type) {
|
|
578
|
+
case "OnConsoleEntry":
|
|
579
|
+
console.write(message.entry);
|
|
580
|
+
// Fallback channel for unexpected errors without EvoluError typing.
|
|
581
|
+
if (message.entry.method === "error") {
|
|
582
|
+
evoluError.set(createUnknownError(message.entry.args));
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
563
585
|
|
|
564
|
-
|
|
565
|
-
|
|
586
|
+
case "OnError":
|
|
587
|
+
evoluError.set(message.error);
|
|
588
|
+
// Keep typed errors visible in logs as operational failures.
|
|
589
|
+
console.error(message.error);
|
|
590
|
+
break;
|
|
566
591
|
|
|
567
|
-
|
|
568
|
-
|
|
592
|
+
default:
|
|
593
|
+
exhaustiveCheck(message);
|
|
569
594
|
}
|
|
595
|
+
};
|
|
570
596
|
|
|
571
|
-
|
|
572
|
-
|
|
597
|
+
sharedWorker.port.postMessage(
|
|
598
|
+
{
|
|
599
|
+
type: "InitTab",
|
|
600
|
+
consoleLevel: console.getLevel(),
|
|
601
|
+
port: tabChannel.port1.native,
|
|
602
|
+
},
|
|
603
|
+
[tabChannel.port1.native],
|
|
604
|
+
);
|
|
573
605
|
|
|
574
|
-
|
|
575
|
-
const loadingPromises = createLoadingPromises(subscribedQueries);
|
|
576
|
-
const onCompleteCallbacks = createCallbacks(deps);
|
|
577
|
-
const exportCallbacks = createCallbacks<Uint8Array<ArrayBuffer>>(deps);
|
|
606
|
+
const moved = stack.move();
|
|
578
607
|
|
|
579
|
-
|
|
608
|
+
return {
|
|
609
|
+
...deps,
|
|
610
|
+
evoluError,
|
|
611
|
+
[Symbol.dispose]: () => moved.dispose(),
|
|
612
|
+
};
|
|
613
|
+
};
|
|
580
614
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Creates an {@link Evolu} instance from {@link EvoluSchema} and
|
|
617
|
+
* {@link EvoluConfig}.
|
|
618
|
+
*/
|
|
619
|
+
export const createEvolu =
|
|
620
|
+
<S extends EvoluSchema>(
|
|
621
|
+
schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>,
|
|
622
|
+
config: EvoluConfig,
|
|
623
|
+
): Task<Evolu<S>, never, EvoluPlatformDeps> =>
|
|
624
|
+
async (run) => {
|
|
625
|
+
const {
|
|
626
|
+
appName,
|
|
627
|
+
appOwner,
|
|
628
|
+
memoryOnly = false,
|
|
629
|
+
transports = [{ type: "WebSocket", url: "wss://free.evoluhq.com" }],
|
|
630
|
+
} = config;
|
|
585
631
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
switch (message.type) {
|
|
590
|
-
case "onError": {
|
|
591
|
-
errorStore.set(message.error);
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
632
|
+
const name = Name.orThrow(`${appName}-${createIdFromString(appOwner.id)}`);
|
|
633
|
+
const console = run.deps.console.child(name).child("Evolu");
|
|
634
|
+
console.info("createEvolu");
|
|
594
635
|
|
|
595
|
-
|
|
596
|
-
resolveAppOwner(message.appOwner);
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
636
|
+
await using stack = new AsyncDisposableStack();
|
|
599
637
|
|
|
600
|
-
|
|
601
|
-
|
|
638
|
+
const rowsByQueryMapStore = stack.use(
|
|
639
|
+
createStore<RowsByQueryMap>(new Map()),
|
|
640
|
+
);
|
|
641
|
+
const subscribedQueriesRefCount = stack.use(createRefCountByKey<Query>());
|
|
642
|
+
|
|
643
|
+
interface LoadingPromise {
|
|
644
|
+
/**
|
|
645
|
+
* React tracks `status`/`value`/`reason` on thenables passed to `use`.
|
|
646
|
+
* Evolu mirrors that shape so cached promises can be unwrapped
|
|
647
|
+
* synchronously and to keep promise-cache behavior stable.
|
|
648
|
+
*
|
|
649
|
+
* React source:
|
|
650
|
+
* https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberThenable.js
|
|
651
|
+
*/
|
|
652
|
+
promise: Promise<QueryRows> & {
|
|
653
|
+
status?: "pending" | "fulfilled" | "rejected";
|
|
654
|
+
value?: QueryRows;
|
|
655
|
+
reason?: unknown;
|
|
656
|
+
};
|
|
657
|
+
resolve: (rows: QueryRows) => void;
|
|
658
|
+
releaseOnResolve: boolean;
|
|
659
|
+
}
|
|
602
660
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Settle pending query loads during disposal so awaiting callers and React
|
|
663
|
+
* `use` thenables do not hang forever during teardown.
|
|
664
|
+
*/
|
|
665
|
+
const loadingPromisesByQuery = stack.adopt(
|
|
666
|
+
new Map<Query, LoadingPromise>(),
|
|
667
|
+
(loadingPromisesByQuery) => {
|
|
668
|
+
for (const loadingPromise of loadingPromisesByQuery.values()) {
|
|
669
|
+
if (loadingPromise.promise.status === "fulfilled") continue;
|
|
670
|
+
fulfillLoadingPromise(loadingPromise, emptyArray);
|
|
671
|
+
}
|
|
672
|
+
loadingPromisesByQuery.clear();
|
|
673
|
+
},
|
|
674
|
+
);
|
|
613
675
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
676
|
+
const fulfillLoadingPromise = (
|
|
677
|
+
loadingPromise: LoadingPromise,
|
|
678
|
+
rows: QueryRows,
|
|
679
|
+
): void => {
|
|
680
|
+
/**
|
|
681
|
+
* Pending promises must be resolved in place to preserve identity for
|
|
682
|
+
* current awaiters. Fulfilled promises are replaced with a new resolved
|
|
683
|
+
* promise so future loads see the latest rows.
|
|
684
|
+
*/
|
|
685
|
+
if (loadingPromise.promise.status !== "fulfilled") {
|
|
686
|
+
loadingPromise.resolve(rows);
|
|
687
|
+
} else {
|
|
688
|
+
loadingPromise.promise = Promise.resolve(rows);
|
|
689
|
+
}
|
|
617
690
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
691
|
+
/** See {@link LoadingPromise.promise}. */
|
|
692
|
+
void Object.assign(loadingPromise.promise, {
|
|
693
|
+
status: "fulfilled",
|
|
694
|
+
value: rows,
|
|
695
|
+
});
|
|
696
|
+
};
|
|
625
697
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
698
|
+
/**
|
|
699
|
+
* Mutations and refreshes invalidate query snapshots. Keep loading promises
|
|
700
|
+
* only for actively subscribed queries and release unsubscribed ones.
|
|
701
|
+
*
|
|
702
|
+
* Fulfilled promises can be dropped immediately because no awaiter is
|
|
703
|
+
* waiting on them. Pending promises must stay alive until they resolve so
|
|
704
|
+
* current awaiters keep the same promise identity.
|
|
705
|
+
*/
|
|
706
|
+
const releaseUnsubscribedLoadingPromises = (): void => {
|
|
707
|
+
for (const [query, loadingPromise] of loadingPromisesByQuery) {
|
|
708
|
+
if (subscribedQueriesRefCount.has(query)) continue;
|
|
709
|
+
|
|
710
|
+
if (loadingPromise.promise.status === "fulfilled") {
|
|
711
|
+
loadingPromisesByQuery.delete(query);
|
|
712
|
+
} else {
|
|
713
|
+
loadingPromise.releaseOnResolve = true;
|
|
630
714
|
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
631
717
|
|
|
632
|
-
|
|
633
|
-
if (message.tabId && message.tabId === getTabId()) return;
|
|
718
|
+
const onMutateCompleteCallbacks = stack.use(createCallbacks(run.deps));
|
|
634
719
|
|
|
635
|
-
|
|
636
|
-
|
|
720
|
+
let exportDatabasePending = null as PromiseWithResolvers<
|
|
721
|
+
Uint8Array<ArrayBuffer>
|
|
722
|
+
> | null;
|
|
637
723
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
724
|
+
stack.defer(() => {
|
|
725
|
+
exportDatabasePending?.reject({ type: "EvoluDisposedError" });
|
|
726
|
+
exportDatabasePending = null;
|
|
727
|
+
});
|
|
642
728
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
729
|
+
const mutateBatch = stack.use(
|
|
730
|
+
createMicrotaskBatch<{
|
|
731
|
+
readonly change: MutationChange;
|
|
732
|
+
readonly onComplete: (() => void) | undefined;
|
|
733
|
+
}>((items) => {
|
|
734
|
+
console.debug("mutateBatch", { changeCount: items.length });
|
|
735
|
+
releaseUnsubscribedLoadingPromises();
|
|
736
|
+
|
|
737
|
+
postMessage({
|
|
738
|
+
type: "Mutate",
|
|
739
|
+
changes: mapArray(items, (item) => item.change),
|
|
740
|
+
onCompleteIds: items.flatMap((item) =>
|
|
741
|
+
item.onComplete
|
|
742
|
+
? [onMutateCompleteCallbacks.register(item.onComplete)]
|
|
743
|
+
: [],
|
|
744
|
+
),
|
|
745
|
+
subscribedQueries: subscribedQueriesRefCount.keys(),
|
|
746
|
+
});
|
|
747
|
+
}),
|
|
748
|
+
);
|
|
646
749
|
|
|
647
|
-
|
|
648
|
-
|
|
750
|
+
const queryBatch = stack.use(
|
|
751
|
+
createMicrotaskBatch<Query>((queries) => {
|
|
752
|
+
const dedupedQueries = new Set(queries);
|
|
753
|
+
assert(
|
|
754
|
+
isNonEmptySet(dedupedQueries),
|
|
755
|
+
"Expected non-empty query batch.",
|
|
756
|
+
);
|
|
757
|
+
console.debug("queryBatch", { queryCount: dedupedQueries.size });
|
|
758
|
+
postMessage({ type: "Query", queries: dedupedQueries });
|
|
759
|
+
}),
|
|
760
|
+
);
|
|
649
761
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
break;
|
|
657
|
-
}
|
|
762
|
+
const useOwnerBatch = stack.use(
|
|
763
|
+
createMicrotaskBatch<
|
|
764
|
+
ExtractType<EvoluInput, "UseOwner">["actions"][number]
|
|
765
|
+
>((actions) => postMessage({ type: "UseOwner", actions })),
|
|
766
|
+
);
|
|
658
767
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
768
|
+
let postMessage: (input: EvoluInput) => void;
|
|
769
|
+
|
|
770
|
+
// Scope worker/channel wiring and keep only postMessage outside.
|
|
771
|
+
{
|
|
772
|
+
const { createDbWorker, createMessageChannel, sharedWorker } = run.deps;
|
|
773
|
+
const dbWorkerChannel = stack.use(
|
|
774
|
+
createMessageChannel<DbWorkerOutput, DbWorkerInput>(),
|
|
775
|
+
);
|
|
776
|
+
const evoluChannel = stack.use(
|
|
777
|
+
createMessageChannel<EvoluInput, EvoluOutput>(),
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
evoluChannel.port1.onMessage = (message) => {
|
|
781
|
+
switch (message.type) {
|
|
782
|
+
case "OnPatchesByQuery": {
|
|
783
|
+
console.debug("onPatchesByQuery", {
|
|
784
|
+
queryCount: message.patchesByQuery.size,
|
|
785
|
+
onCompleteCount: message.onCompleteIds.length,
|
|
786
|
+
});
|
|
787
|
+
const state = rowsByQueryMapStore.get();
|
|
788
|
+
const nextRowsByQueryMap = new Map(state);
|
|
666
789
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
790
|
+
for (const [query, patches] of message.patchesByQuery) {
|
|
791
|
+
nextRowsByQueryMap.set(
|
|
792
|
+
query,
|
|
793
|
+
applyPatches(patches, state.get(query) ?? emptyArray),
|
|
794
|
+
);
|
|
795
|
+
}
|
|
671
796
|
|
|
672
|
-
|
|
797
|
+
for (const query of message.patchesByQuery.keys()) {
|
|
798
|
+
const loadingPromise = loadingPromisesByQuery.get(query);
|
|
799
|
+
if (!loadingPromise) continue;
|
|
673
800
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
Map<string, ValidMutationSize<any>>
|
|
677
|
-
>();
|
|
801
|
+
const rows = nextRowsByQueryMap.get(query);
|
|
802
|
+
assert(rows, "Expected patched query rows to exist.");
|
|
678
803
|
|
|
679
|
-
|
|
680
|
-
const getMutationType = (table: string, kind: MutationKind) => {
|
|
681
|
-
let types = mutationTypesCache.get(kind);
|
|
682
|
-
if (!types) {
|
|
683
|
-
types = new Map();
|
|
684
|
-
mutationTypesCache.set(kind, types);
|
|
685
|
-
}
|
|
686
|
-
let type = types.get(table);
|
|
687
|
-
if (!type) {
|
|
688
|
-
type = { insert: insertable, update: updateable, upsert: upsertable }[
|
|
689
|
-
kind
|
|
690
|
-
](schema[table]);
|
|
691
|
-
types.set(table, type);
|
|
692
|
-
}
|
|
693
|
-
return type;
|
|
694
|
-
};
|
|
804
|
+
fulfillLoadingPromise(loadingPromise, rows);
|
|
695
805
|
|
|
696
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Release promises flagged during mutation when they finish
|
|
808
|
+
* resolving. This keeps in-flight promise identity stable and
|
|
809
|
+
* prevents stale cache entries after completion.
|
|
810
|
+
*/
|
|
811
|
+
if (loadingPromise.releaseOnResolve) {
|
|
812
|
+
loadingPromisesByQuery.delete(query);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
697
815
|
|
|
698
|
-
|
|
699
|
-
|
|
816
|
+
if (run.deps.flushSync && message.onCompleteIds.length > 0) {
|
|
817
|
+
run.deps.flushSync(() => {
|
|
818
|
+
rowsByQueryMapStore.set(nextRowsByQueryMap);
|
|
819
|
+
});
|
|
820
|
+
} else {
|
|
821
|
+
rowsByQueryMapStore.set(nextRowsByQueryMap);
|
|
822
|
+
}
|
|
700
823
|
|
|
701
|
-
|
|
824
|
+
for (const onCompleteId of message.onCompleteIds) {
|
|
825
|
+
onMutateCompleteCallbacks.execute(onCompleteId);
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
702
829
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
> = [];
|
|
830
|
+
case "RefreshQueries": {
|
|
831
|
+
releaseUnsubscribedLoadingPromises();
|
|
706
832
|
|
|
707
|
-
|
|
833
|
+
const queries = new Set<Query>([
|
|
834
|
+
...loadingPromisesByQuery.keys(),
|
|
835
|
+
...subscribedQueriesRefCount.keys(),
|
|
836
|
+
]);
|
|
708
837
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
table: TableName,
|
|
713
|
-
props: InferInput<
|
|
714
|
-
ObjectType<MutationMapping<(typeof schema)[TableName], Kind>>
|
|
715
|
-
>,
|
|
716
|
-
options?: MutationOptions,
|
|
717
|
-
): Result<
|
|
718
|
-
{ readonly id: InferType<(typeof schema)[TableName]["id"]> },
|
|
719
|
-
| ValidMutationSizeError
|
|
720
|
-
| InferErrors<
|
|
721
|
-
ObjectType<MutationMapping<(typeof schema)[TableName], Kind>>
|
|
722
|
-
>
|
|
723
|
-
> => {
|
|
724
|
-
const result = getMutationType(table, kind).fromUnknown(props);
|
|
725
|
-
|
|
726
|
-
const id =
|
|
727
|
-
kind === "insert"
|
|
728
|
-
? createId(deps)
|
|
729
|
-
: (props as unknown as { id: Id }).id;
|
|
730
|
-
|
|
731
|
-
if (options?.onlyValidate !== true) {
|
|
732
|
-
if (!result.ok) {
|
|
733
|
-
// Mark the transaction as invalid by pushing null
|
|
734
|
-
mutateMicrotaskQueue.push([null, undefined]);
|
|
735
|
-
} else {
|
|
736
|
-
const { id: _, isDeleted, ...values } = result.value;
|
|
737
|
-
|
|
738
|
-
const dbChange = {
|
|
739
|
-
table,
|
|
740
|
-
id,
|
|
741
|
-
values,
|
|
742
|
-
isInsert: kind === "insert" || kind === "upsert",
|
|
743
|
-
isDelete: SqliteBoolean.is(isDeleted)
|
|
744
|
-
? sqliteBooleanToBoolean(isDeleted)
|
|
745
|
-
: null,
|
|
746
|
-
};
|
|
838
|
+
if (isNonEmptySet(queries)) postMessage({ type: "Query", queries });
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
747
841
|
|
|
842
|
+
case "OnExport": {
|
|
748
843
|
assert(
|
|
749
|
-
|
|
750
|
-
|
|
844
|
+
exportDatabasePending,
|
|
845
|
+
"OnExport received without pending export.",
|
|
751
846
|
);
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
options?.onComplete,
|
|
756
|
-
]);
|
|
847
|
+
exportDatabasePending.resolve(message.file);
|
|
848
|
+
exportDatabasePending = null;
|
|
849
|
+
break;
|
|
757
850
|
}
|
|
758
851
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
852
|
+
default:
|
|
853
|
+
exhaustiveCheck(message);
|
|
762
854
|
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
sharedWorker.port.postMessage(
|
|
858
|
+
{
|
|
859
|
+
type: "CreateEvolu",
|
|
860
|
+
name,
|
|
861
|
+
evoluPort: evoluChannel.port2.native,
|
|
862
|
+
dbWorkerPort: dbWorkerChannel.port2.native,
|
|
863
|
+
},
|
|
864
|
+
[evoluChannel.port2.native, dbWorkerChannel.port2.native],
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* No stack.use because Evolu instances don't dispose DbWorker because
|
|
869
|
+
* it's SharedWorder responsibility. DbWorker can be used by another tab.
|
|
870
|
+
* That's required because SQLite WASM needs a single web worker.
|
|
871
|
+
*/
|
|
872
|
+
createDbWorker().postMessage(
|
|
873
|
+
{
|
|
874
|
+
type: "Init",
|
|
875
|
+
name,
|
|
876
|
+
consoleLevel: console.getLevel(),
|
|
877
|
+
sqliteSchema: evoluSchemaToSqliteSchema(schema, config.indexes),
|
|
878
|
+
encryptionKey: appOwner.encryptionKey,
|
|
879
|
+
memoryOnly,
|
|
880
|
+
port: dbWorkerChannel.port1.native,
|
|
881
|
+
},
|
|
882
|
+
[dbWorkerChannel.port1.native],
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
postMessage = evoluChannel.port1.postMessage;
|
|
886
|
+
|
|
887
|
+
stack.defer(() => {
|
|
888
|
+
postMessage({ type: "Dispose" });
|
|
889
|
+
});
|
|
890
|
+
}
|
|
763
891
|
|
|
764
|
-
|
|
892
|
+
const createMutation =
|
|
893
|
+
<Kind extends "insert" | "update" | "upsert">(
|
|
894
|
+
kind: Kind,
|
|
895
|
+
): Mutation<S, Kind> =>
|
|
896
|
+
(table, values, options) => {
|
|
897
|
+
assertNotDisposed(moved);
|
|
898
|
+
|
|
899
|
+
const {
|
|
900
|
+
id = createId(run.deps),
|
|
901
|
+
isDeleted,
|
|
902
|
+
...changeValues
|
|
903
|
+
} = values as { id?: Id; isDeleted?: unknown; [key: string]: unknown };
|
|
904
|
+
|
|
905
|
+
const dbChange = {
|
|
906
|
+
table,
|
|
907
|
+
id,
|
|
908
|
+
values: changeValues,
|
|
909
|
+
isInsert: kind === "insert" || kind === "upsert",
|
|
910
|
+
isDelete: SqliteBoolean.is(isDeleted)
|
|
911
|
+
? sqliteBooleanToBoolean(isDeleted)
|
|
912
|
+
: null,
|
|
913
|
+
};
|
|
765
914
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
| InferErrors<
|
|
770
|
-
ObjectType<MutationMapping<(typeof schema)[TableName], Kind>>
|
|
771
|
-
>,
|
|
915
|
+
assert(
|
|
916
|
+
DbChange.is(dbChange),
|
|
917
|
+
`Invalid DbChange for table '${String(table)}'.`,
|
|
772
918
|
);
|
|
919
|
+
|
|
920
|
+
mutateBatch.push({
|
|
921
|
+
change: { ...dbChange, ownerId: options?.ownerId ?? appOwner.id },
|
|
922
|
+
onComplete: options?.onComplete,
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
return { id };
|
|
773
926
|
};
|
|
774
927
|
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
928
|
+
const loadQuery = <R extends Row>(
|
|
929
|
+
query: Query<S, R>,
|
|
930
|
+
): Promise<QueryRows<R>> => {
|
|
931
|
+
assertNotDisposed(moved);
|
|
778
932
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
933
|
+
const loadingPromise = loadingPromisesByQuery.get(query);
|
|
934
|
+
if (loadingPromise) {
|
|
935
|
+
return loadingPromise.promise as Promise<QueryRows<R>>;
|
|
782
936
|
}
|
|
783
937
|
|
|
784
|
-
const
|
|
785
|
-
|
|
938
|
+
const { promise, resolve } = Promise.withResolvers<QueryRows>();
|
|
939
|
+
const typedPromise = promise as LoadingPromise["promise"];
|
|
940
|
+
typedPromise.status = "pending";
|
|
786
941
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
942
|
+
loadingPromisesByQuery.set(query, {
|
|
943
|
+
promise: typedPromise,
|
|
944
|
+
resolve,
|
|
945
|
+
releaseOnResolve: false,
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
queryBatch.push(query);
|
|
792
949
|
|
|
793
|
-
|
|
794
|
-
|
|
950
|
+
return typedPromise as Promise<QueryRows<R>>;
|
|
951
|
+
};
|
|
795
952
|
|
|
796
|
-
|
|
953
|
+
const getQueryRows = <R extends Row>(query: Query<S, R>): QueryRows<R> => {
|
|
954
|
+
assertNotDisposed(moved);
|
|
797
955
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
tabId: getTabId(),
|
|
801
|
-
changes,
|
|
802
|
-
onCompleteIds,
|
|
803
|
-
subscribedQueries: subscribedQueries.get(),
|
|
804
|
-
});
|
|
956
|
+
return (rowsByQueryMapStore.get().get(query) ??
|
|
957
|
+
emptyArray) as QueryRows<R>;
|
|
805
958
|
};
|
|
806
959
|
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
deps.console.log("[evolu]", "loadQuery", { queries });
|
|
824
|
-
dbWorker.postMessage({
|
|
825
|
-
type: "query",
|
|
826
|
-
tabId: getTabId(),
|
|
827
|
-
queries,
|
|
828
|
-
});
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
}
|
|
960
|
+
const useOwner = (
|
|
961
|
+
owner: ReadonlyOwner | Owner,
|
|
962
|
+
ownerTransports?: NonEmptyReadonlyArray<OwnerTransport>,
|
|
963
|
+
): UnuseOwner => {
|
|
964
|
+
assertNotDisposed(moved);
|
|
965
|
+
|
|
966
|
+
const effectiveTransports = ownerTransports ?? transports;
|
|
967
|
+
assertNonEmptyReadonlyArray(
|
|
968
|
+
effectiveTransports,
|
|
969
|
+
"useOwner requires explicit non-empty transports when config.transports is empty.",
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
const syncOwner: SyncOwner = {
|
|
973
|
+
owner,
|
|
974
|
+
transports: effectiveTransports,
|
|
975
|
+
};
|
|
832
976
|
|
|
833
|
-
|
|
834
|
-
|
|
977
|
+
useOwnerBatch.push({
|
|
978
|
+
owner: syncOwner,
|
|
979
|
+
action: "add",
|
|
980
|
+
});
|
|
835
981
|
|
|
836
|
-
|
|
837
|
-
queries: [...Q],
|
|
838
|
-
): [...QueriesToQueryRowsPromises<Q>] =>
|
|
839
|
-
queries.map(evolu.loadQuery) as [...QueriesToQueryRowsPromises<Q>],
|
|
982
|
+
let isUsed = true;
|
|
840
983
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
previousRows = rows;
|
|
848
|
-
listener();
|
|
984
|
+
return () => {
|
|
985
|
+
assert(isUsed, "UnuseOwner can be called only once.");
|
|
986
|
+
isUsed = false;
|
|
987
|
+
useOwnerBatch.push({
|
|
988
|
+
owner: syncOwner,
|
|
989
|
+
action: "remove",
|
|
849
990
|
});
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
unsubscribe();
|
|
853
|
-
};
|
|
854
|
-
},
|
|
991
|
+
};
|
|
992
|
+
};
|
|
855
993
|
|
|
856
|
-
|
|
857
|
-
(rowsStore.get().get(query) ?? emptyRows) as QueryRows<R>,
|
|
994
|
+
const moved = stack.move();
|
|
858
995
|
|
|
859
|
-
|
|
996
|
+
if (isNonEmptyArray(transports)) useOwner(appOwner);
|
|
860
997
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
998
|
+
return ok({
|
|
999
|
+
name,
|
|
1000
|
+
appOwner,
|
|
864
1001
|
|
|
865
1002
|
insert: createMutation("insert"),
|
|
866
1003
|
update: createMutation("update"),
|
|
867
1004
|
upsert: createMutation("upsert"),
|
|
868
1005
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
});
|
|
877
|
-
return promise;
|
|
878
|
-
},
|
|
879
|
-
|
|
880
|
-
restoreAppOwner: (mnemonic, options) => {
|
|
881
|
-
const { promise, resolve } = Promise.withResolvers<undefined>();
|
|
882
|
-
const onCompleteId = onCompleteCallbacks.register(resolve);
|
|
883
|
-
dbWorker.postMessage({
|
|
884
|
-
type: "reset",
|
|
885
|
-
onCompleteId,
|
|
886
|
-
reload: options?.reload ?? true,
|
|
887
|
-
restore: { mnemonic, dbSchema },
|
|
888
|
-
});
|
|
889
|
-
return promise;
|
|
890
|
-
},
|
|
891
|
-
|
|
892
|
-
reloadApp: () => {
|
|
893
|
-
deps.reloadApp(reloadUrl);
|
|
894
|
-
},
|
|
895
|
-
|
|
896
|
-
ensureSchema: (schema) => {
|
|
897
|
-
mutationTypesCache.clear();
|
|
898
|
-
const dbSchema = evoluSchemaToDbSchema(schema);
|
|
899
|
-
dbWorker.postMessage({ type: "ensureDbSchema", dbSchema });
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
exportDatabase: () => {
|
|
903
|
-
const { promise, resolve } =
|
|
904
|
-
Promise.withResolvers<Uint8Array<ArrayBuffer>>();
|
|
905
|
-
const onCompleteId = exportCallbacks.register(resolve);
|
|
906
|
-
dbWorker.postMessage({ type: "export", onCompleteId });
|
|
907
|
-
return promise;
|
|
908
|
-
},
|
|
909
|
-
|
|
910
|
-
useOwner: (owner) => {
|
|
911
|
-
const scheduleOwnerQueueProcessing = () => {
|
|
912
|
-
if (useOwnerMicrotaskQueue.length !== 1) return;
|
|
913
|
-
queueMicrotask(() => {
|
|
914
|
-
const queue = [...useOwnerMicrotaskQueue];
|
|
915
|
-
useOwnerMicrotaskQueue.length = 0;
|
|
1006
|
+
loadQuery,
|
|
1007
|
+
loadQueries: <Q extends Queries<S>>(
|
|
1008
|
+
queries: [...Q],
|
|
1009
|
+
): [...QueriesToQueryRowsPromises<Q>] =>
|
|
1010
|
+
queries.map((query) => loadQuery(query)) as [
|
|
1011
|
+
...QueriesToQueryRowsPromises<Q>,
|
|
1012
|
+
],
|
|
916
1013
|
|
|
917
|
-
|
|
918
|
-
|
|
1014
|
+
subscribeQuery: (query) => (listener) => {
|
|
1015
|
+
assertNotDisposed(moved);
|
|
919
1016
|
|
|
920
|
-
|
|
921
|
-
|
|
1017
|
+
subscribedQueriesRefCount.increment(query);
|
|
1018
|
+
let isSubscribed = true;
|
|
922
1019
|
|
|
923
|
-
|
|
924
|
-
queue[i];
|
|
1020
|
+
let previousRows: unknown = null;
|
|
925
1021
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1022
|
+
const unsubscribe = rowsByQueryMapStore.subscribe(() => {
|
|
1023
|
+
const rows = getQueryRows(query);
|
|
1024
|
+
if (previousRows === rows) return;
|
|
1025
|
+
previousRows = rows;
|
|
1026
|
+
listener();
|
|
1027
|
+
});
|
|
929
1028
|
|
|
930
|
-
|
|
1029
|
+
return () => {
|
|
1030
|
+
assert(
|
|
1031
|
+
isSubscribed,
|
|
1032
|
+
"subscribeQuery unsubscribe can be called only once.",
|
|
1033
|
+
);
|
|
1034
|
+
isSubscribed = false;
|
|
931
1035
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
eqArrayNumber(currentOwnerSerialized, otherOwnerSerialized)
|
|
935
|
-
) {
|
|
936
|
-
// Found cancel-out pair, skip both
|
|
937
|
-
skipIndices.add(i).add(j);
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
1036
|
+
previousRows = null;
|
|
1037
|
+
unsubscribe();
|
|
941
1038
|
|
|
942
|
-
|
|
943
|
-
result.push([currentOwner, currentUse, currentOwnerSerialized]);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
1039
|
+
if (moved.disposed) return;
|
|
946
1040
|
|
|
947
|
-
|
|
948
|
-
dbWorker.postMessage({ type: "useOwner", owner, use });
|
|
949
|
-
}
|
|
950
|
-
});
|
|
1041
|
+
subscribedQueriesRefCount.decrement(query);
|
|
951
1042
|
};
|
|
1043
|
+
},
|
|
1044
|
+
getQueryRows,
|
|
952
1045
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const unuse = () => {
|
|
957
|
-
useOwnerMicrotaskQueue.push([owner, false, pack(owner)]);
|
|
958
|
-
scheduleOwnerQueueProcessing();
|
|
959
|
-
};
|
|
1046
|
+
exportDatabase: () => {
|
|
1047
|
+
assertNotDisposed(moved);
|
|
960
1048
|
|
|
961
|
-
|
|
1049
|
+
if (!exportDatabasePending) {
|
|
1050
|
+
exportDatabasePending =
|
|
1051
|
+
Promise.withResolvers<Uint8Array<ArrayBuffer>>();
|
|
1052
|
+
postMessage({ type: "Export" });
|
|
1053
|
+
}
|
|
1054
|
+
return exportDatabasePending.promise;
|
|
962
1055
|
},
|
|
963
1056
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1057
|
+
deleteDatabase: () => {
|
|
1058
|
+
assertNotDisposed(moved);
|
|
1059
|
+
todo();
|
|
967
1060
|
},
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
return evolu;
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
export const createQuery = <R extends Row>(
|
|
974
|
-
queryCallback: Parameters<CreateQuery<EvoluSchema>>[0],
|
|
975
|
-
options?: Parameters<CreateQuery<EvoluSchema>>[1],
|
|
976
|
-
): Query<R> => {
|
|
977
|
-
const compiledQuery = queryCallback(kysely as IntentionalNever).compile();
|
|
978
|
-
|
|
979
|
-
if (isSqlMutation(compiledQuery.sql))
|
|
980
|
-
throw new Error(
|
|
981
|
-
"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.",
|
|
982
|
-
);
|
|
983
|
-
|
|
984
|
-
return serializeQuery({
|
|
985
|
-
sql: compiledQuery.sql as SafeSql,
|
|
986
|
-
parameters: compiledQuery.parameters as NonNullable<
|
|
987
|
-
SqliteQuery["parameters"]
|
|
988
|
-
>,
|
|
989
|
-
...(options && { options }),
|
|
990
|
-
});
|
|
991
|
-
};
|
|
992
1061
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
readonly isNew: boolean;
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
resolve: (query: Query, rows: ReadonlyArray<Row>) => void;
|
|
1002
|
-
|
|
1003
|
-
releaseUnsubscribedOnMutation: () => void;
|
|
1062
|
+
deleteOwner: (owner) => {
|
|
1063
|
+
assertNotDisposed(moved);
|
|
1064
|
+
void owner;
|
|
1065
|
+
todo();
|
|
1066
|
+
},
|
|
1004
1067
|
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1068
|
+
useOwner,
|
|
1007
1069
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
reason?: unknown;
|
|
1070
|
+
[Symbol.asyncDispose]: () => {
|
|
1071
|
+
console.info("dispose");
|
|
1072
|
+
return moved.disposeAsync();
|
|
1073
|
+
},
|
|
1074
|
+
} as Evolu<S>);
|
|
1014
1075
|
};
|
|
1015
|
-
resolve: (rows: QueryRows) => void;
|
|
1016
|
-
releaseOnResolve: boolean;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
const createLoadingPromises = (
|
|
1020
|
-
subscribedQueries: SubscribedQueries,
|
|
1021
|
-
): LoadingPromises => {
|
|
1022
|
-
const loadingPromiseMap = new Map<Query, LoadingPromise>();
|
|
1023
|
-
|
|
1024
|
-
return {
|
|
1025
|
-
get: <R extends Row>(
|
|
1026
|
-
query: Query<R>,
|
|
1027
|
-
): {
|
|
1028
|
-
readonly promise: Promise<QueryRows<R>>;
|
|
1029
|
-
readonly isNew: boolean;
|
|
1030
|
-
} => {
|
|
1031
|
-
let loadingPromise = loadingPromiseMap.get(query);
|
|
1032
|
-
const isNew = !loadingPromise;
|
|
1033
|
-
if (!loadingPromise) {
|
|
1034
|
-
const { promise, resolve } = Promise.withResolvers<QueryRows>();
|
|
1035
|
-
loadingPromise = { resolve, promise, releaseOnResolve: false };
|
|
1036
|
-
loadingPromiseMap.set(query, loadingPromise);
|
|
1037
|
-
}
|
|
1038
|
-
return {
|
|
1039
|
-
promise: loadingPromise.promise as Promise<QueryRows<R>>,
|
|
1040
|
-
isNew,
|
|
1041
|
-
};
|
|
1042
|
-
},
|
|
1043
|
-
|
|
1044
|
-
resolve: (query, rows) => {
|
|
1045
|
-
const loadingPromise = loadingPromiseMap.get(query);
|
|
1046
|
-
if (!loadingPromise) return;
|
|
1047
|
-
|
|
1048
|
-
if (loadingPromise.promise.status !== "fulfilled") {
|
|
1049
|
-
loadingPromise.resolve(rows);
|
|
1050
|
-
} else {
|
|
1051
|
-
loadingPromise.promise = Promise.resolve(rows);
|
|
1052
|
-
}
|
|
1053
1076
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
};
|
|
1077
|
+
// case "onReset": {
|
|
1078
|
+
// if (message.reload) {
|
|
1079
|
+
// deps.reloadApp(reloadUrl);
|
|
1080
|
+
// } else {
|
|
1081
|
+
// onCompleteCallbacks.execute(message.onCompleteId);
|
|
1082
|
+
// }
|
|
1083
|
+
// break;
|
|
1084
|
+
// }
|
|
1085
|
+
|
|
1086
|
+
// resetAppOwner: (_options) => {
|
|
1087
|
+
// const { promise, resolve } = Promise.withResolvers<undefined>();
|
|
1088
|
+
// const _onCompleteId = onCompleteCallbacks.register(resolve);
|
|
1089
|
+
// // dbWorker.postMessage({
|
|
1090
|
+
// // type: "reset",
|
|
1091
|
+
// // onCompleteId,
|
|
1092
|
+
// // reload: options?.reload ?? true,
|
|
1093
|
+
// // });
|
|
1094
|
+
// return promise;
|
|
1095
|
+
// },
|
|
1096
|
+
|
|
1097
|
+
// restoreAppOwner: (_mnemonic, _options) => {
|
|
1098
|
+
// const { promise, resolve } = Promise.withResolvers<undefined>();
|
|
1099
|
+
// const _onCompleteId = onCompleteCallbacks.register(resolve);
|
|
1100
|
+
// // dbWorker.postMessage({
|
|
1101
|
+
// // type: "reset",
|
|
1102
|
+
// // onCompleteId,
|
|
1103
|
+
// // reload: options?.reload ?? true,
|
|
1104
|
+
// // restore: { mnemonic, sqliteSchema },
|
|
1105
|
+
// // });
|
|
1106
|
+
// return promise;
|
|
1107
|
+
// },
|
|
1108
|
+
|
|
1109
|
+
// reloadApp: () => {
|
|
1110
|
+
// // TODO:
|
|
1111
|
+
// // deps.reloadApp(reloadUrl);
|
|
1112
|
+
// },
|
|
1113
|
+
|
|
1114
|
+
// ensureSchema: (schema) => {
|
|
1115
|
+
// mutationTypesCache.clear();
|
|
1116
|
+
// const sqliteSchema = evoluSchemaToSqliteSchema(schema);
|
|
1117
|
+
// dbWorker.postMessage({ type: "ensureSqliteSchema", sqliteSchema });
|
|
1118
|
+
// },
|
|
1119
|
+
|
|
1120
|
+
// /**
|
|
1121
|
+
// * Delete {@link AppOwner} and all their data from the current device. After
|
|
1122
|
+
// * the deletion, Evolu will purge the application state. For browsers, this
|
|
1123
|
+
// * will reload all tabs using Evolu. For native apps, it will restart the
|
|
1124
|
+
// * app.
|
|
1125
|
+
// *
|
|
1126
|
+
// * Reloading can be turned off via options if you want to provide a different
|
|
1127
|
+
// * UX.
|
|
1128
|
+
// */
|
|
1129
|
+
// readonly resetAppOwner: (options?: {
|
|
1130
|
+
// readonly reload?: boolean;
|
|
1131
|
+
// }) => Promise<void>;
|
|
1132
|
+
|
|
1133
|
+
// /**
|
|
1134
|
+
// * Restore {@link AppOwner} with all their synced data. It uses
|
|
1135
|
+
// * {@link Evolu.resetAppOwner}, so be careful.
|
|
1136
|
+
// */
|
|
1137
|
+
// readonly restoreAppOwner: (
|
|
1138
|
+
// mnemonic: Mnemonic,
|
|
1139
|
+
// options?: {
|
|
1140
|
+
// readonly reload?: boolean;
|
|
1141
|
+
// },
|
|
1142
|
+
// ) => Promise<void>;
|