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.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/package.json +74 -88
  3. package/src/cache/index.ts +2 -2
  4. package/src/cache/memory.ts +22 -13
  5. package/src/cache/redis.ts +43 -16
  6. package/src/cache/types.ts +4 -4
  7. package/src/index.ts +6 -3
  8. package/src/queue/base.ts +276 -214
  9. package/src/queue/index.ts +2 -2
  10. package/src/queue/redis.ts +138 -127
  11. package/src/queue/types.ts +44 -38
  12. package/src/read-mode/errors.ts +19 -0
  13. package/src/read-mode/index.ts +125 -0
  14. package/src/read-mode/service.ts +207 -0
  15. package/src/read-mode/types.ts +108 -0
  16. package/src/server/error.ts +61 -26
  17. package/src/server/index.ts +2160 -1785
  18. package/src/server/listener/index.ts +2 -2
  19. package/src/server/listener/manager.ts +475 -437
  20. package/src/server/listener/transmitter/index.ts +4 -5
  21. package/src/server/listener/transmitter/internal.ts +77 -79
  22. package/src/server/listener/transmitter/pull-responder.ts +363 -329
  23. package/src/server/listener/transmitter/switchboard-push.ts +72 -55
  24. package/src/server/listener/transmitter/types.ts +19 -25
  25. package/src/server/types.ts +536 -349
  26. package/src/server/utils.ts +26 -27
  27. package/src/storage/base.ts +81 -0
  28. package/src/storage/browser.ts +233 -216
  29. package/src/storage/filesystem.ts +257 -256
  30. package/src/storage/index.ts +2 -1
  31. package/src/storage/memory.ts +206 -214
  32. package/src/storage/prisma.ts +575 -568
  33. package/src/storage/sequelize.ts +460 -471
  34. package/src/storage/types.ts +83 -67
  35. package/src/utils/default-drives-manager.ts +341 -0
  36. package/src/utils/document-helpers.ts +19 -18
  37. package/src/utils/graphql.ts +288 -34
  38. package/src/utils/index.ts +61 -59
  39. package/src/utils/logger.ts +39 -37
  40. package/src/utils/migrations.ts +58 -0
  41. package/src/utils/run-asap.ts +156 -0
  42. package/CHANGELOG.md +0 -818
  43. package/src/server/listener/transmitter/subscription.ts +0 -364
@@ -1,296 +1,297 @@
1
- import { DocumentDriveAction } from 'document-model-libs/document-drive';
2
- import { BaseAction, DocumentHeader, Operation, OperationScope } from 'document-model/document';
3
- import type { Dirent } from 'fs';
1
+ import { DocumentDriveAction } from "document-model-libs/document-drive";
4
2
  import {
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- readdirSync,
9
- writeFileSync
10
- } from 'fs';
11
- import fs from 'fs/promises';
12
- import stringify from 'json-stringify-deterministic';
13
- import path from 'path';
14
- import sanitize from 'sanitize-filename';
15
- import type { SynchronizationUnitQuery } from '../server/types';
16
- import { mergeOperations } from '../utils';
17
- import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
3
+ BaseAction,
4
+ DocumentHeader,
5
+ Operation,
6
+ OperationScope,
7
+ } from "document-model/document";
8
+ import type { Dirent } from "fs";
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ readdirSync,
14
+ writeFileSync,
15
+ } from "fs";
16
+ import fs from "fs/promises";
17
+ import stringify from "json-stringify-deterministic";
18
+ import path from "path";
19
+ import sanitize from "sanitize-filename";
20
+ import { DriveNotFoundError } from "../server/error";
21
+ import type { SynchronizationUnitQuery } from "../server/types";
22
+ import { mergeOperations } from "../utils";
23
+ import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from "./types";
18
24
 
19
25
  type FSError = {
20
- errno: number;
21
- code: string;
22
- syscall: string;
23
- path: string;
26
+ errno: number;
27
+ code: string;
28
+ syscall: string;
29
+ path: string;
24
30
  };
25
31
 
26
32
  function ensureDir(dir: string) {
27
- if (!existsSync(dir)) {
28
- mkdirSync(dir, { recursive: true });
29
- }
33
+ if (!existsSync(dir)) {
34
+ mkdirSync(dir, { recursive: true });
35
+ }
30
36
  }
