@evolu/common 6.0.1-preview.28 → 6.0.1-preview.29

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 (59) hide show
  1. package/dist/src/Array.d.ts +58 -5
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +53 -5
  4. package/dist/src/Evolu/Evolu.d.ts +3 -3
  5. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  6. package/dist/src/Evolu/Evolu.js +3 -3
  7. package/dist/src/Evolu/Owner.d.ts +48 -19
  8. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  9. package/dist/src/Evolu/Owner.js +11 -2
  10. package/dist/src/Evolu/Protocol.d.ts +31 -31
  11. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  12. package/dist/src/Evolu/Protocol.js +51 -28
  13. package/dist/src/Evolu/Relay.d.ts +40 -25
  14. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  15. package/dist/src/Evolu/Relay.js +106 -49
  16. package/dist/src/Evolu/Storage.d.ts +59 -12
  17. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  18. package/dist/src/Evolu/Storage.js +77 -50
  19. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  20. package/dist/src/Evolu/Sync.js +14 -5
  21. package/dist/src/Evolu/Timestamp.d.ts +25 -0
  22. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  23. package/dist/src/Evolu/Timestamp.js +25 -0
  24. package/dist/src/Instances.d.ts +34 -0
  25. package/dist/src/Instances.d.ts.map +1 -0
  26. package/dist/src/{Multiton.js → Instances.js} +20 -9
  27. package/dist/src/Sqlite.d.ts +6 -0
  28. package/dist/src/Sqlite.d.ts.map +1 -1
  29. package/dist/src/Sqlite.js +6 -0
  30. package/dist/src/Task.d.ts +75 -0
  31. package/dist/src/Task.d.ts.map +1 -1
  32. package/dist/src/Task.js +29 -6
  33. package/dist/src/Time.d.ts +7 -1
  34. package/dist/src/Time.d.ts.map +1 -1
  35. package/dist/src/Time.js +13 -2
  36. package/dist/src/Type.d.ts +56 -9
  37. package/dist/src/Type.d.ts.map +1 -1
  38. package/dist/src/Type.js +40 -8
  39. package/dist/src/index.d.ts +1 -1
  40. package/dist/src/index.d.ts.map +1 -1
  41. package/dist/src/index.js +1 -1
  42. package/package.json +1 -1
  43. package/src/Array.ts +76 -11
  44. package/src/Evolu/Evolu.ts +4 -5
  45. package/src/Evolu/Owner.ts +75 -26
  46. package/src/Evolu/Protocol.ts +90 -61
  47. package/src/Evolu/Relay.ts +182 -77
  48. package/src/Evolu/Storage.ts +157 -67
  49. package/src/Evolu/Sync.ts +18 -6
  50. package/src/Evolu/Timestamp.ts +25 -0
  51. package/src/Instances.ts +90 -0
  52. package/src/Sqlite.ts +6 -0
  53. package/src/Task.ts +88 -7
  54. package/src/Time.ts +13 -2
  55. package/src/Type.ts +56 -9
  56. package/src/index.ts +1 -1
  57. package/dist/src/Multiton.d.ts +0 -50
  58. package/dist/src/Multiton.d.ts.map +0 -1
  59. package/src/Multiton.ts +0 -98
@@ -85,10 +85,10 @@
85
85
  *
86
86
  * - {@link ProtocolWriteKeyError}: The provided WriteKey is invalid or missing.
87
87
  * - {@link ProtocolWriteError}: A serious relay-side write failure occurred.
88
+ * - {@link ProtocolQuotaError}: Storage or billing quota exceeded.
88
89
  * - {@link ProtocolSyncError}: A serious relay-side synchronization failure
89
90
  * occurred.
90
- * - {@link ProtocolQuotaExceededError}: Storage or billing quota exceeded.
91
- * - {@link ProtocolUnsupportedVersionError}: Protocol version mismatch.
91
+ * - {@link ProtocolVersionError}: Protocol version mismatch.
92
92
  * - {@link ProtocolInvalidDataError}: The message is malformed or corrupted.
93
93
  *
94
94
  * All protocol errors except `ProtocolInvalidDataError` include the `OwnerId`
@@ -159,6 +159,18 @@
159
159
  *
160
160
  * @module
161
161
  */
