document-drive 4.0.4 → 4.1.0-dev.10

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 (279) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/prisma/schema.prisma +1 -2
  6. package/dist/src/drive-document-model/gen/document-model.js +3 -3
  7. package/dist/src/drive-document-model/gen/document-model.js.map +1 -1
  8. package/dist/src/drive-document-model/gen/drive/actions.d.ts +34 -10
  9. package/dist/src/drive-document-model/gen/drive/actions.d.ts.map +1 -1
  10. package/dist/src/drive-document-model/gen/node/actions.d.ts +30 -9
  11. package/dist/src/drive-document-model/gen/node/actions.d.ts.map +1 -1
  12. package/dist/src/drive-document-model/gen/node/creators.d.ts +6 -2
  13. package/dist/src/drive-document-model/gen/node/creators.d.ts.map +1 -1
  14. package/dist/src/drive-document-model/gen/node/creators.js +8 -1
  15. package/dist/src/drive-document-model/gen/node/creators.js.map +1 -1
  16. package/dist/src/drive-document-model/gen/reducer.d.ts.map +1 -1
  17. package/dist/src/drive-document-model/gen/reducer.js +32 -31
  18. package/dist/src/drive-document-model/gen/reducer.js.map +1 -1
  19. package/dist/src/drive-document-model/gen/schema/types.d.ts +1 -14
  20. package/dist/src/drive-document-model/gen/schema/types.d.ts.map +1 -1
  21. package/dist/src/drive-document-model/gen/schema/zod.d.ts +1 -5
  22. package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -1
  23. package/dist/src/drive-document-model/gen/schema/zod.js +0 -12
  24. package/dist/src/drive-document-model/gen/schema/zod.js.map +1 -1
  25. package/dist/src/drive-document-model/gen/types.d.ts +17 -5
  26. package/dist/src/drive-document-model/gen/types.d.ts.map +1 -1
  27. package/dist/src/drive-document-model/gen/types.js.map +1 -1
  28. package/dist/src/drive-document-model/module.d.ts +2 -1
  29. package/dist/src/drive-document-model/module.d.ts.map +1 -1
  30. package/dist/src/drive-document-model/src/reducers/node.d.ts.map +1 -1
  31. package/dist/src/drive-document-model/src/reducers/node.js +1 -25
  32. package/dist/src/drive-document-model/src/reducers/node.js.map +1 -1
  33. package/dist/src/drive-document-model/src/tests/actions.test.js +1 -23
  34. package/dist/src/drive-document-model/src/tests/actions.test.js.map +1 -1
  35. package/dist/src/drive-document-model/src/tests/base.test.js +0 -32
  36. package/dist/src/drive-document-model/src/tests/base.test.js.map +1 -1
  37. package/dist/src/drive-document-model/src/tests/node.test.js +4 -0
  38. package/dist/src/drive-document-model/src/tests/node.test.js.map +1 -1
  39. package/dist/src/drive-document-model/src/tests/utils.test.js +7 -37
  40. package/dist/src/drive-document-model/src/tests/utils.test.js.map +1 -1
  41. package/dist/src/drive-document-model/src/utils.d.ts +2 -8
  42. package/dist/src/drive-document-model/src/utils.d.ts.map +1 -1
  43. package/dist/src/drive-document-model/src/utils.js +6 -55
  44. package/dist/src/drive-document-model/src/utils.js.map +1 -1
  45. package/dist/src/processors/types.d.ts +2 -1
  46. package/dist/src/processors/types.d.ts.map +1 -1
  47. package/dist/src/queue/base.d.ts +6 -27
  48. package/dist/src/queue/base.d.ts.map +1 -1
  49. package/dist/src/queue/base.js +15 -203
  50. package/dist/src/queue/base.js.map +1 -1
  51. package/dist/src/queue/event.d.ts +41 -0
  52. package/dist/src/queue/event.d.ts.map +1 -0
  53. package/dist/src/queue/event.js +222 -0
  54. package/dist/src/queue/event.js.map +1 -0
  55. package/dist/src/queue/redis.d.ts +1 -27
  56. package/dist/src/queue/redis.d.ts.map +1 -1
  57. package/dist/src/queue/redis.js +122 -110
  58. package/dist/src/queue/redis.js.map +1 -1
  59. package/dist/src/queue/types.d.ts +18 -12
  60. package/dist/src/queue/types.d.ts.map +1 -1
  61. package/dist/src/queue/types.js +3 -0
  62. package/dist/src/queue/types.js.map +1 -1
  63. package/dist/src/read-mode/server.js.map +1 -1
  64. package/dist/src/read-mode/types.d.ts.map +1 -1
  65. package/dist/src/server/base-server.d.ts +90 -13
  66. package/dist/src/server/base-server.d.ts.map +1 -1
  67. package/dist/src/server/base-server.js +552 -250
  68. package/dist/src/server/base-server.js.map +1 -1
  69. package/dist/src/server/builder.js +2 -2
  70. package/dist/src/server/builder.js.map +1 -1
  71. package/dist/src/server/error.d.ts +3 -3
  72. package/dist/src/server/error.d.ts.map +1 -1
  73. package/dist/src/server/error.js +2 -2
  74. package/dist/src/server/error.js.map +1 -1
  75. package/dist/src/server/listener/listener-manager.d.ts +5 -6
  76. package/dist/src/server/listener/listener-manager.d.ts.map +1 -1
  77. package/dist/src/server/listener/listener-manager.js +62 -79
  78. package/dist/src/server/listener/listener-manager.js.map +1 -1
  79. package/dist/src/server/listener/transmitter/internal.d.ts +4 -4
  80. package/dist/src/server/listener/transmitter/internal.d.ts.map +1 -1
  81. package/dist/src/server/listener/transmitter/internal.js +18 -13
  82. package/dist/src/server/listener/transmitter/internal.js.map +1 -1
  83. package/dist/src/server/listener/transmitter/pull-responder.d.ts.map +1 -1
  84. package/dist/src/server/listener/transmitter/pull-responder.js +14 -8
  85. package/dist/src/server/listener/transmitter/pull-responder.js.map +1 -1
  86. package/dist/src/server/listener/transmitter/switchboard-push.d.ts.map +1 -1
  87. package/dist/src/server/listener/transmitter/switchboard-push.js +13 -8
  88. package/dist/src/server/listener/transmitter/switchboard-push.js.map +1 -1
  89. package/dist/src/server/listener/util.d.ts +1 -1
  90. package/dist/src/server/listener/util.d.ts.map +1 -1
  91. package/dist/src/server/listener/util.js +1 -1
  92. package/dist/src/server/listener/util.js.map +1 -1
  93. package/dist/src/server/sync-manager.d.ts +8 -10
  94. package/dist/src/server/sync-manager.d.ts.map +1 -1
  95. package/dist/src/server/sync-manager.js +62 -147
  96. package/dist/src/server/sync-manager.js.map +1 -1
  97. package/dist/src/server/sync-unit-map.d.ts +137 -0
  98. package/dist/src/server/sync-unit-map.d.ts.map +1 -0
  99. package/dist/src/server/sync-unit-map.js +234 -0
  100. package/dist/src/server/sync-unit-map.js.map +1 -0
  101. package/dist/src/server/types.d.ts +122 -38
  102. package/dist/src/server/types.d.ts.map +1 -1
  103. package/dist/src/server/types.js.map +1 -1
  104. package/dist/src/server/utils.d.ts +12 -3
  105. package/dist/src/server/utils.d.ts.map +1 -1
  106. package/dist/src/server/utils.js +44 -1
  107. package/dist/src/server/utils.js.map +1 -1
  108. package/dist/src/storage/browser.d.ts +8 -4
  109. package/dist/src/storage/browser.d.ts.map +1 -1
  110. package/dist/src/storage/browser.js +80 -22
  111. package/dist/src/storage/browser.js.map +1 -1
  112. package/dist/src/storage/filesystem.d.ts +8 -4
  113. package/dist/src/storage/filesystem.d.ts.map +1 -1
  114. package/dist/src/storage/filesystem.js +79 -22
  115. package/dist/src/storage/filesystem.js.map +1 -1
  116. package/dist/src/storage/ipfs.d.ts.map +1 -1
  117. package/dist/src/storage/ipfs.js.map +1 -1
  118. package/dist/src/storage/memory.d.ts +9 -5
  119. package/dist/src/storage/memory.d.ts.map +1 -1
  120. package/dist/src/storage/memory.js +76 -22
  121. package/dist/src/storage/memory.js.map +1 -1
  122. package/dist/src/storage/prisma/client/edge.js +5 -4
  123. package/dist/src/storage/prisma/client/index-browser.js +2 -1
  124. package/dist/src/storage/prisma/client/index.d.ts +58 -249
  125. package/dist/src/storage/prisma/client/index.js +5 -4
  126. package/dist/src/storage/prisma/client/package.json +1 -1
  127. package/dist/src/storage/prisma/client/schema.prisma +2 -3
  128. package/dist/src/storage/prisma/client/wasm.js +2 -1
  129. package/dist/src/storage/prisma/prisma.d.ts +13 -8
  130. package/dist/src/storage/prisma/prisma.d.ts.map +1 -1
  131. package/dist/src/storage/prisma/prisma.js +119 -51
  132. package/dist/src/storage/prisma/prisma.js.map +1 -1
  133. package/dist/src/storage/types.d.ts +45 -6
  134. package/dist/src/storage/types.d.ts.map +1 -1
  135. package/dist/src/storage/utils.d.ts +3 -0
  136. package/dist/src/storage/utils.d.ts.map +1 -1
  137. package/dist/src/storage/utils.js +14 -0
  138. package/dist/src/storage/utils.js.map +1 -1
  139. package/dist/src/utils/default-drives-manager.d.ts.map +1 -1
  140. package/dist/src/utils/default-drives-manager.js.map +1 -1
  141. package/dist/src/utils/gql-transformations.d.ts +14 -5
  142. package/dist/src/utils/gql-transformations.d.ts.map +1 -1
  143. package/dist/src/utils/gql-transformations.js +1 -0
  144. package/dist/src/utils/gql-transformations.js.map +1 -1
  145. package/dist/src/utils/graphql.d.ts +2 -2
  146. package/dist/src/utils/graphql.d.ts.map +1 -1
  147. package/dist/src/utils/migrations.d.ts.map +1 -1
  148. package/dist/src/utils/migrations.js.map +1 -1
  149. package/dist/src/utils/misc.d.ts +4 -2
  150. package/dist/src/utils/misc.d.ts.map +1 -1
  151. package/dist/src/utils/misc.js +4 -0
  152. package/dist/src/utils/misc.js.map +1 -1
  153. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  154. package/package.json +5 -4
  155. package/dist/test/cache.test.d.ts +0 -2
  156. package/dist/test/cache.test.d.ts.map +0 -1
  157. package/dist/test/cache.test.js +0 -281
  158. package/dist/test/cache.test.js.map +0 -1
  159. package/dist/test/default-remote-drives.test.d.ts +0 -2
  160. package/dist/test/default-remote-drives.test.d.ts.map +0 -1
  161. package/dist/test/default-remote-drives.test.js +0 -446
  162. package/dist/test/default-remote-drives.test.js.map +0 -1
  163. package/dist/test/document-helpers/addUndo.test.d.ts +0 -2
  164. package/dist/test/document-helpers/addUndo.test.d.ts.map +0 -1
  165. package/dist/test/document-helpers/addUndo.test.js +0 -120
  166. package/dist/test/document-helpers/addUndo.test.js.map +0 -1
  167. package/dist/test/document-helpers/attachBranch.test.d.ts +0 -2
  168. package/dist/test/document-helpers/attachBranch.test.d.ts.map +0 -1
  169. package/dist/test/document-helpers/attachBranch.test.js +0 -333
  170. package/dist/test/document-helpers/attachBranch.test.js.map +0 -1
  171. package/dist/test/document-helpers/checkCleanedOperationsIntegrity.test.d.ts +0 -2
  172. package/dist/test/document-helpers/checkCleanedOperationsIntegrity.test.d.ts.map +0 -1
  173. package/dist/test/document-helpers/checkCleanedOperationsIntegrity.test.js +0 -252
  174. package/dist/test/document-helpers/checkCleanedOperationsIntegrity.test.js.map +0 -1
  175. package/dist/test/document-helpers/garbageCollect.test.d.ts +0 -2
  176. package/dist/test/document-helpers/garbageCollect.test.d.ts.map +0 -1
  177. package/dist/test/document-helpers/garbageCollect.test.js +0 -136
  178. package/dist/test/document-helpers/garbageCollect.test.js.map +0 -1
  179. package/dist/test/document-helpers/groupOperationsByScope.test.d.ts +0 -2
  180. package/dist/test/document-helpers/groupOperationsByScope.test.d.ts.map +0 -1
  181. package/dist/test/document-helpers/groupOperationsByScope.test.js +0 -98
  182. package/dist/test/document-helpers/groupOperationsByScope.test.js.map +0 -1
  183. package/dist/test/document-helpers/merge.test.d.ts +0 -2
  184. package/dist/test/document-helpers/merge.test.d.ts.map +0 -1
  185. package/dist/test/document-helpers/merge.test.js +0 -757
  186. package/dist/test/document-helpers/merge.test.js.map +0 -1
  187. package/dist/test/document-helpers/nextSkipNumber.test.d.ts +0 -2
  188. package/dist/test/document-helpers/nextSkipNumber.test.d.ts.map +0 -1
  189. package/dist/test/document-helpers/nextSkipNumber.test.js +0 -123
  190. package/dist/test/document-helpers/nextSkipNumber.test.js.map +0 -1
  191. package/dist/test/document-helpers/prepareOperations.test.d.ts +0 -2
  192. package/dist/test/document-helpers/prepareOperations.test.d.ts.map +0 -1
  193. package/dist/test/document-helpers/prepareOperations.test.js +0 -304
  194. package/dist/test/document-helpers/prepareOperations.test.js.map +0 -1
  195. package/dist/test/document-helpers/removeExistingOperations.test.d.ts +0 -2
  196. package/dist/test/document-helpers/removeExistingOperations.test.d.ts.map +0 -1
  197. package/dist/test/document-helpers/removeExistingOperations.test.js +0 -150
  198. package/dist/test/document-helpers/removeExistingOperations.test.js.map +0 -1
  199. package/dist/test/document-helpers/reshuffleByTimestamp.test.d.ts +0 -2
  200. package/dist/test/document-helpers/reshuffleByTimestamp.test.d.ts.map +0 -1
  201. package/dist/test/document-helpers/reshuffleByTimestamp.test.js +0 -148
  202. package/dist/test/document-helpers/reshuffleByTimestamp.test.js.map +0 -1
  203. package/dist/test/document-helpers/reshuffleByTimestampAndIndex.test.d.ts +0 -2
  204. package/dist/test/document-helpers/reshuffleByTimestampAndIndex.test.d.ts.map +0 -1
  205. package/dist/test/document-helpers/reshuffleByTimestampAndIndex.test.js +0 -200
  206. package/dist/test/document-helpers/reshuffleByTimestampAndIndex.test.js.map +0 -1
  207. package/dist/test/document-helpers/sortOperations.test.d.ts +0 -2
  208. package/dist/test/document-helpers/sortOperations.test.d.ts.map +0 -1
  209. package/dist/test/document-helpers/sortOperations.test.js +0 -101
  210. package/dist/test/document-helpers/sortOperations.test.js.map +0 -1
  211. package/dist/test/document-helpers/split.test.d.ts +0 -2
  212. package/dist/test/document-helpers/split.test.d.ts.map +0 -1
  213. package/dist/test/document-helpers/split.test.js +0 -121
  214. package/dist/test/document-helpers/split.test.js.map +0 -1
  215. package/dist/test/document-helpers/utils.d.ts +0 -8
  216. package/dist/test/document-helpers/utils.d.ts.map +0 -1
  217. package/dist/test/document-helpers/utils.js +0 -22
  218. package/dist/test/document-helpers/utils.js.map +0 -1
  219. package/dist/test/drive-operations.test.d.ts +0 -2
  220. package/dist/test/drive-operations.test.d.ts.map +0 -1
  221. package/dist/test/drive-operations.test.js +0 -145
  222. package/dist/test/drive-operations.test.js.map +0 -1
  223. package/dist/test/graphql.test.d.ts +0 -2
  224. package/dist/test/graphql.test.d.ts.map +0 -1
  225. package/dist/test/graphql.test.js +0 -10
  226. package/dist/test/graphql.test.js.map +0 -1
  227. package/dist/test/internal-listener.test.d.ts +0 -2
  228. package/dist/test/internal-listener.test.d.ts.map +0 -1
  229. package/dist/test/internal-listener.test.js +0 -277
  230. package/dist/test/internal-listener.test.js.map +0 -1
  231. package/dist/test/queue.test.d.ts +0 -2
  232. package/dist/test/queue.test.d.ts.map +0 -1
  233. package/dist/test/queue.test.js +0 -338
  234. package/dist/test/queue.test.js.map +0 -1
  235. package/dist/test/read-mode.test.d.ts +0 -2
  236. package/dist/test/read-mode.test.d.ts.map +0 -1
  237. package/dist/test/read-mode.test.js +0 -578
  238. package/dist/test/read-mode.test.js.map +0 -1
  239. package/dist/test/server/driveOperationsConflictResolution.test.d.ts +0 -2
  240. package/dist/test/server/driveOperationsConflictResolution.test.d.ts.map +0 -1
  241. package/dist/test/server/driveOperationsConflictResolution.test.js +0 -460
  242. package/dist/test/server/driveOperationsConflictResolution.test.js.map +0 -1
  243. package/dist/test/server/mergeOperations.test.d.ts +0 -2
  244. package/dist/test/server/mergeOperations.test.d.ts.map +0 -1
  245. package/dist/test/server/mergeOperations.test.js +0 -107
  246. package/dist/test/server/mergeOperations.test.js.map +0 -1
  247. package/dist/test/server/processOperations.test.d.ts +0 -2
  248. package/dist/test/server/processOperations.test.d.ts.map +0 -1
  249. package/dist/test/server/processOperations.test.js +0 -380
  250. package/dist/test/server/processOperations.test.js.map +0 -1
  251. package/dist/test/server.test.d.ts +0 -2
  252. package/dist/test/server.test.d.ts.map +0 -1
  253. package/dist/test/server.test.js +0 -899
  254. package/dist/test/server.test.js.map +0 -1
  255. package/dist/test/signature-migration.test.d.ts +0 -2
  256. package/dist/test/signature-migration.test.d.ts.map +0 -1
  257. package/dist/test/signature-migration.test.js +0 -239
  258. package/dist/test/signature-migration.test.js.map +0 -1
  259. package/dist/test/storage.test.d.ts +0 -2
  260. package/dist/test/storage.test.d.ts.map +0 -1
  261. package/dist/test/storage.test.js +0 -523
  262. package/dist/test/storage.test.js.map +0 -1
  263. package/dist/test/utils.d.ts +0 -48
  264. package/dist/test/utils.d.ts.map +0 -1
  265. package/dist/test/utils.js +0 -133
  266. package/dist/test/utils.js.map +0 -1
  267. package/dist/test/utils.test.d.ts +0 -2
  268. package/dist/test/utils.test.d.ts.map +0 -1
  269. package/dist/test/utils.test.js +0 -89
  270. package/dist/test/utils.test.js.map +0 -1
  271. package/dist/test/vitest-setup.d.ts +0 -2
  272. package/dist/test/vitest-setup.d.ts.map +0 -1
  273. package/dist/test/vitest-setup.js +0 -5
  274. package/dist/test/vitest-setup.js.map +0 -1
  275. package/dist/tsconfig.tsbuildinfo +0 -1
  276. package/dist/vitest.config.d.ts +0 -3
  277. package/dist/vitest.config.d.ts.map +0 -1
  278. package/dist/vitest.config.js +0 -27
  279. package/dist/vitest.config.js.map +0 -1
