@verdant-web/server 1.8.13 → 1.8.14-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/cjs/Baselines.d.ts +15 -15
  2. package/dist/cjs/Baselines.js +83 -83
  3. package/dist/cjs/ClientConnection.d.ts +18 -18
  4. package/dist/cjs/ClientConnection.js +105 -105
  5. package/dist/cjs/MessageSender.d.ts +5 -5
  6. package/dist/cjs/MessageSender.js +2 -2
  7. package/dist/cjs/OperationHistory.d.ts +36 -36
  8. package/dist/cjs/OperationHistory.js +120 -120
  9. package/dist/cjs/Presence.d.ts +15 -15
  10. package/dist/cjs/Presence.js +59 -59
  11. package/dist/cjs/Profiles.d.ts +10 -10
  12. package/dist/cjs/Profiles.js +19 -19
  13. package/dist/cjs/ReplicaKeepaliveTimers.d.ts +17 -17
  14. package/dist/cjs/ReplicaKeepaliveTimers.js +30 -30
  15. package/dist/cjs/Replicas.d.ts +24 -24
  16. package/dist/cjs/Replicas.js +137 -137
  17. package/dist/cjs/Server.d.ts +143 -143
  18. package/dist/cjs/Server.js +396 -396
  19. package/dist/cjs/ServerLibrary.d.ts +77 -77
  20. package/dist/cjs/ServerLibrary.js +487 -487
  21. package/dist/cjs/ServerStorage.d.ts +35 -0
  22. package/dist/cjs/ServerStorage.js +60 -0
  23. package/dist/cjs/ServerStorage.js.map +1 -0
  24. package/dist/cjs/TokenProvider.d.ts +16 -16
  25. package/dist/cjs/TokenProvider.js +26 -26
  26. package/dist/cjs/TokenVerifier.d.ts +16 -16
  27. package/dist/cjs/TokenVerifier.js +28 -28
  28. package/dist/cjs/files/FileMetadata.d.ts +28 -28
  29. package/dist/cjs/files/FileMetadata.js +46 -46
  30. package/dist/cjs/files/FileStorage.d.ts +35 -35
  31. package/dist/cjs/files/FileStorage.js +71 -71
  32. package/dist/cjs/index.d.ts +9 -9
  33. package/dist/cjs/index.js +11 -11
  34. package/dist/cjs/migrations/v0.d.ts +1 -0
  35. package/dist/cjs/migrations/v0.js +30 -0
  36. package/dist/cjs/migrations/v0.js.map +1 -0
  37. package/dist/cjs/migrations/v0.sql.d.ts +2 -2
  38. package/dist/cjs/migrations/v0.sql.js +3 -3
  39. package/dist/cjs/migrations/v1.d.ts +1 -0
  40. package/dist/cjs/migrations/v1.js +32 -0
  41. package/dist/cjs/migrations/v1.js.map +1 -0
  42. package/dist/cjs/migrations/v1.sql.d.ts +2 -2
  43. package/dist/cjs/migrations/v1.sql.js +3 -3
  44. package/dist/cjs/migrations/v2.d.ts +1 -0
  45. package/dist/cjs/migrations/v2.js +29 -0
  46. package/dist/cjs/migrations/v2.js.map +1 -0
  47. package/dist/cjs/migrations/v2.sql.d.ts +2 -2
  48. package/dist/cjs/migrations/v2.sql.js +4 -4
  49. package/dist/cjs/migrations/v3.d.ts +1 -0
  50. package/dist/cjs/migrations/v3.js +15 -0
  51. package/dist/cjs/migrations/v3.js.map +1 -0
  52. package/dist/cjs/migrations/v3.sql.d.ts +2 -2
  53. package/dist/cjs/migrations/v3.sql.js +4 -4
  54. package/dist/cjs/migrations/v4.d.ts +1 -0
  55. package/dist/cjs/migrations/v4.js +9 -0
  56. package/dist/cjs/migrations/v4.js.map +1 -0
  57. package/dist/cjs/migrations/v4.sql.d.ts +2 -2
  58. package/dist/cjs/migrations/v4.sql.js +4 -4
  59. package/dist/cjs/migrations/v5.d.ts +2 -0
  60. package/dist/cjs/migrations/v5.js +37 -0
  61. package/dist/cjs/migrations/v5.js.map +1 -0
  62. package/dist/cjs/migrations.d.ts +6 -2
  63. package/dist/cjs/migrations.js +62 -32
  64. package/dist/cjs/migrations.js.map +1 -1
  65. package/dist/cjs/migrations.test.d.ts +1 -0
  66. package/dist/cjs/migrations.test.js +121 -0
  67. package/dist/cjs/migrations.test.js.map +1 -0
  68. package/dist/cjs/types.d.ts +22 -22
  69. package/dist/cjs/types.js +2 -2
  70. package/dist/esm/Baselines.d.ts +15 -15
  71. package/dist/esm/Baselines.js +79 -79
  72. package/dist/esm/ClientConnection.d.ts +18 -18
  73. package/dist/esm/ClientConnection.js +101 -101
  74. package/dist/esm/MessageSender.d.ts +5 -5
  75. package/dist/esm/MessageSender.js +1 -1
  76. package/dist/esm/OperationHistory.d.ts +36 -36
  77. package/dist/esm/OperationHistory.js +116 -116
  78. package/dist/esm/Presence.d.ts +15 -15
  79. package/dist/esm/Presence.js +52 -52
  80. package/dist/esm/Profiles.d.ts +10 -10
  81. package/dist/esm/Profiles.js +15 -15
  82. package/dist/esm/ReplicaKeepaliveTimers.d.ts +17 -17
  83. package/dist/esm/ReplicaKeepaliveTimers.js +26 -26
  84. package/dist/esm/Replicas.d.ts +24 -24
  85. package/dist/esm/Replicas.js +133 -133
  86. package/dist/esm/Server.d.ts +143 -143
  87. package/dist/esm/Server.js +389 -389
  88. package/dist/esm/ServerLibrary.d.ts +77 -77
  89. package/dist/esm/ServerLibrary.js +483 -483
  90. package/dist/esm/ServerStorage.d.ts +44 -0
  91. package/dist/esm/ServerStorage.js +58 -0
  92. package/dist/esm/ServerStorage.js.map +1 -0
  93. package/dist/esm/TokenProvider.d.ts +16 -16
  94. package/dist/esm/TokenProvider.js +19 -19
  95. package/dist/esm/TokenVerifier.d.ts +16 -16
  96. package/dist/esm/TokenVerifier.js +21 -21
  97. package/dist/esm/files/FileMetadata.d.ts +28 -28
  98. package/dist/esm/files/FileMetadata.js +42 -42
  99. package/dist/esm/files/FileStorage.d.ts +35 -35
  100. package/dist/esm/files/FileStorage.js +44 -44
  101. package/dist/esm/index.d.ts +9 -9
  102. package/dist/esm/index.js +4 -4
  103. package/dist/esm/migrations/v0.d.ts +1 -0
  104. package/dist/esm/migrations/v0.js +27 -0
  105. package/dist/esm/migrations/v0.js.map +1 -0
  106. package/dist/esm/migrations/v0.sql.d.ts +2 -2
  107. package/dist/esm/migrations/v0.sql.js +1 -1
  108. package/dist/esm/migrations/v1.d.ts +1 -0
  109. package/dist/esm/migrations/v1.js +29 -0
  110. package/dist/esm/migrations/v1.js.map +1 -0
  111. package/dist/esm/migrations/v1.sql.d.ts +2 -2
  112. package/dist/esm/migrations/v1.sql.js +1 -1
  113. package/dist/esm/migrations/v2.d.ts +1 -0
  114. package/dist/esm/migrations/v2.js +26 -0
  115. package/dist/esm/migrations/v2.js.map +1 -0
  116. package/dist/esm/migrations/v2.sql.d.ts +2 -2
  117. package/dist/esm/migrations/v2.sql.js +2 -2
  118. package/dist/esm/migrations/v3.d.ts +1 -0
  119. package/dist/esm/migrations/v3.js +12 -0
  120. package/dist/esm/migrations/v3.js.map +1 -0
  121. package/dist/esm/migrations/v3.sql.d.ts +2 -2
  122. package/dist/esm/migrations/v3.sql.js +2 -2
  123. package/dist/esm/migrations/v4.d.ts +1 -0
  124. package/dist/esm/migrations/v4.js +6 -0
  125. package/dist/esm/migrations/v4.js.map +1 -0
  126. package/dist/esm/migrations/v4.sql.d.ts +2 -2
  127. package/dist/esm/migrations/v4.sql.js +2 -2
  128. package/dist/esm/migrations/v5.d.ts +2 -0
  129. package/dist/esm/migrations/v5.js +33 -0
  130. package/dist/esm/migrations/v5.js.map +1 -0
  131. package/dist/esm/migrations.d.ts +6 -2
  132. package/dist/esm/migrations.js +35 -25
  133. package/dist/esm/migrations.js.map +1 -1
  134. package/dist/esm/migrations.test.d.ts +1 -0
  135. package/dist/esm/migrations.test.js +116 -0
  136. package/dist/esm/migrations.test.js.map +1 -0
  137. package/dist/esm/types.d.ts +22 -22
  138. package/dist/esm/types.js +1 -1
  139. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  140. package/dist/tsconfig.tsbuildinfo +1 -1
  141. package/package.json +6 -4
  142. package/src/__snapshots__/migrations.test.ts.snap +55 -0
  143. package/src/migrations/{v0.sql.ts → v0.ts} +1 -1
  144. package/src/migrations/{v1.sql.ts → v1.ts} +1 -1
  145. package/src/migrations/{v2.sql.ts → v2.ts} +1 -1
  146. package/src/migrations/{v3.sql.ts → v3.ts} +1 -1
  147. package/src/migrations/{v4.sql.ts → v4.ts} +1 -1
  148. package/src/migrations/v5.ts +49 -0
  149. package/src/migrations.test.ts +133 -0
  150. package/src/migrations.ts +28 -19
