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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-drive",
3
- "version": "1.0.0-alpha.57",
3
+ "version": "1.0.0-alpha.59",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
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");
@@ -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
  }
@@ -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>;
@@ -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, z } from 'document-model-libs/document-drive';
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
- (op) => ({
242
- ...op,
243
- scope: strand.scope,
244
- branch: strand.branch
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 {
@@ -117,6 +117,7 @@ export type OperationUpdate = {
117
117
  input: object;
118
118
  hash: string;
119
119
  context?: ActionContext;
120
+ id?: string;
120
121
  };
121
122
 
122
123
  export type StrandUpdate = {
@@ -1,6 +1,5 @@
1
- import { PrismaClient, Prisma } from '@prisma/client';
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 = Omit<
23
- PrismaClient<Prisma.PrismaClientOptions, never>,
24
- '$connect' | '$disconnect' | '$on' | '$transaction' | '$use' | '$extends'
25
- > | ExtendedPrismaClient;
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>(prisma: T, backOffOptions?: Partial<IBackOffOptions>) {
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["$transaction"]>) => {
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: (e) => {
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 === "P2034";
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<typeof getRetryTransactionsClient<PrismaClient>>;
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 ?? "full"
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
- resultingState: op.resultingState ? JSON.stringify(op.resultingState) : undefined
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(async tx => {
241
- const document = await this.getDocument(drive, id, tx);
242
- if (!document) {
243
- throw new Error(`Document with id ${id} not found`);
244
- }
245
- result = await callback(document);
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<OperationScope, number>,
367
+ revision: JSON.parse(dbDoc.revision) as Record<
368
+ OperationScope,
369
+ number
370
+ >,
351
371
  attachments: {}
352
372
  };
353
373
 
@@ -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
  );