@verdant-web/server 2.1.3 → 3.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.
- package/dist/esm/ClientConnection.d.ts +1 -0
- 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.d.ts +15 -11
- package/dist/esm/Server.js +29 -30
- package/dist/esm/Server.js.map +1 -1
- package/dist/esm/ServerLibrary.d.ts +10 -17
- package/dist/esm/ServerLibrary.js +151 -127
- 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/TokenVerifier.js.map +1 -1
- package/dist/esm/files/FileStorage.js.map +1 -1
- package/dist/esm/storage/Storage.d.ts +67 -0
- package/dist/esm/storage/Storage.js +2 -0
- package/dist/esm/storage/Storage.js.map +1 -0
- package/dist/esm/storage/index.d.ts +2 -0
- package/dist/esm/storage/index.js +3 -0
- package/dist/esm/storage/index.js.map +1 -0
- package/dist/esm/storage/sql/SqlBaselines.d.ts +22 -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.d.ts +18 -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.d.ts +19 -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.d.ts +32 -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.d.ts +3 -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.d.ts +5 -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.d.ts +5 -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.d.ts +40 -0
- package/dist/esm/storage/sql/tables.js +2 -0
- package/dist/esm/storage/sql/tables.js.map +1 -0
- package/dist/esm/types.d.ts +14 -5
- package/package.json +20 -14
- package/src/Server.ts +37 -43
- package/src/ServerLibrary.test.ts +447 -0
- package/src/ServerLibrary.ts +239 -163
- 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 +15 -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 -167
- package/dist/cjs/Server.js +0 -552
- package/dist/cjs/Server.js.map +0 -1
- package/dist/cjs/ServerLibrary.d.ts +0 -65
- package/dist/cjs/ServerLibrary.js +0 -505
- package/dist/cjs/ServerLibrary.js.map +0 -1
- package/dist/cjs/TokenProvider.d.ts +0 -19
- 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 -10
- 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.js +0 -121
- package/dist/cjs/migrations.test.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -38
- 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/OperationHistory.d.ts +0 -36
- package/dist/esm/OperationHistory.js +0 -161
- package/dist/esm/OperationHistory.js.map +0 -1
- 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/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/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/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/dist/{cjs/migrations.test.d.ts → esm/ServerLibrary.test.d.ts} +0 -0
package/src/Baselines.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
applyPatch,
|
|
3
|
-
DocumentBaseline,
|
|
4
|
-
getAllFileFields,
|
|
5
|
-
Operation,
|
|
6
|
-
Ref,
|
|
7
|
-
} from '@verdant-web/common';
|
|
8
|
-
import { Database } from 'better-sqlite3';
|
|
9
|
-
import { DocumentBaselineSpec } from './types.js';
|
|
10
|
-
|
|
11
|
-
export class Baselines {
|
|
12
|
-
constructor(private db: Database) {}
|
|
13
|
-
|
|
14
|
-
get = (libraryId: string, oid: string): DocumentBaseline<any> | null => {
|
|
15
|
-
const row = this.db
|
|
16
|
-
.prepare(
|
|
17
|
-
`
|
|
18
|
-
SELECT * FROM DocumentBaseline
|
|
19
|
-
WHERE libraryId = ? AND oid = ?
|
|
20
|
-
`,
|
|
21
|
-
)
|
|
22
|
-
.get(libraryId, oid);
|
|
23
|
-
|
|
24
|
-
if (!row) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return this.hydrateSnapshot(row);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
getAllWithSubObjects = (
|
|
32
|
-
libraryId: string,
|
|
33
|
-
oid: string,
|
|
34
|
-
): DocumentBaseline<any>[] => {
|
|
35
|
-
const rows = this.db
|
|
36
|
-
.prepare(
|
|
37
|
-
`
|
|
38
|
-
SELECT * FROM DocumentBaseline
|
|
39
|
-
WHERE libraryId = ? AND oid LIKE ?
|
|
40
|
-
`,
|
|
41
|
-
)
|
|
42
|
-
.all(libraryId, `${oid}%`);
|
|
43
|
-
|
|
44
|
-
return rows.map(this.hydrateSnapshot);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
set = (libraryId: string, baseline: DocumentBaseline) => {
|
|
48
|
-
if (!baseline.snapshot) {
|
|
49
|
-
return this.db
|
|
50
|
-
.prepare(
|
|
51
|
-
`
|
|
52
|
-
DELETE FROM DocumentBaseline
|
|
53
|
-
WHERE libraryId = ? AND oid = ?
|
|
54
|
-
`,
|
|
55
|
-
)
|
|
56
|
-
.run(libraryId, baseline.oid);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return this.db
|
|
60
|
-
.prepare(
|
|
61
|
-
`
|
|
62
|
-
INSERT OR REPLACE INTO DocumentBaseline (libraryId, oid, snapshot, timestamp)
|
|
63
|
-
VALUES (?, ?, ?, ?)
|
|
64
|
-
`,
|
|
65
|
-
)
|
|
66
|
-
.run(
|
|
67
|
-
libraryId,
|
|
68
|
-
baseline.oid,
|
|
69
|
-
JSON.stringify(baseline.snapshot),
|
|
70
|
-
baseline.timestamp,
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
insertAll = (libraryId: string, baselines: DocumentBaseline[]) => {
|
|
75
|
-
const tx = this.db.transaction(() => {
|
|
76
|
-
for (const baseline of baselines) {
|
|
77
|
-
this.db
|
|
78
|
-
.prepare(
|
|
79
|
-
`
|
|
80
|
-
INSERT OR REPLACE INTO DocumentBaseline (libraryId, oid, snapshot, timestamp)
|
|
81
|
-
VALUES (?, ?, ?, ?)
|
|
82
|
-
`,
|
|
83
|
-
)
|
|
84
|
-
.run(
|
|
85
|
-
libraryId,
|
|
86
|
-
baseline.oid,
|
|
87
|
-
JSON.stringify(baseline.snapshot),
|
|
88
|
-
baseline.timestamp,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
tx();
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
private hydrateSnapshot = (
|
|
96
|
-
snapshot: DocumentBaselineSpec,
|
|
97
|
-
): DocumentBaseline<any> => {
|
|
98
|
-
return {
|
|
99
|
-
...snapshot,
|
|
100
|
-
snapshot: JSON.parse(snapshot.snapshot),
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
getAllAfter = (
|
|
105
|
-
libraryId: string,
|
|
106
|
-
timestamp: string | null,
|
|
107
|
-
): DocumentBaseline<any>[] => {
|
|
108
|
-
if (!timestamp) {
|
|
109
|
-
return this.db
|
|
110
|
-
.prepare(
|
|
111
|
-
`
|
|
112
|
-
SELECT * FROM DocumentBaseline
|
|
113
|
-
WHERE libraryId = ?
|
|
114
|
-
ORDER BY timestamp ASC
|
|
115
|
-
`,
|
|
116
|
-
)
|
|
117
|
-
.all(libraryId)
|
|
118
|
-
.map(this.hydrateSnapshot);
|
|
119
|
-
}
|
|
120
|
-
return this.db
|
|
121
|
-
.prepare(
|
|
122
|
-
`
|
|
123
|
-
SELECT * FROM DocumentBaseline
|
|
124
|
-
WHERE libraryId = ? AND timestamp > ?
|
|
125
|
-
ORDER BY timestamp ASC
|
|
126
|
-
`,
|
|
127
|
-
)
|
|
128
|
-
.all(libraryId, timestamp)
|
|
129
|
-
.map(this.hydrateSnapshot);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
getFromServerOrder = (
|
|
133
|
-
libraryId: string,
|
|
134
|
-
serverOrder: number,
|
|
135
|
-
): DocumentBaseline<any>[] => {
|
|
136
|
-
return this.db
|
|
137
|
-
.prepare(
|
|
138
|
-
`
|
|
139
|
-
SELECT * FROM DocumentBaseline
|
|
140
|
-
WHERE libraryId = ? AND serverOrder >= ?
|
|
141
|
-
ORDER BY serverOrder ASC
|
|
142
|
-
`,
|
|
143
|
-
)
|
|
144
|
-
.all(libraryId, serverOrder)
|
|
145
|
-
.map(this.hydrateSnapshot);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
applyOperations = (
|
|
149
|
-
libraryId: string,
|
|
150
|
-
oid: string,
|
|
151
|
-
operations: Operation[],
|
|
152
|
-
deletedRefs?: Ref[],
|
|
153
|
-
) => {
|
|
154
|
-
if (operations.length === 0) return [];
|
|
155
|
-
|
|
156
|
-
let baseline = this.get(libraryId, oid);
|
|
157
|
-
if (!baseline) {
|
|
158
|
-
baseline = {
|
|
159
|
-
oid,
|
|
160
|
-
snapshot: {},
|
|
161
|
-
timestamp: operations[0].timestamp,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
for (const operation of operations) {
|
|
165
|
-
baseline.snapshot = applyPatch(
|
|
166
|
-
baseline.snapshot,
|
|
167
|
-
operation.data,
|
|
168
|
-
deletedRefs,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
baseline.timestamp = operations[operations.length - 1].timestamp;
|
|
172
|
-
this.set(libraryId, baseline);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
deleteAll = (libraryId: string) => {
|
|
176
|
-
this.db
|
|
177
|
-
.prepare(
|
|
178
|
-
`
|
|
179
|
-
DELETE FROM DocumentBaseline
|
|
180
|
-
WHERE libraryId = ?
|
|
181
|
-
`,
|
|
182
|
-
)
|
|
183
|
-
.run(libraryId);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
getCount = (libraryId: string): number => {
|
|
187
|
-
return (
|
|
188
|
-
this.db
|
|
189
|
-
.prepare(
|
|
190
|
-
`
|
|
191
|
-
SELECT COUNT(*) AS count FROM DocumentBaseline
|
|
192
|
-
WHERE libraryId = ?
|
|
193
|
-
`,
|
|
194
|
-
)
|
|
195
|
-
.get(libraryId)?.count ?? 0
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
}
|
package/src/OperationHistory.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { Operation } from '@verdant-web/common';
|
|
2
|
-
import { Database } from 'better-sqlite3';
|
|
3
|
-
import { OperationSpec } from './types.js';
|
|
4
|
-
|
|
5
|
-
type StoredOperationHistoryItem = Omit<Operation, 'data'> & {
|
|
6
|
-
data: string;
|
|
7
|
-
libraryId: string;
|
|
8
|
-
replicaId: string;
|
|
9
|
-
serverOrder: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export class OperationHistory {
|
|
13
|
-
constructor(private db: Database) {}
|
|
14
|
-
|
|
15
|
-
private hydratePatch = ({
|
|
16
|
-
libraryId: _,
|
|
17
|
-
data,
|
|
18
|
-
...rest
|
|
19
|
-
}: StoredOperationHistoryItem): OperationSpec => {
|
|
20
|
-
return {
|
|
21
|
-
...rest,
|
|
22
|
-
data: JSON.parse(data),
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
getAllFor = (libraryId: string, oid: string) => {
|
|
27
|
-
return this.db
|
|
28
|
-
.prepare(
|
|
29
|
-
`
|
|
30
|
-
SELECT * FROM OperationHistory
|
|
31
|
-
WHERE libraryId = ? AND oid = ?
|
|
32
|
-
ORDER BY timestamp ASC
|
|
33
|
-
`,
|
|
34
|
-
)
|
|
35
|
-
.all(libraryId, oid)
|
|
36
|
-
.map(this.hydratePatch);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
getEarliest = (libraryId: string, count: number) => {
|
|
40
|
-
return this.db
|
|
41
|
-
.prepare(
|
|
42
|
-
`
|
|
43
|
-
SELECT * FROM OperationHistory
|
|
44
|
-
WHERE libraryId = ?
|
|
45
|
-
ORDER BY timestamp ASC
|
|
46
|
-
LIMIT ?
|
|
47
|
-
`,
|
|
48
|
-
)
|
|
49
|
-
.all(libraryId, count)
|
|
50
|
-
.map(this.hydratePatch);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
getAfter = (libraryId: string, timestamp: string | null = null) => {
|
|
54
|
-
if (timestamp === null) {
|
|
55
|
-
return this.db
|
|
56
|
-
.prepare(
|
|
57
|
-
`
|
|
58
|
-
SELECT * FROM OperationHistory
|
|
59
|
-
WHERE libraryId = ?
|
|
60
|
-
ORDER BY timestamp ASC
|
|
61
|
-
`,
|
|
62
|
-
)
|
|
63
|
-
.all(libraryId)
|
|
64
|
-
.map(this.hydratePatch);
|
|
65
|
-
}
|
|
66
|
-
return this.db
|
|
67
|
-
.prepare(
|
|
68
|
-
`
|
|
69
|
-
SELECT * FROM OperationHistory
|
|
70
|
-
WHERE libraryId = ? AND timestamp > ?
|
|
71
|
-
ORDER BY timestamp ASC
|
|
72
|
-
`,
|
|
73
|
-
)
|
|
74
|
-
.all(libraryId, timestamp)
|
|
75
|
-
.map(this.hydratePatch);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Returns all operations before the given server order
|
|
80
|
-
* in ascending chronological order
|
|
81
|
-
*/
|
|
82
|
-
getBeforeServerOrder = (libraryId: string, beforeServerOrder: number) => {
|
|
83
|
-
return this.db
|
|
84
|
-
.prepare(
|
|
85
|
-
`
|
|
86
|
-
SELECT * FROM OperationHistory
|
|
87
|
-
WHERE libraryId = ? AND serverOrder < ?
|
|
88
|
-
ORDER BY timestamp ASC
|
|
89
|
-
`,
|
|
90
|
-
)
|
|
91
|
-
.all(libraryId, beforeServerOrder)
|
|
92
|
-
.map(this.hydratePatch);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Returns all operations after a given server order. Useful
|
|
97
|
-
* for retrieving operations that were created after a client
|
|
98
|
-
* last synced.
|
|
99
|
-
*/
|
|
100
|
-
getFromServerOrder = (libraryId: string, serverOrder: number) => {
|
|
101
|
-
const result = this.db
|
|
102
|
-
.prepare(
|
|
103
|
-
`
|
|
104
|
-
SELECT * FROM OperationHistory
|
|
105
|
-
WHERE libraryId = ? AND serverOrder > ?
|
|
106
|
-
`,
|
|
107
|
-
)
|
|
108
|
-
.all(libraryId, serverOrder);
|
|
109
|
-
|
|
110
|
-
return (result || []).map(this.hydratePatch);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
getLatestServerOrder = (libraryId: string): number => {
|
|
114
|
-
const result = this.db
|
|
115
|
-
.prepare(
|
|
116
|
-
`
|
|
117
|
-
SELECT MAX(serverOrder) AS serverOrder FROM OperationHistory
|
|
118
|
-
WHERE libraryId = ?
|
|
119
|
-
`,
|
|
120
|
-
)
|
|
121
|
-
.get(libraryId);
|
|
122
|
-
|
|
123
|
-
return result?.serverOrder || 0;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
getCount = (libraryId: string): number => {
|
|
127
|
-
const result = this.db
|
|
128
|
-
.prepare(
|
|
129
|
-
`
|
|
130
|
-
SELECT COUNT(*) AS count FROM OperationHistory
|
|
131
|
-
WHERE libraryId = ?
|
|
132
|
-
`,
|
|
133
|
-
)
|
|
134
|
-
.get(libraryId);
|
|
135
|
-
|
|
136
|
-
return result?.count || 0;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Adds a new operation or replaces an existing one.
|
|
141
|
-
* The server order will be set to the current max + 1
|
|
142
|
-
*/
|
|
143
|
-
insert = (libraryId: string, replicaId: string, item: Operation) => {
|
|
144
|
-
return this.db
|
|
145
|
-
.prepare(
|
|
146
|
-
`
|
|
147
|
-
INSERT OR REPLACE INTO OperationHistory (libraryId, oid, data, timestamp, replicaId, serverOrder)
|
|
148
|
-
VALUES (?, ?, ?, ?, ?, (SELECT COALESCE(MAX(serverOrder), 0) FROM OperationHistory WHERE libraryId = ?) + 1)
|
|
149
|
-
`,
|
|
150
|
-
)
|
|
151
|
-
.run(
|
|
152
|
-
libraryId,
|
|
153
|
-
item.oid,
|
|
154
|
-
JSON.stringify(item.data),
|
|
155
|
-
item.timestamp,
|
|
156
|
-
replicaId,
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Inserts all operations and updates the server
|
|
162
|
-
* order for this library
|
|
163
|
-
*/
|
|
164
|
-
insertAll = async (
|
|
165
|
-
libraryId: string,
|
|
166
|
-
replicaId: string,
|
|
167
|
-
items: Operation[],
|
|
168
|
-
) => {
|
|
169
|
-
const tx = this.db.transaction(() => {
|
|
170
|
-
let orderResult = this.db
|
|
171
|
-
.prepare(
|
|
172
|
-
`
|
|
173
|
-
SELECT COALESCE(MAX(serverOrder), 0) AS serverOrder FROM OperationHistory WHERE libraryId = ?
|
|
174
|
-
`,
|
|
175
|
-
)
|
|
176
|
-
.get(libraryId);
|
|
177
|
-
let currentServerOrder = orderResult.serverOrder || 0;
|
|
178
|
-
const insertStatement = this.db.prepare(
|
|
179
|
-
`
|
|
180
|
-
INSERT OR REPLACE INTO OperationHistory (libraryId, oid, data, timestamp, replicaId, serverOrder)
|
|
181
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
182
|
-
`,
|
|
183
|
-
);
|
|
184
|
-
for (const item of items) {
|
|
185
|
-
insertStatement.run(
|
|
186
|
-
libraryId,
|
|
187
|
-
item.oid,
|
|
188
|
-
JSON.stringify(item.data),
|
|
189
|
-
item.timestamp,
|
|
190
|
-
replicaId,
|
|
191
|
-
++currentServerOrder,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
tx();
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
dropAll = async (
|
|
200
|
-
libraryId: string,
|
|
201
|
-
items: Pick<OperationSpec, 'replicaId' | 'oid' | 'timestamp'>[],
|
|
202
|
-
) => {
|
|
203
|
-
const deleteStatement = this.db.prepare(
|
|
204
|
-
`
|
|
205
|
-
DELETE FROM OperationHistory
|
|
206
|
-
WHERE libraryId = ? AND replicaId = ? AND oid = ? AND timestamp = ?
|
|
207
|
-
`,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
this.db.transaction(() => {
|
|
211
|
-
for (const item of items) {
|
|
212
|
-
deleteStatement.run(
|
|
213
|
-
libraryId,
|
|
214
|
-
item.replicaId,
|
|
215
|
-
item.oid,
|
|
216
|
-
item.timestamp,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
})();
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
deleteAll = (libraryId: string) => {
|
|
223
|
-
this.db
|
|
224
|
-
.prepare(
|
|
225
|
-
`
|
|
226
|
-
DELETE FROM OperationHistory
|
|
227
|
-
WHERE libraryId = ?
|
|
228
|
-
`,
|
|
229
|
-
)
|
|
230
|
-
.run(libraryId);
|
|
231
|
-
};
|
|
232
|
-
}
|
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
|
-
}
|