@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.
Files changed (43) hide show
  1. package/dist/src/Object.d.ts +16 -1
  2. package/dist/src/Object.d.ts.map +1 -1
  3. package/dist/src/Object.js +16 -1
  4. package/dist/src/Order.d.ts +2 -1
  5. package/dist/src/Order.d.ts.map +1 -1
  6. package/dist/src/Order.js +1 -0
  7. package/dist/src/WebSocket.d.ts.map +1 -1
  8. package/dist/src/WebSocket.js +4 -1
  9. package/dist/src/local-first/Db.d.ts.map +1 -1
  10. package/dist/src/local-first/Db.js +5 -11
  11. package/dist/src/local-first/Diff.js +2 -2
  12. package/dist/src/local-first/Evolu.d.ts +14 -11
  13. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  14. package/dist/src/local-first/Evolu.js +8 -5
  15. package/dist/src/local-first/Owner.d.ts +39 -42
  16. package/dist/src/local-first/Owner.d.ts.map +1 -1
  17. package/dist/src/local-first/Owner.js +22 -29
  18. package/dist/src/local-first/Protocol.d.ts +6 -6
  19. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  20. package/dist/src/local-first/Protocol.js +2 -2
  21. package/dist/src/local-first/Public.d.ts +1 -0
  22. package/dist/src/local-first/Public.d.ts.map +1 -1
  23. package/dist/src/local-first/Storage.d.ts +3 -3
  24. package/dist/src/local-first/Storage.d.ts.map +1 -1
  25. package/dist/src/local-first/Sync.d.ts +10 -17
  26. package/dist/src/local-first/Sync.d.ts.map +1 -1
  27. package/dist/src/local-first/Sync.js +108 -130
  28. package/dist/src/local-first/Timestamp.d.ts +13 -0
  29. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  30. package/dist/src/local-first/Timestamp.js +14 -1
  31. package/package.json +1 -1
  32. package/src/Object.ts +20 -1
  33. package/src/Order.ts +2 -1
  34. package/src/WebSocket.ts +6 -1
  35. package/src/local-first/Db.ts +5 -12
  36. package/src/local-first/Diff.ts +2 -2
  37. package/src/local-first/Evolu.ts +27 -15
  38. package/src/local-first/Owner.ts +47 -57
  39. package/src/local-first/Protocol.ts +8 -8
  40. package/src/local-first/Public.ts +1 -0
  41. package/src/local-first/Storage.ts +3 -3
  42. package/src/local-first/Sync.ts +123 -175
  43. 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 the timestamp is always valid
236
+ // `as DateIso` is safe because Timestamp guarantees a valid `millis`
224
237
  new Date(timestamp.millis).toISOString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "7.2.1",
3
+ "version": "7.2.3",
4
4
  "description": "TypeScript library and local-first platform",
5
5
  "keywords": [
6
6
  "evolu",
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: Record<UserId, string> = {};
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
- export const orderUint8Array: Order<globalThis.Uint8Array> = (a, b) => {
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
- socket.close();
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
 
@@ -336,9 +336,12 @@ export const createDbWorkerForPlatform = (
336
336
  DbWorkerInput,
337
337
  DbWorkerOutput,
338
338
  DbWorkerDeps
339
- >({ init: createDbWorkerDeps(platformDeps), handlers });
339
+ >({
340
+ init: createInit(platformDeps),
341
+ handlers,
342
+ });
340
343
 
341
- const createDbWorkerDeps =
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;
@@ -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 = Object.create(null) as Record<string, unknown>;
139
+ const result = createRecord();
140
140
  for (const key in obj) {
141
141
  result[key] = parse(obj[key]);
142
142
  }
@@ -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 an owner. Using an owner means syncing it and subscribing to
427
- * broadcasted changes. Returns a function to stop using the owner.
427
+ * Use a {@link SyncOwner}. Returns a {@link UnuseOwner}.
428
428
  *
429
- * Transport connections are automatically deduplicated and reference-counted,
430
- * so multiple owners using the same transport will share a single
431
- * connection.
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 and subscribing to changes).
437
- * const unuse = evolu.useOwner(shardOwner);
438
+ * // Use an owner (starts syncing).
439
+ * const unuseOwner = evolu.useOwner(shardOwner);
438
440
  *
439
441
  * // Later, stop using the owner.
440
- * unuse();
442
+ * unuseOwner();
441
443
  *
442
444
  * // Bulk operations.
443
- * const unuses = owners.map((owner) => evolu.useOwner(owner));
444
- * // Later: unuses.forEach(unuse => unuse());
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) => () => void;
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 = DbChange.orThrow({
734
+ const dbChange = {
730
735
  table,
731
736
  id,
732
737
  values,
733
738
  isInsert: kind === "insert" || kind === "upsert",
734
- isDelete: SqliteBoolean.is(isDeleted) ? Boolean(isDeleted) : null,
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 },
@@ -22,15 +22,23 @@ import type { EncryptedDbChange, Storage } from "./Storage.js";
22
22
  import { TimestampBytes } from "./Timestamp.js";
23
23
 
24
24
  /**
25
- * The Owner represents ownership of data in Evolu. Every database change is
26
- * assigned to an owner, enabling sync functionality and access control.
25
+ * {@link Owner} without a {@link OwnerWriteKey}.
27
26
  *
28
- * Owners enable **partial sync** - applications can choose which owners to
29
- * sync, allowing selective data synchronization based on specific needs.
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 **real data deletion** - while individual changes in
32
- * local-first/distributed systems can only be marked as deleted, entire owners
33
- * can be completely deleted from both relays and devices (except for
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 EncryptionKey}: Symmetric encryption key for data protection
57
+ * - {@link OwnerEncryptionKey}: Symmetric encryption key for data protection
50
58
  * - {@link OwnerWriteKey}: Authentication token for write operations (rotatable)
51
59
  *
52
- * @see {@link createOwner}
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
- export const createOwner = (secret: OwnerSecret): Owner => ({
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
- ...(transports && { transports }),
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
- ...(transports && { transports }),
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: OwnerSecret,
265
- transports?: ReadonlyArray<OwnerTransport>,
266
- ): SharedOwner => {
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
- /** Base interface for all owner errors. */
387
- export interface BaseOwnerError {
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 BaseOwnerError {
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 BaseOwnerError {
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 BaseOwnerError {
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 BaseOwnerError {
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 BaseOwnerError {
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 = Object.create(null) as Record<string, SqliteValue>;
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
- BaseOwnerError,
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 BaseOwnerError {
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 BaseOwnerError {
180
+ export interface StorageQuotaError extends OwnerError {
181
181
  readonly type: "StorageQuotaError";
182
182
  }
183
183