@evolu/common 6.0.1-preview.15 → 6.0.1-preview.17
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 +5 -9
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +139 -117
- package/dist/src/Evolu/Evolu.d.ts +21 -19
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +9 -31
- 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 +79 -89
- package/dist/src/Evolu/Protocol.d.ts +2 -2
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +2 -1
- package/dist/src/Evolu/Public.d.ts +1 -1
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.d.ts +0 -7
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +1 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1 -0
- package/package.json +1 -1
- package/src/Crypto.ts +6 -4
- package/src/Evolu/Config.ts +7 -7
- package/src/Evolu/Db.ts +206 -169
- package/src/Evolu/Evolu.ts +30 -56
- package/src/Evolu/Owner.ts +129 -203
- package/src/Evolu/Protocol.ts +3 -8
- package/src/Evolu/Public.ts +1 -6
- package/src/Evolu/Schema.ts +1 -9
- package/src/Result.ts +1 -0
- package/src/Type.ts +1 -0
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -465,36 +465,36 @@ export type EvoluDeps = CreateDbWorkerDep &
|
|
|
465
465
|
ConsoleDep &
|
|
466
466
|
CreateAppStateDep;
|
|
467
467
|
|
|
468
|
-
export interface
|
|
469
|
-
extends Config {
|
|
468
|
+
export interface EvoluConfigWithFunctions extends Config {
|
|
470
469
|
/**
|
|
471
|
-
*
|
|
470
|
+
* Callback invoked when the database is initialized. Use `isFirst` to perform
|
|
471
|
+
* one-time setup like initial data seeding.
|
|
472
472
|
*
|
|
473
473
|
* ### Example
|
|
474
474
|
*
|
|
475
475
|
* ```ts
|
|
476
476
|
* const evolu = createEvolu(evoluReactWebDeps)(Schema, {
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
477
|
+
* onInit: ({ appOwner, isFirst }) => {
|
|
478
|
+
* if (isFirst) {
|
|
479
|
+
* const todoCategoryId = getOrThrow(
|
|
480
|
+
* evolu.insert("todoCategory", {
|
|
481
|
+
* name: "Not Urgent",
|
|
482
|
+
* }),
|
|
483
|
+
* );
|
|
481
484
|
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* categoryId: todoCategory.value.id,
|
|
488
|
-
* });
|
|
485
|
+
* evolu.insert("todo", {
|
|
486
|
+
* title: "Try React Suspense",
|
|
487
|
+
* categoryId: todoCategoryId.id,
|
|
488
|
+
* });
|
|
489
|
+
* }
|
|
489
490
|
* },
|
|
490
491
|
* });
|
|
491
492
|
* ```
|
|
492
493
|
*/
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
insert: Mutation<S, "insert">;
|
|
494
|
+
readonly onInit?: (params: {
|
|
495
|
+
readonly appOwner: AppOwner;
|
|
496
|
+
readonly isFirst: boolean;
|
|
497
|
+
}) => void;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
// For hot reloading and Evolu multitenancy.
|
|
@@ -553,7 +553,7 @@ export const createEvolu =
|
|
|
553
553
|
(deps: EvoluDeps) =>
|
|
554
554
|
<S extends EvoluSchema>(
|
|
555
555
|
schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>,
|
|
556
|
-
partialConfig: Partial<
|
|
556
|
+
partialConfig: Partial<EvoluConfigWithFunctions> = {},
|
|
557
557
|
): Evolu<S> => {
|
|
558
558
|
const config = { ...defaultConfig, ...partialConfig };
|
|
559
559
|
|
|
@@ -577,17 +577,17 @@ const createEvoluInstance =
|
|
|
577
577
|
(deps: EvoluDeps) =>
|
|
578
578
|
(
|
|
579
579
|
schema: EvoluSchema,
|
|
580
|
-
evoluConfig:
|
|
580
|
+
evoluConfig: EvoluConfigWithFunctions,
|
|
581
581
|
): InternalEvoluInstance => {
|
|
582
582
|
deps.console.enabled = evoluConfig.enableLogging ?? false;
|
|
583
583
|
|
|
584
584
|
deps.console.log("[evolu]", "createEvoluInstance");
|
|
585
585
|
|
|
586
|
-
const {
|
|
586
|
+
const { onInit, indexes, ...config } = evoluConfig;
|
|
587
587
|
|
|
588
588
|
const errorStore = createStore<EvoluError | null>(null);
|
|
589
589
|
const rowsStore = createStore<QueryRowsMap>(new Map());
|
|
590
|
-
const
|
|
590
|
+
const appOwnerStore = createStore<AppOwner | null>(null);
|
|
591
591
|
const syncStore = createStore<SyncState>(initialSyncState);
|
|
592
592
|
|
|
593
593
|
const subscribedQueries = createSubscribedQueries(rowsStore);
|
|
@@ -605,7 +605,11 @@ const createEvoluInstance =
|
|
|
605
605
|
dbWorker.onMessage((message) => {
|
|
606
606
|
switch (message.type) {
|
|
607
607
|
case "onInit": {
|
|
608
|
-
|
|
608
|
+
appOwnerStore.set(message.appOwner);
|
|
609
|
+
onInit?.({
|
|
610
|
+
appOwner: message.appOwner,
|
|
611
|
+
isFirst: message.isFirst,
|
|
612
|
+
});
|
|
609
613
|
break;
|
|
610
614
|
}
|
|
611
615
|
|
|
@@ -700,40 +704,10 @@ const createEvoluInstance =
|
|
|
700
704
|
return type;
|
|
701
705
|
};
|
|
702
706
|
|
|
703
|
-
const initialDataDbChanges: Array<DbChange> = [];
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Note that the initial data function is called even if it is unnecessary
|
|
707
|
-
* (initial data are already in the DB) because we don't want to wait for
|
|
708
|
-
* SQLite's response. Initial data should be small (because they are inlined
|
|
709
|
-
* in the code), so it's ok.
|
|
710
|
-
*/
|
|
711
|
-
if (initialData)
|
|
712
|
-
initialData({
|
|
713
|
-
insert: (table, props) => {
|
|
714
|
-
const id = createId(deps);
|
|
715
|
-
const values = getMutationType(table, "insert").fromUnknown(props);
|
|
716
|
-
|
|
717
|
-
if (values.ok) {
|
|
718
|
-
const valuesWithCreatedAt = {
|
|
719
|
-
...values.value,
|
|
720
|
-
createdAt: new Date(deps.time.now()).toISOString(),
|
|
721
|
-
};
|
|
722
|
-
const dbChange = { table, id, values: valuesWithCreatedAt };
|
|
723
|
-
assertValidDbChange(dbChange);
|
|
724
|
-
initialDataDbChanges.push(dbChange);
|
|
725
|
-
return ok({ id });
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
return values;
|
|
729
|
-
},
|
|
730
|
-
});
|
|
731
|
-
|
|
732
707
|
dbWorker.postMessage({
|
|
733
708
|
type: "init",
|
|
734
709
|
config,
|
|
735
710
|
dbSchema,
|
|
736
|
-
initialData: initialDataDbChanges,
|
|
737
711
|
});
|
|
738
712
|
|
|
739
713
|
const loadQueryMicrotaskQueue: Array<Query> = [];
|
|
@@ -907,8 +881,8 @@ const createEvoluInstance =
|
|
|
907
881
|
getQueryRows: <R extends Row>(query: Query<R>): QueryRows<R> =>
|
|
908
882
|
(rowsStore.get().get(query) ?? emptyRows) as QueryRows<R>,
|
|
909
883
|
|
|
910
|
-
subscribeAppOwner:
|
|
911
|
-
getAppOwner:
|
|
884
|
+
subscribeAppOwner: appOwnerStore.subscribe,
|
|
885
|
+
getAppOwner: appOwnerStore.get,
|
|
912
886
|
|
|
913
887
|
subscribeSyncState: syncStore.subscribe,
|
|
914
888
|
getSyncState: syncStore.get,
|
package/src/Evolu/Owner.ts
CHANGED
|
@@ -1,12 +1,36 @@
|
|
|
1
|
+
/* eslint-disable jsdoc/no-undefined-types */
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Evolu Owner - Data Ownership and Collaboration
|
|
4
|
+
*
|
|
5
|
+
* An {@link Owner} is an entity that represents ownership of data in Evolu. It
|
|
6
|
+
* consists of cryptographic keys derived from a {@link Mnemonic} via SLIP-21:
|
|
7
|
+
*
|
|
8
|
+
* - **{@link OwnerId}**: Globally unique public identifier
|
|
9
|
+
* - **{@link EncryptionKey}**: Symmetric encryption key for data protection
|
|
10
|
+
* - **{@link WriteKey}**: Authentication token for write operations
|
|
11
|
+
*
|
|
12
|
+
* Every Evolu app has at least one owner, the {@link AppOwner}. There are
|
|
13
|
+
* several owner variants for different use cases:
|
|
14
|
+
*
|
|
15
|
+
* **{@link ShardOwner}**: Derived from {@link AppOwner} for partitioning data and
|
|
16
|
+
* selective synchronization using {@link createShardOwner}
|
|
17
|
+
*
|
|
18
|
+
* **{@link SharedOwner}**: Created for collaboration with write access, not
|
|
19
|
+
* meant to be shared directly
|
|
20
|
+
*
|
|
21
|
+
* **{@link SharedReadonlyOwner}**: Read-only version for safe data sharing,
|
|
22
|
+
* created from {@link SharedOwner} using {@link createSharedReadonlyOwner}
|
|
23
|
+
*
|
|
24
|
+
* Owners are designed for data synchronization and backup. Authentication
|
|
25
|
+
* systems built on public/private key cryptography use these primitives. This
|
|
26
|
+
* design ensures Evolu Relay knows as little as possible - it only sees
|
|
27
|
+
* Timestamp, OwnerId, and EncryptedDbChange.
|
|
3
28
|
*
|
|
4
29
|
* @module
|
|
5
30
|
*/
|
|
6
31
|
|
|
7
|
-
import {
|
|
32
|
+
import { NonEmptyReadonlyArray } from "../Array.js";
|
|
8
33
|
import {
|
|
9
|
-
createEncryptionKey,
|
|
10
34
|
CreateMnemonicDep,
|
|
11
35
|
CreateRandomBytesDep,
|
|
12
36
|
createSlip21,
|
|
@@ -15,61 +39,26 @@ import {
|
|
|
15
39
|
MnemonicSeed,
|
|
16
40
|
mnemonicToMnemonicSeed,
|
|
17
41
|
} from "../Crypto.js";
|
|
18
|
-
import { NanoIdLibDep } from "../NanoId.js";
|
|
19
|
-
import { TimeDep } from "../Time.js";
|
|
20
42
|
import {
|
|
21
43
|
Base64Url,
|
|
22
44
|
brand,
|
|
23
|
-
DateIso,
|
|
24
|
-
DateIsoString,
|
|
25
45
|
Id,
|
|
26
46
|
length,
|
|
27
47
|
Mnemonic,
|
|
28
48
|
NonNegativeInt,
|
|
29
49
|
Uint8Array,
|
|
30
50
|
} 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
51
|
|
|
45
52
|
/**
|
|
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.
|
|
53
|
+
* Represents ownership of data in Evolu. Created from a {@link Mnemonic} via
|
|
54
|
+
* SLIP-21 key derivation using {@link createOwner}, providing cryptographic keys
|
|
55
|
+
* for data access and authentication.
|
|
54
56
|
*
|
|
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.
|
|
57
|
+
* - {@link OwnerId}: Globally unique public identifier
|
|
58
|
+
* - {@link EncryptionKey}: Symmetric encryption key for data protection
|
|
59
|
+
* - {@link WriteKey}: Authentication token for write operations (rotatable)
|
|
63
60
|
*/
|
|
64
61
|
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
62
|
readonly id: OwnerId;
|
|
74
63
|
readonly encryptionKey: EncryptionKey;
|
|
75
64
|
readonly writeKey: WriteKey;
|
|
@@ -89,203 +78,140 @@ export type OwnerId = typeof OwnerId.Type;
|
|
|
89
78
|
export const writeKeyLength = 16 as NonNegativeInt;
|
|
90
79
|
|
|
91
80
|
/**
|
|
92
|
-
* A secure token proving the initiator can write changes. Derived from a
|
|
93
|
-
* mnemonic or randomly generated. It
|
|
81
|
+
* A secure token proving that the initiator can write changes. Derived from a
|
|
82
|
+
* mnemonic or randomly generated via {@link createWriteKey}. It is rotatable.
|
|
94
83
|
*/
|
|
95
84
|
export const WriteKey = brand("WriteKey", length(writeKeyLength)(Uint8Array));
|
|
96
85
|
export type WriteKey = typeof WriteKey.Type;
|
|
97
86
|
|
|
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
|
-
}
|
|
87
|
+
/** Creates a randomly generated {@link WriteKey}. */
|
|
88
|
+
export const createWriteKey = (deps: CreateRandomBytesDep): WriteKey =>
|
|
89
|
+
deps.createRandomBytes(16) as unknown as WriteKey;
|
|
108
90
|
|
|
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
|
-
}
|
|
91
|
+
/** Creates an {@link Owner} from a {@link Mnemonic} using SLIP-21 key derivation. */
|
|
92
|
+
export const createOwner = (mnemonic: Mnemonic): Owner => {
|
|
93
|
+
const seed = mnemonicToMnemonicSeed(mnemonic);
|
|
94
|
+
return createOwnerFromMnemonicSeed(seed);
|
|
95
|
+
};
|
|
124
96
|
|
|
125
97
|
/**
|
|
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.
|
|
98
|
+
* Creates an {@link Owner} from a {@link MnemonicSeed} using SLIP-21 key
|
|
99
|
+
* derivation.
|
|
135
100
|
*/
|
|
136
|
-
export
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
101
|
+
export const createOwnerFromMnemonicSeed = (seed: MnemonicSeed): Owner => ({
|
|
102
|
+
id: createSlip21Id(seed, ["Evolu", "Owner Id"]) as OwnerId,
|
|
103
|
+
|
|
104
|
+
encryptionKey: createSlip21(seed, [
|
|
105
|
+
"Evolu",
|
|
106
|
+
"Encryption Key",
|
|
107
|
+
]) as EncryptionKey,
|
|
108
|
+
|
|
109
|
+
writeKey: createSlip21(seed, ["Evolu", "Write Key"]).slice(0, 16) as WriteKey,
|
|
110
|
+
});
|
|
141
111
|
|
|
142
112
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* Typically derived from a `SharedOwner` by sharing its `mnemonic` without the
|
|
147
|
-
* `writeKey`.
|
|
113
|
+
* The owner representing app data. Can be created from a {@link Mnemonic} or
|
|
114
|
+
* from external keys when the mnemonic should not be shared with the Evolu
|
|
115
|
+
* app.
|
|
148
116
|
*/
|
|
149
|
-
export interface
|
|
150
|
-
readonly type: "
|
|
151
|
-
|
|
117
|
+
export interface AppOwner extends Owner {
|
|
118
|
+
readonly type: "AppOwner";
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The mnemonic that was used to derive the AppOwner keys. Optional when the
|
|
122
|
+
* AppOwner is created from external keys to avoid sharing the mnemonic with
|
|
123
|
+
* the Evolu app.
|
|
124
|
+
*/
|
|
125
|
+
readonly mnemonic?: Mnemonic | null;
|
|
152
126
|
}
|
|
153
127
|
|
|
128
|
+
export const createAppOwner = (mnemonic: Mnemonic): AppOwner => ({
|
|
129
|
+
type: "AppOwner",
|
|
130
|
+
mnemonic,
|
|
131
|
+
...createOwner(mnemonic),
|
|
132
|
+
});
|
|
133
|
+
|
|
154
134
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
135
|
+
* Owner for sharding app data. Allows partitioning of database changes for
|
|
136
|
+
* selective synchronization.
|
|
157
137
|
*/
|
|
158
|
-
export
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const owner = createOwner(deps)(mnemonic);
|
|
162
|
-
return { type: "AppOwner", ...owner };
|
|
163
|
-
};
|
|
138
|
+
export interface ShardOwner extends Owner {
|
|
139
|
+
readonly type: "ShardOwner";
|
|
140
|
+
}
|
|
164
141
|
|
|
165
142
|
/**
|
|
166
|
-
* Creates a {@link ShardOwner}
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
143
|
+
* Creates a {@link ShardOwner} derived from an {@link AppOwner} using the
|
|
144
|
+
* specified path.
|
|
145
|
+
*
|
|
146
|
+
* ### Example
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* const contactsShard = createShardOwner(appOwner, ["contacts"]);
|
|
150
|
+
* const projectShard = createShardOwner(appOwner, [
|
|
151
|
+
* "projects",
|
|
152
|
+
* "project-1",
|
|
153
|
+
* ]);
|
|
154
|
+
* ```
|
|
170
155
|
*/
|
|
171
156
|
export const createShardOwner = (
|
|
172
|
-
|
|
157
|
+
appOwner: AppOwner,
|
|
158
|
+
path: NonEmptyReadonlyArray<string>,
|
|
173
159
|
): ShardOwner => {
|
|
174
|
-
|
|
160
|
+
/**
|
|
161
|
+
* The shardSeed is never shared or persisted, only used for SLIP-21
|
|
162
|
+
* derivation to create shard-specific keys.
|
|
163
|
+
*/
|
|
164
|
+
const shardSeed = createSlip21(
|
|
165
|
+
appOwner.encryptionKey as unknown as MnemonicSeed,
|
|
166
|
+
path,
|
|
167
|
+
) as MnemonicSeed;
|
|
168
|
+
|
|
175
169
|
return {
|
|
176
170
|
type: "ShardOwner",
|
|
177
|
-
|
|
178
|
-
writeKey: owner.writeKey,
|
|
171
|
+
...createOwnerFromMnemonicSeed(shardSeed),
|
|
179
172
|
};
|
|
180
173
|
};
|
|
181
174
|
|
|
182
175
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
176
|
+
* Owner for collaborative data with write access. Created by a user for their
|
|
177
|
+
* own use, not meant to be shared directly. To share data, use
|
|
178
|
+
* {@link createSharedReadonlyOwner} to create a {@link SharedReadonlyOwner} for
|
|
179
|
+
* read-only access.
|
|
187
180
|
*/
|
|
188
|
-
export
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
export interface SharedOwner extends Owner {
|
|
182
|
+
readonly type: "SharedOwner";
|
|
183
|
+
readonly mnemonic: Mnemonic;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Creates a {@link SharedOwner} with a freshly generated {@link Mnemonic}. */
|
|
187
|
+
export const createSharedOwner = (deps: CreateMnemonicDep): SharedOwner => {
|
|
191
188
|
const mnemonic = deps.createMnemonic();
|
|
192
|
-
const writeKey = createWriteKey(deps)(); // Random, no seed
|
|
193
189
|
return {
|
|
194
190
|
type: "SharedOwner",
|
|
195
191
|
mnemonic,
|
|
196
|
-
|
|
192
|
+
...createOwner(mnemonic),
|
|
197
193
|
};
|
|
198
194
|
};
|
|
199
195
|
|
|
200
196
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
197
|
+
* Read-only version of a {@link SharedOwner} for data sharing. Contains only the
|
|
198
|
+
* {@link OwnerId} and {@link EncryptionKey} needed for others to read the shared
|
|
199
|
+
* data without write access.
|
|
204
200
|
*/
|
|
201
|
+
export interface SharedReadonlyOwner {
|
|
202
|
+
readonly type: "SharedReadonlyOwner";
|
|
203
|
+
readonly id: OwnerId;
|
|
204
|
+
readonly encryptionKey: EncryptionKey;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Creates a {@link SharedReadonlyOwner} from a {@link SharedOwner}. */
|
|
205
208
|
export const createSharedReadonlyOwner = (
|
|
206
209
|
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
|
-
};
|
|
210
|
+
): SharedReadonlyOwner => ({
|
|
211
|
+
type: "SharedReadonlyOwner",
|
|
212
|
+
id: sharedOwner.id,
|
|
213
|
+
encryptionKey: sharedOwner.encryptionKey,
|
|
214
|
+
});
|
|
289
215
|
|
|
290
216
|
/**
|
|
291
217
|
* Rotates the {@link WriteKey} for an {@link AppOwner}, {@link ShardOwner}, or
|
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable jsdoc/no-undefined-types */
|
|
1
2
|
/**
|
|
2
3
|
* Evolu Protocol
|
|
3
4
|
*
|
|
@@ -183,13 +184,7 @@ import {
|
|
|
183
184
|
record,
|
|
184
185
|
} from "../Type.js";
|
|
185
186
|
import { Brand, Predicate } from "../Types.js";
|
|
186
|
-
import {
|
|
187
|
-
Owner,
|
|
188
|
-
OwnerId,
|
|
189
|
-
OwnerWithWriteAccess,
|
|
190
|
-
WriteKey,
|
|
191
|
-
writeKeyLength,
|
|
192
|
-
} from "./Owner.js";
|
|
187
|
+
import { Owner, OwnerId, WriteKey, writeKeyLength } from "./Owner.js";
|
|
193
188
|
import {
|
|
194
189
|
BinaryTimestamp,
|
|
195
190
|
binaryTimestampLength,
|
|
@@ -473,7 +468,7 @@ export interface ProtocolTimestampMismatchError {
|
|
|
473
468
|
export const createProtocolMessageFromCrdtMessages =
|
|
474
469
|
(deps: SymmetricCryptoDep & CreateRandomBytesDep) =>
|
|
475
470
|
(
|
|
476
|
-
owner:
|
|
471
|
+
owner: Owner,
|
|
477
472
|
messages: NonEmptyReadonlyArray<CrdtMessage>,
|
|
478
473
|
maxSize?: PositiveInt,
|
|
479
474
|
): ProtocolMessage => {
|
package/src/Evolu/Public.ts
CHANGED
|
@@ -5,12 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export { createEvolu } from "./Evolu.js";
|
|
8
|
-
export type {
|
|
9
|
-
Evolu,
|
|
10
|
-
EvoluConfigWithInitialData,
|
|
11
|
-
EvoluDeps,
|
|
12
|
-
EvoluError,
|
|
13
|
-
} from "./Evolu.js";
|
|
8
|
+
export type { Evolu, EvoluDeps, EvoluError } from "./Evolu.js";
|
|
14
9
|
export * from "./Owner.js";
|
|
15
10
|
export { binaryIdToId, idToBinaryId } from "./Protocol.js";
|
|
16
11
|
export type { BinaryId } from "./Protocol.js";
|
package/src/Evolu/Schema.ts
CHANGED
|
@@ -30,11 +30,10 @@ 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
|
-
maxProtocolMessageRangesSize,
|
|
37
35
|
CrdtMessage,
|
|
36
|
+
maxProtocolMessageRangesSize,
|
|
38
37
|
} from "./Protocol.js";
|
|
39
38
|
import { Query, Row } from "./Query.js";
|
|
40
39
|
import { BinaryTimestamp } from "./Timestamp.js";
|
|
@@ -252,13 +251,6 @@ export interface MutationOptions {
|
|
|
252
251
|
* `onlyValidate: true`.
|
|
253
252
|
*/
|
|
254
253
|
readonly onlyValidate?: boolean;
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* The owner to use for this mutation. Can be a {@link ShardOwner} for sharding
|
|
258
|
-
* app data or a {@link SharedOwner} for collaborative write access. If
|
|
259
|
-
* omitted, defaults to the app's {@link AppOwner}.
|
|
260
|
-
*/
|
|
261
|
-
readonly owner?: ShardOwner | SharedOwner;
|
|
262
254
|
}
|
|
263
255
|
|
|
264
256
|
/**
|
package/src/Result.ts
CHANGED
package/src/Type.ts
CHANGED