@evolu/common 6.0.1-preview.2 → 6.0.1-preview.21

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