document-drive 1.0.0-alpha.26 → 1.0.0-alpha.28
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-drive",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.28",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@prisma/client": "5.11.0",
|
|
33
33
|
"document-model": "^1.0.35",
|
|
34
|
-
"document-model-libs": "^1.
|
|
34
|
+
"document-model-libs": "^1.18.1",
|
|
35
35
|
"localforage": "^1.10.0",
|
|
36
36
|
"sequelize": "^6.35.2",
|
|
37
37
|
"sqlite3": "^5.1.7"
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"@types/node": "^20.11.16",
|
|
54
54
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
55
55
|
"@typescript-eslint/parser": "^6.18.1",
|
|
56
|
-
"@vitest/coverage-v8": "^
|
|
56
|
+
"@vitest/coverage-v8": "^1.4.0",
|
|
57
57
|
"document-model": "^1.0.34",
|
|
58
|
-
"document-model-libs": "^1.
|
|
58
|
+
"document-model-libs": "^1.18.1",
|
|
59
59
|
"eslint": "^8.56.0",
|
|
60
60
|
"eslint-config-prettier": "^9.1.0",
|
|
61
61
|
"fake-indexeddb": "^5.0.1",
|
|
@@ -68,6 +68,6 @@
|
|
|
68
68
|
"sequelize": "^6.35.2",
|
|
69
69
|
"sqlite3": "^5.1.7",
|
|
70
70
|
"typescript": "^5.3.2",
|
|
71
|
-
"vitest": "^1.
|
|
71
|
+
"vitest": "^1.4.0"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -121,12 +121,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
121
121
|
const result = await (!strand.documentId
|
|
122
122
|
? this.addDriveOperations(
|
|
123
123
|
strand.driveId,
|
|
124
|
-
operations as Operation<DocumentDriveAction | BaseAction>[]
|
|
124
|
+
operations as Operation<DocumentDriveAction | BaseAction>[],
|
|
125
|
+
false
|
|
125
126
|
)
|
|
126
127
|
: this.addOperations(
|
|
127
128
|
strand.driveId,
|
|
128
129
|
strand.documentId,
|
|
129
|
-
operations
|
|
130
|
+
operations,
|
|
131
|
+
false
|
|
130
132
|
));
|
|
131
133
|
|
|
132
134
|
if (result.status === 'ERROR') {
|
|
@@ -229,7 +231,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
229
231
|
driveId: string,
|
|
230
232
|
documentId?: string[],
|
|
231
233
|
scope?: string[],
|
|
232
|
-
branch?: string[]
|
|
234
|
+
branch?: string[],
|
|
235
|
+
documentType?: string[]
|
|
233
236
|
) {
|
|
234
237
|
const drive = await this.getDrive(driveId);
|
|
235
238
|
|
|
@@ -238,21 +241,23 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
238
241
|
isFileNode(node) &&
|
|
239
242
|
(!documentId?.length ||
|
|
240
243
|
documentId.includes(node.id) ||
|
|
241
|
-
documentId.includes('*'))
|
|
244
|
+
documentId.includes('*')) &&
|
|
245
|
+
(!documentType?.length ||
|
|
246
|
+
documentType.includes(node.documentType) ||
|
|
247
|
+
documentType.includes('*'))
|
|
242
248
|
) as Pick<
|
|
243
249
|
FileNode,
|
|
244
250
|
'id' | 'documentType' | 'scopes' | 'synchronizationUnits'
|
|
245
251
|
>[];
|
|
246
252
|
|
|
247
|
-
if (documentId && !nodes.length) {
|
|
248
|
-
throw new Error('File node not found');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
253
|
// checks if document drive synchronization unit should be added
|
|
252
254
|
if (
|
|
253
|
-
!documentId ||
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
(!documentId ||
|
|
256
|
+
documentId.includes('*') ||
|
|
257
|
+
documentId.includes('')) &&
|
|
258
|
+
(!documentType?.length ||
|
|
259
|
+
documentType.includes('powerhouse/document-drive') ||
|
|
260
|
+
documentType.includes('*'))
|
|
256
261
|
) {
|
|
257
262
|
nodes.unshift({
|
|
258
263
|
id: '',
|
|
@@ -275,8 +280,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
275
280
|
scope?.length || branch?.length
|
|
276
281
|
? node.synchronizationUnits.filter(
|
|
277
282
|
unit =>
|
|
278
|
-
(!scope?.length ||
|
|
279
|
-
|
|
283
|
+
(!scope?.length ||
|
|
284
|
+
scope.includes(unit.scope) ||
|
|
285
|
+
scope.includes('*')) &&
|
|
286
|
+
(!branch?.length ||
|
|
287
|
+
branch.includes(unit.branch) ||
|
|
288
|
+
branch.includes('*'))
|
|
280
289
|
)
|
|
281
290
|
: node.synchronizationUnits;
|
|
282
291
|
if (!nodeUnits.length) {
|
|
@@ -506,31 +515,15 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
506
515
|
|
|
507
516
|
await this.storage.createDocument(driveId, input.id, document);
|
|
508
517
|
|
|
509
|
-
await this.listenerStateManager.addSyncUnits(
|
|
510
|
-
input.synchronizationUnits.map(({ syncId, scope, branch }) => {
|
|
511
|
-
const lastOperation = document.operations[scope].slice().pop();
|
|
512
|
-
return {
|
|
513
|
-
syncId,
|
|
514
|
-
scope,
|
|
515
|
-
branch,
|
|
516
|
-
driveId,
|
|
517
|
-
documentId: input.id,
|
|
518
|
-
documentType: document.documentType,
|
|
519
|
-
lastUpdated:
|
|
520
|
-
lastOperation?.timestamp ?? document.lastModified,
|
|
521
|
-
revision: lastOperation?.index ?? 0
|
|
522
|
-
};
|
|
523
|
-
})
|
|
524
|
-
);
|
|
525
518
|
return document;
|
|
526
519
|
}
|
|
527
520
|
|
|
528
521
|
async deleteDocument(driveId: string, id: string) {
|
|
529
522
|
try {
|
|
530
523
|
const syncUnits = await this.getSynchronizationUnits(driveId, [id]);
|
|
531
|
-
this.listenerStateManager.removeSyncUnits(syncUnits);
|
|
532
|
-
} catch {
|
|
533
|
-
|
|
524
|
+
await this.listenerStateManager.removeSyncUnits(driveId, syncUnits);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
console.warn('Error deleting document', error);
|
|
534
527
|
}
|
|
535
528
|
return this.storage.deleteDocument(driveId, id);
|
|
536
529
|
}
|
|
@@ -788,7 +781,12 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
788
781
|
}
|
|
789
782
|
}
|
|
790
783
|
|
|
791
|
-
async addOperations(
|
|
784
|
+
async addOperations(
|
|
785
|
+
drive: string,
|
|
786
|
+
id: string,
|
|
787
|
+
operations: Operation[],
|
|
788
|
+
forceSync = true
|
|
789
|
+
) {
|
|
792
790
|
let document: Document | undefined;
|
|
793
791
|
const operationsApplied: Operation[] = [];
|
|
794
792
|
const updatedOperations: Operation[] = [];
|
|
@@ -841,29 +839,26 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
841
839
|
branches
|
|
842
840
|
);
|
|
843
841
|
// update listener cache
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
this.updateSyncStatus(drive, 'ERROR', error as Error);
|
|
865
|
-
});
|
|
866
|
-
}
|
|
842
|
+
this.listenerStateManager
|
|
843
|
+
.updateSynchronizationRevisions(
|
|
844
|
+
drive,
|
|
845
|
+
syncUnits,
|
|
846
|
+
() => this.updateSyncStatus(drive, 'SYNCING'),
|
|
847
|
+
this.handleListenerError.bind(this),
|
|
848
|
+
forceSync
|
|
849
|
+
)
|
|
850
|
+
.then(
|
|
851
|
+
updates =>
|
|
852
|
+
updates.length &&
|
|
853
|
+
this.updateSyncStatus(drive, 'SUCCESS')
|
|
854
|
+
)
|
|
855
|
+
.catch(error => {
|
|
856
|
+
console.error(
|
|
857
|
+
'Non handled error updating sync revision',
|
|
858
|
+
error
|
|
859
|
+
);
|
|
860
|
+
this.updateSyncStatus(drive, 'ERROR', error as Error);
|
|
861
|
+
});
|
|
867
862
|
|
|
868
863
|
// after applying all the valid operations,throws
|
|
869
864
|
// an error if there was an invalid operation
|
|
@@ -943,7 +938,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
943
938
|
|
|
944
939
|
async addDriveOperations(
|
|
945
940
|
drive: string,
|
|
946
|
-
operations: Operation<DocumentDriveAction | BaseAction>[]
|
|
941
|
+
operations: Operation<DocumentDriveAction | BaseAction>[],
|
|
942
|
+
forceSync = true
|
|
947
943
|
) {
|
|
948
944
|
let document: DocumentDriveDocument | undefined;
|
|
949
945
|
const operationsApplied: Operation<DocumentDriveAction | BaseAction>[] =
|
|
@@ -994,13 +990,23 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
994
990
|
.pop();
|
|
995
991
|
if (lastOperation) {
|
|
996
992
|
this.listenerStateManager
|
|
997
|
-
.
|
|
993
|
+
.updateSynchronizationRevisions(
|
|
998
994
|
drive,
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
995
|
+
[
|
|
996
|
+
{
|
|
997
|
+
syncId: '0',
|
|
998
|
+
driveId: drive,
|
|
999
|
+
documentId: '',
|
|
1000
|
+
scope: 'global',
|
|
1001
|
+
branch: 'main',
|
|
1002
|
+
documentType: 'powerhouse/document-drive',
|
|
1003
|
+
lastUpdated: lastOperation.timestamp,
|
|
1004
|
+
revision: lastOperation.index
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1002
1007
|
() => this.updateSyncStatus(drive, 'SYNCING'),
|
|
1003
|
-
this.handleListenerError.bind(this)
|
|
1008
|
+
this.handleListenerError.bind(this),
|
|
1009
|
+
forceSync
|
|
1004
1010
|
)
|
|
1005
1011
|
.then(
|
|
1006
1012
|
updates =>
|
|
@@ -19,7 +19,35 @@ import { InternalTransmitter } from './transmitter/internal';
|
|
|
19
19
|
import { SwitchboardPushTransmitter } from './transmitter/switchboard-push';
|
|
20
20
|
import { ITransmitter } from './transmitter/types';
|
|
21
21
|
|
|
22
|
+
function debounce<T extends unknown[], R>(
|
|
23
|
+
func: (...args: T) => Promise<R>,
|
|
24
|
+
delay = 250
|
|
25
|
+
) {
|
|
26
|
+
let timer: number;
|
|
27
|
+
return (immediate: boolean, ...args: T) => {
|
|
28
|
+
if (timer) {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
}
|
|
31
|
+
return new Promise<R>((resolve, reject) => {
|
|
32
|
+
if (immediate) {
|
|
33
|
+
func(...args)
|
|
34
|
+
.then(resolve)
|
|
35
|
+
.catch(reject);
|
|
36
|
+
} else {
|
|
37
|
+
timer = setTimeout(() => {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
39
|
+
func(...args)
|
|
40
|
+
.then(resolve)
|
|
41
|
+
.catch(reject);
|
|
42
|
+
}, delay) as unknown as number;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
export class ListenerManager extends BaseListenerManager {
|
|
49
|
+
static LISTENER_UPDATE_DELAY = 250;
|
|
50
|
+
|
|
23
51
|
async getTransmitter(
|
|
24
52
|
driveId: string,
|
|
25
53
|
listenerId: string
|
|
@@ -30,14 +58,6 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
30
58
|
async addListener(listener: Listener) {
|
|
31
59
|
const drive = listener.driveId;
|
|
32
60
|
|
|
33
|
-
const syncUnits = await this.drive.getSynchronizationUnits(drive);
|
|
34
|
-
const filteredSyncUnits = [];
|
|
35
|
-
for (const syncUnit of syncUnits) {
|
|
36
|
-
if (this._checkFilter(listener.filter, syncUnit)) {
|
|
37
|
-
filteredSyncUnits.push(syncUnit);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
61
|
if (!this.listenerState.has(drive)) {
|
|
42
62
|
this.listenerState.set(drive, new Map());
|
|
43
63
|
}
|
|
@@ -50,11 +70,7 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
50
70
|
pendingTimeout: '0',
|
|
51
71
|
listener,
|
|
52
72
|
listenerStatus: 'CREATED',
|
|
53
|
-
syncUnits:
|
|
54
|
-
...e,
|
|
55
|
-
listenerRev: -1,
|
|
56
|
-
syncRev: e.revision
|
|
57
|
-
}))
|
|
73
|
+
syncUnits: new Map()
|
|
58
74
|
});
|
|
59
75
|
|
|
60
76
|
let transmitter: ITransmitter | undefined;
|
|
@@ -101,17 +117,28 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
101
117
|
return driveMap.delete(listenerId);
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
async
|
|
120
|
+
async removeSyncUnits(driveId: string, syncUnits: SynchronizationUnit[]) {
|
|
121
|
+
const listeners = this.listenerState.get(driveId);
|
|
122
|
+
if (!listeners) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const [, listener] of listeners) {
|
|
126
|
+
for (const syncUnit of syncUnits) {
|
|
127
|
+
listener.syncUnits.delete(syncUnit.syncId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async updateSynchronizationRevisions(
|
|
105
133
|
driveId: string,
|
|
106
|
-
|
|
107
|
-
syncRev: number,
|
|
108
|
-
lastUpdated: string,
|
|
134
|
+
syncUnits: SynchronizationUnit[],
|
|
109
135
|
willUpdate?: (listeners: Listener[]) => void,
|
|
110
136
|
onError?: (
|
|
111
137
|
error: Error,
|
|
112
138
|
driveId: string,
|
|
113
139
|
listener: ListenerState
|
|
114
|
-
) => void
|
|
140
|
+
) => void,
|
|
141
|
+
forceSync = false
|
|
115
142
|
) {
|
|
116
143
|
const drive = this.listenerState.get(driveId);
|
|
117
144
|
if (!drive) {
|
|
@@ -120,100 +147,37 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
120
147
|
|
|
121
148
|
const outdatedListeners: Listener[] = [];
|
|
122
149
|
for (const [, listener] of drive) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
if (
|
|
151
|
+
outdatedListeners.find(
|
|
152
|
+
l => l.listenerId === listener.listener.listenerId
|
|
153
|
+
)
|
|
154
|
+
) {
|
|
127
155
|
continue;
|
|
128
156
|
}
|
|
129
|
-
|
|
130
157
|
for (const syncUnit of syncUnits) {
|
|
131
|
-
if (
|
|
158
|
+
if (!this._checkFilter(listener.listener.filter, syncUnit)) {
|
|
132
159
|
continue;
|
|
133
160
|
}
|
|
134
161
|
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
const listenerRev = listener.syncUnits.get(syncUnit.syncId);
|
|
163
|
+
|
|
137
164
|
if (
|
|
138
|
-
!
|
|
139
|
-
|
|
140
|
-
)
|
|
165
|
+
!listenerRev ||
|
|
166
|
+
listenerRev.listenerRev < syncUnit.revision
|
|
141
167
|
) {
|
|
142
168
|
outdatedListeners.push(listener.listener);
|
|
169
|
+
break;
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
172
|
}
|
|
146
173
|
|
|
147
174
|
if (outdatedListeners.length) {
|
|
148
175
|
willUpdate?.(outdatedListeners);
|
|
149
|
-
return this.triggerUpdate(onError);
|
|
176
|
+
return this.triggerUpdate(forceSync, onError);
|
|
150
177
|
}
|
|
151
178
|
return [];
|
|
152
179
|
}
|
|
153
180
|
|
|
154
|
-
async addSyncUnits(syncUnits: SynchronizationUnit[]) {
|
|
155
|
-
for (const [driveId, drive] of this.listenerState) {
|
|
156
|
-
for (const [id, listenerState] of drive) {
|
|
157
|
-
const transmitter = await this.getTransmitter(driveId, id);
|
|
158
|
-
if (!transmitter) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const filteredSyncUnits = [];
|
|
162
|
-
const { listener } = listenerState;
|
|
163
|
-
for (const syncUnit of syncUnits) {
|
|
164
|
-
if (!this._checkFilter(listener.filter, syncUnit)) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
const existingSyncUnit = listenerState.syncUnits.find(
|
|
168
|
-
unit => unit.syncId === syncUnit.syncId
|
|
169
|
-
);
|
|
170
|
-
if (existingSyncUnit) {
|
|
171
|
-
existingSyncUnit.syncRev = syncUnit.revision;
|
|
172
|
-
existingSyncUnit.lastUpdated = syncUnit.lastUpdated;
|
|
173
|
-
} else {
|
|
174
|
-
filteredSyncUnits.push(syncUnit);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// TODO is this possible?
|
|
179
|
-
if (!this.listenerState.has(driveId)) {
|
|
180
|
-
this.listenerState.set(driveId, new Map());
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const driveMap = this.listenerState.get(driveId)!;
|
|
184
|
-
|
|
185
|
-
// TODO reuse existing state
|
|
186
|
-
driveMap.set(listener.listenerId, {
|
|
187
|
-
block: listener.block,
|
|
188
|
-
driveId: listener.driveId,
|
|
189
|
-
pendingTimeout: '0',
|
|
190
|
-
listener,
|
|
191
|
-
listenerStatus: 'CREATED',
|
|
192
|
-
syncUnits: listenerState.syncUnits.concat(
|
|
193
|
-
filteredSyncUnits.map(e => ({
|
|
194
|
-
...e,
|
|
195
|
-
listenerRev: -1,
|
|
196
|
-
syncRev: e.revision
|
|
197
|
-
}))
|
|
198
|
-
)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
removeSyncUnits(syncUnits: SynchronizationUnit[]) {
|
|
205
|
-
for (const [driveId, drive] of this.listenerState) {
|
|
206
|
-
const syncIds = syncUnits
|
|
207
|
-
.filter(s => s.driveId === driveId)
|
|
208
|
-
.map(s => s.syncId);
|
|
209
|
-
for (const [, listenerState] of drive) {
|
|
210
|
-
listenerState.syncUnits = listenerState.syncUnits.filter(
|
|
211
|
-
s => !syncIds.includes(s.syncId)
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
181
|
async updateListenerRevision(
|
|
218
182
|
listenerId: string,
|
|
219
183
|
driveId: string,
|
|
@@ -230,14 +194,22 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
230
194
|
return;
|
|
231
195
|
}
|
|
232
196
|
|
|
233
|
-
const
|
|
197
|
+
const lastUpdated = new Date().toISOString();
|
|
198
|
+
const entry = listener.syncUnits.get(syncId);
|
|
234
199
|
if (entry) {
|
|
235
200
|
entry.listenerRev = listenerRev;
|
|
236
|
-
entry.lastUpdated =
|
|
201
|
+
entry.lastUpdated = lastUpdated;
|
|
202
|
+
} else {
|
|
203
|
+
listener.syncUnits.set(syncId, { listenerRev, lastUpdated });
|
|
237
204
|
}
|
|
238
205
|
}
|
|
239
206
|
|
|
240
|
-
|
|
207
|
+
triggerUpdate = debounce(
|
|
208
|
+
this._triggerUpdate.bind(this),
|
|
209
|
+
ListenerManager.LISTENER_UPDATE_DELAY
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
private async _triggerUpdate(
|
|
241
213
|
onError?: (
|
|
242
214
|
error: Error,
|
|
243
215
|
driveId: string,
|
|
@@ -252,18 +224,19 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
252
224
|
continue;
|
|
253
225
|
}
|
|
254
226
|
|
|
227
|
+
const syncUnits = await this.getListenerSyncUnits(
|
|
228
|
+
driveId,
|
|
229
|
+
listener.listener.listenerId
|
|
230
|
+
);
|
|
231
|
+
|
|
255
232
|
const strandUpdates: StrandUpdate[] = [];
|
|
256
|
-
for (const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
scope,
|
|
264
|
-
branch
|
|
265
|
-
} = unit;
|
|
266
|
-
if (listenerRev >= syncRev) {
|
|
233
|
+
for (const syncUnit of syncUnits) {
|
|
234
|
+
const unitState = listener.syncUnits.get(syncUnit.syncId);
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
unitState &&
|
|
238
|
+
unitState.listenerRev >= syncUnit.revision
|
|
239
|
+
) {
|
|
267
240
|
continue;
|
|
268
241
|
}
|
|
269
242
|
|
|
@@ -271,9 +244,9 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
271
244
|
try {
|
|
272
245
|
const data = await this.drive.getOperationData(
|
|
273
246
|
driveId,
|
|
274
|
-
syncId,
|
|
247
|
+
syncUnit.syncId,
|
|
275
248
|
{
|
|
276
|
-
fromRevision: listenerRev
|
|
249
|
+
fromRevision: unitState?.listenerRev
|
|
277
250
|
}
|
|
278
251
|
);
|
|
279
252
|
opData.push(...data);
|
|
@@ -287,10 +260,10 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
287
260
|
|
|
288
261
|
strandUpdates.push({
|
|
289
262
|
driveId,
|
|
290
|
-
documentId,
|
|
291
|
-
branch,
|
|
263
|
+
documentId: syncUnit.documentId,
|
|
264
|
+
branch: syncUnit.branch,
|
|
292
265
|
operations: opData,
|
|
293
|
-
scope: scope as OperationScope
|
|
266
|
+
scope: syncUnit.scope as OperationScope
|
|
294
267
|
});
|
|
295
268
|
}
|
|
296
269
|
|
|
@@ -311,15 +284,25 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
311
284
|
listener.pendingTimeout = '0';
|
|
312
285
|
listener.listenerStatus = 'PENDING';
|
|
313
286
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
287
|
+
const lastUpdated = new Date().toISOString();
|
|
288
|
+
|
|
289
|
+
for (const revision of listenerRevisions) {
|
|
290
|
+
const syncUnit = syncUnits.find(
|
|
291
|
+
unit =>
|
|
292
|
+
revision.documentId === unit.documentId &&
|
|
293
|
+
revision.scope === unit.scope &&
|
|
294
|
+
revision.branch === unit.branch
|
|
320
295
|
);
|
|
321
|
-
if (
|
|
322
|
-
|
|
296
|
+
if (syncUnit) {
|
|
297
|
+
listener.syncUnits.set(syncUnit.syncId, {
|
|
298
|
+
lastUpdated,
|
|
299
|
+
listenerRev: revision.revision
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
console.warn(
|
|
303
|
+
`Received revision for untracked unit for listener ${listener.listener.listenerId}`,
|
|
304
|
+
revision
|
|
305
|
+
);
|
|
323
306
|
}
|
|
324
307
|
}
|
|
325
308
|
const revisionError = listenerRevisions.find(
|
|
@@ -328,7 +311,9 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
328
311
|
if (revisionError) {
|
|
329
312
|
throw new OperationError(
|
|
330
313
|
revisionError.status as ErrorStatus,
|
|
331
|
-
undefined
|
|
314
|
+
undefined,
|
|
315
|
+
revisionError.error,
|
|
316
|
+
revisionError.error
|
|
332
317
|
);
|
|
333
318
|
}
|
|
334
319
|
listener.listenerStatus = 'SUCCESS';
|
|
@@ -372,6 +357,21 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
372
357
|
return false;
|
|
373
358
|
}
|
|
374
359
|
|
|
360
|
+
getListenerSyncUnits(driveId: string, listenerId: string) {
|
|
361
|
+
const listener = this.listenerState.get(driveId)?.get(listenerId);
|
|
362
|
+
if (!listener) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const filter = listener.listener.filter;
|
|
366
|
+
return this.drive.getSynchronizationUnits(
|
|
367
|
+
driveId,
|
|
368
|
+
filter.documentId ?? ['*'],
|
|
369
|
+
filter.scope ?? ['*'],
|
|
370
|
+
filter.branch ?? ['*'],
|
|
371
|
+
filter.documentType ?? ['*']
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
375
|
async initDrive(drive: DocumentDriveDocument) {
|
|
376
376
|
const {
|
|
377
377
|
state: {
|
|
@@ -411,23 +411,26 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
411
411
|
since?: string
|
|
412
412
|
): Promise<StrandUpdate[]> {
|
|
413
413
|
// fetch listenerState from listenerManager
|
|
414
|
-
const
|
|
414
|
+
const listener = await this.getListener(driveId, listenerId);
|
|
415
415
|
|
|
416
416
|
// fetch operations from drive and prepare strands
|
|
417
417
|
const strands: StrandUpdate[] = [];
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
|
|
419
|
+
const syncUnits = await this.getListenerSyncUnits(driveId, listenerId);
|
|
420
|
+
|
|
421
|
+
for (const syncUnit of syncUnits) {
|
|
422
|
+
const entry = listener.syncUnits.get(syncUnit.syncId);
|
|
423
|
+
if (entry && entry.listenerRev >= syncUnit.revision) {
|
|
421
424
|
continue;
|
|
422
425
|
}
|
|
423
426
|
|
|
424
|
-
const { documentId, driveId, scope, branch } =
|
|
427
|
+
const { documentId, driveId, scope, branch } = syncUnit;
|
|
425
428
|
const operations = await this.drive.getOperationData(
|
|
426
|
-
|
|
427
|
-
|
|
429
|
+
driveId,
|
|
430
|
+
syncUnit.syncId,
|
|
428
431
|
{
|
|
429
432
|
since,
|
|
430
|
-
fromRevision: entry
|
|
433
|
+
fromRevision: entry?.listenerRev
|
|
431
434
|
}
|
|
432
435
|
);
|
|
433
436
|
|
|
@@ -71,11 +71,13 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
71
71
|
listenerId: string,
|
|
72
72
|
revisions: ListenerRevision[]
|
|
73
73
|
): Promise<boolean> {
|
|
74
|
-
const
|
|
75
|
-
|
|
74
|
+
const syncUnits = await this.manager.getListenerSyncUnits(
|
|
75
|
+
driveId,
|
|
76
|
+
listenerId
|
|
77
|
+
);
|
|
76
78
|
let success = true;
|
|
77
79
|
for (const revision of revisions) {
|
|
78
|
-
const syncUnit =
|
|
80
|
+
const syncUnit = syncUnits.find(
|
|
79
81
|
s =>
|
|
80
82
|
s.scope === revision.scope &&
|
|
81
83
|
s.branch === revision.branch &&
|
|
@@ -83,7 +85,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
83
85
|
s.documentId == revision.documentId
|
|
84
86
|
);
|
|
85
87
|
if (!syncUnit) {
|
|
86
|
-
console.
|
|
88
|
+
console.warn('Unknown sync unit was acknowledged', revision);
|
|
87
89
|
success = false;
|
|
88
90
|
continue;
|
|
89
91
|
}
|
|
@@ -254,11 +256,6 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
254
256
|
: 'SUCCESS',
|
|
255
257
|
error
|
|
256
258
|
});
|
|
257
|
-
|
|
258
|
-
// TODO: Should try to parse remaining strands?
|
|
259
|
-
// if (error) {
|
|
260
|
-
// break;
|
|
261
|
-
// }
|
|
262
259
|
}
|
|
263
260
|
|
|
264
261
|
onRevisions?.(listenerRevisions);
|
package/src/server/types.ts
CHANGED
|
@@ -92,9 +92,12 @@ export type ListenerRevision = {
|
|
|
92
92
|
branch: string;
|
|
93
93
|
status: UpdateStatus;
|
|
94
94
|
revision: number;
|
|
95
|
+
error?: string;
|
|
95
96
|
};
|
|
96
97
|
|
|
97
|
-
export type ListenerRevisionWithError = ListenerRevision & {
|
|
98
|
+
export type ListenerRevisionWithError = Omit<ListenerRevision, 'error'> & {
|
|
99
|
+
error?: Error;
|
|
100
|
+
};
|
|
98
101
|
|
|
99
102
|
export type ListenerUpdate = {
|
|
100
103
|
listenerId: string;
|
|
@@ -163,12 +166,14 @@ export abstract class BaseDocumentDriveServer {
|
|
|
163
166
|
abstract addOperation(
|
|
164
167
|
drive: string,
|
|
165
168
|
id: string,
|
|
166
|
-
operation: Operation
|
|
169
|
+
operation: Operation,
|
|
170
|
+
forceSync?: boolean
|
|
167
171
|
): Promise<IOperationResult>;
|
|
168
172
|
abstract addOperations(
|
|
169
173
|
drive: string,
|
|
170
174
|
id: string,
|
|
171
|
-
operations: Operation[]
|
|
175
|
+
operations: Operation[],
|
|
176
|
+
forceSync?: boolean
|
|
172
177
|
): Promise<IOperationResult>;
|
|
173
178
|
|
|
174
179
|
abstract addDriveOperation(
|
|
@@ -207,7 +212,8 @@ export abstract class BaseDocumentDriveServer {
|
|
|
207
212
|
driveId: string,
|
|
208
213
|
documentId?: string[],
|
|
209
214
|
scope?: string[],
|
|
210
|
-
branch?: string[]
|
|
215
|
+
branch?: string[],
|
|
216
|
+
documentType?: string[]
|
|
211
217
|
): Promise<SynchronizationUnit[]>;
|
|
212
218
|
|
|
213
219
|
abstract getSynchronizationUnit(
|
|
@@ -289,11 +295,9 @@ export abstract class BaseListenerManager {
|
|
|
289
295
|
since?: string
|
|
290
296
|
): Promise<StrandUpdate[]>;
|
|
291
297
|
|
|
292
|
-
abstract
|
|
298
|
+
abstract updateSynchronizationRevisions(
|
|
293
299
|
driveId: string,
|
|
294
|
-
|
|
295
|
-
syncRev: number,
|
|
296
|
-
lastUpdated: string,
|
|
300
|
+
syncUnits: SynchronizationUnit[],
|
|
297
301
|
willUpdate?: (listeners: Listener[]) => void,
|
|
298
302
|
onError?: (
|
|
299
303
|
error: Error,
|
|
@@ -328,11 +332,11 @@ export interface ListenerState {
|
|
|
328
332
|
block: boolean;
|
|
329
333
|
pendingTimeout: string;
|
|
330
334
|
listener: Listener;
|
|
331
|
-
syncUnits:
|
|
335
|
+
syncUnits: Map<SynchronizationUnit['syncId'], SyncronizationUnitState>;
|
|
332
336
|
listenerStatus: ListenerStatus;
|
|
333
337
|
}
|
|
334
338
|
|
|
335
|
-
export interface SyncronizationUnitState
|
|
339
|
+
export interface SyncronizationUnitState {
|
|
336
340
|
listenerRev: number;
|
|
337
|
-
|
|
341
|
+
lastUpdated: string;
|
|
338
342
|
}
|