document-drive 1.19.1 → 1.20.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/README.md +4 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/src/cache/memory.d.ts +10 -0
- package/dist/src/cache/memory.d.ts.map +1 -0
- package/dist/src/cache/memory.js +26 -0
- package/dist/src/cache/redis.d.ts +14 -0
- package/dist/src/cache/redis.d.ts.map +1 -0
- package/dist/src/cache/redis.js +40 -0
- package/dist/src/cache/types.d.ts +7 -0
- package/dist/src/cache/types.d.ts.map +1 -0
- package/dist/src/cache/types.js +1 -0
- package/dist/src/drive-document-model/constants.d.ts +2 -0
- package/dist/src/drive-document-model/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/constants.js +1 -0
- package/dist/src/drive-document-model/gen/actions.d.ts +7 -0
- package/dist/src/drive-document-model/gen/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/actions.js +2 -0
- package/dist/src/drive-document-model/gen/constants.d.ts +7 -0
- package/dist/src/drive-document-model/gen/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/constants.js +16 -0
- package/dist/src/drive-document-model/gen/creators.d.ts +3 -0
- package/dist/src/drive-document-model/gen/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/creators.js +2 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts +3 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/document-model.js +210 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts +12 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/actions.js +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts +11 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.js +10 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/error.js +1 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/object.js +28 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/operations.js +1 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts +11 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/actions.js +1 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts +10 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/creators.js +9 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/error.js +1 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/object.js +25 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/operations.js +1 -0
- package/dist/src/drive-document-model/gen/object.d.ts +21 -0
- package/dist/src/drive-document-model/gen/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/object.js +28 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts +4 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/reducer.js +74 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts +176 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/types.js +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts +87 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.js +203 -0
- package/dist/src/drive-document-model/gen/types.d.ts +9 -0
- package/dist/src/drive-document-model/gen/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/types.js +1 -0
- package/dist/src/drive-document-model/gen/utils.d.ts +10 -0
- package/dist/src/drive-document-model/gen/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/utils.js +27 -0
- package/dist/src/drive-document-model/index.d.ts +2 -0
- package/dist/src/drive-document-model/index.d.ts.map +1 -0
- package/dist/src/drive-document-model/index.js +1 -0
- package/dist/src/drive-document-model/module.d.ts +3 -0
- package/dist/src/drive-document-model/module.d.ts.map +1 -0
- package/dist/src/drive-document-model/module.js +12 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/drive.js +37 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/node.js +185 -0
- package/dist/src/drive-document-model/src/utils.d.ts +34 -0
- package/dist/src/drive-document-model/src/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/utils.js +146 -0
- package/dist/src/queue/base.d.ts +43 -0
- package/dist/src/queue/base.d.ts.map +1 -0
- package/dist/src/queue/base.js +241 -0
- package/dist/src/queue/redis.d.ts +28 -0
- package/dist/src/queue/redis.d.ts.map +1 -0
- package/dist/src/queue/redis.js +110 -0
- package/dist/src/queue/types.d.ts +55 -0
- package/dist/src/queue/types.d.ts.map +1 -0
- package/dist/src/queue/types.js +6 -0
- package/dist/src/read-mode/errors.d.ts +12 -0
- package/dist/src/read-mode/errors.d.ts.map +1 -0
- package/dist/src/read-mode/errors.js +17 -0
- package/dist/src/read-mode/server.d.ts +4 -0
- package/dist/src/read-mode/server.d.ts.map +1 -0
- package/dist/src/read-mode/server.js +78 -0
- package/dist/src/read-mode/service.d.ts +18 -0
- package/dist/src/read-mode/service.d.ts.map +1 -0
- package/dist/src/read-mode/service.js +112 -0
- package/dist/src/read-mode/types.d.ts +35 -0
- package/dist/src/read-mode/types.d.ts.map +1 -0
- package/dist/src/read-mode/types.js +1 -0
- package/dist/src/server/base-server.d.ts +112 -0
- package/dist/src/server/base-server.d.ts.map +1 -0
- package/dist/src/server/base-server.js +1280 -0
- package/dist/src/server/builder.d.ts +30 -0
- package/dist/src/server/builder.d.ts.map +1 -0
- package/dist/src/server/builder.js +89 -0
- package/dist/src/server/constants.d.ts +2 -0
- package/dist/src/server/constants.d.ts.map +1 -0
- package/dist/src/server/constants.js +1 -0
- package/dist/src/server/error.d.ts +30 -0
- package/dist/src/server/error.d.ts.map +1 -0
- package/dist/src/server/error.js +47 -0
- package/dist/src/server/event-emitter.d.ts +8 -0
- package/dist/src/server/event-emitter.d.ts.map +1 -0
- package/dist/src/server/event-emitter.js +10 -0
- package/dist/src/server/listener/index.d.ts +2 -0
- package/dist/src/server/listener/index.d.ts.map +1 -0
- package/dist/src/server/listener/index.js +1 -0
- package/dist/src/server/listener/listener-manager.d.ts +27 -0
- package/dist/src/server/listener/listener-manager.d.ts.map +1 -0
- package/dist/src/server/listener/listener-manager.js +401 -0
- package/dist/src/server/listener/transmitter/factory.d.ts +8 -0
- package/dist/src/server/listener/transmitter/factory.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/factory.js +25 -0
- package/dist/src/server/listener/transmitter/internal.d.ts +34 -0
- package/dist/src/server/listener/transmitter/internal.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/internal.js +87 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts +38 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/pull-responder.js +256 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts +9 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/switchboard-push.js +77 -0
- package/dist/src/server/listener/transmitter/types.d.ts +20 -0
- package/dist/src/server/listener/transmitter/types.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/types.js +1 -0
- package/dist/src/server/listener/util.d.ts +2 -0
- package/dist/src/server/listener/util.d.ts.map +1 -0
- package/dist/src/server/listener/util.js +22 -0
- package/dist/src/server/sync-manager.d.ts +30 -0
- package/dist/src/server/sync-manager.d.ts.map +1 -0
- package/dist/src/server/sync-manager.js +287 -0
- package/dist/src/server/types.d.ts +308 -0
- package/dist/src/server/types.d.ts.map +1 -0
- package/dist/src/server/types.js +12 -0
- package/dist/src/server/utils.d.ts +8 -0
- package/dist/src/server/utils.d.ts.map +1 -0
- package/dist/src/server/utils.js +47 -0
- package/dist/src/storage/base.d.ts +36 -0
- package/dist/src/storage/base.d.ts.map +1 -0
- package/dist/src/storage/base.js +4 -0
- package/dist/src/storage/browser.d.ts +36 -0
- package/dist/src/storage/browser.d.ts.map +1 -0
- package/dist/src/storage/browser.js +155 -0
- package/dist/src/storage/filesystem.d.ts +33 -0
- package/dist/src/storage/filesystem.d.ts.map +1 -0
- package/dist/src/storage/filesystem.js +197 -0
- package/dist/src/storage/memory.d.ts +33 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +139 -0
- package/dist/src/storage/prisma.d.ts +67 -0
- package/dist/src/storage/prisma.d.ts.map +1 -0
- package/dist/src/storage/prisma.js +445 -0
- package/dist/src/storage/sequelize.d.ts +32 -0
- package/dist/src/storage/sequelize.d.ts.map +1 -0
- package/dist/src/storage/sequelize.js +373 -0
- package/dist/src/storage/types.d.ts +43 -0
- package/dist/src/storage/types.d.ts.map +1 -0
- package/dist/src/storage/types.js +1 -0
- package/dist/src/utils/default-drives-manager.d.ts +29 -0
- package/dist/src/utils/default-drives-manager.d.ts.map +1 -0
- package/dist/src/utils/default-drives-manager.js +208 -0
- package/dist/src/utils/graphql.d.ts +34 -0
- package/dist/src/utils/graphql.d.ts.map +1 -0
- package/dist/src/utils/graphql.js +183 -0
- package/dist/src/utils/logger.d.ts +27 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +105 -0
- package/dist/src/utils/migrations.d.ts +4 -0
- package/dist/src/utils/migrations.d.ts.map +1 -0
- package/dist/src/utils/migrations.js +41 -0
- package/dist/src/utils/misc.d.ts +11 -0
- package/dist/src/utils/misc.d.ts.map +1 -0
- package/dist/src/utils/misc.js +43 -0
- package/dist/src/utils/run-asap.d.ts +12 -0
- package/dist/src/utils/run-asap.d.ts.map +1 -0
- package/dist/src/utils/run-asap.js +131 -0
- package/dist/test/document-helpers/utils.d.ts +8 -0
- package/dist/test/document-helpers/utils.d.ts.map +1 -0
- package/dist/test/document-helpers/utils.js +21 -0
- package/dist/test/utils.d.ts +48 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +132 -0
- package/dist/test/vitest-setup.d.ts +2 -0
- package/dist/test/vitest-setup.d.ts.map +1 -0
- package/dist/test/vitest-setup.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +20 -0
- package/package.json +20 -38
- package/src/cache/index.ts +0 -2
- package/src/cache/memory.ts +0 -33
- package/src/cache/redis.ts +0 -56
- package/src/cache/types.ts +0 -9
- package/src/index.ts +0 -4
- package/src/queue/base.ts +0 -320
- package/src/queue/index.ts +0 -2
- package/src/queue/redis.ts +0 -144
- package/src/queue/types.ts +0 -79
- package/src/read-mode/errors.ts +0 -19
- package/src/read-mode/index.ts +0 -125
- package/src/read-mode/service.ts +0 -207
- package/src/read-mode/types.ts +0 -108
- package/src/server/error.ts +0 -70
- package/src/server/index.ts +0 -2444
- package/src/server/listener/index.ts +0 -2
- package/src/server/listener/manager.ts +0 -652
- package/src/server/listener/transmitter/index.ts +0 -4
- package/src/server/listener/transmitter/internal.ts +0 -143
- package/src/server/listener/transmitter/pull-responder.ts +0 -462
- package/src/server/listener/transmitter/switchboard-push.ts +0 -125
- package/src/server/listener/transmitter/types.ts +0 -27
- package/src/server/types.ts +0 -596
- package/src/server/utils.ts +0 -82
- package/src/storage/base.ts +0 -81
- package/src/storage/browser.ts +0 -238
- package/src/storage/filesystem.ts +0 -297
- package/src/storage/index.ts +0 -2
- package/src/storage/memory.ts +0 -211
- package/src/storage/prisma.ts +0 -653
- package/src/storage/sequelize.ts +0 -498
- package/src/storage/types.ts +0 -97
- package/src/utils/default-drives-manager.ts +0 -341
- package/src/utils/document-helpers.ts +0 -21
- package/src/utils/graphql.ts +0 -301
- package/src/utils/index.ts +0 -90
- package/src/utils/logger.ts +0 -38
- package/src/utils/migrations.ts +0 -58
- package/src/utils/run-asap.ts +0 -156
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { DefaultListenerManagerOptions, } from "#server/types";
|
|
2
|
+
import { childLogger } from "document-drive";
|
|
3
|
+
import { OperationError } from "#server/error";
|
|
4
|
+
import { debounce } from "./util.js";
|
|
5
|
+
const ENABLE_SYNC_DEBUG = false;
|
|
6
|
+
export class ListenerManager {
|
|
7
|
+
static LISTENER_UPDATE_DELAY = 250;
|
|
8
|
+
logger = childLogger([
|
|
9
|
+
"ListenerManager",
|
|
10
|
+
Math.floor(Math.random() * 999).toString(),
|
|
11
|
+
]);
|
|
12
|
+
syncManager;
|
|
13
|
+
options;
|
|
14
|
+
// driveId -> listenerId -> listenerState
|
|
15
|
+
listenerStateByDriveId = new Map();
|
|
16
|
+
constructor(syncManager, options = DefaultListenerManagerOptions) {
|
|
17
|
+
this.syncManager = syncManager;
|
|
18
|
+
this.options = { ...DefaultListenerManagerOptions, ...options };
|
|
19
|
+
this.logger.verbose(`constructor(...)`);
|
|
20
|
+
}
|
|
21
|
+
async initialize(handler) {
|
|
22
|
+
this.logger.verbose("initialize(...)");
|
|
23
|
+
// if network connect comes back online
|
|
24
|
+
// then triggers the listeners update
|
|
25
|
+
if (typeof window !== "undefined") {
|
|
26
|
+
window.addEventListener("online", () => {
|
|
27
|
+
this.triggerUpdate(false, { type: "local" }, handler).catch((error) => {
|
|
28
|
+
this.logger.error("Non handled error updating listeners", error);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
driveHasListeners(driveId) {
|
|
34
|
+
return this.listenerStateByDriveId.has(driveId);
|
|
35
|
+
}
|
|
36
|
+
async setListener(driveId, listener) {
|
|
37
|
+
this.logger.verbose(`setListener(drive: ${driveId}, listener: ${listener.listenerId})`);
|
|
38
|
+
// slight code smell -- drive id may not need to be on listener or not passed in
|
|
39
|
+
if (driveId !== listener.driveId) {
|
|
40
|
+
throw new Error("Drive ID mismatch");
|
|
41
|
+
}
|
|
42
|
+
let existingState;
|
|
43
|
+
try {
|
|
44
|
+
existingState = this.getListenerState(driveId, listener.listenerId);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
existingState = {};
|
|
48
|
+
}
|
|
49
|
+
// keep existing state if it exists
|
|
50
|
+
this.setListenerState(driveId, listener.listenerId, {
|
|
51
|
+
...existingState,
|
|
52
|
+
block: listener.block,
|
|
53
|
+
driveId: listener.driveId,
|
|
54
|
+
pendingTimeout: "0",
|
|
55
|
+
listener,
|
|
56
|
+
listenerStatus: "CREATED",
|
|
57
|
+
syncUnits: new Map(),
|
|
58
|
+
});
|
|
59
|
+
this.triggerUpdate(true, { type: "local" });
|
|
60
|
+
}
|
|
61
|
+
async removeListener(driveId, listenerId) {
|
|
62
|
+
this.logger.verbose("setListener()");
|
|
63
|
+
const driveMap = this.listenerStateByDriveId.get(driveId);
|
|
64
|
+
if (!driveMap) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve(driveMap.delete(listenerId));
|
|
68
|
+
}
|
|
69
|
+
async removeSyncUnits(driveId, syncUnits) {
|
|
70
|
+
const listeners = this.listenerStateByDriveId.get(driveId);
|
|
71
|
+
if (!listeners) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const [, listener] of listeners) {
|
|
75
|
+
for (const syncUnit of syncUnits) {
|
|
76
|
+
listener.syncUnits.delete(syncUnit.syncId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return Promise.resolve();
|
|
80
|
+
}
|
|
81
|
+
async updateSynchronizationRevisions(driveId, syncUnits, source, willUpdate, onError, forceSync = false) {
|
|
82
|
+
const listenerIdToListenerState = this.listenerStateByDriveId.get(driveId);
|
|
83
|
+
if (!listenerIdToListenerState) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const outdatedListeners = [];
|
|
87
|
+
for (const [, listenerState] of listenerIdToListenerState) {
|
|
88
|
+
if (outdatedListeners.find((l) => l.listenerId === listenerState.listener.listenerId)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const transmitter = listenerState.listener.transmitter;
|
|
92
|
+
if (!transmitter?.transmit) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const syncUnit of syncUnits) {
|
|
96
|
+
if (!this._checkFilter(listenerState.listener.filter, syncUnit)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const listenerRev = listenerState.syncUnits.get(syncUnit.syncId);
|
|
100
|
+
if (!listenerRev || listenerRev.listenerRev < syncUnit.revision) {
|
|
101
|
+
outdatedListeners.push(listenerState.listener);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (outdatedListeners.length) {
|
|
107
|
+
willUpdate?.(outdatedListeners);
|
|
108
|
+
return this.triggerUpdate(forceSync, source, onError);
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
async updateListenerRevision(listenerId, driveId, syncId, listenerRev) {
|
|
113
|
+
const drive = this.listenerStateByDriveId.get(driveId);
|
|
114
|
+
if (!drive) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const listener = drive.get(listenerId);
|
|
118
|
+
if (!listener) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const lastUpdated = new Date().toISOString();
|
|
122
|
+
const entry = listener.syncUnits.get(syncId);
|
|
123
|
+
if (entry) {
|
|
124
|
+
entry.listenerRev = listenerRev;
|
|
125
|
+
entry.lastUpdated = lastUpdated;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
listener.syncUnits.set(syncId, { listenerRev, lastUpdated });
|
|
129
|
+
}
|
|
130
|
+
return Promise.resolve();
|
|
131
|
+
}
|
|
132
|
+
triggerUpdate = debounce(this._triggerUpdate.bind(this), ListenerManager.LISTENER_UPDATE_DELAY);
|
|
133
|
+
async _triggerUpdate(source, onError, maxContinues = 500) {
|
|
134
|
+
this.logger.verbose(`_triggerUpdate(source: ${source.type}, maxContinues: ${maxContinues})`, this.listenerStateByDriveId);
|
|
135
|
+
if (maxContinues < 0) {
|
|
136
|
+
throw new Error("Maximum retries exhausted.");
|
|
137
|
+
}
|
|
138
|
+
const listenerUpdates = [];
|
|
139
|
+
for (const [driveId, drive] of this.listenerStateByDriveId) {
|
|
140
|
+
for (const [listenerId, listenerState] of drive) {
|
|
141
|
+
const transmitter = listenerState.listener.transmitter;
|
|
142
|
+
if (!transmitter?.transmit) {
|
|
143
|
+
this.logger.verbose(`Transmitter not set on listener: ${listenerId}`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const syncUnits = await this.getListenerSyncUnits(driveId, listenerId);
|
|
147
|
+
const strandUpdates = [];
|
|
148
|
+
this.logger.verbose("syncUnits", syncUnits);
|
|
149
|
+
// TODO change to push one after the other, reusing operation data
|
|
150
|
+
const tasks = syncUnits.map((syncUnit) => async () => {
|
|
151
|
+
const unitState = listenerState.syncUnits.get(syncUnit.syncId);
|
|
152
|
+
if (unitState && unitState.listenerRev >= syncUnit.revision) {
|
|
153
|
+
this.logger.verbose(`Abandoning push for sync unit ${syncUnit.syncId}: already up-to-date (${unitState.listenerRev} >= ${syncUnit.revision})`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.logger.verbose(`Listener out-of-date for sync unit ${syncUnit.syncId}: ${unitState?.listenerRev} < ${syncUnit.revision}`);
|
|
158
|
+
}
|
|
159
|
+
const opData = [];
|
|
160
|
+
try {
|
|
161
|
+
const data = await this.syncManager.getOperationData(
|
|
162
|
+
// TODO - join queries, DEAL WITH INVALID SYNC ID ERROR
|
|
163
|
+
driveId, syncUnit.syncId, {
|
|
164
|
+
fromRevision: unitState?.listenerRev,
|
|
165
|
+
});
|
|
166
|
+
opData.push(...data);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
this.logger.error(e);
|
|
170
|
+
}
|
|
171
|
+
if (!opData.length) {
|
|
172
|
+
this.logger.verbose(`Abandoning push for ${syncUnit.syncId}: no operations found`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
strandUpdates.push({
|
|
176
|
+
driveId,
|
|
177
|
+
documentId: syncUnit.documentId,
|
|
178
|
+
branch: syncUnit.branch,
|
|
179
|
+
operations: opData,
|
|
180
|
+
scope: syncUnit.scope,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
if (this.options.sequentialUpdates) {
|
|
184
|
+
this.logger.verbose(`Collecting ${tasks.length} syncUnit strandUpdates in sequence`);
|
|
185
|
+
for (const task of tasks) {
|
|
186
|
+
await task();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.logger.verbose(`Collecting ${tasks.length} syncUnit strandUpdates in parallel`);
|
|
191
|
+
await Promise.all(tasks.map((task) => task()));
|
|
192
|
+
}
|
|
193
|
+
if (strandUpdates.length == 0) {
|
|
194
|
+
this.logger.verbose(`No strandUpdates needed for listener ${listenerId}`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
listenerState.pendingTimeout = new Date(new Date().getTime() / 1000 + 300).toISOString();
|
|
198
|
+
listenerState.listenerStatus = "PENDING";
|
|
199
|
+
// TODO update listeners in parallel, blocking for listeners with block=true
|
|
200
|
+
try {
|
|
201
|
+
this.logger.verbose(`_triggerUpdate(source: ${source.type}) > transmitter.transmit`);
|
|
202
|
+
const listenerRevisions = await transmitter.transmit(strandUpdates, source);
|
|
203
|
+
this.logger.verbose(`_triggerUpdate(source: ${source.type}) > transmission succeeded`, listenerRevisions);
|
|
204
|
+
listenerState.pendingTimeout = "0";
|
|
205
|
+
listenerState.listenerStatus = "PENDING";
|
|
206
|
+
const lastUpdated = new Date().toISOString();
|
|
207
|
+
let continuationNeeded = false;
|
|
208
|
+
for (const revision of listenerRevisions) {
|
|
209
|
+
const syncUnit = syncUnits.find((unit) => revision.documentId === unit.documentId &&
|
|
210
|
+
revision.scope === unit.scope &&
|
|
211
|
+
revision.branch === unit.branch);
|
|
212
|
+
if (syncUnit) {
|
|
213
|
+
listenerState.syncUnits.set(syncUnit.syncId, {
|
|
214
|
+
lastUpdated,
|
|
215
|
+
listenerRev: revision.revision,
|
|
216
|
+
});
|
|
217
|
+
// Check for revision status vv
|
|
218
|
+
const su = strandUpdates.find((su) => su.driveId === revision.driveId &&
|
|
219
|
+
su.documentId === revision.documentId &&
|
|
220
|
+
su.scope === revision.scope &&
|
|
221
|
+
su.branch === revision.branch);
|
|
222
|
+
if (su && su.operations.length > 0) {
|
|
223
|
+
const suIndex = su.operations.at(su.operations.length - 1)?.index;
|
|
224
|
+
if (suIndex !== revision.revision) {
|
|
225
|
+
this.logger.verbose(`Revision still out-of-date for ${su.documentId}:${su.scope}:${su.branch} ${suIndex} <> ${revision.revision}`);
|
|
226
|
+
continuationNeeded = true;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
this.logger.verbose(`Revision match for ${su.documentId}:${su.scope}:${su.branch} ${suIndex}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
this.logger.verbose(`Cannot find strand update for (${revision.documentId}:${revision.scope}:${revision.branch} in drive ${revision.driveId})`);
|
|
234
|
+
}
|
|
235
|
+
// Check for revision status ^^
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
this.logger.warn(`Received revision for untracked unit for listener ${listenerState.listener.listenerId}`, revision);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const revision of listenerRevisions) {
|
|
242
|
+
const error = revision.status === "ERROR";
|
|
243
|
+
if (revision.error?.includes("Missing operations")) {
|
|
244
|
+
continuationNeeded = true;
|
|
245
|
+
}
|
|
246
|
+
else if (error) {
|
|
247
|
+
throw new OperationError(revision.status, undefined, revision.error, revision.error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!continuationNeeded) {
|
|
251
|
+
listenerUpdates.push({
|
|
252
|
+
listenerId: listenerState.listener.listenerId,
|
|
253
|
+
listenerRevisions,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const updates = await this._triggerUpdate(source, onError, maxContinues - 1);
|
|
258
|
+
listenerUpdates.push(...updates);
|
|
259
|
+
}
|
|
260
|
+
listenerState.listenerStatus = "SUCCESS";
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
// TODO: Handle error based on listener params (blocking, retry, etc)
|
|
264
|
+
onError?.(e, driveId, listenerState);
|
|
265
|
+
listenerState.listenerStatus =
|
|
266
|
+
e instanceof OperationError ? e.status : "ERROR";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.logger.verbose(`Returning listener updates (maxContinues: ${maxContinues})`, listenerUpdates);
|
|
271
|
+
return listenerUpdates;
|
|
272
|
+
}
|
|
273
|
+
_checkFilter(filter, syncUnit) {
|
|
274
|
+
const { branch, documentId, scope, documentType } = syncUnit;
|
|
275
|
+
// TODO: Needs to be optimized
|
|
276
|
+
if ((!filter.branch ||
|
|
277
|
+
filter.branch.includes(branch) ||
|
|
278
|
+
filter.branch.includes("*")) &&
|
|
279
|
+
(!filter.documentId ||
|
|
280
|
+
filter.documentId.includes(documentId) ||
|
|
281
|
+
filter.documentId.includes("*")) &&
|
|
282
|
+
(!filter.scope ||
|
|
283
|
+
filter.scope.includes(scope) ||
|
|
284
|
+
filter.scope.includes("*")) &&
|
|
285
|
+
(!filter.documentType ||
|
|
286
|
+
filter.documentType.includes(documentType) ||
|
|
287
|
+
filter.documentType.includes("*"))) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
getListenerSyncUnits(driveId, listenerId) {
|
|
293
|
+
const listener = this.listenerStateByDriveId.get(driveId)?.get(listenerId);
|
|
294
|
+
if (!listener) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
const filter = listener.listener.filter;
|
|
298
|
+
return this.syncManager.getSynchronizationUnits(driveId, filter.documentId ?? ["*"], filter.scope ?? ["*"], filter.branch ?? ["*"], filter.documentType ?? ["*"]);
|
|
299
|
+
}
|
|
300
|
+
getListenerSyncUnitIds(driveId, listenerId) {
|
|
301
|
+
const listener = this.listenerStateByDriveId.get(driveId)?.get(listenerId);
|
|
302
|
+
if (!listener) {
|
|
303
|
+
return Promise.resolve([]);
|
|
304
|
+
}
|
|
305
|
+
const filter = listener.listener.filter;
|
|
306
|
+
return this.syncManager.getSynchronizationUnitsIds(driveId, filter.documentId ?? ["*"], filter.scope ?? ["*"], filter.branch ?? ["*"], filter.documentType ?? ["*"]);
|
|
307
|
+
}
|
|
308
|
+
async removeDrive(driveId) {
|
|
309
|
+
const listenerIdToListenerState = this.listenerStateByDriveId.get(driveId);
|
|
310
|
+
if (!listenerIdToListenerState) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// delete first
|
|
314
|
+
this.listenerStateByDriveId.delete(driveId);
|
|
315
|
+
for (const [_, listenerState] of listenerIdToListenerState) {
|
|
316
|
+
// guarantee that all disconnects are called
|
|
317
|
+
try {
|
|
318
|
+
await listenerState.listener.transmitter?.disconnect?.();
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
this.logger.error(error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async getStrands(driveId, listenerId, options) {
|
|
326
|
+
// this will throw if listenerState is not found
|
|
327
|
+
const listenerState = this.getListenerState(driveId, listenerId);
|
|
328
|
+
// fetch operations from drive and prepare strands
|
|
329
|
+
const strands = [];
|
|
330
|
+
const syncUnits = await this.getListenerSyncUnits(driveId, listenerId);
|
|
331
|
+
const limit = options?.limit; // maximum number of operations to send across all sync units
|
|
332
|
+
let operationsCount = 0; // total amount of operations that have been retrieved
|
|
333
|
+
const tasks = syncUnits.map((syncUnit) => async () => {
|
|
334
|
+
if (limit && operationsCount >= limit) {
|
|
335
|
+
// break;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (syncUnit.revision < 0) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const entry = listenerState.syncUnits.get(syncUnit.syncId);
|
|
342
|
+
if (entry && entry.listenerRev >= syncUnit.revision) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const { documentId, driveId, scope, branch } = syncUnit;
|
|
346
|
+
try {
|
|
347
|
+
const operations = await this.syncManager.getOperationData(
|
|
348
|
+
// DEAL WITH INVALID SYNC ID ERROR
|
|
349
|
+
driveId, syncUnit.syncId, {
|
|
350
|
+
since: options?.since,
|
|
351
|
+
fromRevision: options?.fromRevision ?? entry?.listenerRev,
|
|
352
|
+
limit: limit ? limit - operationsCount : undefined,
|
|
353
|
+
});
|
|
354
|
+
if (!operations.length) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
operationsCount += operations.length;
|
|
358
|
+
strands.push({
|
|
359
|
+
driveId,
|
|
360
|
+
documentId,
|
|
361
|
+
scope: scope,
|
|
362
|
+
branch,
|
|
363
|
+
operations,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
this.logger.error(error);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
if (this.options.sequentialUpdates) {
|
|
372
|
+
for (const task of tasks) {
|
|
373
|
+
await task();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
await Promise.all(tasks.map((task) => task()));
|
|
378
|
+
}
|
|
379
|
+
return strands;
|
|
380
|
+
}
|
|
381
|
+
getListenerState(driveId, listenerId) {
|
|
382
|
+
let listenerStateByListenerId = this.listenerStateByDriveId.get(driveId);
|
|
383
|
+
if (!listenerStateByListenerId) {
|
|
384
|
+
listenerStateByListenerId = new Map();
|
|
385
|
+
this.listenerStateByDriveId.set(driveId, listenerStateByListenerId);
|
|
386
|
+
}
|
|
387
|
+
const listenerState = listenerStateByListenerId.get(listenerId);
|
|
388
|
+
if (!listenerState) {
|
|
389
|
+
throw new Error("Listener not found");
|
|
390
|
+
}
|
|
391
|
+
return listenerState;
|
|
392
|
+
}
|
|
393
|
+
setListenerState(driveId, listenerId, listenerState) {
|
|
394
|
+
let listenerStateByListenerId = this.listenerStateByDriveId.get(driveId);
|
|
395
|
+
if (!listenerStateByListenerId) {
|
|
396
|
+
listenerStateByListenerId = new Map();
|
|
397
|
+
this.listenerStateByDriveId.set(driveId, listenerStateByListenerId);
|
|
398
|
+
}
|
|
399
|
+
listenerStateByListenerId.set(listenerId, listenerState);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IBaseDocumentDriveServer, IListenerManager, ITransmitterFactory, Listener } from "#server/types";
|
|
2
|
+
import { ITransmitter } from "./types.js";
|
|
3
|
+
export default class TransmitterFactory implements ITransmitterFactory {
|
|
4
|
+
private readonly listenerManager;
|
|
5
|
+
constructor(listenerManager: IListenerManager);
|
|
6
|
+
instance(transmitterType: string, listener: Listener, driveServer: IBaseDocumentDriveServer): ITransmitter;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../../src/server/listener/transmitter/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACT,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,kBAAmB,YAAW,mBAAmB;IACpE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmB;gBAEvC,eAAe,EAAE,gBAAgB;IAI7C,QAAQ,CACN,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,wBAAwB,GACpC,YAAY;CAiBhB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InternalTransmitter } from "./internal.js";
|
|
2
|
+
import { PullResponderTransmitter } from "./pull-responder.js";
|
|
3
|
+
import { SwitchboardPushTransmitter } from "./switchboard-push.js";
|
|
4
|
+
export default class TransmitterFactory {
|
|
5
|
+
listenerManager;
|
|
6
|
+
constructor(listenerManager) {
|
|
7
|
+
this.listenerManager = listenerManager;
|
|
8
|
+
}
|
|
9
|
+
instance(transmitterType, listener, driveServer) {
|
|
10
|
+
switch (transmitterType) {
|
|
11
|
+
case "SwitchboardPush": {
|
|
12
|
+
if (!listener.callInfo?.data) {
|
|
13
|
+
throw new Error("No call info data: " + JSON.stringify(listener));
|
|
14
|
+
}
|
|
15
|
+
return new SwitchboardPushTransmitter(listener.callInfo.data);
|
|
16
|
+
}
|
|
17
|
+
case "Internal": {
|
|
18
|
+
return new InternalTransmitter(listener, driveServer);
|
|
19
|
+
}
|
|
20
|
+
default: {
|
|
21
|
+
return new PullResponderTransmitter(listener, this.listenerManager);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { GlobalStateFromDocument, LocalStateFromDocument, OperationFromDocument, OperationScope, PHDocument } from "document-model";
|
|
2
|
+
import { IBaseDocumentDriveServer, Listener, ListenerRevision, StrandUpdate } from "#server/types";
|
|
3
|
+
import { ITransmitter, StrandUpdateSource } from "./types.js";
|
|
4
|
+
export interface IReceiver {
|
|
5
|
+
onStrands: <TDocument extends PHDocument>(strands: InternalTransmitterUpdate<TDocument>[]) => Promise<void>;
|
|
6
|
+
onDisconnect: () => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export type InternalOperationUpdate<TDocument extends PHDocument> = Omit<OperationFromDocument<TDocument>, "scope"> & {
|
|
9
|
+
state: GlobalStateFromDocument<TDocument> | LocalStateFromDocument<TDocument>;
|
|
10
|
+
previousState: GlobalStateFromDocument<TDocument> | LocalStateFromDocument<TDocument>;
|
|
11
|
+
};
|
|
12
|
+
export type InternalTransmitterUpdate<TDocument extends PHDocument> = {
|
|
13
|
+
driveId: string;
|
|
14
|
+
documentId: string;
|
|
15
|
+
scope: OperationScope;
|
|
16
|
+
branch: string;
|
|
17
|
+
operations: InternalOperationUpdate<TDocument>[];
|
|
18
|
+
state: GlobalStateFromDocument<TDocument> | LocalStateFromDocument<TDocument>;
|
|
19
|
+
};
|
|
20
|
+
export interface IInternalTransmitter extends ITransmitter {
|
|
21
|
+
setReceiver(receiver: IReceiver): void;
|
|
22
|
+
}
|
|
23
|
+
export declare class InternalTransmitter implements ITransmitter {
|
|
24
|
+
#private;
|
|
25
|
+
protected drive: IBaseDocumentDriveServer;
|
|
26
|
+
protected listener: Listener;
|
|
27
|
+
protected receiver: IReceiver | undefined;
|
|
28
|
+
constructor(listener: Listener, drive: IBaseDocumentDriveServer);
|
|
29
|
+
transmit(strands: StrandUpdate[], _source: StrandUpdateSource): Promise<ListenerRevision[]>;
|
|
30
|
+
setReceiver(receiver: IReceiver): void;
|
|
31
|
+
disconnect(): Promise<void>;
|
|
32
|
+
getListener(): Listener;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../../../../src/server/listener/transmitter/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACX,MAAM,gBAAgB,CAAC;AAMxB,OAAO,EAEL,wBAAwB,EACxB,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACb,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,CAAC,SAAS,SAAS,UAAU,EACtC,OAAO,EAAE,yBAAyB,CAAC,SAAS,CAAC,EAAE,KAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,MAAM,MAAM,uBAAuB,CAAC,SAAS,SAAS,UAAU,IAAI,IAAI,CACtE,qBAAqB,CAAC,SAAS,CAAC,EAChC,OAAO,CACR,GAAG;IACF,KAAK,EAAE,uBAAuB,CAAC,SAAS,CAAC,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC9E,aAAa,EACT,uBAAuB,CAAC,SAAS,CAAC,GAClC,sBAAsB,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,yBAAyB,CAAC,SAAS,SAAS,UAAU,IAAI;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC;IACjD,KAAK,EAAE,uBAAuB,CAAC,SAAS,CAAC,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAC;CAC/E,CAAC;AAEF,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,mBAAoB,YAAW,YAAY;;IACtD,SAAS,CAAC,KAAK,EAAE,wBAAwB,CAAC;IAC1C,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC7B,SAAS,CAAC,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC;gBAE9B,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,wBAAwB;IAkDzD,QAAQ,CACZ,OAAO,EAAE,YAAY,EAAE,EAEvB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiC9B,WAAW,CAAC,QAAQ,EAAE,SAAS;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,WAAW,IAAI,QAAQ;CAGxB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { logger } from "#utils/logger";
|
|
2
|
+
export class InternalTransmitter {
|
|
3
|
+
drive;
|
|
4
|
+
listener;
|
|
5
|
+
receiver;
|
|
6
|
+
constructor(listener, drive) {
|
|
7
|
+
this.listener = listener;
|
|
8
|
+
this.drive = drive;
|
|
9
|
+
}
|
|
10
|
+
async #buildInternalOperationUpdate(strand) {
|
|
11
|
+
const operations = [];
|
|
12
|
+
const stateByIndex = new Map();
|
|
13
|
+
const getStateByIndex = async (index) => {
|
|
14
|
+
const state = stateByIndex.get(index);
|
|
15
|
+
if (state) {
|
|
16
|
+
return state;
|
|
17
|
+
}
|
|
18
|
+
const getDocumentOptions = {
|
|
19
|
+
revisions: {
|
|
20
|
+
[strand.scope]: index,
|
|
21
|
+
},
|
|
22
|
+
checkHashes: false,
|
|
23
|
+
};
|
|
24
|
+
const document = await (strand.documentId
|
|
25
|
+
? this.drive.getDocument(strand.driveId, strand.documentId, getDocumentOptions)
|
|
26
|
+
: this.drive.getDrive(strand.driveId, getDocumentOptions));
|
|
27
|
+
if (index < 0) {
|
|
28
|
+
stateByIndex.set(index, document.initialState.state[strand.scope]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
stateByIndex.set(index, document.state[strand.scope]);
|
|
32
|
+
}
|
|
33
|
+
return stateByIndex.get(index);
|
|
34
|
+
};
|
|
35
|
+
for (const operation of strand.operations) {
|
|
36
|
+
operations.push({
|
|
37
|
+
...operation,
|
|
38
|
+
state: await getStateByIndex(operation.index),
|
|
39
|
+
previousState: await getStateByIndex(operation.index - 1),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return operations;
|
|
43
|
+
}
|
|
44
|
+
async transmit(strands,
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
46
|
+
_source) {
|
|
47
|
+
if (!this.receiver) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const updates = [];
|
|
51
|
+
for (const strand of strands) {
|
|
52
|
+
const operations = await this.#buildInternalOperationUpdate(strand);
|
|
53
|
+
const state = operations.at(-1)?.state ?? {};
|
|
54
|
+
updates.push({
|
|
55
|
+
...strand,
|
|
56
|
+
operations,
|
|
57
|
+
state,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
await this.receiver.onStrands(updates);
|
|
62
|
+
return strands.map(({ operations, ...s }) => ({
|
|
63
|
+
...s,
|
|
64
|
+
status: "SUCCESS",
|
|
65
|
+
revision: operations.at(operations.length - 1)?.index ?? -1,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.error(error);
|
|
70
|
+
// TODO check which strand caused an error
|
|
71
|
+
return strands.map(({ operations, ...s }) => ({
|
|
72
|
+
...s,
|
|
73
|
+
status: "ERROR",
|
|
74
|
+
revision: (operations.at(0)?.index ?? 0) - 1,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
setReceiver(receiver) {
|
|
79
|
+
this.receiver = receiver;
|
|
80
|
+
}
|
|
81
|
+
async disconnect() {
|
|
82
|
+
await this.receiver?.onDisconnect();
|
|
83
|
+
}
|
|
84
|
+
getListener() {
|
|
85
|
+
return this.listener;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { GetStrandsOptions, IListenerManager, IOperationResult, Listener, ListenerRevision, ListenerRevisionWithError, OperationUpdate, RemoteDriveOptions, StrandUpdate } from "#server/types";
|
|
2
|
+
import { ListenerFilter, Trigger } from "document-drive";
|
|
3
|
+
import { ITransmitter, PullResponderTrigger, StrandUpdateSource } from "./types.js";
|
|
4
|
+
export type OperationUpdateGraphQL = Omit<OperationUpdate, "input"> & {
|
|
5
|
+
input: string;
|
|
6
|
+
};
|
|
7
|
+
export type PullStrandsGraphQL = {
|
|
8
|
+
system: {
|
|
9
|
+
sync: {
|
|
10
|
+
strands: StrandUpdateGraphQL[];
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type CancelPullLoop = () => void;
|
|
15
|
+
export type StrandUpdateGraphQL = Omit<StrandUpdate, "operations"> & {
|
|
16
|
+
operations: OperationUpdateGraphQL[];
|
|
17
|
+
};
|
|
18
|
+
export interface IPullResponderTransmitter extends ITransmitter {
|
|
19
|
+
getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]>;
|
|
20
|
+
}
|
|
21
|
+
export declare class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
22
|
+
private static staticLogger;
|
|
23
|
+
private logger;
|
|
24
|
+
private listener;
|
|
25
|
+
private manager;
|
|
26
|
+
constructor(listener: Listener, manager: IListenerManager);
|
|
27
|
+
getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]>;
|
|
28
|
+
disconnect(): Promise<void>;
|
|
29
|
+
processAcknowledge(driveId: string, listenerId: string, revisions: ListenerRevision[]): Promise<boolean>;
|
|
30
|
+
static registerPullResponder(driveId: string, url: string, filter: ListenerFilter): Promise<Listener["listenerId"]>;
|
|
31
|
+
static pullStrands(driveId: string, url: string, listenerId: string, options?: GetStrandsOptions): Promise<StrandUpdate[]>;
|
|
32
|
+
static acknowledgeStrands(driveId: string, url: string, listenerId: string, revisions: ListenerRevision[]): Promise<boolean>;
|
|
33
|
+
private static executePull;
|
|
34
|
+
static setupPull(driveId: string, trigger: PullResponderTrigger, onStrandUpdate: (strand: StrandUpdate, source: StrandUpdateSource) => Promise<IOperationResult>, onError: (error: Error) => void, onRevisions?: (revisions: ListenerRevisionWithError[]) => void, onAcknowledge?: (success: boolean) => void): CancelPullLoop;
|
|
35
|
+
static createPullResponderTrigger(driveId: string, url: string, options: Pick<RemoteDriveOptions, "pullInterval" | "pullFilter">): Promise<PullResponderTrigger>;
|
|
36
|
+
static isPullResponderTrigger(trigger: Trigger): trigger is PullResponderTrigger;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=pull-responder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-responder.d.ts","sourceRoot":"","sources":["../../../../../src/server/listener/transmitter/pull-responder.ts"],"names":[],"mappings":"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"}
|