document-drive 1.0.0-alpha.54 → 1.0.0-alpha.55

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.54",
3
+ "version": "1.0.0-alpha.55",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -28,7 +28,8 @@ import { MemoryStorage } from '../storage/memory';
28
28
  import type {
29
29
  DocumentDriveStorage,
30
30
  DocumentStorage,
31
- IDriveStorage
31
+ IDriveStorage,
32
+ StrictDocumentStorage
32
33
  } from '../storage/types';
33
34
  import { generateUUID, isBefore, isDocumentDrive } from '../utils';
34
35
  import {
@@ -494,7 +495,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
494
495
  logger.error('Error getting drive from cache', e);
495
496
  }
496
497
  const driveStorage = await this.storage.getDrive(drive);
497
- const document = this._replayDocument(driveStorage, options);
498
+ const document = this._buildDocument(driveStorage, options);
498
499
  if (!isDocumentDrive(document)) {
499
500
  throw new Error(
500
501
  `Document with id ${drive} is not a Document Drive`
@@ -518,7 +519,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
518
519
  }
519
520
 
520
521
  const driveStorage = await this.storage.getDriveBySlug(slug);
521
- const document = this._replayDocument(driveStorage, options);
522
+ const document = this._buildDocument(driveStorage, options);
522
523
  if (!isDocumentDrive(document)) {
523
524
  throw new Error(
524
525
  `Document with slug ${slug} is not a Document Drive`
@@ -540,20 +541,10 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
540
541
  } catch (e) {
541
542
  logger.error('Error getting document from cache', e);
542
543
  }
543
- const { initialState, operations, ...header } =
544
+ const documentStorage =
544
545
  await this.storage.getDocument(drive, id);
546
+ const document = this._buildDocument(documentStorage, options)
545
547
 
546
- const documentModel = this._getDocumentModel(header.documentType);
547
-
548
- const document = baseUtils.replayDocument(
549
- initialState,
550
- filterOperationsByRevision(operations, options?.revisions),
551
- documentModel.reducer,
552
- undefined,
553
- header,
554
- undefined,
555
- { checkHashes: false }
556
- );
557
548
  this.cache.setDocument(drive, id, document).catch(logger.error);
558
549
  return document;
559
550
  }
@@ -566,11 +557,41 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
566
557
  driveId: string,
567
558
  input: CreateDocumentInput
568
559
  ) {
569
- const documentModel = this._getDocumentModel(input.documentType);
570
- // TODO validate input.document is of documentType
571
- const document = input.document ?? documentModel.utils.createDocument();
560
+ // if a document was provided then checks if it's valid
561
+ if (input.document) {
562
+ if (input.documentType !== input.document.documentType) {
563
+ throw new Error(`Provided document is not ${input.documentType}`);
564
+ }
565
+ this._buildDocument(input.document);
566
+ }
572
567
 
573
- await this.storage.createDocument(driveId, input.id, document);
568
+ // if no document was provided then create a new one
569
+ const document = input.document ??
570
+ this._getDocumentModel(input.documentType).utils.createDocument();
571
+
572
+ // stores document information
573
+ const documentStorage: DocumentStorage = {
574
+ name: document.name,
575
+ revision: document.revision,
576
+ documentType: document.documentType,
577
+ created: document.created,
578
+ lastModified: document.lastModified,
579
+ operations: { global: [], local: [] },
580
+ initialState: document.initialState,
581
+ clipboard: [],
582
+ };
583
+ await this.storage.createDocument(driveId, input.id, documentStorage);
584
+
585
+ // if the document contains operations then
586
+ // stores the operations in the storage
587
+ const operations = Object.values(document.operations).flat();
588
+ if (operations.length) {
589
+ if (isDocumentDrive(document)) {
590
+ await this.storage.addDriveOperations(driveId, operations as Operation<DocumentDriveAction>[], document);
591
+ } else {
592
+ await this.storage.addDocumentOperations(driveId, input.id, operations, document)
593
+ }
594
+ }
574
595
 
575
596
  return document;
576
597
  }
@@ -603,6 +624,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
603
624
  const storageDocumentOperations =
604
625
  storageDocument.operations[scope as OperationScope];
605
626
 
627
+ // TODO two equal operations done by two clients will be considered the same, ie: { type: "INCREMENT" }
606
628
  const branch = removeExistingOperations(
607
629
  operationsByScope[scope as OperationScope] || [],
608
630
  storageDocumentOperations
@@ -698,20 +720,21 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
698
720
  }
699
721
 
700
722
  private _buildDocument<T extends Document>(
701
- documentStorage: DocumentStorage<T>
702
- ): T {
703
- const documentModel = this._getDocumentModel(
704
- documentStorage.documentType
705
- );
706
- return baseUtils.replayDocument(
723
+ documentStorage: DocumentStorage<T>, options?: GetDocumentOptions) {
724
+ const documentModel = this._getDocumentModel(documentStorage.documentType);
725
+ const document = baseUtils.replayDocument(
707
726
  documentStorage.initialState,
708
- documentStorage.operations,
727
+ filterOperationsByRevision(
728
+ documentStorage.operations,
729
+ options?.revisions
730
+ ),
709
731
  documentModel.reducer,
710
732
  undefined,
711
733
  documentStorage,
712
734
  undefined,
713
- { checkHashes: false }
714
- ) as T;
735
+ { checkHashes: options?.checkHashes ?? true }
736
+ );
737
+ return document as T;
715
738
  }
