@verdant-web/server 3.3.11 → 4.0.1
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/bin/server.mjs +110 -107
- package/dist/esm/TokenVerifier.d.ts +1 -0
- package/dist/esm/TokenVerifier.js +3 -0
- package/dist/esm/TokenVerifier.js.map +1 -1
- package/dist/esm/adapters/node/errorHandler.d.ts +2 -0
- package/dist/esm/adapters/node/errorHandler.js +12 -0
- package/dist/esm/adapters/node/errorHandler.js.map +1 -0
- package/dist/esm/adapters/node/getRequestToken.d.ts +1 -0
- package/dist/esm/adapters/node/getRequestToken.js +25 -0
- package/dist/esm/adapters/node/getRequestToken.js.map +1 -0
- package/dist/esm/adapters/node/httpRouter.d.ts +47 -0
- package/dist/esm/adapters/node/httpRouter.js +88 -0
- package/dist/esm/adapters/node/httpRouter.js.map +1 -0
- package/dist/esm/adapters/node/index.d.ts +5 -0
- package/dist/esm/adapters/node/index.js +6 -0
- package/dist/esm/adapters/node/index.js.map +1 -0
- package/dist/esm/adapters/node/tokenMiddleware.d.ts +7 -0
- package/dist/esm/adapters/node/tokenMiddleware.js +22 -0
- package/dist/esm/adapters/node/tokenMiddleware.js.map +1 -0
- package/dist/esm/adapters/node/websocketHandler.d.ts +4 -0
- package/dist/esm/adapters/node/websocketHandler.js +54 -0
- package/dist/esm/adapters/node/websocketHandler.js.map +1 -0
- package/dist/esm/connections/ClientConnection.d.ts +25 -0
- package/dist/esm/{ClientConnection.js → connections/ClientConnection.js} +47 -38
- package/dist/esm/connections/ClientConnection.js.map +1 -0
- package/dist/esm/connections/MessageSender.d.ts +5 -0
- package/dist/esm/connections/MessageSender.js.map +1 -0
- package/dist/esm/connections/Presence.d.ts +23 -0
- package/dist/esm/connections/Presence.js +65 -0
- package/dist/esm/connections/Presence.js.map +1 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +15 -0
- package/dist/esm/core.js.map +1 -0
- package/dist/esm/defaults.d.ts +2 -0
- package/dist/esm/defaults.js +3 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/files/FileStorage.d.ts +12 -3
- package/dist/esm/files/FileStorage.js +20 -0
- package/dist/esm/files/FileStorage.js.map +1 -1
- package/dist/esm/index.d.ts +7 -7
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internals.d.ts +14 -0
- package/dist/esm/internals.js +12 -0
- package/dist/esm/internals.js.map +1 -0
- package/dist/esm/libraries/Library.d.ts +78 -0
- package/dist/esm/{ServerLibrary.js → libraries/Library.js} +165 -151
- package/dist/esm/libraries/Library.js.map +1 -0
- package/dist/esm/{ServerLibrary.test.js → libraries/Library.test.js} +42 -33
- package/dist/esm/libraries/Library.test.js.map +1 -0
- package/dist/esm/libraries/generic.d.ts +8 -0
- package/dist/esm/libraries/generic.js +2 -0
- package/dist/esm/libraries/generic.js.map +1 -0
- package/dist/esm/libraries/singleNode.d.ts +53 -0
- package/dist/esm/libraries/singleNode.js +88 -0
- package/dist/esm/libraries/singleNode.js.map +1 -0
- package/dist/esm/logger.d.ts +1 -0
- package/dist/esm/logger.js +2 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/storage/Storage.d.ts +39 -38
- package/dist/esm/storage/StorageManager.d.ts +11 -0
- package/dist/esm/storage/StorageManager.js +32 -0
- package/dist/esm/storage/StorageManager.js.map +1 -0
- package/dist/esm/storage/index.d.ts +1 -2
- package/dist/esm/storage/index.js +1 -2
- package/dist/esm/storage/index.js.map +1 -1
- package/dist/esm/storage/sqlShard/Databases.d.ts +5 -3
- package/dist/esm/storage/sqlShard/Databases.js +11 -7
- package/dist/esm/storage/sqlShard/Databases.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlBaselines.d.ts +16 -17
- package/dist/esm/storage/sqlShard/SqlBaselines.js +55 -59
- package/dist/esm/storage/sqlShard/SqlBaselines.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlFileMetadata.d.ts +13 -13
- package/dist/esm/storage/sqlShard/SqlFileMetadata.js +44 -57
- package/dist/esm/storage/sqlShard/SqlFileMetadata.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlOperations.d.ts +12 -12
- package/dist/esm/storage/sqlShard/SqlOperations.js +38 -73
- package/dist/esm/storage/sqlShard/SqlOperations.js.map +1 -1
- package/dist/esm/storage/sqlShard/SqlReplicas.d.ts +18 -17
- package/dist/esm/storage/sqlShard/SqlReplicas.js +63 -100
- package/dist/esm/storage/sqlShard/SqlReplicas.js.map +1 -1
- package/dist/esm/storage/sqlShard/database.d.ts +25 -5
- package/dist/esm/storage/sqlShard/database.js +47 -15
- package/dist/esm/storage/sqlShard/database.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations/v1.d.ts +2 -3
- package/dist/esm/storage/sqlShard/migrations/v1.js +42 -47
- package/dist/esm/storage/sqlShard/migrations/v1.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations/v2.d.ts +2 -3
- package/dist/esm/storage/sqlShard/migrations/v2.js +5 -15
- package/dist/esm/storage/sqlShard/migrations/v2.js.map +1 -1
- package/dist/esm/storage/sqlShard/migrations.d.ts +5 -7
- package/dist/esm/storage/sqlShard/migrations.js +48 -1
- package/dist/esm/storage/sqlShard/migrations.js.map +1 -1
- package/dist/esm/storage/sqlShard/sqlShardStorage.d.ts +25 -4
- package/dist/esm/storage/sqlShard/sqlShardStorage.js +43 -40
- package/dist/esm/storage/sqlShard/sqlShardStorage.js.map +1 -1
- package/dist/esm/storage/sqlShard/tables.d.ts +9 -6
- package/dist/esm/types.d.ts +2 -2
- package/package.json +24 -13
- package/src/TokenVerifier.ts +4 -0
- package/src/adapters/node/errorHandler.ts +18 -0
- package/src/adapters/node/getRequestToken.ts +34 -0
- package/src/adapters/node/httpRouter.ts +139 -0
- package/src/adapters/node/index.ts +5 -0
- package/src/adapters/node/tokenMiddleware.ts +32 -0
- package/src/adapters/node/websocketHandler.ts +79 -0
- package/src/{ClientConnection.ts → connections/ClientConnection.ts} +64 -57
- package/src/connections/MessageSender.ts +6 -0
- package/src/connections/Presence.ts +95 -0
- package/src/core.ts +36 -0
- package/src/defaults.ts +2 -0
- package/src/files/FileStorage.ts +36 -3
- package/src/index.ts +7 -7
- package/src/internals.ts +14 -0
- package/src/{ServerLibrary.test.ts → libraries/Library.test.ts} +51 -49
- package/src/{ServerLibrary.ts → libraries/Library.ts} +205 -206
- package/src/libraries/generic.ts +9 -0
- package/src/libraries/singleNode.ts +145 -0
- package/src/logger.ts +4 -0
- package/src/storage/Storage.ts +37 -66
- package/src/storage/StorageManager.ts +39 -0
- package/src/storage/index.ts +1 -2
- package/src/storage/sqlShard/Databases.ts +26 -13
- package/src/storage/sqlShard/SqlBaselines.ts +58 -67
- package/src/storage/sqlShard/SqlFileMetadata.ts +51 -72
- package/src/storage/sqlShard/SqlOperations.ts +57 -81
- package/src/storage/sqlShard/SqlReplicas.ts +80 -121
- package/src/storage/sqlShard/database.ts +82 -18
- package/src/storage/sqlShard/migrations/v1.ts +42 -52
- package/src/storage/sqlShard/migrations/v2.ts +5 -19
- package/src/storage/sqlShard/migrations.ts +66 -1
- package/src/storage/sqlShard/sqlShardStorage.ts +56 -55
- package/src/storage/sqlShard/tables.ts +6 -6
- package/src/types.ts +2 -4
- package/dist/esm/ClientConnection.d.ts +0 -19
- package/dist/esm/ClientConnection.js.map +0 -1
- package/dist/esm/MessageSender.d.ts +0 -5
- package/dist/esm/MessageSender.js.map +0 -1
- package/dist/esm/Presence.d.ts +0 -18
- package/dist/esm/Presence.js +0 -65
- package/dist/esm/Presence.js.map +0 -1
- package/dist/esm/ReplicaKeepaliveTimers.d.ts +0 -17
- package/dist/esm/ReplicaKeepaliveTimers.js +0 -27
- package/dist/esm/ReplicaKeepaliveTimers.js.map +0 -1
- package/dist/esm/Server.d.ts +0 -166
- package/dist/esm/Server.js +0 -572
- package/dist/esm/Server.js.map +0 -1
- package/dist/esm/ServerLibrary.d.ts +0 -64
- package/dist/esm/ServerLibrary.js.map +0 -1
- package/dist/esm/ServerLibrary.test.js.map +0 -1
- package/dist/esm/storage/sql/SqlBaselines.d.ts +0 -22
- package/dist/esm/storage/sql/SqlBaselines.js +0 -107
- package/dist/esm/storage/sql/SqlBaselines.js.map +0 -1
- package/dist/esm/storage/sql/SqlFileMetadata.d.ts +0 -18
- package/dist/esm/storage/sql/SqlFileMetadata.js +0 -69
- package/dist/esm/storage/sql/SqlFileMetadata.js.map +0 -1
- package/dist/esm/storage/sql/SqlOperations.d.ts +0 -19
- package/dist/esm/storage/sql/SqlOperations.js +0 -122
- package/dist/esm/storage/sql/SqlOperations.js.map +0 -1
- package/dist/esm/storage/sql/SqlReplicas.d.ts +0 -32
- package/dist/esm/storage/sql/SqlReplicas.js +0 -163
- package/dist/esm/storage/sql/SqlReplicas.js.map +0 -1
- package/dist/esm/storage/sql/database.d.ts +0 -9
- package/dist/esm/storage/sql/database.js +0 -24
- package/dist/esm/storage/sql/database.js.map +0 -1
- package/dist/esm/storage/sql/migrations/v1.d.ts +0 -3
- package/dist/esm/storage/sql/migrations/v1.js +0 -59
- package/dist/esm/storage/sql/migrations/v1.js.map +0 -1
- package/dist/esm/storage/sql/migrations/v2.d.ts +0 -3
- package/dist/esm/storage/sql/migrations/v2.js +0 -16
- package/dist/esm/storage/sql/migrations/v2.js.map +0 -1
- package/dist/esm/storage/sql/migrations.d.ts +0 -7
- package/dist/esm/storage/sql/migrations.js +0 -4
- package/dist/esm/storage/sql/migrations.js.map +0 -1
- package/dist/esm/storage/sql/sqlStorage.d.ts +0 -6
- package/dist/esm/storage/sql/sqlStorage.js +0 -35
- package/dist/esm/storage/sql/sqlStorage.js.map +0 -1
- package/dist/esm/storage/sql/tables.d.ts +0 -44
- package/dist/esm/storage/sql/tables.js +0 -2
- package/dist/esm/storage/sql/tables.js.map +0 -1
- package/dist/esm/storage/sqlShard/transfer.d.ts +0 -7
- package/dist/esm/storage/sqlShard/transfer.js +0 -90
- package/dist/esm/storage/sqlShard/transfer.js.map +0 -1
- package/dist/esm/storage/sqlShard/transfer.test.d.ts +0 -1
- package/dist/esm/storage/sqlShard/transfer.test.js +0 -109
- package/dist/esm/storage/sqlShard/transfer.test.js.map +0 -1
- package/src/MessageSender.ts +0 -10
- package/src/Presence.ts +0 -86
- package/src/ReplicaKeepaliveTimers.ts +0 -34
- package/src/Server.ts +0 -859
- package/src/storage/sql/SqlBaselines.ts +0 -147
- package/src/storage/sql/SqlFileMetadata.ts +0 -94
- package/src/storage/sql/SqlOperations.ts +0 -160
- package/src/storage/sql/SqlReplicas.ts +0 -234
- package/src/storage/sql/database.ts +0 -32
- package/src/storage/sql/migrations/v1.ts +0 -64
- package/src/storage/sql/migrations/v2.ts +0 -20
- package/src/storage/sql/migrations.ts +0 -4
- package/src/storage/sql/sqlStorage.ts +0 -57
- package/src/storage/sql/tables.ts +0 -51
- package/src/storage/sqlShard/transfer.test.ts +0 -149
- package/src/storage/sqlShard/transfer.ts +0 -123
- /package/dist/esm/{MessageSender.js → connections/MessageSender.js} +0 -0
- /package/dist/esm/{ServerLibrary.test.d.ts → libraries/Library.test.d.ts} +0 -0
|
@@ -1,98 +1,77 @@
|
|
|
1
|
-
import { Kysely } from 'kysely';
|
|
2
|
-
import { FileMetadataStorage } from '../Storage.js';
|
|
3
|
-
import { Database, FileMetadataRow } from './tables.js';
|
|
4
|
-
import { FileMetadata } from '../../types.js';
|
|
5
1
|
import { FileInfo } from '../../files/FileStorage.js';
|
|
6
|
-
import {
|
|
2
|
+
import { FileMetadata } from '../../types.js';
|
|
3
|
+
import { FileMetadataStorage } from '../Storage.js';
|
|
4
|
+
import { SqliteExecutor } from './database.js';
|
|
5
|
+
import { FileMetadataRow } from './tables.js';
|
|
7
6
|
|
|
8
7
|
export class SqlFileMetadata implements FileMetadataStorage {
|
|
9
8
|
constructor(
|
|
10
|
-
private
|
|
9
|
+
private db: SqliteExecutor,
|
|
10
|
+
private libraryId: string,
|
|
11
11
|
private deleteExpirationDays: number,
|
|
12
|
-
private dialect: 'postgres' | 'sqlite',
|
|
13
12
|
) {}
|
|
14
13
|
|
|
15
|
-
private attachLibrary = (
|
|
14
|
+
private attachLibrary = (row: FileMetadataRow) => {
|
|
16
15
|
if (!row) return row;
|
|
17
|
-
(row as any).libraryId = libraryId;
|
|
16
|
+
(row as any).libraryId = this.libraryId;
|
|
18
17
|
return row as FileMetadata;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
get = async (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.selectFrom('FileMetadata')
|
|
29
|
-
.where('fileId', '=', fileId)
|
|
30
|
-
.selectAll()
|
|
31
|
-
.executeTakeFirst()) ?? null;
|
|
32
|
-
if (row) return this.attachLibrary(libraryId, row);
|
|
20
|
+
get = async (fileId: string): Promise<FileMetadata | null> => {
|
|
21
|
+
const db = this.db;
|
|
22
|
+
const row = db.first<FileMetadataRow>(
|
|
23
|
+
`SELECT * FROM FileMetadata WHERE fileId = ?`,
|
|
24
|
+
[fileId],
|
|
25
|
+
);
|
|
26
|
+
if (row) return this.attachLibrary(row);
|
|
33
27
|
return row;
|
|
34
28
|
};
|
|
35
29
|
|
|
36
|
-
getAll = async (
|
|
37
|
-
const db =
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
getAll = async (): Promise<FileMetadata[]> => {
|
|
31
|
+
const db = this.db;
|
|
32
|
+
return db
|
|
33
|
+
.query<FileMetadataRow>(`SELECT * FROM FileMetadata`)
|
|
34
|
+
.map(this.attachLibrary);
|
|
41
35
|
};
|
|
42
36
|
|
|
43
|
-
deleteAll = async (
|
|
44
|
-
const db =
|
|
45
|
-
|
|
37
|
+
deleteAll = async (): Promise<void> => {
|
|
38
|
+
const db = this.db;
|
|
39
|
+
db.exec('DELETE FROM FileMetadata');
|
|
46
40
|
};
|
|
47
41
|
|
|
48
|
-
put = async (
|
|
49
|
-
const db =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
name: fileInfo.fileName,
|
|
60
|
-
type: fileInfo.type,
|
|
61
|
-
pendingDeleteAt: null,
|
|
62
|
-
}),
|
|
63
|
-
)
|
|
64
|
-
.execute();
|
|
42
|
+
put = async (fileInfo: FileInfo): Promise<void> => {
|
|
43
|
+
const db = this.db;
|
|
44
|
+
db.exec(
|
|
45
|
+
`INSERT INTO FileMetadata (fileId, name, type) VALUES (?, ?, ?)
|
|
46
|
+
ON CONFLICT(fileId) DO UPDATE SET
|
|
47
|
+
name = excluded.name,
|
|
48
|
+
type = excluded.type,
|
|
49
|
+
pendingDeleteAt = NULL
|
|
50
|
+
`,
|
|
51
|
+
[fileInfo.id, fileInfo.fileName, fileInfo.type],
|
|
52
|
+
);
|
|
65
53
|
};
|
|
66
54
|
|
|
67
|
-
markPendingDelete = async (
|
|
68
|
-
|
|
69
|
-
fileId
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.updateTable('FileMetadata')
|
|
74
|
-
.set({ pendingDeleteAt: Date.now() })
|
|
75
|
-
.where('fileId', '=', fileId)
|
|
76
|
-
.execute();
|
|
55
|
+
markPendingDelete = async (fileId: string): Promise<void> => {
|
|
56
|
+
const db = this.db;
|
|
57
|
+
db.exec(`UPDATE FileMetadata SET pendingDeleteAt = ? WHERE fileId = ?`, [
|
|
58
|
+
Date.now(),
|
|
59
|
+
fileId,
|
|
60
|
+
]);
|
|
77
61
|
};
|
|
78
62
|
|
|
79
|
-
delete = async (
|
|
80
|
-
const db =
|
|
81
|
-
|
|
63
|
+
delete = async (fileId: string): Promise<void> => {
|
|
64
|
+
const db = this.db;
|
|
65
|
+
db.exec(`DELETE FROM FileMetadata WHERE fileId = ?`, [fileId]);
|
|
82
66
|
};
|
|
83
67
|
|
|
84
|
-
getPendingDelete = async (
|
|
85
|
-
const db =
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Date.now() - 1000 * 60 * 60 * 24 * this.deleteExpirationDays,
|
|
93
|
-
)
|
|
94
|
-
.selectAll()
|
|
95
|
-
.execute()
|
|
96
|
-
).map(this.attachLibrary.bind(this, libraryId));
|
|
68
|
+
getPendingDelete = async (): Promise<FileMetadata[]> => {
|
|
69
|
+
const db = this.db;
|
|
70
|
+
return db
|
|
71
|
+
.query<FileMetadataRow>(
|
|
72
|
+
`SELECT * FROM FileMetadata WHERE pendingDeleteAt < ?`,
|
|
73
|
+
[Date.now() - this.deleteExpirationDays * 24 * 60 * 60 * 1000],
|
|
74
|
+
)
|
|
75
|
+
.map(this.attachLibrary);
|
|
97
76
|
};
|
|
98
77
|
}
|
|
@@ -1,73 +1,62 @@
|
|
|
1
1
|
import { Operation } from '@verdant-web/common';
|
|
2
2
|
import { StoredOperation } from '../../types.js';
|
|
3
3
|
import { OperationStorage } from '../Storage.js';
|
|
4
|
+
import { SqliteExecutor } from './database.js';
|
|
4
5
|
import { OperationHistoryRow } from './tables.js';
|
|
5
|
-
import { Databases } from './Databases.js';
|
|
6
6
|
|
|
7
7
|
export class SqlOperations implements OperationStorage {
|
|
8
|
-
constructor(
|
|
8
|
+
constructor(
|
|
9
|
+
private db: SqliteExecutor,
|
|
10
|
+
private libraryId: string,
|
|
11
|
+
) {}
|
|
9
12
|
|
|
10
13
|
private hydrate = (row: OperationHistoryRow) => {
|
|
11
14
|
// avoiding extra allocation from .map by type-asserting here
|
|
12
15
|
row.data = JSON.parse(row.data);
|
|
13
16
|
};
|
|
14
17
|
|
|
15
|
-
getAll = async (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.selectFrom('OperationHistory')
|
|
22
|
-
.where('oid', '=', oid)
|
|
23
|
-
.orderBy('timestamp', 'asc')
|
|
24
|
-
.selectAll()
|
|
25
|
-
.execute();
|
|
18
|
+
getAll = async (oid: string): Promise<StoredOperation[]> => {
|
|
19
|
+
const db = this.db;
|
|
20
|
+
const raw = db.query<OperationHistoryRow>(
|
|
21
|
+
`SELECT * FROM OperationHistory WHERE oid = ? ORDER BY timestamp ASC`,
|
|
22
|
+
[oid],
|
|
23
|
+
);
|
|
26
24
|
|
|
27
25
|
raw.forEach(this.hydrate);
|
|
28
26
|
return raw as unknown as StoredOperation[];
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
getBeforeServerOrder = async (
|
|
32
|
-
libraryId: string,
|
|
33
30
|
beforeServerOrder: number,
|
|
34
31
|
): Promise<StoredOperation[]> => {
|
|
35
|
-
const db =
|
|
36
|
-
const raw =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.selectAll()
|
|
41
|
-
.execute();
|
|
32
|
+
const db = this.db;
|
|
33
|
+
const raw = db.query<OperationHistoryRow>(
|
|
34
|
+
`SELECT * FROM OperationHistory WHERE serverOrder < ? ORDER BY timestamp ASC`,
|
|
35
|
+
[beforeServerOrder],
|
|
36
|
+
);
|
|
42
37
|
|
|
43
38
|
raw.forEach(this.hydrate);
|
|
44
39
|
return raw as unknown as StoredOperation[];
|
|
45
40
|
};
|
|
46
41
|
|
|
47
42
|
getAfterServerOrder = async (
|
|
48
|
-
libraryId: string,
|
|
49
43
|
afterServerOrder: number,
|
|
50
44
|
): Promise<StoredOperation[]> => {
|
|
51
|
-
const db =
|
|
52
|
-
const raw =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.selectAll()
|
|
57
|
-
.execute();
|
|
45
|
+
const db = this.db;
|
|
46
|
+
const raw = db.query<OperationHistoryRow>(
|
|
47
|
+
`SELECT * FROM OperationHistory WHERE serverOrder > ? ORDER BY timestamp ASC`,
|
|
48
|
+
[afterServerOrder],
|
|
49
|
+
);
|
|
58
50
|
|
|
59
51
|
raw.forEach(this.hydrate);
|
|
60
52
|
return raw as unknown as StoredOperation[];
|
|
61
53
|
};
|
|
62
54
|
|
|
63
|
-
getLatestServerOrder = async (
|
|
64
|
-
const db =
|
|
65
|
-
const result =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.select('serverOrder')
|
|
69
|
-
.limit(1)
|
|
70
|
-
.executeTakeFirst();
|
|
55
|
+
getLatestServerOrder = async (): Promise<number> => {
|
|
56
|
+
const db = this.db;
|
|
57
|
+
const result = db.first<Pick<OperationHistoryRow, 'serverOrder'>>(
|
|
58
|
+
`SELECT serverOrder FROM OperationHistory ORDER BY serverOrder DESC LIMIT 1`,
|
|
59
|
+
);
|
|
71
60
|
if (result) {
|
|
72
61
|
return result.serverOrder;
|
|
73
62
|
}
|
|
@@ -75,51 +64,42 @@ export class SqlOperations implements OperationStorage {
|
|
|
75
64
|
return 0;
|
|
76
65
|
};
|
|
77
66
|
|
|
78
|
-
getCount = async (
|
|
79
|
-
const db =
|
|
67
|
+
getCount = async (): Promise<number> => {
|
|
68
|
+
const db = this.db;
|
|
80
69
|
return (
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
.selectFrom('OperationHistory')
|
|
84
|
-
.select(({ fn }) => fn.countAll<number>().as('count'))
|
|
85
|
-
.executeTakeFirst()
|
|
70
|
+
db.first<{ count: number }>(
|
|
71
|
+
`SELECT COUNT(*) as count FROM OperationHistory`,
|
|
86
72
|
)?.count ?? 0
|
|
87
73
|
);
|
|
88
74
|
};
|
|
89
75
|
|
|
90
76
|
insertAll = async (
|
|
91
|
-
libraryId: string,
|
|
92
77
|
replicaId: string,
|
|
93
78
|
operations: Operation[],
|
|
94
79
|
): Promise<number> => {
|
|
95
|
-
const db =
|
|
80
|
+
const db = this.db;
|
|
96
81
|
// inserts all operations and updates server order
|
|
97
82
|
// FIXME: this whole thing is kinda sus
|
|
98
|
-
return
|
|
99
|
-
let orderResult =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.orderBy('serverOrder', 'desc')
|
|
103
|
-
.limit(1)
|
|
104
|
-
.executeTakeFirst();
|
|
83
|
+
return db.transaction((tx): number => {
|
|
84
|
+
let orderResult = tx.first<Pick<OperationHistoryRow, 'serverOrder'>>(
|
|
85
|
+
`SELECT serverOrder FROM OperationHistory ORDER BY serverOrder DESC LIMIT 1`,
|
|
86
|
+
);
|
|
105
87
|
let currentServerOrder = orderResult?.serverOrder ?? 0;
|
|
106
88
|
for (const item of operations) {
|
|
107
89
|
// utilizing returned serverOrder accommodates for conflicts
|
|
108
|
-
const result =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
90
|
+
const result = tx.first(
|
|
91
|
+
`INSERT INTO OperationHistory (oid, data, timestamp, replicaId, serverOrder, authz) VALUES (?, ?, ?, ?, ?, ?)
|
|
92
|
+
ON CONFLICT (replicaId, oid, timestamp) DO NOTHING
|
|
93
|
+
RETURNING serverOrder`,
|
|
94
|
+
[
|
|
95
|
+
item.oid,
|
|
96
|
+
JSON.stringify(item.data),
|
|
97
|
+
item.timestamp,
|
|
114
98
|
replicaId,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
cb.columns(['replicaId', 'oid', 'timestamp']).doNothing(),
|
|
120
|
-
)
|
|
121
|
-
.returning('serverOrder')
|
|
122
|
-
.executeTakeFirst();
|
|
99
|
+
currentServerOrder + 1,
|
|
100
|
+
item.authz,
|
|
101
|
+
],
|
|
102
|
+
);
|
|
123
103
|
if (result) {
|
|
124
104
|
currentServerOrder = result.serverOrder;
|
|
125
105
|
} else {
|
|
@@ -132,22 +112,18 @@ export class SqlOperations implements OperationStorage {
|
|
|
132
112
|
});
|
|
133
113
|
};
|
|
134
114
|
|
|
135
|
-
deleteAll = async (
|
|
136
|
-
const db =
|
|
137
|
-
|
|
115
|
+
deleteAll = async (): Promise<void> => {
|
|
116
|
+
const db = this.db;
|
|
117
|
+
db.exec('DELETE FROM OperationHistory');
|
|
138
118
|
};
|
|
139
|
-
delete = async (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
): Promise<void> => {
|
|
143
|
-
const db = await this.dbs.get(libraryId);
|
|
144
|
-
await db.transaction().execute(async (tx) => {
|
|
119
|
+
delete = async (operations: Operation[]): Promise<void> => {
|
|
120
|
+
const db = this.db;
|
|
121
|
+
return db.transaction((tx) => {
|
|
145
122
|
for (const item of operations) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
.execute();
|
|
123
|
+
tx.exec(
|
|
124
|
+
`DELETE FROM OperationHistory WHERE oid = ? AND timestamp = ?`,
|
|
125
|
+
[item.oid, item.timestamp],
|
|
126
|
+
);
|
|
151
127
|
}
|
|
152
128
|
});
|
|
153
129
|
};
|
|
@@ -1,61 +1,50 @@
|
|
|
1
|
+
import { ReplicaType, VerdantError } from '@verdant-web/common';
|
|
1
2
|
import { StoredReplicaInfo } from '../../types.js';
|
|
2
3
|
import { ReplicaStorage } from '../Storage.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { ReplicaType, VerdantError } from '@verdant-web/common';
|
|
6
|
-
import { Databases } from './Databases.js';
|
|
4
|
+
import { SqliteExecutor } from './database.js';
|
|
5
|
+
import { ReplicaInfoRow } from './tables.js';
|
|
7
6
|
|
|
8
7
|
export class SqlReplicas implements ReplicaStorage {
|
|
9
8
|
constructor(
|
|
10
|
-
private
|
|
9
|
+
private db: SqliteExecutor,
|
|
10
|
+
private libraryId: string,
|
|
11
11
|
private readonly replicaTruancyMinutes: number,
|
|
12
|
-
private readonly dialect: 'sqlite' | 'postgres',
|
|
13
12
|
) {}
|
|
14
13
|
|
|
15
14
|
get truantCutoff(): number {
|
|
16
15
|
return Date.now() - this.replicaTruancyMinutes * 60 * 1000;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
private attachLibraryId = (
|
|
20
|
-
(row as any).libraryId = libraryId;
|
|
18
|
+
private attachLibraryId = (row: ReplicaInfoRow) => {
|
|
19
|
+
(row as any).libraryId = this.libraryId;
|
|
21
20
|
return row as StoredReplicaInfo;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
get = async (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.selectFrom('ReplicaInfo')
|
|
32
|
-
.where('id', '=', replicaId)
|
|
33
|
-
.selectAll()
|
|
34
|
-
.executeTakeFirst()) ?? null;
|
|
35
|
-
if (row) return this.attachLibraryId(libraryId, row);
|
|
23
|
+
get = async (replicaId: string): Promise<StoredReplicaInfo | null> => {
|
|
24
|
+
const db = this.db;
|
|
25
|
+
const row = db.first<ReplicaInfoRow>(
|
|
26
|
+
`SELECT * FROM ReplicaInfo WHERE id = ?`,
|
|
27
|
+
[replicaId],
|
|
28
|
+
);
|
|
29
|
+
if (row) return this.attachLibraryId(row);
|
|
36
30
|
return row;
|
|
37
31
|
};
|
|
38
32
|
getOrCreate = async (
|
|
39
|
-
libraryId: string,
|
|
40
33
|
replicaId: string,
|
|
41
34
|
info: { userId: string; type: ReplicaType },
|
|
42
35
|
): Promise<{
|
|
43
36
|
status: 'new' | 'existing' | 'truant';
|
|
44
37
|
replicaInfo: StoredReplicaInfo;
|
|
45
38
|
}> => {
|
|
46
|
-
const existing = await this.get(
|
|
39
|
+
const existing = await this.get(replicaId);
|
|
47
40
|
if (!existing) {
|
|
48
|
-
const db =
|
|
49
|
-
const created =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ackedServerOrder: 0,
|
|
56
|
-
})
|
|
57
|
-
.returningAll()
|
|
58
|
-
.executeTakeFirst();
|
|
41
|
+
const db = this.db;
|
|
42
|
+
const created = db.first<ReplicaInfoRow>(
|
|
43
|
+
`INSERT INTO ReplicaInfo (id, clientId, type, ackedServerOrder) VALUES (?, ?, ?, ?)
|
|
44
|
+
ON CONFLICT(id) DO NOTHING
|
|
45
|
+
RETURNING *`,
|
|
46
|
+
[replicaId, info.userId, info.type, 0],
|
|
47
|
+
);
|
|
59
48
|
if (!created) {
|
|
60
49
|
throw new VerdantError(
|
|
61
50
|
VerdantError.Code.Unexpected,
|
|
@@ -65,18 +54,17 @@ export class SqlReplicas implements ReplicaStorage {
|
|
|
65
54
|
}
|
|
66
55
|
return {
|
|
67
56
|
status: 'new',
|
|
68
|
-
replicaInfo: this.attachLibraryId(
|
|
57
|
+
replicaInfo: this.attachLibraryId(created),
|
|
69
58
|
};
|
|
70
59
|
}
|
|
71
60
|
|
|
72
61
|
if (existing.type !== info.type) {
|
|
73
|
-
const db =
|
|
62
|
+
const db = this.db;
|
|
74
63
|
// type should be updated if a new token changes it
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.execute();
|
|
64
|
+
db.exec(`UPDATE ReplicaInfo SET type = ? WHERE id = ?`, [
|
|
65
|
+
info.type,
|
|
66
|
+
replicaId,
|
|
67
|
+
]);
|
|
80
68
|
}
|
|
81
69
|
|
|
82
70
|
if (existing.clientId !== info.userId) {
|
|
@@ -99,112 +87,83 @@ export class SqlReplicas implements ReplicaStorage {
|
|
|
99
87
|
};
|
|
100
88
|
|
|
101
89
|
getAll = async (
|
|
102
|
-
libraryId: string,
|
|
103
90
|
options?: { omitTruant: boolean } | undefined,
|
|
104
91
|
): Promise<StoredReplicaInfo[]> => {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (options?.omitTruant) {
|
|
109
|
-
builder = builder.where('lastSeenWallClockTime', '>', this.truantCutoff);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return (await builder.selectAll().execute()).map(
|
|
113
|
-
this.attachLibraryId.bind(this, libraryId),
|
|
92
|
+
const result = this.db.query<ReplicaInfoRow>(
|
|
93
|
+
`SELECT * FROM ReplicaInfo${options?.omitTruant ? ' WHERE lastSeenWallClockTime > ?' : ''}`,
|
|
94
|
+
options?.omitTruant ? [this.truantCutoff] : [],
|
|
114
95
|
);
|
|
96
|
+
|
|
97
|
+
return result.map(this.attachLibraryId);
|
|
115
98
|
};
|
|
116
99
|
|
|
117
|
-
updateLastSeen = async (
|
|
118
|
-
libraryId: string,
|
|
119
|
-
replicaId: string,
|
|
120
|
-
): Promise<void> => {
|
|
100
|
+
updateLastSeen = async (replicaId: string): Promise<void> => {
|
|
121
101
|
const clockTime = Date.now();
|
|
122
|
-
const db =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.execute();
|
|
102
|
+
const db = this.db;
|
|
103
|
+
db.exec(`UPDATE ReplicaInfo SET lastSeenWallClockTime = ? WHERE id = ?`, [
|
|
104
|
+
clockTime,
|
|
105
|
+
replicaId,
|
|
106
|
+
]);
|
|
128
107
|
};
|
|
129
108
|
|
|
130
109
|
updateAckedServerOrder = async (
|
|
131
|
-
libraryId: string,
|
|
132
110
|
replicaId: string,
|
|
133
111
|
serverOrder: number,
|
|
134
112
|
): Promise<void> => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
.set(
|
|
140
|
-
'ackedServerOrder',
|
|
141
|
-
({ val }) =>
|
|
142
|
-
sql<number>`${sql.raw(max)}(ackedServerOrder, ${val(serverOrder)})`,
|
|
143
|
-
)
|
|
144
|
-
.where('id', '=', replicaId)
|
|
145
|
-
.execute();
|
|
113
|
+
this.db.exec(
|
|
114
|
+
`UPDATE ReplicaInfo SET ackedServerOrder = MAX(ackedServerOrder, ?) WHERE id = ?`,
|
|
115
|
+
[serverOrder, replicaId],
|
|
116
|
+
);
|
|
146
117
|
};
|
|
147
118
|
|
|
148
119
|
updateAcknowledgedLogicalTime = async (
|
|
149
|
-
libraryId: string,
|
|
150
120
|
replicaId: string,
|
|
151
121
|
timestamp: string,
|
|
152
122
|
): Promise<void> => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.where('id', '=', replicaId)
|
|
158
|
-
.execute();
|
|
123
|
+
this.db.exec(`UPDATE ReplicaInfo SET ackedLogicalTime = ? WHERE id = ?`, [
|
|
124
|
+
timestamp,
|
|
125
|
+
replicaId,
|
|
126
|
+
]);
|
|
159
127
|
};
|
|
160
128
|
|
|
161
|
-
getEarliestAckedServerOrder = async (
|
|
162
|
-
const db = await this.dbs.get(libraryId);
|
|
129
|
+
getEarliestAckedServerOrder = async (): Promise<number> => {
|
|
163
130
|
// gets earliest acked server order of all non-truant replicas.
|
|
164
|
-
const res =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.select('ackedServerOrder')
|
|
169
|
-
.executeTakeFirst();
|
|
131
|
+
const res = this.db.first<Pick<ReplicaInfoRow, 'ackedServerOrder'>>(
|
|
132
|
+
`SELECT ackedServerOrder FROM ReplicaInfo WHERE lastSeenWallClockTime > ? ORDER BY ackedServerOrder ASC LIMIT 1`,
|
|
133
|
+
[this.truantCutoff],
|
|
134
|
+
);
|
|
170
135
|
return res?.ackedServerOrder ?? 0;
|
|
171
136
|
};
|
|
172
137
|
|
|
173
138
|
acknowledgeOperation = async (
|
|
174
|
-
libraryId: string,
|
|
175
139
|
replicaId: string,
|
|
176
140
|
timestamp: string,
|
|
177
141
|
): Promise<void> => {
|
|
178
142
|
if (!timestamp) return;
|
|
179
|
-
const db = await this.dbs.get(libraryId);
|
|
180
143
|
// when acking an operation, we also set the replica's server order
|
|
181
144
|
// to that operation's server order, if it's greater.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
),
|
|
195
|
-
0
|
|
145
|
+
this.db.exec(
|
|
146
|
+
`UPDATE ReplicaInfo SET
|
|
147
|
+
ackedLogicalTime = ?,
|
|
148
|
+
ackedServerOrder = MAX(
|
|
149
|
+
ackedServerOrder,
|
|
150
|
+
COALESCE(
|
|
151
|
+
(
|
|
152
|
+
SELECT serverOrder FROM OperationHistory
|
|
153
|
+
WHERE timestamp = ?
|
|
154
|
+
),
|
|
155
|
+
0
|
|
156
|
+
)
|
|
196
157
|
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.execute();
|
|
158
|
+
WHERE id = ?`,
|
|
159
|
+
[timestamp, timestamp, replicaId],
|
|
160
|
+
);
|
|
201
161
|
};
|
|
202
162
|
|
|
203
163
|
getGlobalAck = async (
|
|
204
|
-
libraryId: string,
|
|
205
164
|
onlineReplicaIds?: string[] | undefined,
|
|
206
165
|
): Promise<string | null> => {
|
|
207
|
-
const nonTruant = await this.getAll(
|
|
166
|
+
const nonTruant = await this.getAll({ omitTruant: true });
|
|
208
167
|
if (nonTruant.length === 0) return null;
|
|
209
168
|
const globalAckEligible = nonTruant.filter(
|
|
210
169
|
(replica) => replica.type < 2 || onlineReplicaIds?.includes(replica.id),
|
|
@@ -219,19 +178,19 @@ export class SqlReplicas implements ReplicaStorage {
|
|
|
219
178
|
null as string | null,
|
|
220
179
|
);
|
|
221
180
|
};
|
|
222
|
-
delete = async (
|
|
223
|
-
|
|
224
|
-
await db.deleteFrom('ReplicaInfo').where('id', '=', replicaId).execute();
|
|
181
|
+
delete = async (replicaId: string): Promise<void> => {
|
|
182
|
+
this.db.exec(`DELETE FROM ReplicaInfo WHERE id = ?`, [replicaId]);
|
|
225
183
|
};
|
|
226
|
-
deleteAll = async (
|
|
227
|
-
|
|
228
|
-
await db.deleteFrom('ReplicaInfo').execute();
|
|
184
|
+
deleteAll = async (): Promise<void> => {
|
|
185
|
+
this.db.exec(`DELETE FROM ReplicaInfo`, []);
|
|
229
186
|
};
|
|
230
|
-
deleteAllForUser = async (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
): Promise<void> => {
|
|
234
|
-
|
|
235
|
-
|
|
187
|
+
deleteAllForUser = async (userId: string): Promise<void> => {
|
|
188
|
+
this.db.exec(`DELETE FROM ReplicaInfo WHERE clientId = ?`, [userId]);
|
|
189
|
+
};
|
|
190
|
+
forceTruant = async (replicaId: string): Promise<void> => {
|
|
191
|
+
this.db.exec(
|
|
192
|
+
`UPDATE ReplicaInfo SET lastSeenWallClockTime = ? WHERE id = ?`,
|
|
193
|
+
[Date.now() - this.replicaTruancyMinutes * 60 * 1000 - 1, replicaId],
|
|
194
|
+
);
|
|
236
195
|
};
|
|
237
196
|
}
|