@evolu/common 7.4.0 → 8.0.0-next.0
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.
- package/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +1064 -630
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -823
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +209 -282
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -529
- package/src/Instances.ts +0 -90
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -960
package/src/local-first/Relay.ts
CHANGED
|
@@ -1,52 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay server for data synchronization.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import {
|
|
2
8
|
filterArray,
|
|
3
9
|
firstInArray,
|
|
4
|
-
|
|
10
|
+
isNonEmptyArray,
|
|
5
11
|
mapArray,
|
|
6
12
|
} from "../Array.js";
|
|
7
|
-
import {
|
|
8
|
-
import { TimingSafeEqualDep } from "../Crypto.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { assert } from "../Assert.js";
|
|
14
|
+
import type { TimingSafeEqualDep } from "../Crypto.js";
|
|
15
|
+
import { err, getOk, ok } from "../Result.js";
|
|
16
|
+
import type { SqliteDep } from "../Sqlite.js";
|
|
17
|
+
import { sql } from "../Sqlite.js";
|
|
18
|
+
import { createMutexByKey } from "../Task.js";
|
|
19
|
+
import { Name, PositiveInt } from "../Type.js";
|
|
20
|
+
import { isPromiseLike, type Awaitable } from "../Types.js";
|
|
15
21
|
import {
|
|
16
22
|
OwnerId,
|
|
17
23
|
ownerIdBytesToOwnerId,
|
|
18
|
-
OwnerTransport,
|
|
24
|
+
// OwnerTransport,
|
|
19
25
|
OwnerWriteKey,
|
|
20
26
|
} from "./Owner.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
createBaseSqliteStorage,
|
|
24
|
-
CreateBaseSqliteStorageConfig,
|
|
27
|
+
import type {
|
|
25
28
|
EncryptedDbChange,
|
|
26
|
-
getOwnerUsage,
|
|
27
|
-
getTimestampInsertStrategy,
|
|
28
29
|
SqliteStorageDeps,
|
|
29
30
|
Storage,
|
|
30
31
|
StorageConfig,
|
|
31
32
|
StorageQuotaError,
|
|
33
|
+
} from "./Storage.js";
|
|
34
|
+
import {
|
|
35
|
+
createBaseSqliteStorage,
|
|
36
|
+
getOwnerUsage,
|
|
37
|
+
getTimestampInsertStrategy,
|
|
32
38
|
updateOwnerUsage,
|
|
33
39
|
} from "./Storage.js";
|
|
34
40
|
import { timestampToTimestampBytes } from "./Timestamp.js";
|
|
35
41
|
|
|
36
|
-
export interface RelayConfig extends
|
|
42
|
+
export interface RelayConfig extends StorageConfig {
|
|
37
43
|
/**
|
|
38
44
|
* The relay name.
|
|
39
45
|
*
|
|
40
46
|
* Implementations can use this for identification purposes (e.g., database
|
|
41
47
|
* file name, logging).
|
|
42
48
|
*/
|
|
43
|
-
readonly name?:
|
|
49
|
+
readonly name?: Name;
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
52
|
* Optional callback to check if an {@link OwnerId} is allowed to access the
|
|
47
53
|
* relay. If this callback is not provided, all owners are allowed.
|
|
48
54
|
*
|
|
49
|
-
* The callback receives the {@link OwnerId} and returns a {@link
|
|
55
|
+
* The callback receives the {@link OwnerId} and returns a {@link Awaitable}
|
|
50
56
|
* boolean: `true` to allow access, or `false` to deny.
|
|
51
57
|
*
|
|
52
58
|
* The callback can be synchronous (for SQLite or in-memory checks) or
|
|
@@ -60,7 +66,7 @@ export interface RelayConfig extends ConsoleConfig, StorageConfig {
|
|
|
60
66
|
* relay access, not write permissions. Since all data is encrypted on the
|
|
61
67
|
* relay, OwnerId exposure is safe.
|
|
62
68
|
*
|
|
63
|
-
* Owners specify which relays to connect to via
|
|
69
|
+
* Owners specify which relays to connect to via `OwnerTransport`. In
|
|
64
70
|
* WebSocket-based implementations, this check occurs before accepting the
|
|
65
71
|
* connection, with the OwnerId typically extracted from the URL Path (e.g.,
|
|
66
72
|
* `ws://localhost:4000/<ownerId>`). The relay requires the URL to be in the
|
|
@@ -85,7 +91,7 @@ export interface RelayConfig extends ConsoleConfig, StorageConfig {
|
|
|
85
91
|
* Promise.resolve(ownerId === "6jy_2F4RT5qqeLgJ14_dnQ"),
|
|
86
92
|
* ```
|
|
87
93
|
*/
|
|
88
|
-
readonly isOwnerAllowed?: (ownerId: OwnerId) =>
|
|
94
|
+
readonly isOwnerAllowed?: (ownerId: OwnerId) => Awaitable<boolean>;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
/**
|
|
@@ -97,21 +103,15 @@ export interface RelayConfig extends ConsoleConfig, StorageConfig {
|
|
|
97
103
|
* decentralization and infinite horizontal scalability with minimal
|
|
98
104
|
* infrastructure.
|
|
99
105
|
*/
|
|
100
|
-
export interface Relay extends
|
|
106
|
+
export interface Relay extends AsyncDisposable {}
|
|
101
107
|
|
|
102
108
|
export const createRelaySqliteStorage =
|
|
103
109
|
(deps: SqliteStorageDeps & TimingSafeEqualDep) =>
|
|
104
|
-
(config:
|
|
105
|
-
const sqliteStorageBase = createBaseSqliteStorage(deps)
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
* the same owner. Instances are never evicted, causing a memory leak
|
|
110
|
-
* proportional to unique owner count. However, per-instance overhead should
|
|
111
|
-
* be small. Monitor production memory usage to determine if
|
|
112
|
-
* eviction/cleanup is needed.
|
|
113
|
-
*/
|
|
114
|
-
const ownerMutexes = createInstances<OwnerId, Mutex>();
|
|
110
|
+
(config: StorageConfig): Storage => {
|
|
111
|
+
const sqliteStorageBase = createBaseSqliteStorage(deps);
|
|
112
|
+
|
|
113
|
+
/** Mutex keyed by OwnerId to prevent concurrent writes. */
|
|
114
|
+
const mutexByOwnerId = createMutexByKey<OwnerId>();
|
|
115
115
|
|
|
116
116
|
return {
|
|
117
117
|
...sqliteStorageBase,
|
|
@@ -133,63 +133,46 @@ export const createRelaySqliteStorage =
|
|
|
133
133
|
where ownerId = ${ownerId};
|
|
134
134
|
`,
|
|
135
135
|
);
|
|
136
|
-
|
|
137
|
-
config.onStorageError(selectWriteKey.error);
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
136
|
+
const { rows } = selectWriteKey;
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (isNonEmptyReadonlyArray(rows)) {
|
|
138
|
+
if (isNonEmptyArray(rows)) {
|
|
144
139
|
return deps.timingSafeEqual(rows[0].writeKey, writeKey);
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
|
|
142
|
+
deps.sqlite.exec(sql`
|
|
148
143
|
insert into evolu_writeKey (ownerId, writeKey)
|
|
149
144
|
values (${ownerId}, ${writeKey});
|
|
150
145
|
`);
|
|
151
|
-
if (!insertWriteKey.ok) {
|
|
152
|
-
config.onStorageError(insertWriteKey.error);
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
146
|
|
|
156
147
|
return true;
|
|
157
148
|
},
|
|
158
149
|
|
|
159
150
|
setWriteKey: (ownerId, writeKey) => {
|
|
160
|
-
|
|
151
|
+
deps.sqlite.exec(sql`
|
|
161
152
|
insert into evolu_writeKey (ownerId, writeKey)
|
|
162
153
|
values (${ownerId}, ${writeKey})
|
|
163
154
|
on conflict (ownerId) do update
|
|
164
155
|
set writeKey = excluded.writeKey;
|
|
165
156
|
`);
|
|
166
|
-
if (!upsertWriteKey.ok) {
|
|
167
|
-
config.onStorageError(upsertWriteKey.error);
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return true;
|
|
172
157
|
},
|
|
173
158
|
|
|
174
|
-
writeMessages:
|
|
159
|
+
writeMessages: (ownerIdBytes, messages) => async (run) => {
|
|
175
160
|
const ownerId = ownerIdBytesToOwnerId(ownerIdBytes);
|
|
176
161
|
const messagesWithTimestampBytes = mapArray(messages, (m) => ({
|
|
177
162
|
timestamp: timestampToTimestampBytes(m.timestamp),
|
|
178
163
|
change: m.change,
|
|
179
164
|
}));
|
|
180
165
|
|
|
181
|
-
const result = await
|
|
182
|
-
.
|
|
183
|
-
.withLock<void, SqliteError | StorageQuotaError>(async () => {
|
|
166
|
+
const result = await run(
|
|
167
|
+
mutexByOwnerId.withLock(ownerId, async () => {
|
|
184
168
|
const existingTimestampsResult =
|
|
185
169
|
sqliteStorageBase.getExistingTimestamps(
|
|
186
170
|
ownerIdBytes,
|
|
187
171
|
mapArray(messagesWithTimestampBytes, (m) => m.timestamp),
|
|
188
172
|
);
|
|
189
|
-
if (!existingTimestampsResult.ok) return existingTimestampsResult;
|
|
190
173
|
|
|
191
174
|
const existingTimestampsSet = new Set(
|
|
192
|
-
existingTimestampsResult.
|
|
175
|
+
existingTimestampsResult.map((t) => t.toString()),
|
|
193
176
|
);
|
|
194
177
|
const newMessages = filterArray(
|
|
195
178
|
messagesWithTimestampBytes,
|
|
@@ -197,33 +180,40 @@ export const createRelaySqliteStorage =
|
|
|
197
180
|
);
|
|
198
181
|
|
|
199
182
|
// Nothing to write
|
|
200
|
-
if (!
|
|
183
|
+
if (!isNonEmptyArray(newMessages)) {
|
|
201
184
|
return ok();
|
|
202
185
|
}
|
|
203
186
|
|
|
204
|
-
const usage =
|
|
205
|
-
|
|
206
|
-
|
|
187
|
+
const usage = getOk(
|
|
188
|
+
getOwnerUsage(deps)(
|
|
189
|
+
ownerIdBytes,
|
|
190
|
+
firstInArray(newMessages).timestamp,
|
|
191
|
+
),
|
|
207
192
|
);
|
|
208
|
-
if (!usage.ok) return usage;
|
|
209
|
-
|
|
210
|
-
const { storedBytes } = usage.value;
|
|
211
193
|
|
|
212
194
|
const incomingBytes = newMessages.reduce(
|
|
213
195
|
(sum, m) => sum + m.change.length,
|
|
214
196
|
0,
|
|
215
197
|
);
|
|
216
198
|
const newStoredBytes = PositiveInt.orThrow(
|
|
217
|
-
(storedBytes ?? 0) + incomingBytes,
|
|
199
|
+
(usage.storedBytes ?? 0) + incomingBytes,
|
|
218
200
|
);
|
|
219
201
|
|
|
220
|
-
const
|
|
221
|
-
|
|
202
|
+
const quotaResult = config.isOwnerWithinQuota(
|
|
203
|
+
ownerId,
|
|
204
|
+
newStoredBytes,
|
|
205
|
+
);
|
|
206
|
+
const isWithinQuota = isPromiseLike(quotaResult)
|
|
207
|
+
? await quotaResult
|
|
208
|
+
: quotaResult;
|
|
222
209
|
if (!isWithinQuota) {
|
|
223
|
-
return err({
|
|
210
|
+
return err<StorageQuotaError>({
|
|
211
|
+
type: "StorageQuotaError",
|
|
212
|
+
ownerId,
|
|
213
|
+
});
|
|
224
214
|
}
|
|
225
215
|
|
|
226
|
-
let { firstTimestamp, lastTimestamp } = usage
|
|
216
|
+
let { firstTimestamp, lastTimestamp } = usage;
|
|
227
217
|
|
|
228
218
|
return deps.sqlite.transaction(() => {
|
|
229
219
|
for (const { timestamp, change } of newMessages) {
|
|
@@ -235,42 +225,35 @@ export const createRelaySqliteStorage =
|
|
|
235
225
|
lastTimestamp,
|
|
236
226
|
);
|
|
237
227
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
values (${ownerIdBytes}, ${timestamp}, ${change})
|
|
251
|
-
on conflict do nothing;
|
|
252
|
-
`);
|
|
253
|
-
if (!result.ok) return result;
|
|
254
|
-
}
|
|
228
|
+
sqliteStorageBase.insertTimestamp(
|
|
229
|
+
ownerIdBytes,
|
|
230
|
+
timestamp,
|
|
231
|
+
strategy,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
deps.sqlite.exec(sql`
|
|
235
|
+
insert into evolu_message
|
|
236
|
+
("ownerId", "timestamp", "change")
|
|
237
|
+
values (${ownerIdBytes}, ${timestamp}, ${change})
|
|
238
|
+
on conflict do nothing;
|
|
239
|
+
`);
|
|
255
240
|
}
|
|
256
241
|
|
|
257
|
-
|
|
242
|
+
updateOwnerUsage(deps)(
|
|
258
243
|
ownerIdBytes,
|
|
259
244
|
newStoredBytes,
|
|
260
245
|
firstTimestamp,
|
|
261
246
|
lastTimestamp,
|
|
262
247
|
);
|
|
248
|
+
|
|
249
|
+
return ok();
|
|
263
250
|
});
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return err({ type: "StorageWriteError", ownerId });
|
|
271
|
-
case "StorageQuotaError":
|
|
272
|
-
return err({ type: "StorageQuotaError", ownerId });
|
|
273
|
-
}
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
if (!result.ok) {
|
|
255
|
+
if (result.error.type === "AbortError") return ok();
|
|
256
|
+
return result;
|
|
274
257
|
}
|
|
275
258
|
|
|
276
259
|
return ok();
|
|
@@ -284,51 +267,35 @@ export const createRelaySqliteStorage =
|
|
|
284
267
|
from evolu_message
|
|
285
268
|
where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
|
|
286
269
|
`);
|
|
287
|
-
if (!result.ok) {
|
|
288
|
-
config.onStorageError(result.error);
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
270
|
|
|
292
|
-
|
|
271
|
+
const row = result.rows[0];
|
|
272
|
+
assert(row, "Every timestamp must have a change");
|
|
273
|
+
return row.change;
|
|
293
274
|
},
|
|
294
275
|
|
|
295
276
|
deleteOwner: (ownerId) => {
|
|
296
|
-
|
|
297
|
-
|
|
277
|
+
deps.sqlite.transaction(() => {
|
|
278
|
+
deps.sqlite.exec(sql`
|
|
298
279
|
delete from evolu_writeKey where ownerId = ${ownerId};
|
|
299
280
|
`);
|
|
300
|
-
if (!deleteWriteKey.ok) return deleteWriteKey;
|
|
301
281
|
|
|
302
|
-
|
|
282
|
+
deps.sqlite.exec(sql`
|
|
303
283
|
delete from evolu_message where ownerId = ${ownerId};
|
|
304
284
|
`);
|
|
305
|
-
if (!deleteMessages.ok) return deleteMessages;
|
|
306
285
|
|
|
307
|
-
|
|
286
|
+
deps.sqlite.exec(sql`
|
|
308
287
|
delete from evolu_usage where ownerId = ${ownerId};
|
|
309
288
|
`);
|
|
310
|
-
if (!deleteUsage.ok) return deleteUsage;
|
|
311
289
|
|
|
312
|
-
|
|
313
|
-
if (!deleteBaseOwner) return err(null);
|
|
290
|
+
sqliteStorageBase.deleteOwner(ownerId);
|
|
314
291
|
|
|
315
292
|
return ok();
|
|
316
293
|
});
|
|
317
|
-
|
|
318
|
-
if (!transactionResult.ok) {
|
|
319
|
-
if (transactionResult.error)
|
|
320
|
-
config.onStorageError(transactionResult.error);
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return true;
|
|
325
294
|
},
|
|
326
295
|
};
|
|
327
296
|
};
|
|
328
297
|
|
|
329
|
-
export const createRelayStorageTables = (
|
|
330
|
-
deps: SqliteDep,
|
|
331
|
-
): Result<void, SqliteError> => {
|
|
298
|
+
export const createRelayStorageTables = (deps: SqliteDep): void => {
|
|
332
299
|
for (const query of [
|
|
333
300
|
sql`
|
|
334
301
|
create table evolu_writeKey (
|
|
@@ -349,134 +316,6 @@ export const createRelayStorageTables = (
|
|
|
349
316
|
strict;
|
|
350
317
|
`,
|
|
351
318
|
]) {
|
|
352
|
-
|
|
353
|
-
if (!result.ok) return result;
|
|
319
|
+
deps.sqlite.exec(query);
|
|
354
320
|
}
|
|
355
|
-
|
|
356
|
-
return ok();
|
|
357
321
|
};
|
|
358
|
-
|
|
359
|
-
export interface RelayLogger {
|
|
360
|
-
readonly started: (enableLogging: boolean, port: number) => void;
|
|
361
|
-
readonly storageError: (error: unknown) => void;
|
|
362
|
-
readonly upgradeSocketError: (error: Error) => void;
|
|
363
|
-
readonly invalidOrMissingOwnerIdInUrl: (url: string | undefined) => void;
|
|
364
|
-
readonly unauthorizedOwner: (ownerId: OwnerId) => void;
|
|
365
|
-
readonly connectionEstablished: (totalConnectionCount: number) => void;
|
|
366
|
-
readonly connectionWebSocketError: (error: Error) => void;
|
|
367
|
-
readonly relayOptionSubscribe: (
|
|
368
|
-
ownerId: OwnerId,
|
|
369
|
-
getSubscriberCount: LazyValue<number>,
|
|
370
|
-
) => void;
|
|
371
|
-
readonly relayOptionUnsubscribe: (
|
|
372
|
-
ownerId: OwnerId,
|
|
373
|
-
getSubscriberCount: LazyValue<number>,
|
|
374
|
-
) => void;
|
|
375
|
-
readonly relayOptionBroadcast: (
|
|
376
|
-
ownerId: OwnerId,
|
|
377
|
-
broadcastCount: number,
|
|
378
|
-
subscriberCount: number,
|
|
379
|
-
) => void;
|
|
380
|
-
readonly messageLength: (messageLength: number) => void;
|
|
381
|
-
readonly applyProtocolMessageAsRelayError: (
|
|
382
|
-
error: ProtocolInvalidDataError,
|
|
383
|
-
) => void;
|
|
384
|
-
readonly responseLength: (responseLength: number) => void;
|
|
385
|
-
readonly applyProtocolMessageAsRelayUnknownError: (error: unknown) => void;
|
|
386
|
-
readonly connectionClosed: (totalConnectionCount: number) => void;
|
|
387
|
-
readonly shuttingDown: () => void;
|
|
388
|
-
readonly webSocketServerDisposed: () => void;
|
|
389
|
-
readonly httpServerDisposed: () => void;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export const createRelayLogger = (deps: ConsoleDep): RelayLogger => ({
|
|
393
|
-
started: (enableLogging, port) => {
|
|
394
|
-
deps.console.enabled = true;
|
|
395
|
-
deps.console.log(`Evolu Relay started on port ${port}`);
|
|
396
|
-
deps.console.enabled = enableLogging;
|
|
397
|
-
},
|
|
398
|
-
|
|
399
|
-
storageError: (error) => {
|
|
400
|
-
deps.console.error("[relay]", "storage", error);
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
upgradeSocketError: (error) => {
|
|
404
|
-
deps.console.warn("[relay]", "socket error", { error });
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
invalidOrMissingOwnerIdInUrl: (url) => {
|
|
408
|
-
deps.console.warn("[relay]", "invalid or missing ownerId in URL", { url });
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
unauthorizedOwner: (ownerId) => {
|
|
412
|
-
deps.console.warn("[relay]", "unauthorized owner", { ownerId });
|
|
413
|
-
},
|
|
414
|
-
|
|
415
|
-
connectionEstablished: (totalConnectionCount) => {
|
|
416
|
-
deps.console.log("[relay]", "connection", { totalConnectionCount });
|
|
417
|
-
},
|
|
418
|
-
|
|
419
|
-
connectionWebSocketError: (error) => {
|
|
420
|
-
deps.console.error("[relay]", "error", { error });
|
|
421
|
-
},
|
|
422
|
-
|
|
423
|
-
relayOptionSubscribe: (ownerId, getSubscriberCount) => {
|
|
424
|
-
if (deps.console.enabled)
|
|
425
|
-
deps.console.log("[relay]", "subscribe", {
|
|
426
|
-
ownerId,
|
|
427
|
-
subscriberCount: getSubscriberCount(),
|
|
428
|
-
});
|
|
429
|
-
},
|
|
430
|
-
|
|
431
|
-
relayOptionUnsubscribe: (ownerId, getSubscriberCount) => {
|
|
432
|
-
if (deps.console.enabled)
|
|
433
|
-
deps.console.log("[relay]", "unsubscribe", {
|
|
434
|
-
ownerId,
|
|
435
|
-
subscriberCount: getSubscriberCount(),
|
|
436
|
-
});
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
relayOptionBroadcast: (ownerId, broadcastCount, totalSubscribers) => {
|
|
440
|
-
deps.console.log("[relay]", "broadcast", {
|
|
441
|
-
ownerId,
|
|
442
|
-
broadcastCount,
|
|
443
|
-
totalSubscribers,
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
|
-
|
|
447
|
-
messageLength: (messageLength) => {
|
|
448
|
-
deps.console.log("[relay]", "on message", { messageLength });
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
applyProtocolMessageAsRelayError: (error) => {
|
|
452
|
-
deps.console.error("[relay]", "applyProtocolMessageAsRelay", error);
|
|
453
|
-
},
|
|
454
|
-
|
|
455
|
-
responseLength: (responseLength) => {
|
|
456
|
-
deps.console.log("[relay]", "responseLength", { responseLength });
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
applyProtocolMessageAsRelayUnknownError: (error) => {
|
|
460
|
-
deps.console.error(
|
|
461
|
-
"[relay]",
|
|
462
|
-
"applyProtocolMessageAsRelayUnknownError",
|
|
463
|
-
error,
|
|
464
|
-
);
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
connectionClosed: (totalConnectionCount) => {
|
|
468
|
-
deps.console.log("[relay]", "close", { totalConnectionCount });
|
|
469
|
-
},
|
|
470
|
-
|
|
471
|
-
shuttingDown: () => {
|
|
472
|
-
deps.console.log("Shutting down Evolu Relay...");
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
webSocketServerDisposed: () => {
|
|
476
|
-
deps.console.log("Evolu Relay WebSocketServer disposed");
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
httpServerDisposed: () => {
|
|
480
|
-
deps.console.log("Evolu Relay HTTP server disposed");
|
|
481
|
-
},
|
|
482
|
-
});
|