@evolu/common 7.2.1 → 7.2.3
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/Object.d.ts +16 -1
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +16 -1
- package/dist/src/Order.d.ts +2 -1
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +1 -0
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +4 -1
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +5 -11
- package/dist/src/local-first/Diff.js +2 -2
- package/dist/src/local-first/Evolu.d.ts +14 -11
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +8 -5
- package/dist/src/local-first/Owner.d.ts +39 -42
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +22 -29
- package/dist/src/local-first/Protocol.d.ts +6 -6
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +2 -2
- package/dist/src/local-first/Public.d.ts +1 -0
- package/dist/src/local-first/Public.d.ts.map +1 -1
- package/dist/src/local-first/Storage.d.ts +3 -3
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Sync.d.ts +10 -17
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +108 -130
- package/dist/src/local-first/Timestamp.d.ts +13 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +14 -1
- package/package.json +1 -1
- package/src/Object.ts +20 -1
- package/src/Order.ts +2 -1
- package/src/WebSocket.ts +6 -1
- package/src/local-first/Db.ts +5 -12
- package/src/local-first/Diff.ts +2 -2
- package/src/local-first/Evolu.ts +27 -15
- package/src/local-first/Owner.ts +47 -57
- package/src/local-first/Protocol.ts +8 -8
- package/src/local-first/Public.ts +1 -0
- package/src/local-first/Storage.ts +3 -3
- package/src/local-first/Sync.ts +123 -175
- package/src/local-first/Timestamp.ts +14 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,KAAK,EAAmB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAEL,OAAO,EACP,SAAS,EAOV,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,MAAM,cAAc,GACtB,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAAC;AAEjC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAC;CAChD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,8BAA8B,CAAC;CAC/C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,kwBAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC,eAAO,MAAM,SAAS,EAAQ,MAAM,CAAC;AACrC,eAAO,MAAM,SAAS,EAA4B,MAAM,CAAC;AAEzD,eAAO,MAAM,OAAO,8vBAGnB,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAE1C,eAAO,MAAM,UAAU,EAAQ,OAAO,CAAC;AACvC,eAAO,MAAM,UAAU,EAAY,OAAO,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,MAAM,wPAA4C,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC,eAAO,MAAM,SAAS,EAAyB,MAAM,CAAC;AACtD,eAAO,MAAM,SAAS,EAAyB,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,SAAS;;;;EAIpB,CAAC;AACH,MAAM,WAAW,SAAU,SAAQ,SAAS,CAAC,OAAO,SAAS,CAAC;CAAG;AAEjE,yDAAyD;AACzD,eAAO,MAAM,WAAW;;;;EAItB,CAAC;AAEH,eAAO,MAAM,eAAe,GAAI,+BAI7B,OAAO,CAAC,SAAS,CAAM,KAAG,SAA0C,CAAC;AAExE,eAAO,MAAM,sBAAsB,GAAI,MAAM,cAAc,KAAG,SAG7D,CAAC;AA6BF,eAAO,MAAM,aAAa,GACvB,MAAM,OAAO,GAAG,kBAAkB,MAEjC,WAAW,SAAS,KACnB,MAAM,CACP,SAAS,EACP,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAgB/B,CAAC;AAEJ,eAAO,MAAM,gBAAgB,GAC1B,MAAM,OAAO,GAAG,kBAAkB,MAEjC,OAAO,SAAS,EAChB,QAAQ,SAAS,KAChB,MAAM,CACP,SAAS,EACP,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAqB/B,CAAC;AAEJ,0DAA0D;AAC1D,eAAO,MAAM,cAAc,2WAAsC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,eAAO,MAAM,oBAAoB,0FAA6B,CAAC;AAE/D,eAAO,MAAM,yBAAyB,GACpC,WAAW,SAAS,KACnB,cA0BF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,WAAW,cAAc,KACxB,SAoBF,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,cAAc,CAAmB,CAAC;AAE1E,eAAO,MAAM,kBAAkB,GAAI,WAAW,SAAS,KAAG,OAEL,CAAC"}
|
|
1
|
+
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}
|
|
@@ -218,7 +218,20 @@ export const timestampBytesToTimestamp = (timestamp) => {
|
|
|
218
218
|
}
|
|
219
219
|
return { millis: Number(millis), counter, nodeId };
|
|
220
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* An {@link Order} for {@link TimestampBytes}.
|
|
223
|
+
*
|
|
224
|
+
* This `Order` uses lexicographic byte order to compare serialized
|
|
225
|
+
* {@link TimestampBytes} produced by {@link timestampToTimestampBytes}. See
|
|
226
|
+
* {@link orderUint8Array} for the underlying implementation.
|
|
227
|
+
*/
|
|
221
228
|
export const orderTimestampBytes = orderUint8Array;
|
|
229
|
+
/**
|
|
230
|
+
* Convert a {@link Timestamp} to an ISO 8601 {@link DateIso} string.
|
|
231
|
+
*
|
|
232
|
+
* The conversion uses the timestamp's `millis` (a {@link Millis} value) and
|
|
233
|
+
* `Date.prototype.toISOString()` to produce a `DateIso`.
|
|
234
|
+
*/
|
|
222
235
|
export const timestampToDateIso = (timestamp) =>
|
|
223
|
-
// `as DateIso` is safe because
|
|
236
|
+
// `as DateIso` is safe because Timestamp guarantees a valid `millis`
|
|
224
237
|
new Date(timestamp.millis).toISOString();
|
package/package.json
CHANGED
package/src/Object.ts
CHANGED
|
@@ -31,7 +31,7 @@ type StringKeyOf<T> = Extract<keyof T, string>;
|
|
|
31
31
|
*
|
|
32
32
|
* ```ts
|
|
33
33
|
* type UserId = string & { readonly __brand: "UserId" };
|
|
34
|
-
* const users
|
|
34
|
+
* const users = createRecord<UserId, string>();
|
|
35
35
|
* const entries = objectToEntries(users); // [UserId, string][]
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
@@ -70,3 +70,22 @@ export const excludeProp = <T extends object, K extends keyof T>(
|
|
|
70
70
|
const { [prop]: _, ...rest } = obj;
|
|
71
71
|
return rest;
|
|
72
72
|
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a prototype-less object typed as `Record<K, V>`.
|
|
76
|
+
*
|
|
77
|
+
* Use this function when you need a plain record without a prototype chain
|
|
78
|
+
* (e.g. when keys are controlled by external sources) to avoid prototype
|
|
79
|
+
* pollution and accidental collisions with properties like `__proto__`.
|
|
80
|
+
*
|
|
81
|
+
* Example:
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* const values = createRecord<string, SqliteValue>();
|
|
85
|
+
* values["__proto__"] = someValue; // safe, no prototype pollution
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export const createRecord = <K extends string = string, V = unknown>(): Record<
|
|
89
|
+
K,
|
|
90
|
+
V
|
|
91
|
+
> => Object.create(null) as Record<K, V>;
|
package/src/Order.ts
CHANGED
|
@@ -94,7 +94,8 @@ export const orderNumber = createOrder<number>((a, b) => a < b);
|
|
|
94
94
|
*/
|
|
95
95
|
export const orderBigInt = createOrder<bigint>((a, b) => a < b);
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
/** An {@link Order} for Uint8Array. */
|
|
98
|
+
export const orderUint8Array: Order<Uint8Array> = (a, b) => {
|
|
98
99
|
if (a.byteLength > b.byteLength) return 1;
|
|
99
100
|
if (a.byteLength < b.byteLength) return -1;
|
|
100
101
|
|
package/src/WebSocket.ts
CHANGED
|
@@ -171,7 +171,12 @@ export const createWebSocket: CreateWebSocket = (
|
|
|
171
171
|
socket.onmessage = null;
|
|
172
172
|
socket.onerror = null;
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
if (
|
|
175
|
+
socket.readyState !== socket.CLOSING &&
|
|
176
|
+
socket.readyState !== socket.CLOSED
|
|
177
|
+
) {
|
|
178
|
+
socket.close();
|
|
179
|
+
}
|
|
175
180
|
socket = null;
|
|
176
181
|
};
|
|
177
182
|
|
package/src/local-first/Db.ts
CHANGED
|
@@ -336,9 +336,12 @@ export const createDbWorkerForPlatform = (
|
|
|
336
336
|
DbWorkerInput,
|
|
337
337
|
DbWorkerOutput,
|
|
338
338
|
DbWorkerDeps
|
|
339
|
-
>({
|
|
339
|
+
>({
|
|
340
|
+
init: createInit(platformDeps),
|
|
341
|
+
handlers,
|
|
342
|
+
});
|
|
340
343
|
|
|
341
|
-
const
|
|
344
|
+
const createInit =
|
|
342
345
|
(platformDeps: DbWorkerPlatformDeps) =>
|
|
343
346
|
async (
|
|
344
347
|
initMessage: Extract<DbWorkerInput, { type: "init" }>,
|
|
@@ -376,16 +379,6 @@ const createDbWorkerDeps =
|
|
|
376
379
|
}>(sql`select protocolVersion from evolu_version limit 1;`);
|
|
377
380
|
if (!currentVersion.ok) return currentVersion;
|
|
378
381
|
|
|
379
|
-
// TODO: Handle version migrations here if needed
|
|
380
|
-
// const [{ protocolVersion }] = protocolVersionResult.value.rows;
|
|
381
|
-
// if (protocolVersion < currentProtocolVersion) {
|
|
382
|
-
// const migrateResult = migrateDatabase({ sqlite })(
|
|
383
|
-
// protocolVersion,
|
|
384
|
-
// currentProtocolVersion
|
|
385
|
-
// );
|
|
386
|
-
// if (!migrateResult.ok) return migrateResult;
|
|
387
|
-
// }
|
|
388
|
-
|
|
389
382
|
const configResult = sqlite.exec<{
|
|
390
383
|
clock: TimestampBytes;
|
|
391
384
|
appOwnerId: OwnerId;
|
package/src/local-first/Diff.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRandomBytes } from "../Crypto.js";
|
|
2
|
-
import { isPlainObject, ReadonlyRecord } from "../Object.js";
|
|
2
|
+
import { createRecord, isPlainObject, ReadonlyRecord } from "../Object.js";
|
|
3
3
|
import { orderUint8Array } from "../Order.js";
|
|
4
4
|
import { SqliteValue } from "../Sqlite.js";
|
|
5
5
|
import { createId, String } from "../Type.js";
|
|
@@ -136,7 +136,7 @@ const parse = (obj: unknown): unknown => {
|
|
|
136
136
|
const parseObject = (
|
|
137
137
|
obj: ReadonlyRecord<string, unknown>,
|
|
138
138
|
): ReadonlyRecord<string, unknown> => {
|
|
139
|
-
const result =
|
|
139
|
+
const result = createRecord();
|
|
140
140
|
for (const key in obj) {
|
|
141
141
|
result[key] = parse(obj[key]);
|
|
142
142
|
}
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
isNonEmptyArray,
|
|
5
5
|
isNonEmptyReadonlyArray,
|
|
6
6
|
} from "../Array.js";
|
|
7
|
-
import { assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
7
|
+
import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
8
8
|
import { createCallbacks } from "../Callbacks.js";
|
|
9
9
|
import { ConsoleDep } from "../Console.js";
|
|
10
10
|
import { RandomBytesDep, SymmetricCryptoDecryptError } from "../Crypto.js";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isSqlMutation,
|
|
18
18
|
SafeSql,
|
|
19
19
|
SqliteBoolean,
|
|
20
|
+
sqliteBooleanToBoolean,
|
|
20
21
|
SqliteError,
|
|
21
22
|
SqliteQuery,
|
|
22
23
|
} from "../Sqlite.js";
|
|
@@ -423,32 +424,36 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
423
424
|
readonly exportDatabase: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
424
425
|
|
|
425
426
|
/**
|
|
426
|
-
* Use
|
|
427
|
-
* broadcasted changes. Returns a function to stop using the owner.
|
|
427
|
+
* Use a {@link SyncOwner}. Returns a {@link UnuseOwner}.
|
|
428
428
|
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
429
|
+
* Using an owner means syncing it with its transports, or the transports
|
|
430
|
+
* defined in Evolu config if the owner has no transports defined.
|
|
431
|
+
*
|
|
432
|
+
* Transport are automatically deduplicated and reference-counted, so multiple
|
|
433
|
+
* owners using the same transport will share a single connection.
|
|
432
434
|
*
|
|
433
435
|
* ### Example
|
|
434
436
|
*
|
|
435
437
|
* ```ts
|
|
436
|
-
* // Use an owner (starts syncing
|
|
437
|
-
* const
|
|
438
|
+
* // Use an owner (starts syncing).
|
|
439
|
+
* const unuseOwner = evolu.useOwner(shardOwner);
|
|
438
440
|
*
|
|
439
441
|
* // Later, stop using the owner.
|
|
440
|
-
*
|
|
442
|
+
* unuseOwner();
|
|
441
443
|
*
|
|
442
444
|
* // Bulk operations.
|
|
443
|
-
* const
|
|
444
|
-
* // Later:
|
|
445
|
+
* const unuseOwners = owners.map((owner) => evolu.useOwner(owner));
|
|
446
|
+
* // Later: for (const unuse of unuseOwners) unuse();
|
|
445
447
|
* ```
|
|
446
448
|
*
|
|
447
449
|
* @experimental
|
|
448
450
|
*/
|
|
449
|
-
readonly useOwner: (owner: SyncOwner) =>
|
|
451
|
+
readonly useOwner: (owner: SyncOwner) => UnuseOwner;
|
|
450
452
|
}
|
|
451
453
|
|
|
454
|
+
/** Function returned by {@link Evolu#useOwner} to stop using an {@link SyncOwner}. */
|
|
455
|
+
export type UnuseOwner = () => void;
|
|
456
|
+
|
|
452
457
|
/** Represents errors that can occur in Evolu. */
|
|
453
458
|
export type EvoluError =
|
|
454
459
|
| ProtocolError
|
|
@@ -726,13 +731,20 @@ const createEvoluInstance =
|
|
|
726
731
|
} else {
|
|
727
732
|
const { id: _, isDeleted, ...values } = result.value;
|
|
728
733
|
|
|
729
|
-
const dbChange =
|
|
734
|
+
const dbChange = {
|
|
730
735
|
table,
|
|
731
736
|
id,
|
|
732
737
|
values,
|
|
733
738
|
isInsert: kind === "insert" || kind === "upsert",
|
|
734
|
-
isDelete: SqliteBoolean.is(isDeleted)
|
|
735
|
-
|
|
739
|
+
isDelete: SqliteBoolean.is(isDeleted)
|
|
740
|
+
? sqliteBooleanToBoolean(isDeleted)
|
|
741
|
+
: null,
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
assert(
|
|
745
|
+
DbChange.is(dbChange),
|
|
746
|
+
`Invalid DbChange for table '${table}': Please check schema type errors.`,
|
|
747
|
+
);
|
|
736
748
|
|
|
737
749
|
mutateMicrotaskQueue.push([
|
|
738
750
|
{ ...dbChange, ownerId: options?.ownerId },
|
package/src/local-first/Owner.ts
CHANGED
|
@@ -22,15 +22,23 @@ import type { EncryptedDbChange, Storage } from "./Storage.js";
|
|
|
22
22
|
import { TimestampBytes } from "./Timestamp.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* assigned to an owner, enabling sync functionality and access control.
|
|
25
|
+
* {@link Owner} without a {@link OwnerWriteKey}.
|
|
27
26
|
*
|
|
28
|
-
*
|
|
29
|
-
|
|
27
|
+
* @see {@link createSharedReadonlyOwner}
|
|
28
|
+
*/
|
|
29
|
+
export interface ReadonlyOwner {
|
|
30
|
+
readonly id: OwnerId;
|
|
31
|
+
readonly encryptionKey: OwnerEncryptionKey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The Owner represents ownership of data in Evolu. Every database change is
|
|
36
|
+
* assigned to an owner and encrypted with its {@link OwnerEncryptionKey}. Owners
|
|
37
|
+
* allow partial sync, only the {@link AppOwner} is synced by default.
|
|
30
38
|
*
|
|
31
|
-
* Owners also provide
|
|
32
|
-
* local-first/distributed systems can only be
|
|
33
|
-
*
|
|
39
|
+
* Owners can also provide real data deletion, while individual changes in
|
|
40
|
+
* local-first/distributed systems can only be soft deleted, entire owners can
|
|
41
|
+
* be completely deleted from both relays and devices (except for
|
|
34
42
|
* {@link AppOwner}, which must be preserved for sync coordination).
|
|
35
43
|
*
|
|
36
44
|
* Evolu provides different owner types depending on their use case:
|
|
@@ -46,21 +54,19 @@ import { TimestampBytes } from "./Timestamp.js";
|
|
|
46
54
|
* SLIP-21, ensuring secure and deterministic key generation:
|
|
47
55
|
*
|
|
48
56
|
* - {@link OwnerId}: Globally unique public identifier
|
|
49
|
-
* - {@link
|
|
57
|
+
* - {@link OwnerEncryptionKey}: Symmetric encryption key for data protection
|
|
50
58
|
* - {@link OwnerWriteKey}: Authentication token for write operations (rotatable)
|
|
51
59
|
*
|
|
52
|
-
* @see {@link
|
|
60
|
+
* @see {@link createAppOwner}
|
|
61
|
+
* @see {@link createShardOwner}
|
|
62
|
+
* @see {@link createSharedOwner}
|
|
63
|
+
* @see {@link createSharedReadonlyOwner}
|
|
53
64
|
*/
|
|
54
|
-
export interface Owner {
|
|
55
|
-
readonly id: OwnerId;
|
|
56
|
-
readonly encryptionKey: OwnerEncryptionKey;
|
|
65
|
+
export interface Owner extends ReadonlyOwner {
|
|
57
66
|
readonly writeKey: OwnerWriteKey;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
/**
|
|
61
|
-
* OwnerId is a branded {@link Id} that uniquely identifies an {@link Owner}.
|
|
62
|
-
* Branded from {@link Id} to leverage existing helpers like {@link idToIdBytes}.
|
|
63
|
-
*/
|
|
69
|
+
/** OwnerId is a branded {@link Id} that uniquely identifies an {@link Owner}. */
|
|
64
70
|
export const OwnerId = brand("OwnerId", Id);
|
|
65
71
|
export type OwnerId = typeof OwnerId.Type;
|
|
66
72
|
|
|
@@ -68,14 +74,17 @@ export type OwnerId = typeof OwnerId.Type;
|
|
|
68
74
|
export const OwnerIdBytes = brand("OwnerIdBytes", IdBytes);
|
|
69
75
|
export type OwnerIdBytes = typeof OwnerIdBytes.Type;
|
|
70
76
|
|
|
77
|
+
/** Converts {@link OwnerId} to {@link OwnerIdBytes}. */
|
|
71
78
|
export const ownerIdToOwnerIdBytes = (ownerId: OwnerId): OwnerIdBytes =>
|
|
72
79
|
idToIdBytes(ownerId) as OwnerIdBytes;
|
|
73
80
|
|
|
81
|
+
/** Converts {@link OwnerIdBytes} to {@link OwnerId}. */
|
|
74
82
|
export const ownerIdBytesToOwnerId = (ownerIdBytes: OwnerIdBytes): OwnerId =>
|
|
75
83
|
idBytesToId(ownerIdBytes as IdBytes) as OwnerId;
|
|
76
84
|
|
|
77
85
|
export const ownerWriteKeyLength = NonNegativeInt.orThrow(16);
|
|
78
86
|
|
|
87
|
+
/** Symmetric encryption key for {@link Owner} data protection. */
|
|
79
88
|
export const OwnerEncryptionKey = brand("OwnerEncryptionKey", EncryptionKey);
|
|
80
89
|
export type OwnerEncryptionKey = typeof OwnerEncryptionKey.Type;
|
|
81
90
|
|
|
@@ -86,6 +95,16 @@ export type OwnerEncryptionKey = typeof OwnerEncryptionKey.Type;
|
|
|
86
95
|
export const OwnerWriteKey = brand("OwnerWriteKey", Entropy16);
|
|
87
96
|
export type OwnerWriteKey = typeof OwnerWriteKey.Type;
|
|
88
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Creates a new random {@link OwnerWriteKey} for rotation.
|
|
100
|
+
*
|
|
101
|
+
* The initial OwnerWriteKey is deterministically derived from
|
|
102
|
+
* {@link OwnerSecret}. Use `createOwnerWriteKey` to rotate (replace) the write
|
|
103
|
+
* key without changing the owner identity.
|
|
104
|
+
*/
|
|
105
|
+
export const createOwnerWriteKey = (deps: RandomBytesDep): OwnerWriteKey =>
|
|
106
|
+
deps.randomBytes.create(16) as OwnerWriteKey;
|
|
107
|
+
|
|
89
108
|
/**
|
|
90
109
|
* 32 bytes of cryptographic entropy used to derive {@link Owner} keys.
|
|
91
110
|
*
|
|
@@ -107,22 +126,11 @@ export const ownerSecretToMnemonic = (secret: OwnerSecret): Mnemonic =>
|
|
|
107
126
|
export const mnemonicToOwnerSecret = (mnemonic: Mnemonic): OwnerSecret =>
|
|
108
127
|
bip39.mnemonicToEntropy(mnemonic, wordlist) as OwnerSecret;
|
|
109
128
|
|
|
110
|
-
/** Creates a randomly generated {@link OwnerWriteKey}. */
|
|
111
|
-
export const createOwnerWriteKey = (deps: RandomBytesDep): OwnerWriteKey =>
|
|
112
|
-
deps.randomBytes.create(16) as OwnerWriteKey;
|
|
113
|
-
|
|
114
129
|
/**
|
|
115
130
|
* Creates an {@link Owner} from a {@link OwnerSecret} using SLIP-21 key
|
|
116
131
|
* derivation.
|
|
117
|
-
*
|
|
118
|
-
* This is an internal helper function, use:
|
|
119
|
-
*
|
|
120
|
-
* - {@link createAppOwner}
|
|
121
|
-
* - {@link createShardOwner}
|
|
122
|
-
* - {@link createSharedOwner}
|
|
123
|
-
* - {@link createSharedReadonlyOwner}
|
|
124
132
|
*/
|
|
125
|
-
|
|
133
|
+
const createOwner = (secret: OwnerSecret): Owner => ({
|
|
126
134
|
id: ownerIdBytesToOwnerId(
|
|
127
135
|
OwnerIdBytes.orThrow(
|
|
128
136
|
createSlip21(secret, ["Evolu", "OwnerIdBytes"]).slice(0, 16),
|
|
@@ -182,9 +190,9 @@ export interface AppOwner extends Owner {
|
|
|
182
190
|
|
|
183
191
|
/** Creates an {@link AppOwner} from an {@link OwnerSecret}. */
|
|
184
192
|
export const createAppOwner = (secret: OwnerSecret): AppOwner => ({
|
|
193
|
+
...createOwner(secret),
|
|
185
194
|
type: "AppOwner",
|
|
186
195
|
mnemonic: ownerSecretToMnemonic(secret),
|
|
187
|
-
...createOwner(secret),
|
|
188
196
|
});
|
|
189
197
|
|
|
190
198
|
/**
|
|
@@ -200,18 +208,13 @@ export const createAppOwner = (secret: OwnerSecret): AppOwner => ({
|
|
|
200
208
|
*/
|
|
201
209
|
export interface ShardOwner extends Owner {
|
|
202
210
|
readonly type: "ShardOwner";
|
|
203
|
-
readonly transports?: ReadonlyArray<OwnerTransport>;
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
/** Creates a {@link ShardOwner} from an {@link OwnerSecret}. */
|
|
207
|
-
export const createShardOwner = (
|
|
208
|
-
secret: OwnerSecret,
|
|
209
|
-
transports?: ReadonlyArray<OwnerTransport>,
|
|
210
|
-
): ShardOwner => {
|
|
214
|
+
export const createShardOwner = (secret: OwnerSecret): ShardOwner => {
|
|
211
215
|
return {
|
|
212
|
-
type: "ShardOwner",
|
|
213
216
|
...createOwner(secret),
|
|
214
|
-
|
|
217
|
+
type: "ShardOwner",
|
|
215
218
|
};
|
|
216
219
|
};
|
|
217
220
|
|
|
@@ -236,21 +239,18 @@ export const createShardOwner = (
|
|
|
236
239
|
export const deriveShardOwner = (
|
|
237
240
|
owner: AppOwner,
|
|
238
241
|
path: NonEmptyReadonlyArray<string | number>,
|
|
239
|
-
transports?: ReadonlyArray<OwnerTransport>,
|
|
240
242
|
): ShardOwner => {
|
|
241
243
|
const secret = createSlip21(owner.encryptionKey, path) as OwnerSecret;
|
|
242
244
|
|
|
243
245
|
return {
|
|
244
|
-
type: "ShardOwner",
|
|
245
246
|
...createOwner(secret),
|
|
246
|
-
|
|
247
|
+
type: "ShardOwner",
|
|
247
248
|
};
|
|
248
249
|
};
|
|
249
250
|
|
|
250
251
|
/** An {@link Owner} for collaborative data with write access. */
|
|
251
252
|
export interface SharedOwner extends Owner {
|
|
252
253
|
readonly type: "SharedOwner";
|
|
253
|
-
readonly transports?: ReadonlyArray<OwnerTransport>;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/**
|
|
@@ -260,27 +260,18 @@ export interface SharedOwner extends Owner {
|
|
|
260
260
|
* Use {@link createSharedReadonlyOwner} to create a read-only version for
|
|
261
261
|
* sharing.
|
|
262
262
|
*/
|
|
263
|
-
export const createSharedOwner = (
|
|
264
|
-
secret
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
return {
|
|
268
|
-
type: "SharedOwner",
|
|
269
|
-
...createOwner(secret),
|
|
270
|
-
...(transports && { transports }),
|
|
271
|
-
};
|
|
272
|
-
};
|
|
263
|
+
export const createSharedOwner = (secret: OwnerSecret): SharedOwner => ({
|
|
264
|
+
...createOwner(secret),
|
|
265
|
+
type: "SharedOwner",
|
|
266
|
+
});
|
|
273
267
|
|
|
274
268
|
/**
|
|
275
269
|
* Read-only version of a {@link SharedOwner} for data sharing. Contains only the
|
|
276
270
|
* {@link OwnerId} and {@link EncryptionKey} needed for others to read the shared
|
|
277
271
|
* data without write access.
|
|
278
272
|
*/
|
|
279
|
-
export interface SharedReadonlyOwner {
|
|
273
|
+
export interface SharedReadonlyOwner extends ReadonlyOwner {
|
|
280
274
|
readonly type: "SharedReadonlyOwner";
|
|
281
|
-
readonly id: OwnerId;
|
|
282
|
-
readonly encryptionKey: EncryptionKey;
|
|
283
|
-
readonly transports?: ReadonlyArray<OwnerTransport>;
|
|
284
275
|
}
|
|
285
276
|
|
|
286
277
|
/** Creates a {@link SharedReadonlyOwner} from a {@link SharedOwner}. */
|
|
@@ -290,7 +281,6 @@ export const createSharedReadonlyOwner = (
|
|
|
290
281
|
type: "SharedReadonlyOwner",
|
|
291
282
|
id: sharedOwner.id,
|
|
292
283
|
encryptionKey: sharedOwner.encryptionKey,
|
|
293
|
-
...(sharedOwner.transports && { transports: sharedOwner.transports }),
|
|
294
284
|
});
|
|
295
285
|
|
|
296
286
|
/**
|
|
@@ -383,8 +373,8 @@ export const parseOwnerIdFromOwnerWebSocketTransportUrl = (
|
|
|
383
373
|
url: string,
|
|
384
374
|
): OwnerId | null => getOrNull(OwnerId.fromUnknown(url.split("=")[1]));
|
|
385
375
|
|
|
386
|
-
/**
|
|
387
|
-
export interface
|
|
376
|
+
/** Common interface implemented by all owner domain errors. */
|
|
377
|
+
export interface OwnerError {
|
|
388
378
|
readonly ownerId: OwnerId;
|
|
389
379
|
}
|
|
390
380
|
|
|
@@ -198,7 +198,7 @@ import {
|
|
|
198
198
|
} from "../Crypto.js";
|
|
199
199
|
import { eqArrayNumber } from "../Eq.js";
|
|
200
200
|
import { computeBalancedBuckets } from "../Number.js";
|
|
201
|
-
import { objectToEntries } from "../Object.js";
|
|
201
|
+
import { createRecord, objectToEntries } from "../Object.js";
|
|
202
202
|
import { err, ok, Result } from "../Result.js";
|
|
203
203
|
import { SqliteValue } from "../Sqlite.js";
|
|
204
204
|
import {
|
|
@@ -221,8 +221,8 @@ import {
|
|
|
221
221
|
} from "../Type.js";
|
|
222
222
|
import { Predicate } from "../Types.js";
|
|
223
223
|
import {
|
|
224
|
-
BaseOwnerError,
|
|
225
224
|
Owner,
|
|
225
|
+
OwnerError,
|
|
226
226
|
OwnerId,
|
|
227
227
|
OwnerIdBytes,
|
|
228
228
|
ownerIdToOwnerIdBytes,
|
|
@@ -382,7 +382,7 @@ export type ProtocolError =
|
|
|
382
382
|
* Represents a version mismatch in the Evolu Protocol. Occurs when the
|
|
383
383
|
* initiator and non-initiator are using incompatible protocol versions.
|
|
384
384
|
*/
|
|
385
|
-
export interface ProtocolVersionError extends
|
|
385
|
+
export interface ProtocolVersionError extends OwnerError {
|
|
386
386
|
readonly type: "ProtocolVersionError";
|
|
387
387
|
readonly version: NonNegativeInt;
|
|
388
388
|
/** Indicates which side is obsolete and should update. */
|
|
@@ -397,7 +397,7 @@ export interface ProtocolInvalidDataError {
|
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
/** Error when a {@link OwnerWriteKey} is invalid, missing, or fails validation. */
|
|
400
|
-
export interface ProtocolWriteKeyError extends
|
|
400
|
+
export interface ProtocolWriteKeyError extends OwnerError {
|
|
401
401
|
readonly type: "ProtocolWriteKeyError";
|
|
402
402
|
}
|
|
403
403
|
|
|
@@ -405,7 +405,7 @@ export interface ProtocolWriteKeyError extends BaseOwnerError {
|
|
|
405
405
|
* Error indicating a serious relay-side write failure. Clients should log this
|
|
406
406
|
* error and show a generic sync error to the user.
|
|
407
407
|
*/
|
|
408
|
-
export interface ProtocolWriteError extends
|
|
408
|
+
export interface ProtocolWriteError extends OwnerError {
|
|
409
409
|
readonly type: "ProtocolWriteError";
|
|
410
410
|
}
|
|
411
411
|
|
|
@@ -422,7 +422,7 @@ export interface ProtocolWriteError extends BaseOwnerError {
|
|
|
422
422
|
* plan. Quota monitoring and management is the relay provider's
|
|
423
423
|
* responsibility.
|
|
424
424
|
*/
|
|
425
|
-
export interface ProtocolQuotaError extends
|
|
425
|
+
export interface ProtocolQuotaError extends OwnerError {
|
|
426
426
|
readonly type: "ProtocolQuotaError";
|
|
427
427
|
}
|
|
428
428
|
|
|
@@ -430,7 +430,7 @@ export interface ProtocolQuotaError extends BaseOwnerError {
|
|
|
430
430
|
* Error indicating a serious relay-side synchronization failure. Clients should
|
|
431
431
|
* log this error and show a generic sync error to the user.
|
|
432
432
|
*/
|
|
433
|
-
export interface ProtocolSyncError extends
|
|
433
|
+
export interface ProtocolSyncError extends OwnerError {
|
|
434
434
|
readonly type: "ProtocolSyncError";
|
|
435
435
|
}
|
|
436
436
|
|
|
@@ -1816,7 +1816,7 @@ export const decryptAndDecodeDbChange =
|
|
|
1816
1816
|
const id = decodeId(buffer);
|
|
1817
1817
|
|
|
1818
1818
|
const length = decodeLength(buffer);
|
|
1819
|
-
const values =
|
|
1819
|
+
const values = createRecord<string, SqliteValue>();
|
|
1820
1820
|
|
|
1821
1821
|
for (let i = 0; i < length; i++) {
|
|
1822
1822
|
const column = decodeString(buffer);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
export { createEvolu } from "./Evolu.js";
|
|
8
8
|
export type { Evolu, EvoluConfig, EvoluDeps, EvoluError } from "./Evolu.js";
|
|
9
|
+
export type { UnuseOwner } from "./Evolu.js";
|
|
9
10
|
export * from "./LocalAuth.js";
|
|
10
11
|
export * from "./Owner.js";
|
|
11
12
|
export * as kysely from "./PublicKysely.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
TypeError,
|
|
27
27
|
} from "../Type.js";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
OwnerError,
|
|
30
30
|
Owner,
|
|
31
31
|
OwnerId,
|
|
32
32
|
OwnerIdBytes,
|
|
@@ -172,12 +172,12 @@ export interface StorageDep {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/** Error indicating a serious write failure. */
|
|
175
|
-
export interface StorageWriteError extends
|
|
175
|
+
export interface StorageWriteError extends OwnerError {
|
|
176
176
|
readonly type: "StorageWriteError";
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/** Error when storage or billing quota is exceeded. */
|
|
180
|
-
export interface StorageQuotaError extends
|
|
180
|
+
export interface StorageQuotaError extends OwnerError {
|
|
181
181
|
readonly type: "StorageQuotaError";
|
|
182
182
|
}
|
|
183
183
|
|