@verdant-web/server 3.3.11 → 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,18 +1,67 @@
1
- import { Kysely, SqliteDialect } from 'kysely';
2
- import { join } from 'path';
3
- import { Database as DatabaseTypes } from './tables.js';
4
1
  import Database from 'better-sqlite3';
5
- import { migrateToLatest } from '@a-type/kysely';
6
- import migrations from './migrations.js';
2
+ import { join } from 'path';
3
+ import { Logger } from '../../logger.js';
4
+ import { migrateToLatest } from './migrations.js';
5
+
6
+ export interface SqliteExecutor {
7
+ query: <O extends Record<string, any>>(
8
+ sql: string,
9
+ parameters?: readonly unknown[],
10
+ ) => O[];
11
+ first: <O extends Record<string, any>>(
12
+ sql: string,
13
+ parameters?: readonly unknown[],
14
+ ) => O | null;
15
+ exec: (sql: string, parameters?: readonly unknown[]) => void;
16
+ migrate: () => void;
17
+ close: () => void;
18
+ transaction: <T>(cb: (tx: SqliteExecutor) => T) => T | Promise<T>;
19
+ migrated: boolean;
20
+ }
21
+
22
+ export class SqliteExecutor {
23
+ private log;
24
+ constructor(
25
+ partial: Pick<SqliteExecutor, 'query' | 'exec' | 'close'> & {
26
+ createTransaction: (
27
+ exec: SqliteExecutor,
28
+ ) => <T>(cb: (tx: SqliteExecutor) => T) => T | Promise<T>;
29
+ },
30
+ { log }: { log?: Logger } = {},
31
+ ) {
32
+ this.log = log || (() => {});
33
+ this.query = partial.query.bind(this) as any;
34
+ this.exec = partial.exec.bind(this);
35
+ this.close = partial.close.bind(this);
36
+ this.transaction = partial.createTransaction(this);
37
+ }
7
38
 
8
- export async function openDatabase(
39
+ migrate = () => {
40
+ return migrateToLatest(this, this.log);
41
+ };
42
+ first = <O extends Record<string, any>>(
43
+ sql: string,
44
+ parameters?: readonly unknown[],
45
+ ): O | null => {
46
+ const results = this.query<O>(sql, parameters);
47
+ return results[0] ?? null;
48
+ };
49
+ migrated = false;
50
+ }
51
+
52
+ export function openDatabase(executor: SqliteExecutor) {
53
+ executor.migrate();
54
+ return executor;
55
+ }
56
+
57
+ export function createFilesystemExecutor(
9
58
  directory: string,
10
59
  libraryId: string,
11
60
  options: {
12
- skipMigrations?: boolean;
13
61
  disableWal?: boolean;
62
+ log?: Logger;
14
63
  } = {},
15
- ) {
64
+ ): SqliteExecutor {
16
65
  const label = `openDatabase ${libraryId}`;
17
66
  console.time(label);
18
67
  const filePath =
@@ -23,15 +72,30 @@ export async function openDatabase(
23
72
  if (!options.disableWal) {
24
73
  internalDb.pragma('journal_mode = WAL');
25
74
  }
26
- const db = new Kysely<DatabaseTypes>({
27
- dialect: new SqliteDialect({
28
- database: internalDb,
29
- }),
30
- });
31
- // only migrate on first open
32
- if (!options.skipMigrations) {
33
- await migrateToLatest(db, migrations);
34
- }
35
75
  console.timeEnd(label);
36
- return db;
76
+
77
+ return new SqliteExecutor(
78
+ {
79
+ query: function (sql, parameters = []) {
80
+ const stmt = internalDb.prepare(sql);
81
+ return stmt.all(...parameters) as any;
82
+ },
83
+ exec: function (sql, parameters = []) {
84
+ const stmt = internalDb.prepare(sql);
85
+ stmt.run(...parameters);
86
+ },
87
+ close: function () {
88
+ internalDb.close();
89
+ },
90
+ createTransaction: (exec) => (cb) => {
91
+ const tx = internalDb.transaction(() => {
92
+ return cb(exec);
93
+ });
94
+ return tx();
95
+ },
96
+ },
97
+ {
98
+ log: options.log ?? (() => {}),
99
+ },
100
+ );
37
101
  }
@@ -1,53 +1,43 @@
1
- import { Kysely } from 'kysely';
1
+ export const version = 1;
2
2
 
3
- export async function up(db: Kysely<any>) {
4
- await db.schema
5
- .createTable('DocumentBaseline')
6
- .ifNotExists()
7
- .addColumn('oid', 'text', (cb) => cb.primaryKey())
8
- .addColumn('snapshot', 'text')
9
- .addColumn('timestamp', 'text', (cb) => cb.notNull())
10
- .execute();
11
-
12
- await db.schema
13
- .createTable('OperationHistory')
14
- .ifNotExists()
15
- .addColumn('oid', 'text', (cb) => cb.notNull())
16
- .addColumn('timestamp', 'text', (cb) => cb.notNull())
17
- .addColumn('data', 'text', (cb) => cb.notNull())
18
- .addColumn('serverOrder', 'integer', (cb) => cb.notNull().defaultTo(0))
19
- .addColumn('replicaId', 'text', (cb) => cb.notNull())
20
- .addPrimaryKeyConstraint('OperationHistory_primaryKey', [
21
- 'replicaId',
22
- 'oid',
23
- 'timestamp',
24
- ])
25
- .execute();
26
-
27
- await db.schema
28
- .createTable('ReplicaInfo')
29
- .ifNotExists()
30
- .addColumn('id', 'text', (cb) => cb.primaryKey())
31
- .addColumn('clientId', 'text', (cb) => cb.notNull())
32
- .addColumn('lastSeenWallClockTime', 'integer')
33
- .addColumn('ackedLogicalTime', 'text')
34
- .addColumn('type', 'integer', (cb) => cb.notNull().defaultTo(0))
35
- .addColumn('ackedServerOrder', 'integer', (cb) => cb.notNull().defaultTo(0))
36
- .execute();
37
-
38
- await db.schema
39
- .createTable('FileMetadata')
40
- .ifNotExists()
41
- .addColumn('fileId', 'text', (cb) => cb.primaryKey())
42
- .addColumn('name', 'text', (cb) => cb.notNull())
43
- .addColumn('type', 'text', (cb) => cb.notNull())
44
- .addColumn('pendingDeleteAt', 'integer')
45
- .execute();
46
- }
47
-
48
- export async function down(db: Kysely<any>) {
49
- await db.schema.dropTable('DocumentBaseline').execute();
50
- await db.schema.dropTable('OperationHistory').execute();
51
- await db.schema.dropTable('ReplicaInfo').execute();
52
- await db.schema.dropTable('FileMetadata').execute();
53
- }
3
+ export const up = [
4
+ `
5
+ CREATE TABLE IF NOT EXISTS "_Migrations" (
6
+ "id" INTEGER PRIMARY KEY,
7
+ "appliedAt" TEXT NOT NULL
8
+ );`,
9
+ `
10
+ CREATE TABLE IF NOT EXISTS "DocumentBaseline" (
11
+ "oid" TEXT PRIMARY KEY,
12
+ "snapshot" TEXT,
13
+ "timestamp" TEXT NOT NULL
14
+ );
15
+ `,
16
+ `
17
+ CREATE TABLE IF NOT EXISTS "OperationHistory" (
18
+ "oid" TEXT NOT NULL,
19
+ "timestamp" TEXT NOT NULL,
20
+ "data" TEXT NOT NULL,
21
+ "serverOrder" INTEGER NOT NULL DEFAULT 0,
22
+ "replicaId" TEXT NOT NULL,
23
+ PRIMARY KEY ("replicaId", "oid", "timestamp")
24
+ );
25
+ `,
26
+ `
27
+ CREATE TABLE IF NOT EXISTS "ReplicaInfo" (
28
+ "id" TEXT PRIMARY KEY,
29
+ "clientId" TEXT NOT NULL,
30
+ "lastSeenWallClockTime" INTEGER,
31
+ "ackedLogicalTime" TEXT,
32
+ "type" INTEGER NOT NULL DEFAULT 0,
33
+ "ackedServerOrder" INTEGER NOT NULL DEFAULT 0
34
+ );`,
35
+ `
36
+ CREATE TABLE IF NOT EXISTS "FileMetadata" (
37
+ "fileId" TEXT PRIMARY KEY,
38
+ "name" TEXT NOT NULL,
39
+ "type" TEXT NOT NULL,
40
+ "pendingDeleteAt" INTEGER
41
+ );
42
+ `,
43
+ ];
@@ -1,20 +1,6 @@
1
- import { Kysely } from 'kysely';
1
+ export const version = 2;
2
2
 
3
- export async function up(db: Kysely<any>) {
4
- // add authz column to OperationHistory and DocumentBaseline
5
- await db.schema
6
- .alterTable('OperationHistory')
7
- .addColumn('authz', 'text', (cb) => cb.defaultTo(null))
8
- .execute();
9
-
10
- await db.schema
11
- .alterTable('DocumentBaseline')
12
- .addColumn('authz', 'text', (cb) => cb.defaultTo(null))
13
- .execute();
14
- }
15
-
16
- export async function down(db: Kysely<any>) {
17
- await db.schema.alterTable('OperationHistory').dropColumn('authz').execute();
18
-
19
- await db.schema.alterTable('DocumentBaseline').dropColumn('authz').execute();
20
- }
3
+ export const up = [
4
+ `ALTER TABLE "OperationHistory" ADD COLUMN "authz" TEXT DEFAULT NULL;`,
5
+ `ALTER TABLE "DocumentBaseline" ADD COLUMN "authz" TEXT DEFAULT NULL;`,
6
+ ];
@@ -1,4 +1,69 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { SqliteExecutor } from './database.js';
1
3
  import * as v1 from './migrations/v1.js';
2
4
  import * as v2 from './migrations/v2.js';
3
5
 
4
- export default { v1, v2 };
6
+ export function getMigrationVersion(run: SqliteExecutor['query']) {
7
+ try {
8
+ const result = run(
9
+ `SELECT id FROM _Migrations
10
+ ORDER BY id DESC
11
+ LIMIT 1;`,
12
+ );
13
+ const value = result[0]?.id ?? 0;
14
+ return value;
15
+ } catch (e) {
16
+ if (
17
+ e instanceof Error &&
18
+ e.message.includes('no such table: _Migrations')
19
+ ) {
20
+ return 0;
21
+ }
22
+ throw e;
23
+ }
24
+ }
25
+
26
+ export function updateMigrationVersion(
27
+ run: SqliteExecutor['exec'],
28
+ version: number,
29
+ ) {
30
+ run(`INSERT INTO _Migrations (id, appliedAt) VALUES (?, ?)`, [
31
+ version,
32
+ Date.now(),
33
+ ]);
34
+ }
35
+
36
+ export function migrateToLatest(
37
+ e: Pick<SqliteExecutor, 'exec' | 'query' | 'migrated'>,
38
+ log: Logger,
39
+ ) {
40
+ const allMigrations = [v1, v2];
41
+
42
+ const currentVersion = getMigrationVersion(e.query);
43
+
44
+ const pendingMigrations = allMigrations.slice(currentVersion);
45
+ if (pendingMigrations.length === 0) {
46
+ e.migrated = true;
47
+ return;
48
+ }
49
+ log('info', `Applying ${pendingMigrations.length} pending migrations...`);
50
+ for (const migration of pendingMigrations) {
51
+ for (const step of migration.up) {
52
+ try {
53
+ e.exec(step);
54
+ } catch (err) {
55
+ log('error', `Error applying migration v${migration.version}:`, err);
56
+ log('error', `SQL:`, step);
57
+ throw err;
58
+ }
59
+ }
60
+ log('info', `Applied migration v${migration.version}`);
61
+ }
62
+
63
+ updateMigrationVersion(
64
+ e.exec,
65
+ allMigrations[allMigrations.length - 1].version,
66
+ );
67
+
68
+ e.migrated = true;
69
+ }
@@ -1,81 +1,82 @@
1
- import Database from 'better-sqlite3';
2
- import { Kysely, SqliteDialect } from 'kysely';
3
- import { StorageFactory } from '../Storage.js';
1
+ import { existsSync, mkdirSync } from 'fs';
2
+ import {
3
+ defaultFileExpirationDays,
4
+ defaultReplicaTruancyMinutes,
5
+ } from '../../defaults.js';
6
+ import { Logger } from '../../logger.js';
7
+ import { Storage, StorageFactory, StorageOptions } from '../Storage.js';
8
+ import { Databases } from './Databases.js';
4
9
  import { SqlBaselines } from './SqlBaselines.js';
10
+ import { SqlFileMetadata } from './SqlFileMetadata.js';
5
11
  import { SqlOperations } from './SqlOperations.js';
6
12
  import { SqlReplicas } from './SqlReplicas.js';
7
- import { Database as DatabaseTypes } from './tables.js';
8
- import { SqlFileMetadata } from './SqlFileMetadata.js';
9
- import { migrateToLatest } from '@a-type/kysely';
10
- import migrations from './migrations.js';
11
- import { Databases } from './Databases.js';
12
- import { transferToShards } from './transfer.js';
13
- import { existsSync, mkdirSync, readdirSync } from 'fs';
13
+ import { SqliteExecutor } from './database.js';
14
14
 
15
15
  export const sqlShardStorage = ({
16
16
  databasesDirectory,
17
- transferFromUnifiedDatabaseFile,
18
17
  disableWal,
19
18
  closeTimeout,
19
+ fileDeleteExpirationDays = defaultFileExpirationDays,
20
+ replicaTruancyMinutes = defaultReplicaTruancyMinutes,
21
+ log,
20
22
  }: {
21
23
  databasesDirectory: string;
22
- transferFromUnifiedDatabaseFile?: string;
23
24
  disableWal?: boolean;
24
25
  closeTimeout?: number;
25
- }): StorageFactory => {
26
- let ready = Promise.resolve<void>(undefined);
26
+ databases?: Databases;
27
+ log?: Logger;
28
+ } & StorageOptions): StorageFactory => {
27
29
  if (databasesDirectory !== ':memory:' && !existsSync(databasesDirectory)) {
28
30
  mkdirSync(databasesDirectory);
29
31
  console.info(`Created databases directory: ${databasesDirectory}`);
30
32
  }
31
- if (transferFromUnifiedDatabaseFile) {
32
- // check if directory is empty
33
- const files =
34
- databasesDirectory === ':memory:' ? [] : readdirSync(databasesDirectory);
35
- if (files.length > 0) {
36
- console.error(
37
- `Cannot transfer from unified database to non-empty directory: ${databasesDirectory}. This might mean the transfer has already happened and you're free to turn off the transferFromUnifiedDatabaseFile option.`,
38
- );
39
- } else {
40
- ready = transferToShards({
41
- file: transferFromUnifiedDatabaseFile,
42
- directory: databasesDirectory,
43
- }).then();
44
- }
45
- }
46
33
  const dbs = new Databases({
47
34
  directory: databasesDirectory,
48
35
  disableWal,
49
36
  closeTimeout,
37
+ log,
50
38
  });
51
- return (options) => {
52
- const baselines = new SqlBaselines(dbs, 'sqlite');
53
- const operations = new SqlOperations(dbs, 'sqlite');
54
- const replicas = new SqlReplicas(
55
- dbs,
39
+ return async (libraryId) => {
40
+ const db = await dbs.get(libraryId);
41
+ return new SqlShardStorage(db, {
42
+ fileDeleteExpirationDays,
43
+ replicaTruancyMinutes,
44
+ libraryId,
45
+ });
46
+ };
47
+ };
48
+
49
+ export class SqlShardStorage implements Storage {
50
+ baselines;
51
+ operations;
52
+ replicas;
53
+ fileMetadata;
54
+
55
+ #open = true;
56
+ get open() {
57
+ return this.#open;
58
+ }
59
+
60
+ constructor(
61
+ private db: SqliteExecutor,
62
+ options: Required<StorageOptions> & { libraryId: string },
63
+ ) {
64
+ this.baselines = new SqlBaselines(db, options.libraryId);
65
+ this.operations = new SqlOperations(db, options.libraryId);
66
+ this.replicas = new SqlReplicas(
67
+ db,
68
+ options.libraryId,
56
69
  options.replicaTruancyMinutes,
57
- 'sqlite',
58
70
  );
59
- const fileMetadata = new SqlFileMetadata(
60
- dbs,
71
+ this.fileMetadata = new SqlFileMetadata(
72
+ db,
73
+ options.libraryId,
61
74
  options.fileDeleteExpirationDays,
62
- 'sqlite',
63
75
  );
64
- const close = async () => {
65
- storage.open = false;
66
- await dbs.destroy();
67
- };
68
- const storage = {
69
- baselines,
70
- operations,
71
- replicas,
72
- fileMetadata,
73
- close,
74
- open: false,
75
- ready: ready.then(() => {
76
- storage.open = true;
77
- }),
78
- };
79
- return storage;
76
+ }
77
+
78
+ close = async () => {
79
+ this.#open = false;
80
+ await this.db.close();
80
81
  };
81
- };
82
+ }
@@ -1,11 +1,11 @@
1
1
  import { ReplicaType } from '@verdant-web/common';
2
- import { Generated, Selectable } from 'kysely';
3
2
 
4
3
  export interface Database {
5
4
  OperationHistory: OperationHistoryTable;
6
5
  DocumentBaseline: DocumentBaselineTable;
7
6
  ReplicaInfo: ReplicaInfoTable;
8
7
  FileMetadata: FileMetadataTable;
8
+ _Migrations: { id: number; appliedAt: string };
9
9
  }
10
10
 
11
11
  export interface OperationHistoryTable {
@@ -16,7 +16,7 @@ export interface OperationHistoryTable {
16
16
  replicaId: string;
17
17
  authz: string | null;
18
18
  }
19
- export type OperationHistoryRow = Selectable<OperationHistoryTable>;
19
+ export type OperationHistoryRow = OperationHistoryTable;
20
20
 
21
21
  export interface DocumentBaselineTable {
22
22
  oid: string;
@@ -24,7 +24,7 @@ export interface DocumentBaselineTable {
24
24
  timestamp: string;
25
25
  authz: string | null;
26
26
  }
27
- export type DocumentBaselineRow = Selectable<DocumentBaselineTable>;
27
+ export type DocumentBaselineRow = DocumentBaselineTable;
28
28
 
29
29
  export interface ReplicaInfoTable {
30
30
  id: string;
@@ -32,10 +32,10 @@ export interface ReplicaInfoTable {
32
32
  lastSeenWallClockTime: number | null;
33
33
  ackedLogicalTime: string | null;
34
34
  type: ReplicaType;
35
- ackedServerOrder: Generated<number>;
35
+ ackedServerOrder: number;
36
36
  }
37
37
 
38
- export type ReplicaInfoRow = Selectable<ReplicaInfoTable>;
38
+ export type ReplicaInfoRow = ReplicaInfoTable;
39
39
 
40
40
  export interface FileMetadataTable {
41
41
  fileId: string;
@@ -44,4 +44,4 @@ export interface FileMetadataTable {
44
44
  pendingDeleteAt: number | null;
45
45
  }
46
46
 
47
- export type FileMetadataRow = Selectable<FileMetadataTable>;
47
+ export type FileMetadataRow = FileMetadataTable;
package/src/types.ts CHANGED
@@ -30,7 +30,7 @@ export interface StoredReplicaInfo extends ReplicaInfo {
30
30
  ackedServerOrder: number;
31
31
  }
32
32
 
33
- export type LibraryInfo<Profile = unknown> = {
33
+ export type LibraryInfo = {
34
34
  id: string;
35
35
  replicas: {
36
36
  id: string;
@@ -38,7 +38,6 @@ export type LibraryInfo<Profile = unknown> = {
38
38
  ackedServerOrder: number;
39
39
  type: ReplicaType;
40
40
  truant: boolean;
41
- profile: Profile;
42
41
  }[];
43
42
  latestServerOrder: number;
44
43
  operationsCount: number;
@@ -1,19 +0,0 @@
1
- import { ServerMessage } from '@verdant-web/common';
2
- import { IncomingMessage, ServerResponse } from 'http';
3
- import { WebSocket } from 'ws';
4
- import { TokenInfo } from './TokenVerifier.js';
5
- export declare class ClientConnectionManager {
6
- private readonly connections;
7
- private getLibraryConnections;
8
- addSocket: (libraryId: string, key: string, socket: WebSocket, info: TokenInfo) => void;
9
- addRequest: (libraryId: string, key: string, request: IncomingMessage, response: ServerResponse, info: TokenInfo) => () => void;
10
- addFetch: (libraryId: string, key: string, req: Request, info: TokenInfo) => (() => Response);
11
- remove: (libraryId: string, key: string) => void;
12
- respond: (libraryId: string, key: string, message: ServerMessage) => void;
13
- /**
14
- * Broadcasts a message to all live-connected clients in a library.
15
- * This does not reach push/pull clients.
16
- */
17
- broadcast: (libraryId: string, message: ServerMessage, omitKeys?: string[]) => void;
18
- disconnectAll: (libraryId: string) => void;
19
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"ClientConnection.js","sourceRoot":"","sources":["../../src/ClientConnection.ts"],"names":[],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- import { ServerMessage } from '@verdant-web/common';
2
- export interface MessageSender {
3
- broadcast(libraryId: string, message: ServerMessage, omitKeys?: string[]): void;
4
- send(libraryId: string, clientKey: string, message: ServerMessage): void;
5
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"MessageSender.js","sourceRoot":"","sources":["../../src/MessageSender.ts"],"names":[],"mappings":""}
@@ -1,18 +0,0 @@
1
- import { UserInfo, UserInfoUpdate } from '@verdant-web/common';
2
- import EventEmitter from 'events';
3
- import { UserProfiles } from './Profiles.js';
4
- /**
5
- * Stores client presence in-memory for connected
6
- * clients
7
- */
8
- export declare class Presence extends EventEmitter {
9
- private profiles;
10
- private presences;
11
- constructor(profiles: UserProfiles<any>);
12
- private getLibrary;
13
- set: (libraryId: string, userId: string, userInfo: UserInfoUpdate) => Promise<UserInfo<any, any>>;
14
- get: (libraryId: string, userId: string) => UserInfo<any, any> | undefined;
15
- removeReplica: (libraryId: string, replicaId: string) => void;
16
- all: (libraryId: string) => Record<string, UserInfo<any, any>>;
17
- clear: (libraryId: string) => void;
18
- }
@@ -1,65 +0,0 @@
1
- import { initialInternalPresence, } from '@verdant-web/common';
2
- import EventEmitter from 'events';
3
- /**
4
- * Stores client presence in-memory for connected
5
- * clients
6
- */
7
- export class Presence extends EventEmitter {
8
- constructor(profiles) {
9
- super();
10
- this.profiles = profiles;
11
- this.presences = new Map();
12
- this.getLibrary = (libraryId) => {
13
- let map = this.presences.get(libraryId);
14
- if (!map) {
15
- map = {
16
- presences: {},
17
- replicaToUser: {},
18
- userToReplica: {},
19
- };
20
- this.presences.set(libraryId, map);
21
- }
22
- return map;
23
- };
24
- this.set = async (libraryId, userId, userInfo) => {
25
- const lib = this.getLibrary(libraryId);
26
- if (!lib.presences[userId]) {
27
- lib.presences[userId] = Object.assign(Object.assign({}, userInfo), { profile: await this.profiles.get(userId), internal: userInfo.internal || initialInternalPresence });
28
- }
29
- else {
30
- Object.assign(lib.presences[userId], userInfo);
31
- }
32
- lib.replicaToUser[userInfo.replicaId] = userId;
33
- lib.userToReplica[userId] = lib.userToReplica[userId] || new Set();
34
- lib.userToReplica[userId].add(userInfo.replicaId);
35
- return lib.presences[userId];
36
- };
37
- this.get = (libraryId, userId) => {
38
- var _a;
39
- return (_a = this.presences.get(libraryId)) === null || _a === void 0 ? void 0 : _a.presences[userId];
40
- };
41
- this.removeReplica = (libraryId, replicaId) => {
42
- const lib = this.getLibrary(libraryId);
43
- const userId = lib.replicaToUser[replicaId];
44
- if (!userId)
45
- return;
46
- lib.userToReplica[userId].delete(replicaId);
47
- if (lib.userToReplica[userId].size === 0) {
48
- delete lib.presences[userId];
49
- this.emit('lost', libraryId, replicaId, userId);
50
- // memory cleanup
51
- if (Object.keys(lib.presences).length === 0) {
52
- this.clear(libraryId);
53
- }
54
- }
55
- };
56
- this.all = (libraryId) => {
57
- var _a;
58
- return ((_a = this.presences.get(libraryId)) === null || _a === void 0 ? void 0 : _a.presences) || {};
59
- };
60
- this.clear = (libraryId) => {
61
- this.presences.delete(libraryId);
62
- };
63
- }
64
- }
65
- //# sourceMappingURL=Presence.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Presence.js","sourceRoot":"","sources":["../../src/Presence.ts"],"names":[],"mappings":"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"}
@@ -1,17 +0,0 @@
1
- import { EventSubscriber } from '@verdant-web/common';
2
- /**
3
- * A set of timers per-replica which are
4
- * refreshed whenever the replica sends
5
- * a message to the server. If a timer expires,
6
- * the replica is removed from presence. This
7
- * is a failsafe for socket-based replicas but
8
- * essential for pull-based replicas.
9
- */
10
- export declare class ReplicaKeepaliveTimers extends EventSubscriber<{
11
- lost: (libraryId: string, replicaId: string) => void;
12
- }> {
13
- private timeout;
14
- private timers;
15
- constructor(timeout?: number);
16
- refresh: (libraryId: string, replicaId: string) => void;
17
- }