@verdant-web/server 2.1.2 → 3.0.0-next.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.
- package/dist/esm/ClientConnection.js.map +1 -1
- package/dist/esm/Presence.js.map +1 -1
- package/dist/esm/Profiles.js.map +1 -1
- package/dist/esm/ReplicaKeepaliveTimers.js.map +1 -1
- package/dist/esm/Server.js +29 -30
- package/dist/esm/Server.js.map +1 -1
- package/dist/esm/ServerLibrary.js +161 -129
- package/dist/esm/ServerLibrary.js.map +1 -1
- package/dist/esm/ServerLibrary.test.js +369 -0
- package/dist/esm/ServerLibrary.test.js.map +1 -0
- package/dist/esm/TokenProvider.js.map +1 -1
- package/dist/esm/TokenVerifier.js.map +1 -1
- package/dist/esm/files/FileStorage.js.map +1 -1
- package/dist/esm/storage/Storage.js +2 -0
- package/dist/esm/storage/Storage.js.map +1 -0
- package/dist/esm/storage/index.js +3 -0
- package/dist/esm/storage/index.js.map +1 -0
- package/dist/esm/storage/sql/SqlBaselines.js +101 -0
- package/dist/esm/storage/sql/SqlBaselines.js.map +1 -0
- package/dist/esm/storage/sql/SqlFileMetadata.js +69 -0
- package/dist/esm/storage/sql/SqlFileMetadata.js.map +1 -0
- package/dist/esm/storage/sql/SqlOperations.js +104 -0
- package/dist/esm/storage/sql/SqlOperations.js.map +1 -0
- package/dist/esm/storage/sql/SqlReplicas.js +163 -0
- package/dist/esm/storage/sql/SqlReplicas.js.map +1 -0
- package/dist/esm/storage/sql/migrations/v1.js +59 -0
- package/dist/esm/storage/sql/migrations/v1.js.map +1 -0
- package/dist/esm/storage/sql/migrations.js +3 -0
- package/dist/esm/storage/sql/migrations.js.map +1 -0
- package/dist/esm/storage/sql/sqlStorage.js +43 -0
- package/dist/esm/storage/sql/sqlStorage.js.map +1 -0
- package/dist/esm/storage/sql/tables.js +2 -0
- package/dist/esm/storage/sql/tables.js.map +1 -0
- package/package.json +20 -14
- package/src/Server.ts +37 -43
- package/src/ServerLibrary.test.ts +447 -0
- package/src/ServerLibrary.ts +253 -166
- package/src/TokenProvider.ts +3 -0
- package/src/index.ts +1 -0
- package/src/storage/Storage.ts +118 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/sql/SqlBaselines.ts +141 -0
- package/src/storage/sql/SqlFileMetadata.ts +94 -0
- package/src/storage/sql/SqlOperations.ts +149 -0
- package/src/storage/sql/SqlReplicas.ts +234 -0
- package/src/storage/sql/migrations/v1.ts +64 -0
- package/src/storage/sql/migrations.ts +3 -0
- package/src/storage/sql/sqlStorage.ts +59 -0
- package/src/storage/sql/tables.ts +45 -0
- package/src/types.ts +31 -4
- package/bin/server.mjs +0 -115
- package/dist/cjs/Baselines.d.ts +0 -16
- package/dist/cjs/Baselines.js +0 -128
- package/dist/cjs/Baselines.js.map +0 -1
- package/dist/cjs/ClientConnection.d.ts +0 -20
- package/dist/cjs/ClientConnection.js +0 -139
- package/dist/cjs/ClientConnection.js.map +0 -1
- package/dist/cjs/MessageSender.d.ts +0 -5
- package/dist/cjs/MessageSender.js +0 -3
- package/dist/cjs/MessageSender.js.map +0 -1
- package/dist/cjs/OperationHistory.d.ts +0 -36
- package/dist/cjs/OperationHistory.js +0 -165
- package/dist/cjs/OperationHistory.js.map +0 -1
- package/dist/cjs/Presence.d.ts +0 -15
- package/dist/cjs/Presence.js +0 -60
- package/dist/cjs/Presence.js.map +0 -1
- package/dist/cjs/Profiles.d.ts +0 -10
- package/dist/cjs/Profiles.js +0 -20
- package/dist/cjs/Profiles.js.map +0 -1
- package/dist/cjs/ReplicaKeepaliveTimers.d.ts +0 -17
- package/dist/cjs/ReplicaKeepaliveTimers.js +0 -31
- package/dist/cjs/ReplicaKeepaliveTimers.js.map +0 -1
- package/dist/cjs/Replicas.d.ts +0 -25
- package/dist/cjs/Replicas.js +0 -196
- package/dist/cjs/Replicas.js.map +0 -1
- package/dist/cjs/Server.d.ts +0 -180
- package/dist/cjs/Server.js +0 -552
- package/dist/cjs/Server.js.map +0 -1
- package/dist/cjs/ServerLibrary.d.ts +0 -77
- package/dist/cjs/ServerLibrary.js +0 -497
- package/dist/cjs/ServerLibrary.js.map +0 -1
- package/dist/cjs/TokenProvider.d.ts +0 -16
- package/dist/cjs/TokenProvider.js +0 -27
- package/dist/cjs/TokenProvider.js.map +0 -1
- package/dist/cjs/TokenVerifier.d.ts +0 -16
- package/dist/cjs/TokenVerifier.js +0 -40
- package/dist/cjs/TokenVerifier.js.map +0 -1
- package/dist/cjs/files/FileMetadata.d.ts +0 -28
- package/dist/cjs/files/FileMetadata.js +0 -69
- package/dist/cjs/files/FileMetadata.js.map +0 -1
- package/dist/cjs/files/FileStorage.d.ts +0 -35
- package/dist/cjs/files/FileStorage.js +0 -72
- package/dist/cjs/files/FileStorage.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -9
- package/dist/cjs/index.js +0 -12
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/migrations/v0.d.ts +0 -1
- package/dist/cjs/migrations/v0.js +0 -30
- package/dist/cjs/migrations/v0.js.map +0 -1
- package/dist/cjs/migrations/v1.d.ts +0 -1
- package/dist/cjs/migrations/v1.js +0 -32
- package/dist/cjs/migrations/v1.js.map +0 -1
- package/dist/cjs/migrations/v2.d.ts +0 -1
- package/dist/cjs/migrations/v2.js +0 -29
- package/dist/cjs/migrations/v2.js.map +0 -1
- package/dist/cjs/migrations/v3.d.ts +0 -1
- package/dist/cjs/migrations/v3.js +0 -15
- package/dist/cjs/migrations/v3.js.map +0 -1
- package/dist/cjs/migrations/v4.d.ts +0 -1
- package/dist/cjs/migrations/v4.js +0 -9
- package/dist/cjs/migrations/v4.js.map +0 -1
- package/dist/cjs/migrations/v5.d.ts +0 -2
- package/dist/cjs/migrations/v5.js +0 -37
- package/dist/cjs/migrations/v5.js.map +0 -1
- package/dist/cjs/migrations/v6.d.ts +0 -1
- package/dist/cjs/migrations/v6.js +0 -8
- package/dist/cjs/migrations/v6.js.map +0 -1
- package/dist/cjs/migrations/v7.d.ts +0 -1
- package/dist/cjs/migrations/v7.js +0 -10
- package/dist/cjs/migrations/v7.js.map +0 -1
- package/dist/cjs/migrations.d.ts +0 -6
- package/dist/cjs/migrations.js +0 -72
- package/dist/cjs/migrations.js.map +0 -1
- package/dist/cjs/migrations.test.d.ts +0 -1
- package/dist/cjs/migrations.test.js +0 -121
- package/dist/cjs/migrations.test.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -23
- package/dist/cjs/types.js +0 -3
- package/dist/cjs/types.js.map +0 -1
- package/dist/esm/Baselines.d.ts +0 -16
- package/dist/esm/Baselines.js +0 -124
- package/dist/esm/Baselines.js.map +0 -1
- package/dist/esm/ClientConnection.d.ts +0 -20
- package/dist/esm/MessageSender.d.ts +0 -5
- package/dist/esm/OperationHistory.d.ts +0 -36
- package/dist/esm/OperationHistory.js +0 -161
- package/dist/esm/OperationHistory.js.map +0 -1
- package/dist/esm/Presence.d.ts +0 -15
- package/dist/esm/Profiles.d.ts +0 -10
- package/dist/esm/ReplicaKeepaliveTimers.d.ts +0 -17
- package/dist/esm/Replicas.d.ts +0 -25
- package/dist/esm/Replicas.js +0 -192
- package/dist/esm/Replicas.js.map +0 -1
- package/dist/esm/Server.d.ts +0 -180
- package/dist/esm/ServerLibrary.d.ts +0 -77
- package/dist/esm/TokenProvider.d.ts +0 -16
- package/dist/esm/TokenVerifier.d.ts +0 -16
- package/dist/esm/files/FileMetadata.d.ts +0 -28
- package/dist/esm/files/FileMetadata.js +0 -65
- package/dist/esm/files/FileMetadata.js.map +0 -1
- package/dist/esm/files/FileStorage.d.ts +0 -35
- package/dist/esm/index.d.ts +0 -9
- package/dist/esm/migrations/v0.d.ts +0 -1
- package/dist/esm/migrations/v0.js +0 -27
- package/dist/esm/migrations/v0.js.map +0 -1
- package/dist/esm/migrations/v1.d.ts +0 -1
- package/dist/esm/migrations/v1.js +0 -29
- package/dist/esm/migrations/v1.js.map +0 -1
- package/dist/esm/migrations/v2.d.ts +0 -1
- package/dist/esm/migrations/v2.js +0 -26
- package/dist/esm/migrations/v2.js.map +0 -1
- package/dist/esm/migrations/v3.d.ts +0 -1
- package/dist/esm/migrations/v3.js +0 -12
- package/dist/esm/migrations/v3.js.map +0 -1
- package/dist/esm/migrations/v4.d.ts +0 -1
- package/dist/esm/migrations/v4.js +0 -6
- package/dist/esm/migrations/v4.js.map +0 -1
- package/dist/esm/migrations/v5.d.ts +0 -2
- package/dist/esm/migrations/v5.js +0 -33
- package/dist/esm/migrations/v5.js.map +0 -1
- package/dist/esm/migrations/v6.d.ts +0 -1
- package/dist/esm/migrations/v6.js +0 -5
- package/dist/esm/migrations/v6.js.map +0 -1
- package/dist/esm/migrations/v7.d.ts +0 -1
- package/dist/esm/migrations/v7.js +0 -7
- package/dist/esm/migrations/v7.js.map +0 -1
- package/dist/esm/migrations.d.ts +0 -6
- package/dist/esm/migrations.js +0 -45
- package/dist/esm/migrations.js.map +0 -1
- package/dist/esm/migrations.test.d.ts +0 -1
- package/dist/esm/migrations.test.js +0 -116
- package/dist/esm/migrations.test.js.map +0 -1
- package/dist/esm/types.d.ts +0 -23
- package/dist/tsconfig-cjs.tsbuildinfo +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/Baselines.ts +0 -198
- package/src/OperationHistory.ts +0 -232
- package/src/Replicas.ts +0 -274
- package/src/files/FileMetadata.ts +0 -112
- package/src/migrations/v0.ts +0 -26
- package/src/migrations/v1.ts +0 -28
- package/src/migrations/v2.ts +0 -26
- package/src/migrations/v3.ts +0 -12
- package/src/migrations/v4.ts +0 -6
- package/src/migrations/v5.ts +0 -49
- package/src/migrations/v6.ts +0 -4
- package/src/migrations/v7.ts +0 -6
- package/src/migrations.test.ts +0 -133
- package/src/migrations.ts +0 -61
package/src/Replicas.ts
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { Database } from 'better-sqlite3';
|
|
2
|
-
import { TokenInfo } from './TokenVerifier.js';
|
|
3
|
-
import { ReplicaInfoSpec } from './types.js';
|
|
4
|
-
|
|
5
|
-
export class ReplicaInfos {
|
|
6
|
-
constructor(
|
|
7
|
-
private db: Database,
|
|
8
|
-
private readonly replicaTruancyMinutes: number,
|
|
9
|
-
) {}
|
|
10
|
-
|
|
11
|
-
get truantCutoff() {
|
|
12
|
-
return Date.now() - this.replicaTruancyMinutes * 60 * 1000;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
get = (libraryId: string, replicaId: string): ReplicaInfoSpec | null => {
|
|
16
|
-
const row = this.db
|
|
17
|
-
.prepare(
|
|
18
|
-
`
|
|
19
|
-
SELECT * FROM ReplicaInfo
|
|
20
|
-
WHERE id = ? AND libraryId = ?
|
|
21
|
-
`,
|
|
22
|
-
)
|
|
23
|
-
.get(replicaId, libraryId);
|
|
24
|
-
if (!row) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
id: row.id,
|
|
29
|
-
libraryId: row.libraryId,
|
|
30
|
-
clientId: row.clientId,
|
|
31
|
-
lastSeenWallClockTime: row.lastSeenWallClockTime,
|
|
32
|
-
ackedLogicalTime: row.ackedLogicalTime,
|
|
33
|
-
type: row.type,
|
|
34
|
-
ackedServerOrder: row.ackedServerOrder,
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
getOrCreate = (
|
|
39
|
-
libraryId: string,
|
|
40
|
-
replicaId: string,
|
|
41
|
-
info: TokenInfo,
|
|
42
|
-
): {
|
|
43
|
-
status: 'new' | 'existing' | 'truant';
|
|
44
|
-
replicaInfo: ReplicaInfoSpec;
|
|
45
|
-
} => {
|
|
46
|
-
const replicaInfo: ReplicaInfoSpec = this.db
|
|
47
|
-
.prepare(
|
|
48
|
-
`
|
|
49
|
-
SELECT * FROM ReplicaInfo
|
|
50
|
-
WHERE id = ? AND libraryId = ?
|
|
51
|
-
`,
|
|
52
|
-
)
|
|
53
|
-
.get(replicaId, libraryId);
|
|
54
|
-
|
|
55
|
-
if (!replicaInfo) {
|
|
56
|
-
this.db
|
|
57
|
-
.prepare(
|
|
58
|
-
`
|
|
59
|
-
INSERT INTO ReplicaInfo (id, clientId, libraryId, type)
|
|
60
|
-
VALUES (?, ?, ?, ?)
|
|
61
|
-
`,
|
|
62
|
-
)
|
|
63
|
-
.run(replicaId, info.userId, libraryId, info.type);
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
status: 'new',
|
|
67
|
-
replicaInfo: {
|
|
68
|
-
id: replicaId,
|
|
69
|
-
clientId: info.userId,
|
|
70
|
-
ackedLogicalTime: null,
|
|
71
|
-
lastSeenWallClockTime: null,
|
|
72
|
-
libraryId: libraryId,
|
|
73
|
-
type: info.type,
|
|
74
|
-
ackedServerOrder: 0,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (replicaInfo.type !== info.type) {
|
|
80
|
-
// type should be updated if a new token changes it
|
|
81
|
-
this.db
|
|
82
|
-
.prepare(
|
|
83
|
-
`
|
|
84
|
-
UPDATE ReplicaInfo
|
|
85
|
-
SET type = ?
|
|
86
|
-
WHERE id = ? AND libraryId = ?
|
|
87
|
-
`,
|
|
88
|
-
)
|
|
89
|
-
.run(info.type, replicaId, libraryId);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (replicaInfo.clientId !== info.userId) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`Replica ${replicaId} already exists with a different user ID`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
replicaInfo.lastSeenWallClockTime &&
|
|
100
|
-
replicaInfo.lastSeenWallClockTime < this.truantCutoff
|
|
101
|
-
) {
|
|
102
|
-
return {
|
|
103
|
-
status: 'truant',
|
|
104
|
-
replicaInfo,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
status: 'existing',
|
|
110
|
-
replicaInfo,
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
getAllNonTruant = (libraryId: string): ReplicaInfoSpec[] => {
|
|
115
|
-
return this.db
|
|
116
|
-
.prepare(
|
|
117
|
-
`
|
|
118
|
-
SELECT * FROM ReplicaInfo
|
|
119
|
-
WHERE libraryId = ? AND lastSeenWallClockTime > ?
|
|
120
|
-
`,
|
|
121
|
-
)
|
|
122
|
-
.all(libraryId, this.truantCutoff);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
getAll = (libraryId: string): ReplicaInfoSpec[] => {
|
|
126
|
-
return this.db
|
|
127
|
-
.prepare(
|
|
128
|
-
`
|
|
129
|
-
SELECT * FROM ReplicaInfo
|
|
130
|
-
WHERE libraryId = ?
|
|
131
|
-
`,
|
|
132
|
-
)
|
|
133
|
-
.all(libraryId);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
updateLastSeen = (libraryId: string, replicaId: string) => {
|
|
137
|
-
const clockTime = new Date().getTime();
|
|
138
|
-
return this.db
|
|
139
|
-
.prepare(
|
|
140
|
-
`
|
|
141
|
-
UPDATE ReplicaInfo
|
|
142
|
-
SET lastSeenWallClockTime = ?
|
|
143
|
-
WHERE id = ? AND libraryId = ?
|
|
144
|
-
`,
|
|
145
|
-
)
|
|
146
|
-
.run(clockTime, replicaId, libraryId);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
updateAcknowledged = (
|
|
150
|
-
libraryId: string,
|
|
151
|
-
replicaId: string,
|
|
152
|
-
timestamp: string,
|
|
153
|
-
) => {
|
|
154
|
-
this.db
|
|
155
|
-
.prepare(
|
|
156
|
-
`
|
|
157
|
-
UPDATE ReplicaInfo
|
|
158
|
-
SET ackedLogicalTime = ?
|
|
159
|
-
WHERE id = ? AND libraryId = ?
|
|
160
|
-
`,
|
|
161
|
-
)
|
|
162
|
-
.run(timestamp, replicaId, libraryId);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
updateServerOrder = (
|
|
166
|
-
libraryId: string,
|
|
167
|
-
replicaId: string,
|
|
168
|
-
serverOrder: number,
|
|
169
|
-
) => {
|
|
170
|
-
// only increment the server order, never set it to lower than before
|
|
171
|
-
this.db
|
|
172
|
-
.prepare(
|
|
173
|
-
`
|
|
174
|
-
UPDATE ReplicaInfo
|
|
175
|
-
SET ackedServerOrder = MAX(ackedServerOrder, ?)
|
|
176
|
-
WHERE id = ? AND libraryId = ?
|
|
177
|
-
`,
|
|
178
|
-
)
|
|
179
|
-
.run(serverOrder, replicaId, libraryId);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
getEarliestAckedServerOrder = (libraryId: string) => {
|
|
183
|
-
// get the earliest acked server order of all non-truant replicas
|
|
184
|
-
const allAcks = (
|
|
185
|
-
this.db
|
|
186
|
-
.prepare(
|
|
187
|
-
`
|
|
188
|
-
SELECT ackedServerOrder FROM ReplicaInfo
|
|
189
|
-
WHERE libraryId = ?
|
|
190
|
-
AND lastSeenWallClockTime > ?
|
|
191
|
-
`,
|
|
192
|
-
)
|
|
193
|
-
.all(libraryId, this.truantCutoff) as {
|
|
194
|
-
ackedServerOrder: number | null;
|
|
195
|
-
}[]
|
|
196
|
-
).map((row: { ackedServerOrder: number | null }) => row.ackedServerOrder);
|
|
197
|
-
|
|
198
|
-
if (allAcks.some((ack) => ack === null)) {
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
return Math.min(...(allAcks as number[]));
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
acknowledgeOperation = (
|
|
205
|
-
libraryId: string,
|
|
206
|
-
replicaId: string,
|
|
207
|
-
timestamp: string,
|
|
208
|
-
) => {
|
|
209
|
-
if (!timestamp) return;
|
|
210
|
-
|
|
211
|
-
// get the server order from the operation with the timestamp and update the replica's server order
|
|
212
|
-
// to the max of the current server order and the server order of the operation
|
|
213
|
-
this.db
|
|
214
|
-
.prepare(
|
|
215
|
-
`
|
|
216
|
-
UPDATE ReplicaInfo
|
|
217
|
-
SET ackedLogicalTime = ?, ackedServerOrder = MAX(ackedServerOrder, COALESCE((
|
|
218
|
-
SELECT serverOrder FROM OperationHistory
|
|
219
|
-
WHERE libraryId = ? AND timestamp = ?
|
|
220
|
-
), 0))
|
|
221
|
-
WHERE id = ? AND libraryId = ?
|
|
222
|
-
`,
|
|
223
|
-
)
|
|
224
|
-
.run(timestamp, libraryId, timestamp, replicaId, libraryId);
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
getGlobalAck = (libraryId: string, onlineReplicaIds?: string[]) => {
|
|
228
|
-
const nonTruant = this.getAllNonTruant(libraryId);
|
|
229
|
-
const globalAckEligible = nonTruant.filter(
|
|
230
|
-
(replica) => replica.type < 2 || onlineReplicaIds?.includes(replica.id),
|
|
231
|
-
);
|
|
232
|
-
// get the earliest acked time
|
|
233
|
-
return globalAckEligible.reduce(
|
|
234
|
-
(acc, replica) => {
|
|
235
|
-
if (!replica.ackedLogicalTime) {
|
|
236
|
-
return acc;
|
|
237
|
-
}
|
|
238
|
-
if (acc === undefined) {
|
|
239
|
-
return replica.ackedLogicalTime;
|
|
240
|
-
}
|
|
241
|
-
return acc < replica.ackedLogicalTime ? acc : replica.ackedLogicalTime;
|
|
242
|
-
},
|
|
243
|
-
undefined as string | undefined,
|
|
244
|
-
);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
delete = (libraryId: string, replicaId: string) => {
|
|
248
|
-
return this.db
|
|
249
|
-
.prepare(
|
|
250
|
-
`
|
|
251
|
-
DELETE FROM ReplicaInfo
|
|
252
|
-
WHERE id = ? AND libraryId = ?
|
|
253
|
-
`,
|
|
254
|
-
)
|
|
255
|
-
.run(replicaId, libraryId);
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
deleteAll = (libraryId: string) => {
|
|
259
|
-
return this.db
|
|
260
|
-
.prepare(
|
|
261
|
-
`
|
|
262
|
-
DELETE FROM ReplicaInfo
|
|
263
|
-
WHERE libraryId = ?
|
|
264
|
-
`,
|
|
265
|
-
)
|
|
266
|
-
.run(libraryId);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
deleteAllForUser = (libraryId: string, userId: string) => {
|
|
270
|
-
return this.db
|
|
271
|
-
.prepare(`DELETE FROM ReplicaInfo WHERE libraryId = ? AND clientId = ?`)
|
|
272
|
-
.run(libraryId, userId);
|
|
273
|
-
};
|
|
274
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Database } from 'better-sqlite3';
|
|
2
|
-
import { FileInfo } from './FileStorage.js';
|
|
3
|
-
|
|
4
|
-
export interface FileMetadataConfig {
|
|
5
|
-
deleteExpirationDays?: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class FileMetadata {
|
|
9
|
-
private deleteExpirationDays = 1;
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
private db: Database,
|
|
13
|
-
{ deleteExpirationDays = 1 }: FileMetadataConfig = {},
|
|
14
|
-
) {
|
|
15
|
-
this.deleteExpirationDays = deleteExpirationDays;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get = (
|
|
19
|
-
libraryId: string,
|
|
20
|
-
fileId: string,
|
|
21
|
-
): {
|
|
22
|
-
libraryId: string;
|
|
23
|
-
fileId: string;
|
|
24
|
-
name: string;
|
|
25
|
-
type: string;
|
|
26
|
-
} => {
|
|
27
|
-
return this.db
|
|
28
|
-
.prepare(
|
|
29
|
-
`
|
|
30
|
-
SELECT * FROM FileMetadata
|
|
31
|
-
WHERE libraryId = ? AND fileId = ?
|
|
32
|
-
`,
|
|
33
|
-
)
|
|
34
|
-
.get(libraryId, fileId);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
getAll = (libraryId: string) => {
|
|
38
|
-
return this.db
|
|
39
|
-
.prepare(
|
|
40
|
-
`
|
|
41
|
-
SELECT * FROM FileMetadata
|
|
42
|
-
WHERE libraryId = ?
|
|
43
|
-
`,
|
|
44
|
-
)
|
|
45
|
-
.all(libraryId);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
deleteAll = (libraryId: string) => {
|
|
49
|
-
return this.db
|
|
50
|
-
.prepare(
|
|
51
|
-
`
|
|
52
|
-
DELETE FROM FileMetadata
|
|
53
|
-
WHERE libraryId = ?
|
|
54
|
-
`,
|
|
55
|
-
)
|
|
56
|
-
.run(libraryId);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
put = (libraryId: string, fileInfo: FileInfo) => {
|
|
60
|
-
return this.db
|
|
61
|
-
.prepare(
|
|
62
|
-
`
|
|
63
|
-
INSERT OR REPLACE INTO FileMetadata (libraryId, fileId, name, type)
|
|
64
|
-
VALUES (?, ?, ?, ?)
|
|
65
|
-
`,
|
|
66
|
-
)
|
|
67
|
-
.run(libraryId, fileInfo.id, fileInfo.fileName, fileInfo.type);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
markPendingDelete = (libraryId: string, fileId: string) => {
|
|
71
|
-
return this.db
|
|
72
|
-
.prepare(
|
|
73
|
-
`
|
|
74
|
-
UPDATE FileMetadata
|
|
75
|
-
SET pendingDeleteAt = ?
|
|
76
|
-
WHERE libraryId = ? AND fileId = ?
|
|
77
|
-
`,
|
|
78
|
-
)
|
|
79
|
-
.run(Date.now(), libraryId, fileId);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
delete = (libraryId: string, fileId: string) => {
|
|
83
|
-
return this.db
|
|
84
|
-
.prepare(
|
|
85
|
-
`
|
|
86
|
-
DELETE FROM FileMetadata
|
|
87
|
-
WHERE libraryId = ? AND fileId = ?
|
|
88
|
-
`,
|
|
89
|
-
)
|
|
90
|
-
.run(libraryId, fileId);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
getPendingDeletes = (libraryId: string) => {
|
|
94
|
-
return this.db
|
|
95
|
-
.prepare(
|
|
96
|
-
`
|
|
97
|
-
SELECT * FROM FileMetadata
|
|
98
|
-
WHERE libraryId = ? AND pendingDeleteAt < ?
|
|
99
|
-
`,
|
|
100
|
-
)
|
|
101
|
-
.all(
|
|
102
|
-
libraryId,
|
|
103
|
-
Date.now() - 1000 * 60 * 60 * 24 * this.deleteExpirationDays,
|
|
104
|
-
) as {
|
|
105
|
-
libraryId: string;
|
|
106
|
-
fileId: string;
|
|
107
|
-
name: string;
|
|
108
|
-
type: string;
|
|
109
|
-
pendingDeleteAt: number;
|
|
110
|
-
}[];
|
|
111
|
-
};
|
|
112
|
-
}
|
package/src/migrations/v0.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export const sql = `
|
|
2
|
-
CREATE TABLE IF NOT EXISTS ReplicaInfo (
|
|
3
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
4
|
-
libraryId TEXT NOT NULL,
|
|
5
|
-
clientId TEXT NOT NULL,
|
|
6
|
-
lastSeenWallClockTime INTEGER,
|
|
7
|
-
ackedLogicalTime TEXT,
|
|
8
|
-
type INTEGER NOT NULL DEFAULT 0
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
CREATE TABLE IF NOT EXISTS OperationHistory (
|
|
12
|
-
libraryId TEXT NOT NULL,
|
|
13
|
-
replicaId TEXT NOT NULL,
|
|
14
|
-
oid TEXT NOT NULL,
|
|
15
|
-
data TEXT NOT NULL,
|
|
16
|
-
timestamp INTEGER NOT NULL,
|
|
17
|
-
PRIMARY KEY (libraryId, replicaId, oid, timestamp)
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
CREATE TABLE IF NOT EXISTS DocumentBaseline (
|
|
21
|
-
oid TEXT PRIMARY KEY NOT NULL,
|
|
22
|
-
snapshot TEXT,
|
|
23
|
-
timestamp TEXT NOT NULL,
|
|
24
|
-
libraryId TEXT NOT NULL
|
|
25
|
-
);
|
|
26
|
-
`;
|
package/src/migrations/v1.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export const sql = `
|
|
2
|
-
ALTER TABLE
|
|
3
|
-
ReplicaInfo RENAME TO ReplicaInfoOld;
|
|
4
|
-
|
|
5
|
-
CREATE TABLE ReplicaInfo (
|
|
6
|
-
id TEXT NOT NULL,
|
|
7
|
-
libraryId TEXT NOT NULL,
|
|
8
|
-
clientId TEXT NOT NULL,
|
|
9
|
-
lastSeenWallClockTime INTEGER,
|
|
10
|
-
ackedLogicalTime TEXT,
|
|
11
|
-
type INTEGER NOT NULL DEFAULT 0,
|
|
12
|
-
PRIMARY KEY (id, libraryId)
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
INSERT INTO
|
|
16
|
-
ReplicaInfo
|
|
17
|
-
SELECT
|
|
18
|
-
id,
|
|
19
|
-
libraryId,
|
|
20
|
-
clientId,
|
|
21
|
-
lastSeenWallClockTime,
|
|
22
|
-
ackedLogicalTime,
|
|
23
|
-
type
|
|
24
|
-
FROM
|
|
25
|
-
ReplicaInfoOld;
|
|
26
|
-
|
|
27
|
-
DROP TABLE ReplicaInfoOld;
|
|
28
|
-
`;
|
package/src/migrations/v2.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// fixes primary key for document baselines to include libraryId
|
|
2
|
-
|
|
3
|
-
export const sql = `
|
|
4
|
-
ALTER TABLE
|
|
5
|
-
DocumentBaseline RENAME TO DocumentBaselineOld;
|
|
6
|
-
|
|
7
|
-
CREATE TABLE DocumentBaseline (
|
|
8
|
-
oid TEXT NOT NULL,
|
|
9
|
-
snapshot TEXT,
|
|
10
|
-
timestamp TEXT NOT NULL,
|
|
11
|
-
libraryId TEXT NOT NULL,
|
|
12
|
-
PRIMARY KEY (libraryId, oid)
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
INSERT INTO
|
|
16
|
-
DocumentBaseline
|
|
17
|
-
SELECT
|
|
18
|
-
oid,
|
|
19
|
-
snapshot,
|
|
20
|
-
timestamp,
|
|
21
|
-
libraryId
|
|
22
|
-
FROM
|
|
23
|
-
DocumentBaselineOld;
|
|
24
|
-
|
|
25
|
-
DROP TABLE DocumentBaselineOld;
|
|
26
|
-
`;
|
package/src/migrations/v3.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// add the FileMetadata table
|
|
2
|
-
|
|
3
|
-
export const sql = `
|
|
4
|
-
CREATE TABLE IF NOT EXISTS FileMetadata (
|
|
5
|
-
libraryId TEXT NOT NULL,
|
|
6
|
-
fileId TEXT NOT NULL,
|
|
7
|
-
name TEXT NOT NULL,
|
|
8
|
-
type TEXT NOT NULL,
|
|
9
|
-
pendingDeleteAt INTEGER,
|
|
10
|
-
PRIMARY KEY (libraryId, fileId)
|
|
11
|
-
);
|
|
12
|
-
`;
|
package/src/migrations/v4.ts
DELETED
package/src/migrations/v5.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
convertLegacyOid,
|
|
3
|
-
replaceLegacyOidsInJsonString,
|
|
4
|
-
} from '@verdant-web/common';
|
|
5
|
-
import SQL from 'better-sqlite3';
|
|
6
|
-
|
|
7
|
-
// read all operations and write them back without legacy IDs.
|
|
8
|
-
export const procedure = (db: SQL.Database) => {
|
|
9
|
-
const operations = db
|
|
10
|
-
.prepare(/* sql */ `SELECT * from OperationHistory;`)
|
|
11
|
-
.all();
|
|
12
|
-
for (const op of operations) {
|
|
13
|
-
db.prepare(
|
|
14
|
-
/* sql */ `
|
|
15
|
-
UPDATE OperationHistory
|
|
16
|
-
SET oid = ?,
|
|
17
|
-
data = ?
|
|
18
|
-
WHERE oid = ? AND timestamp = ? AND libraryId = ?;
|
|
19
|
-
`,
|
|
20
|
-
).run(
|
|
21
|
-
convertLegacyOid(op.oid),
|
|
22
|
-
// data is already a string, so just leave it and do the conversion on the string
|
|
23
|
-
op.data ? replaceLegacyOidsInJsonString(op.data) : op.data,
|
|
24
|
-
op.oid,
|
|
25
|
-
op.timestamp,
|
|
26
|
-
op.libraryId,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
const baselines = db
|
|
30
|
-
.prepare(/* sql */ `SELECT * from DocumentBaseline;`)
|
|
31
|
-
.all();
|
|
32
|
-
for (const baseline of baselines) {
|
|
33
|
-
db.prepare(
|
|
34
|
-
/* sql */ `
|
|
35
|
-
UPDATE DocumentBaseline
|
|
36
|
-
SET oid = ?,
|
|
37
|
-
snapshot = ?
|
|
38
|
-
WHERE oid = ? AND libraryId = ?;
|
|
39
|
-
`,
|
|
40
|
-
).run(
|
|
41
|
-
convertLegacyOid(baseline.oid),
|
|
42
|
-
baseline.snapshot
|
|
43
|
-
? replaceLegacyOidsInJsonString(baseline.snapshot)
|
|
44
|
-
: baseline.snapshot,
|
|
45
|
-
baseline.oid,
|
|
46
|
-
baseline.libraryId,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
};
|
package/src/migrations/v6.ts
DELETED
package/src/migrations/v7.ts
DELETED
package/src/migrations.test.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
|
2
|
-
import create from 'better-sqlite3';
|
|
3
|
-
import { migrations } from './migrations.js';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
describe('the v5->v6 migration', () => {
|
|
7
|
-
let dbFile: string;
|
|
8
|
-
let db: create.Database;
|
|
9
|
-
beforeAll(() => {
|
|
10
|
-
dbFile = `v5-migration-test-${Math.random()}.sqlite`;
|
|
11
|
-
db = create(dbFile);
|
|
12
|
-
});
|
|
13
|
-
afterAll(() => {
|
|
14
|
-
db.close();
|
|
15
|
-
fs.rmSync(dbFile);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('rewrites legacy oids in all ops and baselines', () => {
|
|
19
|
-
// manually migrate to v4 before starting and seed the database with some
|
|
20
|
-
// operations and baselines.
|
|
21
|
-
migrations(db, 5);
|
|
22
|
-
// manually insert some operations and baselines
|
|
23
|
-
db.transaction(() => {
|
|
24
|
-
[
|
|
25
|
-
{
|
|
26
|
-
libraryId: 'test-1',
|
|
27
|
-
replicaId: 'foo',
|
|
28
|
-
oid: 'weirds/b',
|
|
29
|
-
data: {
|
|
30
|
-
op: 'set',
|
|
31
|
-
value: { '@@type': 'ref', id: 'weirds/b.objectMap.one:ghi' },
|
|
32
|
-
},
|
|
33
|
-
timestamp: '1',
|
|
34
|
-
serverOrder: 1,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
libraryId: 'test-1',
|
|
38
|
-
replicaId: 'foo',
|
|
39
|
-
oid: 'weirds/b.list:ghi',
|
|
40
|
-
data: {
|
|
41
|
-
op: 'list-push',
|
|
42
|
-
value: { '@@type': 'ref', id: 'weirds/b.list.#:abc' },
|
|
43
|
-
},
|
|
44
|
-
timestamp: '1',
|
|
45
|
-
serverOrder: 2,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
libraryId: 'test-1',
|
|
49
|
-
replicaId: 'foo',
|
|
50
|
-
oid: 'weirds/b.list:ghi',
|
|
51
|
-
data: {
|
|
52
|
-
op: 'list-remove',
|
|
53
|
-
value: { '@@type': 'ref', id: 'weirds/b.list.#:abc' },
|
|
54
|
-
},
|
|
55
|
-
timestamp: '2',
|
|
56
|
-
serverOrder: 3,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
libraryId: 'test-2',
|
|
60
|
-
replicaId: 'bar',
|
|
61
|
-
oid: 'weirds/b',
|
|
62
|
-
data: {
|
|
63
|
-
op: 'set',
|
|
64
|
-
value: { '@@type': 'ref', id: 'weirds/b.objectMap.one:ghi' },
|
|
65
|
-
},
|
|
66
|
-
timestamp: '1a',
|
|
67
|
-
serverOrder: 1,
|
|
68
|
-
},
|
|
69
|
-
].forEach(
|
|
70
|
-
({ libraryId, replicaId, oid, data, timestamp, serverOrder }) => {
|
|
71
|
-
db.prepare(
|
|
72
|
-
/* sql */ `
|
|
73
|
-
INSERT INTO OperationHistory (libraryId, replicaId, oid, data, timestamp, serverOrder)
|
|
74
|
-
VALUES (?, ?, ?, ?, ?, ?);
|
|
75
|
-
`,
|
|
76
|
-
).run(
|
|
77
|
-
libraryId,
|
|
78
|
-
replicaId,
|
|
79
|
-
oid,
|
|
80
|
-
JSON.stringify(data),
|
|
81
|
-
timestamp,
|
|
82
|
-
serverOrder,
|
|
83
|
-
);
|
|
84
|
-
},
|
|
85
|
-
);
|
|
86
|
-
[
|
|
87
|
-
{
|
|
88
|
-
libraryId: 'test-1',
|
|
89
|
-
oid: 'weirds/b.objectMap.one:ghi',
|
|
90
|
-
snapshot: { '@@type': 'ref', id: 'weirds/b.list.#:abc' },
|
|
91
|
-
timestamp: '1',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
libraryId: 'test-2',
|
|
95
|
-
oid: 'weirds/b.objectMap.one:ghi',
|
|
96
|
-
snapshot: { '@@type': 'ref', id: 'weirds/b.list.#:abc' },
|
|
97
|
-
timestamp: '1a',
|
|
98
|
-
},
|
|
99
|
-
].forEach(({ libraryId, oid, snapshot, timestamp }) => {
|
|
100
|
-
db.prepare(
|
|
101
|
-
/* sql */ `
|
|
102
|
-
INSERT INTO DocumentBaseline (libraryId, oid, snapshot, timestamp)
|
|
103
|
-
VALUES (?, ?, ?, ?);
|
|
104
|
-
`,
|
|
105
|
-
).run(libraryId, oid, JSON.stringify(snapshot), timestamp);
|
|
106
|
-
});
|
|
107
|
-
})();
|
|
108
|
-
|
|
109
|
-
migrations(db);
|
|
110
|
-
|
|
111
|
-
// check that the operations and baselines have been rewritten
|
|
112
|
-
db.transaction(() => {
|
|
113
|
-
const operations = db
|
|
114
|
-
.prepare(
|
|
115
|
-
/* sql */ `
|
|
116
|
-
SELECT * from OperationHistory;
|
|
117
|
-
`,
|
|
118
|
-
)
|
|
119
|
-
.all();
|
|
120
|
-
expect(operations).toMatchSnapshot();
|
|
121
|
-
const baselines = db
|
|
122
|
-
.prepare(
|
|
123
|
-
/* sql */ `
|
|
124
|
-
SELECT * from DocumentBaseline;
|
|
125
|
-
`,
|
|
126
|
-
)
|
|
127
|
-
.all();
|
|
128
|
-
expect(baselines).toMatchSnapshot();
|
|
129
|
-
})();
|
|
130
|
-
|
|
131
|
-
db.close();
|
|
132
|
-
});
|
|
133
|
-
});
|