@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,17 +1,10 @@
1
- import { EventSubscriber, ReplicaType, applyOperations, isFileRef, isObjectRef, rewriteAuthzOriginator, } from '@verdant-web/common';
2
- import { Presence } from './Presence.js';
3
- export class ServerLibrary extends EventSubscriber {
4
- constructor({ storage, sender, profiles, log = () => { }, disableRebasing, fileStorage, }) {
5
- super();
6
- /**
7
- * Validates a user's access to a replica. If the replica does not
8
- * exist, a user may create it. But if it does exist, its user (clientId)
9
- * must match the user making the request.
10
- */
1
+ import { applyOperations, isFileRef, isObjectRef, ReplicaType, rewriteAuthzOriginator, } from '@verdant-web/common';
2
+ export class Library {
3
+ constructor({ storage, sender, log = () => { }, disableRebasing, fileStorage, events, id, presence, }) {
11
4
  this.validateReplicaAccess = async (replicaId, clientKey, info) => {
12
- const replica = await this.storage.replicas.get(info.libraryId, replicaId);
5
+ const replica = await this.storage.replicas.get(replicaId);
13
6
  if (replica && replica.clientId !== info.userId) {
14
- this.sender.send(info.libraryId, clientKey, {
7
+ this.sender.respond(clientKey, {
15
8
  type: 'forbidden',
16
9
  });
17
10
  return false;
@@ -22,8 +15,11 @@ export class ServerLibrary extends EventSubscriber {
22
15
  return (info.type !== ReplicaType.ReadOnlyPull &&
23
16
  info.type !== ReplicaType.ReadOnlyRealtime);
24
17
  };
25
- this.receive = async (message, clientKey, info) => {
26
- await this.storage.ready;
18
+ this.handleMessage = async (message, clientKey, info) => {
19
+ if (message.type === 'heartbeat') {
20
+ this.handleHeartbeat(message, clientKey, info);
21
+ return;
22
+ }
27
23
  const allowed = await this.validateReplicaAccess(message.replicaId, clientKey, info);
28
24
  if (!allowed)
29
25
  return;
@@ -37,14 +33,11 @@ export class ServerLibrary extends EventSubscriber {
37
33
  case 'ack':
38
34
  await this.handleAck(message, clientKey, info);
39
35
  break;
40
- case 'heartbeat':
41
- await this.handleHeartbeat(message, clientKey, info);
42
- break;
43
36
  case 'presence-update':
44
37
  await this.handlePresenceUpdate(message, clientKey, info);
45
38
  break;
46
39
  default:
47
- this.log('Unknown message type', message.type);
40
+ this.log('error', 'Unknown message type', message.type);
48
41
  break;
49
42
  }
50
43
  // a bit fragile - skipping presence-update since currently
@@ -52,17 +45,28 @@ export class ServerLibrary extends EventSubscriber {
52
45
  // sees a recent last seen for the replica even if it was
53
46
  // truant before the presence-update.
54
47
  if (message.type !== 'presence-update') {
55
- await this.storage.replicas.updateLastSeen(info.libraryId, message.replicaId);
48
+ await this.storage.replicas.updateLastSeen(message.replicaId);
56
49
  }
57
50
  };
58
- this.remove = (libraryId, replicaId) => {
59
- this.presences.removeReplica(libraryId, replicaId);
51
+ this.rebroadcastOperations = async (libraryId, clientKey, replicaId, ops, baselines) => {
52
+ var _a, _b;
53
+ if (ops.length === 0 && !(baselines === null || baselines === void 0 ? void 0 : baselines.length))
54
+ return;
55
+ this.log('info', 'Rebroadcasting', ops.length, 'operations', (_a = baselines === null || baselines === void 0 ? void 0 : baselines.length) !== null && _a !== void 0 ? _a : 0, 'baselines');
56
+ this.sender.broadcast({
57
+ type: 'op-re',
58
+ operations: ops,
59
+ baselines,
60
+ replicaId,
61
+ globalAckTimestamp: (_b = (await this.storage.replicas.getGlobalAck())) !== null && _b !== void 0 ? _b : undefined,
62
+ }, [clientKey]);
60
63
  };
61
64
  this.handleOperation = async (message, clientKey, info) => {
65
+ var _a;
62
66
  if (!message.operations.length)
63
67
  return;
64
68
  if (!this.hasWriteAccess(info)) {
65
- this.sender.send(info.libraryId, clientKey, {
69
+ this.sender.respond(clientKey, {
66
70
  type: 'forbidden',
67
71
  });
68
72
  return;
@@ -71,20 +75,20 @@ export class ServerLibrary extends EventSubscriber {
71
75
  this.rebroadcastOperations(info.libraryId, clientKey, message.replicaId, message.operations);
72
76
  // insert patches into history
73
77
  // first, record the replica's serverOrder before insertion.
74
- const replicaInfo = await this.storage.replicas.getOrCreate(info.libraryId, message.replicaId, {
78
+ const replicaInfo = await this.storage.replicas.getOrCreate(message.replicaId, {
75
79
  userId: info.userId,
76
80
  type: info.type,
77
81
  });
78
- const newServerOrder = await this.storage.operations.insertAll(info.libraryId, message.replicaId, message.operations);
82
+ const newServerOrder = await this.storage.operations.insertAll(message.replicaId, message.operations);
79
83
  this.preemptiveUpdateServerOrder(replicaInfo.replicaInfo, newServerOrder, message.operations.length);
80
- this.enqueueRebase(info.libraryId);
84
+ this.enqueueRebase();
81
85
  // tell sender we got and processed their operation
82
- this.sender.send(info.libraryId, clientKey, {
86
+ this.sender.respond(clientKey, {
83
87
  type: 'server-ack',
84
88
  timestamp: message.timestamp,
85
89
  });
86
90
  await this.updateHighwater(message.replicaId, message.operations[message.operations.length - 1].timestamp, info);
87
- this.emit(`changes`, info, message.operations, []);
91
+ (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(`changes`, info, message.operations, []);
88
92
  };
89
93
  /**
90
94
  * If a replica inserts operations when it's already
@@ -94,31 +98,18 @@ export class ServerLibrary extends EventSubscriber {
94
98
  this.preemptiveUpdateServerOrder = async (replicaInfo, newServerOrder, insertedOperationsCount) => {
95
99
  if (newServerOrder - replicaInfo.ackedServerOrder ===
96
100
  insertedOperationsCount) {
97
- await this.storage.replicas.updateAckedServerOrder(replicaInfo.libraryId, replicaInfo.id, newServerOrder);
101
+ await this.storage.replicas.updateAckedServerOrder(replicaInfo.id, newServerOrder);
98
102
  this.log('debug', 'Preemptively updated ackedServerOrder for', replicaInfo.id, 'to', newServerOrder);
99
103
  }
100
104
  };
101
- this.rebroadcastOperations = async (libraryId, clientKey, replicaId, ops, baselines) => {
102
- var _a, _b;
103
- if (ops.length === 0 && !(baselines === null || baselines === void 0 ? void 0 : baselines.length))
104
- return;
105
- this.log('info', 'Rebroadcasting', ops.length, 'operations', (_a = baselines === null || baselines === void 0 ? void 0 : baselines.length) !== null && _a !== void 0 ? _a : 0, 'baselines');
106
- this.sender.broadcast(libraryId, {
107
- type: 'op-re',
108
- operations: ops,
109
- baselines,
110
- replicaId,
111
- globalAckTimestamp: (_b = (await this.storage.replicas.getGlobalAck(libraryId))) !== null && _b !== void 0 ? _b : undefined,
112
- }, [clientKey]);
113
- };
114
105
  this.handleSync = async (message, clientKey, info) => {
115
- var _a;
106
+ var _a, _b;
116
107
  const replicaId = message.replicaId;
117
108
  // TODO: is this the right way to do this? should it just ignore
118
109
  // read-only changes?
119
110
  if (!this.hasWriteAccess(info) &&
120
111
  (message.operations.length > 0 || message.baselines.length > 0)) {
121
- this.sender.send(info.libraryId, clientKey, {
112
+ this.sender.respond(clientKey, {
122
113
  type: 'forbidden',
123
114
  });
124
115
  this.log('warn', 'sync with changes from read-only replica', replicaId);
@@ -126,22 +117,22 @@ export class ServerLibrary extends EventSubscriber {
126
117
  }
127
118
  if (message.resyncAll) {
128
119
  // forget our local understanding of the replica and reset it
129
- await this.storage.replicas.delete(info.libraryId, replicaId);
120
+ await this.storage.replicas.delete(replicaId);
130
121
  }
131
- const { status, replicaInfo: clientReplicaInfo } = await this.storage.replicas.getOrCreate(info.libraryId, replicaId, info);
122
+ const { status, replicaInfo: clientReplicaInfo } = await this.storage.replicas.getOrCreate(replicaId, info);
132
123
  this.log('info', `Sync from ${replicaId} (user: ${info.userId}) [ackedServerOrder: ${clientReplicaInfo.ackedServerOrder}, status: ${status}}]`);
133
124
  // for truant replicas -- if their ackedServerOrder is up-to-date, that's fine,
134
125
  // restore them.
135
126
  let overrideTruant = false;
136
127
  if (status === 'truant') {
137
- const latestServerOrder = await this.storage.operations.getLatestServerOrder(info.libraryId);
128
+ const latestServerOrder = await this.storage.operations.getLatestServerOrder();
138
129
  if (clientReplicaInfo.ackedServerOrder >= latestServerOrder) {
139
130
  overrideTruant = true;
140
131
  this.log('debug', 'Overriding truant reset; truant replica has up-to-date server order');
141
132
  }
142
133
  }
143
134
  // lookup operations after the last ack the client gave us
144
- const ops = await this.storage.operations.getAfterServerOrder(info.libraryId, clientReplicaInfo.ackedServerOrder);
135
+ const ops = await this.storage.operations.getAfterServerOrder(clientReplicaInfo.ackedServerOrder);
145
136
  // new, unseen replicas should reset their existing storage
146
137
  // to that of the server when joining a library.
147
138
  // truant replicas should also reset their storage.
@@ -151,7 +142,7 @@ export class ServerLibrary extends EventSubscriber {
151
142
  // replica will generate the same baseline locally, so it
152
143
  // doesn't need to receive it from the server.
153
144
  const baselines = replicaShouldReset
154
- ? await this.storage.baselines.getAll(info.libraryId)
145
+ ? await this.storage.baselines.getAll()
155
146
  : [];
156
147
  // We detect that a library is new by checking if it has any history.
157
148
  // If there are no existing operations or baselines (and the requested
@@ -182,7 +173,7 @@ export class ServerLibrary extends EventSubscriber {
182
173
  // sync after replica has updated us with full history.
183
174
  if (isEmptyLibrary && status === 'new' && message.since !== null) {
184
175
  this.log('info', 'Detected local data is incomplete, requesting full history from replica', replicaId);
185
- this.sender.send(info.libraryId, clientKey, {
176
+ this.sender.respond(clientKey, {
186
177
  type: 'need-since',
187
178
  since: null,
188
179
  });
@@ -202,11 +193,11 @@ export class ServerLibrary extends EventSubscriber {
202
193
  // we are not overwriting the replica's data
203
194
  if (!overwriteReplicaData) {
204
195
  // store all incoming operations and baselines
205
- await this.storage.baselines.insertAll(info.libraryId, message.baselines);
196
+ await this.storage.baselines.insertAll(message.baselines);
206
197
  this.log('debug', 'Storing', message.baselines.length, 'baselines and', message.operations.length, 'operations');
207
- const newServerOrder = await this.storage.operations.insertAll(info.libraryId, replicaId, message.operations);
198
+ const newServerOrder = await this.storage.operations.insertAll(replicaId, message.operations);
208
199
  this.preemptiveUpdateServerOrder(clientReplicaInfo, newServerOrder, message.operations.length);
209
- await this.storage.replicas.updateAcknowledgedLogicalTime(info.libraryId, replicaId, message.timestamp);
200
+ await this.storage.replicas.updateAcknowledgedLogicalTime(replicaId, message.timestamp);
210
201
  // will include the new global ack
211
202
  this.rebroadcastOperations(info.libraryId, clientKey, message.replicaId, message.operations,
212
203
  // we only need to broadcast baselines if we just initialized
@@ -215,7 +206,7 @@ export class ServerLibrary extends EventSubscriber {
215
206
  // rebasing.test.ts, for example.
216
207
  isEmptyLibrary ? message.baselines : undefined);
217
208
  if (message.operations.length || message.baselines.length) {
218
- this.emit(`changes`, info, message.operations, message.baselines);
209
+ (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(`changes`, info, message.operations, message.baselines);
219
210
  }
220
211
  }
221
212
  if (status === 'truant') {
@@ -229,22 +220,28 @@ export class ServerLibrary extends EventSubscriber {
229
220
  ? this.createAckNonce(serverOrderToAck)
230
221
  : undefined;
231
222
  // respond to client
232
- this.log('info', 'Sending sync response with', ops.length, 'operations and', baselines.length, 'baselines');
223
+ this.log('info', 'Sending sync response with', ops.length, 'operations and', baselines.length, 'baselines', 'to client key', clientKey);
233
224
  // mutating baselines/operations to remove extra data -
234
225
  // mutating just to save on allocations here.
235
- this.sender.send(info.libraryId, clientKey, {
236
- type: 'sync-resp',
237
- operations: this.removeOperationExtras(ops),
238
- baselines: this.removeBaselineExtras(baselines),
239
- globalAckTimestamp: (_a = (await this.storage.replicas.getGlobalAck(info.libraryId))) !== null && _a !== void 0 ? _a : undefined,
240
- peerPresence: this.presences.all(info.libraryId),
241
- // only request the client to overwrite local data if a reset is requested
242
- // and there is data to overwrite it. otherwise the client may still
243
- // send its own history to us.
244
- overwriteLocalData: overwriteReplicaData,
245
- ackedTimestamp: message.timestamp,
246
- ackThisNonce,
247
- });
226
+ try {
227
+ this.sender.respond(clientKey, {
228
+ type: 'sync-resp',
229
+ operations: this.removeOperationExtras(ops),
230
+ baselines: this.removeBaselineExtras(baselines),
231
+ globalAckTimestamp: (_b = (await this.storage.replicas.getGlobalAck())) !== null && _b !== void 0 ? _b : undefined,
232
+ peerPresence: this.presence.all(),
233
+ // only request the client to overwrite local data if a reset is requested
234
+ // and there is data to overwrite it. otherwise the client may still
235
+ // send its own history to us.
236
+ overwriteLocalData: overwriteReplicaData,
237
+ ackedTimestamp: message.timestamp,
238
+ ackThisNonce,
239
+ });
240
+ }
241
+ catch (err) {
242
+ this.log('error', err);
243
+ throw err;
244
+ }
248
245
  };
249
246
  this.createAckNonce = (serverOrder) => {
250
247
  return Buffer.from(JSON.stringify(serverOrder)).toString('base64');
@@ -252,19 +249,19 @@ export class ServerLibrary extends EventSubscriber {
252
249
  this.handleAck = async (message, clientKey, info) => {
253
250
  if (message.nonce) {
254
251
  const decodedNonce = JSON.parse(Buffer.from(message.nonce, 'base64').toString('utf8'));
255
- this.log('Ack from', message.replicaId, '(user', info.userId, ')', 'with order', decodedNonce);
252
+ this.log('debug', 'Ack from', message.replicaId, '(user', info.userId, ')', 'with order', decodedNonce);
256
253
  if (typeof decodedNonce !== 'number') {
257
254
  this.log('error', 'Invalid nonce', message.nonce);
258
255
  return;
259
256
  }
260
257
  const replicaId = message.replicaId;
261
- await this.storage.replicas.updateAckedServerOrder(info.libraryId, replicaId, decodedNonce);
258
+ await this.storage.replicas.updateAckedServerOrder(replicaId, decodedNonce);
262
259
  }
263
260
  else if (message.timestamp) {
264
- await this.storage.replicas.acknowledgeOperation(info.libraryId, message.replicaId, message.timestamp);
265
- const globalAck = await this.storage.replicas.getGlobalAck(info.libraryId);
261
+ await this.storage.replicas.acknowledgeOperation(message.replicaId, message.timestamp);
262
+ const globalAck = await this.storage.replicas.getGlobalAck();
266
263
  if (globalAck) {
267
- this.sender.broadcast(info.libraryId, {
264
+ this.sender.broadcast({
268
265
  type: 'global-ack',
269
266
  timestamp: globalAck,
270
267
  });
@@ -272,24 +269,24 @@ export class ServerLibrary extends EventSubscriber {
272
269
  }
273
270
  };
274
271
  this.updateHighwater = async (replicaId, timestamp, info, ignoreClientKeys = []) => {
275
- await this.storage.replicas.updateAcknowledgedLogicalTime(info.libraryId, replicaId, timestamp);
276
- const newGlobalAck = await this.storage.replicas.getGlobalAck(info.libraryId);
272
+ await this.storage.replicas.updateAcknowledgedLogicalTime(replicaId, timestamp);
273
+ const newGlobalAck = await this.storage.replicas.getGlobalAck();
277
274
  if (newGlobalAck) {
278
- this.sender.broadcast(info.libraryId, {
275
+ this.sender.broadcast({
279
276
  type: 'global-ack',
280
277
  timestamp: newGlobalAck,
281
278
  }, ignoreClientKeys);
282
279
  }
283
280
  };
284
281
  this.pendingRebaseTimeout = null;
285
- this.enqueueRebase = (libraryId) => {
282
+ this.enqueueRebase = () => {
286
283
  if (this.disableRebasing)
287
284
  return;
288
285
  if (!this.pendingRebaseTimeout) {
289
- setTimeout(() => this.rebase(libraryId), 0);
286
+ setTimeout(() => this.rebase(), 0);
290
287
  }
291
288
  };
292
- this.rebase = async (libraryId) => {
289
+ this.rebase = async () => {
293
290
  if (this.disableRebasing)
294
291
  return;
295
292
  this.log('debug', 'Performing rebase check');
@@ -308,9 +305,9 @@ export class ServerLibrary extends EventSubscriber {
308
305
  // for global ack, to determine consensus, also allow
309
306
  // for all actively connected replicas to ack regardless of their
310
307
  // type.
311
- const activeReplicaIds = Object.values(this.presences.all(libraryId)).map((p) => p.replicaId);
312
- const globalAck = await this.storage.replicas.getGlobalAck(libraryId, activeReplicaIds);
313
- const globalServerOrder = await this.storage.replicas.getEarliestAckedServerOrder(libraryId);
308
+ const activeReplicaIds = Object.values(this.presence.all()).map((p) => p.replicaId);
309
+ const globalAck = await this.storage.replicas.getGlobalAck(activeReplicaIds);
310
+ const globalServerOrder = await this.storage.replicas.getEarliestAckedServerOrder();
314
311
  if (!globalAck) {
315
312
  this.log('debug', 'No global ack, skipping rebase');
316
313
  return;
@@ -320,7 +317,7 @@ export class ServerLibrary extends EventSubscriber {
320
317
  return;
321
318
  }
322
319
  // these are in forward chronological order
323
- const ops = await this.storage.operations.getBeforeServerOrder(libraryId, globalServerOrder);
320
+ const ops = await this.storage.operations.getBeforeServerOrder(globalServerOrder);
324
321
  const opsToApply = {};
325
322
  // if we encounter a sequential operation in history which does
326
323
  // not meet our conditions, we must ignore subsequent operations
@@ -338,33 +335,33 @@ export class ServerLibrary extends EventSubscriber {
338
335
  }
339
336
  const deletedRefs = new Array();
340
337
  for (const [documentId, ops] of Object.entries(opsToApply)) {
341
- this.log('Rebasing', documentId);
342
- await this.storage.baselines.applyOperations(libraryId, documentId, ops, deletedRefs);
343
- await this.storage.operations.delete(libraryId, ops);
338
+ this.log('debug', 'Rebasing', documentId);
339
+ await this.storage.baselines.applyOperations(documentId, ops, deletedRefs);
340
+ await this.storage.operations.delete(ops);
344
341
  }
345
342
  // hint to clients they can rebase too
346
- this.sender.broadcast(libraryId, {
343
+ this.sender.broadcast({
347
344
  type: 'global-ack',
348
345
  timestamp: globalAck,
349
346
  });
350
347
  // cleanup deleted files
351
348
  await Promise.all(deletedRefs
352
349
  .filter(isFileRef)
353
- .map((ref) => this.storage.fileMetadata.markPendingDelete(libraryId, ref.id)));
350
+ .map((ref) => this.storage.fileMetadata.markPendingDelete(ref.id)));
354
351
  };
355
352
  this.handleHeartbeat = (_, clientKey, info) => {
356
- this.sender.send(info.libraryId, clientKey, {
353
+ this.sender.respond(clientKey, {
357
354
  type: 'heartbeat-response',
358
355
  });
359
356
  };
360
357
  this.handlePresenceUpdate = async (message, clientKey, info) => {
361
- const updated = await this.presences.set(info.libraryId, info.userId, {
358
+ const updated = await this.presence.set(clientKey, info.userId, {
362
359
  presence: message.presence,
363
360
  internal: message.internal,
364
361
  replicaId: message.replicaId,
365
362
  id: info.userId,
366
363
  });
367
- this.sender.broadcast(info.libraryId, {
364
+ this.sender.broadcast({
368
365
  type: 'presence-changed',
369
366
  replicaId: message.replicaId,
370
367
  userInfo: updated,
@@ -372,21 +369,21 @@ export class ServerLibrary extends EventSubscriber {
372
369
  // mirror back to the replica which sent it so it has profile
373
370
  []);
374
371
  };
375
- this.onPresenceLost = async (libraryId, replicaId, userId) => {
372
+ this.onPresenceLost = async (replicaId, userId) => {
376
373
  this.log('info', 'User disconnected from all replicas:', userId);
377
- this.sender.broadcast(libraryId, {
374
+ this.sender.broadcast({
378
375
  type: 'presence-offline',
379
376
  replicaId,
380
377
  userId,
381
378
  });
382
- if (Object.keys(this.presences.all(libraryId)).length === 0) {
383
- this.log('info', `All users have disconnected from ${libraryId}`);
379
+ if (Object.keys(this.presence.all()).length === 0) {
380
+ this.log('info', `All users have disconnected`);
384
381
  // could happen - if the server is shutting down manually
385
382
  if (!this.storage.open) {
386
383
  this.log('debug', 'Database not open, skipping cleanup');
387
384
  return;
388
385
  }
389
- const pendingDelete = await this.storage.fileMetadata.getPendingDelete(libraryId);
386
+ const pendingDelete = await this.storage.fileMetadata.getPendingDelete();
390
387
  if (pendingDelete.length > 0) {
391
388
  if (!this.fileStorage) {
392
389
  throw new Error('File storage not configured, cannot delete files');
@@ -396,12 +393,11 @@ export class ServerLibrary extends EventSubscriber {
396
393
  var _a;
397
394
  try {
398
395
  await ((_a = this.fileStorage) === null || _a === void 0 ? void 0 : _a.delete({
399
- libraryId: fileInfo.libraryId,
400
396
  fileName: fileInfo.name,
401
397
  id: fileInfo.fileId,
402
398
  type: fileInfo.type,
403
399
  }));
404
- this.storage.fileMetadata.delete(libraryId, fileInfo.fileId);
400
+ await this.storage.fileMetadata.delete(fileInfo.fileId);
405
401
  }
406
402
  catch (e) {
407
403
  this.log('error', 'Failed to delete file', fileInfo.fileId, ' the file will remain in a pending delete state', e);
@@ -410,52 +406,49 @@ export class ServerLibrary extends EventSubscriber {
410
406
  }
411
407
  }
412
408
  };
413
- this.destroy = async (libraryId) => {
409
+ this.destroy = async () => {
414
410
  var _a;
415
- this.log('info', 'Destroying library', libraryId);
416
- this.presences.clear(libraryId);
411
+ this.log('info', 'Destroying library');
412
+ this.presence.clear();
417
413
  await Promise.all([
418
- this.storage.replicas.deleteAll(libraryId),
419
- this.storage.operations.deleteAll(libraryId),
420
- this.storage.baselines.deleteAll(libraryId),
414
+ this.storage.replicas.deleteAll(),
415
+ this.storage.operations.deleteAll(),
416
+ this.storage.baselines.deleteAll(),
421
417
  ]);
422
- const allFiles = await this.storage.fileMetadata.getAll(libraryId);
418
+ const allFiles = await this.storage.fileMetadata.getAll();
423
419
  if (allFiles.length > 0) {
424
- this.log('info', `Deleting ${allFiles.length} files for library ${libraryId}`, allFiles.map((f) => f.fileId).join(', '));
420
+ this.log('info', `Deleting ${allFiles.length} files`, allFiles.map((f) => f.fileId).join(', '));
425
421
  for (const fileInfo of allFiles) {
426
422
  await ((_a = this.fileStorage) === null || _a === void 0 ? void 0 : _a.delete({
427
- libraryId,
428
423
  fileName: fileInfo.name,
429
424
  id: fileInfo.fileId,
430
425
  type: fileInfo.type,
431
426
  }));
432
427
  }
433
- await this.storage.fileMetadata.deleteAll(libraryId);
428
+ await this.storage.fileMetadata.deleteAll();
434
429
  }
435
430
  };
436
- this.getPresence = (libraryId) => {
437
- return this.presences.all(libraryId);
431
+ this.getPresence = () => {
432
+ return Promise.resolve(this.presence.all());
438
433
  };
439
- this.getInfo = async (libraryId) => {
434
+ this.getInfo = async () => {
440
435
  var _a;
441
- const rawReplicas = await this.storage.replicas.getAll(libraryId);
442
- const profiles = await Promise.all(rawReplicas.map((r) => this.profiles.get(r.clientId)));
443
- const replicas = rawReplicas.map((r, index) => ({
436
+ const rawReplicas = await this.storage.replicas.getAll();
437
+ const replicas = rawReplicas.map((r) => ({
444
438
  id: r.id,
445
439
  ackedLogicalTime: r.ackedLogicalTime,
446
440
  ackedServerOrder: r.ackedServerOrder,
447
441
  type: r.type,
448
442
  truant: !!r.lastSeenWallClockTime &&
449
443
  r.lastSeenWallClockTime < this.storage.replicas.truantCutoff,
450
- profile: profiles[index],
451
444
  }));
452
445
  const data = {
453
- id: libraryId,
446
+ id: this.id,
454
447
  replicas,
455
- latestServerOrder: await this.storage.operations.getLatestServerOrder(libraryId),
456
- operationsCount: await this.storage.operations.getCount(libraryId),
457
- baselinesCount: await this.storage.baselines.getCount(libraryId),
458
- globalAck: (_a = (await this.storage.replicas.getGlobalAck(libraryId))) !== null && _a !== void 0 ? _a : null,
448
+ latestServerOrder: await this.storage.operations.getLatestServerOrder(),
449
+ operationsCount: await this.storage.operations.getCount(),
450
+ baselinesCount: await this.storage.baselines.getCount(),
451
+ globalAck: (_a = (await this.storage.replicas.getGlobalAck())) !== null && _a !== void 0 ? _a : null,
459
452
  };
460
453
  if (data.replicas.length === 0 &&
461
454
  data.operationsCount === 0 &&
@@ -464,30 +457,46 @@ export class ServerLibrary extends EventSubscriber {
464
457
  }
465
458
  return data;
466
459
  };
467
- this.evictUser = async (libraryId, userId) => {
468
- await this.storage.replicas.deleteAllForUser(libraryId, userId);
460
+ this.evictUser = async (userId) => {
461
+ await this.storage.replicas.deleteAllForUser(userId);
462
+ };
463
+ this.forceTruant = async (replicaId) => {
464
+ await this.storage.replicas.forceTruant(replicaId);
469
465
  };
470
- this.getDocumentSnapshot = (libraryId, oid) => {
471
- return this.hydrateObject(libraryId, oid);
466
+ this.getDocumentSnapshot = (oid) => {
467
+ return this.hydrateObject(oid);
472
468
  };
473
- this.getObjectSnapshot = async (libraryId, oid) => {
469
+ this.getFileInfo = async (fileId) => {
470
+ const rawFile = await this.hydrateFile(fileId);
471
+ if (rawFile) {
472
+ return Object.assign(Object.assign({}, rawFile), { libraryId: this.id });
473
+ }
474
+ else {
475
+ return null;
476
+ }
477
+ };
478
+ this.putFileInfo = async (info) => {
479
+ this.log('debug', 'Storing file info', info.id, info.fileName);
480
+ await this.storage.fileMetadata.put(info);
481
+ };
482
+ this.getObjectSnapshot = async (oid) => {
474
483
  var _a;
475
484
  const [baseline, ops] = await Promise.all([
476
- this.storage.baselines.get(libraryId, oid),
477
- this.storage.operations.getAll(libraryId, oid),
485
+ this.storage.baselines.get(oid),
486
+ this.storage.operations.getAll(oid),
478
487
  ]);
479
488
  const snapshot = applyOperations((_a = baseline === null || baseline === void 0 ? void 0 : baseline.snapshot) !== null && _a !== void 0 ? _a : undefined, ops);
480
489
  return snapshot;
481
490
  };
482
- this.hydrateObject = async (libraryId, oid) => {
483
- const snapshot = await this.getObjectSnapshot(libraryId, oid);
491
+ this.hydrateObject = async (oid) => {
492
+ const snapshot = await this.getObjectSnapshot(oid);
484
493
  if (Array.isArray(snapshot)) {
485
494
  return Promise.all(snapshot.map(async (item) => {
486
495
  if (isObjectRef(item)) {
487
- return this.hydrateObject(libraryId, item.id);
496
+ return this.hydrateObject(item.id);
488
497
  }
489
498
  else if (isFileRef(item)) {
490
- return this.hydrateFile(libraryId, item.id);
499
+ return this.hydrateFile(item.id);
491
500
  }
492
501
  else {
493
502
  return item;
@@ -498,10 +507,10 @@ export class ServerLibrary extends EventSubscriber {
498
507
  const hydrated = Object.assign({}, snapshot);
499
508
  await Promise.all(Object.entries(snapshot).map(async ([key, value]) => {
500
509
  if (isObjectRef(value)) {
501
- hydrated[key] = await this.hydrateObject(libraryId, value.id);
510
+ hydrated[key] = await this.hydrateObject(value.id);
502
511
  }
503
512
  else if (isFileRef(value)) {
504
- hydrated[key] = await this.hydrateFile(libraryId, value.id);
513
+ hydrated[key] = await this.hydrateFile(value.id);
505
514
  }
506
515
  }));
507
516
  return hydrated;
@@ -510,21 +519,20 @@ export class ServerLibrary extends EventSubscriber {
510
519
  return snapshot;
511
520
  }
512
521
  };
513
- this.hydrateFile = async (libraryId, fileId) => {
522
+ this.hydrateFile = async (fileId) => {
514
523
  var _a, _b;
515
- const data = await this.storage.fileMetadata.get(libraryId, fileId);
524
+ const data = await this.storage.fileMetadata.get(fileId);
516
525
  if (data) {
517
- const url = (_b = (_a = this.fileStorage) === null || _a === void 0 ? void 0 : _a.getUrl({
526
+ const url = (_b = (await ((_a = this.fileStorage) === null || _a === void 0 ? void 0 : _a.getUrl({
518
527
  fileName: data.name,
519
528
  id: data.fileId,
520
- libraryId,
521
529
  type: data.type,
522
- })) !== null && _b !== void 0 ? _b : null;
530
+ })))) !== null && _b !== void 0 ? _b : null;
523
531
  return {
524
- url,
525
532
  id: data.fileId,
526
533
  name: data.name,
527
534
  type: data.type,
535
+ url,
528
536
  };
529
537
  }
530
538
  else {
@@ -553,14 +561,15 @@ export class ServerLibrary extends EventSubscriber {
553
561
  }
554
562
  return operations;
555
563
  };
564
+ this.id = id;
565
+ this.storage = storage;
566
+ this.sender = sender;
556
567
  this.log = log;
557
568
  this.disableRebasing = !!disableRebasing;
558
- this.sender = sender;
559
- this.profiles = profiles;
560
- this.presences = new Presence(profiles);
561
- this.storage = storage;
562
569
  this.fileStorage = fileStorage;
563
- this.presences.on('lost', this.onPresenceLost);
570
+ this.events = events;
571
+ this.presence = presence;
572
+ this.presence.subscribe('lost', this.onPresenceLost);
564
573
  }
565
574
  }
566
- //# sourceMappingURL=ServerLibrary.js.map
575
+ //# sourceMappingURL=Library.js.map