document-drive 1.9.0 → 1.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-drive",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "module": "./src/index.ts",
@@ -35,7 +35,7 @@
35
35
  "uuid": "^9.0.1",
36
36
  "@powerhousedao/scalars": "1.13.0",
37
37
  "document-model": "2.11.0",
38
- "document-model-libs": "1.121.0"
38
+ "document-model-libs": "1.121.1"
39
39
  },
40
40
  "optionalDependencies": {
41
41
  "@prisma/client": "^5.18.0",
@@ -67,7 +67,7 @@
67
67
  "sanitize-filename": "^1.6.3",
68
68
  "uuid": "^9.0.1",
69
69
  "document-model": "2.11.0",
70
- "document-model-libs": "1.121.0"
70
+ "document-model-libs": "1.121.1"
71
71
  },
72
72
  "scripts": {
73
73
  "check-types": "tsc --build",
@@ -100,7 +100,7 @@ import {
100
100
  type SignalResult,
101
101
  type SynchronizationUnit,
102
102
  } from "./types";
103
- import { filterOperationsByRevision } from "./utils";
103
+ import { filterOperationsByRevision, isAtRevision } from "./utils";
104
104
 
105
105
  export * from "./listener";
106
106
  export type * from "./types";
@@ -1000,21 +1000,27 @@ export class BaseDocumentDriveServer
1000
1000
  }
1001
1001
 
