@evolu/common 6.0.1-preview.14 → 6.0.1-preview.16
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/Crypto.d.ts +0 -1
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +2 -1
- package/dist/src/Evolu/Config.d.ts +7 -7
- package/dist/src/Evolu/Config.d.ts.map +1 -1
- package/dist/src/Evolu/Db.d.ts +4 -8
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +136 -112
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +5 -4
- package/dist/src/Evolu/Owner.d.ts +84 -105
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +78 -89
- package/dist/src/Evolu/Protocol.d.ts +6 -2
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +15 -1
- package/dist/src/Evolu/Schema.d.ts +0 -7
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Crypto.ts +6 -4
- package/src/Evolu/Config.ts +7 -7
- package/src/Evolu/Db.ts +204 -159
- package/src/Evolu/Evolu.ts +6 -4
- package/src/Evolu/Owner.ts +128 -203
- package/src/Evolu/Protocol.ts +18 -8
- package/src/Evolu/Schema.ts +6 -7
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -583,11 +583,13 @@ const createEvoluInstance =
|
|
|
583
583
|
|
|
584
584
|
deps.console.log("[evolu]", "createEvoluInstance");
|
|
585
585
|
|
|
586
|
+
// evoluConfig.mnemonic
|
|
587
|
+
|
|
586
588
|
const { initialData, indexes, ...config } = evoluConfig;
|
|
587
589
|
|
|
588
590
|
const errorStore = createStore<EvoluError | null>(null);
|
|
589
591
|
const rowsStore = createStore<QueryRowsMap>(new Map());
|
|
590
|
-
const
|
|
592
|
+
const appOwnerStore = createStore<AppOwner | null>(null);
|
|
591
593
|
const syncStore = createStore<SyncState>(initialSyncState);
|
|
592
594
|
|
|
593
595
|
const subscribedQueries = createSubscribedQueries(rowsStore);
|
|
@@ -605,7 +607,7 @@ const createEvoluInstance =
|
|
|
605
607
|
dbWorker.onMessage((message) => {
|
|
606
608
|
switch (message.type) {
|
|
607
609
|
case "onInit": {
|
|
608
|
-
|
|
610
|
+
appOwnerStore.set(message.appOwner);
|
|
609
611
|
break;
|
|
610
612
|
}
|
|
611
613
|
|
|
@@ -907,8 +909,8 @@ const createEvoluInstance =
|
|
|
907
909
|
getQueryRows: <R extends Row>(query: Query<R>): QueryRows<R> =>
|
|
908
910
|
(rowsStore.get().get(query) ?? emptyRows) as QueryRows<R>,
|
|
909
911
|
|
|
910
|
-
subscribeAppOwner:
|
|
911
|
-
getAppOwner:
|
|
912
|
+
subscribeAppOwner: appOwnerStore.subscribe,
|
|
913
|
+
getAppOwner: appOwnerStore.get,
|
|
912
914
|
|
|
913
915
|
subscribeSyncState: syncStore.subscribe,
|
|
914
916
|
getSyncState: syncStore.get,
|
package/src/Evolu/Owner.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Evolu Owner - Data Ownership and Collaboration
|
|
3
|
+
*
|
|
4
|
+
* An {@link Owner} is an entity that represents ownership of data in Evolu. It
|
|
5
|
+
* consists of cryptographic keys derived from a {@link Mnemonic} via SLIP-21:
|
|
6
|
+
*
|
|
7
|
+
* - **{@link OwnerId}**: Globally unique public identifier
|
|
8
|
+
* - **{@link EncryptionKey}**: Symmetric encryption key for data protection
|
|
9
|
+
* - **{@link WriteKey}**: Authentication token for write operations
|
|
10
|
+
*
|
|
11
|
+
* Every Evolu app has at least one owner, the {@link AppOwner}. There are
|
|
12
|
+
* several owner variants for different use cases:
|
|
13
|
+
*
|
|
14
|
+
* **{@link ShardOwner}**: Derived from {@link AppOwner} for partitioning data and
|
|
15
|
+
* selective synchronization using {@link createShardOwner}
|
|
16
|
+
*
|
|
17
|
+
* **{@link SharedOwner}**: Created for collaboration with write access, not
|
|
18
|
+
* meant to be shared directly
|
|
19
|
+
*
|
|
20
|
+
* **{@link SharedReadonlyOwner}**: Read-only version for safe data sharing,
|
|
21
|
+
* created from {@link SharedOwner} using {@link createSharedReadonlyOwner}
|
|
22
|
+
*
|
|
23
|
+
* Owners are designed for data synchronization and backup. Authentication
|
|
24
|
+
* systems built on public/private key cryptography use these primitives. This
|
|
25
|
+
* design ensures Evolu Relay knows as little as possible - it only sees
|
|
26
|
+
* Timestamp, OwnerId, and EncryptedDbChange.
|
|
3
27
|
*
|
|
4
28
|
* @module
|
|
5
29
|
*/
|
|
6
30
|
|
|
7
|
-
import {
|
|
31
|
+
import { NonEmptyReadonlyArray } from "../Array.js";
|
|
8
32
|
import {
|
|
9
|
-
createEncryptionKey,
|
|
10
33
|
CreateMnemonicDep,
|
|
11
34
|
CreateRandomBytesDep,
|
|
12
35
|
createSlip21,
|
|
@@ -15,61 +38,26 @@ import {
|
|
|
15
38
|
MnemonicSeed,
|
|
16
39
|
mnemonicToMnemonicSeed,
|
|
17
40
|
} from "../Crypto.js";
|
|
18
|
-
import { NanoIdLibDep } from "../NanoId.js";
|
|
19
|
-
import { TimeDep } from "../Time.js";
|
|
20
41
|
import {
|
|
21
42
|
Base64Url,
|
|
22
43
|
brand,
|
|
23
|
-
DateIso,
|
|
24
|
-
DateIsoString,
|
|
25
44
|
Id,
|
|
26
45
|
length,
|
|
27
46
|
Mnemonic,
|
|
28
47
|
NonNegativeInt,
|
|
29
48
|
Uint8Array,
|
|
30
49
|
} from "../Type.js";
|
|
31
|
-
import {
|
|
32
|
-
createInitialTimestamp,
|
|
33
|
-
TimestampString,
|
|
34
|
-
timestampToTimestampString,
|
|
35
|
-
} from "./Timestamp.js";
|
|
36
|
-
|
|
37
|
-
// TODO: Clean API
|
|
38
|
-
// - createOwner should be createAppOwner
|
|
39
|
-
// - Docs mention WriteKey is optional but it's required in Owner.
|
|
40
|
-
// - For Protocol, we need only ownerId, encryptionKey, and writeKey.
|
|
41
|
-
// - Not sure whether we need JSDoc for this module, and we don't
|
|
42
|
-
// use modules for Evolu internal API yet.
|
|
43
|
-
// It's not single responsibility API.
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* An owner has a {@link Mnemonic} from which {@link OwnerId} and
|
|
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.
|
|
52
|
+
* Represents ownership of data in Evolu. Created from a {@link Mnemonic} via
|
|
53
|
+
* SLIP-21 key derivation using {@link createOwner}, providing cryptographic keys
|
|
54
|
+
* for data access and authentication.
|
|
54
55
|
*
|
|
55
|
-
*
|
|
56
|
-
* {@link
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Public-key cryptography isn’t included here as it belongs to app and varies
|
|
60
|
-
* by use case. An Evolu app without collaboration doesn’t need it, while a
|
|
61
|
-
* Nostr-like app can leverage Nostr NIPs, or a super-safe app can use
|
|
62
|
-
* post-quantum cryptography.
|
|
56
|
+
* - {@link OwnerId}: Globally unique public identifier
|
|
57
|
+
* - {@link EncryptionKey}: Symmetric encryption key for data protection
|
|
58
|
+
* - {@link WriteKey}: Authentication token for write operations (rotatable)
|
|
63
59
|
*/
|
|
64
60
|
export interface Owner {
|
|
65
|
-
readonly mnemonic: Mnemonic;
|
|
66
|
-
readonly createdAt: DateIsoString;
|
|
67
|
-
readonly id: OwnerId;
|
|
68
|
-
readonly encryptionKey: EncryptionKey;
|
|
69
|
-
readonly writeKey: WriteKey;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface OwnerWithWriteAccess {
|
|
73
61
|
readonly id: OwnerId;
|
|
74
62
|
readonly encryptionKey: EncryptionKey;
|
|
75
63
|
readonly writeKey: WriteKey;
|
|
@@ -89,203 +77,140 @@ export type OwnerId = typeof OwnerId.Type;
|
|
|
89
77
|
export const writeKeyLength = 16 as NonNegativeInt;
|
|
90
78
|
|
|
91
79
|
/**
|
|
92
|
-
* A secure token proving the initiator can write changes. Derived from a
|
|
93
|
-
* mnemonic or randomly generated. It
|
|
80
|
+
* A secure token proving that the initiator can write changes. Derived from a
|
|
81
|
+
* mnemonic or randomly generated via {@link createWriteKey}. It is rotatable.
|
|
94
82
|
*/
|
|
95
83
|
export const WriteKey = brand("WriteKey", length(writeKeyLength)(Uint8Array));
|
|
96
84
|
export type WriteKey = typeof WriteKey.Type;
|
|
97
85
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* existing mnemonic. It manages the app's core data, including the storage of
|
|
102
|
-
* other owners' mnemonics in an encrypted app table. Its `writeKey` is
|
|
103
|
-
* deterministic and rotatable. Never share the AppOwner mnemonic with anyone.
|
|
104
|
-
*/
|
|
105
|
-
export interface AppOwner extends Owner {
|
|
106
|
-
readonly type: "AppOwner";
|
|
107
|
-
}
|
|
86
|
+
/** Creates a randomly generated {@link WriteKey}. */
|
|
87
|
+
export const createWriteKey = (deps: CreateRandomBytesDep): WriteKey =>
|
|
88
|
+
deps.createRandomBytes(16) as unknown as WriteKey;
|
|
108
89
|
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* subsets. Not intended for sharing outside the app.
|
|
115
|
-
*
|
|
116
|
-
* This type omits `id`, `encryptionKey`, and `createdAt` as they are derived by
|
|
117
|
-
* Evolu from the `mnemonic`, reducing storage overhead.
|
|
118
|
-
*/
|
|
119
|
-
export interface ShardOwner {
|
|
120
|
-
readonly type: "ShardOwner";
|
|
121
|
-
readonly mnemonic: Mnemonic;
|
|
122
|
-
readonly writeKey: WriteKey;
|
|
123
|
-
}
|
|
90
|
+
/** Creates an {@link Owner} from a {@link Mnemonic} using SLIP-21 key derivation. */
|
|
91
|
+
export const createOwner = (mnemonic: Mnemonic): Owner => {
|
|
92
|
+
const seed = mnemonicToMnemonicSeed(mnemonic);
|
|
93
|
+
return createOwnerFromMnemonicSeed(seed);
|
|
94
|
+
};
|
|
124
95
|
|
|
125
96
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
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.
|
|
132
|
-
*
|
|
133
|
-
* This type omits `id`, `encryptionKey`, and `createdAt` as they are derived by
|
|
134
|
-
* Evolu from the `mnemonic`, reducing storage overhead.
|
|
97
|
+
* Creates an {@link Owner} from a {@link MnemonicSeed} using SLIP-21 key
|
|
98
|
+
* derivation.
|
|
135
99
|
*/
|
|
136
|
-
export
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
100
|
+
export const createOwnerFromMnemonicSeed = (seed: MnemonicSeed): Owner => ({
|
|
101
|
+
id: createSlip21Id(seed, ["Evolu", "Owner Id"]) as OwnerId,
|
|
102
|
+
|
|
103
|
+
encryptionKey: createSlip21(seed, [
|
|
104
|
+
"Evolu",
|
|
105
|
+
"Encryption Key",
|
|
106
|
+
]) as EncryptionKey,
|
|
107
|
+
|
|
108
|
+
writeKey: createSlip21(seed, ["Evolu", "Write Key"]).slice(0, 16) as WriteKey,
|
|
109
|
+
});
|
|
141
110
|
|
|
142
111
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* Typically derived from a `SharedOwner` by sharing its `mnemonic` without the
|
|
147
|
-
* `writeKey`.
|
|
112
|
+
* The owner representing app data. Can be created from a {@link Mnemonic} or
|
|
113
|
+
* from external keys when the mnemonic should not be shared with the Evolu
|
|
114
|
+
* app.
|
|
148
115
|
*/
|
|
149
|
-
export interface
|
|
150
|
-
readonly type: "
|
|
151
|
-
|
|
116
|
+
export interface AppOwner extends Owner {
|
|
117
|
+
readonly type: "AppOwner";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The mnemonic that was used to derive the AppOwner keys. Optional when the
|
|
121
|
+
* AppOwner is created from external keys to avoid sharing the mnemonic with
|
|
122
|
+
* the Evolu app.
|
|
123
|
+
*/
|
|
124
|
+
readonly mnemonic?: Mnemonic | null;
|
|
152
125
|
}
|
|
153
126
|
|
|
127
|
+
export const createAppOwner = (mnemonic: Mnemonic): AppOwner => ({
|
|
128
|
+
type: "AppOwner",
|
|
129
|
+
mnemonic,
|
|
130
|
+
...createOwner(mnemonic),
|
|
131
|
+
});
|
|
132
|
+
|
|
154
133
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
134
|
+
* Owner for sharding app data. Allows partitioning of database changes for
|
|
135
|
+
* selective synchronization.
|
|
157
136
|
*/
|
|
158
|
-
export
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const owner = createOwner(deps)(mnemonic);
|
|
162
|
-
return { type: "AppOwner", ...owner };
|
|
163
|
-
};
|
|
137
|
+
export interface ShardOwner extends Owner {
|
|
138
|
+
readonly type: "ShardOwner";
|
|
139
|
+
}
|
|
164
140
|
|
|
165
141
|
/**
|
|
166
|
-
* Creates a {@link ShardOwner}
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
142
|
+
* Creates a {@link ShardOwner} derived from an {@link AppOwner} using the
|
|
143
|
+
* specified path.
|
|
144
|
+
*
|
|
145
|
+
* ### Example
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* const contactsShard = createShardOwner(appOwner, ["contacts"]);
|
|
149
|
+
* const projectShard = createShardOwner(appOwner, [
|
|
150
|
+
* "projects",
|
|
151
|
+
* "project-1",
|
|
152
|
+
* ]);
|
|
153
|
+
* ```
|
|
170
154
|
*/
|
|
171
155
|
export const createShardOwner = (
|
|
172
|
-
|
|
156
|
+
appOwner: AppOwner,
|
|
157
|
+
path: NonEmptyReadonlyArray<string>,
|
|
173
158
|
): ShardOwner => {
|
|
174
|
-
|
|
159
|
+
/**
|
|
160
|
+
* The shardSeed is never shared or persisted, only used for SLIP-21
|
|
161
|
+
* derivation to create shard-specific keys.
|
|
162
|
+
*/
|
|
163
|
+
const shardSeed = createSlip21(
|
|
164
|
+
appOwner.encryptionKey as unknown as MnemonicSeed,
|
|
165
|
+
path,
|
|
166
|
+
) as MnemonicSeed;
|
|
167
|
+
|
|
175
168
|
return {
|
|
176
169
|
type: "ShardOwner",
|
|
177
|
-
|
|
178
|
-
writeKey: owner.writeKey,
|
|
170
|
+
...createOwnerFromMnemonicSeed(shardSeed),
|
|
179
171
|
};
|
|
180
172
|
};
|
|
181
173
|
|
|
182
174
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
175
|
+
* Owner for collaborative data with write access. Created by a user for their
|
|
176
|
+
* own use, not meant to be shared directly. To share data, use
|
|
177
|
+
* {@link createSharedReadonlyOwner} to create a {@link SharedReadonlyOwner} for
|
|
178
|
+
* read-only access.
|
|
187
179
|
*/
|
|
188
|
-
export
|
|
189
|
-
|
|
190
|
-
|
|
180
|
+
export interface SharedOwner extends Owner {
|
|
181
|
+
readonly type: "SharedOwner";
|
|
182
|
+
readonly mnemonic: Mnemonic;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Creates a {@link SharedOwner} with a freshly generated {@link Mnemonic}. */
|
|
186
|
+
export const createSharedOwner = (deps: CreateMnemonicDep): SharedOwner => {
|
|
191
187
|
const mnemonic = deps.createMnemonic();
|
|
192
|
-
const writeKey = createWriteKey(deps)(); // Random, no seed
|
|
193
188
|
return {
|
|
194
189
|
type: "SharedOwner",
|
|
195
190
|
mnemonic,
|
|
196
|
-
|
|
191
|
+
...createOwner(mnemonic),
|
|
197
192
|
};
|
|
198
193
|
};
|
|
199
194
|
|
|
200
195
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
196
|
+
* Read-only version of a {@link SharedOwner} for data sharing. Contains only the
|
|
197
|
+
* {@link OwnerId} and {@link EncryptionKey} needed for others to read the shared
|
|
198
|
+
* data without write access.
|
|
204
199
|
*/
|
|
200
|
+
export interface SharedReadonlyOwner {
|
|
201
|
+
readonly type: "SharedReadonlyOwner";
|
|
202
|
+
readonly id: OwnerId;
|
|
203
|
+
readonly encryptionKey: EncryptionKey;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Creates a {@link SharedReadonlyOwner} from a {@link SharedOwner}. */
|
|
205
207
|
export const createSharedReadonlyOwner = (
|
|
206
208
|
sharedOwner: SharedOwner,
|
|
207
|
-
): SharedReadonlyOwner => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/** Creates an {@link Owner} with optional `mnemonic` and `writeKey`. */
|
|
215
|
-
export const createOwner =
|
|
216
|
-
(deps: TimeDep & CreateRandomBytesDep & CreateMnemonicDep) =>
|
|
217
|
-
(mnemonic = deps.createMnemonic(), writeKey?: WriteKey): Owner => {
|
|
218
|
-
const seed = mnemonicToMnemonicSeed(mnemonic);
|
|
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
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* An `OwnerRow` represents a row in the `evolu_owner` table, based on an
|
|
250
|
-
* {@link Owner} with an added `timestamp` ({@link TimestampString}) for CRDT
|
|
251
|
-
* sync. It supports all {@link Owner} variants with an optional `writeKey`; use
|
|
252
|
-
* {@link createOwnerRow} to align it with a specific {@link Owner}.
|
|
253
|
-
*/
|
|
254
|
-
export type OwnerRow = Omit<Owner, "writeKey"> & {
|
|
255
|
-
readonly writeKey: WriteKey | null;
|
|
256
|
-
readonly timestamp: TimestampString;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Creates an {@link OwnerRow} from any {@link Owner} variant for the
|
|
261
|
-
* `evolu_owner` table, adding a `timestamp` ({@link TimestampString}) for CRDT
|
|
262
|
-
* sync.
|
|
263
|
-
*/
|
|
264
|
-
export const createOwnerRow =
|
|
265
|
-
(deps: TimeDep & CreateRandomBytesDep & CreateMnemonicDep & NanoIdLibDep) =>
|
|
266
|
-
(
|
|
267
|
-
owner: AppOwner | ShardOwner | SharedOwner | SharedReadonlyOwner,
|
|
268
|
-
): OwnerRow => {
|
|
269
|
-
const timestamp = timestampToTimestampString(createInitialTimestamp(deps));
|
|
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
|
-
};
|
|
209
|
+
): SharedReadonlyOwner => ({
|
|
210
|
+
type: "SharedReadonlyOwner",
|
|
211
|
+
id: sharedOwner.id,
|
|
212
|
+
encryptionKey: sharedOwner.encryptionKey,
|
|
213
|
+
});
|
|
289
214
|
|
|
290
215
|
/**
|
|
291
216
|
* Rotates the {@link WriteKey} for an {@link AppOwner}, {@link ShardOwner}, or
|
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
* if further sync is needed or possible, continuing until both sides are
|
|
62
62
|
* synchronized.
|
|
63
63
|
*
|
|
64
|
+
* The **non-initiator always responds** to provide sync completion feedback,
|
|
65
|
+
* even with empty messages containing only the header and no error. This allows
|
|
66
|
+
* the initiator to reliably detect when synchronization is complete.
|
|
67
|
+
*
|
|
64
68
|
* Both **Messages** and **Ranges** are optional, allowing each side to send,
|
|
65
69
|
* sync, or only subscribe data as needed.
|
|
66
70
|
*
|
|
@@ -179,13 +183,7 @@ import {
|
|
|
179
183
|
record,
|
|
180
184
|
} from "../Type.js";
|
|
181
185
|
import { Brand, Predicate } from "../Types.js";
|
|
182
|
-
import {
|
|
183
|
-
Owner,
|
|
184
|
-
OwnerId,
|
|
185
|
-
OwnerWithWriteAccess,
|
|
186
|
-
WriteKey,
|
|
187
|
-
writeKeyLength,
|
|
188
|
-
} from "./Owner.js";
|
|
186
|
+
import { Owner, OwnerId, WriteKey, writeKeyLength } from "./Owner.js";
|
|
189
187
|
import {
|
|
190
188
|
BinaryTimestamp,
|
|
191
189
|
binaryTimestampLength,
|
|
@@ -469,7 +467,7 @@ export interface ProtocolTimestampMismatchError {
|
|
|
469
467
|
export const createProtocolMessageFromCrdtMessages =
|
|
470
468
|
(deps: SymmetricCryptoDep & CreateRandomBytesDep) =>
|
|
471
469
|
(
|
|
472
|
-
owner:
|
|
470
|
+
owner: Owner,
|
|
473
471
|
messages: NonEmptyReadonlyArray<CrdtMessage>,
|
|
474
472
|
maxSize?: PositiveInt,
|
|
475
473
|
): ProtocolMessage => {
|
|
@@ -1162,6 +1160,11 @@ const sync =
|
|
|
1162
1160
|
const ranges = decodeRanges(input);
|
|
1163
1161
|
|
|
1164
1162
|
if (!isNonEmptyReadonlyArray(ranges)) {
|
|
1163
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
1164
|
+
// even when there's nothing to sync.
|
|
1165
|
+
if (role === "non-initiator") {
|
|
1166
|
+
return ok(output.unwrap());
|
|
1167
|
+
}
|
|
1165
1168
|
// Nothing to sync.
|
|
1166
1169
|
return ok(null);
|
|
1167
1170
|
}
|
|
@@ -1362,6 +1365,13 @@ const sync =
|
|
|
1362
1365
|
|
|
1363
1366
|
// If all ranges were skipped, there are no changes and sync is complete.
|
|
1364
1367
|
const hasChange = output.getSize() > outputInitialSize;
|
|
1368
|
+
|
|
1369
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
1370
|
+
// even with empty messages. This allows clients to detect sync completion.
|
|
1371
|
+
if (role === "non-initiator" && !hasChange) {
|
|
1372
|
+
return ok(output.unwrap());
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1365
1375
|
return ok(hasChange ? output.unwrap() : null);
|
|
1366
1376
|
};
|
|
1367
1377
|
|
package/src/Evolu/Schema.ts
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
import { Simplify } from "../Types.js";
|
|
31
31
|
import { DbSchema } from "./Db.js";
|
|
32
32
|
import { createIndexes, DbIndexesBuilder } from "./Kysely.js";
|
|
33
|
-
import { AppOwner, ShardOwner, SharedOwner } from "./Owner.js";
|
|
34
33
|
import {
|
|
35
34
|
BinaryId,
|
|
36
35
|
maxProtocolMessageRangesSize,
|
|
@@ -253,12 +252,12 @@ export interface MutationOptions {
|
|
|
253
252
|
*/
|
|
254
253
|
readonly onlyValidate?: boolean;
|
|
255
254
|
|
|
256
|
-
/**
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
readonly owner?: ShardOwner | SharedOwner;
|
|
255
|
+
// /**
|
|
256
|
+
// * The owner to use for this mutation. Can be a {@link ShardOwner} for sharding
|
|
257
|
+
// * app data or a {@link SharedOwner} for collaborative write access. If
|
|
258
|
+
// * omitted, defaults to the app's {@link AppOwner}.
|
|
259
|
+
// */
|
|
260
|
+
// readonly owner?: ShardOwner | SharedOwner;
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
/**
|