@@ -1,19 +1,19 @@
1
1
  import { removeListener, removeTrigger, setSharingType, } from "#drive-document-model/gen/creators";
2
2
  import { createDocument } from "#drive-document-model/gen/utils";
3
- import { isActionJob, isOperationJob, } from "#queue/types";
3
+ import { isActionJob, isDocumentJob, isOperationJob, } from "#queue/types";
4
4
  import { ReadModeServer } from "#read-mode/server";
5
5
  import { DefaultDrivesManager, } from "#utils/default-drives-manager";
6
6
  import { requestPublicDriveWithTokenFromReactor } from "#utils/graphql";
7
7
  import { isDocumentDrive, runAsapAsync } from "#utils/misc";
8
8
  import { RunAsap } from "#utils/run-asap";
9
- import { childLogger, } from "document-drive";
9
+ import { DocumentAlreadyExistsError, childLogger, } from "document-drive";
10
10
  import { attachBranch, createPresignedHeader, garbageCollect, garbageCollectDocumentOperations, groupOperationsByScope, merge, precedes, removeExistingOperations, replayDocument, reshuffleByTimestamp, skipHeaderOperations, sortOperations, validateHeader, } from "document-model";
11
11
  import { ClientError } from "graphql-request";
12
12
  import { ConflictOperationError, OperationError, } from "./error.js";
