@evolu/common 6.0.1-preview.2 → 6.0.1-preview.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +7 -4
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +1 -1
- package/dist/src/CallbackRegistry.d.ts +53 -0
- package/dist/src/CallbackRegistry.d.ts.map +1 -0
- package/dist/src/CallbackRegistry.js +25 -0
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +61 -34
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +29 -42
- package/dist/src/Evolu/Db.d.ts +159 -66
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +300 -705
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +200 -115
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +287 -157
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +144 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +171 -0
- package/dist/src/Evolu/Owner.d.ts +209 -124
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +118 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +256 -211
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +558 -359
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.js +3 -3
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.d.ts +72 -4
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +124 -13
- package/dist/src/Evolu/Schema.d.ts +129 -73
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +169 -89
- package/dist/src/Evolu/Storage.d.ts +159 -14
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +42 -32
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +414 -20
- package/dist/src/Evolu/Timestamp.d.ts +35 -27
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +27 -18
- package/dist/src/ManyToManyMap.d.ts +74 -10
- package/dist/src/ManyToManyMap.d.ts.map +1 -1
- package/dist/src/ManyToManyMap.js +41 -6
- package/dist/src/Number.d.ts +2 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/RefCountedResourceManager.d.ts +119 -0
- package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
- package/dist/src/RefCountedResourceManager.js +197 -0
- package/dist/src/Result.d.ts +176 -51
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Sqlite.d.ts +21 -4
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +50 -8
- package/dist/src/Task.d.ts +511 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +410 -0
- package/dist/src/Time.d.ts +59 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +87 -4
- package/dist/src/Type.d.ts +474 -327
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +502 -436
- package/dist/src/Types.d.ts +0 -74
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -13
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/package.json +14 -13
- package/src/Assert.ts +8 -7
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +1 -1
- package/src/CallbackRegistry.ts +84 -0
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +94 -79
- package/src/Evolu/Db.ts +528 -1027
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +682 -329
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +422 -0
- package/src/Evolu/Owner.ts +282 -227
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +788 -640
- package/src/Evolu/Public.ts +7 -14
- package/src/Evolu/PublicKysely.ts +3 -3
- package/src/Evolu/Query.ts +2 -1
- package/src/Evolu/Relay.ts +238 -18
- package/src/Evolu/Schema.ts +391 -191
- package/src/Evolu/Storage.ts +275 -55
- package/src/Evolu/Sync.ts +708 -37
- package/src/Evolu/Timestamp.ts +38 -35
- package/src/ManyToManyMap.ts +127 -24
- package/src/Number.ts +2 -6
- package/src/Random.ts +3 -2
- package/src/RefCountedResourceManager.ts +368 -0
- package/src/Result.ts +183 -53
- package/src/Sqlite.ts +59 -23
- package/src/Task.ts +779 -0
- package/src/Time.ts +168 -4
- package/src/Type.ts +708 -660
- package/src/Types.ts +0 -76
- package/src/WebSocket.ts +23 -17
- package/src/Worker.ts +72 -23
- package/src/index.ts +3 -3
- package/dist/src/Callbacks.d.ts +0 -20
- package/dist/src/Callbacks.d.ts.map +0 -1
- package/dist/src/Callbacks.js +0 -18
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Callbacks.ts +0 -43
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/src/Evolu/Owner.ts
CHANGED
|
@@ -1,302 +1,357 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* @module
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { assert } from "../Assert.js";
|
|
1
|
+
import * as bip39 from "@scure/bip39";
|
|
2
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
3
|
+
import { NonEmptyReadonlyArray } from "../Array.js";
|
|
8
4
|
import {
|
|
9
|
-
createEncryptionKey,
|
|
10
|
-
CreateMnemonicDep,
|
|
11
|
-
CreateRandomBytesDep,
|
|
12
5
|
createSlip21,
|
|
13
|
-
createSlip21Id,
|
|
14
6
|
EncryptionKey,
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
Entropy16,
|
|
8
|
+
Entropy32,
|
|
9
|
+
RandomBytesDep,
|
|
17
10
|
} from "../Crypto.js";
|
|
18
|
-
import { NanoIdLibDep } from "../NanoId.js";
|
|
19
|
-
import { TimeDep } from "../Time.js";
|
|
20
11
|
import {
|
|
21
|
-
Base64Url,
|
|
22
12
|
brand,
|
|
23
|
-
DateIso,
|
|
24
|
-
DateIsoString,
|
|
25
13
|
Id,
|
|
26
|
-
|
|
14
|
+
IdBytes,
|
|
15
|
+
idBytesToId,
|
|
16
|
+
idToIdBytes,
|
|
27
17
|
Mnemonic,
|
|
28
18
|
NonNegativeInt,
|
|
29
|
-
Uint8Array,
|
|
30
19
|
} from "../Type.js";
|
|
31
|
-
import {
|
|
32
|
-
createInitialTimestamp,
|
|
33
|
-
TimestampString,
|
|
34
|
-
timestampToTimestampString,
|
|
35
|
-
} from "./Timestamp.js";
|
|
20
|
+
import { getOrNull } from "../Result.js";
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
/**
|
|
23
|
+
* 32 bytes of cryptographic entropy used to derive {@link Owner} keys.
|
|
24
|
+
*
|
|
25
|
+
* Can be created using {@link createOwnerSecret} or converted from a
|
|
26
|
+
* {@link Mnemonic} using {@link mnemonicToOwnerSecret}.
|
|
27
|
+
*/
|
|
28
|
+
export const OwnerSecret = brand("OwnerSecret", Entropy32);
|
|
29
|
+
export type OwnerSecret = typeof OwnerSecret.Type;
|
|
30
|
+
|
|
31
|
+
/** Creates a {@link OwnerSecret}. */
|
|
32
|
+
export const createOwnerSecret = (deps: RandomBytesDep): OwnerSecret =>
|
|
33
|
+
deps.randomBytes.create(32) as OwnerSecret;
|
|
34
|
+
|
|
35
|
+
/** Converts an {@link OwnerSecret} to a {@link Mnemonic}. */
|
|
36
|
+
export const ownerSecretToMnemonic = (secret: OwnerSecret): Mnemonic =>
|
|
37
|
+
bip39.entropyToMnemonic(secret, wordlist) as Mnemonic;
|
|
38
|
+
|
|
39
|
+
/** Converts a {@link Mnemonic} to an {@link OwnerSecret}. */
|
|
40
|
+
export const mnemonicToOwnerSecret = (mnemonic: Mnemonic): OwnerSecret =>
|
|
41
|
+
bip39.mnemonicToEntropy(mnemonic, wordlist) as OwnerSecret;
|
|
44
42
|
|
|
45
43
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* The Owner represents ownership of data in Evolu. Every database change is
|
|
45
|
+
* assigned to an owner, enabling sync functionality and access control.
|
|
46
|
+
*
|
|
47
|
+
* Owners enable **partial sync** - applications can choose which owners to
|
|
48
|
+
* sync, allowing selective data synchronization based on specific needs.
|
|
49
|
+
*
|
|
50
|
+
* Owners also provide **real data deletion** - while individual changes in
|
|
51
|
+
* local-first/distributed systems can only be marked as deleted, entire owners
|
|
52
|
+
* can be completely deleted from both relays and devices (except for
|
|
53
|
+
* {@link AppOwner}, which must be preserved for sync coordination).
|
|
49
54
|
*
|
|
50
|
-
*
|
|
51
|
-
* {@link EncryptionKey} are deterministically derived using SLIP-21, and an
|
|
52
|
-
* optional {@link WriteKey} that, when present, enables writing to the Evolu
|
|
53
|
-
* Relay or peers. The {@link WriteKey} can be rotated.
|
|
55
|
+
* Evolu provides different owner types depending on their use case:
|
|
54
56
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
57
|
+
* - **Coordination**: {@link AppOwner} for sync coordination and long-term
|
|
58
|
+
* persistence
|
|
59
|
+
* - **Data partitioning**: {@link ShardOwner} for partitioning application data
|
|
60
|
+
* - **Collaboration**: {@link SharedOwner} for collaborative write access
|
|
61
|
+
* - **Data sharing**: {@link SharedReadonlyOwner} for read-only access to shared
|
|
62
|
+
* data
|
|
58
63
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
64
|
+
* Owners are cryptographically derived from an {@link OwnerSecret} using
|
|
65
|
+
* SLIP-21, ensuring secure and deterministic key generation:
|
|
66
|
+
*
|
|
67
|
+
* - {@link OwnerId}: Globally unique public identifier
|
|
68
|
+
* - {@link EncryptionKey}: Symmetric encryption key for data protection
|
|
69
|
+
* - {@link OwnerWriteKey}: Authentication token for write operations (rotatable)
|
|
70
|
+
*
|
|
71
|
+
* @see {@link createOwner}
|
|
63
72
|
*/
|
|
64
73
|
export interface Owner {
|
|
65
|
-
readonly mnemonic: Mnemonic;
|
|
66
|
-
readonly createdAt: DateIsoString;
|
|
67
74
|
readonly id: OwnerId;
|
|
68
|
-
readonly encryptionKey:
|
|
69
|
-
readonly writeKey:
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface OwnerWithWriteAccess {
|
|
73
|
-
readonly id: OwnerId;
|
|
74
|
-
readonly encryptionKey: EncryptionKey;
|
|
75
|
-
readonly writeKey: WriteKey;
|
|
75
|
+
readonly encryptionKey: OwnerEncryptionKey;
|
|
76
|
+
readonly writeKey: OwnerWriteKey;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* This branded {@link Id} type, generated by {@link createSlip21Id}, is a
|
|
82
|
-
* 21-character {@link Base64Url} string (126 bits of entropy), providing a
|
|
83
|
-
* compact, shareable, and secure identifier for UI use, tied to the owner's
|
|
84
|
-
* mnemonic and derivation path.
|
|
80
|
+
* OwnerId is a branded {@link Id} that uniquely identifies an {@link Owner}.
|
|
81
|
+
* Branded from {@link Id} to leverage existing helpers like {@link idToIdBytes}.
|
|
85
82
|
*/
|
|
86
83
|
export const OwnerId = brand("OwnerId", Id);
|
|
87
84
|
export type OwnerId = typeof OwnerId.Type;
|
|
88
85
|
|
|
89
|
-
|
|
86
|
+
/** Bytes representation of {@link OwnerId}. */
|
|
87
|
+
export const OwnerIdBytes = brand("OwnerIdBytes", IdBytes);
|
|
88
|
+
export type OwnerIdBytes = typeof OwnerIdBytes.Type;
|
|
89
|
+
|
|
90
|
+
export const ownerIdToOwnerIdBytes = (ownerId: OwnerId): OwnerIdBytes =>
|
|
91
|
+
idToIdBytes(ownerId) as OwnerIdBytes;
|
|
92
|
+
|
|
93
|
+
export const ownerIdBytesToOwnerId = (ownerIdBytes: OwnerIdBytes): OwnerId =>
|
|
94
|
+
idBytesToId(ownerIdBytes as IdBytes) as OwnerId;
|
|
95
|
+
|
|
96
|
+
export const ownerWriteKeyLength = 16 as NonNegativeInt;
|
|
97
|
+
|
|
98
|
+
export const OwnerEncryptionKey = brand("OwnerEncryptionKey", EncryptionKey);
|
|
99
|
+
export type OwnerEncryptionKey = typeof OwnerEncryptionKey.Type;
|
|
90
100
|
|
|
91
101
|
/**
|
|
92
|
-
* A secure token
|
|
93
|
-
*
|
|
102
|
+
* A secure token for write operations. It's derived from {@link OwnerSecret} by
|
|
103
|
+
* default and can be rotated via {@link createOwnerWriteKey}.
|
|
94
104
|
*/
|
|
95
|
-
export const
|
|
96
|
-
export type
|
|
105
|
+
export const OwnerWriteKey = brand("OwnerWriteKey", Entropy16);
|
|
106
|
+
export type OwnerWriteKey = typeof OwnerWriteKey.Type;
|
|
107
|
+
|
|
108
|
+
/** Creates a randomly generated {@link OwnerWriteKey}. */
|
|
109
|
+
export const createOwnerWriteKey = (deps: RandomBytesDep): OwnerWriteKey =>
|
|
110
|
+
deps.randomBytes.create(16) as OwnerWriteKey;
|
|
97
111
|
|
|
98
112
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
113
|
+
* Creates an {@link Owner} from a {@link OwnerSecret} using SLIP-21 key
|
|
114
|
+
* derivation.
|
|
115
|
+
*
|
|
116
|
+
* This is an internal helper function, use:
|
|
117
|
+
*
|
|
118
|
+
* - {@link createAppOwner}
|
|
119
|
+
* - {@link createShardOwner}
|
|
120
|
+
* - {@link createSharedOwner}
|
|
121
|
+
* - {@link createSharedReadonlyOwner}
|
|
122
|
+
*/
|
|
123
|
+
export const createOwner = (secret: OwnerSecret): Owner => ({
|
|
124
|
+
id: ownerIdBytesToOwnerId(
|
|
125
|
+
OwnerIdBytes.orThrow(
|
|
126
|
+
createSlip21(secret, ["Evolu", "OwnerIdBytes"]).slice(0, 16),
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
encryptionKey: OwnerEncryptionKey.orThrow(
|
|
131
|
+
createSlip21(secret, ["Evolu", "OwnerEncryptionKey"]),
|
|
132
|
+
),
|
|
133
|
+
|
|
134
|
+
writeKey: OwnerWriteKey.orThrow(
|
|
135
|
+
createSlip21(secret, ["Evolu", "OwnerWriteKey"]).slice(0, 16),
|
|
136
|
+
),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The AppOwner represents the application owner. It's created using a
|
|
141
|
+
* cryptographically secure random generator or derived from an external source,
|
|
142
|
+
* e.g., mnemonic stored securely in a hardware device.
|
|
143
|
+
*
|
|
144
|
+
* While it's possible to store all application data in AppOwner, the better
|
|
145
|
+
* approach is to use it only for sync coordination. Storing all app data in
|
|
146
|
+
* AppOwner means that data will be stored/synced forever. And that's a problem
|
|
147
|
+
* if we want to provide real data deletion or in-app data migration without
|
|
148
|
+
* data duplication. In local-first apps/distributed systems, we can't delete
|
|
149
|
+
* individual changes, we only mark them as deleted, otherwise sync could not
|
|
150
|
+
* work.
|
|
151
|
+
*
|
|
152
|
+
* If we really want to delete data or at least avoid syncing it, we must store
|
|
153
|
+
* it using a different owner than AppOwner, e.g. {@link ShardOwner} or
|
|
154
|
+
* {@link SharedOwner}, and delete that owner. The AppOwner itself must be
|
|
155
|
+
* preserved because it coordinates deletion information across devices. Other
|
|
156
|
+
* devices need to sync the information that an owner was deleted so they can
|
|
157
|
+
* delete their local data as well.
|
|
104
158
|
*/
|
|
105
159
|
export interface AppOwner extends Owner {
|
|
106
160
|
readonly type: "AppOwner";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The mnemonic that was used to derive the AppOwner keys. Optional when the
|
|
164
|
+
* AppOwner is created from external keys to avoid sharing the mnemonic with
|
|
165
|
+
* the Evolu app.
|
|
166
|
+
*/
|
|
167
|
+
readonly mnemonic?: Mnemonic | null;
|
|
107
168
|
}
|
|
108
169
|
|
|
170
|
+
/** Creates an {@link AppOwner} from an {@link OwnerSecret}. */
|
|
171
|
+
export const createAppOwner = (secret: OwnerSecret): AppOwner => ({
|
|
172
|
+
type: "AppOwner",
|
|
173
|
+
mnemonic: ownerSecretToMnemonic(secret),
|
|
174
|
+
...createOwner(secret),
|
|
175
|
+
});
|
|
176
|
+
|
|
109
177
|
/**
|
|
110
|
-
*
|
|
111
|
-
* Evolu `initialData` function or dynamically as needed. Its mnemonic is stored
|
|
112
|
-
* in an app table (encrypted by `AppOwner`) and synced between devices. Its
|
|
113
|
-
* `writeKey` is deterministic and rotatable, enabling selective syncing of data
|
|
114
|
-
* subsets. Not intended for sharing outside the app.
|
|
178
|
+
* Transport configuration for connecting to relays.
|
|
115
179
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
180
|
+
* Each {@link Owner} can specify one or more transports to connect to different
|
|
181
|
+
* relays for data synchronization. Currently supports WebSocket transport, with
|
|
182
|
+
* future support planned for Bluetooth, LocalNetwork, and other protocols.
|
|
118
183
|
*/
|
|
119
|
-
export
|
|
120
|
-
readonly type: "ShardOwner";
|
|
121
|
-
readonly mnemonic: Mnemonic;
|
|
122
|
-
readonly writeKey: WriteKey;
|
|
123
|
-
}
|
|
184
|
+
export type TransportConfig = WebSocketTransportConfig;
|
|
124
185
|
|
|
125
186
|
/**
|
|
126
|
-
*
|
|
127
|
-
* controlled access. Its `writeKey` is random (not derived from the mnemonic,
|
|
128
|
-
* stored alongside it in the app table) and rotatable, ensuring it cannot be
|
|
129
|
-
* regenerated by others if shared. Share the `mnemonic` alone for read-only
|
|
130
|
-
* access (as `SharedReadonlyOwner`) or share SharedOwner itself for write
|
|
131
|
-
* access.
|
|
187
|
+
* WebSocket transport configuration for relay connections.
|
|
132
188
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
189
|
+
* Use {@link createWebSocketTransportConfig} to create a properly formatted URL
|
|
190
|
+
* with {@link OwnerId}. The relay uses {@link parseOwnerIdFromUrl} to extract the
|
|
191
|
+
* OwnerId from the query string.
|
|
192
|
+
*
|
|
193
|
+
* ### Authentication and Error Handling
|
|
194
|
+
*
|
|
195
|
+
* When a relay rejects a connection (invalid OwnerId, unauthorized owner, or
|
|
196
|
+
* server error), the browser WebSocket API does not expose the specific HTTP
|
|
197
|
+
* status code or reason - it only reports a generic connection failure. The
|
|
198
|
+
* client automatically retries with exponential backoff and jitter, eventually
|
|
199
|
+
* succeeding once the configuration or server issue is resolved.
|
|
200
|
+
*
|
|
201
|
+
* Legitimate clients will be properly configured with valid credentials, so
|
|
202
|
+
* automatic retry is appropriate.
|
|
203
|
+
*
|
|
204
|
+
* @see {@link createWebSocketTransportConfig}
|
|
205
|
+
* @see {@link parseOwnerIdFromUrl}
|
|
135
206
|
*/
|
|
136
|
-
export interface
|
|
137
|
-
readonly type: "
|
|
138
|
-
readonly
|
|
139
|
-
readonly writeKey: WriteKey;
|
|
207
|
+
export interface WebSocketTransportConfig {
|
|
208
|
+
readonly type: "WebSocket";
|
|
209
|
+
readonly url: string;
|
|
140
210
|
}
|
|
141
211
|
|
|
142
212
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
213
|
+
* Creates a {@link WebSocketTransportConfig} for the given relay URL and
|
|
214
|
+
* {@link OwnerId}.
|
|
215
|
+
*
|
|
216
|
+
* ### Example
|
|
217
|
+
*
|
|
218
|
+
* ```ts
|
|
219
|
+
* const transport = createWebSocketTransportConfig({
|
|
220
|
+
* relayUrl: "wss://relay.evolu.dev",
|
|
221
|
+
* ownerId: owner.id,
|
|
222
|
+
* });
|
|
223
|
+
* // Result: { type: "WebSocket", url: "wss://relay.evolu.dev?ownerId=..." }
|
|
224
|
+
* ```
|
|
148
225
|
*/
|
|
149
|
-
export
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
226
|
+
export const createWebSocketTransportConfig = ({
|
|
227
|
+
relayUrl,
|
|
228
|
+
ownerId,
|
|
229
|
+
}: {
|
|
230
|
+
readonly relayUrl: string;
|
|
231
|
+
readonly ownerId: OwnerId;
|
|
232
|
+
}): WebSocketTransportConfig => ({
|
|
233
|
+
type: "WebSocket",
|
|
234
|
+
url: `${relayUrl}?ownerId=${ownerId}`,
|
|
235
|
+
});
|
|
153
236
|
|
|
154
237
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
238
|
+
* Extracts {@link OwnerId} from a URL query string.
|
|
239
|
+
*
|
|
240
|
+
* Parses the query string `?ownerId=...` and validates that the extracted value
|
|
241
|
+
* is a valid {@link OwnerId}.
|
|
242
|
+
*
|
|
243
|
+
* ### Example
|
|
244
|
+
*
|
|
245
|
+
* ```ts
|
|
246
|
+
* parseOwnerIdFromUrl("/sync?ownerId=_12345678abcdefgh");
|
|
247
|
+
* // Returns: OwnerId or null
|
|
248
|
+
* ```
|
|
157
249
|
*/
|
|
158
|
-
export const
|
|
159
|
-
(
|
|
160
|
-
(mnemonic = deps.createMnemonic()): AppOwner => {
|
|
161
|
-
const owner = createOwner(deps)(mnemonic);
|
|
162
|
-
return { type: "AppOwner", ...owner };
|
|
163
|
-
};
|
|
250
|
+
export const parseOwnerIdFromUrl = (url: string | undefined): OwnerId | null =>
|
|
251
|
+
getOrNull(OwnerId.fromUnknown(url?.split("=")[1]));
|
|
164
252
|
|
|
165
253
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
254
|
+
* An {@link Owner} for sharding data.
|
|
255
|
+
*
|
|
256
|
+
* ShardOwners are the recommended storage location for most application data
|
|
257
|
+
* because they can be completely deleted (both on relays and devices) and
|
|
258
|
+
* conditionally synced.
|
|
259
|
+
*
|
|
260
|
+
* Can be created from {@link OwnerSecret} via {@link createShardOwner} or
|
|
261
|
+
* deterministically derived from {@link AppOwner} using
|
|
262
|
+
* {@link deriveShardOwner}.
|
|
170
263
|
*/
|
|
264
|
+
export interface ShardOwner extends Owner {
|
|
265
|
+
readonly type: "ShardOwner";
|
|
266
|
+
readonly transports?: ReadonlyArray<TransportConfig>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Creates a {@link ShardOwner} from an {@link OwnerSecret}. */
|
|
171
270
|
export const createShardOwner = (
|
|
172
|
-
|
|
271
|
+
secret: OwnerSecret,
|
|
272
|
+
transports?: ReadonlyArray<TransportConfig>,
|
|
173
273
|
): ShardOwner => {
|
|
174
|
-
const owner = createOwner(deps)();
|
|
175
274
|
return {
|
|
176
275
|
type: "ShardOwner",
|
|
177
|
-
|
|
178
|
-
|
|
276
|
+
...createOwner(secret),
|
|
277
|
+
...(transports && { transports }),
|
|
179
278
|
};
|
|
180
279
|
};
|
|
181
280
|
|
|
182
281
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
282
|
+
* Derives a {@link ShardOwner} from an {@link AppOwner} using the specified path.
|
|
283
|
+
*
|
|
284
|
+
* **Advantages of derived owners:**
|
|
285
|
+
*
|
|
286
|
+
* - **Deterministic**: Same path always produces the same ShardOwner across all
|
|
287
|
+
* devices
|
|
288
|
+
* - **Immediate availability**: Can be hardcoded and used before sync occurs
|
|
289
|
+
* - **Consistent setup**: All devices start with identical data structure
|
|
290
|
+
* - **Lifecycle management**: Can implement epoch patterns for clean data
|
|
291
|
+
* deletion and recreation
|
|
292
|
+
*
|
|
293
|
+
* **Common patterns:**
|
|
294
|
+
*
|
|
295
|
+
* - Use paths like `["shard", 1]` for versioned data lifecycle
|
|
296
|
+
* - Use paths like `["project", "MyApp", 1]` for named partitions with versions
|
|
297
|
+
* - Each device can derive the same owners and set up initial structure
|
|
187
298
|
*/
|
|
188
|
-
export const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
type: "SharedOwner",
|
|
195
|
-
mnemonic,
|
|
196
|
-
writeKey,
|
|
197
|
-
};
|
|
198
|
-
};
|
|
299
|
+
export const deriveShardOwner = (
|
|
300
|
+
owner: AppOwner,
|
|
301
|
+
path: NonEmptyReadonlyArray<string | number>,
|
|
302
|
+
transports?: ReadonlyArray<TransportConfig>,
|
|
303
|
+
): ShardOwner => {
|
|
304
|
+
const secret = createSlip21(owner.encryptionKey, path) as OwnerSecret;
|
|
199
305
|
|
|
200
|
-
/**
|
|
201
|
-
* Creates a {@link SharedReadonlyOwner} from a {@link SharedOwner} for read-only
|
|
202
|
-
* data sharing. Extracts the `mnemonic` from the provided {@link SharedOwner},
|
|
203
|
-
* omitting its `writeKey` to ensure read-only access.
|
|
204
|
-
*/
|
|
205
|
-
export const createSharedReadonlyOwner = (
|
|
206
|
-
sharedOwner: SharedOwner,
|
|
207
|
-
): SharedReadonlyOwner => {
|
|
208
306
|
return {
|
|
209
|
-
type: "
|
|
210
|
-
|
|
307
|
+
type: "ShardOwner",
|
|
308
|
+
...createOwner(secret),
|
|
309
|
+
...(transports && { transports }),
|
|
211
310
|
};
|
|
212
311
|
};
|
|
213
312
|
|
|
214
|
-
/**
|
|
215
|
-
export
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const id = createSlip21Id(seed, ["Evolu", "Owner Id"]) as OwnerId;
|
|
221
|
-
const encryptionKey = createEncryptionKey(seed);
|
|
222
|
-
|
|
223
|
-
const createdAt = DateIso.fromParent(new Date(deps.time.now()));
|
|
224
|
-
assert(createdAt.ok, "Invalid DateIso: bad system clock");
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
mnemonic,
|
|
228
|
-
createdAt: createdAt.value,
|
|
229
|
-
id,
|
|
230
|
-
encryptionKey,
|
|
231
|
-
writeKey: writeKey ?? createWriteKey(deps)(seed),
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
export const createWriteKey =
|
|
236
|
-
(deps: CreateRandomBytesDep) =>
|
|
237
|
-
(seed?: MnemonicSeed): WriteKey => {
|
|
238
|
-
const key = seed
|
|
239
|
-
? createSlip21(seed, ["Evolu", "Write Key"]).slice(0, 16)
|
|
240
|
-
: deps.createRandomBytes(16);
|
|
241
|
-
|
|
242
|
-
const writeKey = WriteKey.from(key);
|
|
243
|
-
assert(writeKey.ok, "Ensure valid WriteKey");
|
|
244
|
-
|
|
245
|
-
return writeKey.value;
|
|
246
|
-
};
|
|
313
|
+
/** An {@link Owner} for collaborative data with write access. */
|
|
314
|
+
export interface SharedOwner extends Owner {
|
|
315
|
+
readonly type: "SharedOwner";
|
|
316
|
+
readonly transports?: ReadonlyArray<TransportConfig>;
|
|
317
|
+
}
|
|
247
318
|
|
|
248
319
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* {@link
|
|
320
|
+
* Creates a {@link SharedOwner} from an {@link OwnerSecret} for collaborative
|
|
321
|
+
* write access.
|
|
322
|
+
*
|
|
323
|
+
* Use {@link createSharedReadonlyOwner} to create a read-only version for
|
|
324
|
+
* sharing.
|
|
253
325
|
*/
|
|
254
|
-
export
|
|
255
|
-
|
|
256
|
-
|
|
326
|
+
export const createSharedOwner = (
|
|
327
|
+
secret: OwnerSecret,
|
|
328
|
+
transports?: ReadonlyArray<TransportConfig>,
|
|
329
|
+
): SharedOwner => {
|
|
330
|
+
return {
|
|
331
|
+
type: "SharedOwner",
|
|
332
|
+
...createOwner(secret),
|
|
333
|
+
...(transports && { transports }),
|
|
334
|
+
};
|
|
257
335
|
};
|
|
258
336
|
|
|
259
337
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
338
|
+
* Read-only version of a {@link SharedOwner} for data sharing. Contains only the
|
|
339
|
+
* {@link OwnerId} and {@link EncryptionKey} needed for others to read the shared
|
|
340
|
+
* data without write access.
|
|
263
341
|
*/
|
|
264
|
-
export
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
switch (owner.type) {
|
|
271
|
-
case "AppOwner": {
|
|
272
|
-
const { type, ...rest } = owner;
|
|
273
|
-
return { ...rest, timestamp };
|
|
274
|
-
}
|
|
275
|
-
case "ShardOwner":
|
|
276
|
-
case "SharedOwner":
|
|
277
|
-
return {
|
|
278
|
-
...createOwner(deps)(owner.mnemonic, owner.writeKey),
|
|
279
|
-
timestamp,
|
|
280
|
-
};
|
|
281
|
-
case "SharedReadonlyOwner":
|
|
282
|
-
return {
|
|
283
|
-
...createOwner(deps)(owner.mnemonic),
|
|
284
|
-
writeKey: null,
|
|
285
|
-
timestamp,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
};
|
|
342
|
+
export interface SharedReadonlyOwner {
|
|
343
|
+
readonly type: "SharedReadonlyOwner";
|
|
344
|
+
readonly id: OwnerId;
|
|
345
|
+
readonly encryptionKey: EncryptionKey;
|
|
346
|
+
readonly transports?: ReadonlyArray<TransportConfig>;
|
|
347
|
+
}
|
|
289
348
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
...owner,
|
|
300
|
-
writeKey: newWriteKey,
|
|
301
|
-
} as T;
|
|
302
|
-
};
|
|
349
|
+
/** Creates a {@link SharedReadonlyOwner} from a {@link SharedOwner}. */
|
|
350
|
+
export const createSharedReadonlyOwner = (
|
|
351
|
+
sharedOwner: SharedOwner,
|
|
352
|
+
): SharedReadonlyOwner => ({
|
|
353
|
+
type: "SharedReadonlyOwner",
|
|
354
|
+
id: sharedOwner.id,
|
|
355
|
+
encryptionKey: sharedOwner.encryptionKey,
|
|
356
|
+
...(sharedOwner.transports && { transports: sharedOwner.transports }),
|
|
357
|
+
});
|
package/src/Evolu/Platform.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Config } from "./Config.js";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* FlushSync is for libraries like React to flush updates synchronously inside
|
|
5
3
|
* the provided callback to ensure the DOM is updated immediately.
|
|
@@ -16,12 +14,14 @@ export interface FlushSyncDep {
|
|
|
16
14
|
readonly flushSync: FlushSync;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Reload the app in a platform-specific way.
|
|
19
|
+
*
|
|
20
|
+
* - **Web**: Redirects to the specified URL
|
|
21
|
+
* - **React Native**: Restarts the app (URL parameter ignored)
|
|
22
|
+
*/
|
|
23
|
+
export type ReloadApp = (url: string) => void;
|
|
24
24
|
|
|
25
|
-
export interface
|
|
26
|
-
readonly
|
|
25
|
+
export interface ReloadAppDep {
|
|
26
|
+
readonly reloadApp: ReloadApp;
|
|
27
27
|
}
|