162
+ /**
163
+ * TODO:
164
+ *
165
+ * - The client-relay naming convention in functions like
166
+ * `applyProtocolMessageAsClient` and `applyProtocolMessageAsRelay` is not
167
+ * ideal. In the future, clients will be able to sync directly with each other
168
+ * (P2P), making the current naming misleading. Consider using
169
+ * initiator/non-initiator terminology instead, and consolidate into a single
170
+ * `applyProtocolMessage` function with conditional arguments to reduce code
171
+ * duplication.
172
+ * - ProtocolQuotaError should return storedBytes and actual quota.
173
+ */
162
174
  import { Packr } from "msgpackr";
163
175
  import { isNonEmptyReadonlyArray } from "../Array.js";
164
176
  import { assert } from "../Assert.js";
@@ -248,10 +260,10 @@ export const ProtocolErrorCode = {
248
260
  WriteKeyError: 1,
249
261
  /** A code for {@link ProtocolWriteError}. */
250
262
  WriteError: 2,
263
+ /** A code for {@link ProtocolQuotaError}. */
264
+ QuotaError: 3,
251
265
  /** A code for {@link ProtocolSyncError}. */
252
- SyncError: 3,
253
- /** A code for {@link ProtocolQuotaExceededError}. */
254
- QuotaExceededError: 4,
266
+ SyncError: 4,
255
267
  };
256
268
  /**
257
269
  * Creates a {@link ProtocolMessage} from CRDT messages.
@@ -524,14 +536,15 @@ const createRunLengthEncoder = (encodeValue) => {
524
536
  };
525
537
  export const applyProtocolMessageAsClient = (deps) => async (inputMessage, options = {}) => {
526
538
  // try-catch instead of Result for performance and stacktraces
539
+ // DEV: Measure it again, I think we should use Result with new Error.
527
540
  try {
528
541
  const input = createBuffer(inputMessage);
529
542
  const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
530
543
  const version = options.version ?? protocolVersion;
531
544
  if (requestedVersion !== version) {
532
545
  return err({
533
- type: "ProtocolUnsupportedVersionError",
534
- unsupportedVersion: requestedVersion,
546
+ type: "ProtocolVersionError",
547
+ version: requestedVersion,
535
548
  isInitiator: version < requestedVersion,
536
549
  ownerId,
537
550
  });
@@ -553,14 +566,14 @@ export const applyProtocolMessageAsClient = (deps) => async (inputMessage, optio
553
566
  type: "ProtocolWriteError",
554
567
  ownerId,
555
568
  });
556
- case ProtocolErrorCode.SyncError:
569
+ case ProtocolErrorCode.QuotaError:
557
570
  return err({
558
- type: "ProtocolSyncError",
571
+ type: "ProtocolQuotaError",
559
572
  ownerId,
560
573
  });
561
- case ProtocolErrorCode.QuotaExceededError:
574
+ case ProtocolErrorCode.SyncError:
562
575
  return err({
563
- type: "ProtocolQuotaExceededError",
576
+ type: "ProtocolSyncError",
564
577
  ownerId,
565
578
  });
566
579
  default:
@@ -570,9 +583,11 @@ export const applyProtocolMessageAsClient = (deps) => async (inputMessage, optio
570
583
  }
571
584
  const messages = decodeMessages(input);
572
585
  const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
573
- if (isNonEmptyReadonlyArray(messages) &&
574
- !(await deps.storage.writeMessages(ownerIdBytes, messages))) {
575
- return ok({ type: "no-response" });
586
+ if (isNonEmptyReadonlyArray(messages)) {
587
+ const writeResult = await deps.storage.writeMessages(ownerIdBytes, messages);
588
+ // Errors are handled by the Storage. Here we just stop syncing.
589
+ if (!writeResult.ok)
590
+ return ok({ type: "no-response" });
576
591
  }
577
592
  // Now: No writeKey, no sync.
578
593
  // TODO: Allow to sync SharedReadonlyOwner
@@ -611,9 +626,10 @@ export const applyProtocolMessageAsClient = (deps) => async (inputMessage, optio
611
626
  }
612
627
  };
613
628
  export const applyProtocolMessageAsRelay = (deps) => async (inputMessage, options = {},
614
- /** For testing purposes only; should not be used in production. */
629
+ /** For tests only. */
615
630
  version = protocolVersion) => {
616
631
  // try-catch instead of Result for performance and stacktraces
632
+ // DEV: Measure it again, I think we should use Result with new Error.
617
633
  try {
618
634
  const input = createBuffer(inputMessage);
619
635
  const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
@@ -669,17 +685,33 @@ version = protocolVersion) => {
669
685
  }).unwrap(),