13
13
  import { PullResponderTransmitter, } from "./listener/transmitter/pull-responder.js";
14
14
  import { SwitchboardPushTransmitter } from "./listener/transmitter/switchboard-push.js";
15
15
  import { DefaultListenerManagerOptions, } from "./types.js";
16
- import { filterOperationsByRevision, isAtRevision } from "./utils.js";
16
+ import { filterOperationsByRevision, isAtRevision, resolveCreateDocumentInput, } from "./utils.js";
17
17
  export class BaseDocumentDriveServer {
18
18
  logger = childLogger(["BaseDocumentDriveServer"]);
19
19
  // external dependencies
@@ -35,15 +35,47 @@ export class BaseDocumentDriveServer {
35
35
  };
36
36
  queueDelegate = {
37
37
  exists: (documentId) => this.documentStorage.exists(documentId),
38
- processOperationJob: async ({ driveId, documentId, operations, options, }) => {
39
- return !documentId || driveId === documentId
40
- ? this.processDriveOperations(driveId, operations, options)
41
- : this.processOperations(driveId, documentId, operations, options);
38
+ processOperationJob: async ({ documentId, operations, options, }) => {
39
+ const document = await this.getDocument(documentId);
40
+ return isDocumentDrive(document)
41
+ ? this.processDriveOperations(documentId, operations, options)
42
+ : this.processOperations(documentId, operations, options);
42
43
  },
43
- processActionJob: async ({ driveId, documentId, actions, options, }) => {
44
- return documentId
45
- ? this.processActions(driveId, documentId, actions, options)
46
- : this.processDriveActions(driveId, actions, options);
44
+ processActionJob: async ({ documentId, actions, options }) => {
45
+ const document = await this.getDocument(documentId);
46
+ return isDocumentDrive(document)
47
+ ? this.processDriveActions(documentId, actions, options)
48
+ : this.processActions(documentId, actions, options);
49
+ },
50
+ processDocumentJob: async ({ documentId, documentType, initialState, options, }) => {
51
+ const documentModelModule = this.getDocumentModelModule(documentType);
52
+ const document = documentModelModule.utils.createDocument({
53
+ ...initialState,
54
+ });
55
+ // TODO: header must be included
56
+ const header = createPresignedHeader(documentId, documentType);
57
+ document.header.id = documentId;
58
+ document.header.sig = header.sig;
59
+ document.header.documentType = documentType;
60
+ try {
61
+ const createdDocument = await this.createDocument({ document }, options?.source ?? { type: "local" }, initialState?.header.meta);
62
+ return {
63
+ status: "SUCCESS",
64
+ operations: [],
65
+ document: createdDocument,
66
+ signals: [],
67
+ };
68
+ }
69
+ catch (error) {
70
+ const cause = error instanceof Error ? error : new Error(JSON.stringify(error));
71
+ return {
72
+ status: "ERROR",
73
+ error: new OperationError("ERROR", undefined, `Error creating document: ${cause.message}`, cause),
74
+ operations: [],
75
+ document: undefined,
76
+ signals: [],
77
+ };
78
+ }
47
79
  },
48
80
  processJob: async (job) => {
49
81
  if (isOperationJob(job)) {
@@ -52,6 +84,9 @@ export class BaseDocumentDriveServer {
52
84
  else if (isActionJob(job)) {
53
85
  return this.queueDelegate.processActionJob(job);
54
86
  }
87
+ else if (isDocumentJob(job)) {
88
+ return this.queueDelegate.processDocumentJob(job);
89
+ }
55
90
  else {
56
91
  throw new Error("Unknown job type", job);
57
92
  }
@@ -98,7 +133,7 @@ export class BaseDocumentDriveServer {
98
133
  return this.initializePromise;
99
134
  }
100
135
  async _initialize() {
101
- await this.listenerManager.initialize(this.handleListenerError);
136
+ await this.listenerManager.initialize(this.handleListenerError.bind(this));
102
137
  await this.queueManager.init(this.queueDelegate, (error) => {
103
138
  this.logger.error(`Error initializing queue manager`, error);
104
139
  errors.push(error);
@@ -153,7 +188,7 @@ export class BaseDocumentDriveServer {
153
188
  }
154
189
  async startSyncRemoteDrive(driveId) {
155
190
  let driveTriggers = this.triggerMap.get(driveId);
156
- const syncUnits = await this.getSynchronizationUnitsIds(driveId);
191
+ const syncUnits = await this.synchronizationManager.getSynchronizationUnitsIds(driveId);
157
192
  const drive = await this.getDrive(driveId);
158
193
  for (const trigger of drive.state.local.triggers) {
159
194
  if (driveTriggers?.get(trigger.id)) {
@@ -166,7 +201,7 @@ export class BaseDocumentDriveServer {
166
201
  pull: "SYNCING",
167
202
  });
168
203
  for (const syncUnit of syncUnits) {
169
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
204
+ this.synchronizationManager.updateSyncStatus(syncUnit, {
170
205
  pull: "SYNCING",
171
206
  });
172
207
  }
@@ -178,46 +213,43 @@ export class BaseDocumentDriveServer {
178
213
  if (error instanceof ClientError) {
179
214
  this.eventEmitter.emit("clientStrandsError", driveId, trigger, error.response.status, error.message);
180
215
  }
181
- }, (revisions) => {
182
- const errorRevision = revisions.filter((r) => r.status !== "SUCCESS");
183
- if (errorRevision.length < 1) {
216
+ }, async (revisions) => {
217
+ const errorRevisions = revisions.filter((r) => r.status !== "SUCCESS");
218
+ if (errorRevisions.length < 1) {
184
219
  this.synchronizationManager.updateSyncStatus(driveId, {
185
220
  pull: "SUCCESS",
186
221
  });
187
222
  }
188
- const documentIdsFromRevision = revisions
189
- .filter((rev) => rev.documentId !== "")
190
- .map((rev) => rev.documentId);
191
- this.getSynchronizationUnitsIds(driveId, documentIdsFromRevision)
192
- .then((revSyncUnits) => {
193
- for (const syncUnit of revSyncUnits) {
194
- const fileErrorRevision = errorRevision.find((r) => r.documentId === syncUnit.documentId);
195
- if (fileErrorRevision) {
196
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, { pull: fileErrorRevision.status }, fileErrorRevision.error);
197
- }
198
- else {
199
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
200
- pull: "SUCCESS",
201
- });
202
- }
203
- }
204
- })
205
- .catch(console.error);
223
+ for (const revision of revisions) {
224
+ const { documentId, scope, branch, status, error } = revision;
225
+ this.synchronizationManager.updateSyncStatus({ documentId, scope, branch }, { pull: status }, error);
226
+ }
206
227
  // if it is the first pull and returns empty
207
- // then updates corresponding push transmitter
228
+ // then updates drive documents to "SUCCESS" and
229
+ // updates corresponding push transmitter
208
230
  if (firstPull) {
209
231
  firstPull = false;
232
+ const syncUnitsIds = await this.synchronizationManager.getSynchronizationUnitsIds(driveId);
233
+ const unchangedSyncUnits = syncUnitsIds.filter((syncUnit) => {
234
+ return !revisions.find((revision) => {
235
+ return (revision.documentId === syncUnit.documentId &&
236
+ revision.scope === syncUnit.scope &&
237
+ revision.branch === syncUnit.branch);
238
+ });
239
+ });
240
+ unchangedSyncUnits.forEach((syncUnit) => {
241
+ this.synchronizationManager.updateSyncStatus(syncUnit, {
242
+ pull: "SUCCESS",
243
+ });
244
+ });
210
245
  const pushListener = drive.state.local.listeners.find((listener) => trigger.data.url === listener.callInfo?.data);
211
246
  if (pushListener) {
212
- this.getSynchronizationUnitsRevision(driveId, syncUnits)
213
- .then((syncUnitRevisions) => {
214
- for (const revision of syncUnitRevisions) {
215
- this.listenerManager
216
- .updateListenerRevision(pushListener.listenerId, driveId, revision.syncId, revision.revision)
217
- .catch(this.logger.error);
218
- }
219
- })
220
- .catch(this.logger.error);
247
+ for (const revision of revisions) {
248
+ const { documentId, scope, branch } = revision;
249
+ this.listenerManager
250
+ .updateListenerRevision(pushListener.listenerId, driveId, { documentId, scope, branch }, revision.revision)
251
+ .catch(this.logger.error);
252
+ }
221
253
  }
222
254
  }
223
255
  }, undefined, this.listeners);
@@ -227,15 +259,12 @@ export class BaseDocumentDriveServer {
227
259
  }
228
260
  }
229
261
  async stopSyncRemoteDrive(driveId) {
230
- const syncUnits = await this.getSynchronizationUnitsIds(driveId);
231
- const filesNodeSyncId = syncUnits
232
- .filter((syncUnit) => syncUnit.documentId !== "")
233
- .map((syncUnit) => syncUnit.syncId);
234
262
  const triggers = this.triggerMap.get(driveId);
235
263
  triggers?.forEach((cancel) => cancel());
236
264
  this.synchronizationManager.updateSyncStatus(driveId, null);
237
- for (const fileNodeSyncId of filesNodeSyncId) {
238
- this.synchronizationManager.updateSyncStatus(fileNodeSyncId, null);
265
+ const syncUnits = await this.synchronizationManager.getSynchronizationUnitsIds(driveId);
266
+ for (const syncUnit of syncUnits) {
267
+ this.synchronizationManager.updateSyncStatus(syncUnit, null);
239
268
  }
240
269
  return this.triggerMap.delete(driveId);
241
270
  }
@@ -298,19 +327,6 @@ export class BaseDocumentDriveServer {
298
327
  }
299
328
  }
300
329
  }
301
- // Delegate synchronization methods to synchronizationManager
302
- getSynchronizationUnits(driveId, documentId, scope, branch, documentType) {
303
- return this.synchronizationManager.getSynchronizationUnits(driveId, documentId, scope, branch, documentType);
304
- }
305
- getSynchronizationUnitsIds(driveId, documentId, scope, branch, documentType) {
306
- return this.synchronizationManager.getSynchronizationUnitsIds(driveId, documentId, scope, branch, documentType);
307
- }
308
- getOperationData(driveId, syncId, filter) {
309
- return this.synchronizationManager.getOperationData(driveId, syncId, filter);
310
- }
311
- getSynchronizationUnitsRevision(driveId, syncUnitsQuery) {
312
- return this.synchronizationManager.getSynchronizationUnitsRevision(driveId, syncUnitsQuery);
313
- }
314
330
  getDocumentModelModule(documentType) {
315
331
  const documentModelModule = this.documentModelModules.find((module) => module.documentModel.id === documentType);
316
332
  if (!documentModelModule) {
@@ -321,6 +337,12 @@ export class BaseDocumentDriveServer {
321
337
  getDocumentModelModules() {
322
338
  return [...this.documentModelModules];
323
339
  }
340
+ addDocument(documentOrType, meta) {
341
+ const input = typeof documentOrType === "string"
342
+ ? { documentType: documentOrType }
343
+ : { document: documentOrType };
344
+ return this.createDocument(input, { type: "local" }, meta);
345
+ }
324
346
  async addDrive(input, preferredEditor) {
325
347
  const document = createDocument({
326
348
  state: {
@@ -428,6 +450,7 @@ export class BaseDocumentDriveServer {
428
450
  }
429
451
  else {
430
452
  if (!options?.revisions) {
453
+ this.cache.setDocument(driveId, result).catch(this.logger.error);
431
454
  this.cache.setDrive(driveId, result).catch(this.logger.error);
432
455
  }
433
456
  return result;
@@ -466,7 +489,12 @@ export class BaseDocumentDriveServer {
466
489
  const driveStorage = await this.documentStorage.getBySlug(slug);
467
490
  return driveStorage.header.id;
468
491
  }
469
- async getDocument(driveId, documentId, options) {
492
+ getDocument(driveId, documentId, options) {
493
+ const id = typeof documentId === "string" ? documentId : driveId;
494
+ const resolvedOptions = typeof documentId === "object" ? documentId : options;
495
+ return this._getDocument(id, resolvedOptions);
496
+ }
497
+ async _getDocument(documentId, options) {
470
498
  let cachedDocument;
471
499
  try {
472
500
  cachedDocument = await this.cache.getDocument(documentId); // TODO support GetDocumentOptions
@@ -487,115 +515,166 @@ export class BaseDocumentDriveServer {
487
515
  getDocuments(driveId) {
488
516
  return this.documentStorage.getChildren(driveId);
489
517
  }
490
- async createDocument(driveId, input) {
518
+ async addChild(parentId, documentId) {
519
+ // TODO: check if document exists? Should that be a concern here?
520
+ try {
521
+ await this.documentStorage.addChild(parentId, documentId);
522
+ // TODO: update listener manager?
523
+ }
524
+ catch (e) {
525
+ this.logger.error("Error adding child document", e);
526
+ throw e;
527
+ }
528
+ }
529
+ async removeChild(parentId, documentId) {
530
+ // TODO: check if document exists? Should that be a concern here?
531
+ // cleanup child sync units state from the parent listeners
532
+ try {
533
+ const childSynUnits = await this.synchronizationManager.getSynchronizationUnitsIds(parentId, [
534
+ documentId,
535
+ ]);
536
+ await this.listenerManager.removeSyncUnits(parentId, childSynUnits);
537
+ }
538
+ catch (e) {
539
+ this.logger.warn("Error removing sync units of child", e);
540
+ }
541
+ // remove child relationship from storage
542
+ try {
543
+ await this.documentStorage.removeChild(parentId, documentId);
544
+ }
545
+ catch (e) {
546
+ this.logger.error("Error adding child document", e);
547
+ throw e;
548
+ }
549
+ }
550
+ async createDocument(input, source, meta) {
551
+ const { documentType, document: inputDocument } = resolveCreateDocumentInput(input);
491
552
  // if a document was provided then checks if it's valid
492
553
  let state = undefined;
493
- if (input.document) {
494
- if (input.documentType !== input.document.header.documentType) {
495
- throw new Error(`Provided document is not ${input.documentType}`);
554
+ if (inputDocument) {
555
+ if ("documentType" in input &&
556
+ documentType !== inputDocument.header.documentType) {
557
+ throw new Error(`Provided document is not ${documentType}`);
496
558
  }
497
- const doc = this._buildDocument(input.document);
559
+ const doc = this._buildDocument(inputDocument);
498
560
  state = doc.state;
499
561
  }
500
562
  // if no document was provided then create a new one
501
- const document = input.document ??
502
- this.getDocumentModelModule(input.documentType).utils.createDocument();
563
+ const document = inputDocument ??
564
+ this.getDocumentModelModule(documentType).utils.createDocument({
565
+ state,
566
+ });
503
567
  // get the header
504
568
  let header;
505
569
  // handle the legacy case where an id is provided
506
- // eslint-disable-next-line
507
- if (input.id && input.id.length > 0) {
508
- if (input.document) {
570
+ if ("id" in input && input.id) {
571
+ if (inputDocument) {
509
572
  header = document.header;
510
- // eslint-disable-next-line
511
573
  document.header.id = input.id;
512
574
  this.logger.warn("Assigning an id to a document is deprecated. Use the header field instead.");
513
575
  }
514
576
  else {
515
577
  this.logger.warn("Creating a document with an id is deprecated. Use the header field instead.");
516
- header = createPresignedHeader();
517
- // eslint-disable-next-line
518
- header.id = input.id;
519
- header.documentType = input.documentType;
578
+ header = createPresignedHeader(input.id, documentType);
520
579
  }
521
580
  }
522
- else if (input.header) {
581
+ else if ("header" in input) {
523
582
  // validate the header passed in
524
583
  await validateHeader(input.header);
525
584
  header = input.header;
526
585
  }
586
+ else if (inputDocument?.header) {
587
+ if (!inputDocument.header.id) {
588
+ throw new Error("Document header id is required");
589
+ }
590
+ if (!inputDocument.header.documentType) {
591
+ throw new Error("Document header documentType is required");
592
+ }
593
+ if (!inputDocument.header.createdAtUtcIso) {
594
+ throw new Error("Document header createdAtUtcIso is required");
595
+ }
596
+ if (!inputDocument.header.sig.nonce) {
597
+ this.logger.warn("Creating a document with an unsigned id is deprecated. Use createSignedHeaderForSigner.");
598
+ // throw new Error("Document header sig nonce is required"); TODO: uncomment when ready to enforce signed documents
599
+ }
600
+ else {
601
+ await validateHeader(inputDocument.header);
602
+ }
603
+ header = inputDocument.header;
604
+ }
527
605
  else {
528
606
  // otherwise, generate a header
529
- header = createPresignedHeader();
530
- header.documentType = input.documentType;
607
+ header = createPresignedHeader(undefined, documentType);
608
+ }
609
+ if (meta) {
610
+ header.meta = { ...header.meta, ...meta };
531
611
  }
532
612
  // stores document information
533
613
  const documentStorage = {
534
614
  header,
535
615
  history: document.history,
536
- operations: document.operations,
616
+ operations: { global: [], local: [] },
537
617
  initialState: document.initialState,
538
618
  clipboard: [],
619
+ attachments: document.attachments,
539
620
  state: state ?? document.state,
540
621
  };
541
622
  await this.documentStorage.create(documentStorage);
542
- try {
543
- await this.documentStorage.addChild(driveId, header.id);
544
- }
545
- catch (e) {
546
- this.logger.error("Error adding child document", e);
547
- // revert the document creation
548
- try {
549
- await this.documentStorage.delete(header.id);
550
- }
551
- catch (e) {
552
- this.logger.error("FATAL: Could not revert document creation. This means that we created a document but failed to add it to the drive..", e);
553
- }
554
- throw e;
555
- }
556
- // set initial state for new syncUnits
557
- for (const syncUnit of input.synchronizationUnits) {
558
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
559
- pull: this.triggerMap.get(driveId) ? "INITIAL_SYNC" : undefined,
560
- push: this.listenerManager.driveHasListeners(driveId)
561
- ? "SUCCESS"
562
- : undefined,
563
- });
564
- }
623
+ // TODO set initial state for document sync units
624
+ // if (source.type === "trigger") {
625
+ // for (const scope of Object.keys(document.state)) {
626
+ // this.synchronizationManager.updateSyncStatus(
627
+ // {
628
+ // documentId: document.id,
629
+ // scope,
630
+ // branch: "main" /* TODO handle branches */,
631
+ // },
632
+ // {
633
+ // pull: "INITIAL_SYNC",
634
+ // push: this.listenerManager.driveHasListeners(driveId)
635
+ // ? "SUCCESS"
636
+ // : undefined,
637
+ // },
638
+ // );
639
+ // }
640
+ // }
565
641
  // if the document contains operations then
566
642
  // stores the operations in the storage
567
643
  const operations = Object.values(document.operations).flat();
568
644
  if (operations.length) {
569
645
  if (isDocumentDrive(document)) {
570
- await this.legacyStorage.addDriveOperations(driveId, operations, document);
646
+ await this.legacyStorage.addDriveOperations(header.id, operations, document);
571
647
  }
572
648
  else {
573
- await this.legacyStorage.addDocumentOperations(driveId, header.id, operations, document);
649
+ await this.legacyStorage.addDocumentOperations(header.id, operations, document);
574
650
  }
575
651
  }
576
652
  return document;
577
653
  }
578
- async deleteDocument(driveId, documentId) {
654
+ async deleteDocument(documentId) {
579
655
  try {
580
- const syncUnits = await this.getSynchronizationUnitsIds(driveId, [
581
- documentId,
582
- ]);
656
+ const syncUnits = await this.synchronizationManager.getSynchronizationUnitsIds(undefined, [documentId]);
583
657
  // remove document sync units status when a document is deleted
584
658
  for (const syncUnit of syncUnits) {
585
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, null);
659
+ this.synchronizationManager.updateSyncStatus(syncUnit, null);
660
+ }
661
+ const parents = await this.documentStorage.getParents(documentId);
662
+ for (const parent of parents) {
663
+ this.listenerManager
664
+ .removeSyncUnits(parent, syncUnits)
665
+ .catch(this.logger.warn);
586
666
  }
587
- await this.listenerManager.removeSyncUnits(driveId, syncUnits);
588
667
  }
589
668
  catch (error) {
590
669
  this.logger.warn("Error deleting document", error);
591
670
  }
592
671
  await this.cache.deleteDocument(documentId);
593
- return this.documentStorage.delete(documentId);
672
+ await this.documentStorage.delete(documentId);
594
673
  }
595
- async _processOperations(driveId, documentId, documentStorage, operations) {
674
+ async _processOperations(documentId, documentStorage, operations) {
596
675
  const operationsApplied = [];
597
676
  const signals = [];
598
- const documentStorageWithState = await this._addDocumentResultingStage(documentStorage, driveId, documentId);
677
+ const documentStorageWithState = await this._addDocumentResultingStage(documentStorage, documentId);
599
678
  let document = this._buildDocument(documentStorageWithState);
600
679
  let error; // TODO: replace with an array of errors/consistency issues
601
680
  const operationsByScope = groupOperationsByScope(operations);
@@ -627,7 +706,7 @@ export class BaseDocumentDriveServer {
627
706
  try {
628
707
  // runs operation on next available tick, to avoid blocking the main thread
629
708
  const taskQueueMethod = this.options.taskQueueMethod;
630
- const task = () => this._performOperation(driveId, documentId, document, nextOperation, skipHashValidation);
709
+ const task = () => this._performOperation(documentId, document, nextOperation, skipHashValidation);
631
710
  const appliedResult = await (taskQueueMethod
632
711
  ? runAsapAsync(task, taskQueueMethod)
633
712
  : task());
@@ -653,7 +732,7 @@ export class BaseDocumentDriveServer {
653
732
  error,
654
733
  };
655
734
  }
656
- async _addDocumentResultingStage(document, driveId, documentId, options) {
735
+ async _addDocumentResultingStage(document, documentId, options) {
657
736
  // apply skip header operations to all scopes
658
737
  const operations = options?.revisions !== undefined
659
738
  ? filterOperationsByRevision(document.operations, options.revisions)
@@ -664,9 +743,9 @@ export class BaseDocumentDriveServer {
664
743
  // if the latest operation doesn't have a resulting state then tries
665
744
  // to retrieve it from the db to avoid rerunning all the operations
666
745
  if (lastRemainingOperation && !lastRemainingOperation.resultingState) {
667
- lastRemainingOperation.resultingState = await (documentId
668
- ? this.legacyStorage.getOperationResultingState?.(driveId, documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main")
669
- : this.legacyStorage.getDriveOperationResultingState?.(driveId, lastRemainingOperation.index, lastRemainingOperation.scope, "main"));
746
+ lastRemainingOperation.resultingState = await (isDocumentDrive(document)
747
+ ? this.legacyStorage.getOperationResultingState?.(documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main")
748
+ : this.legacyStorage.getDriveOperationResultingState?.(documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main"));
670
749
  }
671
750
  }
672
751
  return {
@@ -691,7 +770,7 @@ export class BaseDocumentDriveServer {
691
770
  reuseOperationResultingState: options?.checkHashes ?? true,
692
771
  });
693
772
  }
694
- async _performOperation(driveId, documentId, document, operation, skipHashValidation = false) {
773
+ async _performOperation(documentId, document, operation, skipHashValidation = false) {
695
774
  const documentModelModule = this.getDocumentModelModule(document.header.documentType);
696
775
  const signalResults = [];
697
776
  let newDocument = document;
@@ -704,36 +783,22 @@ export class BaseDocumentDriveServer {
704
783
  // if the latest operation doesn't have a resulting state then tries
705
784
  // to retrieve it from the db to avoid rerunning all the operations
706
785
  if (lastRemainingOperation && !lastRemainingOperation.resultingState) {
707
- lastRemainingOperation.resultingState = await (documentId
708
- ? this.legacyStorage.getOperationResultingState?.(driveId, documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main")
709
- : this.legacyStorage.getDriveOperationResultingState?.(driveId, lastRemainingOperation.index, lastRemainingOperation.scope, "main"));
786
+ lastRemainingOperation.resultingState = await (isDocumentDrive(document)
787
+ ? this.legacyStorage.getOperationResultingState?.(documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main")
788
+ : this.legacyStorage.getDriveOperationResultingState?.(documentId, lastRemainingOperation.index, lastRemainingOperation.scope, "main"));
710
789
  }
711
790
  const operationSignals = [];
712
791
  newDocument = documentModelModule.reducer(newDocument, operation, (signal) => {
713
792
  let handler = undefined;
714
793
  switch (signal.type) {
715
794
  case "CREATE_CHILD_DOCUMENT":
716
- handler = () => this.createDocument(driveId, signal.input);
795
+ handler = () => this.addChild(documentId, signal.input.id);
717
796
  break;
718
797
  case "DELETE_CHILD_DOCUMENT":
719
- handler = () => this.deleteDocument(driveId, signal.input.id);
798
+ handler = () => this.removeChild(documentId, signal.input.id);
720
799
  break;
721
800
  case "COPY_CHILD_DOCUMENT":
722
- handler = () => this.getDocument(driveId, signal.input.id).then((documentToCopy) => {
723
- const doc = {
724
- ...documentToCopy,
725
- header: {
726
- ...documentToCopy.header,
727
- slug: signal.input.newId,
728
- },
729
- };
730
- return this.createDocument(driveId, {
731
- id: signal.input.newId,
732
- documentType: documentToCopy.header.documentType,
733
- document: doc,
734
- synchronizationUnits: signal.input.synchronizationUnits,
735
- });
736
- });
801
+ handler = () => this.addChild(documentId, signal.input.newId);
737
802
  break;
738
803
  }
739
804
  if (handler) {
@@ -748,6 +813,7 @@ export class BaseDocumentDriveServer {
748
813
  if (!appliedOperation.error &&
749
814
  appliedOperation.hash !== operation.hash &&
750
815
  !skipHashValidation) {
816
+ this.logger.warn(JSON.stringify(appliedOperation, null, 2));
751
817
  throw new ConflictOperationError(operation, appliedOperation);
752
818
  }
753
819
  for (const signalHandler of operationSignals) {
@@ -760,28 +826,103 @@ export class BaseDocumentDriveServer {
760
826
  operation: appliedOperation,
761
827
  };
762
828
  }
763
- addOperation(driveId, documentId, operation, options) {
764
- return this.addOperations(driveId, documentId, [operation], options);
829
+ addOperation(driveIdOrDocumentId, documentIdOrOperation, operationOrOptions, maybeOptions) {
830
+ let documentId;
831
+ let operation;
832
+ let options;
833
+ if (typeof documentIdOrOperation === "string") {
834
+ // Deprecated overload: (driveId, documentId, operation, options)
835
+ documentId = documentIdOrOperation;
836
+ operation = operationOrOptions;
837
+ options = maybeOptions;
838
+ }
839
+ else {
840
+ // Standard overload: (documentId, operation, options)
841
+ documentId = driveIdOrDocumentId;
842
+ operation = documentIdOrOperation;
843
+ options = operationOrOptions;
844
+ }
845
+ return this.addOperations(documentId, [operation], options);
765
846
  }
766
- async _addOperations(driveId, documentId, callback) {
847
+ async _addOperations(documentId, callback) {
767
848
  if (!this.legacyStorage.addDocumentOperationsWithTransaction) {
768
849
  const documentStorage = await this.documentStorage.get(documentId);
769
850
  const result = await callback(documentStorage);
770
851
  // saves the applied operations to storage
771
852
  if (result.operations.length > 0) {
772
- await this.legacyStorage.addDocumentOperations(driveId, documentId, result.operations, result.document);
853
+ await this.legacyStorage.addDocumentOperations(documentId, result.operations, result.document);
773
854
  }
774
855
  }
775
856
  else {
776
- await this.legacyStorage.addDocumentOperationsWithTransaction(driveId, documentId, callback);
857
+ await this.legacyStorage.addDocumentOperationsWithTransaction(documentId, callback);
858
+ }
859
+ }
860
+ async queueDocument(input, options) {
861
+ const { id, documentType, document } = resolveCreateDocumentInput(input);
862
+ if (!id) {
863
+ throw new Error("Document id is required", { cause: input });
864
+ }
865
+ if (!documentType) {
866
+ throw new Error("Document type is required", { cause: input });
867
+ }
868
+ const exists = await this.documentStorage.exists(id);
869
+ if (exists) {
870
+ throw new DocumentAlreadyExistsError(id);
871
+ }
872
+ // add listeners first
873
+ let jobId;
874
+ const promise = new Promise((resolve, reject) => {
875
+ const unsubscribe = this.queueManager.on("jobCompleted", (job, result) => {
876
+ if (job.jobId === jobId) {
877
+ unsubscribe();
878
+ unsubscribeError();
879
+ resolve(result);
880
+ }
881
+ });
882
+ const unsubscribeError = this.queueManager.on("jobFailed", (job, error) => {
883
+ if (job.jobId === jobId) {
884
+ unsubscribe();
885
+ unsubscribeError();
886
+ reject(error);
887
+ }
888
+ });
889
+ });
890
+ // now queue the job
891
+ try {
892
+ jobId = await this.queueManager.addJob({
893
+ documentId: id,
894
+ documentType,
895
+ initialState: document,
896
+ options,
897
+ });
898
+ }
899
+ catch (error) {
900
+ this.logger.error("Error adding job", error);
901
+ throw error;
777
902
  }
903
+ return promise;
778
904
  }
779
- queueOperation(driveId, documentId, operation, options) {
780
- return this.queueOperations(driveId, documentId, [operation], options);
905
+ queueOperation(driveIdOrDocumentId, documentIdOrOperation, operationOrOptions, maybeOptions) {
906
+ let documentId;
907
+ let operation;
908
+ let options;
909
+ if (typeof documentIdOrOperation === "string") {
910
+ // Deprecated overload: (driveId, documentId, operation, options)
911
+ documentId = documentIdOrOperation;
912
+ operation = operationOrOptions;
913
+ options = maybeOptions;
914
+ }
915
+ else {
916
+ // Standard overload: (documentId, operation, options)
917
+ documentId = driveIdOrDocumentId;
918
+ operation = documentIdOrOperation;
919
+ options = operationOrOptions;
920
+ }
921
+ return this._queueOperations(documentId, [operation], options);
781
922
  }
782
- async resultIfExistingOperations(drive, id, operations) {
923
+ async resultIfExistingOperations(id, operations) {
783
924
  try {
784
- const document = await this.getDocument(drive, id);
925
+ const document = await this.getDocument(id);
785
926
  const newOperation = operations.find((op) => !op.id ||
786
927
  !document.operations[op.scope].find((existingOp) => existingOp.id === op.id &&
787
928
  existingOp.index === op.index &&
@@ -806,9 +947,27 @@ export class BaseDocumentDriveServer {
806
947
  return undefined;
807
948
  }
808
949
  }
809
- async queueOperations(driveId, documentId, operations, options) {
950
+ queueOperations(driveIdOrDocumentId, documentIdOrOperations, operationsOrOptions, maybeOptions) {
951
+ let documentId;
952
+ let operations;
953
+ let options;
954
+ if (typeof documentIdOrOperations === "string") {
955
+ // Deprecated overload: (driveId, documentId, operations, options)
956
+ documentId = documentIdOrOperations;
957
+ operations = operationsOrOptions;
958
+ options = maybeOptions;
959
+ }
960
+ else {
961
+ // Standard overload: (documentId, operations, options)
962
+ documentId = driveIdOrDocumentId;
963
+ operations = documentIdOrOperations;
964
+ options = operationsOrOptions;
965
+ }
966
+ return this._queueOperations(documentId, operations, options);
967
+ }
968
+ async _queueOperations(documentId, operations, options) {
810
969
  // if operations are already stored then returns cached document
811
- const result = await this.resultIfExistingOperations(driveId, documentId, operations);
970
+ const result = await this.resultIfExistingOperations(documentId, operations);
812
971
  if (result) {
813
972
  return result;
814
973
  }
@@ -833,8 +992,7 @@ export class BaseDocumentDriveServer {
833
992
  // now queue the job
834
993
  try {
835
994
  jobId = await this.queueManager.addJob({
836
- driveId: driveId,
837
- documentId: documentId,
995
+ documentId,
838
996
  operations,
839
997
  options,
840
998
  });
@@ -845,14 +1003,46 @@ export class BaseDocumentDriveServer {
845
1003
  }
846
1004
  return promise;
847
1005
  }
848
- async queueAction(driveId, documentId, action, options) {
849
- return this.queueActions(driveId, documentId, [action], options);
1006
+ queueAction(driveIdOrDocumentId, documentIdOrAction, actionOrOptions, maybeOptions) {
1007
+ let documentId;
1008
+ let action;
1009
+ let options;
1010
+ if (typeof documentIdOrAction === "string") {
1011
+ // Deprecated overload: (driveId, documentId, action, options)
1012
+ documentId = documentIdOrAction;
1013
+ action = actionOrOptions;
1014
+ options = maybeOptions;
1015
+ }
1016
+ else {
1017
+ // Standard overload: (documentId, action, options)
1018
+ documentId = driveIdOrDocumentId;
1019
+ action = documentIdOrAction;
1020
+ options = actionOrOptions;
1021
+ }
1022
+ return this._queueActions(documentId, [action], options);
1023
+ }
1024
+ queueActions(driveIdOrDocumentId, documentIdOrActions, actionsOrOptions, maybeOptions) {
1025
+ let documentId;
1026
+ let actions;
1027
+ let options;
1028
+ if (typeof documentIdOrActions === "string") {
1029
+ // Deprecated overload: (driveId, documentId, actions, options)
1030
+ documentId = documentIdOrActions;
1031
+ actions = actionsOrOptions;
1032
+ options = maybeOptions;
1033
+ }
1034
+ else {
1035
+ // Standard overload: (documentId, actions, options)
1036
+ documentId = driveIdOrDocumentId;
1037
+ actions = documentIdOrActions;
1038
+ options = actionsOrOptions;
1039
+ }
1040
+ return this._queueActions(documentId, actions, options);
850
1041
  }
851
- async queueActions(driveId, documentId, actions, options) {
1042
+ async _queueActions(documentId, actions, options) {
852
1043
  try {
853
1044
  const jobId = await this.queueManager.addJob({
854
- driveId: driveId,
855
- documentId: documentId,
1045
+ documentId,
856
1046
  actions,
857
1047
  options,
858
1048
  });
@@ -878,13 +1068,19 @@ export class BaseDocumentDriveServer {
878
1068
  throw error;
879
1069
  }
880
1070
  }
1071
+ /**
1072
+ * @deprecated Use the {@link queueAction} method instead.
1073
+ */
881
1074
  async queueDriveAction(driveId, action, options) {
882
1075
  return this.queueDriveActions(driveId, [action], options);
883
1076
  }
1077
+ /**
1078
+ * @deprecated Use the {@link queueActions} method instead.
1079
+ */
884
1080
  async queueDriveActions(driveId, actions, options) {
885
1081
  try {
886
1082
  const jobId = await this.queueManager.addJob({
887
- driveId: driveId,
1083
+ documentId: driveId,
888
1084
  actions,
889
1085
  options,
890
1086
  });
@@ -910,12 +1106,27 @@ export class BaseDocumentDriveServer {
910
1106
  throw error;
911
1107
  }
912
1108
  }
913
- async addOperations(driveId, documentId, operations, options) {
914
- return this.queueOperations(driveId, documentId, operations, options);
1109
+ addOperations(driveIdOrDocumentId, documentIdOrOperations, operationsOrOptions, maybeOptions) {
1110
+ let documentId;
1111
+ let operations;
1112
+ let options;
1113
+ if (typeof documentIdOrOperations === "string") {
1114
+ // Deprecated overload: (driveId, documentId, operations, options)
1115
+ documentId = documentIdOrOperations;
1116
+ operations = operationsOrOptions;
1117
+ options = maybeOptions;
1118
+ }
1119
+ else {
1120
+ // Standard overload: (documentId, operations, options)
1121
+ documentId = driveIdOrDocumentId;
1122
+ operations = documentIdOrOperations;
1123
+ options = operationsOrOptions;
1124
+ }
1125
+ return this._queueOperations(documentId, operations, options);
915
1126
  }
916
- async processOperations(driveId, documentId, operations, options) {
1127
+ async processOperations(documentId, operations, options) {
917
1128
  // if operations are already stored then returns the result
918
- const result = await this.resultIfExistingOperations(driveId, documentId, operations);
1129
+ const result = await this.resultIfExistingOperations(documentId, operations);
919
1130
  if (result) {
920
1131
  return result;
921
1132
  }
@@ -924,8 +1135,8 @@ export class BaseDocumentDriveServer {
924
1135
  const signals = [];
925
1136
  let error;
926
1137
  try {
927
- await this._addOperations(driveId, documentId, async (documentStorage) => {
928
- const result = await this._processOperations(driveId, documentId, documentStorage, operations);
1138
+ await this._addOperations(documentId, async (documentStorage) => {
1139
+ const result = await this._processOperations(documentId, documentStorage, operations);
929
1140
  if (!result.document) {
930
1141
  this.logger.error("Invalid document");
931
1142
  throw result.error ?? new Error("Invalid document");
@@ -939,17 +1150,28 @@ export class BaseDocumentDriveServer {
939
1150
  document: result.document,
940
1151
  };
941
1152
  });
1153
+ const syncUnits = new Array();
942
1154
  if (document) {
943
1155
  this.cache.setDocument(documentId, document).catch(this.logger.error);
944
- }
945
- // gets all the different scopes and branches combinations from the operations
946
- const { scopes, branches } = operationsApplied.reduce((acc, operation) => {
947
- if (!acc.scopes.includes(operation.scope)) {
948
- acc.scopes.push(operation.scope);
1156
+ // creates array of unique sync units from the applied operations
1157
+ for (const operation of operationsApplied) {
1158
+ const syncUnit = {
1159
+ documentId,
1160
+ documentType: document.header.documentType,
1161
+ scope: operation.scope,
1162
+ branch: "main", // TODO: handle branches
1163
+ revision: operation.index + 1,
1164
+ lastUpdated: operation.timestamp,
1165
+ };
1166
+ // checks if this sync unit was already added
1167
+ const exists = syncUnits.some((unit) => unit.documentId === syncUnit.documentId &&
1168
+ unit.scope === syncUnit.scope &&
1169
+ unit.branch === syncUnit.branch);
1170
+ if (!exists) {
1171
+ syncUnits.push(syncUnit);
1172
+ }
949
1173
  }
950
- return acc;
951
- }, { scopes: [], branches: ["main"] });
952
- const syncUnits = await this.getSynchronizationUnits(driveId, [documentId], scopes, branches);
1174
+ }
953
1175
  // checks if any of the provided operations where reshufled
954
1176
  const newOp = operationsApplied.find((appliedOp) => !operations.find((o) => o.id === appliedOp.id &&
955
1177
  o.index === appliedOp.index &&
@@ -963,47 +1185,51 @@ export class BaseDocumentDriveServer {
963
1185
  : (options?.source ?? { type: "local" });
964
1186
  // update listener cache
965
1187
  const operationSource = this.getOperationSource(source);
966
- this.listenerManager
967
- .updateSynchronizationRevisions(driveId, syncUnits, source, () => {
968
- this.synchronizationManager.updateSyncStatus(driveId, {
969
- [operationSource]: "SYNCING",
970
- });
971
- for (const syncUnit of syncUnits) {
972
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
1188
+ // TODO Decouple the operation processing from syncing it to the listeners?
1189
+ // Listener manager should be the one keeping the sync status since it depends on the listeners
1190
+ if (syncUnits.length) {
1191
+ this.listenerManager
1192
+ .updateSynchronizationRevisions(syncUnits, source, () => {
1193
+ this.synchronizationManager.updateSyncStatus(documentId, {
973
1194
  [operationSource]: "SYNCING",
974
1195
  });
975
- }
976
- }, this.handleListenerError.bind(this), options?.forceSync ?? source.type === "local")
977
- .then((updates) => {
978
- if (updates.length) {
979
- this.synchronizationManager.updateSyncStatus(driveId, {
980
- [operationSource]: "SUCCESS",
981
- });
982
- }
983
- for (const syncUnit of syncUnits) {
984
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
985
- [operationSource]: "SUCCESS",
986
- });
987
- }
988
- })
989
- .catch((error) => {
990
- this.logger.error("Non handled error updating sync revision", error);
991
- this.synchronizationManager.updateSyncStatus(driveId, {
992
- [operationSource]: "ERROR",
993
- }, error);
994
- for (const syncUnit of syncUnits) {
995
- this.synchronizationManager.updateSyncStatus(syncUnit.syncId, {
1196
+ for (const syncUnit of syncUnits) {
1197
+ this.synchronizationManager.updateSyncStatus(syncUnit, {
1198
+ [operationSource]: "SYNCING",
1199
+ });
1200
+ }
1201
+ }, this.handleListenerError.bind(this), options?.forceSync ?? source.type === "local")
1202
+ .then((updates) => {
1203
+ if (updates.length) {
1204
+ this.synchronizationManager.updateSyncStatus(documentId, {
1205
+ [operationSource]: "SUCCESS",
1206
+ });
1207
+ }
1208
+ for (const syncUnit of syncUnits) {
1209
+ this.synchronizationManager.updateSyncStatus(syncUnit, {
1210
+ [operationSource]: "SUCCESS",
1211
+ });
1212
+ }
1213
+ })
1214
+ .catch((error) => {
1215
+ this.logger.error("Non handled error updating sync revision", error);
1216
+ this.synchronizationManager.updateSyncStatus(documentId, {
996
1217
  [operationSource]: "ERROR",
997
1218
  }, error);
998
- }
999
- });
1219
+ for (const syncUnit of syncUnits) {
1220
+ this.synchronizationManager.updateSyncStatus(syncUnit, {
1221
+ [operationSource]: "ERROR",
1222
+ }, error);
1223
+ }
1224
+ });
1225
+ }
1000
1226
  // after applying all the valid operations,throws
1001
1227
  // an error if there was an invalid operation
1002
1228
  if (error) {
1003
1229
  throw error;
1004
1230
  }
1005
- this.eventEmitter.emit("documentOperationsAdded", driveId, documentId, operations);
1006
- this.eventEmitter.emit("operationsAdded", driveId, documentId, operations);
1231
+ this.eventEmitter.emit("documentOperationsAdded", documentId, operations);
1232
+ this.eventEmitter.emit("operationsAdded", documentId, operations);
1007
1233
  return {
1008
1234
  status: "SUCCESS",
1009
1235
  document,
@@ -1024,6 +1250,9 @@ export class BaseDocumentDriveServer {
1024
1250
  };
1025
1251
  }
1026
1252
  }
1253
+ /**
1254
+ * @deprecated Use the {@link addOperation} method instead.
1255
+ */
1027
1256
  addDriveOperation(driveId, operation, options) {
1028
1257
  return this.addDriveOperations(driveId, [operation], options);
1029
1258
  }
@@ -1041,6 +1270,9 @@ export class BaseDocumentDriveServer {
1041
1270
  return this.legacyStorage.addDriveOperationsWithTransaction(driveId, callback);
1042
1271
  }
1043
1272
  }
1273
+ /**
1274
+ * @deprecated Use the {@link queueOperation} method instead.
1275
+ */
1044
1276
  queueDriveOperation(driveId, operation, options) {
1045
1277
  return this.queueDriveOperations(driveId, [operation], options);
1046
1278
  }
@@ -1069,6 +1301,9 @@ export class BaseDocumentDriveServer {
1069
1301
  return undefined;
1070
1302
  }
1071
1303
  }
1304
+ /**
1305
+ * @deprecated Use the {@link queueOperations} method instead.
1306
+ */
1072
1307
  async queueDriveOperations(driveId, operations, options) {
1073
1308
  // if operations are already stored then returns cached document
1074
1309
  const result = await this.resultIfExistingDriveOperations(driveId, operations);
@@ -1077,7 +1312,7 @@ export class BaseDocumentDriveServer {
1077
1312
  }
1078
1313
  try {
1079
1314
  const jobId = await this.queueManager.addJob({
1080
- driveId: driveId,
1315
+ documentId: driveId,
1081
1316
  operations,
1082
1317
  options,
1083
1318
  });
@@ -1103,6 +1338,9 @@ export class BaseDocumentDriveServer {
1103
1338
  throw error;
1104
1339
  }
1105
1340
  }
1341
+ /**
1342
+ * @deprecated Use the {@link addOperations} method instead.
1343
+ */
1106
1344
  async addDriveOperations(driveId, operations, options) {
1107
1345
  return this.queueDriveOperations(driveId, operations, options);
1108
1346
  }
@@ -1118,7 +1356,7 @@ export class BaseDocumentDriveServer {
1118
1356
  }
1119
1357
  try {
1120
1358
  await this._addDriveOperations(driveId, async (documentStorage) => {
1121
- const result = await this._processOperations(driveId, undefined, documentStorage, operations.slice());
1359
+ const result = await this._processOperations(driveId, documentStorage, operations.slice());
1122
1360
  document = result.document;
1123
1361
  operationsApplied.push(...result.operationsApplied);
1124
1362
  signals.push(...result.signals);
@@ -1131,6 +1369,7 @@ export class BaseDocumentDriveServer {
1131
1369
  if (!document || !isDocumentDrive(document)) {
1132
1370
  throw error ?? new Error("Invalid Document Drive document");
1133
1371
  }
1372
+ this.cache.setDocument(driveId, document).catch(this.logger.error);
1134
1373
  this.cache.setDrive(driveId, document).catch(this.logger.error);
1135
1374
  // update listener cache
1136
1375
  const lastOperation = operationsApplied
@@ -1151,13 +1390,12 @@ export class BaseDocumentDriveServer {
1151
1390
  : (options?.source ?? { type: "local" });
1152
1391
  const operationSource = this.getOperationSource(source);
1153
1392
  this.listenerManager
1154
- .updateSynchronizationRevisions(driveId, [
1393
+ .updateSynchronizationRevisions([
1155
1394
  {
1156
- syncId: "0",
1157
- documentId: "",
1395
+ documentId: driveId,
1396
+ documentType: document.header.documentType,
1158
1397
  scope: "global",
1159
1398
  branch: "main",
1160
- documentType: "powerhouse/document-drive",
1161
1399
  lastUpdated: lastOperation.timestamp,
1162
1400
  revision: lastOperation.index,
1163
1401
  },
@@ -1181,10 +1419,10 @@ export class BaseDocumentDriveServer {
1181
1419
  });
1182
1420
  }
1183
1421
  if (this.shouldSyncRemoteDrive(document)) {
1184
- this.startSyncRemoteDrive(driveId);
1422
+ this.startSyncRemoteDrive(driveId).catch(this.logger.error);
1185
1423
  }
1186
1424
  else {
1187
- this.stopSyncRemoteDrive(driveId);
1425
+ this.stopSyncRemoteDrive(driveId).catch(this.logger.error);
1188
1426
  }
1189
1427
  // after applying all the valid operations,throws
1190
1428
  // an error if there was an invalid operation
@@ -1192,7 +1430,7 @@ export class BaseDocumentDriveServer {
1192
1430
  throw error;
1193
1431
  }
1194
1432
  this.eventEmitter.emit("driveOperationsAdded", driveId, operations);
1195
- this.eventEmitter.emit("operationsAdded", driveId, null, operations);
1433
+ this.eventEmitter.emit("operationsAdded", driveId, operations);
1196
1434
  return {
1197
1435
  status: "SUCCESS",
1198
1436
  document,
@@ -1226,20 +1464,81 @@ export class BaseDocumentDriveServer {
1226
1464
  }
1227
1465
  return operations;
1228
1466
  }
1229
- async addAction(driveId, documentId, action, options) {
1230
- return this.addActions(driveId, documentId, [action], options);
1231
- }
1232
- async addActions(driveId, documentId, actions, options) {
1233
- return this.queueActions(driveId, documentId, actions, options);
1467
+ addAction(driveIdOrDocumentId, documentIdOrAction, actionOrOptions, maybeOptions) {
1468
+ let documentId;
1469
+ let action;
1470
+ let options;
1471
+ if (typeof documentIdOrAction === "string") {
1472
+ // Deprecated overload: (driveId, documentId, action, options)
1473
+ documentId = documentIdOrAction;
1474
+ action = actionOrOptions;
1475
+ options = maybeOptions;
1476
+ }
1477
+ else {
1478
+ // Standard overload: (documentId, action, options)
1479
+ documentId = driveIdOrDocumentId;
1480
+ action = documentIdOrAction;
1481
+ options = actionOrOptions;
1482
+ }
1483
+ return this._addAction(documentId, action, options);
1484
+ }
1485
+ async _addAction(documentId, action, options) {
1486
+ return this.addActions(documentId, [action], options);
1487
+ }
1488
+ addActions(driveIdOrDocumentId, documentIdOrActions, actionsOrOptions, maybeOptions) {
1489
+ let documentId;
1490
+ let actions;
1491
+ let options;
1492
+ if (typeof documentIdOrActions === "string") {
1493
+ // Deprecated overload: (driveId, documentId, actions, options)
1494
+ documentId = documentIdOrActions;
1495
+ actions = actionsOrOptions;
1496
+ options = maybeOptions;
1497
+ }
1498
+ else {
1499
+ // Standard overload: (documentId, actions, options)
1500
+ documentId = driveIdOrDocumentId;
1501
+ actions = documentIdOrActions;
1502
+ options = actionsOrOptions;
1503
+ }
1504
+ return this._queueActions(documentId, actions, options);
1234
1505
  }
1235
- async processActions(driveId, documentId, actions, options) {
1236
- const document = await this.getDocument(driveId, documentId);
1506
+ async processActions(documentId, actions, options) {
1507
+ const document = await this.getDocument(documentId);
1237
1508
  const operations = this._buildOperations(document, actions);
1238
- return this.processOperations(driveId, documentId, operations, options);
1509
+ return this.processOperations(documentId, operations, options);
1239
1510
  }
1511
+ /**
1512
+ * @deprecated Use the {@link addAction} method instead.
1513
+ */
1240
1514
  async addDriveAction(driveId, action, options) {
1515
+ if ("synchronizationUnits" in action.input) {
1516
+ return this._legacyAddFileAction(driveId, action, options);
1517
+ }
1241
1518
  return this.addDriveActions(driveId, [action], options);
1242
1519
  }
1520
+ async _legacyAddFileAction(driveId, action, options) {
1521
+ // create document before adding it to the drive
1522
+ const document = this.getDocumentModelModule(action.input.documentType).utils.createDocument({ ...action.input.document });
1523
+ document.header.id = action.input.id;
1524
+ document.header.name = action.input.name;
1525
+ document.header.documentType = action.input.documentType;
1526
+ await this.queueDocument({ document }, { source: options?.source || { type: "local" } });
1527
+ // create updated version of the ADD_FILE action
1528
+ const newAction = {
1529
+ ...action,
1530
+ input: {
1531
+ id: action.input.id,
1532
+ documentType: document.header.documentType,
1533
+ name: action.input.name,
1534
+ parentFolder: action.input.parentFolder,
1535
+ },
1536
+ };
1537
+ return (await this.addAction(driveId, newAction, options));
1538
+ }
1539
+ /**
1540
+ * @deprecated Use the {@link addActions} method instead.
1541
+ */
1243
1542
  async addDriveActions(driveId, actions, options) {
1244
1543
  return this.queueDriveActions(driveId, actions, options);
1245
1544
  }
@@ -1260,8 +1559,8 @@ export class BaseDocumentDriveServer {
1260
1559
  }
1261
1560
  await this.addDriveAction(driveId, setSharingType({ type: "LOCAL" }));
1262
1561
  }
1263
- getSyncStatus(syncUnitId) {
1264
- return this.synchronizationManager.getSyncStatus(syncUnitId);
1562
+ getSyncStatus(documentId, scope, branch) {
1563
+ return this.synchronizationManager.getSyncStatus(documentId, scope, branch);
1265
1564
  }
1266
1565
  on(event, cb) {
1267
1566
  return this.eventEmitter.on(event, cb);
@@ -1269,9 +1568,6 @@ export class BaseDocumentDriveServer {
1269
1568
  emit(event, ...args) {
1270
1569
  return this.eventEmitter.emit(event, ...args);
1271
1570
  }
1272
- getSynchronizationUnit(driveId, syncId) {
1273
- return this.synchronizationManager.getSynchronizationUnit(driveId, syncId);
1274
- }
1275
1571
  // Add delegated methods to properly implement ISynchronizationManager
1276
1572
  updateSyncStatus(syncUnitId, status, error) {
1277
1573
  this.synchronizationManager.updateSyncStatus(syncUnitId, status, error);
@@ -1284,15 +1580,24 @@ export class BaseDocumentDriveServer {
1284
1580
  }
1285
1581
  // Add back the saveStrand method that was accidentally removed
1286
1582
  async saveStrand(strand, source) {
1583
+ const isNewDocument = !(await this.documentStorage.exists(strand.documentId));
1584
+ let result = undefined;
1585
+ if (isNewDocument) {
1586
+ result = await this.queueDocument({
1587
+ id: strand.documentId,
1588
+ documentType: strand.documentType,
1589
+ });
1590
+ }
1287
1591
  const operations = strand.operations.map((op) => ({
1288
1592
  ...op,
1289
1593
  scope: strand.scope,
1290
1594
  branch: strand.branch,
1291
1595
  }));
1292
- let result;
1293
- if (strand.documentId) {
1596
+ // if document already existed or queueDocument
1597
+ // was successful, queues the operations
1598
+ if ((!isNewDocument || result?.status === "SUCCESS") && operations.length) {
1294
1599
  try {
1295
- result = await this.queueOperations(strand.driveId, strand.documentId, operations, {
1600
+ result = await this.queueOperations(strand.documentId, operations, {
1296
1601
  source,
1297
1602
  });
1298
1603
  }
@@ -1301,25 +1606,22 @@ export class BaseDocumentDriveServer {
1301
1606
  throw error;
1302
1607
  }
1303
1608
  }
1304
- else {
1305
- try {
1306
- result = await this.queueDriveOperations(strand.driveId, operations, {
1307
- source,
1308
- });
1309
- }
1310
- catch (error) {
1311
- this.logger.error("Error queueing operations", error);
1312
- throw error;
1313
- }
1609
+ if (!result) {
1610
+ this.logger.debug(`Document ${strand.documentId} already exists`);
1611
+ return {
1612
+ status: "SUCCESS",
1613
+ document: await this.getDocument(strand.documentId),
1614
+ operations: [],
1615
+ signals: [],
1616
+ };
1314
1617
  }
1315
1618
  if (result.status === "ERROR") {
1316
- const syncUnits = strand.documentId !== ""
1317
- ? (await this.getSynchronizationUnitsIds(strand.driveId, [strand.documentId], [strand.scope], [strand.branch])).map((s) => s.syncId)
1318
- : [strand.driveId];
1319
1619
  const operationSource = this.getOperationSource(source);
1320
- for (const syncUnit of syncUnits) {
1321
- this.synchronizationManager.updateSyncStatus(syncUnit, { [operationSource]: result.status }, result.error);
1322
- }
1620
+ this.synchronizationManager.updateSyncStatus({
1621
+ documentId: strand.documentId || strand.driveId,
1622
+ scope: strand.scope,
1623
+ branch: strand.branch,
1624
+ }, { [operationSource]: result.status }, result.error);
1323
1625
  }
1324
1626
  this.eventEmitter.emit("strandUpdate", strand);
1325
1627
  return result;