@@ -1,24 +1,24 @@
1
- import { Database } from 'better-sqlite3';
2
- import { TokenInfo } from './TokenVerifier.js';
3
- import { ReplicaInfoSpec } from './types.js';
4
- export declare class ReplicaInfos {
5
- private db;
6
- private readonly replicaTruancyMinutes;
7
- constructor(db: Database, replicaTruancyMinutes: number);
8
- get truantCutoff(): number;
9
- get: (libraryId: string, replicaId: string) => ReplicaInfoSpec | null;
10
- getOrCreate: (libraryId: string, replicaId: string, info: TokenInfo) => {
11
- status: 'new' | 'existing' | 'truant';
12
- replicaInfo: ReplicaInfoSpec;
13
- };
14
- getAllNonTruant: (libraryId: string) => ReplicaInfoSpec[];
15
- getAll: (libraryId: string) => ReplicaInfoSpec[];
16
- updateLastSeen: (libraryId: string, replicaId: string) => import("better-sqlite3").RunResult;
17
- updateAcknowledged: (libraryId: string, replicaId: string, timestamp: string) => void;
18
- updateServerOrder: (libraryId: string, replicaId: string, serverOrder: number) => void;
19
- acknowledgeOperation: (libraryId: string, replicaId: string, timestamp: string) => void;
20
- getGlobalAck: (libraryId: string, onlineReplicaIds?: string[]) => string | undefined;
21
- delete: (libraryId: string, replicaId: string) => import("better-sqlite3").RunResult;
22
- deleteAll: (libraryId: string) => import("better-sqlite3").RunResult;
23
- deleteAllForUser: (libraryId: string, userId: string) => import("better-sqlite3").RunResult;
24
- }
1
+ import { Database } from 'better-sqlite3';
2
+ import { TokenInfo } from './TokenVerifier.js';
3
+ import { ReplicaInfoSpec } from './types.js';
4
+ export declare class ReplicaInfos {
5
+ private db;
6
+ private readonly replicaTruancyMinutes;
7
+ constructor(db: Database, replicaTruancyMinutes: number);
8
+ get truantCutoff(): number;
9
+ get: (libraryId: string, replicaId: string) => ReplicaInfoSpec | null;
10
+ getOrCreate: (libraryId: string, replicaId: string, info: TokenInfo) => {
11
+ status: 'new' | 'existing' | 'truant';
12
+ replicaInfo: ReplicaInfoSpec;
13
+ };
14
+ getAllNonTruant: (libraryId: string) => ReplicaInfoSpec[];
15
+ getAll: (libraryId: string) => ReplicaInfoSpec[];
16
+ updateLastSeen: (libraryId: string, replicaId: string) => import("better-sqlite3").RunResult;
17
+ updateAcknowledged: (libraryId: string, replicaId: string, timestamp: string) => void;
18
+ updateServerOrder: (libraryId: string, replicaId: string, serverOrder: number) => void;
19
+ acknowledgeOperation: (libraryId: string, replicaId: string, timestamp: string) => void;
20
+ getGlobalAck: (libraryId: string, onlineReplicaIds?: string[]) => string | undefined;
21
+ delete: (libraryId: string, replicaId: string) => import("better-sqlite3").RunResult;
22
+ deleteAll: (libraryId: string) => import("better-sqlite3").RunResult;
23
+ deleteAllForUser: (libraryId: string, userId: string) => import("better-sqlite3").RunResult;
24
+ }
@@ -1,130 +1,130 @@
1
- export class ReplicaInfos {
2
- constructor(db, replicaTruancyMinutes) {
3
- this.db = db;
4
- this.replicaTruancyMinutes = replicaTruancyMinutes;
5
- this.get = (libraryId, replicaId) => {
6
- const row = this.db
1
+ export class ReplicaInfos {
2
+ constructor(db, replicaTruancyMinutes) {
3
+ this.db = db;
4
+ this.replicaTruancyMinutes = replicaTruancyMinutes;
5
+ this.get = (libraryId, replicaId) => {
6
+ const row = this.db
7
7
  .prepare(`
8
8
  SELECT * FROM ReplicaInfo
9
9
  WHERE id = ? AND libraryId = ?
10
- `)
11
- .get(replicaId, libraryId);
12
- if (!row) {
13
- return null;
14
- }
15
- return {
16
- id: row.id,
17
- libraryId: row.libraryId,
18
- clientId: row.clientId,
19
- lastSeenWallClockTime: row.lastSeenWallClockTime,
20
- ackedLogicalTime: row.ackedLogicalTime,
21
- type: row.type,
22
- ackedServerOrder: row.ackedServerOrder,
23
- };
24
- };
25
- this.getOrCreate = (libraryId, replicaId, info) => {
26
- const replicaInfo = this.db
10
+ `)
11
+ .get(replicaId, libraryId);
12
+ if (!row) {
13
+ return null;
14
+ }
15
+ return {
16
+ id: row.id,
17
+ libraryId: row.libraryId,
18
+ clientId: row.clientId,
19
+ lastSeenWallClockTime: row.lastSeenWallClockTime,
20
+ ackedLogicalTime: row.ackedLogicalTime,
21
+ type: row.type,
22
+ ackedServerOrder: row.ackedServerOrder,
23
+ };
24
+ };
25
+ this.getOrCreate = (libraryId, replicaId, info) => {
26
+ const replicaInfo = this.db
27
27
  .prepare(`
28
28
  SELECT * FROM ReplicaInfo
29
29
  WHERE id = ? AND libraryId = ?
30
- `)
31
- .get(replicaId, libraryId);
32
- if (!replicaInfo) {
33
- this.db
30
+ `)
31
+ .get(replicaId, libraryId);
32
+ if (!replicaInfo) {
33
+ this.db
34
34
  .prepare(`
35
35
  INSERT INTO ReplicaInfo (id, clientId, libraryId, type)
36
36
  VALUES (?, ?, ?, ?)
37
- `)
38
- .run(replicaId, info.userId, libraryId, info.type);
39
- return {
40
- status: 'new',
41
- replicaInfo: {
42
- id: replicaId,
43
- clientId: info.userId,
44
- ackedLogicalTime: null,
45
- lastSeenWallClockTime: null,
46
- libraryId: libraryId,
47
- type: info.type,
48
- ackedServerOrder: 0,
49
- },
50
- };
51
- }
52
- if (replicaInfo.type !== info.type) {
53
- // type should be updated if a new token changes it
54
- this.db
37
+ `)
38
+ .run(replicaId, info.userId, libraryId, info.type);
39
+ return {
40
+ status: 'new',
41
+ replicaInfo: {
42
+ id: replicaId,
43
+ clientId: info.userId,
44
+ ackedLogicalTime: null,
45
+ lastSeenWallClockTime: null,
46
+ libraryId: libraryId,
47
+ type: info.type,
48
+ ackedServerOrder: 0,
49
+ },
50
+ };
51
+ }
52
+ if (replicaInfo.type !== info.type) {
53
+ // type should be updated if a new token changes it
54
+ this.db
55
55
  .prepare(`
56
56
  UPDATE ReplicaInfo
57
57
  SET type = ?
58
58
  WHERE id = ? AND libraryId = ?
59
- `)
60
- .run(info.type, replicaId, libraryId);
61
- }
62
- if (replicaInfo.clientId !== info.userId) {
63
- throw new Error(`Replica ${replicaId} already exists with a different user ID`);
64
- }
65
- if (replicaInfo.lastSeenWallClockTime &&
66
- replicaInfo.lastSeenWallClockTime < this.truantCutoff) {
67
- return {
68
- status: 'truant',
69
- replicaInfo,
70
- };
71
- }
72
- return {
73
- status: 'existing',
74
- replicaInfo,
75
- };
76
- };
77
- this.getAllNonTruant = (libraryId) => {
78
- return this.db
59
+ `)
60
+ .run(info.type, replicaId, libraryId);
61
+ }
62
+ if (replicaInfo.clientId !== info.userId) {
63
+ throw new Error(`Replica ${replicaId} already exists with a different user ID`);
64
+ }
65
+ if (replicaInfo.lastSeenWallClockTime &&
66
+ replicaInfo.lastSeenWallClockTime < this.truantCutoff) {
67
+ return {
68
+ status: 'truant',
69
+ replicaInfo,
70
+ };
71
+ }
72
+ return {
73
+ status: 'existing',
74
+ replicaInfo,
75
+ };
76
+ };
77
+ this.getAllNonTruant = (libraryId) => {
78
+ return this.db
79
79
  .prepare(`
80
80
  SELECT * FROM ReplicaInfo
81
81
  WHERE libraryId = ? AND lastSeenWallClockTime > ?
82
- `)
83
- .all(libraryId, this.truantCutoff);
84
- };
85
- this.getAll = (libraryId) => {
86
- return this.db
82
+ `)
83
+ .all(libraryId, this.truantCutoff);
84
+ };
85
+ this.getAll = (libraryId) => {
86
+ return this.db
87
87
  .prepare(`
88
88
  SELECT * FROM ReplicaInfo
89
89
  WHERE libraryId = ?
90
- `)
91
- .all(libraryId);
92
- };
93
- this.updateLastSeen = (libraryId, replicaId) => {
94
- const clockTime = new Date().getTime();
95
- return this.db
90
+ `)
91
+ .all(libraryId);
92
+ };
93
+ this.updateLastSeen = (libraryId, replicaId) => {
94
+ const clockTime = new Date().getTime();
95
+ return this.db
96
96
  .prepare(`
97
97
  UPDATE ReplicaInfo
98
98
  SET lastSeenWallClockTime = ?
99
99
  WHERE id = ? AND libraryId = ?
100
- `)
101
- .run(clockTime, replicaId, libraryId);
102
- };
103
- this.updateAcknowledged = (libraryId, replicaId, timestamp) => {
104
- this.db
100
+ `)
101
+ .run(clockTime, replicaId, libraryId);
102
+ };
103
+ this.updateAcknowledged = (libraryId, replicaId, timestamp) => {
104
+ this.db
105
105
  .prepare(`
106
106
  UPDATE ReplicaInfo
107
107
  SET ackedLogicalTime = ?
108
108
  WHERE id = ? AND libraryId = ?
109
- `)
110
- .run(timestamp, replicaId, libraryId);
111
- };
112
- this.updateServerOrder = (libraryId, replicaId, serverOrder) => {
113
- // only increment the server order, never set it to lower than before
114
- this.db
109
+ `)
110
+ .run(timestamp, replicaId, libraryId);
111
+ };
112
+ this.updateServerOrder = (libraryId, replicaId, serverOrder) => {
113
+ // only increment the server order, never set it to lower than before
114
+ this.db
115
115
  .prepare(`
116
116
  UPDATE ReplicaInfo
117
117
  SET ackedServerOrder = MAX(ackedServerOrder, ?)
118
118
  WHERE id = ? AND libraryId = ?
119
- `)
120
- .run(serverOrder, replicaId, libraryId);
121
- };
122
- this.acknowledgeOperation = (libraryId, replicaId, timestamp) => {
123
- if (!timestamp)
124
- return;
125
- // get the server order from the operation with the timestamp and update the replica's server order
126
- // to the max of the current server order and the server order of the operation
127
- this.db
119
+ `)
120
+ .run(serverOrder, replicaId, libraryId);
121
+ };
122
+ this.acknowledgeOperation = (libraryId, replicaId, timestamp) => {
123
+ if (!timestamp)
124
+ return;
125
+ // get the server order from the operation with the timestamp and update the replica's server order
126
+ // to the max of the current server order and the server order of the operation
127
+ this.db
128
128
  .prepare(`
129
129
  UPDATE ReplicaInfo
130
130
  SET ackedLogicalTime = ?, ackedServerOrder = MAX(ackedServerOrder, COALESCE((
@@ -132,47 +132,47 @@ export class ReplicaInfos {
132
132
  WHERE libraryId = ? AND timestamp = ?
133
133
  ), 0))
134
134
  WHERE id = ? AND libraryId = ?
135
- `)
136
- .run(timestamp, libraryId, timestamp, replicaId, libraryId);
137
- };
138
- this.getGlobalAck = (libraryId, onlineReplicaIds) => {
139
- const nonTruant = this.getAllNonTruant(libraryId);
140
- const globalAckEligible = nonTruant.filter((replica) => replica.type < 2 || (onlineReplicaIds === null || onlineReplicaIds === void 0 ? void 0 : onlineReplicaIds.includes(replica.id)));
141
- // get the earliest acked time
142
- return globalAckEligible.reduce((acc, replica) => {
143
- if (!replica.ackedLogicalTime) {
144
- return acc;
145
- }
146
- if (acc === undefined) {
147
- return replica.ackedLogicalTime;
148
- }
149
- return acc < replica.ackedLogicalTime ? acc : replica.ackedLogicalTime;
150
- }, undefined);
151
- };
152
- this.delete = (libraryId, replicaId) => {
153
- return this.db
135
+ `)
136
+ .run(timestamp, libraryId, timestamp, replicaId, libraryId);
137
+ };
138
+ this.getGlobalAck = (libraryId, onlineReplicaIds) => {
139
+ const nonTruant = this.getAllNonTruant(libraryId);
140
+ const globalAckEligible = nonTruant.filter((replica) => replica.type < 2 || (onlineReplicaIds === null || onlineReplicaIds === void 0 ? void 0 : onlineReplicaIds.includes(replica.id)));
141
+ // get the earliest acked time
142
+ return globalAckEligible.reduce((acc, replica) => {
143
+ if (!replica.ackedLogicalTime) {
144
+ return acc;
145
+ }
146
+ if (acc === undefined) {
147
+ return replica.ackedLogicalTime;
148
+ }
149
+ return acc < replica.ackedLogicalTime ? acc : replica.ackedLogicalTime;
150
+ }, undefined);
151
+ };
152
+ this.delete = (libraryId, replicaId) => {
153
+ return this.db
154
154
  .prepare(`
155
155
  DELETE FROM ReplicaInfo
156
156
  WHERE id = ? AND libraryId = ?
157
- `)
158
- .run(replicaId, libraryId);
159
- };
160
- this.deleteAll = (libraryId) => {
161
- return this.db
157
+ `)
158
+ .run(replicaId, libraryId);
159
+ };
160
+ this.deleteAll = (libraryId) => {
161
+ return this.db
162
162
  .prepare(`
163
163
  DELETE FROM ReplicaInfo
164
164
  WHERE libraryId = ?
165
- `)
166
- .run(libraryId);
167
- };
168
- this.deleteAllForUser = (libraryId, userId) => {
169
- return this.db
170
- .prepare(`DELETE FROM ReplicaInfo WHERE libraryId = ? AND clientId = ?`)
171
- .run(libraryId, userId);
172
- };
173
- }
174
- get truantCutoff() {
175
- return Date.now() - this.replicaTruancyMinutes * 60 * 1000;
176
- }
177
- }
165
+ `)
166
+ .run(libraryId);
167
+ };
168
+ this.deleteAllForUser = (libraryId, userId) => {
169
+ return this.db
170
+ .prepare(`DELETE FROM ReplicaInfo WHERE libraryId = ? AND clientId = ?`)
171
+ .run(libraryId, userId);
172
+ };
173
+ }
174
+ get truantCutoff() {
175
+ return Date.now() - this.replicaTruancyMinutes * 60 * 1000;
176
+ }
177
+ }
178
178
  //# sourceMappingURL=Replicas.js.map
