document-drive 1.0.0-alpha.83 → 1.0.0-alpha.84

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 CHANGED
@@ -1 +1 @@
1
- # Document Drive
1
+ # Document Drive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-drive",
3
- "version": "1.0.0-alpha.83",
3
+ "version": "1.0.0-alpha.84",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -1,5 +1,5 @@
1
- import { Document } from "document-model/document";
2
- import { ICache } from "./types";
1
+ import { Document } from 'document-model/document';
2
+ import { ICache } from './types';
3
3
 
4
4
  class InMemoryCache implements ICache {
5
5
  private cache = new Map<string, Map<string, Document>>();
@@ -13,7 +13,7 @@ class InMemoryCache implements ICache {
13
13
  delete e.resultingState;
14
14
  return e;
15
15
  });
16
- const doc = { ...document, operations: { global, local } }
16
+ const doc = { ...document, operations: { global, local } };
17
17
  if (!this.cache.has(drive)) {
18
18
  this.cache.set(drive, new Map());
19
19
  }
@@ -1,12 +1,15 @@
1
- import { Document } from "document-model/document";
2
- import { ICache } from "./types";
3
- import type { RedisClientType } from "redis";
1
+ import { Document } from 'document-model/document';
2
+ import type { RedisClientType } from 'redis';
3
+ import { ICache } from './types';
4
4
 