670
686
  });
671
687
  }
688
+ const writeResult = await deps.storage.writeMessages(ownerIdBytes, messages);
689
+ if (!writeResult.ok) {
690
+ const errorCode = writeResult.error.type === "StorageWriteError"
691
+ ? ProtocolErrorCode.WriteError
692
+ : ProtocolErrorCode.QuotaError;
693
+ const message = createProtocolMessageBuffer(ownerId, {
694
+ messageType: MessageType.Response,
695
+ errorCode,
696
+ }).unwrap();
697
+ return ok({ type: "response", message });
698
+ }
672
699
  /**
673
- * Broadcast messages to all subscribed devices. This ensures real-time
700
+ * Broadcast messages to all subscribed owners for real-time
674
701
  * synchronization between clients.
675
702
  *
703
+ * Messages are only broadcasted after successful write to ensure
704
+ * devices that can still sync aren't affected by quota errors, and to
705
+ * prevent using a half-working relay service (broadcasting without
706
+ * persistence).
707
+ *
676
708
  * When a relay's database is deleted or clients migrate to a new relay
677
709
  * (without data migration), clients will sync their data to the relay,
678
710
  * and the relay will broadcast those messages to other connected
679
711
  * clients. Those clients may receive messages they already have, but
680
- * this is safe because `applyMessages` is idempotent. As the relay
681
- * becomes more synchronized with clients over time, fewer duplicate
682
- * messages will be broadcasted.
712
+ * this is safe because Evolu sync is idempotent. As the relay becomes
713
+ * more synchronized with clients over time, fewer duplicate messages
714
+ * will be broadcasted.
683
715
  */
684
716
  if (options.broadcast) {
685
717
  const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
@@ -693,15 +725,6 @@ version = protocolVersion) => {
693
725
  }
694
726
  options.broadcast(ownerId, broadcastBuffer.unwrap());
695
727
  }
696
- if (!(await deps.storage.writeMessages(ownerIdBytes, messages))) {
697
- return ok({
698
- type: "response",
699
- message: createProtocolMessageBuffer(ownerId, {
700
- messageType: MessageType.Response,
701
- errorCode: ProtocolErrorCode.WriteError,
702
- }).unwrap(),
703
- });
704
- }
705
728
  }
706
729
  const ranges = decodeRanges(input);
