document-drive 1.0.0-alpha.57 → 1.0.0-alpha.59
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 +1 -1
- package/src/queue/base.ts +0 -16
- package/src/queue/redis.ts +0 -11
- package/src/queue/types.ts +0 -3
- package/src/server/index.ts +2 -1
- package/src/server/listener/transmitter/pull-responder.ts +7 -8
- package/src/server/types.ts +1 -0
- package/src/storage/prisma.ts +60 -40
- package/src/storage/sequelize.ts +11 -7
package/package.json
CHANGED
package/src/queue/base.ts
CHANGED
|
@@ -10,7 +10,6 @@ export class MemoryQueue<T, R> implements IQueue<T, R> {
|
|
|
10
10
|
private blocked = false;
|
|
11
11
|
private deleted = false;
|
|
12
12
|
private items: IJob<T>[] = [];
|
|
13
|
-
private results = new Map<JobId, R>();
|
|
14
13
|
private dependencies = new Array<IJob<OperationJob>>();
|
|
15
14
|
|
|
16
15
|
constructor(id: string) {
|
|
@@ -25,15 +24,6 @@ export class MemoryQueue<T, R> implements IQueue<T, R> {
|
|
|
25
24
|
return this.deleted;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
async setResult(jobId: string, result: R): Promise<void> {
|
|
29
|
-
this.results.set(jobId, result);
|
|
30
|
-
return Promise.resolve();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getResult(jobId: string): Promise<R | undefined> {
|
|
34
|
-
return Promise.resolve(this.results.get(jobId));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
27
|
async addJob(data: IJob<T>) {
|
|
38
28
|
this.items.push(data);
|
|
39
29
|
return Promise.resolve();
|
|
@@ -157,11 +147,6 @@ export class BaseQueueManager implements IQueueManager {
|
|
|
157
147
|
return jobId;
|
|
158
148
|
}
|
|
159
149
|
|
|
160
|
-
async getResult(driveId: string, documentId: string, jobId: JobId): Promise<IOperationResult | undefined> {
|
|
161
|
-
const queue = this.getQueue(driveId, documentId);
|
|
162
|
-
return queue.getResult(jobId);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
150
|
getQueue(driveId: string, documentId?: string) {
|
|
166
151
|
const queueId = this.getQueueId(driveId, documentId);
|
|
167
152
|
let queue = this.queues.find((q) => q.getId() === queueId);
|
|
@@ -237,7 +222,6 @@ export class BaseQueueManager implements IQueueManager {
|
|
|
237
222
|
|
|
238
223
|
try {
|
|
239
224
|
const result = await this.delegate.processOperationJob(nextJob);
|
|
240
|
-
await queue.setResult(nextJob.jobId, result);
|
|
241
225
|
|
|
242
226
|
// unblock the document queues of each add_file operation
|
|
243
227
|
const addFileOperations = nextJob.operations.filter((op) => op.type === "ADD_FILE");
|
package/src/queue/redis.ts
CHANGED
|
@@ -13,17 +13,6 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
|
|
|
13
13
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async setResult(jobId: string, result: any): Promise<void> {
|
|
17
|
-
await this.client.hSet(this.id + "-results", jobId, JSON.stringify(result));
|
|
18
|
-
}
|
|
19
|
-
async getResult(jobId: string): Promise<any> {
|
|
20
|
-
const results = await this.client.hGet(this.id + "-results", jobId);
|
|
21
|
-
if (!results) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return JSON.parse(results);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
16
|
async addJob(data: any) {
|
|
28
17
|
await this.client.lPush(this.id + "-jobs", JSON.stringify(data));
|
|
29
18
|
}
|
package/src/queue/types.ts
CHANGED
|
@@ -23,7 +23,6 @@ export interface IServerDelegate {
|
|
|
23
23
|
|
|
24
24
|
export interface IQueueManager {
|
|
25
25
|
addJob(job: OperationJob): Promise<JobId>;
|
|
26
|
-
getResult(driveId: string, documentId: string, jobId: JobId): Promise<IOperationResult | undefined>;
|
|
27
26
|
getQueue(driveId: string, document?: string): IQueue<OperationJob, IOperationResult>;
|
|
28
27
|
removeQueue(driveId: string, documentId?: string): void;
|
|
29
28
|
getQueueByIndex(index: number): IQueue<OperationJob, IOperationResult> | null;
|
|
@@ -47,8 +46,6 @@ export interface IQueue<T, R> {
|
|
|
47
46
|
isBlocked(): Promise<boolean>;
|
|
48
47
|
isDeleted(): Promise<boolean>;
|
|
49
48
|
setDeleted(deleted: boolean): Promise<void>;
|
|
50
|
-
setResult(jobId: JobId, result: R): Promise<void>;
|
|
51
|
-
getResult(jobId: JobId): Promise<R | undefined>;
|
|
52
49
|
getJobs(): Promise<IJob<T>[]>;
|
|
53
50
|
addDependencies(job: IJob<OperationJob>): Promise<void>;
|
|
54
51
|
removeDependencies(job: IJob<OperationJob>): Promise<void>;
|
package/src/server/index.ts
CHANGED
|
@@ -425,7 +425,8 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
425
425
|
type: operation.type,
|
|
426
426
|
input: operation.input as object,
|
|
427
427
|
skip: operation.skip,
|
|
428
|
-
context: operation.context
|
|
428
|
+
context: operation.context,
|
|
429
|
+
id: operation.id
|
|
429
430
|
}));
|
|
430
431
|
}
|
|
431
432
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListenerFilter, Trigger
|
|
1
|
+
import { ListenerFilter, Trigger } from 'document-model-libs/document-drive';
|
|
2
2
|
import { Operation, OperationScope } from 'document-model/document';
|
|
3
3
|
import { PULL_DRIVE_INTERVAL } from '../..';
|
|
4
4
|
import { generateUUID } from '../../../utils';
|
|
@@ -153,6 +153,7 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
153
153
|
scope
|
|
154
154
|
branch
|
|
155
155
|
operations {
|
|
156
|
+
id
|
|
156
157
|
timestamp
|
|
157
158
|
skip
|
|
158
159
|
type
|
|
@@ -237,13 +238,11 @@ export class PullResponderTransmitter implements IPullResponderTransmitter {
|
|
|
237
238
|
const listenerRevisions: ListenerRevisionWithError[] = [];
|
|
238
239
|
|
|
239
240
|
for (const strand of strands) {
|
|
240
|
-
const operations: Operation[] = strand.operations.map(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
})
|
|
246
|
-
);
|
|
241
|
+
const operations: Operation[] = strand.operations.map(op => ({
|
|
242
|
+
...op,
|
|
243
|
+
scope: strand.scope,
|
|
244
|
+
branch: strand.branch
|
|
245
|
+
}));
|
|
247
246
|
|
|
248
247
|
let error: Error | undefined = undefined;
|
|
249
248
|
try {
|
package/src/server/types.ts
CHANGED
package/src/storage/prisma.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Prisma, PrismaClient } from '@prisma/client';
|
|
2
2
|
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
|
3
|
-
import { backOff, IBackOffOptions } from "exponential-backoff";
|
|
4
3
|
import {
|
|
5
4
|
DocumentDriveAction,
|
|
6
5
|
DocumentDriveLocalState,
|
|
@@ -15,14 +14,22 @@ import type {
|
|
|
15
14
|
OperationScope,
|
|
16
15
|
State
|
|
17
16
|
} from 'document-model/document';
|
|
17
|
+
import { IBackOffOptions, backOff } from 'exponential-backoff';
|
|
18
18
|
import { ConflictOperationError } from '../server/error';
|
|
19
19
|
import { logger } from '../utils/logger';
|
|
20
20
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
21
21
|
|
|
22
|
-
type Transaction =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
type Transaction =
|
|
23
|
+
| Omit<
|
|
24
|
+
PrismaClient<Prisma.PrismaClientOptions, never>,
|
|
25
|
+
| '$connect'
|
|
26
|
+
| '$disconnect'
|
|
27
|
+
| '$on'
|
|
28
|
+
| '$transaction'
|
|
29
|
+
| '$use'
|
|
30
|
+
| '$extends'
|
|
31
|
+
>
|
|
32
|
+
| ExtendedPrismaClient;
|
|
26
33
|
|
|
27
34
|
function storageToOperation(
|
|
28
35
|
op: Prisma.$OperationPayload['scalars']
|
|
@@ -35,7 +42,8 @@ function storageToOperation(
|
|
|
35
42
|
input: JSON.parse(op.input),
|
|
36
43
|
type: op.type,
|
|
37
44
|
scope: op.scope as OperationScope,
|
|
38
|
-
resultingState: op.resultingState ? op.resultingState : undefined
|
|
45
|
+
resultingState: op.resultingState ? op.resultingState : undefined,
|
|
46
|
+
id: op.opId || undefined
|
|
39
47
|
// attachments: fileRegistry
|
|
40
48
|
};
|
|
41
49
|
if (op.context) {
|
|
@@ -46,27 +54,32 @@ function storageToOperation(
|
|
|
46
54
|
|
|
47
55
|
export type PrismaStorageOptions = {
|
|
48
56
|
transactionRetryBackoff?: IBackOffOptions;
|
|
49
|
-
}
|
|
57
|
+
};
|
|
50
58
|
|
|
51
|
-
function getRetryTransactionsClient<T extends PrismaClient>(
|
|
59
|
+
function getRetryTransactionsClient<T extends PrismaClient>(
|
|
60
|
+
prisma: T,
|
|
61
|
+
backOffOptions?: Partial<IBackOffOptions>
|
|
62
|
+
) {
|
|
52
63
|
return prisma.$extends({
|
|
53
64
|
client: {
|
|
54
|
-
$transaction: (...args: Parameters<T[
|
|
65
|
+
$transaction: (...args: Parameters<T['$transaction']>) => {
|
|
55
66
|
// eslint-disable-next-line prefer-spread
|
|
56
67
|
return backOff(() => prisma.$transaction.apply(prisma, args), {
|
|
57
|
-
retry:
|
|
68
|
+
retry: e => {
|
|
58
69
|
// Retry the transaction only if the error was due to a write conflict or deadlock
|
|
59
70
|
// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034
|
|
60
|
-
return (e as { code: string }).code ===
|
|
71
|
+
return (e as { code: string }).code === 'P2034';
|
|
61
72
|
},
|
|
62
|
-
...backOffOptions
|
|
73
|
+
...backOffOptions
|
|
63
74
|
});
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
});
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
type ExtendedPrismaClient = ReturnType<
|
|
80
|
+
type ExtendedPrismaClient = ReturnType<
|
|
81
|
+
typeof getRetryTransactionsClient<PrismaClient>
|
|
82
|
+
>;
|
|
70
83
|
|
|
71
84
|
export class PrismaStorage implements IDriveStorage {
|
|
72
85
|
private db: ExtendedPrismaClient;
|
|
@@ -75,9 +88,8 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
75
88
|
const backOffOptions = options?.transactionRetryBackoff;
|
|
76
89
|
this.db = getRetryTransactionsClient(db, {
|
|
77
90
|
...backOffOptions,
|
|
78
|
-
jitter: backOffOptions?.jitter ??
|
|
91
|
+
jitter: backOffOptions?.jitter ?? 'full'
|
|
79
92
|
});
|
|
80
|
-
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
@@ -138,7 +150,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
138
150
|
initialState: JSON.stringify(document.initialState),
|
|
139
151
|
lastModified: document.lastModified,
|
|
140
152
|
revision: JSON.stringify(document.revision),
|
|
141
|
-
id
|
|
153
|
+
id
|
|
142
154
|
}
|
|
143
155
|
});
|
|
144
156
|
}
|
|
@@ -169,7 +181,10 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
169
181
|
branch: 'main',
|
|
170
182
|
skip: op.skip,
|
|
171
183
|
context: op.context,
|
|
172
|
-
|
|
184
|
+
opId: op.id,
|
|
185
|
+
resultingState: op.resultingState
|
|
186
|
+
? JSON.stringify(op.resultingState)
|
|
187
|
+
: undefined
|
|
173
188
|
}))
|
|
174
189
|
});
|
|
175
190
|
|
|
@@ -180,7 +195,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
180
195
|
},
|
|
181
196
|
data: {
|
|
182
197
|
lastModified: header.lastModified,
|
|
183
|
-
revision: JSON.stringify(header.revision)
|
|
198
|
+
revision: JSON.stringify(header.revision)
|
|
184
199
|
}
|
|
185
200
|
});
|
|
186
201
|
} catch (e) {
|
|
@@ -228,7 +243,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
228
243
|
callback: (document: DocumentStorage) => Promise<{
|
|
229
244
|
operations: Operation[];
|
|
230
245
|
header: DocumentHeader;
|
|
231
|
-
newState?: State<any, any> | undefined
|
|
246
|
+
newState?: State<any, any> | undefined;
|
|
232
247
|
}>
|
|
233
248
|
) {
|
|
234
249
|
let result: {
|
|
@@ -237,23 +252,25 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
237
252
|
newState?: State<any, any> | undefined;
|
|
238
253
|
} | null = null;
|
|
239
254
|
|
|
240
|
-
await this.db.$transaction(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const { operations, header, newState } = result;
|
|
248
|
-
return this._addDocumentOperations(
|
|
249
|
-
tx,
|
|
250
|
-
drive,
|
|
251
|
-
id,
|
|
252
|
-
operations,
|
|
253
|
-
header,
|
|
254
|
-
);
|
|
255
|
-
}, { isolationLevel: "Serializable" });
|
|
255
|
+
await this.db.$transaction(
|
|
256
|
+
async tx => {
|
|
257
|
+
const document = await this.getDocument(drive, id, tx);
|
|
258
|
+
if (!document) {
|
|
259
|
+
throw new Error(`Document with id ${id} not found`);
|
|
260
|
+
}
|
|
261
|
+
result = await callback(document);
|
|
256
262
|
|
|
263
|
+
const { operations, header, newState } = result;
|
|
264
|
+
return this._addDocumentOperations(
|
|
265
|
+
tx,
|
|
266
|
+
drive,
|
|
267
|
+
id,
|
|
268
|
+
operations,
|
|
269
|
+
header
|
|
270
|
+
);
|
|
271
|
+
},
|
|
272
|
+
{ isolationLevel: 'Serializable' }
|
|
273
|
+
);
|
|
257
274
|
|
|
258
275
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
259
276
|
if (!result) {
|
|
@@ -267,14 +284,14 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
267
284
|
drive: string,
|
|
268
285
|
id: string,
|
|
269
286
|
operations: Operation[],
|
|
270
|
-
header: DocumentHeader
|
|
287
|
+
header: DocumentHeader
|
|
271
288
|
): Promise<void> {
|
|
272
289
|
return this._addDocumentOperations(
|
|
273
290
|
this.db,
|
|
274
291
|
drive,
|
|
275
292
|
id,
|
|
276
293
|
operations,
|
|
277
|
-
header
|
|
294
|
+
header
|
|
278
295
|
);
|
|
279
296
|
}
|
|
280
297
|
|
|
@@ -298,7 +315,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
298
315
|
where: {
|
|
299
316
|
id: id,
|
|
300
317
|
driveId: driveId
|
|
301
|
-
}
|
|
318
|
+
}
|
|
302
319
|
});
|
|
303
320
|
return count > 0;
|
|
304
321
|
}
|
|
@@ -347,7 +364,10 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
347
364
|
clipboard: dbDoc.operations
|
|
348
365
|
.filter(op => op.clipboard)
|
|
349
366
|
.map(storageToOperation),
|
|
350
|
-
revision: JSON.parse(dbDoc.revision) as Record<
|
|
367
|
+
revision: JSON.parse(dbDoc.revision) as Record<
|
|
368
|
+
OperationScope,
|
|
369
|
+
number
|
|
370
|
+
>,
|
|
351
371
|
attachments: {}
|
|
352
372
|
};
|
|
353
373
|
|
package/src/storage/sequelize.ts
CHANGED
|
@@ -25,8 +25,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
25
25
|
type: DataTypes.STRING,
|
|
26
26
|
primaryKey: true
|
|
27
27
|
},
|
|
28
|
-
id: DataTypes.STRING
|
|
29
|
-
})
|
|
28
|
+
id: DataTypes.STRING
|
|
29
|
+
});
|
|
30
30
|
const Document = this.db.define('document', {
|
|
31
31
|
id: {
|
|
32
32
|
type: DataTypes.STRING,
|
|
@@ -155,7 +155,7 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
155
155
|
drive: string,
|
|
156
156
|
id: string,
|
|
157
157
|
operations: Operation[],
|
|
158
|
-
header: DocumentHeader
|
|
158
|
+
header: DocumentHeader
|
|
159
159
|
): Promise<void> {
|
|
160
160
|
const document = await this.getDocument(drive, id);
|
|
161
161
|
if (!document) {
|
|
@@ -177,7 +177,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
177
177
|
timestamp: op.timestamp,
|
|
178
178
|
type: op.type,
|
|
179
179
|
scope: op.scope,
|
|
180
|
-
branch: 'main'
|
|
180
|
+
branch: 'main',
|
|
181
|
+
opId: op.id
|
|
181
182
|
}))
|
|
182
183
|
);
|
|
183
184
|
|
|
@@ -284,8 +285,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
284
285
|
where: {
|
|
285
286
|
id: id,
|
|
286
287
|
driveId: driveId
|
|
287
|
-
}
|
|
288
|
-
})
|
|
288
|
+
}
|
|
289
|
+
});
|
|
289
290
|
|
|
290
291
|
return count > 0;
|
|
291
292
|
}
|
|
@@ -323,6 +324,7 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
323
324
|
input: JSON;
|
|
324
325
|
type: string;
|
|
325
326
|
scope: string;
|
|
327
|
+
opId?: string;
|
|
326
328
|
}
|
|
327
329
|
];
|
|
328
330
|
revision: Required<Record<OperationScope, number>>;
|
|
@@ -348,13 +350,15 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
348
350
|
input: JSON;
|
|
349
351
|
type: string;
|
|
350
352
|
scope: string;
|
|
353
|
+
opId?: string;
|
|
351
354
|
}) => ({
|
|
352
355
|
hash: op.hash,
|
|
353
356
|
index: op.index,
|
|
354
357
|
timestamp: new Date(op.timestamp).toISOString(),
|
|
355
358
|
input: op.input,
|
|
356
359
|
type: op.type,
|
|
357
|
-
scope: op.scope as OperationScope
|
|
360
|
+
scope: op.scope as OperationScope,
|
|
361
|
+
id: op.opId
|
|
358
362
|
// attachments: fileRegistry
|
|
359
363
|
})
|
|
360
364
|
);
|