Skip to content

Commit b04aeaa

Browse files
committed
Refactor quota and owner checks to be required callbacks
Changed `isOwnerAllowed` and `isOwnerWithinQuota` to required callbacks in relay and storage configs, removing their optional status.
1 parent a51c23d commit b04aeaa

7 files changed

Lines changed: 42 additions & 59 deletions

File tree

apps/relay/src/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,12 @@ const relay = await createNodeJsRelay({
1212
port: 4000,
1313
enableLogging: false,
1414

15-
// Click to `isOwnerAllowed` to read the docs.
16-
// isOwnerAllowed: (_ownerId) => true,
15+
isOwnerAllowed: (_ownerId) => true,
1716

18-
// Click to `isOwnerWithinQuota` to read the docs.
19-
// isOwnerWithinQuota: (ownerId, requiredBytes) => {
20-
// console.log(ownerId, requiredBytes);
21-
// // Check error via evolu.subscribeError
22-
// return true;
23-
// },
17+
isOwnerWithinQuota: (_ownerId, requiredBytes) => {
18+
const maxBytes = 1024 * 1024; // 1MB
19+
return requiredBytes <= maxBytes;
20+
},
2421
});
2522

2623
if (relay.ok) {

packages/common/src/Evolu/Relay.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ export interface RelayConfig extends ConsoleConfig, StorageConfig {
3535
readonly name?: SimpleName;
3636

3737
/**
38-
* Optional callback to check if an {@link OwnerId} is allowed to access the
39-
* relay. If this callback is not provided, all owners are allowed.
38+
* Callback to check if an {@link OwnerId} is allowed to access the relay.
4039
*
41-
* If provided, the callback receives the OwnerId and should return a
42-
* {@link MaybeAsync} boolean: `true` to allow access, or `false` to deny.
40+
* The callback receives the OwnerId and returns a {@link MaybeAsync} boolean:
41+
* `true` to allow access, or `false` to deny.
4342
*
4443
* The callback can be synchronous (for SQLite or in-memory checks) or
4544
* asynchronous (for calling remote APIs).
@@ -76,7 +75,7 @@ export interface RelayConfig extends ConsoleConfig, StorageConfig {
7675
* Promise.resolve(ownerId === "6jy_2F4RT5qqeLgJ14_dnQ"),
7776
* ```
7877
*/
79-
readonly isOwnerAllowed?: (ownerId: OwnerId) => MaybeAsync<boolean>;
78+
readonly isOwnerAllowed: (ownerId: OwnerId) => MaybeAsync<boolean>;
8079
}
8180

8281
/**
@@ -211,17 +210,15 @@ export const createRelaySqliteStorage =
211210
storedBytes + incomingBytes,
212211
);
213212

214-
if (config.isOwnerWithinQuota) {
215-
const withinQuotaResult = config.isOwnerWithinQuota(
216-
ownerId,
217-
newStoredBytes,
218-
);
219-
const isWithinQuota = isAsync(withinQuotaResult)
220-
? await withinQuotaResult
221-
: withinQuotaResult;
222-
if (!isWithinQuota) {
223-
return err({ type: "StorageQuotaError", ownerId });
224-
}
213+
const withinQuotaResult = config.isOwnerWithinQuota(
214+
ownerId,
215+
newStoredBytes,
216+
);
217+
const isWithinQuota = isAsync(withinQuotaResult)
218+
? await withinQuotaResult
219+
: withinQuotaResult;
220+
if (!isWithinQuota) {
221+
return err({ type: "StorageQuotaError", ownerId });
225222
}
226223

227224
return deps.sqlite.transaction(() => {

packages/common/src/Evolu/Storage.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ import { orderTimestampBytes, Timestamp, TimestampBytes } from "./Timestamp.js";
2929

3030
export interface StorageConfig {
3131
/**
32-
* Optional callback to check if an {@link OwnerId} is within their quota for
33-
* the requested write. If this callback is not provided, all writes are
34-
* allowed regardless of size.
32+
* Callback called before an attempt to write, to check if an {@link OwnerId}
33+
* has sufficient quota for the write.
3534
*
36-
* If provided, the callback receives the OwnerId and the number of bytes
37-
* required for the write, and should return a {@link MaybeAsync} boolean:
38-
* `true` to allow the write, or `false` to deny it due to quota limits.
35+
* The callback receives the {@link OwnerId} and the number of bytes required
36+
* for the write, and returns a {@link MaybeAsync} boolean: `true` to allow the
37+
* write, or `false` to deny it due to quota limits.
3938
*
4039
* The callback can be synchronous (for SQLite or in-memory checks) or
4140
* asynchronous (for calling remote APIs).
@@ -58,7 +57,7 @@ export interface StorageConfig {
5857
* };
5958
* ```
6059
*/
61-
readonly isOwnerWithinQuota?: (
60+
readonly isOwnerWithinQuota: (
6261
ownerId: OwnerId,
6362
requiredBytes: PositiveInt,
6463
) => MaybeAsync<boolean>;

packages/common/src/Evolu/Sync.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "../Crypto.js";
1010
import { eqArrayNumber } from "../Eq.js";
1111
import { createTransferableError, TransferableError } from "../Error.js";
12-
import { constFalse } from "../Function.js";
12+
import { constFalse, constTrue } from "../Function.js";
1313
import { objectToEntries } from "../Object.js";
1414
import { RandomDep } from "../Random.js";
1515
import { createResources } from "../Resources.js";
@@ -451,6 +451,7 @@ const createClientStorage =
451451
}): Result<ClientStorage, SqliteError> => {
452452
const sqliteStorageBase = createBaseSqliteStorage(deps)({
453453
onStorageError: config.onError,
454+
isOwnerWithinQuota: constTrue, // Clients don't have quota limits
454455
});
455456

456457
// TODO: Mutex per OwnerId

packages/common/test/Evolu/Storage.test.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { sha256 } from "@noble/hashes/sha2.js";
22
import { assert, expect, test } from "vitest";
3+
import { ownerIdToOwnerIdBytes } from "../../src/Evolu/Owner.js";
34
import {
45
BaseSqliteStorageDep,
56
createBaseSqliteStorage,
@@ -14,20 +15,15 @@ import {
1415
createTimestamp,
1516
Millis,
1617
orderTimestampBytes,
18+
TimestampBytes,
1719
timestampToTimestampBytes,
1820
} from "../../src/Evolu/Timestamp.js";
19-
import {
20-
computeBalancedBuckets,
21-
createRandom,
22-
getOrThrow,
23-
NonNegativeInt,
24-
ok,
25-
ownerIdToOwnerIdBytes,
26-
PositiveInt,
27-
sql,
28-
SqliteDep,
29-
TimestampBytes,
30-
} from "../../src/index.js";
21+
import { constTrue } from "../../src/Function.js";
22+
import { computeBalancedBuckets } from "../../src/Number.js";
23+
import { createRandom } from "../../src/Random.js";
24+
import { getOrThrow, ok } from "../../src/Result.js";
25+
import { sql, SqliteDep } from "../../src/Sqlite.js";
26+
import { NonNegativeInt, PositiveInt } from "../../src/Type.js";
3127
import { testCreateSqlite, testOwner2, testOwnerIdBytes } from "../_deps.js";
3228
import {
3329
testAnotherTimestampsAsc,
@@ -49,6 +45,7 @@ const createDeps = async (): Promise<SqliteDep & BaseSqliteStorageDep> => {
4945
onStorageError: (error) => {
5046
throw new Error(error.type);
5147
},
48+
isOwnerWithinQuota: constTrue, // Allow all writes in tests
5249
});
5350
return { sqlite, storage };
5451
};

packages/common/test/_deps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
StorageConfig,
2323
StorageDep,
2424
} from "../src/Evolu/Storage.js";
25-
import { constFalse, constVoid } from "../src/Function.js";
25+
import { constFalse, constTrue, constVoid } from "../src/Function.js";
2626
import {
2727
createRandom,
2828
createRandomLibWithSeed,
@@ -319,6 +319,7 @@ export const testCreateRelayStorageAndSqliteDeps = async (
319319
onStorageError: (error) => {
320320
throw new Error(error.type);
321321
},
322+
isOwnerWithinQuota: constTrue, // Allow all writes in tests by default
322323
...config,
323324
});
324325