31
37
 
32
38
  export class FilesystemStorage implements IDriveStorage {
33
- private basePath: string;
34
- private drivesPath: string;
35
- private static DRIVES_DIR = 'drives';
39
+ private basePath: string;
40
+ private drivesPath: string;
41
+ private static DRIVES_DIR = "drives";
36
42
 
37
- constructor(basePath: string) {
38
- this.basePath = basePath;
39
- ensureDir(this.basePath);
40
- this.drivesPath = path.join(
41
- this.basePath,
42
- FilesystemStorage.DRIVES_DIR
43
- );
44
- ensureDir(this.drivesPath);
45
- }
43
+ constructor(basePath: string) {
44
+ this.basePath = basePath;
45
+ ensureDir(this.basePath);
46
+ this.drivesPath = path.join(this.basePath, FilesystemStorage.DRIVES_DIR);
47
+ ensureDir(this.drivesPath);
48
+ }
46
49
 
47
- private _buildDocumentPath(...args: string[]) {
48
- return `${path.join(
49
- this.basePath,
50
- ...args.map(arg => sanitize(arg))
51
- )}.json`;
52
- }
50
+ private _buildDocumentPath(...args: string[]) {
51
+ return `${path.join(
52
+ this.basePath,
53
+ ...args.map((arg) => sanitize(arg)),
54
+ )}.json`;
55
+ }
53
56
 
54
- async getDocuments(drive: string) {
55
- let files: Dirent[] = [];
56
- try {
57
- files = readdirSync(path.join(this.basePath, drive), {
58
- withFileTypes: true
59
- });
60
- } catch (error) {
61
- // if folder is not found then drive has no documents
62
- if ((error as FSError).code !== 'ENOENT') {
63
- throw error;
64
- }
65
- }
66
- const documents: string[] = [];
67
- for (const file of files.filter(file => file.isFile())) {
68
- try {
69
- const documentId = path.parse(file.name).name;
70
-
71
- // checks if file is document
72
- await this.getDocument(drive, documentId);
73
- documents.push(documentId);
74
- } catch {
75
- /* Ignore invalid document*/
76
- }
77
- }
78
- return documents;
57
+ async getDocuments(drive: string) {
58
+ let files: Dirent[] = [];
59
+ try {
60
+ files = readdirSync(path.join(this.basePath, drive), {
61
+ withFileTypes: true,
62
+ });
63
+ } catch (error) {
64
+ // if folder is not found then drive has no documents
65
+ if ((error as FSError).code !== "ENOENT") {
66
+ throw error;
67
+ }
79
68
  }
69
+ const documents: string[] = [];
70
+ for (const file of files.filter((file) => file.isFile())) {
71
+ try {
72
+ const documentId = path.parse(file.name).name;
80
73
 
81
- checkDocumentExists(drive: string, id: string): Promise<boolean> {
82
- const documentExists = existsSync(this._buildDocumentPath(drive, id));
83
- return Promise.resolve(documentExists);
74
+ // checks if file is document
75
+ await this.getDocument(drive, documentId);
76
+ documents.push(documentId);
77
+ } catch {
78
+ /* Ignore invalid document*/
79
+ }
84
80
  }
81
+ return documents;
82
+ }
85
83
 
86
- async getDocument(drive: string, id: string) {
87
- try {
88
- const content = readFileSync(this._buildDocumentPath(drive, id), {
89
- encoding: 'utf-8'
90
- });
91
- return JSON.parse(content) as Promise<DocumentStorage>;
92
- } catch (error) {
93
- throw new Error(`Document with id ${id} not found`);
94
- }
95
- }
84
+ checkDocumentExists(drive: string, id: string): Promise<boolean> {
85
+ const documentExists = existsSync(this._buildDocumentPath(drive, id));
86
+ return Promise.resolve(documentExists);
87
+ }
96
88
 
97
- async createDocument(drive: string, id: string, document: DocumentStorage) {
98
- const documentPath = this._buildDocumentPath(drive, id);
99
- ensureDir(path.dirname(documentPath));
100
- writeFileSync(documentPath, stringify(document), {
101
- encoding: 'utf-8'
102
- });
103
- return Promise.resolve();
89
+ async getDocument(drive: string, id: string) {
90
+ try {
91
+ const content = readFileSync(this._buildDocumentPath(drive, id), {
92
+ encoding: "utf-8",
93
+ });
94
+ return JSON.parse(content) as Promise<DocumentStorage>;
95
+ } catch (error) {
96
+ throw new Error(`Document with id ${id} not found`);
104
97
  }
98
+ }
105
99
 
106
- async clearStorage() {
107
- const drivesPath = path.join(
108
- this.basePath,
109
- FilesystemStorage.DRIVES_DIR
110
- );
100
+ async createDocument(drive: string, id: string, document: DocumentStorage) {
101
+ const documentPath = this._buildDocumentPath(drive, id);
102
+ ensureDir(path.dirname(documentPath));
103
+ writeFileSync(documentPath, stringify(document), {
104
+ encoding: "utf-8",
105
+ });
106
+ return Promise.resolve();
107
+ }
111
108
 
112
- // delete content of drives directory
113
- const drives = (
114
- await fs.readdir(drivesPath, {
115
- withFileTypes: true,
116
- recursive: true
117
- })
118
- ).filter(dirent => !!dirent.name);
109
+ async clearStorage() {
110
+ const drivesPath = path.join(this.basePath, FilesystemStorage.DRIVES_DIR);
119
111
 
120
- await Promise.all(
121
- drives.map(async dirent => {
122
- await fs.rm(path.join(drivesPath, dirent.name), {
123
- recursive: true
124
- });
125
- })
126
- );
112
+ // delete content of drives directory
113
+ const drives = (
114
+ await fs.readdir(drivesPath, {
115
+ withFileTypes: true,
116
+ recursive: true,
117
+ })
118
+ ).filter((dirent) => !!dirent.name);
127
119
 
128
- // delete files in basePath
129
- const files = (
130
- await fs.readdir(this.basePath, { withFileTypes: true })
131
- ).filter(
132
- file => file.name !== FilesystemStorage.DRIVES_DIR && !!file.name
133
- );
134
-
135
- await Promise.all(
136
- files.map(async dirent => {
137
- await fs.rm(path.join(this.basePath, dirent.name), {
138
- recursive: true
139
- });
140
- })
141
- );
142
- }
120
+ await Promise.all(
121
+ drives.map(async (dirent) => {
122
+ await fs.rm(path.join(drivesPath, dirent.name), {
123
+ recursive: true,
124
+ });
125
+ }),
126
+ );
143
127
 
144
- async deleteDocument(drive: string, id: string) {
145
- return fs.rm(this._buildDocumentPath(drive, id));
146
- }
128
+ // delete files in basePath
129
+ const files = (
130
+ await fs.readdir(this.basePath, { withFileTypes: true })
131
+ ).filter(
132
+ (file) => file.name !== FilesystemStorage.DRIVES_DIR && !!file.name,
133
+ );
147
134
 
148
- async addDocumentOperations(
149
- drive: string,
150
- id: string,
151
- operations: Operation[],
152
- header: DocumentHeader
153
- ) {
154
- const document = await this.getDocument(drive, id);
155
- if (!document) {
156
- throw new Error(`Document with id ${id} not found`);
157
- }
135
+ await Promise.all(
136
+ files.map(async (dirent) => {
137
+ await fs.rm(path.join(this.basePath, dirent.name), {
138
+ recursive: true,
139
+ });
140
+ }),
141
+ );
142
+ }
158
143
 
159
- const mergedOperations = mergeOperations(
160
- document.operations,
161
- operations
162
- );
144
+ async deleteDocument(drive: string, id: string) {
145
+ return fs.rm(this._buildDocumentPath(drive, id));
146
+ }
163
147
 
164
- await this.createDocument(drive, id, {
165
- ...document,
166
- ...header,
167
- operations: mergedOperations
168
- });
148
+ async addDocumentOperations(
149
+ drive: string,
150
+ id: string,
151
+ operations: Operation[],
152
+ header: DocumentHeader,
153
+ ) {
154
+ const document = await this.getDocument(drive, id);
155
+ if (!document) {
156
+ throw new Error(`Document with id ${id} not found`);
169
157
  }
170
158
 
171
- async getDrives() {
172
- const files = readdirSync(this.drivesPath, {
173
- withFileTypes: true
174
- });
175
- const drives: string[] = [];
176
- for (const file of files.filter(file => file.isFile())) {
177
- try {
178
- const driveId = path.parse(file.name).name;
159
+ const mergedOperations = mergeOperations(document.operations, operations);
179
160
 
180
- // checks if file is drive
181
- await this.getDrive(driveId);
182
- drives.push(driveId);
183
- } catch {
184
- /* Ignore invalid drive document found on drives dir */
185
- }
186
- }
187
- return drives;
188
- }
161
+ await this.createDocument(drive, id, {
162
+ ...document,
163
+ ...header,
164
+ operations: mergedOperations,
165
+ });
166
+ }
189
167
 
190
- async getDrive(id: string) {
191
- try {
192
- return (await this.getDocument(
193
- FilesystemStorage.DRIVES_DIR,
194
- id
195
- )) as DocumentDriveStorage;
196
- } catch {
197
- throw new Error(`Drive with id ${id} not found`);
198
- }
199
- }
168
+ async getDrives() {
169
+ const files = readdirSync(this.drivesPath, {
170
+ withFileTypes: true,
171
+ });
172
+ const drives: string[] = [];
173
+ for (const file of files.filter((file) => file.isFile())) {
174
+ try {
175
+ const driveId = path.parse(file.name).name;
200
176
 
201
- async getDriveBySlug(slug: string) {
202
- // get oldes drives first
203
- const drives = (await this.getDrives()).reverse();
204
- for (const drive of drives) {
205
- const { initialState: { state: { global: { slug: driveSlug } } } } = await this.getDrive(drive);
206
- if (driveSlug === slug) {
207
- return this.getDrive(drive);
208
- }
209
- }
210
- throw new Error(`Drive with slug ${slug} not found`);
177
+ // checks if file is drive
178
+ await this.getDrive(driveId);
179
+ drives.push(driveId);
180
+ } catch {
181
+ /* Ignore invalid drive document found on drives dir */
182
+ }
211
183
  }
184
+ return drives;
185
+ }
212
186
 
213
- createDrive(id: string, drive: DocumentDriveStorage) {
214
- return this.createDocument(FilesystemStorage.DRIVES_DIR, id, drive);
187
+ async getDrive(id: string) {
188
+ try {
189
+ return (await this.getDocument(
190
+ FilesystemStorage.DRIVES_DIR,
191
+ id,
192
+ )) as DocumentDriveStorage;
193
+ } catch {
194
+ throw new DriveNotFoundError(id);
215
195
  }
196
+ }
216
197
 
217
- async deleteDrive(id: string) {
218
- const documents = await this.getDocuments(id);
219
- await this.deleteDocument(FilesystemStorage.DRIVES_DIR, id);
220
- await Promise.all(
221
- documents.map(document => this.deleteDocument(id, document))
222
- );
198
+ async getDriveBySlug(slug: string) {
199
+ // get oldes drives first
200
+ const drives = (await this.getDrives()).reverse();
201
+ for (const drive of drives) {
202
+ const {
203
+ initialState: {
204
+ state: {
205
+ global: { slug: driveSlug },
206
+ },
207
+ },
208
+ } = await this.getDrive(drive);
209
+ if (driveSlug === slug) {
210
+ return this.getDrive(drive);
211
+ }
223
212
  }
213
+ throw new Error(`Drive with slug ${slug} not found`);
214
+ }
224
215
 
225
- async addDriveOperations(
226
- id: string,
227
- operations: Operation<DocumentDriveAction | BaseAction>[],
228
- header: DocumentHeader
229
- ): Promise<void> {
230
- const drive = await this.getDrive(id);
231
- const mergedOperations = mergeOperations(drive.operations, operations);
216
+ createDrive(id: string, drive: DocumentDriveStorage) {
217
+ return this.createDocument(FilesystemStorage.DRIVES_DIR, id, drive);
218
+ }
232
219
 
233
- await this.createDrive(id, {
234
- ...drive,
235
- ...header,
236
- operations: mergedOperations
237
- });
238
- }
220
+ async deleteDrive(id: string) {
221
+ const documents = await this.getDocuments(id);
222
+ await this.deleteDocument(FilesystemStorage.DRIVES_DIR, id);
223
+ await Promise.all(
224
+ documents.map((document) => this.deleteDocument(id, document)),
225
+ );
226
+ }
239
227
 
240
- async getSynchronizationUnitsRevision(
241
- units: SynchronizationUnitQuery[]
242
- ): Promise<
243
- {
244
- driveId: string;
245
- documentId: string;
246
- scope: string;
247
- branch: string;
248
- lastUpdated: string;
249
- revision: number;
250
- }[]
251
- > {
252
- const results = await Promise.allSettled(
253
- units.map(async unit => {
254
- try {
255
- const document = await (unit.documentId
256
- ? this.getDocument(unit.driveId, unit.documentId)
257
- : this.getDrive(unit.driveId));
258
- if (!document) {
259
- return undefined;
260
- }
261
- const operation =
262
- document.operations[unit.scope as OperationScope]?.at(
263
- -1
264
- );
265
- if (operation) {
266
- return {
267
- driveId: unit.driveId,
268
- documentId: unit.documentId,
269
- scope: unit.scope,
270
- branch: unit.branch,
271
- lastUpdated: operation.timestamp,
272
- revision: operation.index
273
- };
274
- }
275
- } catch {
276
- return undefined;
277
- }
278
- })
279
- );
280
- return results.reduce<
281
- {
282
- driveId: string;
283
- documentId: string;
284
- scope: string;
285
- branch: string;
286
- lastUpdated: string;
287
- revision: number;
288
- }[]
289
- >((acc, curr) => {
290
- if (curr.status === 'fulfilled' && curr.value !== undefined) {
291
- acc.push(curr.value);
292
- }
293
- return acc;
294
- }, []);
295
- }
228
+ async addDriveOperations(
229
+ id: string,
230
+ operations: Operation<DocumentDriveAction | BaseAction>[],
231
+ header: DocumentHeader,
232
+ ): Promise<void> {
233
+ const drive = await this.getDrive(id);
234
+ const mergedOperations = mergeOperations(drive.operations, operations);
235
+
236
+ await this.createDrive(id, {
237
+ ...drive,
238
+ ...header,
239
+ operations: mergedOperations,
240
+ });
241
+ }
242
+
243
+ async getSynchronizationUnitsRevision(
244
+ units: SynchronizationUnitQuery[],
245
+ ): Promise<
246
+ {
247
+ driveId: string;
248
+ documentId: string;
249
+ scope: string;
250
+ branch: string;
251
+ lastUpdated: string;
252
+ revision: number;
253
+ }[]
254
+ > {
255
+ const results = await Promise.allSettled(
256
+ units.map(async (unit) => {
257
+ try {
258
+ const document = await (unit.documentId
259
+ ? this.getDocument(unit.driveId, unit.documentId)
260
+ : this.getDrive(unit.driveId));
261
+ if (!document) {
262
+ return undefined;
263
+ }
264
+ const operation =
265
+ document.operations[unit.scope as OperationScope].at(-1);
266
+ if (operation) {
267
+ return {
268
+ driveId: unit.driveId,
269
+ documentId: unit.documentId,
270
+ scope: unit.scope,
271
+ branch: unit.branch,
272
+ lastUpdated: operation.timestamp,
273
+ revision: operation.index,
274
+ };
275
+ }
276
+ } catch {
277
+ return undefined;
278
+ }
279
+ }),
280
+ );
281
+ return results.reduce<
282
+ {
283
+ driveId: string;
284
+ documentId: string;
285
+ scope: string;
286
+ branch: string;
287
+ lastUpdated: string;
288
+ revision: number;
289
+ }[]
290
+ >((acc, curr) => {
291
+ if (curr.status === "fulfilled" && curr.value !== undefined) {
292
+ acc.push(curr.value);
293
+ }
294
+ return acc;
295
+ }, []);
296
+ }
296
297
  }
@@ -1 +1,2 @@
1
- export type * from './types';
1
+ export * from "./base";
2
+ export type * from "./types";