5
5
  class RedisCache implements ICache {
6
6
  private redis: RedisClientType;
7
7
  private timeoutInSeconds: number;
8
8
 
9
- constructor(redis: RedisClientType, timeoutInSeconds: number | undefined = 5 * 60) {
9
+ constructor(
10
+ redis: RedisClientType,
11
+ timeoutInSeconds: number | undefined = 5 * 60
12
+ ) {
10
13
  this.redis = redis;
11
14
  this.timeoutInSeconds = timeoutInSeconds;
12
15
  }
@@ -24,7 +27,7 @@ class RedisCache implements ICache {
24
27
  delete e.resultingState;
25
28
  return e;
26
29
  });
27
- const doc = { ...document, operations: { global, local } }
30
+ const doc = { ...document, operations: { global, local } };
28
31
  const redisId = RedisCache._getId(drive, id);
29
32
  const result = await this.redis.set(redisId, JSON.stringify(doc), {
30
33
  EX: this.timeoutInSeconds ? this.timeoutInSeconds : undefined
@@ -41,7 +44,7 @@ class RedisCache implements ICache {
41
44
  const redisId = RedisCache._getId(drive, id);
42
45
  const doc = await this.redis.get(redisId);
43
46
 
44
- return doc ? JSON.parse(doc) as Document : undefined;
47
+ return doc ? (JSON.parse(doc) as Document) : undefined;
45
48
  }
46
49
 
47
50
  async deleteDocument(drive: string, id: string) {
@@ -1,9 +1,13 @@
1
- import type { Document } from "document-model/document";
1
+ import type { Document } from 'document-model/document';
2
2
 
3
3
  export interface ICache {
4
- setDocument(drive: string, id: string, document: Document): Promise<boolean>
5
- getDocument(drive: string, id: string): Promise<Document | undefined>
4
+ setDocument(
5
+ drive: string,
6
+ id: string,
7
+ document: Document
8
+ ): Promise<boolean>;
9
+ getDocument(drive: string, id: string): Promise<Document | undefined>;
6
10
 
7
11
  // @returns — true if a document existed and has been removed, or false if the document is not cached.
8
- deleteDocument(drive: string, id: string): Promise<boolean>
12
+ deleteDocument(drive: string, id: string): Promise<boolean>;
9
13
  }
package/src/queue/base.ts CHANGED
@@ -1,8 +1,21 @@
1
- import { IJob, IJobQueue, IQueue, IQueueManager, IServerDelegate, JobId, Job, QueueEvents, isOperationJob } from "./types";
2
- import { generateUUID } from "../utils";
3
- import { createNanoEvents, Unsubscribe } from 'nanoevents';
4
- import { Action } from "document-model/document";
5
- import { AddFileInput, DeleteNodeInput } from "document-model-libs/document-drive";
1
+ import {
2
+ AddFileInput,
3
+ DeleteNodeInput
4
+ } from 'document-model-libs/document-drive';
5
+ import { Action } from 'document-model/document';
6
+ import { Unsubscribe, createNanoEvents } from 'nanoevents';
7
+ import { generateUUID } from '../utils';
8
+ import {
9
+ IJob,
10
+ IJobQueue,
11
+ IQueue,
12
+ IQueueManager,
13
+ IServerDelegate,
14
+ Job,
15
+ JobId,
16
+ QueueEvents,
17
+ isOperationJob
18
+ } from './types';
6
19
 
7
20
  export class MemoryQueue<T, R> implements IQueue<T, R> {
8
21
  private id: string;
@@ -63,7 +76,9 @@ export class MemoryQueue<T, R> implements IQueue<T, R> {
63
76
  }
64
77
 
65
78
  async removeDependencies(job: IJob<Job>) {
66
- this.dependencies = this.dependencies.filter((j) => j.jobId !== job.jobId && j.driveId !== job.driveId);
79
+ this.dependencies = this.dependencies.filter(
80
+ j => j.jobId !== job.jobId && j.driveId !== job.driveId
81
+ );
67
82
  if (this.dependencies.length === 0) {
68
83
  await this.setBlocked(false);
69
84
  }
@@ -83,29 +98,40 @@ export class BaseQueueManager implements IQueueManager {
83
98
  this.timeout = timeout;
84
99
  }
85
100
 
86
- async init(delegate: IServerDelegate, onError: (error: Error) => void): Promise<void> {
101
+ async init(
102
+ delegate: IServerDelegate,
103
+ onError: (error: Error) => void
104
+ ): Promise<void> {
87
105
  this.delegate = delegate;
88
106
  for (let i = 0; i < this.workers; i++) {
89
- setTimeout(() => this.processNextJob.bind(this)().catch(onError), 100 * i);
107
+ setTimeout(
108
+ () => this.processNextJob.bind(this)().catch(onError),
109
+ 100 * i
110
+ );
90
111
  }
91
- return Promise.resolve()
112
+ return Promise.resolve();
92
113
  }
93
114
 
94
115
  async addJob(job: Job): Promise<JobId> {
95
116
  if (!this.delegate) {
96
- throw new Error("No server delegate defined");
117
+ throw new Error('No server delegate defined');
97
118
  }
98
119
 
99
120
  const jobId = generateUUID();
100
121
  const queue = this.getQueue(job.driveId, job.documentId);
101
122
 
102
123
  if (await queue.isDeleted()) {
103
- throw new Error("Queue is deleted")
124
+ throw new Error('Queue is deleted');
104
125
  }
105
126
 
106
127
  // checks if the job is for a document that doesn't exist in storage yet
107
- const newDocument = job.documentId && !(await this.delegate.checkDocumentExists(job.driveId, job.documentId));
108
- // if it is a new document and queue is not yet blocked then
128
+ const newDocument =
129
+ job.documentId &&
130
+ !(await this.delegate.checkDocumentExists(
131
+ job.driveId,
132
+ job.documentId
133
+ ));
134
+ // if it is a new document and queue is not yet blocked then
109
135
  // blocks it so the jobs are not processed until it's ready
110
136
  if (newDocument && !(await queue.isBlocked())) {
111
137
  await queue.setBlocked(true);
@@ -114,11 +140,13 @@ export class BaseQueueManager implements IQueueManager {
114
140
  const driveQueue = this.getQueue(job.driveId);
115
141
  const jobs = await driveQueue.getJobs();
116
142
  for (const driveJob of jobs) {
117
- const actions = isOperationJob(driveJob) ? driveJob.operations : driveJob.actions;
143
+ const actions = isOperationJob(driveJob)
144
+ ? driveJob.operations
145
+ : driveJob.actions;
118
146
  const op = actions.find((j: Action) => {
119
147
  const input = j.input as AddFileInput;
120
- return j.type === "ADD_FILE" && input.id === job.documentId
121
- })
148
+ return j.type === 'ADD_FILE' && input.id === job.documentId;
149
+ });
122
150
  if (op) {
123
151
  await queue.addDependencies(driveJob);
124
152
  }
@@ -128,15 +156,17 @@ export class BaseQueueManager implements IQueueManager {
128
156
  // if it has ADD_FILE operations then adds the job as
129
157
  // a dependency to the corresponding document queues
130
158
  const actions = isOperationJob(job) ? job.operations : job.actions;
131
- const addFileOps = actions.filter((j: Action) => j.type === "ADD_FILE");
159
+ const addFileOps = actions.filter((j: Action) => j.type === 'ADD_FILE');
132
160
  for (const addFileOp of addFileOps) {
133
161
  const input = addFileOp.input as AddFileInput;
134
- const q = this.getQueue(job.driveId, input.id)
162
+ const q = this.getQueue(job.driveId, input.id);
135
163
  await q.addDependencies({ jobId, ...job });
136
164
  }
137
165
 
138
166
  // remove document if operations contains delete_node
139
- const removeFileOps = actions.filter((j: Action) => j.type === "DELETE_NODE");
167
+ const removeFileOps = actions.filter(
168
+ (j: Action) => j.type === 'DELETE_NODE'
169
+ );
140
170
  for (const removeFileOp of removeFileOps) {
141
171
  const input = removeFileOp.input as DeleteNodeInput;
142
172
  const queue = this.getQueue(job.driveId, input.id);
@@ -150,7 +180,7 @@ export class BaseQueueManager implements IQueueManager {
150
180
 
151
181
  getQueue(driveId: string, documentId?: string) {
152
182
  const queueId = this.getQueueId(driveId, documentId);
153
- let queue = this.queues.find((q) => q.getId() === queueId);
183
+ let queue = this.queues.find(q => q.getId() === queueId);
154
184
 
155
185
  if (!queue) {
156
186
  queue = new MemoryQueue(queueId);
@@ -162,8 +192,8 @@ export class BaseQueueManager implements IQueueManager {
162
192
 
163
193
  removeQueue(driveId: string, documentId?: string) {
164
194
  const queueId = this.getQueueId(driveId, documentId);
165
- this.queues = this.queues.filter((q) => q.getId() !== queueId);
166
- this.emit("queueRemoved", queueId)
195
+ this.queues = this.queues.filter(q => q.getId() !== queueId);
196
+ this.emit('queueRemoved', queueId);
167
197
  }
168
198
 
169
199
  getQueueByIndex(index: number) {
@@ -176,17 +206,20 @@ export class BaseQueueManager implements IQueueManager {
176
206
  }
177
207
 
178
208
  getQueues() {
179
- return Object.keys(new Array(this.queues));
209
+ return this.queues.map(q => q.getId());
180
210
  }
181
211
 
182
212
  private retryNextJob() {
183
- const retry = this.timeout === 0 && typeof setImmediate !== "undefined" ? setImmediate : (fn: () => void) => setTimeout(fn, this.timeout);
213
+ const retry =
214
+ this.timeout === 0 && typeof setImmediate !== 'undefined'
215
+ ? setImmediate
216
+ : (fn: () => void) => setTimeout(fn, this.timeout);
184
217
  return retry(() => this.processNextJob());
185
218
  }
186
219
 
187
220
  private async processNextJob() {
188
221
  if (!this.delegate) {
189
- throw new Error("No server delegate defined");
222
+ throw new Error('No server delegate defined');
190
223
  }
191
224
 
192
225
  if (this.queues.length === 0) {
@@ -195,7 +228,8 @@ export class BaseQueueManager implements IQueueManager {
195
228
  }
196
229
 
197
230
  const queue = this.queues[this.ticker];
198
- this.ticker = this.ticker === this.queues.length - 1 ? 0 : this.ticker + 1;
231
+ this.ticker =
232
+ this.ticker === this.queues.length - 1 ? 0 : this.ticker + 1;
199
233
  if (!queue) {
200
234
  this.ticker = 0;
201
235
  this.retryNextJob();
@@ -225,17 +259,22 @@ export class BaseQueueManager implements IQueueManager {
225
259
  const result = await this.delegate.processJob(nextJob);
226
260
 
227
261
  // unblock the document queues of each add_file operation
228
- const actions = isOperationJob(nextJob) ? nextJob.operations : nextJob.actions;
229
- const addFileActions = actions.filter((op) => op.type === "ADD_FILE");
262
+ const actions = isOperationJob(nextJob)
263
+ ? nextJob.operations
264
+ : nextJob.actions;
265
+ const addFileActions = actions.filter(op => op.type === 'ADD_FILE');
230
266
  if (addFileActions.length > 0) {
231
267
  for (const addFile of addFileActions) {
232
- const documentQueue = this.getQueue(nextJob.driveId, (addFile.input as AddFileInput).id);
268
+ const documentQueue = this.getQueue(
269
+ nextJob.driveId,
270
+ (addFile.input as AddFileInput).id
271
+ );
233
272
  await documentQueue.removeDependencies(nextJob);
234
- };
273
+ }
235
274
  }
236
- this.emit("jobCompleted", nextJob, result);
275
+ this.emit('jobCompleted', nextJob, result);
237
276
  } catch (e) {
238
- this.emit("jobFailed", nextJob, e as Error);
277
+ this.emit('jobFailed', nextJob, e as Error);
239
278
  } finally {
240
279
  await queue.setBlocked(false);
241
280
  await this.processNextJob();
@@ -248,11 +287,15 @@ export class BaseQueueManager implements IQueueManager {
248
287
  ) {
249
288
  this.emitter.emit(event, ...args);
250
289
  }
251
- on<K extends keyof QueueEvents>(this: this, event: K, cb: QueueEvents[K]): Unsubscribe {
290
+ on<K extends keyof QueueEvents>(
291
+ this: this,
292
+ event: K,
293
+ cb: QueueEvents[K]
294
+ ): Unsubscribe {
252
295
  return this.emitter.on(event, cb);
253
296
  }
254
297
 
255
298
  protected getQueueId(driveId: string, documentId?: string) {
256
299
  return `queue:${driveId}${documentId ? `:${documentId}` : ''}`;
257
300
  }
258
- }
301
+ }
@@ -1,2 +1,2 @@
1
1
  export * from './base';
2
- export * from './types';
2
+ export * from './types';
@@ -1,6 +1,12 @@
1
- import { RedisClientType } from "redis";
2
- import { IJob, IQueue, IQueueManager, IServerDelegate, OperationJob } from "./types";
3
- import { BaseQueueManager } from "./base";
1
+ import { RedisClientType } from 'redis';
2
+ import { BaseQueueManager } from './base';
3
+ import {
4
+ IJob,
5
+ IQueue,
6
+ IQueueManager,
7
+ IServerDelegate,
8
+ OperationJob
9
+ } from './types';
4
10
 
5
11
  export class RedisQueue<T, R> implements IQueue<T, R> {
6
12
  private id: string;
@@ -9,16 +15,16 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
9
15
  constructor(id: string, client: RedisClientType) {
10
16
  this.client = client;
11
17
  this.id = id;
12
- this.client.hSet("queues", id, "true");
13
- this.client.hSet(this.id, "blocked", "false");
18
+ this.client.hSet('queues', id, 'true');
19
+ this.client.hSet(this.id, 'blocked', 'false');
14
20
  }
15
21
 
16
22
  async addJob(data: any) {
17
- await this.client.lPush(this.id + "-jobs", JSON.stringify(data));
23
+ await this.client.lPush(this.id + '-jobs', JSON.stringify(data));
18
24
  }
19
25
 
20
26
  async getNextJob() {
21
- const job = await this.client.rPop(this.id + "-jobs");
27
+ const job = await this.client.rPop(this.id + '-jobs');
22
28
  if (!job) {
23
29
  return undefined;
24
30
  }
@@ -26,20 +32,20 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
26
32
  }
27
33
 
28
34
  async amountOfJobs() {
29
- return this.client.lLen(this.id + "-jobs");
35
+ return this.client.lLen(this.id + '-jobs');
30
36
  }
31
37
 
32
38
  async setBlocked(blocked: boolean) {
33
39
  if (blocked) {
34
- await this.client.hSet(this.id, "blocked", "true");
40
+ await this.client.hSet(this.id, 'blocked', 'true');
35
41
  } else {
36
- await this.client.hSet(this.id, "blocked", "false");
42
+ await this.client.hSet(this.id, 'blocked', 'false');
37
43
  }
38
44
  }
39
45
 
40
46
  async isBlocked() {
41
- const blockedResult = await this.client.hGet(this.id, "blocked");
42
- if (blockedResult === "true") {
47
+ const blockedResult = await this.client.hGet(this.id, 'blocked');
48
+ if (blockedResult === 'true') {
43
49
  return true;
44
50
  }
45
51
 
@@ -51,7 +57,7 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
51
57
  }
52
58
 
53
59
  async getJobs() {
54
- const entries = await this.client.lRange(this.id + "-jobs", 0, -1)
60
+ const entries = await this.client.lRange(this.id + '-jobs', 0, -1);
55
61
  return entries.map(e => JSON.parse(e) as IJob<T>);
56
62
  }
57
63
 
@@ -59,18 +65,18 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
59
65
  if (await this.hasDependency(job)) {
60
66
  return;
61
67
  }
62
- await this.client.lPush(this.id + "-deps", JSON.stringify(job));
68
+ await this.client.lPush(this.id + '-deps', JSON.stringify(job));
63
69
  await this.setBlocked(true);
64
70
  }
65
71
 
66
72
  async hasDependency(job: IJob<OperationJob>) {
67
- const deps = await this.client.lRange(this.id + "-deps", 0, -1);
73
+ const deps = await this.client.lRange(this.id + '-deps', 0, -1);
68
74
  return deps.some(d => d === JSON.stringify(job));
69
75
  }
70
76
 
71
77
  async removeDependencies(job: IJob<OperationJob>) {
72
- await this.client.lRem(this.id + "-deps", 1, JSON.stringify(job));
73
- const allDeps = await this.client.lLen(this.id + "-deps");
78
+ await this.client.lRem(this.id + '-deps', 1, JSON.stringify(job));
79
+ const allDeps = await this.client.lLen(this.id + '-deps');
74
80
  if (allDeps > 0) {
75
81
  await this.setBlocked(true);
76
82
  } else {
@@ -79,21 +85,23 @@ export class RedisQueue<T, R> implements IQueue<T, R> {
79
85
  }
80
86
 
81
87
  async isDeleted() {
82
- const active = await this.client.hGet("queues", this.id);
83
- return active === "false";
88
+ const active = await this.client.hGet('queues', this.id);
89
+ return active === 'false';
84
90
  }
85
91
 
86
92
  async setDeleted(deleted: boolean) {
87
93
  if (deleted) {
88
- await this.client.hSet("queues", this.id, "false");
94
+ await this.client.hSet('queues', this.id, 'false');
89
95
  } else {
90
- await this.client.hSet("queues", this.id, "true");
96
+ await this.client.hSet('queues', this.id, 'true');
91
97
  }
92
98
  }
93
99
  }
94
100
 
95
- export class RedisQueueManager extends BaseQueueManager implements IQueueManager {
96
-
101
+ export class RedisQueueManager
102
+ extends BaseQueueManager
103
+ implements IQueueManager
104
+ {
97
105
  private client: RedisClientType;
98
106
 
99
107
  constructor(workers = 3, timeout = 0, client: RedisClientType) {
@@ -101,12 +109,15 @@ export class RedisQueueManager extends BaseQueueManager implements IQueueManager
101
109
  this.client = client;
102
110
  }
103
111
 
104
- async init(delegate: IServerDelegate, onError: (error: Error) => void): Promise<void> {
112
+ async init(
113
+ delegate: IServerDelegate,
114
+ onError: (error: Error) => void
115
+ ): Promise<void> {
105
116
  await super.init(delegate, onError);
106
- const queues = await this.client.hGetAll("queues");
117
+ const queues = await this.client.hGetAll('queues');
107
118
  for (const queueId in queues) {
108
- const active = await this.client.hGet("queues", queueId);
109
- if (active === "true") {
119
+ const active = await this.client.hGet('queues', queueId);
120
+ if (active === 'true') {
110
121
  this.queues.push(new RedisQueue(queueId, this.client));
111
122
  }
112
123
  }
@@ -114,7 +125,7 @@ export class RedisQueueManager extends BaseQueueManager implements IQueueManager
114
125
 
115
126
  getQueue(driveId: string, documentId?: string) {
116
127
  const queueId = this.getQueueId(driveId, documentId);
117
- let queue = this.queues.find((q) => q.getId() === queueId);
128
+ let queue = this.queues.find(q => q.getId() === queueId);
118
129
 
119
130
  if (!queue) {
120
131
  queue = new RedisQueue(queueId, this.client);
@@ -128,6 +139,6 @@ export class RedisQueueManager extends BaseQueueManager implements IQueueManager
128
139
  super.removeQueue(driveId, documentId);
129
140
 
130
141
  const queueId = this.getQueueId(driveId, documentId);
131
- this.client.hDel("queues", queueId);
142
+ this.client.hDel('queues', queueId);
132
143
  }
133
- }
144
+ }
@@ -1,20 +1,20 @@
1
- import { Action, Operation } from "document-model/document";
2
- import { AddOperationOptions, IOperationResult } from "../server";
3
- import type { Unsubscribe } from "nanoevents";
1
+ import { Action, Operation } from 'document-model/document';
2
+ import type { Unsubscribe } from 'nanoevents';
3
+ import { AddOperationOptions, IOperationResult } from '../server';
4
4
 
5
5
  export interface BaseJob {
6
6
  driveId: string;
7
- documentId?: string
8
- actions?: Action[]
9
- options?: AddOperationOptions;
7
+ documentId?: string;
8
+ actions?: Action[];
9
+ options?: AddOperationOptions;
10
10
  }
11
11
 
12
12
  export interface OperationJob extends BaseJob {
13
- operations: Operation[]
13
+ operations: Operation[];
14
14
  }
15
15
 
16
16
  export interface ActionJob extends BaseJob {
17
- actions: Action[]
17
+ actions: Action[];
18
18
  }
19
19
 
20
20
  export type Job = OperationJob | ActionJob;
@@ -28,9 +28,12 @@ export interface QueueEvents {
28
28
  }
29
29
 
30
30
  export interface IServerDelegate {
31
- checkDocumentExists: (driveId: string, documentId: string) => Promise<boolean>;
31
+ checkDocumentExists: (
32
+ driveId: string,
33
+ documentId: string
34
+ ) => Promise<boolean>;
32
35
  processJob: (job: Job) => Promise<IOperationResult>;
33
- };
36
+ }
34
37
 
35
38
  export interface IQueueManager {
36
39
  addJob(job: Job): Promise<JobId>;
@@ -38,7 +41,10 @@ export interface IQueueManager {
38
41
  removeQueue(driveId: string, documentId?: string): void;
39
42
  getQueueByIndex(index: number): IQueue<Job, IOperationResult> | null;
40
43
  getQueues(): string[];
41
- init(delegate: IServerDelegate, onError: (error: Error) => void): Promise<void>;
44
+ init(
45
+ delegate: IServerDelegate,
46
+ onError: (error: Error) => void
47
+ ): Promise<void>;
42
48
  on<K extends keyof QueueEvents>(
43
49
  this: this,
44
50
  event: K,
@@ -65,9 +71,9 @@ export interface IQueue<T, R> {
65
71
  export type IJobQueue = IQueue<Job, IOperationResult>;
66
72
 
67
73
  export function isOperationJob(job: Job): job is OperationJob {
68
- return "operations" in job;
74
+ return 'operations' in job;
69
75
  }
70
76
 
71
77
  export function isActionJob(job: Job): job is ActionJob {
72
- return "actions" in job;
73
- }
78
+ return 'actions' in job;
79
+ }
@@ -1368,37 +1368,42 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1368
1368
  actions: (DocumentDriveAction | BaseAction)[],
1369
1369
  options?: AddOperationOptions
1370
1370
  ): Promise<IOperationResult<DocumentDriveDocument>> {
1371
- const jobId = await this.queueManager.addJob({
1372
- driveId: drive,
1373
- actions,
1374
- options
1375
- });
1376
- return new Promise<IOperationResult<DocumentDriveDocument>>(
1377
- (resolve, reject) => {
1378
- const unsubscribe = this.queueManager.on(
1379
- 'jobCompleted',
1380
- (job, result) => {
1381
- if (job.jobId === jobId) {
1382
- unsubscribe();
1383
- unsubscribeError();
1384
- resolve(
1385
- result as IOperationResult<DocumentDriveDocument>
1386
- );
1371
+ try {
1372
+ const jobId = await this.queueManager.addJob({
1373
+ driveId: drive,
1374
+ actions,
1375
+ options
1376
+ });
1377
+ return new Promise<IOperationResult<DocumentDriveDocument>>(
1378
+ (resolve, reject) => {
1379
+ const unsubscribe = this.queueManager.on(
1380
+ 'jobCompleted',
1381
+ (job, result) => {
1382
+ if (job.jobId === jobId) {
1383
+ unsubscribe();
1384
+ unsubscribeError();
1385
+ resolve(
1386
+ result as IOperationResult<DocumentDriveDocument>
1387
+ );
1388
+ }
1387
1389
  }
1388
- }
1389
- );
1390
- const unsubscribeError = this.queueManager.on(
1391
- 'jobFailed',
1392
- (job, error) => {
1393
- if (job.jobId === jobId) {
1394
- unsubscribe();
1395
- unsubscribeError();
1396
- reject(error);
1390
+ );
1391
+ const unsubscribeError = this.queueManager.on(
1392
+ 'jobFailed',
1393
+ (job, error) => {
1394
+ if (job.jobId === jobId) {
1395
+ unsubscribe();
1396
+ unsubscribeError();
1397
+ reject(error);
1398
+ }
1397
1399
  }
1398
- }
1399
- );
1400
- }
1401
- );
1400
+ );
1401
+ }
1402
+ );
1403
+ } catch (error) {
1404
+ logger.error('Error adding drive job', error);
1405
+ throw error;
1406
+ }
1402
1407
  }
1403
1408
 
1404
1409
  async addOperations(
@@ -1657,38 +1662,42 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1657
1662
  if (result) {
1658
1663
  return result;
1659
1664
  }
1660
-
1661
- const jobId = await this.queueManager.addJob({
1662
- driveId: drive,
1663
- operations,
1664
- options
1665
- });
1666
- return new Promise<IOperationResult<DocumentDriveDocument>>(
1667
- (resolve, reject) => {
1668
- const unsubscribe = this.queueManager.on(
1669
- 'jobCompleted',
1670
- (job, result) => {
1671
- if (job.jobId === jobId) {
1672
- unsubscribe();
1673
- unsubscribeError();
1674
- resolve(
1675
- result as IOperationResult<DocumentDriveDocument>
1676
- );
1665
+ try {
1666
+ const jobId = await this.queueManager.addJob({
1667
+ driveId: drive,
1668
+ operations,
1669
+ options
1670
+ });
1671
+ return new Promise<IOperationResult<DocumentDriveDocument>>(
1672
+ (resolve, reject) => {
1673
+ const unsubscribe = this.queueManager.on(
1674
+ 'jobCompleted',
1675
+ (job, result) => {
1676
+ if (job.jobId === jobId) {
1677
+ unsubscribe();
1678
+ unsubscribeError();
1679
+ resolve(
1680
+ result as IOperationResult<DocumentDriveDocument>
1681
+ );
1682
+ }
1677
1683
  }
1678
- }
1679
- );
1680
- const unsubscribeError = this.queueManager.on(
1681
- 'jobFailed',
1682
- (job, error) => {
1683
- if (job.jobId === jobId) {
1684
- unsubscribe();
1685
- unsubscribeError();
1686
- reject(error);
1684
+ );
1685
+ const unsubscribeError = this.queueManager.on(
1686
+ 'jobFailed',
1687
+ (job, error) => {
1688
+ if (job.jobId === jobId) {
1689
+ unsubscribe();
1690
+ unsubscribeError();
1691
+ reject(error);
1692
+ }
1687
1693
  }
1688
- }
1689
- );
1690
- }
1691
- );
1694
+ );
1695
+ }
1696
+ );
1697
+ } catch (error) {
1698
+ logger.error('Error adding drive job', error);
1699
+ throw error;
1700
+ }
1692
1701
  }
1693
1702
 
1694
1703
  async addDriveOperations(
@@ -1,4 +1,4 @@
1
+ export * from './internal';
1
2
  export * from './pull-responder';
2
3
  export * from './switchboard-push';
3
- export * from './internal';
4
4
  export * from './types';
@@ -1,4 +1,5 @@
1
1
  import { Document, OperationScope } from 'document-model/document';
2
+ import { logger } from '../../../utils/logger';
2
3
  import {
3
4
  BaseDocumentDriveServer,
4
5
  Listener,
@@ -8,7 +9,6 @@ import {
8
9
  } from '../../types';
9
10
  import { buildRevisionsFilter } from '../../utils';
10
11
  import { ITransmitter } from './types';
11
- import { logger } from '../../../utils/logger';
12
12
 
13
13
  export interface IReceiver {
14
14
  transmit: (strands: InternalTransmitterUpdate[]) => Promise<void>;
@@ -56,10 +56,10 @@ export class InternalTransmitter implements ITransmitter {
56
56
  );
57
57
  document = await (strand.documentId
58
58
  ? this.drive.getDocument(
59
- strand.driveId,
60
- strand.documentId,
61
- { revisions }
62
- )
59
+ strand.driveId,
60
+ strand.documentId,
61
+ { revisions }
62
+ )
63
63
  : this.drive.getDrive(strand.driveId, { revisions }));
64
64
  retrievedDocuments.set(
65
65
  `${strand.driveId}:${strand.documentId}`,
@@ -32,4 +32,3 @@ export function filterOperationsByRevision(
32
32
  return acc;
33
33
  }, operations);
34
34
  }
35
-
@@ -7,11 +7,7 @@ import {
7
7
  OperationScope
8
8
  } from 'document-model/document';
9
9
  import { mergeOperations, type SynchronizationUnitQuery } from '..';
10
- import {
11
- DocumentDriveStorage,
12
- DocumentStorage,
13
- IDriveStorage,
14
- } from './types';
10
+ import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
15
11
 
16
12
  export class BrowserStorage implements IDriveStorage {
17
13
  private db: Promise<LocalForage>;
@@ -1,5 +1,10 @@
1
1
  import { DocumentDriveAction } from 'document-model-libs/document-drive';
2
- import { BaseAction, DocumentHeader, Operation, OperationScope } from 'document-model/document';
2
+ import {
3
+ BaseAction,
4
+ DocumentHeader,
5
+ Operation,
6
+ OperationScope
7
+ } from 'document-model/document';
3
8
  import type { Dirent } from 'fs';
4
9
  import {
5
10
  existsSync,
@@ -202,7 +207,13 @@ export class FilesystemStorage implements IDriveStorage {
202
207
  // get oldes drives first
203
208
  const drives = (await this.getDrives()).reverse();
204
209
  for (const drive of drives) {
205
- const { initialState: { state: { global: { slug: driveSlug } } } } = await this.getDrive(drive);
210
+ const {
211
+ initialState: {
212
+ state: {
213
+ global: { slug: driveSlug }
214
+ }
215
+ }
216
+ } = await this.getDrive(drive);
206
217
  if (driveSlug === slug) {
207
218
  return this.getDrive(drive);
208
219
  }
@@ -6,13 +6,9 @@ import {
6
6
  Operation,
7
7
  OperationScope
8
8
  } from 'document-model/document';
9
- import {
10
- DocumentDriveStorage,
11
- DocumentStorage,
12
- IDriveStorage,
13
- } from './types';
14
9
  import type { SynchronizationUnitQuery } from '../server/types';
15
10
  import { mergeOperations } from '../utils';
11
+ import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
16
12
 
17
13
  export class MemoryStorage implements IDriveStorage {
18
14
  private documents: Record<string, Record<string, DocumentStorage>>;
@@ -31,14 +31,14 @@ import {
31
31
 
32
32
  type Transaction =
33
33
  | Omit<
34
- PrismaClient<Prisma.PrismaClientOptions, never>,
35
- | '$connect'
36
- | '$disconnect'
37
- | '$on'
38
- | '$transaction'
39
- | '$use'
40
- | '$extends'
41
- >
34
+ PrismaClient<Prisma.PrismaClientOptions, never>,
35
+ | '$connect'
36
+ | '$disconnect'
37
+ | '$on'
38
+ | '$transaction'
39
+ | '$use'
40
+ | '$extends'
41
+ >
42
42
  | ExtendedPrismaClient;
43
43
 
44
44
  function storageToOperation(
@@ -504,7 +504,13 @@ export class PrismaStorage implements IDriveStorage {
504
504
  });
505
505
  } catch (e: any) {
506
506
  // Ignore Error: P2025: An operation failed because it depends on one or more records that were required but not found.
507
- if ((e.code && e.code === "P2025") || (e.message && e.message.includes("An operation failed because it depends on one or more records that were required but not found."))) {
507
+ if (
508
+ (e.code && e.code === 'P2025') ||
509
+ (e.message &&
510
+ e.message.includes(
511
+ 'An operation failed because it depends on one or more records that were required but not found.'
512
+ ))
513
+ ) {
508
514
  return;
509
515
  }
510
516
 
@@ -10,8 +10,8 @@ import {
10
10
  OperationScope
11
11
  } from 'document-model/document';
12
12
  import { DataTypes, Options, Sequelize } from 'sequelize';
13
- import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
14
13
  import type { SynchronizationUnitQuery } from '../server/types';
14
+ import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
15
15
 
16
16
  export class SequelizeStorage implements IDriveStorage {
17
17
  private db: Sequelize;
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  DocumentDriveAction,
3
- DocumentDriveDocument,
3
+ DocumentDriveDocument
4
4
  } from 'document-model-libs/document-drive';
5
5
  import type {
6
6
  Action,
@@ -8,7 +8,7 @@ import type {
8
8
  Document,
9
9
  DocumentHeader,
10
10
  DocumentOperations,
11
- Operation,
11
+ Operation
12
12
  } from 'document-model/document';
13
13
  import type { SynchronizationUnitQuery } from '../server/types';
14
14
 
@@ -20,7 +20,10 @@ export type DocumentStorage<D extends Document = Document> = Omit<
20
20
  export type DocumentDriveStorage = DocumentStorage<DocumentDriveDocument>;
21
21
 
22
22
  export interface IStorageDelegate {
23
- getCachedOperations(drive: string, id: string): Promise<DocumentOperations<Action> | undefined>;
23
+ getCachedOperations(
24
+ drive: string,
25
+ id: string
26
+ ): Promise<DocumentOperations<Action> | undefined>;
24
27
  }
25
28
 
26
29
  export interface IStorage {
@@ -36,7 +39,7 @@ export interface IStorage {
36
39
  drive: string,
37
40
  id: string,
38
41
  operations: Operation[],
39
- header: DocumentHeader,
42
+ header: DocumentHeader
40
43
  ): Promise<void>;
41
44
  addDocumentOperationsWithTransaction?(
42
45
  drive: string,
@@ -47,16 +50,24 @@ export interface IStorage {
47
50
  }>
48
51
  ): Promise<void>;
49
52
  deleteDocument(drive: string, id: string): Promise<void>;
50
- getOperationResultingState?(drive: string, id: string, index: number, scope: string, branch: string): Promise<unknown>;
53
+ getOperationResultingState?(
54
+ drive: string,
55
+ id: string,
56
+ index: number,
57
+ scope: string,
58
+ branch: string
59
+ ): Promise<unknown>;
51
60
  setStorageDelegate?(delegate: IStorageDelegate): void;
52
- getSynchronizationUnitsRevision(units: SynchronizationUnitQuery[]): Promise<{
53
- driveId: string;
54
- documentId: string;
55
- scope: string;
56
- branch: string;
57
- lastUpdated: string;
58
- revision: number;
59
- }[]>
61
+ getSynchronizationUnitsRevision(units: SynchronizationUnitQuery[]): Promise<
62
+ {
63
+ driveId: string;
64
+ documentId: string;
65
+ scope: string;
66
+ branch: string;
67
+ lastUpdated: string;
68
+ revision: number;
69
+ }[]
70
+ >;
60
71
  }
61
72
  export interface IDriveStorage extends IStorage {
62
73
  getDrives(): Promise<string[]>;
@@ -77,5 +88,10 @@ export interface IDriveStorage extends IStorage {
77
88
  header: DocumentHeader;
78
89
  }>
79
90
  ): Promise<void>;
80
- getDriveOperationResultingState?(drive: string, index: number, scope: string, branch: string): Promise<unknown>;
81
- }
91
+ getDriveOperationResultingState?(
92
+ drive: string,
93
+ index: number,
94
+ scope: string,
95
+ branch: string
96
+ ): Promise<unknown>;
97
+ }
@@ -1,6 +1,7 @@
1
1
  import { utils } from 'document-model/document';
2
2
 
3
- export const { attachBranch,
3
+ export const {
4
+ attachBranch,
4
5
  garbageCollect,
5
6
  groupOperationsByScope,
6
7
  merge,
@@ -1,8 +1,6 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
1
  import {
3
2
  DocumentDriveDocument,
4
- documentModel as DocumentDriveModel,
5
- z
3
+ documentModel as DocumentDriveModel
6
4
  } from 'document-model-libs/document-drive';
7
5
  import {
8
6
  Action,
@@ -12,49 +10,51 @@ import {
12
10
  Operation,
13
11
  OperationScope
14
12
  } from 'document-model/document';
13
+ import { v4 as uuidv4 } from 'uuid';
15
14
  import { OperationError } from '../server/error';
16
15
  import { DocumentDriveStorage, DocumentStorage } from '../storage';
17
16
 
18
17
  export function isDocumentDriveStorage(
19
18
  document: DocumentStorage
20
19
  ): document is DocumentDriveStorage {
21
- return (
22
- document.documentType === DocumentDriveModel.id
23
- );
20
+ return document.documentType === DocumentDriveModel.id;
24
21
  }
25
22
 
26
23
  export function isDocumentDrive(
27
24
  document: Document
28
25
  ): document is DocumentDriveDocument {
29
- return (
30
- document.documentType === DocumentDriveModel.id
31
- );
26
+ return document.documentType === DocumentDriveModel.id;
32
27
  }
33
28
 
34
29
  export function mergeOperations<A extends Action = Action>(
35
30
  currentOperations: DocumentOperations<A>,
36
31
  newOperations: Operation<A | BaseAction>[]
37
32
  ): DocumentOperations<A> {
38
- const minIndexByScope = Object.keys(currentOperations).reduce<Partial<Record<OperationScope, number>>>((acc, curr) => {
33
+ const minIndexByScope = Object.keys(currentOperations).reduce<
34
+ Partial<Record<OperationScope, number>>
35
+ >((acc, curr) => {
39
36
  const scope = curr as OperationScope;
40
- acc[scope] = currentOperations[scope].at(-1)?.index ?? 0
37
+ acc[scope] = currentOperations[scope].at(-1)?.index ?? 0;
41
38
  return acc;
42
39
  }, {});
43
40
 
44
- const conflictOp = newOperations.find(op => op.index < (minIndexByScope[op.scope] ?? 0));
41
+ const conflictOp = newOperations.find(
42
+ op => op.index < (minIndexByScope[op.scope] ?? 0)
43
+ );
45
44
  if (conflictOp) {
46
45
  throw new OperationError(
47
- "ERROR",
46
+ 'ERROR',
48
47
  conflictOp,
49
48
  `Tried to add operation with index ${conflictOp.index} and document is at index ${minIndexByScope[conflictOp.scope]}`
50
49
  );
51
50
  }
52
51
 
53
- return newOperations.sort((a, b) => a.index - b.index
54
- ).reduce<DocumentOperations<A>>((acc, curr) => {
55
- const existingOperations = acc[curr.scope] || [];
56
- return { ...acc, [curr.scope]: [...existingOperations, curr] };
57
- }, currentOperations);
52
+ return newOperations
53
+ .sort((a, b) => a.index - b.index)
54
+ .reduce<DocumentOperations<A>>((acc, curr) => {
55
+ const existingOperations = acc[curr.scope] || [];
56
+ return { ...acc, [curr.scope]: [...existingOperations, curr] };
57
+ }, currentOperations);
58
58
  }
59
59
 
60
60
  export function generateUUID(): string {
@@ -86,4 +86,3 @@ export function isNoopUpdate(
86
86
  export function isBefore(dateA: Date | string, dateB: Date | string) {
87
87
  return new Date(dateA) < new Date(dateB);
88
88
  }
89
-
@@ -1,5 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- export type ILogger = Pick<Console, 'log' | 'info' | 'warn' | 'error' | 'debug' | 'trace'>;
2
+ export type ILogger = Pick<
3
+ Console,
4
+ 'log' | 'info' | 'warn' | 'error' | 'debug' | 'trace'
5
+ >;
3
6
  class Logger implements ILogger {
4
7
  #logger: ILogger = console;
5
8