@@ -1,143 +1,143 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
3
- import { DocumentBaseline, Operation, ServerMessage } from '@verdant-web/common';
4
- import EventEmitter from 'events';
5
- import { IncomingMessage, Server as HttpServer, ServerResponse } from 'http';
6
- import { UserProfiles } from './Profiles.js';
7
- import { MessageSender } from './MessageSender.js';
8
- import { TokenInfo } from './TokenVerifier.js';
9
- import { FileStorage } from './files/FileStorage.js';
10
- import { FileMetadataConfig } from './files/FileMetadata.js';
11
- export interface ServerOptions {
12
- /**
13
- * Attach to a pre-existing HTTP server. Provide this if you have another server,
14
- * like an Express server, that you want to add lofi to. If provided, you don't
15
- * need to call .listen()
16
- */
17
- httpServer?: HttpServer;
18
- /**
19
- * The secret value used to generate tokens from your authentication endpoint.
20
- * Used to verify and decode user identity and library access on connection.
21
- */
22
- tokenSecret: string;
23
- /**
24
- * The path to your SQLite database.
25
- */
26
- databaseFile: string;
27
- /**
28
- * Optionally provide an implementation of the UserProfiles interface to look up
29
- * static user profile data to use in persistence. This data is considered trusted
30
- * and can be used to identify users with information like name or profile image.
31
- */
32
- profiles?: UserProfiles<any>;
33
- /**
34
- * How many minutes of inactivity before a replica is considered truant and
35
- * removed from the library. Defaults to 30 days.
36
- */
37
- replicaTruancyMinutes?: number;
38
- /**
39
- * Supply a logging function to log debug messages.
40
- */
41
- log?: (...args: any[]) => void;
42
- /**
43
- * Disable history compaction. Storage usage will grow indefinitely. Not recommended.
44
- */
45
- disableRebasing?: boolean;
46
- /**
47
- * Provide a FileStorage backend to accept file uploads. Without a file backend,
48
- * users will not be able to synchronize files, and all files added on peer replicas
49
- * will be forever unavailable.
50
- *
51
- * A default, filesystem-backed FileStorage implementation is provided as an export
52
- * of this library.
53
- */
54
- fileStorage?: FileStorage;
55
- fileConfig?: FileMetadataConfig;
56
- }
57
- export interface ServerEvents {
58
- error: (err: any) => void;
59
- warning: (msg: string, ...args: any[]) => void;
60
- changes: (info: Omit<TokenInfo, 'token'>, operations: Operation[], baselines: DocumentBaseline[]) => void;
61
- request: (info: TokenInfo) => void;
62
- 'socket-close': (info: TokenInfo) => void;
63
- 'socket-connection': (info: TokenInfo) => void;
64
- }
65
- export declare interface Server {
66
- on<Event extends keyof ServerEvents>(ev: Event, cb: ServerEvents[Event]): this;
67
- off<Event extends keyof ServerEvents>(ev: Event, cb: ServerEvents[Event]): this;
68
- emit<Event extends keyof ServerEvents>(ev: Event, ...args: Parameters<ServerEvents[Event]>): boolean;
69
- }
70
- export declare class Server extends EventEmitter implements MessageSender {
71
- readonly httpServer: HttpServer;
72
- private wss;
73
- private fileStorage?;
74
- private fileMetadata;
75
- private library;
76
- private clientConnections;
77
- private keepalives;
78
- private tokenVerifier;
79
- private connectionToReplicaIdMap;
80
- private log;
81
- private __testMode;
82
- constructor(options: ServerOptions);
83
- private authorizeRequest;
84
- private getRequestToken;
85
- private internalServerHandleRequest;
86
- handleRequest: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
87
- /**
88
- * Handles a multipart upload of a file from a verdant client. The upload
89
- * will include parameters for the file's ID, name, and type. The request
90
- * must be authenticated with a token to tie it to a library.
91
- */
92
- handleFileRequest: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
93
- broadcast: (libraryId: string, message: ServerMessage, omitKeys?: string[]) => void;
94
- send: (libraryId: string, key: string, message: ServerMessage) => void;
95
- private handleMessage;
96
- private handleConnection;
97
- listen: (handle: any, listeningListener?: (() => void) | undefined) => void;
98
- close: () => Promise<void>;
99
- /**
100
- * Completely remove a library from the server.
101
- * This will remove all data associated with the library.
102
- * Use it very carefully! Data will still be stored on user
103
- * devices, so this is not as scary as it sounds - the next time
104
- * any replica connects, it will repopulate the library. But
105
- * you should still be careful.
106
- */
107
- evictLibrary: (libraryId: string) => void;
108
- /**
109
- * Returns currently connected replica info for a library
110
- */
111
- getLibraryPresence: (libraryId: string) => Record<string, import("@verdant-web/common").UserInfo<any, any>>;
112
- /**
113
- * Returns helpful information about a library.
114
- * This is currently only the known replicas and some
115
- * more low-level metadata.
116
- */
117
- getLibraryInfo: (libraryId: string) => Promise<{
118
- replicas: {
119
- id: string;
120
- ackedLogicalTime: string | null;
121
- ackedServerOrder: number;
122
- type: import("@verdant-web/common").ReplicaType;
123
- truant: boolean | 0 | null;
124
- profile: any;
125
- }[];
126
- latestServerOrder: number;
127
- operationsCount: number;
128
- baselinesCount: number;
129
- globalAck: string | undefined;
130
- }>;
131
- /**
132
- * Removes all replicas associated with a User ID from
133
- * the server. Consistency algorithms will no longer wait on these
134
- * replicas before confirming and squashing changes.
135
- */
136
- evictUser: (libraryId: string, userId: string) => void;
137
- /**
138
- * Retrieves a snapshot of the full document contents at this time.
139
- * Useful for capturing the shape of data for static usage outside
140
- * of the live system.
141
- */
142
- getDocumentSnapshot: (libraryId: string, collection: string, documentId: string) => any;
143
- }
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import { DocumentBaseline, Operation, ServerMessage } from '@verdant-web/common';
4
+ import EventEmitter from 'events';
5
+ import { IncomingMessage, Server as HttpServer, ServerResponse } from 'http';
6
+ import { UserProfiles } from './Profiles.js';
7
+ import { MessageSender } from './MessageSender.js';
8
+ import { TokenInfo } from './TokenVerifier.js';
9
+ import { FileStorage } from './files/FileStorage.js';
10
+ import { FileMetadataConfig } from './files/FileMetadata.js';
11
+ export interface ServerOptions {
12
+ /**
13
+ * Attach to a pre-existing HTTP server. Provide this if you have another server,
14
+ * like an Express server, that you want to add lofi to. If provided, you don't
15
+ * need to call .listen()
16
+ */
17
+ httpServer?: HttpServer;
18
+ /**
19
+ * The secret value used to generate tokens from your authentication endpoint.
20
+ * Used to verify and decode user identity and library access on connection.
21
+ */
22
+ tokenSecret: string;
23
+ /**
24
+ * The path to your SQLite database.
25
+ */
26
+ databaseFile: string;
27
+ /**
28
+ * Optionally provide an implementation of the UserProfiles interface to look up
29
+ * static user profile data to use in persistence. This data is considered trusted
30
+ * and can be used to identify users with information like name or profile image.
31
+ */
32
+ profiles?: UserProfiles<any>;
33
+ /**
34
+ * How many minutes of inactivity before a replica is considered truant and
35
+ * removed from the library. Defaults to 30 days.
36
+ */
37
+ replicaTruancyMinutes?: number;
38
+ /**
39
+ * Supply a logging function to log debug messages.
40
+ */
41
+ log?: (...args: any[]) => void;
42
+ /**
43
+ * Disable history compaction. Storage usage will grow indefinitely. Not recommended.
44
+ */
45
+ disableRebasing?: boolean;
46
+ /**
47
+ * Provide a FileStorage backend to accept file uploads. Without a file backend,
48
+ * users will not be able to synchronize files, and all files added on peer replicas
49
+ * will be forever unavailable.
50
+ *
51
+ * A default, filesystem-backed FileStorage implementation is provided as an export
52
+ * of this library.
53
+ */
54
+ fileStorage?: FileStorage;
55
+ fileConfig?: FileMetadataConfig;
56
+ }
57
+ export interface ServerEvents {
58
+ error: (err: any) => void;
59
+ warning: (msg: string, ...args: any[]) => void;
60
+ changes: (info: Omit<TokenInfo, 'token'>, operations: Operation[], baselines: DocumentBaseline[]) => void;
61
+ request: (info: TokenInfo) => void;
62
+ 'socket-close': (info: TokenInfo) => void;
63
+ 'socket-connection': (info: TokenInfo) => void;
64
+ }
65
+ export declare interface Server {
66
+ on<Event extends keyof ServerEvents>(ev: Event, cb: ServerEvents[Event]): this;
67
+ off<Event extends keyof ServerEvents>(ev: Event, cb: ServerEvents[Event]): this;
68
+ emit<Event extends keyof ServerEvents>(ev: Event, ...args: Parameters<ServerEvents[Event]>): boolean;
69
+ }
70
+ export declare class Server extends EventEmitter implements MessageSender {
71
+ readonly httpServer: HttpServer;
72
+ private wss;
73
+ private fileStorage?;
74
+ private fileMetadata;
75
+ private library;
76
+ private clientConnections;
77
+ private keepalives;
78
+ private tokenVerifier;
79
+ private connectionToReplicaIdMap;
80
+ private log;
81
+ private __testMode;
82
+ constructor(options: ServerOptions);
83
+ private authorizeRequest;
84
+ private getRequestToken;
85
+ private internalServerHandleRequest;
86
+ handleRequest: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
87
+ /**
88
+ * Handles a multipart upload of a file from a verdant client. The upload
89
+ * will include parameters for the file's ID, name, and type. The request
90
+ * must be authenticated with a token to tie it to a library.
91
+ */
92
+ handleFileRequest: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
93
+ broadcast: (libraryId: string, message: ServerMessage, omitKeys?: string[]) => void;
94
+ send: (libraryId: string, key: string, message: ServerMessage) => void;
95
+ private handleMessage;
96
+ private handleConnection;
97
+ listen: (handle: any, listeningListener?: (() => void) | undefined) => void;
98
+ close: () => Promise<void>;
99
+ /**
100
+ * Completely remove a library from the server.
101
+ * This will remove all data associated with the library.
102
+ * Use it very carefully! Data will still be stored on user
103
+ * devices, so this is not as scary as it sounds - the next time
104
+ * any replica connects, it will repopulate the library. But
105
+ * you should still be careful.
106
+ */
107
+ evictLibrary: (libraryId: string) => void;
108
+ /**
109
+ * Returns currently connected replica info for a library
110
+ */
111
+ getLibraryPresence: (libraryId: string) => Record<string, import("@verdant-web/common").UserInfo<any, any>>;
112
+ /**
113
+ * Returns helpful information about a library.
114
+ * This is currently only the known replicas and some
115
+ * more low-level metadata.
116
+ */
117
+ getLibraryInfo: (libraryId: string) => Promise<{
118
+ replicas: {
119
+ id: string;
120
+ ackedLogicalTime: string | null;
121
+ ackedServerOrder: number;
122
+ type: import("@verdant-web/common").ReplicaType;
123
+ truant: boolean | 0 | null;
124
+ profile: any;
125
+ }[];
126
+ latestServerOrder: number;
127
+ operationsCount: number;
128
+ baselinesCount: number;
129
+ globalAck: string | undefined;
130
+ }>;
131
+ /**
132
+ * Removes all replicas associated with a User ID from
133
+ * the server. Consistency algorithms will no longer wait on these
134
+ * replicas before confirming and squashing changes.
135
+ */
136
+ evictUser: (libraryId: string, userId: string) => void;
137
+ /**
138
+ * Retrieves a snapshot of the full document contents at this time.
139
+ * Useful for capturing the shape of data for static usage outside
140
+ * of the live system.
141
+ */
142
+ getDocumentSnapshot: (libraryId: string, collection: string, documentId: string) => any;
143
+ }