1002
1002
  async getDrive(drive: string, options?: GetDocumentOptions) {
1003
+ let document: DocumentDriveDocument | undefined;
1003
1004
  try {
1004
- const document = await this.cache.getDocument("drives", drive); // TODO support GetDocumentOptions
1005
- if (document && isDocumentDrive(document)) {
1006
- return document;
1005
+ const cachedDocument = await this.cache.getDocument("drives", drive); // TODO support GetDocumentOptions
1006
+ if (cachedDocument && isDocumentDrive(cachedDocument)) {
1007
+ document = cachedDocument;
1008
+ if (isAtRevision(document, options?.revisions)) {
1009
+ return document;
1010
+ }
1007
1011
  }
1008
1012
  } catch (e) {
1009
1013
  logger.error("Error getting drive from cache", e);
1010
1014
  }
1011
- const driveStorage = await this.storage.getDrive(drive);
1012
- const document = this._buildDocument(driveStorage, options);
1013
- if (!isDocumentDrive(document)) {
1015
+ const driveStorage = document ?? (await this.storage.getDrive(drive));
1016
+ const result = this._buildDocument(driveStorage, options);
1017
+ if (!isDocumentDrive(result)) {
1014
1018
  throw new Error(`Document with id ${drive} is not a Document Drive`);
1015
1019
  } else {
1016
- this.cache.setDocument("drives", drive, document).catch(logger.error);
1017
- return document;
1020
+ if (!options?.revisions) {
1021
+ this.cache.setDocument("drives", drive, result).catch(logger.error);
1022
+ }
1023
+ return result;
1018
1024
  }
1019
1025
  }
1020
1026
 
@@ -1039,18 +1045,22 @@ export class BaseDocumentDriveServer
1039
1045
  }
1040
1046
 
1041
1047
  async getDocument(drive: string, id: string, options?: GetDocumentOptions) {
1048
+ let cachedDocument: Document | undefined;
1042
1049
  try {
1043
- const document = await this.cache.getDocument(drive, id); // TODO support GetDocumentOptions
1044
- if (document) {
1045
- return document;
1050
+ cachedDocument = await this.cache.getDocument(drive, id); // TODO support GetDocumentOptions
1051
+ if (cachedDocument && isAtRevision(cachedDocument, options?.revisions)) {
1052
+ return cachedDocument;
1046
1053
  }
1047
1054
  } catch (e) {
1048
1055
  logger.error("Error getting document from cache", e);
1049
1056
  }
1050
- const documentStorage = await this.storage.getDocument(drive, id);
1057
+ const documentStorage =
1058
+ cachedDocument ?? (await this.storage.getDocument(drive, id));
1051
1059
  const document = this._buildDocument(documentStorage, options);
1052
1060
 
1053
- this.cache.setDocument(drive, id, document).catch(logger.error);
1061
+ if (!options?.revisions) {
1062
+ this.cache.setDocument(drive, id, document).catch(logger.error);
1063
+ }
1054
1064
  return document;
1055
1065
  }
1056
1066
 
@@ -1293,7 +1303,11 @@ export class BaseDocumentDriveServer
1293
1303
  documentStorage: DocumentStorage<T>,
1294
1304
  options?: GetDocumentOptions,
1295
1305
  ): T {
1296
- if (documentStorage.state && (!options || options.checkHashes === false)) {
1306
+ if (
1307
+ documentStorage.state &&
1308
+ (!options || options.checkHashes === false) &&
1309
+ isAtRevision(documentStorage as unknown as Document, options?.revisions)
1310
+ ) {
1297
1311
  return documentStorage as T;
1298
1312
  }
1299
1313
 
@@ -1,6 +1,7 @@
1
1
  import { Document, Operation, OperationScope } from "document-model/document";
2
2
  import { logger } from "../../../utils/logger";
3
3
  import {
4
+ GetDocumentOptions,
4
5
  IBaseDocumentDriveServer,
5
6
  Listener,
6
7
  ListenerRevision,
@@ -59,16 +60,21 @@ export class InternalTransmitter implements ITransmitter {
59
60
  if (state) {
60
61
  return state;
61
62
  }
62
- const document = await this.drive.getDocument(
63
- strand.driveId,
64
- strand.documentId,
65
- {
66
- revisions: {
67
- [strand.scope]: index,
68
- },
69
- checkHashes: false,
63
+
64
+ const getDocumentOptions: GetDocumentOptions = {
65
+ revisions: {
66
+ [strand.scope]: index,
70
67
  },
71
- );
68
+ checkHashes: false,
69
+ };
70
+ const document = await (strand.documentId
71
+ ? this.drive.getDocument(
72
+ strand.driveId,
73
+ strand.documentId,
74
+ getDocumentOptions,
75
+ )
76
+ : this.drive.getDrive(strand.driveId, getDocumentOptions));
77
+
72
78
  if (index < 0) {
73
79
  stateByIndex.set(index, document.initialState.state[strand.scope]);
74
80
  } else {
@@ -15,6 +15,18 @@ export function buildRevisionsFilter(
15
15
  }, {});
16
16
  }
17
17
 
18
+ export function buildDocumentRevisionsFilter(
19
+ document: Document,
20
+ ): RevisionsFilter {
21
+ return Object.entries(document.operations).reduce<RevisionsFilter>(
22
+ (acc, [scope, operations]) => {
23
+ acc[scope as OperationScope] = operations.at(-1)?.index ?? -1;
24
+ return acc;
25
+ },
26
+ {} as RevisionsFilter,
27
+ );
28
+ }
29
+
18
30
  export function filterOperationsByRevision(
19
31
  operations: Document["operations"],
20
32
  revisions?: RevisionsFilter,
@@ -24,11 +36,47 @@ export function filterOperationsByRevision(
24
36
  }
25
37
  return (Object.keys(operations) as OperationScope[]).reduce<
26
38
  Document["operations"]
27
- >((acc, scope) => {
28
- const revision = revisions[scope];
29
- if (revision !== undefined) {
30
- acc[scope] = operations[scope].filter((op) => op.index <= revision);
31
- }
32
- return acc;
33
- }, operations);
39
+ >(
40
+ (acc, scope) => {
41
+ const revision = revisions[scope];
42
+ if (revision !== undefined) {
43
+ acc[scope] = operations[scope].filter((op) => op.index <= revision);
44
+ }
45
+ return acc;
46
+ },
47
+ { global: [], local: [] } as unknown as Document["operations"],
48
+ );
49
+ }
50
+
51
+ export function isAtRevision(
52
+ document: Document,
53
+ revisions?: RevisionsFilter,
54
+ ): boolean {
55
+ return (
56
+ !revisions ||
57
+ Object.entries(revisions).find(([scope, revision]) => {
58
+ const operation = document.operations[scope as OperationScope].at(-1);
59
+ if (revision === -1) {
60
+ return operation !== undefined;
61
+ }
62
+ return operation?.index !== revision;
63
+ }) === undefined
64
+ );
65
+ }
66
+
67
+ export function isAfterRevision(
68
+ document: Document,
69
+ revisions?: RevisionsFilter,
70
+ ): boolean {
71
+ return (
72
+ !revisions ||
73
+ Object.entries(revisions).every(([scope, revision]) => {
74
+ const operation = document.operations[scope as OperationScope].at(-1);
75
+
76
+ if (revision === -1) {
77
+ return operation !== undefined;
78
+ }
79
+ return operation && operation.index > revision;
80
+ })
81
+ );
34
82
  }