packages/nodejs/src/Evolu/Relay.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const createNodeJsRelayWithDeps =
9696

9797
const storage = createRelaySqliteStorage(depsWithSqlite)({
9898
onStorageError: log.storageError,
99-
...(isOwnerWithinQuota && { isOwnerWithinQuota }),
99+
isOwnerWithinQuota,
100100
});
101101

102102
const server = createServer();
@@ -110,18 +110,6 @@ const createNodeJsRelayWithDeps =
110110
server.on("upgrade", (request, socket, head) => {
111111
socket.on("error", log.upgradeSocketError);
112112

113-
const completeUpgrade = () => {
114-
socket.removeListener("error", log.upgradeSocketError);
115-
wss.handleUpgrade(request, socket, head, (ws) => {
116-
wss.emit("connection", ws, request);
117-
});
118-
};
119-
120-
if (!isOwnerAllowed) {
121-
completeUpgrade();
122-
return;
123-
}
124-
125113
const ownerId = parseOwnerIdFromOwnerWebSocketTransportUrl(
126114
request.url ?? "",
127115
);
@@ -142,7 +130,10 @@ const createNodeJsRelayWithDeps =
142130
socket.destroy();
143131
return;
144132
}
145-
completeUpgrade();
133+
socket.removeListener("error", log.upgradeSocketError);
134+
wss.handleUpgrade(request, socket, head, (ws) => {
135+
wss.emit("connection", ws, request);
136+
});
146137
})();
147138
});
148139

0 commit comments

Comments
 (0)