@verdant-web/server 3.3.10 → 4.0.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.
Files changed (204) hide show
  1. package/bin/server.mjs +110 -107
  2. package/dist/esm/TokenVerifier.d.ts +1 -0
  3. package/dist/esm/TokenVerifier.js +3 -0
  4. package/dist/esm/TokenVerifier.js.map +1 -1
  5. package/dist/esm/adapters/node/errorHandler.d.ts +2 -0
  6. package/dist/esm/adapters/node/errorHandler.js +12 -0
  7. package/dist/esm/adapters/node/errorHandler.js.map +1 -0
  8. package/dist/esm/adapters/node/getRequestToken.d.ts +1 -0
  9. package/dist/esm/adapters/node/getRequestToken.js +25 -0
  10. package/dist/esm/adapters/node/getRequestToken.js.map +1 -0
  11. package/dist/esm/adapters/node/httpRouter.d.ts +47 -0
  12. package/dist/esm/adapters/node/httpRouter.js +88 -0
  13. package/dist/esm/adapters/node/httpRouter.js.map +1 -0
  14. package/dist/esm/adapters/node/index.d.ts +5 -0
  15. package/dist/esm/adapters/node/index.js +6 -0
  16. package/dist/esm/adapters/node/index.js.map +1 -0
  17. package/dist/esm/adapters/node/tokenMiddleware.d.ts +7 -0
  18. package/dist/esm/adapters/node/tokenMiddleware.js +22 -0
  19. package/dist/esm/adapters/node/tokenMiddleware.js.map +1 -0
  20. package/dist/esm/adapters/node/websocketHandler.d.ts +4 -0
  21. package/dist/esm/adapters/node/websocketHandler.js +54 -0
  22. package/dist/esm/adapters/node/websocketHandler.js.map +1 -0
  23. package/dist/esm/connections/ClientConnection.d.ts +25 -0
  24. package/dist/esm/{ClientConnection.js → connections/ClientConnection.js} +47 -38
  25. package/dist/esm/connections/ClientConnection.js.map +1 -0
  26. package/dist/esm/connections/MessageSender.d.ts +5 -0
  27. package/dist/esm/connections/MessageSender.js.map +1 -0
  28. package/dist/esm/connections/Presence.d.ts +23 -0
  29. package/dist/esm/connections/Presence.js +65 -0
  30. package/dist/esm/connections/Presence.js.map +1 -0
  31. package/dist/esm/core.d.ts +15 -0
  32. package/dist/esm/core.js +15 -0
  33. package/dist/esm/core.js.map +1 -0
  34. package/dist/esm/defaults.d.ts +2 -0
  35. package/dist/esm/defaults.js +3 -0
  36. package/dist/esm/defaults.js.map +1 -0
  37. package/dist/esm/files/FileStorage.d.ts +12 -3
  38. package/dist/esm/files/FileStorage.js +20 -0
  39. package/dist/esm/files/FileStorage.js.map +1 -1
  40. package/dist/esm/index.d.ts +7 -7
  41. package/dist/esm/index.js +2 -2
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/internals.d.ts +14 -0
  44. package/dist/esm/internals.js +12 -0
  45. package/dist/esm/internals.js.map +1 -0
  46. package/dist/esm/libraries/Library.d.ts +78 -0
  47. package/dist/esm/{ServerLibrary.js → libraries/Library.js} +153 -144
  48. package/dist/esm/libraries/Library.js.map +1 -0
  49. package/dist/esm/{ServerLibrary.test.js → libraries/Library.test.js} +42 -33
  50. package/dist/esm/libraries/Library.test.js.map +1 -0
  51. package/dist/esm/libraries/generic.d.ts +8 -0
  52. package/dist/esm/libraries/generic.js +2 -0
  53. package/dist/esm/libraries/generic.js.map +1 -0
  54. package/dist/esm/libraries/singleNode.d.ts +53 -0
  55. package/dist/esm/libraries/singleNode.js +88 -0
  56. package/dist/esm/libraries/singleNode.js.map +1 -0
  57. package/dist/esm/logger.d.ts +1 -0
  58. package/dist/esm/logger.js +2 -0
  59. package/dist/esm/logger.js.map +1 -0
  60. package/dist/esm/storage/Storage.d.ts +39 -38
  61. package/dist/esm/storage/StorageManager.d.ts +11 -0
  62. package/dist/esm/storage/StorageManager.js +32 -0
  63. package/dist/esm/storage/StorageManager.js.map +1 -0
  64. package/dist/esm/storage/index.d.ts +1 -2
  65. package/dist/esm/storage/index.js +1 -2
  66. package/dist/esm/storage/index.js.map +1 -1
  67. package/dist/esm/storage/sqlShard/Databases.d.ts +5 -3
  68. package/dist/esm/storage/sqlShard/Databases.js +11 -7
  69. package/dist/esm/storage/sqlShard/Databases.js.map +1 -1
  70. package/dist/esm/storage/sqlShard/SqlBaselines.d.ts +16 -17
  71. package/dist/esm/storage/sqlShard/SqlBaselines.js +55 -59
  72. package/dist/esm/storage/sqlShard/SqlBaselines.js.map +1 -1
  73. package/dist/esm/storage/sqlShard/SqlFileMetadata.d.ts +13 -13
  74. package/dist/esm/storage/sqlShard/SqlFileMetadata.js +44 -57
  75. package/dist/esm/storage/sqlShard/SqlFileMetadata.js.map +1 -1
  76. package/dist/esm/storage/sqlShard/SqlOperations.d.ts +12 -12
  77. package/dist/esm/storage/sqlShard/SqlOperations.js +38 -73
  78. package/dist/esm/storage/sqlShard/SqlOperations.js.map +1 -1
  79. package/dist/esm/storage/sqlShard/SqlReplicas.d.ts +18 -17
  80. package/dist/esm/storage/sqlShard/SqlReplicas.js +63 -100
  81. package/dist/esm/storage/sqlShard/SqlReplicas.js.map +1 -1
  82. package/dist/esm/storage/sqlShard/database.d.ts +25 -5
  83. package/dist/esm/storage/sqlShard/database.js +47 -15
  84. package/dist/esm/storage/sqlShard/database.js.map +1 -1
  85. package/dist/esm/storage/sqlShard/migrations/v1.d.ts +2 -3
  86. package/dist/esm/storage/sqlShard/migrations/v1.js +42 -47
  87. package/dist/esm/storage/sqlShard/migrations/v1.js.map +1 -1
  88. package/dist/esm/storage/sqlShard/migrations/v2.d.ts +2 -3
  89. package/dist/esm/storage/sqlShard/migrations/v2.js +5 -15
  90. package/dist/esm/storage/sqlShard/migrations/v2.js.map +1 -1
  91. package/dist/esm/storage/sqlShard/migrations.d.ts +5 -7
  92. package/dist/esm/storage/sqlShard/migrations.js +48 -1
  93. package/dist/esm/storage/sqlShard/migrations.js.map +1 -1
  94. package/dist/esm/storage/sqlShard/sqlShardStorage.d.ts +25 -4
  95. package/dist/esm/storage/sqlShard/sqlShardStorage.js +43 -40
  96. package/dist/esm/storage/sqlShard/sqlShardStorage.js.map +1 -1
  97. package/dist/esm/storage/sqlShard/tables.d.ts +9 -6
  98. package/dist/esm/types.d.ts +1 -2
  99. package/package.json +24 -13
  100. package/src/TokenVerifier.ts +4 -0
  101. package/src/adapters/node/errorHandler.ts +18 -0
  102. package/src/adapters/node/getRequestToken.ts +34 -0
  103. package/src/adapters/node/httpRouter.ts +139 -0
  104. package/src/adapters/node/index.ts +5 -0
  105. package/src/adapters/node/tokenMiddleware.ts +32 -0
  106. package/src/adapters/node/websocketHandler.ts +79 -0
  107. package/src/{ClientConnection.ts → connections/ClientConnection.ts} +64 -57
  108. package/src/connections/MessageSender.ts +6 -0
  109. package/src/connections/Presence.ts +95 -0
  110. package/src/core.ts +36 -0
  111. package/src/defaults.ts +2 -0
  112. package/src/files/FileStorage.ts +36 -3
  113. package/src/index.ts +7 -7
  114. package/src/internals.ts +14 -0
  115. package/src/{ServerLibrary.test.ts → libraries/Library.test.ts} +51 -49
  116. package/src/{ServerLibrary.ts → libraries/Library.ts} +203 -206
  117. package/src/libraries/generic.ts +9 -0
  118. package/src/libraries/singleNode.ts +145 -0
  119. package/src/logger.ts +4 -0
  120. package/src/storage/Storage.ts +37 -66
  121. package/src/storage/StorageManager.ts +39 -0
  122. package/src/storage/index.ts +1 -2
  123. package/src/storage/sqlShard/Databases.ts +26 -13
  124. package/src/storage/sqlShard/SqlBaselines.ts +58 -67
  125. package/src/storage/sqlShard/SqlFileMetadata.ts +51 -72
  126. package/src/storage/sqlShard/SqlOperations.ts +57 -81
  127. package/src/storage/sqlShard/SqlReplicas.ts +80 -121
  128. package/src/storage/sqlShard/database.ts +82 -18
  129. package/src/storage/sqlShard/migrations/v1.ts +42 -52
  130. package/src/storage/sqlShard/migrations/v2.ts +5 -19
  131. package/src/storage/sqlShard/migrations.ts +66 -1
  132. package/src/storage/sqlShard/sqlShardStorage.ts +56 -55
  133. package/src/storage/sqlShard/tables.ts +6 -6
  134. package/src/types.ts +1 -2
  135. package/dist/esm/ClientConnection.d.ts +0 -19
  136. package/dist/esm/ClientConnection.js.map +0 -1
  137. package/dist/esm/MessageSender.d.ts +0 -5
  138. package/dist/esm/MessageSender.js.map +0 -1
  139. package/dist/esm/Presence.d.ts +0 -18
  140. package/dist/esm/Presence.js +0 -65
  141. package/dist/esm/Presence.js.map +0 -1
  142. package/dist/esm/ReplicaKeepaliveTimers.d.ts +0 -17
  143. package/dist/esm/ReplicaKeepaliveTimers.js +0 -27
  144. package/dist/esm/ReplicaKeepaliveTimers.js.map +0 -1
  145. package/dist/esm/Server.d.ts +0 -166
  146. package/dist/esm/Server.js +0 -572
  147. package/dist/esm/Server.js.map +0 -1
  148. package/dist/esm/ServerLibrary.d.ts +0 -64
  149. package/dist/esm/ServerLibrary.js.map +0 -1
  150. package/dist/esm/ServerLibrary.test.js.map +0 -1
  151. package/dist/esm/storage/sql/SqlBaselines.d.ts +0 -22
  152. package/dist/esm/storage/sql/SqlBaselines.js +0 -107
  153. package/dist/esm/storage/sql/SqlBaselines.js.map +0 -1
  154. package/dist/esm/storage/sql/SqlFileMetadata.d.ts +0 -18
  155. package/dist/esm/storage/sql/SqlFileMetadata.js +0 -69
  156. package/dist/esm/storage/sql/SqlFileMetadata.js.map +0 -1
  157. package/dist/esm/storage/sql/SqlOperations.d.ts +0 -19
  158. package/dist/esm/storage/sql/SqlOperations.js +0 -122
  159. package/dist/esm/storage/sql/SqlOperations.js.map +0 -1
  160. package/dist/esm/storage/sql/SqlReplicas.d.ts +0 -32
  161. package/dist/esm/storage/sql/SqlReplicas.js +0 -163
  162. package/dist/esm/storage/sql/SqlReplicas.js.map +0 -1
  163. package/dist/esm/storage/sql/database.d.ts +0 -9
  164. package/dist/esm/storage/sql/database.js +0 -24
  165. package/dist/esm/storage/sql/database.js.map +0 -1
  166. package/dist/esm/storage/sql/migrations/v1.d.ts +0 -3
  167. package/dist/esm/storage/sql/migrations/v1.js +0 -59
  168. package/dist/esm/storage/sql/migrations/v1.js.map +0 -1
  169. package/dist/esm/storage/sql/migrations/v2.d.ts +0 -3
  170. package/dist/esm/storage/sql/migrations/v2.js +0 -16
  171. package/dist/esm/storage/sql/migrations/v2.js.map +0 -1
  172. package/dist/esm/storage/sql/migrations.d.ts +0 -7
  173. package/dist/esm/storage/sql/migrations.js +0 -4
  174. package/dist/esm/storage/sql/migrations.js.map +0 -1
  175. package/dist/esm/storage/sql/sqlStorage.d.ts +0 -6
  176. package/dist/esm/storage/sql/sqlStorage.js +0 -35
  177. package/dist/esm/storage/sql/sqlStorage.js.map +0 -1
  178. package/dist/esm/storage/sql/tables.d.ts +0 -44
  179. package/dist/esm/storage/sql/tables.js +0 -2
  180. package/dist/esm/storage/sql/tables.js.map +0 -1
  181. package/dist/esm/storage/sqlShard/transfer.d.ts +0 -7
  182. package/dist/esm/storage/sqlShard/transfer.js +0 -90
  183. package/dist/esm/storage/sqlShard/transfer.js.map +0 -1
  184. package/dist/esm/storage/sqlShard/transfer.test.d.ts +0 -1
  185. package/dist/esm/storage/sqlShard/transfer.test.js +0 -109
  186. package/dist/esm/storage/sqlShard/transfer.test.js.map +0 -1
  187. package/src/MessageSender.ts +0 -10
  188. package/src/Presence.ts +0 -86
  189. package/src/ReplicaKeepaliveTimers.ts +0 -34
  190. package/src/Server.ts +0 -859
  191. package/src/storage/sql/SqlBaselines.ts +0 -147
  192. package/src/storage/sql/SqlFileMetadata.ts +0 -94
  193. package/src/storage/sql/SqlOperations.ts +0 -160
  194. package/src/storage/sql/SqlReplicas.ts +0 -234
  195. package/src/storage/sql/database.ts +0 -32
  196. package/src/storage/sql/migrations/v1.ts +0 -64
  197. package/src/storage/sql/migrations/v2.ts +0 -20
  198. package/src/storage/sql/migrations.ts +0 -4
  199. package/src/storage/sql/sqlStorage.ts +0 -57
  200. package/src/storage/sql/tables.ts +0 -51
  201. package/src/storage/sqlShard/transfer.test.ts +0 -149
  202. package/src/storage/sqlShard/transfer.ts +0 -123
  203. /package/dist/esm/{MessageSender.js → connections/MessageSender.js} +0 -0
  204. /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 { Databases } from './Databases.js';
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 dbs: Databases,
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 = (libraryId: string, row: FileMetadataRow) => {
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
- libraryId: string,
23
- fileId: string,
24
- ): Promise<FileMetadata | null> => {
25
- const db = await this.dbs.get(libraryId);
26
- const row =
27
- (await db
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 (libraryId: string): Promise<FileMetadata[]> => {
37
- const db = await this.dbs.get(libraryId);
38
- return (await db.selectFrom('FileMetadata').selectAll().execute()).map(
39
- this.attachLibrary.bind(this, libraryId),
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 (libraryId: string): Promise<void> => {
44
- const db = await this.dbs.get(libraryId);
45
- await db.deleteFrom('FileMetadata').execute();
37
+ deleteAll = async (): Promise<void> => {
38
+ const db = this.db;
39
+ db.exec('DELETE FROM FileMetadata');
46
40
  };
47
41
 
48
- put = async (libraryId: string, fileInfo: FileInfo): Promise<void> => {
49
- const db = await this.dbs.get(libraryId);
50
- await db
51
- .insertInto('FileMetadata')
52
- .values({
53
- fileId: fileInfo.id,
54
- name: fileInfo.fileName,
55
- type: fileInfo.type,
56
- })
57
- .onConflict((cb) =>
58
- cb.column('fileId').doUpdateSet({
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
- libraryId: string,
69
- fileId: string,
70
- ): Promise<void> => {
71
- const db = await this.dbs.get(libraryId);
72
- await db
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 (libraryId: string, fileId: string): Promise<void> => {
80
- const db = await this.dbs.get(libraryId);
81
- await db.deleteFrom('FileMetadata').where('fileId', '=', fileId).execute();
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 (libraryId: string): Promise<FileMetadata[]> => {
85
- const db = await this.dbs.get(libraryId);
86
- return (
87
- await db
88
- .selectFrom('FileMetadata')
89
- .where(
90
- 'pendingDeleteAt',
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(private dbs: Databases, private dialect: 'postgres' | 'sqlite') {}
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
- libraryId: string,
17
- oid: string,
18
- ): Promise<StoredOperation[]> => {
19
- const db = await this.dbs.get(libraryId);
20
- const raw = await db
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 = await this.dbs.get(libraryId);
36
- const raw = await db
37
- .selectFrom('OperationHistory')
38
- .where('serverOrder', '<', beforeServerOrder)
39
- .orderBy('timestamp', 'asc')
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 = await this.dbs.get(libraryId);
52
- const raw = await db
53
- .selectFrom('OperationHistory')
54
- .where('serverOrder', '>', afterServerOrder)
55
- .orderBy('timestamp', 'asc')
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 (libraryId: string): Promise<number> => {
64
- const db = await this.dbs.get(libraryId);
65
- const result = await db
66
- .selectFrom('OperationHistory')
67
- .orderBy('serverOrder', 'desc')
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 (libraryId: string): Promise<number> => {
79
- const db = await this.dbs.get(libraryId);
67
+ getCount = async (): Promise<number> => {
68
+ const db = this.db;
80
69
  return (
81
- (
82
- await db
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 = await this.dbs.get(libraryId);
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 await db.transaction().execute(async (tx): Promise<number> => {
99
- let orderResult = await tx
100
- .selectFrom('OperationHistory')
101
- .select('serverOrder')
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 = await tx
109
- .insertInto('OperationHistory')
110
- .values({
111
- oid: item.oid,
112
- data: JSON.stringify(item.data),
113
- timestamp: item.timestamp,
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
- serverOrder: currentServerOrder + 1,
116
- authz: item.authz,
117
- })
118
- .onConflict((cb) =>
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 (libraryId: string): Promise<void> => {
136
- const db = await this.dbs.get(libraryId);
137
- await db.deleteFrom('OperationHistory').execute();
115
+ deleteAll = async (): Promise<void> => {
116
+ const db = this.db;
117
+ db.exec('DELETE FROM OperationHistory');
138
118
  };
139
- delete = async (
140
- libraryId: string,
141
- operations: Operation[],
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
- await tx
147
- .deleteFrom('OperationHistory')
148
- .where('oid', '=', item.oid)
149
- .where('timestamp', '=', item.timestamp)
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 { sql, type Kysely } from 'kysely';
4
- import { Database, ReplicaInfoRow } from './tables.js';
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 dbs: Databases,
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 = (libraryId: string, row: ReplicaInfoRow) => {
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
- libraryId: string,
26
- replicaId: string,
27
- ): Promise<StoredReplicaInfo | null> => {
28
- const db = await this.dbs.get(libraryId);
29
- const row =
30
- (await db
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(libraryId, replicaId);
39
+ const existing = await this.get(replicaId);
47
40
  if (!existing) {
48
- const db = await this.dbs.get(libraryId);
49
- const created = await db
50
- .insertInto('ReplicaInfo')
51
- .values({
52
- id: replicaId,
53
- clientId: info.userId,
54
- type: info.type,
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(libraryId, created),
57
+ replicaInfo: this.attachLibraryId(created),
69
58
  };
70
59
  }
71
60
 
72
61
  if (existing.type !== info.type) {
73
- const db = await this.dbs.get(libraryId);
62
+ const db = this.db;
74
63
  // type should be updated if a new token changes it
75
- await db
76
- .updateTable('ReplicaInfo')
77
- .set({ type: info.type })
78
- .where('id', '=', replicaId)
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 db = await this.dbs.get(libraryId);
106
- let builder = db.selectFrom('ReplicaInfo');
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 = await this.dbs.get(libraryId);
123
- await db
124
- .updateTable('ReplicaInfo')
125
- .set({ lastSeenWallClockTime: clockTime })
126
- .where('id', '=', replicaId)
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
- const max = this.dialect === 'postgres' ? 'GREATEST' : 'MAX';
136
- const db = await this.dbs.get(libraryId);
137
- await db
138
- .updateTable('ReplicaInfo')
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
- const db = await this.dbs.get(libraryId);
154
- await db
155
- .updateTable('ReplicaInfo')
156
- .set({ ackedLogicalTime: timestamp })
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 (libraryId: string): Promise<number> => {
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 = await db
165
- .selectFrom('ReplicaInfo')
166
- .where('lastSeenWallClockTime', '>', this.truantCutoff)
167
- .orderBy('ackedServerOrder', 'asc')
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
- const max = this.dialect === 'postgres' ? 'GREATEST' : 'MAX';
183
- await db
184
- .updateTable('ReplicaInfo')
185
- .set({ ackedLogicalTime: timestamp })
186
- .set(
187
- 'ackedServerOrder',
188
- ({ val }) => sql<number>`${sql.raw(max)}(
189
- ackedServerOrder,
190
- COALESCE(
191
- (
192
- SELECT serverOrder FROM OperationHistory
193
- WHERE timestamp = ${val(timestamp)}
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
- .where('id', '=', replicaId)
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(libraryId, { omitTruant: true });
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 (libraryId: string, replicaId: string): Promise<void> => {
223
- const db = await this.dbs.get(libraryId);
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 (libraryId: string): Promise<void> => {
227
- const db = await this.dbs.get(libraryId);
228
- await db.deleteFrom('ReplicaInfo').execute();
184
+ deleteAll = async (): Promise<void> => {
185
+ this.db.exec(`DELETE FROM ReplicaInfo`, []);
229
186
  };
230
- deleteAllForUser = async (
231
- libraryId: string,
232
- userId: string,
233
- ): Promise<void> => {
234
- const db = await this.dbs.get(libraryId);
235
- await db.deleteFrom('ReplicaInfo').where('clientId', '=', userId).execute();
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
  }