document-drive 1.0.0-websockets → 1.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/README.md +1 -0
- package/package.json +74 -88
- package/src/cache/index.ts +2 -2
- package/src/cache/memory.ts +22 -13
- package/src/cache/redis.ts +43 -16
- package/src/cache/types.ts +4 -4
- package/src/index.ts +6 -3
- package/src/queue/base.ts +276 -214
- package/src/queue/index.ts +2 -2
- package/src/queue/redis.ts +138 -127
- package/src/queue/types.ts +44 -38
- package/src/read-mode/errors.ts +19 -0
- package/src/read-mode/index.ts +125 -0
- package/src/read-mode/service.ts +207 -0
- package/src/read-mode/types.ts +108 -0
- package/src/server/error.ts +61 -26
- package/src/server/index.ts +2160 -1785
- package/src/server/listener/index.ts +2 -2
- package/src/server/listener/manager.ts +475 -437
- package/src/server/listener/transmitter/index.ts +4 -5
- package/src/server/listener/transmitter/internal.ts +77 -79
- package/src/server/listener/transmitter/pull-responder.ts +363 -329
- package/src/server/listener/transmitter/switchboard-push.ts +72 -55
- package/src/server/listener/transmitter/types.ts +19 -25
- package/src/server/types.ts +536 -349
- package/src/server/utils.ts +26 -27
- package/src/storage/base.ts +81 -0
- package/src/storage/browser.ts +233 -216
- package/src/storage/filesystem.ts +257 -256
- package/src/storage/index.ts +2 -1
- package/src/storage/memory.ts +206 -214
- package/src/storage/prisma.ts +575 -568
- package/src/storage/sequelize.ts +460 -471
- package/src/storage/types.ts +83 -67
- package/src/utils/default-drives-manager.ts +341 -0
- package/src/utils/document-helpers.ts +19 -18
- package/src/utils/graphql.ts +288 -34
- package/src/utils/index.ts +61 -59
- package/src/utils/logger.ts +39 -37
- package/src/utils/migrations.ts +58 -0
- package/src/utils/run-asap.ts +156 -0
- package/CHANGELOG.md +0 -818
- package/src/server/listener/transmitter/subscription.ts +0 -364
|
@@ -1,375 +1,409 @@
|
|
|
1
|
-
import { ListenerFilter, Trigger } from
|
|
2
|
-
import { OperationScope } from
|
|
3
|
-
import { PULL_DRIVE_INTERVAL } from
|
|
4
|
-
import { generateUUID } from
|
|
5
|
-
import { gql, requestGraphql } from
|
|
6
|
-
import { logger as defaultLogger } from
|
|
7
|
-
import { OperationError } from
|
|
1
|
+
import { ListenerFilter, Trigger } from "document-model-libs/document-drive";
|
|
2
|
+
import { Operation, OperationScope } from "document-model/document";
|
|
3
|
+
import { PULL_DRIVE_INTERVAL } from "../..";
|
|
4
|
+
import { generateUUID } from "../../../utils";
|
|
5
|
+
import { gql, requestGraphql } from "../../../utils/graphql";
|
|
6
|
+
import { logger as defaultLogger } from "../../../utils/logger";
|
|
7
|
+
import { OperationError } from "../../error";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
GetStrandsOptions,
|
|
10
|
+
IBaseDocumentDriveServer,
|
|
11
|
+
IOperationResult,
|
|
12
|
+
Listener,
|
|
13
|
+
ListenerRevision,
|
|
14
|
+
ListenerRevisionWithError,
|
|
15
|
+
OperationUpdate,
|
|
16
|
+
RemoteDriveOptions,
|
|
17
|
+
StrandUpdate,
|
|
18
|
+
} from "../../types";
|
|
19
|
+
import { ListenerManager } from "../manager";
|
|
20
|
+
import {
|
|
21
|
+
ITransmitter,
|
|
22
|
+
PullResponderTrigger,
|
|
23
|
+
StrandUpdateSource,
|
|
24
|
+
} from "./types";
|
|
25
|
+
|
|
26
|
+
export type OperationUpdateGraphQL = Omit<OperationUpdate, "input"> & {
|
|
27
|
+
input: string;
|
|
23
28
|
};
|
|
24
29
|
|
|
25
30
|
export type PullStrandsGraphQL = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
31
|
+
system: {
|
|
32
|
+
sync: {
|
|
33
|
+
strands: StrandUpdateGraphQL[];
|
|
30
34
|
};
|
|
35
|
+
};
|
|
31
36
|
};
|
|
32
37
|
|
|
33
38
|
export type CancelPullLoop = () => void;
|
|
34
39
|
|
|
35
|
-
export type StrandUpdateGraphQL = Omit<StrandUpdate,
|
|
36
|
-
|
|
40
|
+
export type StrandUpdateGraphQL = Omit<StrandUpdate, "operations"> & {
|
|
41
|
+
operations: OperationUpdateGraphQL[];
|
|
37
42
|
};
|
|
38
43
|
|
|
39
|
-
export interface IPullResponderTransmitter extends
|
|
40
|
-
|
|
44
|
+
export interface IPullResponderTransmitter extends ITransmitter {
|
|
45
|
+
getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]>;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
constructor(
|
|
49
|
-
listener: Listener,
|
|
50
|
-
drive: BaseDocumentDriveServer,
|
|
51
|
-
manager: ListenerManager
|
|
52
|
-
) {
|
|
53
|
-
this.listener = listener;
|
|
54
|
-
this.drive = drive;
|
|
55
|
-
this.manager = manager;
|
|
56
|
-
}
|
|
49
|
+
private drive: IBaseDocumentDriveServer;
|
|
50
|
+
private listener: Listener;
|
|
51
|
+
private manager: ListenerManager;
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
constructor(
|
|
54
|
+
listener: Listener,
|
|
55
|
+
drive: IBaseDocumentDriveServer,
|
|
56
|
+
manager: ListenerManager,
|
|
57
|
+
) {
|
|
58
|
+
this.listener = listener;
|
|
59
|
+
this.drive = drive;
|
|
60
|
+
this.manager = manager;
|
|
61
|
+
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
getStrands(options?: GetStrandsOptions): Promise<StrandUpdate[]> {
|
|
64
|
+
return this.manager.getStrands(
|
|
65
|
+
this.listener.driveId,
|
|
66
|
+
this.listener.listenerId,
|
|
67
|
+
options,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
disconnect(): Promise<void> {
|
|
72
|
+
// TODO remove listener from switchboard
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async processAcknowledge(
|
|
77
|
+
driveId: string,
|
|
78
|
+
listenerId: string,
|
|
79
|
+
revisions: ListenerRevision[],
|
|
80
|
+
): Promise<boolean> {
|
|
81
|
+
const syncUnits = await this.manager.getListenerSyncUnitIds(
|
|
82
|
+
driveId,
|
|
83
|
+
listenerId,
|
|
84
|
+
);
|
|
85
|
+
let success = true;
|
|
86
|
+
for (const revision of revisions) {
|
|
87
|
+
const syncUnit = syncUnits.find(
|
|
88
|
+
(s) =>
|
|
89
|
+
s.scope === revision.scope &&
|
|
90
|
+
s.branch === revision.branch &&
|
|
91
|
+
s.driveId === revision.driveId &&
|
|
92
|
+
s.documentId == revision.documentId,
|
|
93
|
+
);
|
|
94
|
+
if (!syncUnit) {
|
|
95
|
+
defaultLogger.warn("Unknown sync unit was acknowledged", revision);
|
|
96
|
+
success = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await this.manager.updateListenerRevision(
|
|
101
|
+
listenerId,
|
|
102
|
+
driveId,
|
|
103
|
+
syncUnit.syncId,
|
|
104
|
+
revision.revision,
|
|
105
|
+
);
|
|
68
106
|
}
|
|
69
107
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
listenerId: string,
|
|
73
|
-
revisions: ListenerRevision[]
|
|
74
|
-
): Promise<boolean> {
|
|
75
|
-
const syncUnits = await this.manager.getListenerSyncUnitIds(
|
|
76
|
-
driveId,
|
|
77
|
-
listenerId
|
|
78
|
-
);
|
|
79
|
-
let success = true;
|
|
80
|
-
for (const revision of revisions) {
|
|
81
|
-
const syncUnit = syncUnits.find(
|
|
82
|
-
s =>
|
|
83
|
-
s.scope === revision.scope &&
|
|
84
|
-
s.branch === revision.branch &&
|
|
85
|
-
s.driveId === revision.driveId &&
|
|
86
|
-
s.documentId == revision.documentId
|
|
87
|
-
);
|
|
88
|
-
if (!syncUnit) {
|
|
89
|
-
defaultLogger.warn(
|
|
90
|
-
'Unknown sync unit was acknowledged',
|
|
91
|
-
revision
|
|
92
|
-
);
|
|
93
|
-
success = false;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
108
|
+
return success;
|
|
109
|
+
}
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
static async registerPullResponder(
|
|
112
|
+
driveId: string,
|
|
113
|
+
url: string,
|
|
114
|
+
filter: ListenerFilter,
|
|
115
|
+
): Promise<Listener["listenerId"]> {
|
|
116
|
+
// graphql request to switchboard
|
|
117
|
+
const result = await requestGraphql<{
|
|
118
|
+
registerPullResponderListener: {
|
|
119
|
+
listenerId: Listener["listenerId"];
|
|
120
|
+
};
|
|
121
|
+
}>(
|
|
122
|
+
url,
|
|
123
|
+
gql`
|
|
124
|
+
mutation registerPullResponderListener($filter: InputListenerFilter!) {
|
|
125
|
+
registerPullResponderListener(filter: $filter) {
|
|
126
|
+
listenerId
|
|
127
|
+
}
|
|
103
128
|
}
|
|
129
|
+
`,
|
|
130
|
+
{ filter },
|
|
131
|
+
);
|
|
104
132
|
|
|
105
|
-
|
|
133
|
+
const error = result.errors?.at(0);
|
|
134
|
+
if (error) {
|
|
135
|
+
throw error;
|
|
106
136
|
}
|
|
107
137
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
url: string,
|
|
111
|
-
filter: ListenerFilter
|
|
112
|
-
): Promise<Listener['listenerId']> {
|
|
113
|
-
// graphql request to switchboard
|
|
114
|
-
const { registerPullResponderListener } = await requestGraphql<{
|
|
115
|
-
registerPullResponderListener: {
|
|
116
|
-
listenerId: Listener['listenerId'];
|
|
117
|
-
};
|
|
118
|
-
}>(
|
|
119
|
-
url,
|
|
120
|
-
gql`
|
|
121
|
-
mutation registerPullResponderListener(
|
|
122
|
-
$filter: InputListenerFilter!
|
|
123
|
-
) {
|
|
124
|
-
registerPullResponderListener(filter: $filter) {
|
|
125
|
-
listenerId
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
`,
|
|
129
|
-
{ filter }
|
|
130
|
-
);
|
|
131
|
-
return registerPullResponderListener.listenerId;
|
|
138
|
+
if (!result.registerPullResponderListener) {
|
|
139
|
+
throw new Error("Error registering listener");
|
|
132
140
|
}
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
signature
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
142
|
+
return result.registerPullResponderListener.listenerId;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static async pullStrands(
|
|
146
|
+
driveId: string,
|
|
147
|
+
url: string,
|
|
148
|
+
listenerId: string,
|
|
149
|
+
options?: GetStrandsOptions, // TODO add support for since
|
|
150
|
+
): Promise<StrandUpdate[]> {
|
|
151
|
+
const result = await requestGraphql<PullStrandsGraphQL>(
|
|
152
|
+
url,
|
|
153
|
+
gql`
|
|
154
|
+
query strands($listenerId: ID!) {
|
|
155
|
+
system {
|
|
156
|
+
sync {
|
|
157
|
+
strands(listenerId: $listenerId) {
|
|
158
|
+
driveId
|
|
159
|
+
documentId
|
|
160
|
+
scope
|
|
161
|
+
branch
|
|
162
|
+
operations {
|
|
163
|
+
id
|
|
164
|
+
timestamp
|
|
165
|
+
skip
|
|
166
|
+
type
|
|
167
|
+
input
|
|
168
|
+
hash
|
|
169
|
+
index
|
|
170
|
+
context {
|
|
171
|
+
signer {
|
|
172
|
+
user {
|
|
173
|
+
address
|
|
174
|
+
networkId
|
|
175
|
+
chainId
|
|
176
|
+
}
|
|
177
|
+
app {
|
|
178
|
+
name
|
|
179
|
+
key
|
|
180
|
+
}
|
|
181
|
+
signatures
|
|
180
182
|
}
|
|
183
|
+
}
|
|
181
184
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
`,
|
|
190
|
+
{ listenerId },
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const error = result.errors?.at(0);
|
|
194
|
+
if (error) {
|
|
195
|
+
throw error;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
url: string,
|
|
197
|
-
listenerId: string,
|
|
198
|
-
revisions: ListenerRevision[]
|
|
199
|
-
): Promise<boolean> {
|
|
200
|
-
const result = await requestGraphql<{ acknowledge: boolean }>(
|
|
201
|
-
url,
|
|
202
|
-
gql`
|
|
203
|
-
mutation acknowledge(
|
|
204
|
-
$listenerId: String!
|
|
205
|
-
$revisions: [ListenerRevisionInput]
|
|
206
|
-
) {
|
|
207
|
-
acknowledge(listenerId: $listenerId, revisions: $revisions)
|
|
208
|
-
}
|
|
209
|
-
`,
|
|
210
|
-
{ listenerId, revisions }
|
|
211
|
-
);
|
|
212
|
-
return result.acknowledge;
|
|
198
|
+
if (!result.system) {
|
|
199
|
+
return [];
|
|
213
200
|
}
|
|
214
201
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
202
|
+
return result.system.sync.strands.map((s) => ({
|
|
203
|
+
...s,
|
|
204
|
+
operations: s.operations.map((o) => ({
|
|
205
|
+
...o,
|
|
206
|
+
input: JSON.parse(o.input) as object,
|
|
207
|
+
})),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static async acknowledgeStrands(
|
|
212
|
+
driveId: string,
|
|
213
|
+
url: string,
|
|
214
|
+
listenerId: string,
|
|
215
|
+
revisions: ListenerRevision[],
|
|
216
|
+
): Promise<boolean> {
|
|
217
|
+
const result = await requestGraphql<{ acknowledge: boolean }>(
|
|
218
|
+
url,
|
|
219
|
+
gql`
|
|
220
|
+
mutation acknowledge(
|
|
221
|
+
$listenerId: String!
|
|
222
|
+
$revisions: [ListenerRevisionInput]
|
|
223
|
+
) {
|
|
224
|
+
acknowledge(listenerId: $listenerId, revisions: $revisions)
|
|
225
|
+
}
|
|
226
|
+
`,
|
|
227
|
+
{ listenerId, revisions },
|
|
228
|
+
);
|
|
229
|
+
const error = result.errors?.at(0);
|
|
230
|
+
if (error) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
237
233
|
|
|
238
|
-
|
|
234
|
+
if (result.acknowledge === null) {
|
|
235
|
+
throw new Error("Error acknowledging strands");
|
|
236
|
+
}
|
|
237
|
+
return result.acknowledge;
|
|
238
|
+
}
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
private static async executePull(
|
|
241
|
+
driveId: string,
|
|
242
|
+
trigger: PullResponderTrigger,
|
|
243
|
+
onStrandUpdate: (
|
|
244
|
+
strand: StrandUpdate,
|
|
245
|
+
source: StrandUpdateSource,
|
|
246
|
+
) => Promise<IOperationResult>,
|
|
247
|
+
onError: (error: Error) => void,
|
|
248
|
+
onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
|
|
249
|
+
onAcknowledge?: (success: boolean) => void,
|
|
250
|
+
) {
|
|
251
|
+
try {
|
|
252
|
+
const { url, listenerId } = trigger.data;
|
|
253
|
+
const strands = await PullResponderTransmitter.pullStrands(
|
|
254
|
+
driveId,
|
|
255
|
+
url,
|
|
256
|
+
listenerId,
|
|
257
|
+
// since ?
|
|
258
|
+
);
|
|
252
259
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
result?.document?.operations[strand.scope]?.at(-1)
|
|
259
|
-
?.index ?? -1,
|
|
260
|
-
scope: strand.scope as OperationScope,
|
|
261
|
-
status: error
|
|
262
|
-
? error instanceof OperationError
|
|
263
|
-
? error.status
|
|
264
|
-
: 'ERROR'
|
|
265
|
-
: 'SUCCESS',
|
|
266
|
-
error
|
|
267
|
-
});
|
|
268
|
-
}
|
|
260
|
+
// if there are no new strands then do nothing
|
|
261
|
+
if (!strands.length) {
|
|
262
|
+
onRevisions?.([]);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
269
265
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await PullResponderTransmitter.acknowledgeStrands(
|
|
273
|
-
driveId,
|
|
274
|
-
url,
|
|
275
|
-
listenerId,
|
|
276
|
-
listenerRevisions.map(revision => {
|
|
277
|
-
const { error, ...rest } = revision;
|
|
278
|
-
return rest;
|
|
279
|
-
})
|
|
280
|
-
)
|
|
281
|
-
.then(result => onAcknowledge?.(result))
|
|
282
|
-
.catch(error => defaultLogger.error('ACK error', error));
|
|
283
|
-
} catch (error) {
|
|
284
|
-
onError(error as Error);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
266
|
+
const listenerRevisions: ListenerRevisionWithError[] = [];
|
|
287
267
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
268
|
+
for (const strand of strands) {
|
|
269
|
+
const operations: Operation[] = strand.operations.map((op) => ({
|
|
270
|
+
...op,
|
|
271
|
+
scope: strand.scope,
|
|
272
|
+
branch: strand.branch,
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
let error: Error | undefined = undefined;
|
|
276
|
+
try {
|
|
277
|
+
const result = await onStrandUpdate(strand, {
|
|
278
|
+
type: "trigger",
|
|
279
|
+
trigger,
|
|
280
|
+
});
|
|
281
|
+
if (result.error) {
|
|
282
|
+
throw result.error;
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
error = e as Error;
|
|
286
|
+
onError(error);
|
|
307
287
|
}
|
|
308
288
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
timeout = setTimeout(
|
|
324
|
-
resolve,
|
|
325
|
-
loopInterval
|
|
326
|
-
) as unknown as number;
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
};
|
|
289
|
+
listenerRevisions.push({
|
|
290
|
+
branch: strand.branch,
|
|
291
|
+
documentId: strand.documentId || "",
|
|
292
|
+
driveId: strand.driveId,
|
|
293
|
+
revision: operations.pop()?.index ?? -1,
|
|
294
|
+
scope: strand.scope,
|
|
295
|
+
status: error
|
|
296
|
+
? error instanceof OperationError
|
|
297
|
+
? error.status
|
|
298
|
+
: "ERROR"
|
|
299
|
+
: "SUCCESS",
|
|
300
|
+
error,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
330
303
|
|
|
331
|
-
|
|
304
|
+
onRevisions?.(listenerRevisions);
|
|
332
305
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
306
|
+
await PullResponderTransmitter.acknowledgeStrands(
|
|
307
|
+
driveId,
|
|
308
|
+
url,
|
|
309
|
+
listenerId,
|
|
310
|
+
listenerRevisions.map((revision) => {
|
|
311
|
+
const { error, ...rest } = revision;
|
|
312
|
+
return rest;
|
|
313
|
+
}),
|
|
314
|
+
)
|
|
315
|
+
.then((result) => onAcknowledge?.(result))
|
|
316
|
+
.catch((error) => defaultLogger.error("ACK error", error));
|
|
317
|
+
} catch (error) {
|
|
318
|
+
onError(error as Error);
|
|
339
319
|
}
|
|
320
|
+
}
|
|
340
321
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
322
|
+
static setupPull(
|
|
323
|
+
driveId: string,
|
|
324
|
+
trigger: PullResponderTrigger,
|
|
325
|
+
onStrandUpdate: (
|
|
326
|
+
strand: StrandUpdate,
|
|
327
|
+
source: StrandUpdateSource,
|
|
328
|
+
) => Promise<IOperationResult>,
|
|
329
|
+
onError: (error: Error) => void,
|
|
330
|
+
onRevisions?: (revisions: ListenerRevisionWithError[]) => void,
|
|
331
|
+
onAcknowledge?: (success: boolean) => void,
|
|
332
|
+
): CancelPullLoop {
|
|
333
|
+
const { interval } = trigger.data;
|
|
334
|
+
let loopInterval = PULL_DRIVE_INTERVAL;
|
|
335
|
+
if (interval) {
|
|
336
|
+
try {
|
|
337
|
+
const intervalNumber = parseInt(interval);
|
|
338
|
+
if (intervalNumber) {
|
|
339
|
+
loopInterval = intervalNumber;
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
// ignore invalid interval
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let isCancelled = false;
|
|
347
|
+
let timeout: number | undefined;
|
|
348
|
+
|
|
349
|
+
const executeLoop = async () => {
|
|
350
|
+
while (!isCancelled) {
|
|
351
|
+
await this.executePull(
|
|
352
|
+
driveId,
|
|
353
|
+
trigger,
|
|
354
|
+
onStrandUpdate,
|
|
355
|
+
onError,
|
|
356
|
+
onRevisions,
|
|
357
|
+
onAcknowledge,
|
|
356
358
|
);
|
|
359
|
+
await new Promise((resolve) => {
|
|
360
|
+
timeout = setTimeout(resolve, loopInterval) as unknown as number;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
};
|
|
357
364
|
|
|
358
|
-
|
|
359
|
-
id: generateUUID(),
|
|
360
|
-
type: 'PullResponder',
|
|
361
|
-
data: {
|
|
362
|
-
url,
|
|
363
|
-
listenerId,
|
|
364
|
-
interval: pullInterval?.toString() ?? ''
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
return pullTrigger;
|
|
368
|
-
}
|
|
365
|
+
executeLoop().catch(defaultLogger.error);
|
|
369
366
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
367
|
+
return () => {
|
|
368
|
+
isCancelled = true;
|
|
369
|
+
if (timeout !== undefined) {
|
|
370
|
+
clearTimeout(timeout);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
static async createPullResponderTrigger(
|
|
376
|
+
driveId: string,
|
|
377
|
+
url: string,
|
|
378
|
+
options: Pick<RemoteDriveOptions, "pullInterval" | "pullFilter">,
|
|
379
|
+
): Promise<PullResponderTrigger> {
|
|
380
|
+
const { pullFilter, pullInterval } = options;
|
|
381
|
+
const listenerId = await PullResponderTransmitter.registerPullResponder(
|
|
382
|
+
driveId,
|
|
383
|
+
url,
|
|
384
|
+
pullFilter ?? {
|
|
385
|
+
documentId: ["*"],
|
|
386
|
+
documentType: ["*"],
|
|
387
|
+
branch: ["*"],
|
|
388
|
+
scope: ["*"],
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const pullTrigger: PullResponderTrigger = {
|
|
393
|
+
id: generateUUID(),
|
|
394
|
+
type: "PullResponder",
|
|
395
|
+
data: {
|
|
396
|
+
url,
|
|
397
|
+
listenerId,
|
|
398
|
+
interval: pullInterval?.toString() ?? "",
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
return pullTrigger;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
static isPullResponderTrigger(
|
|
405
|
+
trigger: Trigger,
|
|
406
|
+
): trigger is PullResponderTrigger {
|
|
407
|
+
return trigger.type === "PullResponder";
|
|
408
|
+
}
|
|
375
409
|
}
|