707
730
  const output = createProtocolMessageBuffer(ownerId, {
@@ -3,13 +3,12 @@ import { TimingSafeEqualDep } from "../Crypto.js";
3
3
  import { LazyValue } from "../Function.js";
4
4
  import { Result } from "../Result.js";
5
5
  import { SqliteDep, SqliteError } from "../Sqlite.js";
6
+ import { MaybeAsync } from "../Task.js";
6
7
  import { SimpleName } from "../Type.js";
7
8
  import { OwnerId } from "./Owner.js";
8
9
  import { ProtocolInvalidDataError } from "./Protocol.js";
9
- import { CreateBaseSqliteStorageOptions, SqliteStorageDeps, Storage } from "./Storage.js";
10
- export interface Relay extends Disposable {
11
- }
12
- export interface RelayConfig extends ConsoleConfig {
10
+ import { CreateBaseSqliteStorageConfig, SqliteStorageDeps, Storage, StorageConfig } from "./Storage.js";
11
+ export interface RelayConfig extends ConsoleConfig, StorageConfig {
13
12
  /**
14
13
  * The relay name.
15
14
  *
@@ -18,21 +17,22 @@ export interface RelayConfig extends ConsoleConfig {
18
17
  */
19
18
  readonly name?: SimpleName;
20
19
  /**
21
- * Optional callback to authenticate an {@link OwnerId} with the relay.
20
+ * Optional callback to check if an {@link OwnerId} is allowed to access the
21
+ * relay. If this callback is not provided, all owners are allowed.
22
22
  *
23
- * If this callback is not provided, all owners are allowed.
23
+ * If provided, the callback receives the OwnerId and should return a
24
+ * {@link MaybeAsync} boolean: `true` to allow access, or `false` to deny.
24
25
  *
25
- * If provided, the callback receives the OwnerId and should return a promise
26
- * that resolves to `true` to allow access, or `false` to deny.
26
+ * The callback can be synchronous (for SQLite or in-memory checks) or
27
+ * asynchronous (for calling remote APIs).
27
28
  *
28
29
  * The callback returns a boolean rather than an error type because error
29
- * handling and logging are the responsibility of the callback implementation,
30
- * not the relay. This prevents leaking authentication implementation details
31
- * into the generic relay interface.
30
+ * handling and logging are the responsibility of the callback
31
+ * implementation.
32
32
  *
33
- * OwnerId is used for authentication rather than short-lived tokens because
34
- * this only controls relay access, not write permissions. Since all data is
35
- * encrypted on the relay, OwnerId exposure is safe.
33
+ * OwnerId is used rather than short-lived tokens because this only controls
34
+ * relay access, not write permissions. Since all data is encrypted on the
35
+ * relay, OwnerId exposure is safe.
36
36
  *
37
37
  * Owners specify which relays to connect to via {@link OwnerTransport}. In
38
38
  * WebSocket-based implementations, this check occurs before accepting the
@@ -42,20 +42,36 @@ export interface RelayConfig extends ConsoleConfig {
42
42
  * ### Example
43
43
  *
44
44
  * ```ts
45
- * const relay = await createNodeJsRelay(deps)({
46
- * authenticateOwner: async (ownerId) => {
47
- * const isRegistered = await db.checkOwner(ownerId);
48
- * if (!isRegistered) {
49
- * logger.warn("Unauthorized access attempt", { ownerId });
50
- * }
51
- * return isRegistered;
52
- * },
45
+ * // Client
46
+ * const transport = createOwnerWebSocketTransport({
47
+ * url: "wss://relay.evolu.dev",
48
+ * ownerId: owner.id,
49
+ * });
50
+ *
51
+ * const evolu = createEvolu(deps)(Schema, {
52
+ * transports: [transport],
53
53
  * });
54
+ *
55
+ *
56
+ * // Relay
57
+ * isOwnerAllowed: (ownerId) =>
58
+ * Promise.resolve(ownerId === "6jy_2F4RT5qqeLgJ14_dnQ"),
54
59
  * ```
55
60
  */
56
- readonly authenticateOwner?: (ownerId: OwnerId) => Promise<boolean>;
61
+ readonly isOwnerAllowed?: (ownerId: OwnerId) => MaybeAsync<boolean>;
62
+ }
63
+ /**
64
+ * A completely interchangeable server for syncing and backing up encrypted data
65
+ * between Evolu clients.
66
+ *
67
+ * Unlike traditional servers, relays are blind by design—they transmit
68
+ * encrypted data without understanding its shape or meaning. This enables true
69
+ * decentralization and infinite horizontal scalability with minimal
70
+ * infrastructure.
71
+ */
72
+ export interface Relay extends Disposable {
57
73
  }
58
- export declare const createRelaySqliteStorage: (deps: SqliteStorageDeps & TimingSafeEqualDep) => (options: CreateBaseSqliteStorageOptions) => Storage;
74
+ export declare const createRelaySqliteStorage: (deps: SqliteStorageDeps & TimingSafeEqualDep) => (config: CreateBaseSqliteStorageConfig) => Storage;
59
75
  export declare const createRelayStorageTables: (deps: SqliteDep) => Result<void, SqliteError>;
60
76
  export interface RelayLogger {
61
77
  readonly started: (enableLogging: boolean, port: number) => void;
@@ -63,7 +79,6 @@ export interface RelayLogger {
63
79
  readonly upgradeSocketError: (error: Error) => void;
64
80
  readonly invalidOrMissingOwnerIdInUrl: (url: string | undefined) => void;
65
81
  readonly unauthorizedOwner: (ownerId: OwnerId) => void;
66
- readonly authenticateOwnerError: (error: unknown) => void;
67
82
  readonly connectionEstablished: (totalConnectionCount: number) => void;
68
83
  readonly connectionWebSocketError: (error: Error) => void;
69
84
  readonly relayOptionSubscribe: (ownerId: OwnerId, getSubscriberCount: LazyValue<number>) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,22 @@
1
- import { isNonEmptyReadonlyArray } from "../Array.js";
1
+ import { filterArray, isNonEmptyReadonlyArray, mapArray } from "../Array.js";
2
+ import { createInstances } from "../Instances.js";
2
3
  import { err, ok } from "../Result.js";
3
4
  import { sql } from "../Sqlite.js";
5
+ import { createMutex, isAsync } from "../Task.js";
6
+ import { PositiveInt } from "../Type.js";
7
+ import { ownerIdBytesToOwnerId, } from "./Owner.js";
4
8
  import { createBaseSqliteStorage, } from "./Storage.js";
5
9
  import { timestampToTimestampBytes } from "./Timestamp.js";
6
- export const createRelaySqliteStorage = (deps) => (options) => {
7
- const sqliteStorageBase = createBaseSqliteStorage(deps)(options);
10
+ export const createRelaySqliteStorage = (deps) => (config) => {
11
+ const sqliteStorageBase = createBaseSqliteStorage(deps)(config);
12
+ /**
13
+ * Mutex instances are cached per OwnerId to prevent concurrent writes for
14
+ * the same owner. Instances are never evicted, causing a memory leak
15
+ * proportional to unique owner count. However, per-instance overhead should
16
+ * be small. Monitor production memory usage to determine if
17
+ * eviction/cleanup is needed.
18
+ */
19
+ const ownerMutexes = createInstances();
8
20
  return {
9
21
  ...sqliteStorageBase,
10
22
  /**
@@ -23,22 +35,22 @@ export const createRelaySqliteStorage = (deps) => (options) => {
23
35
  where ownerId = ${ownerId};
24
36
  `);
25
37
  if (!selectWriteKey.ok) {
26
- options.onStorageError(selectWriteKey.error);
38
+ config.onStorageError(selectWriteKey.error);
27
39
  return false;
28
40
  }
29
41
  const { rows } = selectWriteKey.value;
30
- if (!isNonEmptyReadonlyArray(rows)) {
31
- const insertWriteKey = deps.sqlite.exec(sql `
32
- insert into evolu_writeKey (ownerId, writeKey)
33
- values (${ownerId}, ${writeKey});
34
- `);
35
- if (!insertWriteKey.ok) {
36
- options.onStorageError(insertWriteKey.error);
37
- return false;
38
- }
39
- return true;
42
+ if (isNonEmptyReadonlyArray(rows)) {
43
+ return deps.timingSafeEqual(rows[0].writeKey, writeKey);
40
44
  }
41
- return deps.timingSafeEqual(rows[0].writeKey, writeKey);
45
+ const insertWriteKey = deps.sqlite.exec(sql `
46
+ insert into evolu_writeKey (ownerId, writeKey)
47
+ values (${ownerId}, ${writeKey});
48
+ `);
49
+ if (!insertWriteKey.ok) {
50
+ config.onStorageError(insertWriteKey.error);
51
+ return false;
52
+ }
53
+ return true;
42
54
  },
43
55
  setWriteKey: (ownerId, writeKey) => {
44
56
  const upsertWriteKey = deps.sqlite.exec(sql `
@@ -48,39 +60,82 @@ export const createRelaySqliteStorage = (deps) => (options) => {
48
60
  set writeKey = excluded.writeKey;
49
61
  `);
50
62
  if (!upsertWriteKey.ok) {
51
- options.onStorageError(upsertWriteKey.error);
63
+ config.onStorageError(upsertWriteKey.error);
52
64
  return false;
53
65
  }
54
66
  return true;
55
67
  },
56
- // https://eslint.org/docs/latest/rules/require-await#when-not-to-use-it
57
- // eslint-disable-next-line @typescript-eslint/require-await
58
- writeMessages: async (ownerId, messages) => {
59
- const result = deps.sqlite.transaction(() => {
60
- for (const message of messages) {
61
- const insertTimestampResult = sqliteStorageBase.insertTimestamp(ownerId, timestampToTimestampBytes(message.timestamp));
62
- if (!insertTimestampResult.ok)
63
- return insertTimestampResult;
64
- const insertMessage = deps.sqlite.exec(sql `
65
- insert into evolu_message ("ownerId", "timestamp", "change")
66
- values
67
- (
68
- ${ownerId},
69
- ${timestampToTimestampBytes(message.timestamp)},
70
- ${message.change}
71
- )
72
- on conflict do nothing;
68
+ writeMessages: async (ownerIdBytes, messages) => {
69
+ const ownerId = ownerIdBytesToOwnerId(ownerIdBytes);
70
+ const messagesWithTimestampBytes = mapArray(messages, (m) => ({
71
+ timestamp: timestampToTimestampBytes(m.timestamp),
72
+ change: m.change,
73
+ }));
74
+ const result = await ownerMutexes
75
+ .ensure(ownerId, createMutex)
76
+ .withLock(async () => {
77
+ const existingTimestampsResult = sqliteStorageBase.getExistingTimestamps(ownerIdBytes, mapArray(messagesWithTimestampBytes, (m) => m.timestamp));
78
+ if (!existingTimestampsResult.ok)
79
+ return existingTimestampsResult;
80
+ const existingTimestampsSet = new Set(existingTimestampsResult.value.map((t) => t.toString()));
81
+ const newMessages = filterArray(messagesWithTimestampBytes, (m) => !existingTimestampsSet.has(m.timestamp.toString()));
82
+ // Nothing to write
83
+ if (!isNonEmptyReadonlyArray(newMessages)) {
84
+ return ok();
85
+ }
86
+ const storedBytesResult = deps.sqlite.exec(sql `
87
+ select storedBytes
88
+ from evolu_usage
89
+ where ownerId = ${ownerIdBytes};
73
90
  `);
74
- if (!insertMessage.ok)
75
- return insertMessage;
91
+ if (!storedBytesResult.ok)
92
+ return storedBytesResult;
93
+ const storedBytes = storedBytesResult.value.rows[0]?.storedBytes ?? 0;
94
+ const incomingBytes = newMessages.reduce((sum, m) => sum + m.change.length, 0);
95
+ const newStoredBytes = PositiveInt.orThrow(storedBytes + incomingBytes);
96
+ if (config.isOwnerWithinQuota) {
97
+ const withinQuotaResult = config.isOwnerWithinQuota(ownerId, newStoredBytes);
98
+ const isWithinQuota = isAsync(withinQuotaResult)
99
+ ? await withinQuotaResult
100
+ : withinQuotaResult;
101
+ if (!isWithinQuota) {
102
+ return err({ type: "StorageQuotaError", ownerId });
103
+ }
104
+ }
105
+ return deps.sqlite.transaction(() => {
106
+ for (const { timestamp, change } of newMessages) {
107
+ const insertTimestampResult = sqliteStorageBase.insertTimestamp(ownerIdBytes, timestamp);
108
+ if (!insertTimestampResult.ok)
109
+ return insertTimestampResult;
110
+ const insertMessage = deps.sqlite.exec(sql `
111
+ insert into evolu_message ("ownerId", "timestamp", "change")
112
+ values (${ownerIdBytes}, ${timestamp}, ${change})
113
+ on conflict do nothing;
114
+ `);
115
+ if (!insertMessage.ok)
116
+ return insertMessage;
117
+ }
118
+ const updateUsage = deps.sqlite.exec(sql `
119
+ insert into evolu_usage ("ownerId", "storedBytes")
120
+ values (${ownerIdBytes}, ${newStoredBytes})
121
+ on conflict (ownerId) do update
122
+ set storedBytes = ${newStoredBytes};
123
+ `);
124
+ if (!updateUsage.ok)
125
+ return updateUsage;
126
+ return ok();
127
+ });
128
+ })();
129
+ if (!result.ok && result.error.type !== "AbortError") {
130
+ switch (result.error.type) {
131
+ case "SqliteError":
132
+ config.onStorageError(result.error);
133
+ return err({ type: "StorageWriteError", ownerId });
134
+ case "StorageQuotaError":
135
+ return err({ type: "StorageQuotaError", ownerId });
76
136
  }
77
- return ok();
78
- });
79
- if (!result.ok) {
80
- options.onStorageError(result.error);
81
- return false;
82
137
  }
83
- return true;
138
+ return ok();
84
139
  },
85
140
  readDbChange: (ownerId, timestamp) => {
86
141
  const result = deps.sqlite.exec(sql `
@@ -89,13 +144,13 @@ export const createRelaySqliteStorage = (deps) => (options) => {
89
144
  where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
90
145
  `);
91
146
  if (!result.ok) {
92
- options.onStorageError(result.error);
147
+ config.onStorageError(result.error);
93
148
  return null;
94
149
  }
95
150
  return result.value.rows[0]?.change;
96
151
  },
97
152
  deleteOwner: (ownerId) => {
98
- const result = deps.sqlite.transaction(() => {
153
+ const transactionResult = deps.sqlite.transaction(() => {
99
154
  const deleteWriteKey = deps.sqlite.exec(sql `
100
155
  delete from evolu_writeKey where ownerId = ${ownerId};
101
156
  `);
@@ -106,14 +161,19 @@ export const createRelaySqliteStorage = (deps) => (options) => {
106
161
  `);
107
162
  if (!deleteMessages.ok)
108
163
  return deleteMessages;
164
+ const deleteUsage = deps.sqlite.exec(sql `
165
+ delete from evolu_usage where ownerId = ${ownerId};
166
+ `);
167
+ if (!deleteUsage.ok)
168
+ return deleteUsage;
109
169
  const deleteBaseOwner = sqliteStorageBase.deleteOwner(ownerId);
110
170
  if (!deleteBaseOwner)
111
171
  return err(null);
112
172
  return ok();
113
173
  });
114
- if (!result.ok) {
115
- if (result.error)
116
- options.onStorageError(result.error);
174
+ if (!transactionResult.ok) {
175
+ if (transactionResult.error)
176
+ config.onStorageError(transactionResult.error);
117
177
  return false;
118
178
  }
119
179
  return true;
@@ -164,9 +224,6 @@ export const createRelayLogger = (deps) => ({
164
224
  unauthorizedOwner: (ownerId) => {
165
225
  deps.console.warn("[relay]", "unauthorized owner", { ownerId });
166
226
  },
167
- authenticateOwnerError: (error) => {
168
- deps.console.error("[relay]", "authenticateOwner error", error);
169
- },
170
227
  connectionEstablished: (totalConnectionCount) => {
171
228
  deps.console.log("[relay]", "connection", { totalConnectionCount });
172
229
  },
@@ -3,16 +3,50 @@ import { Brand } from "../Brand.js";
3
3
  import { RandomDep } from "../Random.js";
4
4
  import { Result } from "../Result.js";
5
5
  import { SqliteDep, SqliteError } from "../Sqlite.js";
6
- import { NonNegativeInt } from "../Type.js";
7
- import { OwnerIdBytes, OwnerWriteKey } from "./Owner.js";
6
+ import { MaybeAsync } from "../Task.js";
7
+ import { NonNegativeInt, PositiveInt } from "../Type.js";
8
+ import { BaseOwnerError, OwnerId, OwnerIdBytes, OwnerWriteKey } from "./Owner.js";
8
9
  import { Timestamp, TimestampBytes } from "./Timestamp.js";
10
+ export interface StorageConfig {
11
+ /**
12
+ * Optional callback to check if an {@link OwnerId} is within their quota for
13
+ * the requested write. If this callback is not provided, all writes are
14
+ * allowed regardless of size.
15
+ *
16
+ * If provided, the callback receives the OwnerId and the number of bytes
17
+ * required for the write, and should return a {@link MaybeAsync} boolean:
18
+ * `true` to allow the write, or `false` to deny it due to quota limits.
19
+ *
20
+ * The callback can be synchronous (for SQLite or in-memory checks) or
21
+ * asynchronous (for calling remote APIs).
22
+ *
23
+ * The callback returns a boolean rather than an error type because error
24
+ * handling and logging are the responsibility of the callback
25
+ * implementation.
26
+ *
27
+ * ### Example
28
+ *
29
+ * ```ts
30
+ * // Client
31
+ * // evolu.subscribeError
32
+ *
33
+ * // Relay
34
+ * isOwnerWithinQuota: (ownerId, requiredBytes) => {
35
+ * console.log(ownerId, requiredBytes);
36
+ * // Check error via evolu.subscribeError
37
+ * return true;
38
+ * };
39
+ * ```
40
+ */
41
+ readonly isOwnerWithinQuota?: (ownerId: OwnerId, requiredBytes: PositiveInt) => MaybeAsync<boolean>;
42
+ }
9
43
  /**
10
44
  * Evolu Storage
11
45
  *
12
- * The protocol using Storage is agnostic to storage implementation details—any
13
- * storage can be plugged in, as long as it implements this interface.
14
- * Implementations must handle their own errors; return values only indicate
15
- * overall success or failure.
46
+ * Evolu protocol using Storage is agnostic to storage implementation
47
+ * details—any storage can be plugged in, as long as it implements this
48
+ * interface. Implementations must handle their own errors; return values only
49
+ * indicate overall success or failure.
16
50
  *
17
51
  * The Storage API is synchronous because SQLite's synchronous API is the
18
52
  * fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call
@@ -47,12 +81,12 @@ export interface Storage {
47
81
  /**
48
82
  * Write encrypted {@link CrdtMessage}s to storage.
49
83
  *
50
- * Must use a mutex (per ownerId on Relay) to ensure sequential processing and
51
- * proper protocol logic handling during sync operations.
84
+ * Must use a mutex per ownerId to ensure sequential processing and proper
85
+ * protocol logic handling during sync operations.
52
86
  *
53
- * Returns `true` on success, `false` on failure.
87
+ * TODO: Use MaybeAsync
54
88
  */
55
- readonly writeMessages: (ownerId: OwnerIdBytes, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => Promise<boolean>;
89
+ readonly writeMessages: (ownerIdBytes: OwnerIdBytes, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => MaybeAsync<Result<void, StorageWriteError | StorageQuotaError>>;
56
90
  /** Read encrypted {@link DbChange}s from storage. */
57
91
  readonly readDbChange: (ownerId: OwnerIdBytes, timestamp: TimestampBytes) => EncryptedDbChange | null;
58
92
  /**
@@ -65,6 +99,14 @@ export interface Storage {
65
99
  export interface StorageDep {
66
100
  readonly storage: Storage;
67
101
  }
102
+ /** Error indicating a serious write failure. */
103
+ export interface StorageWriteError extends BaseOwnerError {
104
+ readonly type: "StorageWriteError";
105
+ }
106
+ /** Error when storage or billing quota is exceeded. */
107
+ export interface StorageQuotaError extends BaseOwnerError {
108
+ readonly type: "StorageQuotaError";
109
+ }
68
110
  /**
69
111
  * A cryptographic hash used for efficiently comparing collections of
70
112
  * {@link TimestampBytes}s.
@@ -174,15 +216,20 @@ export interface BaseSqliteStorage extends Pick<Storage, "getSize" | "fingerprin
174
216
  * messages may be received and processed multiple times.
175
217
  */
176
218
  readonly insertTimestamp: (ownerId: OwnerIdBytes, timestamp: TimestampBytes) => Result<void, SqliteError>;
219
+ /**
220
+ * Efficiently checks which timestamps already exist in the database using a
221
+ * single CTE query instead of N individual queries.
222
+ */
223
+ readonly getExistingTimestamps: (ownerIdBytes: OwnerIdBytes, timestampsBytes: NonEmptyReadonlyArray<TimestampBytes>) => Result<ReadonlyArray<TimestampBytes>, SqliteError>;
177
224
  }
178
225
  export interface BaseSqliteStorageDep {
179
226
  readonly storage: BaseSqliteStorage;
180
227
  }
181
228
  export type SqliteStorageDeps = RandomDep & SqliteDep;
182
- export interface CreateBaseSqliteStorageOptions {
229
+ export interface CreateBaseSqliteStorageConfig extends StorageConfig {
183
230
  onStorageError: (error: SqliteError) => void;
184
231
  }
185
- export declare const createBaseSqliteStorage: (deps: SqliteStorageDeps) => (options: CreateBaseSqliteStorageOptions) => BaseSqliteStorage;
232
+ export declare const createBaseSqliteStorage: (deps: SqliteStorageDeps) => (config: CreateBaseSqliteStorageConfig) => BaseSqliteStorage;
186
233
  export declare const createBaseSqliteStorageTables: (deps: SqliteDep) => Result<void, SqliteError>;
187
234
  export declare const timestampBytesToFingerprint: (timestamp: TimestampBytes) => Fingerprint;
188
235
  export declare const getTimestampByIndex: (deps: SqliteDep) => (ownerId: OwnerIdBytes, index: NonNegativeInt) => Result<TimestampBytes, SqliteError>;
@@ -1 +1 @@
1
- {"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"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"}