716
739
 
717
740
  private async _performOperation<T extends Document>(
@@ -1280,22 +1303,4 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
1280
1303
  logger.debug(`Emitting event ${event}`, args);
1281
1304
  return this.emitter.emit(event, ...args);
1282
1305
  }
1283
-
1284
- private _replayDocument(documentStorage: DocumentStorage, options?: GetDocumentOptions) {
1285
- const documentModel = this._getDocumentModel(documentStorage.documentType);
1286
- const document = baseUtils.replayDocument(
1287
- documentStorage.initialState,
1288
- filterOperationsByRevision(
1289
- documentStorage.operations,
1290
- options?.revisions
1291
- ),
1292
- documentModel.reducer,
1293
- undefined,
1294
- documentStorage,
1295
- undefined,
1296
- { checkHashes: false }
1297
- );
1298
-
1299
- return document;
1300
- }
1301
1306
  }
@@ -142,6 +142,7 @@ export type RevisionsFilter = PartialRecord<OperationScope, number>;
142
142
 
143
143
  export type GetDocumentOptions = {
144
144
  revisions?: RevisionsFilter;
145
+ checkHashes?: boolean;
145
146
  };
146
147
 
147
148
  export abstract class BaseDocumentDriveServer {
@@ -90,8 +90,8 @@ export class FilesystemStorage implements IDriveStorage {
90
90
 
91
91
  async createDocument(drive: string, id: string, document: DocumentStorage) {
92
92
  const documentPath = this._buildDocumentPath(drive, id);
93
- await ensureDir(path.dirname(documentPath));
94
- await writeFileSync(documentPath, stringify(document), {
93
+ ensureDir(path.dirname(documentPath));
94
+ writeFileSync(documentPath, stringify(document), {
95
95
  encoding: 'utf-8'
96
96
  });
97
97
  }
@@ -7,7 +7,6 @@ import {
7
7
  DocumentDriveState
8
8
  } from 'document-model-libs/document-drive';
9
9
  import type {
10
- ActionContext,
11
10
  BaseAction,
12
11
  DocumentHeader,
13
12
  ExtendedState,
@@ -26,17 +25,20 @@ type Transaction = Omit<
26
25
  function storageToOperation(
27
26
  op: Prisma.$OperationPayload['scalars']
28
27
  ): Operation {
29
- return {
28
+ const operation: Operation = {
30
29
  skip: op.skip,
31
30
  hash: op.hash,
32
31
  index: op.index,
33
32
  timestamp: new Date(op.timestamp).toISOString(),
34
- input: op.input,
33
+ input: JSON.parse(op.input),
35
34
  type: op.type,
36
35
  scope: op.scope as OperationScope,
37
- context: op.context ? op.context as ActionContext : undefined,
38
36
  // attachments: fileRegistry
39
37
  };
38
+ if (op.context) {
39
+ operation.context = op.context as Prisma.JsonObject;
40
+ }
41
+ return operation;
40
42
  }
41
43
 
42
44
  export type PrismaStorageOptions = {
@@ -78,7 +80,7 @@ export class PrismaStorage implements IDriveStorage {
78
80
  async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
79
81
  // drive for all drive documents
80
82
  await this.createDocument('drives', id, drive as DocumentStorage);
81
- const count = await this.db.drive.upsert({
83
+ await this.db.drive.upsert({
82
84
  where: {
83
85
  slug: drive.initialState.state.global.slug ?? id
84
86
  },
@@ -159,7 +161,7 @@ export class PrismaStorage implements IDriveStorage {
159
161
  documentId: id,
160
162
  hash: op.hash,
161
163
  index: op.index,
162
- input: op.input as Prisma.InputJsonObject,
164
+ input: JSON.stringify(op.input),
163
165
  timestamp: op.timestamp,
164
166
  type: op.type,
165
167
  scope: op.scope,
@@ -186,7 +188,7 @@ export class PrismaStorage implements IDriveStorage {
186
188
  documentId: id,
187
189
  hash: op.hash,
188
190
  index: op.index,
189
- input: op.input as Prisma.InputJsonObject,
191
+ input: JSON.stringify(op.input),
190
192
  timestamp: op.timestamp,
191
193
  type: op.type,
192
194
  scope: op.scope,
@@ -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
  BaseAction,
@@ -8,12 +8,12 @@ import type {
8
8
  DocumentHeader,
9
9
  Operation
10
10
  } from 'document-model/document';
11
- import { GetDocumentOptions } from '../server';
12
11
 
13
12
  export type DocumentStorage<D extends Document = Document> = Omit<
14
13
  D,
15
14
  'state' | 'attachments'
16
15
  >;
16
+
17
17
  export type DocumentDriveStorage = DocumentStorage<DocumentDriveDocument>;
18
18
 
19
19
  export interface IStorage {
@@ -11,6 +11,15 @@ import {
11
11
  Operation
12
12
  } from 'document-model/document';
13
13
  import { ConflictOperationError } from '../server/error';
14
+ import { DocumentDriveStorage, DocumentStorage } from '../storage';
15
+
16
+ export function isDocumentDriveStorage(
17
+ document: DocumentStorage
18
+ ): document is DocumentDriveStorage {
19
+ return (
20
+ document.documentType === DocumentDriveModel.id
21
+ );
22
+ }
14
23
 
15
24
  export function isDocumentDrive(
16
25
  document: Document