@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
@@ -3,33 +3,54 @@
3
3
  *
4
4
  * Evolu Protocol is a local-first, end-to-end encrypted binary synchronization
5
5
  * protocol optimized for minimal size and maximum speed. It enables data sync
6
- * between a client and a relay, clients in a peer-to-peer (P2P) setup, or
7
- * relays with each other.
6
+ * between a client and a relay. In the future, direct peer-to-peer (P2P) sync
7
+ * between clients will be possible without a relay.
8
+ *
9
+ * Relays don't need to sync with each other—clients using those relays will
10
+ * sync them eventually. If a relay is offline (e.g., for maintenance), it will
11
+ * sync automatically later via client sync logic. For relay backup using
12
+ * SQLite, see https://sqlite.org/rsync.html (uses a similar algorithm to Evolu
13
+ * RBSR).
8
14
  *
9
15
  * Evolu Protocol is designed for SQLite but can be extended to any database. It
10
16
  * implements [Range-Based Set
11
17
  * Reconciliation](https://arxiv.org/abs/2212.13567). To learn how RBSR works,
12
18
  * check [Negentropy](https://logperiodic.com/rbsr.html). Evolu Protocol is
13
19
  * similar to Negentropy but uses different encoding and also provides data
14
- * transfer and ownership.
15
- *
16
- * ### Message Structure
17
- *
18
- * | Field | Notes |
19
- * | :----------------------------- | :------------------------- |
20
- * | **Header** | |
21
- * | - {@link protocolVersion} | |
22
- * | - {@link OwnerId} | |
23
- * | - {@link ProtocolErrorCode} | In non-initiator response. |
24
- * | **Messages** | |
25
- * | - {@link NonNegativeInt} | A number of messages. |
26
- * | - {@link EncryptedCrdtMessage} | |
27
- * | - {@link WriteKey} | In initiator request. |
28
- * | **Ranges** | |
29
- * | - {@link NonNegativeInt} | Number of ranges. |
30
- * | - {@link Range} | |
31
- *
32
- * Every protocol message belongs to an {@link Owner}.
20
+ * transfer, ownership, real-time broadcasting, request-response semantics, and
21
+ * error handling.
22
+ *
23
+ * ### Message structure
24
+ *
25
+ * | Field | Notes |
26
+ * | :----------------------------- | :------------------------ |
27
+ * | **Header** | |
28
+ * | - {@link protocolVersion} | |
29
+ * | - {@link OwnerId} | {@link Owner} |
30
+ * | - messageType | {@link MessageType} |
31
+ * | **Request (messageType=0)** | |
32
+ * | - hasWriteKey | 0 = no, 1 = yes |
33
+ * | - {@link OwnerWriteKey} | If hasWriteKey = 1 |
34
+ * | - subscriptionFlag | {@link SubscriptionFlags} |
35
+ * | **Response (messageType=1)** | |
36
+ * | - {@link ProtocolErrorCode} | |
37
+ * | **Broadcast (messageType=2)** | |
38
+ * | - (no additional fields) | |
39
+ * | **Messages** | |
40
+ * | - {@link NonNegativeInt} | A number of messages. |
41
+ * | - {@link EncryptedCrdtMessage} | |
42
+ * | **Ranges** | |
43
+ * | - {@link NonNegativeInt} | Number of ranges. |
44
+ * | - {@link Range} | |
45
+ *
46
+ * ### WriteKey validation
47
+ *
48
+ * The initiator sends a hasWriteKey flag and optionally a WriteKey. The
49
+ * WriteKey is required when sending messages as a secure token proving the
50
+ * initiator can write changes. It's ok to not send a WriteKey if the initiator
51
+ * is only syncing (read-only) and not sending messages. The non-initiator
52
+ * validates the WriteKey immediately after parsing the initiator header, before
53
+ * processing any messages or ranges.
33
54
  *
34
55
  * ### Synchronization
35
56
  *
@@ -43,38 +64,48 @@
43
64
  * if further sync is needed or possible, continuing until both sides are
44
65
  * synchronized.
45
66
  *
67
+ * The **non-initiator always responds** to provide sync completion feedback,
68
+ * even with empty messages containing only the header and no error. This allows
69
+ * the initiator to detect when synchronization is complete.
70
+ *
46
71
  * Both **Messages** and **Ranges** are optional, allowing each side to send,
47
72
  * sync, or only subscribe data as needed.
48
73
  *
49
- * When the initiator sends data, the {@link WriteKey} is required in Messages as
50
- * a secure token proving the initiator can write changes. The non-initiator
51
- * responds without a {@link WriteKey}, since the initiator’s request already
52
- * signals it wants data. If the non-initiator detects an issue, it sends an
53
- * error code via the `Error` field in the header back to the initiator. In
54
- * relay-to-relay or P2P sync, both sides may require the {@link WriteKey}
55
- * depending on who is the initiator.
74
+ * When the initiator sends data, the {@link OwnerWriteKey} is required as a
75
+ * secure token proving the initiator can write changes. The non-initiator
76
+ * responds without a {@link OwnerWriteKey}, since the initiator’s request
77
+ * already signals it wants data. If the non-initiator detects an issue, it
78
+ * sends an error code via the `Error` field in the header back to the
79
+ * initiator. In relay-to-relay or P2P sync, both sides may require the
80
+ * {@link OwnerWriteKey} depending on who is the initiator.
56
81
  *
57
- * ### Protocol Errors
82
+ * ### Protocol errors
58
83
  *
59
84
  * The protocol uses error codes in the header to signal issues:
60
85
  *
61
86
  * - {@link ProtocolWriteKeyError}: The provided WriteKey is invalid or missing.
62
- * - {@link ProtocolWriteError}: A write operation failed (e.g., due to storage
63
- * limits or billing).
64
- * - {@link ProtocolSyncError}: A generic or unexpected synchronization failure
87
+ * - {@link ProtocolWriteError}: A serious relay-side write failure occurred.
88
+ * - {@link ProtocolSyncError}: A serious relay-side synchronization failure
65
89
  * occurred.
90
+ * - {@link ProtocolQuotaExceededError}: Storage or billing quota exceeded.
66
91
  * - {@link ProtocolUnsupportedVersionError}: Protocol version mismatch.
67
92
  * - {@link ProtocolInvalidDataError}: The message is malformed or corrupted.
68
93
  *
69
- * All protocol errors except `ProtocolInvalidDataError` include the `ownerId`
94
+ * All protocol errors except `ProtocolInvalidDataError` include the `OwnerId`
70
95
  * to allow clients to associate errors with the correct owner.
71
96
  *
72
- * ### Message Size Limit
97
+ * ### Message size limit
73
98
  *
74
99
  * The protocol enforces a strict maximum size for all messages, defined by
75
- * {@link maxProtocolMessageSize}. This ensures every {@link ProtocolMessage} is
76
- * less than or equal to this limit, eliminating the need for applications to
77
- * fragment and reconstruct messages during transmission.
100
+ * {@link ProtocolMessageMaxSize}. This ensures every {@link ProtocolMessage} is
101
+ * less than or equal to this limit, enabling stateless transports, simplified
102
+ * relay implementation, and predictable memory usage. When all messages don't
103
+ * fit within the limit, the protocol automatically continues synchronization in
104
+ * subsequent rounds using range-based reconciliation.
105
+ *
106
+ * Database mutations are limited to 640KB, which is smaller than the protocol
107
+ * message limit to ensure efficient sync with
108
+ * {@link defaultProtocolMessageRangesMaxSize}.
78
109
  *
79
110
  * ### Why Binary?
80
111
  *
@@ -82,13 +113,12 @@
82
113
  *
83
114
  * - Encrypted data doesn’t compress well, unlike plain JSON.
84
115
  * - Message size must be controlled during creation.
85
- * - Sequential byte reading is faster than parsing and can avoid conversions.
116
+ * - Sequential byte reading is faster than parsing and avoids conversions.
86
117
  *
87
118
  * It uses structure-aware encoding, significantly outperforming generic binary
88
119
  * serialization formats with the following optimizations:
89
120
  *
90
121
  * - **NonNegativeInt:** Up to 33% smaller than MessagePack.
91
- * - **Base64Url Strings:** Up to 25% size reduction.
92
122
  * - **DateIso:** Up to 75% smaller.
93
123
  * - **Timestamp Encoding:** Delta encoding for milliseconds and run-length
94
124
  * encoding (RLE) for counters and NodeIds.
@@ -118,26 +148,94 @@
118
148
  * Version negotiation is per-owner, allowing Evolu Protocol to evolve safely
119
149
  * over time and provide clear feedback about version mismatches.
120
150
  *
151
+ * ### Credible exit
152
+ *
153
+ * The protocol specification is intentionally non-configurable to ensure
154
+ * universal compatibility. This design allows applications (users) to switch
155
+ * between any compliant relay without negotiation or compatibility checks
156
+ * beyond version matching. Relays are generic infrastructure that any
157
+ * application can use interchangeably making exit from any single provider
158
+ * technically feasible and economically viable.
159
+ *
121
160
  * @module
122
161
  */
123
162
  import { NonEmptyReadonlyArray } from "../Array.js";
163
+ import { Brand } from "../Brand.js";
124
164
  import { Buffer } from "../Buffer.js";
125
- import { CreateRandomBytesDep, EncryptionKey, SymmetricCryptoDecryptError, SymmetricCryptoDep } from "../Crypto.js";
126
- import { ReadonlyRecord } from "../Object.js";
165
+ import { EncryptionKey, RandomBytesDep, SymmetricCryptoDecryptError, SymmetricCryptoDep } from "../Crypto.js";
127
166
  import { Result } from "../Result.js";
128
167
  import { SqliteValue } from "../Sqlite.js";
129
- import { Id, NanoId, NonNegativeInt, PositiveInt } from "../Type.js";
130
- import { Brand } from "../Types.js";
131
- import { OwnerId, OwnerWithWriteAccess, WriteKey } from "./Owner.js";
132
- import { BinaryTimestamp, NodeId, Timestamp } from "./Timestamp.js";
133
- /** Maximum size of the entire protocol message in bytes. */
134
- export declare const maxProtocolMessageSize: PositiveInt;
135
- /** Maximum size of the ranges in bytes. */
136
- export declare const maxProtocolMessageRangesSize: PositiveInt;
168
+ import { NonNegativeInt, PositiveInt } from "../Type.js";
169
+ import { Owner, OwnerId, OwnerWriteKey } from "./Owner.js";
170
+ import { BaseRange, CrdtMessage, DbChange, EncryptedCrdtMessage, EncryptedDbChange, FingerprintRange, RangeType, SkipRange, StorageDep } from "./Storage.js";
171
+ import { NodeId, Timestamp } from "./Timestamp.js";
172
+ /**
173
+ * Protocol message maximum size.
174
+ *
175
+ * Defines the upper limit for how large a single protocol message can be.
176
+ * Implementations must enforce a maximum size between 1MB and 100MB to ensure
177
+ * compatibility across all Evolu implementations (the maximum size of mutation
178
+ * change is hardcoded and enforced hence the maximum size can't be smaller).
179
+ *
180
+ * Larger maximum sizes can be configured by relays to reduce roundtrips. For
181
+ * example, a dedicated relay with ample resources could configure a 100MB
182
+ * maximum to minimize roundtrips for large syncs.
183
+ *
184
+ * Only relays can safely configure larger sizes, as clients will handle them.
185
+ * Increasing this value on the client side would break compatibility with
186
+ * relays that enforce smaller limits.
187
+ */
188
+ export declare const ProtocolMessageMaxSize: import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int">, number, import("../Type.js").IntError, number, import("../Type.js").NumberError>, "Between1000000-100000000", import("../Type.js").BetweenError<1000000, 100000000>, import("../Type.js").IntError | import("../Type.js").NumberError>;
189
+ export type ProtocolMessageMaxSize = typeof ProtocolMessageMaxSize.Type;
190
+ /**
191
+ * Default {@link ProtocolMessageMaxSize} (1MB).
192
+ *
193
+ * The standard size used across Evolu implementations. Relays with more
194
+ * resources can configure larger sizes to reduce roundtrips.
195
+ */
196
+ export declare const defaultProtocolMessageMaxSize: ProtocolMessageMaxSize;
197
+ /**
198
+ * Protocol message ranges maximum size.
199
+ *
200
+ * Defines the upper limit for how large the ranges section of a protocol
201
+ * message can be. Implementations must enforce a maximum size between 3KB and
202
+ * 100KB to ensure compatibility.
203
+ *
204
+ * The upper bound is set to ensure ranges fit within the default 1MB
205
+ * {@link defaultProtocolMessageMaxSize}, maintaining compatibility between all
206
+ * clients and relays.
207
+ */
208
+ export declare const ProtocolMessageRangesMaxSize: import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int">, number, import("../Type.js").IntError, number, import("../Type.js").NumberError>, "Between3000-100000", import("../Type.js").BetweenError<3000, 100000>, import("../Type.js").IntError | import("../Type.js").NumberError>;
209
+ export type ProtocolMessageRangesMaxSize = typeof ProtocolMessageRangesMaxSize.Type;
210
+ /**
211
+ * Default {@link ProtocolMessageRangesMaxSize} (30KB).
212
+ *
213
+ * The standard size used across Evolu implementations. Relays with more
214
+ * resources can configure larger sizes to reduce roundtrips.
215
+ */
216
+ export declare const defaultProtocolMessageRangesMaxSize: ProtocolMessageRangesMaxSize;
137
217
  /** Evolu Protocol Message. */
138
218
  export type ProtocolMessage = Uint8Array & Brand<"ProtocolMessage">;
139
219
  /** Evolu Protocol version. */
140
220
  export declare const protocolVersion: NonNegativeInt;
221
+ export declare const MessageType: {
222
+ /** Request message from initiator (client) to non-initiator (relay). */
223
+ readonly Request: 0;
224
+ /** Response message from non-initiator (relay) to initiator (client). */
225
+ readonly Response: 1;
226
+ /** Broadcast message from non-initiator (relay) to subscribed clients. */
227
+ readonly Broadcast: 2;
228
+ };
229
+ export type MessageType = (typeof MessageType)[keyof typeof MessageType];
230
+ export declare const SubscriptionFlags: {
231
+ /** No subscription changes for this owner. */
232
+ readonly None: 0;
233
+ /** Subscribe to updates for this owner. */
234
+ readonly Subscribe: 1;
235
+ /** Unsubscribe from updates for this owner. */
236
+ readonly Unsubscribe: 2;
237
+ };
238
+ export type SubscriptionFlag = (typeof SubscriptionFlags)[keyof typeof SubscriptionFlags];
141
239
  export declare const ProtocolErrorCode: {
142
240
  readonly NoError: 0;
143
241
  /** A code for {@link ProtocolWriteKeyError}. */
@@ -146,112 +244,11 @@ export declare const ProtocolErrorCode: {
146
244
  readonly WriteError: 2;
147
245
  /** A code for {@link ProtocolSyncError}. */
148
246
  readonly SyncError: 3;
247
+ /** A code for {@link ProtocolQuotaExceededError}. */
248
+ readonly QuotaExceededError: 4;
149
249
  };
150
250
  type ProtocolErrorCode = (typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
151
- /**
152
- * Evolu Protocol Storage
153
- *
154
- * The protocol is agnostic to storage implementation details—any storage can be
155
- * plugged in, as long as it implements this interface. Implementations must
156
- * handle their own errors; return values only indicates overall success or
157
- * failure.
158
- */
159
- export interface Storage {
160
- readonly getSize: (ownerId: BinaryOwnerId) => NonNegativeInt | null;
161
- readonly fingerprint: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt) => Fingerprint | null;
162
- /**
163
- * Computes fingerprints with their upper bounds in one call.
164
- *
165
- * This function can be replaced with many fingerprint/findLowerBound calls,
166
- * but implementations can leverage it for batching and more efficient
167
- * fingerprint computation.
168
- */
169
- readonly fingerprintRanges: (ownerId: BinaryOwnerId, buckets: ReadonlyArray<NonNegativeInt>, upperBound?: RangeUpperBound) => ReadonlyArray<FingerprintRange> | null;
170
- readonly findLowerBound: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, upperBound: RangeUpperBound) => NonNegativeInt | null;
171
- readonly iterate: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean) => void;
172
- /**
173
- * Authorizes the initiator's {@link WriteKey} for the given
174
- * {@link BinaryOwnerId}.
175
- *
176
- * For a client that does not expect foreign writes, return `false`.
177
- */
178
- readonly validateWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
179
- /** Write encrypted {@link CrdtMessage}s to storage. */
180
- readonly writeMessages: (ownerId: BinaryOwnerId, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => boolean;
181
- /** Read encrypted {@link DbChange}s from storage. */
182
- readonly readDbChange: (ownerId: BinaryOwnerId, timestamp: BinaryTimestamp) => EncryptedDbChange | null;
183
- }
184
- export interface StorageDep {
185
- readonly storage: Storage;
186
- }
187
- /** An encrypted {@link CrdtMessage}. */
188
- export interface EncryptedCrdtMessage {
189
- readonly timestamp: Timestamp;
190
- readonly change: EncryptedDbChange;
191
- }
192
- /** Encrypted DbChange */
193
- export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
194
- /**
195
- * A CRDT message that combines a unique {@link Timestamp} with a
196
- * {@link DbChange}.
197
- */
198
- export interface CrdtMessage {
199
- readonly timestamp: Timestamp;
200
- readonly change: DbChange;
201
- }
202
- /**
203
- * A DbChange is a change to a table row. Together with a unique
204
- * {@link Timestamp}, it forms a {@link CrdtMessage}.
205
- */
206
- export interface DbChange {
207
- readonly table: Base64Url256;
208
- readonly id: Id;
209
- readonly values: ReadonlyRecord<Base64Url256, SqliteValue>;
210
- }
211
- export declare const RangeType: {
212
- readonly Fingerprint: 1;
213
- readonly Skip: 0;
214
- readonly Timestamps: 2;
215
- };
216
- export type RangeType = (typeof RangeType)[keyof typeof RangeType];
217
- export declare const InfiniteUpperBound: unique symbol;
218
- export type InfiniteUpperBound = typeof InfiniteUpperBound;
219
- /**
220
- * Union type for Range's upperBound: either a {@link BinaryTimestamp} or
221
- * {@link InfiniteUpperBound}.
222
- */
223
- export type RangeUpperBound = BinaryTimestamp | InfiniteUpperBound;
224
- interface BaseRange {
225
- readonly upperBound: RangeUpperBound;
226
- }
227
- export interface SkipRange extends BaseRange {
228
- readonly type: typeof RangeType.Skip;
229
- }
230
- export interface FingerprintRange extends BaseRange {
231
- readonly type: typeof RangeType.Fingerprint;
232
- readonly fingerprint: Fingerprint;
233
- }
234
- /**
235
- * A cryptographic hash used for efficiently comparing collections of
236
- * {@link BinaryTimestamp}s.
237
- *
238
- * It consists of the first {@link fingerprintSize} bytes of the SHA-256 hash of
239
- * one or more timestamps.
240
- */
241
- export type Fingerprint = Uint8Array & Brand<"Fingerprint">;
242
- export declare const fingerprintSize: NonNegativeInt;
243
- /** A fingerprint of an empty range. */
244
- export declare const zeroFingerprint: Fingerprint;
245
- export interface TimestampsRange extends BaseRange {
246
- readonly type: typeof RangeType.Timestamps;
247
- readonly timestamps: ReadonlyArray<BinaryTimestamp>;
248
- }
249
- export interface TimestampsRangeWithTimestampsBuffer extends BaseRange {
250
- readonly type: typeof RangeType.Timestamps;
251
- readonly timestamps: TimestampsBuffer;
252
- }
253
- export type Range = SkipRange | FingerprintRange | TimestampsRange;
254
- export type ProtocolError = ProtocolUnsupportedVersionError | ProtocolInvalidDataError | ProtocolWriteKeyError | ProtocolWriteError | ProtocolSyncError;
251
+ export type ProtocolError = ProtocolUnsupportedVersionError | ProtocolInvalidDataError | ProtocolWriteKeyError | ProtocolWriteError | ProtocolSyncError | ProtocolQuotaExceededError | ProtocolTimestampMismatchError;
255
252
  /** Base interface for all protocol errors. */
256
253
  export interface ProtocolErrorBase {
257
254
  readonly ownerId: OwnerId;
@@ -272,34 +269,55 @@ export interface ProtocolInvalidDataError {
272
269
  readonly data: globalThis.Uint8Array;
273
270
  readonly error: unknown;
274
271
  }
275
- /** Error when a {@link WriteKey} is invalid, missing, or fails validation. */
272
+ /** Error when a {@link OwnerWriteKey} is invalid, missing, or fails validation. */
276
273
  export interface ProtocolWriteKeyError extends ProtocolErrorBase {
277
274
  readonly type: "ProtocolWriteKeyError";
278
275
  }
279
276
  /**
280
- * Error when a write fails due to storage limits or billing requirements.
281
- * Indicates the need to expand capacity or resolve payment issues.
277
+ * Error indicating a serious relay-side write failure. Clients should log this
278
+ * error and show a generic sync error to the user.
282
279
  */
283
280
  export interface ProtocolWriteError extends ProtocolErrorBase {
284
281
  readonly type: "ProtocolWriteError";
285
282
  }
286
283
  /**
287
- * Error indicating a synchronization failure during the protocol exchange. Used
288
- * for unexpected or generic sync errors not covered by other error types.
284
+ * Error indicating a serious relay-side synchronization failure. Clients should
285
+ * log this error and show a generic sync error to the user.
289
286
  */
290
287
  export interface ProtocolSyncError extends ProtocolErrorBase {
291
288
  readonly type: "ProtocolSyncError";
292
289
  }
290
+ /**
291
+ * Error when storage or billing quota is exceeded. Clients should prompt the
292
+ * user to upgrade their plan or expand capacity.
293
+ *
294
+ * TODO: Add callback to relay config to check quota and return this error when
295
+ * limits are reached.
296
+ */
297
+ export interface ProtocolQuotaExceededError extends ProtocolErrorBase {
298
+ readonly type: "ProtocolQuotaExceededError";
299
+ }
300
+ /**
301
+ * Error when embedded timestamp doesn't match expected timestamp in
302
+ * EncryptedDbChange. Indicates potential tampering or corruption of CRDT
303
+ * messages.
304
+ */
305
+ export interface ProtocolTimestampMismatchError {
306
+ readonly type: "ProtocolTimestampMismatchError";
307
+ readonly expected: Timestamp;
308
+ readonly embedded: Timestamp;
309
+ }
293
310
  /**
294
311
  * Creates a {@link ProtocolMessage} from CRDT messages.
295
312
  *
296
- * If the message size would exceed {@link maxProtocolMessageSize}, the protocol
297
- * ensures all messages will be sent in the next round(s) even over
313
+ * If the message size would exceed {@link defaultProtocolMessageMaxSize}, the
314
+ * protocol ensures all messages will be sent in the next round(s) even over
298
315
  * unidirectional and stateless transports.
299
316
  */
300
- export declare const createProtocolMessageFromCrdtMessages: (deps: SymmetricCryptoDep & CreateRandomBytesDep) => (owner: OwnerWithWriteAccess, messages: NonEmptyReadonlyArray<CrdtMessage>, maxSize?: PositiveInt) => ProtocolMessage;
317
+ export declare const createProtocolMessageFromCrdtMessages: (deps: RandomBytesDep & SymmetricCryptoDep) => (owner: Owner, messages: NonEmptyReadonlyArray<CrdtMessage>, maxSize?: ProtocolMessageMaxSize) => ProtocolMessage;
301
318
  /** Creates a {@link ProtocolMessage} for sync. */
302
- export declare const createProtocolMessageForSync: (deps: StorageDep) => (ownerId: OwnerId) => ProtocolMessage | null;
319
+ export declare const createProtocolMessageForSync: (deps: StorageDep) => (ownerId: OwnerId, subscriptionFlag?: SubscriptionFlag) => ProtocolMessage | null;
320
+ export declare const createProtocolMessageForUnsubscribe: (ownerId: OwnerId) => ProtocolMessage;
303
321
  /**
304
322
  * Mutable builder for constructing {@link ProtocolMessage} respecting size
305
323
  * limits.
@@ -313,13 +331,24 @@ export interface ProtocolMessageBuffer {
313
331
  readonly unwrap: () => ProtocolMessage;
314
332
  readonly getSize: () => PositiveInt;
315
333
  }
316
- export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options?: {
317
- readonly errorCode?: ProtocolErrorCode;
318
- readonly writeKey?: WriteKey;
319
- readonly totalMaxSize?: PositiveInt | undefined;
320
- readonly rangesMaxSize?: PositiveInt | undefined;
334
+ export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options: {
335
+ readonly totalMaxSize?: ProtocolMessageMaxSize | undefined;
336
+ readonly rangesMaxSize?: ProtocolMessageRangesMaxSize | undefined;
321
337
  readonly version?: NonNegativeInt;
322
- }) => ProtocolMessageBuffer;
338
+ } & ({
339
+ readonly messageType: typeof MessageType.Request;
340
+ readonly writeKey?: OwnerWriteKey;
341
+ readonly subscriptionFlag?: SubscriptionFlag;
342
+ } | {
343
+ readonly messageType: typeof MessageType.Response;
344
+ readonly errorCode: ProtocolErrorCode;
345
+ } | {
346
+ readonly messageType: typeof MessageType.Broadcast;
347
+ })) => ProtocolMessageBuffer;
348
+ export interface TimestampsRangeWithTimestampsBuffer extends BaseRange {
349
+ readonly type: typeof RangeType.Timestamps;
350
+ readonly timestamps: TimestampsBuffer;
351
+ }
323
352
  export interface TimestampsBuffer {
324
353
  readonly add: (timestamp: Timestamp) => void;
325
354
  readonly addInfinite: () => void;
@@ -329,67 +358,71 @@ export interface TimestampsBuffer {
329
358
  }
330
359
  export declare const createTimestampsBuffer: () => TimestampsBuffer;
331
360
  export interface ApplyProtocolMessageAsClientOptions {
332
- getWriteKey?: (ownerId: OwnerId) => WriteKey | null;
361
+ getWriteKey?: (ownerId: OwnerId) => OwnerWriteKey | null;
333
362
  /** For testing purposes only; should not be used in production. */
334
363
  version?: NonNegativeInt;
335
- totalMaxSize?: PositiveInt;
336
- rangesMaxSize?: PositiveInt;
364
+ rangesMaxSize?: ProtocolMessageRangesMaxSize;
337
365
  }
338
- export declare const applyProtocolMessageAsClient: (deps: StorageDep) => (inputMessage: Uint8Array, { getWriteKey, version, totalMaxSize, rangesMaxSize, }?: ApplyProtocolMessageAsClientOptions) => Result<ProtocolMessage | null, ProtocolError>;
366
+ /**
367
+ * Result type for {@link applyProtocolMessageAsClient} that distinguishes
368
+ * between responses to client requests and broadcast messages.
369
+ */
370
+ export type ApplyProtocolMessageAsClientResult = {
371
+ readonly type: "response";
372
+ readonly message: ProtocolMessage;
373
+ } | {
374
+ readonly type: "no-response";
375
+ } | {
376
+ readonly type: "broadcast";
377
+ };
378
+ export declare const applyProtocolMessageAsClient: (deps: StorageDep) => (inputMessage: Uint8Array, options?: ApplyProtocolMessageAsClientOptions) => Promise<Result<ApplyProtocolMessageAsClientResult, ProtocolInvalidDataError | ProtocolSyncError | ProtocolUnsupportedVersionError | ProtocolWriteError | ProtocolWriteKeyError | ProtocolQuotaExceededError>>;
339
379
  export interface ApplyProtocolMessageAsRelayOptions {
340
380
  /** To subscribe an owner for broadcasting. */
341
381
  subscribe?: (ownerId: OwnerId) => void;
382
+ /** To unsubscribe an owner from broadcasting. */
383
+ unsubscribe?: (ownerId: OwnerId) => void;
342
384
  /** To broadcast a protocol message to all subscribers. */
343
385
  broadcast?: (ownerId: OwnerId, message: ProtocolMessage) => void;
344
- totalMaxSize?: PositiveInt;
345
- rangesMaxSize?: PositiveInt;
386
+ totalMaxSize?: ProtocolMessageMaxSize;
387
+ rangesMaxSize?: ProtocolMessageRangesMaxSize;
346
388
  }
347
- export declare const applyProtocolMessageAsRelay: (deps: StorageDep) => (inputMessage: Uint8Array, { subscribe, broadcast, totalMaxSize, rangesMaxSize, }?: ApplyProtocolMessageAsRelayOptions,
348
- /** For testing purposes only; should not be used in production. */
349
- version?: number & Brand<"Int"> & Brand<"NonNegative">) => Result<ProtocolMessage | null, ProtocolInvalidDataError>;
350
- /** Binary representation of {@link Id}. */
351
- export type BinaryId = Uint8Array & Brand<"BinaryId">;
352
- export declare const binaryIdLength: NonNegativeInt;
353
- export declare const idToBinaryId: (id: Id) => BinaryId;
354
- export declare const binaryIdToId: (binaryId: BinaryId) => Id;
355
- /** Binary representation of {@link OwnerId}. */
356
- export type BinaryOwnerId = Uint8Array & Brand<"BinaryOwnerId">;
357
- export declare const ownerIdToBinaryOwnerId: (ownerId: OwnerId) => BinaryOwnerId;
358
- export declare const binaryOwnerIdToOwnerId: (binaryOwnerId: BinaryOwnerId) => OwnerId;
359
- /**
360
- * Base64Url string with maximum length of 256 characters. Encoding strings as
361
- * Base64UrlString saves up to 25% in size compared to regular strings.
362
- */
363
- export declare const Base64Url256: import("../Type.js").BrandType<import("../Type.js").Type<"Brand", string & Brand<"Base64Url">, string, import("../Type.js").RegexError<"Base64Url">, string, import("../Type.js").StringError>, "MaxLength256", import("../Type.js").MaxLengthError<256>, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">>;
364
- export type Base64Url256 = typeof Base64Url256.Type;
365
- /**
366
- * Union type for all variants of Base64Url strings with limited length. All
367
- * these types use Base64Url alphabet and are < 256 characters.
368
- */
369
- export type Base64Url256Variant = Base64Url256 | Id | NanoId | OwnerId;
370
389
  /**
371
- * Converts a Base64Url string to a Uint8Array for binary storage. This encoding
372
- * is more space-efficient than UTF-8 for Base64Url strings.
390
+ * Result type for {@link applyProtocolMessageAsRelay}.
391
+ *
392
+ * Unlike {@link ApplyProtocolMessageAsClientResult}, relays always respond with
393
+ * a message to provide sync completion feedback. This ensures the initiator can
394
+ * reliably detect when synchronization is complete, even when there's nothing
395
+ * to sync. Clients may choose not to respond in certain cases (like when they
396
+ * receive broadcast messages or when they lack a write key for syncing).
373
397
  */
374
- export declare const base64Url256ToBytes: (string: Base64Url256Variant) => globalThis.Uint8Array;
375
- export declare const decodeBase64Url256: (buffer: Buffer, stringLength: number) => Base64Url256Variant;
398
+ export interface ApplyProtocolMessageAsRelayResult {
399
+ readonly type: "response";
400
+ readonly message: ProtocolMessage;
401
+ }
402
+ export declare const applyProtocolMessageAsRelay: (deps: StorageDep) => (inputMessage: Uint8Array, options?: ApplyProtocolMessageAsRelayOptions,
403
+ /** For testing purposes only; should not be used in production. */
404
+ version?: number & Brand<"Int"> & Brand<"NonNegative">) => Promise<Result<ApplyProtocolMessageAsRelayResult, ProtocolInvalidDataError>>;
376
405
  /**
377
406
  * Evolu uses MessagePack to handle all number variants except for
378
407
  * NonNegativeInt. For NonNegativeInt, Evolu provides more efficient encoding.
379
408
  */
380
409
  export declare const encodeNumber: (buffer: Buffer, number: number) => void;
381
410
  export declare const decodeNumber: (buffer: Buffer) => number;
382
- export declare const binaryTimestampToFingerprint: (timestamp: BinaryTimestamp) => Fingerprint;
383
411
  /**
384
412
  * Encodes and encrypts a {@link DbChange} using the provided owner's encryption
385
413
  * key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
414
+ *
415
+ * The format includes the protocol version for backward compatibility and the
416
+ * timestamp for tamper-proof verification that the timestamp matches the change
417
+ * data.
386
418
  */
387
- export declare const encodeAndEncryptDbChange: (deps: SymmetricCryptoDep) => (change: DbChange, key: EncryptionKey) => EncryptedDbChange;
419
+ export declare const encodeAndEncryptDbChange: (deps: SymmetricCryptoDep) => (message: CrdtMessage, key: EncryptionKey) => EncryptedDbChange;
388
420
  /**
389
- * Decrypts and decodes an {@link EncryptedDbChange} using the provided owner's
390
- * encryption key.
421
+ * Decrypts and decodes an {@link EncryptedCrdtMessage} using the provided
422
+ * owner's encryption key. Verifies that the embedded timestamp matches the
423
+ * expected timestamp to ensure message integrity.
391
424
  */
392
- export declare const decryptAndDecodeDbChange: (deps: SymmetricCryptoDep) => (change: EncryptedDbChange, key: EncryptionKey) => Result<DbChange, SymmetricCryptoDecryptError | ProtocolInvalidDataError>;
425
+ export declare const decryptAndDecodeDbChange: (deps: SymmetricCryptoDep) => (message: EncryptedCrdtMessage, key: EncryptionKey) => Result<DbChange, SymmetricCryptoDecryptError | ProtocolInvalidDataError | ProtocolTimestampMismatchError>;
393
426
  /**
394
427
  * Encodes a non-negative integer into a variable-length integer format. It's
395
428
  * more efficient than encoding via {@link encodeNumber}.
@@ -409,21 +442,33 @@ export declare const encodeString: (buffer: Buffer, value: string) => void;
409
442
  export declare const decodeString: (buffer: Buffer) => string;
410
443
  export declare const encodeNodeId: (buffer: Buffer, nodeId: NodeId) => void;
411
444
  export declare const decodeNodeId: (buffer: Buffer) => NodeId;
412
- export declare const encodeBase64Url256: (buffer: Buffer, string: Base64Url256Variant) => void;
413
- export declare const decodeBase64Url256WithLength: (buffer: Buffer) => Base64Url256;
414
445
  export declare const ProtocolValueType: {
415
446
  readonly String: NonNegativeInt;
416
447
  readonly Number: NonNegativeInt;
417
448
  readonly Null: NonNegativeInt;
418
- readonly Binary: NonNegativeInt;
419
- readonly Id: NonNegativeInt;
420
- readonly Base64Url256: NonNegativeInt;
449
+ readonly Bytes: NonNegativeInt;
421
450
  readonly NonNegativeInt: NonNegativeInt;
451
+ readonly EmptyString: NonNegativeInt;
452
+ readonly Base64Url: NonNegativeInt;
453
+ readonly Id: NonNegativeInt;
422
454
  readonly Json: NonNegativeInt;
423
455
  readonly DateIsoWithNonNegativeTime: NonNegativeInt;
424
456
  readonly DateIsoWithNegativeTime: NonNegativeInt;
425
457
  };
426
458
  export declare const encodeSqliteValue: (buffer: Buffer, value: SqliteValue) => void;
427
459
  export declare const decodeSqliteValue: (buffer: Buffer) => SqliteValue;
460
+ /**
461
+ * Decodes a ProtocolMessage into a readable JSON object for debugging.
462
+ *
463
+ * Note: This is a stub for future implementation. It should use:
464
+ *
465
+ * - DecodeVersionAndOwner
466
+ * - DecodeError or decodeWriteKeys (depending on context)
467
+ * - DecodeMessages
468
+ * - DecodeRanges
469
+ *
470
+ * If you want to help, please contribute to this function.
471
+ */
472
+ export declare const decodeProtocolMessageToJson: (_protocolMessage: ProtocolMessage, _isInitiator: boolean) => unknown;
428
473
  export {};
429
474
  //# sourceMappingURL=Protocol.d.ts.map