document-drive 0.0.30 → 1.0.0-alpha.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-drive",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"lint": "eslint src --ext .js,.jsx,.ts,.tsx && yarn check-types",
|
|
25
25
|
"lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
|
|
26
26
|
"format": "prettier . --write",
|
|
27
|
+
"release": "semantic-release",
|
|
27
28
|
"test": "vitest run --coverage",
|
|
28
29
|
"test:watch": "vitest watch"
|
|
29
30
|
},
|
|
@@ -43,7 +44,11 @@
|
|
|
43
44
|
"sanitize-filename": "^1.6.3"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@commitlint/cli": "^18.6.1",
|
|
48
|
+
"@commitlint/config-conventional": "^18.6.2",
|
|
46
49
|
"@prisma/client": "5.8.1",
|
|
50
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
51
|
+
"@semantic-release/git": "^10.0.1",
|
|
47
52
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
48
53
|
"@types/node": "^20.11.16",
|
|
49
54
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
@@ -58,6 +63,7 @@
|
|
|
58
63
|
"msw": "^2.1.2",
|
|
59
64
|
"prettier": "^3.1.1",
|
|
60
65
|
"prettier-plugin-organize-imports": "^3.2.4",
|
|
66
|
+
"semantic-release": "^23.0.2",
|
|
61
67
|
"sequelize": "^6.35.2",
|
|
62
68
|
"sqlite3": "^5.1.7",
|
|
63
69
|
"typescript": "^5.3.2",
|
package/src/server/index.ts
CHANGED
|
@@ -138,9 +138,9 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
138
138
|
|
|
139
139
|
if (!driveTriggers) {
|
|
140
140
|
driveTriggers = new Map();
|
|
141
|
-
this.updateSyncStatus(driveId, 'SYNCING');
|
|
142
141
|
}
|
|
143
142
|
|
|
143
|
+
this.updateSyncStatus(driveId, 'SYNCING');
|
|
144
144
|
if (PullResponderTransmitter.isPullResponderTrigger(trigger)) {
|
|
145
145
|
const intervalId = PullResponderTransmitter.setupPull(
|
|
146
146
|
driveId,
|
|
@@ -786,8 +786,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
786
786
|
syncUnit.syncId,
|
|
787
787
|
syncUnit.revision,
|
|
788
788
|
syncUnit.lastUpdated,
|
|
789
|
+
() => this.updateSyncStatus(drive, 'SYNCING'),
|
|
789
790
|
this.handleListenerError.bind(this)
|
|
790
791
|
)
|
|
792
|
+
.then(
|
|
793
|
+
updates =>
|
|
794
|
+
updates.length &&
|
|
795
|
+
this.updateSyncStatus(drive, 'SUCCESS')
|
|
796
|
+
)
|
|
791
797
|
.catch(error => {
|
|
792
798
|
console.error(
|
|
793
799
|
'Non handled error updating sync revision',
|
|
@@ -917,8 +923,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
917
923
|
'0',
|
|
918
924
|
lastOperation.index,
|
|
919
925
|
lastOperation.timestamp,
|
|
926
|
+
() => this.updateSyncStatus(drive, 'SYNCING'),
|
|
920
927
|
this.handleListenerError.bind(this)
|
|
921
928
|
)
|
|
929
|
+
.then(
|
|
930
|
+
updates =>
|
|
931
|
+
updates.length &&
|
|
932
|
+
this.updateSyncStatus(drive, 'SUCCESS')
|
|
933
|
+
)
|
|
922
934
|
.catch(error => {
|
|
923
935
|
console.error(
|
|
924
936
|
'Non handled error updating sync revision',
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ErrorStatus,
|
|
10
10
|
Listener,
|
|
11
11
|
ListenerState,
|
|
12
|
+
ListenerUpdate,
|
|
12
13
|
OperationUpdate,
|
|
13
14
|
StrandUpdate,
|
|
14
15
|
SynchronizationUnit
|
|
@@ -122,6 +123,7 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
122
123
|
syncId: string,
|
|
123
124
|
syncRev: number,
|
|
124
125
|
lastUpdated: string,
|
|
126
|
+
willUpdate?: (listeners: Listener[]) => void,
|
|
125
127
|
onError?: (
|
|
126
128
|
error: Error,
|
|
127
129
|
driveId: string,
|
|
@@ -130,10 +132,10 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
130
132
|
) {
|
|
131
133
|
const drive = this.listenerState.get(driveId);
|
|
132
134
|
if (!drive) {
|
|
133
|
-
return;
|
|
135
|
+
return [];
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
const outdatedListeners: Listener[] = [];
|
|
137
139
|
for (const [, listener] of drive) {
|
|
138
140
|
const syncUnits = listener.syncUnits.filter(
|
|
139
141
|
e => e.syncId === syncId
|
|
@@ -149,13 +151,21 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
149
151
|
|
|
150
152
|
syncUnit.syncRev = syncRev;
|
|
151
153
|
syncUnit.lastUpdated = lastUpdated;
|
|
152
|
-
|
|
154
|
+
if (
|
|
155
|
+
!outdatedListeners.find(
|
|
156
|
+
l => l.listenerId === listener.listener.listenerId
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
outdatedListeners.push(listener.listener);
|
|
160
|
+
}
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
if (
|
|
164
|
+
if (outdatedListeners.length) {
|
|
165
|
+
willUpdate?.(outdatedListeners);
|
|
157
166
|
return this.triggerUpdate(onError);
|
|
158
167
|
}
|
|
168
|
+
return [];
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
async addSyncUnits(syncUnits: SynchronizationUnit[]) {
|
|
@@ -251,6 +261,7 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
251
261
|
listener: ListenerState
|
|
252
262
|
) => void
|
|
253
263
|
) {
|
|
264
|
+
const listenerUpdates: ListenerUpdate[] = [];
|
|
254
265
|
for (const [driveId, drive] of this.listenerState) {
|
|
255
266
|
for (const [id, listener] of drive) {
|
|
256
267
|
const transmitter = await this.getTransmitter(driveId, id);
|
|
@@ -338,6 +349,10 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
338
349
|
);
|
|
339
350
|
}
|
|
340
351
|
listener.listenerStatus = 'SUCCESS';
|
|
352
|
+
listenerUpdates.push({
|
|
353
|
+
listenerId: listener.listener.listenerId,
|
|
354
|
+
listenerRevisions
|
|
355
|
+
});
|
|
341
356
|
} catch (e) {
|
|
342
357
|
// TODO: Handle error based on listener params (blocking, retry, etc)
|
|
343
358
|
onError?.(e as Error, driveId, listener);
|
|
@@ -346,6 +361,7 @@ export class ListenerManager extends BaseListenerManager {
|
|
|
346
361
|
}
|
|
347
362
|
}
|
|
348
363
|
}
|
|
364
|
+
return listenerUpdates;
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
private _checkFilter(
|
|
@@ -218,6 +218,87 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
218
218
|
return result.acknowledge;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
private static async executePull(
|
|
222
|
+
driveId: string,
|
|
223
|
+
trigger: PullResponderTrigger,
|
|
224
|
+
onStrandUpdate: (strand: StrandUpdate) => Promise<IOperationResult>,
|
|
225
|
+
onError: (error: Error) => void,
|
|
226
|
+
onAcknowledge?: (success: boolean) => void
|
|
227
|
+
) {
|
|
228
|
+
try {
|
|
229
|
+
const { url, listenerId } = trigger.data;
|
|
230
|
+
const strands = await PullResponderTransmitter.pullStrands(
|
|
231
|
+
driveId,
|
|
232
|
+
url,
|
|
233
|
+
listenerId
|
|
234
|
+
// since ?
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// if there are no new strands then do nothing
|
|
238
|
+
if (!strands.length) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const listenerRevisions: ListenerRevision[] = [];
|
|
243
|
+
|
|
244
|
+
for (const strand of strands) {
|
|
245
|
+
const operations: Operation[] = strand.operations.map(
|
|
246
|
+
({ index, type, hash, input, skip, timestamp }) => ({
|
|
247
|
+
index,
|
|
248
|
+
type,
|
|
249
|
+
hash,
|
|
250
|
+
input,
|
|
251
|
+
skip,
|
|
252
|
+
timestamp,
|
|
253
|
+
scope: strand.scope,
|
|
254
|
+
branch: strand.branch
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
let error: Error | undefined = undefined;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = await onStrandUpdate(strand);
|
|
262
|
+
if (result.error) {
|
|
263
|
+
throw result.error;
|
|
264
|
+
}
|
|
265
|
+
} catch (e) {
|
|
266
|
+
error = e as Error;
|
|
267
|
+
onError(error);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
listenerRevisions.push({
|
|
271
|
+
branch: strand.branch,
|
|
272
|
+
documentId: strand.documentId || '',
|
|
273
|
+
driveId: strand.driveId,
|
|
274
|
+
revision: operations.pop()?.index ?? -1,
|
|
275
|
+
scope: strand.scope as OperationScope,
|
|
276
|
+
status: error
|
|
277
|
+
? error instanceof OperationError
|
|
278
|
+
? error.status
|
|
279
|
+
: 'ERROR'
|
|
280
|
+
: 'SUCCESS'
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// TODO: Should try to parse remaining strands?
|
|
284
|
+
if (error) {
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await PullResponderTransmitter.acknowledgeStrands(
|
|
290
|
+
driveId,
|
|
291
|
+
url,
|
|
292
|
+
listenerId,
|
|
293
|
+
listenerRevisions
|
|
294
|
+
)
|
|
295
|
+
.then(result => onAcknowledge?.(result))
|
|
296
|
+
.catch(error => console.error('ACK error', error));
|
|
297
|
+
} catch (error) {
|
|
298
|
+
onError(error as Error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
221
302
|
static setupPull(
|
|
222
303
|
driveId: string,
|
|
223
304
|
trigger: PullResponderTrigger,
|
|
@@ -225,7 +306,7 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
225
306
|
onError: (error: Error) => void,
|
|
226
307
|
onAcknowledge?: (success: boolean) => void
|
|
227
308
|
): number {
|
|
228
|
-
const {
|
|
309
|
+
const { interval } = trigger.data;
|
|
229
310
|
let loopInterval = PULL_DRIVE_INTERVAL;
|
|
230
311
|
if (interval) {
|
|
231
312
|
try {
|
|
@@ -238,79 +319,25 @@ export class PullResponderTransmitter implements ITransmitter {
|
|
|
238
319
|
}
|
|
239
320
|
}
|
|
240
321
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// if there are no new strands then do nothing
|
|
251
|
-
if (!strands.length) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const listenerRevisions: ListenerRevision[] = [];
|
|
256
|
-
|
|
257
|
-
for (const strand of strands) {
|
|
258
|
-
const operations: Operation[] = strand.operations.map(
|
|
259
|
-
({ index, type, hash, input, skip, timestamp }) => ({
|
|
260
|
-
index,
|
|
261
|
-
type,
|
|
262
|
-
hash,
|
|
263
|
-
input,
|
|
264
|
-
skip,
|
|
265
|
-
timestamp,
|
|
266
|
-
scope: strand.scope,
|
|
267
|
-
branch: strand.branch
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
let error: Error | undefined = undefined;
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const result = await onStrandUpdate(strand);
|
|
275
|
-
if (result.error) {
|
|
276
|
-
throw result.error;
|
|
277
|
-
}
|
|
278
|
-
} catch (e) {
|
|
279
|
-
error = e as Error;
|
|
280
|
-
onError?.(error);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
listenerRevisions.push({
|
|
284
|
-
branch: strand.branch,
|
|
285
|
-
documentId: strand.documentId ?? '',
|
|
286
|
-
driveId: strand.driveId,
|
|
287
|
-
revision: operations.pop()?.index ?? -1,
|
|
288
|
-
scope: strand.scope as OperationScope,
|
|
289
|
-
status: error
|
|
290
|
-
? error instanceof OperationError
|
|
291
|
-
? error.status
|
|
292
|
-
: 'ERROR'
|
|
293
|
-
: 'SUCCESS'
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// TODO: Should try to parse remaining strands?
|
|
297
|
-
if (error) {
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
322
|
+
this.executePull(
|
|
323
|
+
driveId,
|
|
324
|
+
trigger,
|
|
325
|
+
onStrandUpdate,
|
|
326
|
+
onError,
|
|
327
|
+
onAcknowledge
|
|
328
|
+
);
|
|
301
329
|
|
|
302
|
-
|
|
330
|
+
const timeout = setInterval(
|
|
331
|
+
async () =>
|
|
332
|
+
this.executePull(
|
|
303
333
|
driveId,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
onError(error as Error);
|
|
312
|
-
}
|
|
313
|
-
}, loopInterval);
|
|
334
|
+
trigger,
|
|
335
|
+
onStrandUpdate,
|
|
336
|
+
onError,
|
|
337
|
+
onAcknowledge
|
|
338
|
+
),
|
|
339
|
+
loopInterval
|
|
340
|
+
);
|
|
314
341
|
return timeout as unknown as number;
|
|
315
342
|
}
|
|
316
343
|
|
package/src/server/types.ts
CHANGED
|
@@ -93,6 +93,11 @@ export type ListenerRevision = {
|
|
|
93
93
|
revision: number;
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
export type ListenerUpdate = {
|
|
97
|
+
listenerId: string;
|
|
98
|
+
listenerRevisions: ListenerRevision[];
|
|
99
|
+
};
|
|
100
|
+
|
|
96
101
|
export type UpdateStatus = 'SUCCESS' | 'CONFLICT' | 'MISSING' | 'ERROR';
|
|
97
102
|
export type ErrorStatus = Exclude<UpdateStatus, 'SUCCESS'>;
|
|
98
103
|
|
|
@@ -233,8 +238,14 @@ export abstract class BaseListenerManager {
|
|
|
233
238
|
driveId: string,
|
|
234
239
|
syncId: string,
|
|
235
240
|
syncRev: number,
|
|
236
|
-
lastUpdated: string
|
|
237
|
-
|
|
241
|
+
lastUpdated: string,
|
|
242
|
+
willUpdate?: (listeners: Listener[]) => void,
|
|
243
|
+
onError?: (
|
|
244
|
+
error: Error,
|
|
245
|
+
driveId: string,
|
|
246
|
+
listener: ListenerState
|
|
247
|
+
) => void
|
|
248
|
+
): Promise<ListenerUpdate[]>;
|
|
238
249
|
|
|
239
250
|
abstract updateListenerRevision(
|
|
240
251
|
listenerId: string,
|
package/src/storage/prisma.ts
CHANGED
|
@@ -119,28 +119,6 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
119
119
|
} catch (e) {
|
|
120
120
|
console.log(e);
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
await this.db.document.upsert({
|
|
124
|
-
where: {
|
|
125
|
-
id_driveId: {
|
|
126
|
-
id: 'drives',
|
|
127
|
-
driveId: id
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
create: {
|
|
131
|
-
id: 'drives',
|
|
132
|
-
driveId: id,
|
|
133
|
-
documentType: header.documentType,
|
|
134
|
-
initialState: document.initialState,
|
|
135
|
-
lastModified: header.lastModified,
|
|
136
|
-
revision: header.revision,
|
|
137
|
-
created: header.created
|
|
138
|
-
},
|
|
139
|
-
update: {
|
|
140
|
-
lastModified: header.lastModified,
|
|
141
|
-
revision: header.revision
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
122
|
}
|
|
145
123
|
|
|
146
124
|
async getDocuments(drive: string) {
|