document-drive 1.19.1 → 1.20.1

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 (252) hide show
  1. package/README.md +4 -0
  2. package/dist/index.d.ts +28 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +18 -0
  5. package/dist/src/cache/memory.d.ts +10 -0
  6. package/dist/src/cache/memory.d.ts.map +1 -0
  7. package/dist/src/cache/memory.js +26 -0
  8. package/dist/src/cache/redis.d.ts +14 -0
  9. package/dist/src/cache/redis.d.ts.map +1 -0
  10. package/dist/src/cache/redis.js +40 -0
  11. package/dist/src/cache/types.d.ts +7 -0
  12. package/dist/src/cache/types.d.ts.map +1 -0
  13. package/dist/src/cache/types.js +1 -0
  14. package/dist/src/drive-document-model/constants.d.ts +2 -0
  15. package/dist/src/drive-document-model/constants.d.ts.map +1 -0
  16. package/dist/src/drive-document-model/constants.js +1 -0
  17. package/dist/src/drive-document-model/gen/actions.d.ts +7 -0
  18. package/dist/src/drive-document-model/gen/actions.d.ts.map +1 -0
  19. package/dist/src/drive-document-model/gen/actions.js +2 -0
  20. package/dist/src/drive-document-model/gen/constants.d.ts +7 -0
  21. package/dist/src/drive-document-model/gen/constants.d.ts.map +1 -0
  22. package/dist/src/drive-document-model/gen/constants.js +16 -0
  23. package/dist/src/drive-document-model/gen/creators.d.ts +3 -0
  24. package/dist/src/drive-document-model/gen/creators.d.ts.map +1 -0
  25. package/dist/src/drive-document-model/gen/creators.js +2 -0
  26. package/dist/src/drive-document-model/gen/document-model.d.ts +3 -0
  27. package/dist/src/drive-document-model/gen/document-model.d.ts.map +1 -0
  28. package/dist/src/drive-document-model/gen/document-model.js +210 -0
  29. package/dist/src/drive-document-model/gen/drive/actions.d.ts +12 -0
  30. package/dist/src/drive-document-model/gen/drive/actions.d.ts.map +1 -0
  31. package/dist/src/drive-document-model/gen/drive/actions.js +1 -0
  32. package/dist/src/drive-document-model/gen/drive/creators.d.ts +11 -0
  33. package/dist/src/drive-document-model/gen/drive/creators.d.ts.map +1 -0
  34. package/dist/src/drive-document-model/gen/drive/creators.js +10 -0
  35. package/dist/src/drive-document-model/gen/drive/error.d.ts +2 -0
  36. package/dist/src/drive-document-model/gen/drive/error.d.ts.map +1 -0
  37. package/dist/src/drive-document-model/gen/drive/error.js +1 -0
  38. package/dist/src/drive-document-model/gen/drive/object.d.ts +14 -0
  39. package/dist/src/drive-document-model/gen/drive/object.d.ts.map +1 -0
  40. package/dist/src/drive-document-model/gen/drive/object.js +28 -0
  41. package/dist/src/drive-document-model/gen/drive/operations.d.ts +14 -0
  42. package/dist/src/drive-document-model/gen/drive/operations.d.ts.map +1 -0
  43. package/dist/src/drive-document-model/gen/drive/operations.js +1 -0
  44. package/dist/src/drive-document-model/gen/node/actions.d.ts +11 -0
  45. package/dist/src/drive-document-model/gen/node/actions.d.ts.map +1 -0
  46. package/dist/src/drive-document-model/gen/node/actions.js +1 -0
  47. package/dist/src/drive-document-model/gen/node/creators.d.ts +10 -0
  48. package/dist/src/drive-document-model/gen/node/creators.d.ts.map +1 -0
  49. package/dist/src/drive-document-model/gen/node/creators.js +9 -0
  50. package/dist/src/drive-document-model/gen/node/error.d.ts +2 -0
  51. package/dist/src/drive-document-model/gen/node/error.d.ts.map +1 -0
  52. package/dist/src/drive-document-model/gen/node/error.js +1 -0
  53. package/dist/src/drive-document-model/gen/node/object.d.ts +13 -0
  54. package/dist/src/drive-document-model/gen/node/object.d.ts.map +1 -0
  55. package/dist/src/drive-document-model/gen/node/object.js +25 -0
  56. package/dist/src/drive-document-model/gen/node/operations.d.ts +13 -0
  57. package/dist/src/drive-document-model/gen/node/operations.d.ts.map +1 -0
  58. package/dist/src/drive-document-model/gen/node/operations.js +1 -0
  59. package/dist/src/drive-document-model/gen/object.d.ts +21 -0
  60. package/dist/src/drive-document-model/gen/object.d.ts.map +1 -0
  61. package/dist/src/drive-document-model/gen/object.js +28 -0
  62. package/dist/src/drive-document-model/gen/reducer.d.ts +4 -0
  63. package/dist/src/drive-document-model/gen/reducer.d.ts.map +1 -0
  64. package/dist/src/drive-document-model/gen/reducer.js +74 -0
  65. package/dist/src/drive-document-model/gen/schema/types.d.ts +176 -0
  66. package/dist/src/drive-document-model/gen/schema/types.d.ts.map +1 -0
  67. package/dist/src/drive-document-model/gen/schema/types.js +1 -0
  68. package/dist/src/drive-document-model/gen/schema/zod.d.ts +87 -0
  69. package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -0
  70. package/dist/src/drive-document-model/gen/schema/zod.js +203 -0
  71. package/dist/src/drive-document-model/gen/types.d.ts +9 -0
  72. package/dist/src/drive-document-model/gen/types.d.ts.map +1 -0
  73. package/dist/src/drive-document-model/gen/types.js +1 -0
  74. package/dist/src/drive-document-model/gen/utils.d.ts +10 -0
  75. package/dist/src/drive-document-model/gen/utils.d.ts.map +1 -0
  76. package/dist/src/drive-document-model/gen/utils.js +27 -0
  77. package/dist/src/drive-document-model/index.d.ts +2 -0
  78. package/dist/src/drive-document-model/index.d.ts.map +1 -0
  79. package/dist/src/drive-document-model/index.js +1 -0
  80. package/dist/src/drive-document-model/module.d.ts +3 -0
  81. package/dist/src/drive-document-model/module.d.ts.map +1 -0
  82. package/dist/src/drive-document-model/module.js +12 -0
  83. package/dist/src/drive-document-model/src/reducers/drive.d.ts +8 -0
  84. package/dist/src/drive-document-model/src/reducers/drive.d.ts.map +1 -0
  85. package/dist/src/drive-document-model/src/reducers/drive.js +37 -0
  86. package/dist/src/drive-document-model/src/reducers/node.d.ts +8 -0
  87. package/dist/src/drive-document-model/src/reducers/node.d.ts.map +1 -0
  88. package/dist/src/drive-document-model/src/reducers/node.js +185 -0
  89. package/dist/src/drive-document-model/src/utils.d.ts +34 -0
  90. package/dist/src/drive-document-model/src/utils.d.ts.map +1 -0
  91. package/dist/src/drive-document-model/src/utils.js +146 -0
  92. package/dist/src/queue/base.d.ts +43 -0
  93. package/dist/src/queue/base.d.ts.map +1 -0
  94. package/dist/src/queue/base.js +241 -0
  95. package/dist/src/queue/redis.d.ts +28 -0
  96. package/dist/src/queue/redis.d.ts.map +1 -0
  97. package/dist/src/queue/redis.js +110 -0
  98. package/dist/src/queue/types.d.ts +55 -0
  99. package/dist/src/queue/types.d.ts.map +1 -0
  100. package/dist/src/queue/types.js +6 -0
  101. package/dist/src/read-mode/errors.d.ts +12 -0
  102. package/dist/src/read-mode/errors.d.ts.map +1 -0
  103. package/dist/src/read-mode/errors.js +17 -0
  104. package/dist/src/read-mode/server.d.ts +4 -0
  105. package/dist/src/read-mode/server.d.ts.map +1 -0
  106. package/dist/src/read-mode/server.js +78 -0
  107. package/dist/src/read-mode/service.d.ts +18 -0
  108. package/dist/src/read-mode/service.d.ts.map +1 -0
  109. package/dist/src/read-mode/service.js +112 -0
  110. package/dist/src/read-mode/types.d.ts +35 -0
  111. package/dist/src/read-mode/types.d.ts.map +1 -0
  112. package/dist/src/read-mode/types.js +1 -0
  113. package/dist/src/server/base-server.d.ts +112 -0
  114. package/dist/src/server/base-server.d.ts.map +1 -0
  115. package/dist/src/server/base-server.js +1280 -0
  116. package/dist/src/server/builder.d.ts +30 -0
  117. package/dist/src/server/builder.d.ts.map +1 -0
  118. package/dist/src/server/builder.js +89 -0
  119. package/dist/src/server/constants.d.ts +2 -0
  120. package/dist/src/server/constants.d.ts.map +1 -0
  121. package/dist/src/server/constants.js +1 -0
  122. package/dist/src/server/error.d.ts +30 -0
  123. package/dist/src/server/error.d.ts.map +1 -0
  124. package/dist/src/server/error.js +47 -0
  125. package/dist/src/server/event-emitter.d.ts +8 -0
  126. package/dist/src/server/event-emitter.d.ts.map +1 -0
  127. package/dist/src/server/event-emitter.js +10 -0
  128. package/dist/src/server/listener/index.d.ts +2 -0
  129. package/dist/src/server/listener/index.d.ts.map +1 -0
  130. package/dist/src/server/listener/index.js +1 -0
  131. package/dist/src/server/listener/listener-manager.d.ts +27 -0
  132. package/dist/src/server/listener/listener-manager.d.ts.map +1 -0
  133. package/dist/src/server/listener/listener-manager.js +401 -0
  134. package/dist/src/server/listener/transmitter/factory.d.ts +8 -0
  135. package/dist/src/server/listener/transmitter/factory.d.ts.map +1 -0
  136. package/dist/src/server/listener/transmitter/factory.js +25 -0
  137. package/dist/src/server/listener/transmitter/internal.d.ts +34 -0
  138. package/dist/src/server/listener/transmitter/internal.d.ts.map +1 -0
  139. package/dist/src/server/listener/transmitter/internal.js +87 -0
  140. package/dist/src/server/listener/transmitter/pull-responder.d.ts +38 -0
  141. package/dist/src/server/listener/transmitter/pull-responder.d.ts.map +1 -0
  142. package/dist/src/server/listener/transmitter/pull-responder.js +256 -0
  143. package/dist/src/server/listener/transmitter/switchboard-push.d.ts +9 -0
  144. package/dist/src/server/listener/transmitter/switchboard-push.d.ts.map +1 -0
  145. package/dist/src/server/listener/transmitter/switchboard-push.js +77 -0
  146. package/dist/src/server/listener/transmitter/types.d.ts +20 -0
  147. package/dist/src/server/listener/transmitter/types.d.ts.map +1 -0
  148. package/dist/src/server/listener/transmitter/types.js +1 -0
  149. package/dist/src/server/listener/util.d.ts +2 -0
  150. package/dist/src/server/listener/util.d.ts.map +1 -0
  151. package/dist/src/server/listener/util.js +22 -0
  152. package/dist/src/server/sync-manager.d.ts +30 -0
  153. package/dist/src/server/sync-manager.d.ts.map +1 -0
  154. package/dist/src/server/sync-manager.js +287 -0
  155. package/dist/src/server/types.d.ts +308 -0
  156. package/dist/src/server/types.d.ts.map +1 -0
  157. package/dist/src/server/types.js +12 -0
  158. package/dist/src/server/utils.d.ts +8 -0
  159. package/dist/src/server/utils.d.ts.map +1 -0
  160. package/dist/src/server/utils.js +47 -0
  161. package/dist/src/storage/base.d.ts +36 -0
  162. package/dist/src/storage/base.d.ts.map +1 -0
  163. package/dist/src/storage/base.js +4 -0
  164. package/dist/src/storage/browser.d.ts +36 -0
  165. package/dist/src/storage/browser.d.ts.map +1 -0
  166. package/dist/src/storage/browser.js +155 -0
  167. package/dist/src/storage/filesystem.d.ts +33 -0
  168. package/dist/src/storage/filesystem.d.ts.map +1 -0
  169. package/dist/src/storage/filesystem.js +197 -0
  170. package/dist/src/storage/memory.d.ts +33 -0
  171. package/dist/src/storage/memory.d.ts.map +1 -0
  172. package/dist/src/storage/memory.js +139 -0
  173. package/dist/src/storage/prisma.d.ts +67 -0
  174. package/dist/src/storage/prisma.d.ts.map +1 -0
  175. package/dist/src/storage/prisma.js +445 -0
  176. package/dist/src/storage/sequelize.d.ts +32 -0
  177. package/dist/src/storage/sequelize.d.ts.map +1 -0
  178. package/dist/src/storage/sequelize.js +373 -0
  179. package/dist/src/storage/types.d.ts +43 -0
  180. package/dist/src/storage/types.d.ts.map +1 -0
  181. package/dist/src/storage/types.js +1 -0
  182. package/dist/src/utils/default-drives-manager.d.ts +29 -0
  183. package/dist/src/utils/default-drives-manager.d.ts.map +1 -0
  184. package/dist/src/utils/default-drives-manager.js +208 -0
  185. package/dist/src/utils/graphql.d.ts +34 -0
  186. package/dist/src/utils/graphql.d.ts.map +1 -0
  187. package/dist/src/utils/graphql.js +183 -0
  188. package/dist/src/utils/logger.d.ts +27 -0
  189. package/dist/src/utils/logger.d.ts.map +1 -0
  190. package/dist/src/utils/logger.js +105 -0
  191. package/dist/src/utils/migrations.d.ts +4 -0
  192. package/dist/src/utils/migrations.d.ts.map +1 -0
  193. package/dist/src/utils/migrations.js +41 -0
  194. package/dist/src/utils/misc.d.ts +11 -0
  195. package/dist/src/utils/misc.d.ts.map +1 -0
  196. package/dist/src/utils/misc.js +43 -0
  197. package/dist/src/utils/run-asap.d.ts +12 -0
  198. package/dist/src/utils/run-asap.d.ts.map +1 -0
  199. package/dist/src/utils/run-asap.js +131 -0
  200. package/dist/test/document-helpers/utils.d.ts +8 -0
  201. package/dist/test/document-helpers/utils.d.ts.map +1 -0
  202. package/dist/test/document-helpers/utils.js +21 -0
  203. package/dist/test/utils.d.ts +48 -0
  204. package/dist/test/utils.d.ts.map +1 -0
  205. package/dist/test/utils.js +132 -0
  206. package/dist/test/vitest-setup.d.ts +2 -0
  207. package/dist/test/vitest-setup.d.ts.map +1 -0
  208. package/dist/test/vitest-setup.js +4 -0
  209. package/dist/tsconfig.tsbuildinfo +1 -0
  210. package/dist/vitest.config.d.ts +3 -0
  211. package/dist/vitest.config.d.ts.map +1 -0
  212. package/dist/vitest.config.js +20 -0
  213. package/package.json +20 -38
  214. package/src/cache/index.ts +0 -2
  215. package/src/cache/memory.ts +0 -33
  216. package/src/cache/redis.ts +0 -56
  217. package/src/cache/types.ts +0 -9
  218. package/src/index.ts +0 -4
  219. package/src/queue/base.ts +0 -320
  220. package/src/queue/index.ts +0 -2
  221. package/src/queue/redis.ts +0 -144
  222. package/src/queue/types.ts +0 -79
  223. package/src/read-mode/errors.ts +0 -19
  224. package/src/read-mode/index.ts +0 -125
  225. package/src/read-mode/service.ts +0 -207
  226. package/src/read-mode/types.ts +0 -108
  227. package/src/server/error.ts +0 -70
  228. package/src/server/index.ts +0 -2444
  229. package/src/server/listener/index.ts +0 -2
  230. package/src/server/listener/manager.ts +0 -652
  231. package/src/server/listener/transmitter/index.ts +0 -4
  232. package/src/server/listener/transmitter/internal.ts +0 -143
  233. package/src/server/listener/transmitter/pull-responder.ts +0 -462
  234. package/src/server/listener/transmitter/switchboard-push.ts +0 -125
  235. package/src/server/listener/transmitter/types.ts +0 -27
  236. package/src/server/types.ts +0 -596
  237. package/src/server/utils.ts +0 -82
  238. package/src/storage/base.ts +0 -81
  239. package/src/storage/browser.ts +0 -238
  240. package/src/storage/filesystem.ts +0 -297
  241. package/src/storage/index.ts +0 -2
  242. package/src/storage/memory.ts +0 -211
  243. package/src/storage/prisma.ts +0 -653
  244. package/src/storage/sequelize.ts +0 -498
  245. package/src/storage/types.ts +0 -97
  246. package/src/utils/default-drives-manager.ts +0 -341
  247. package/src/utils/document-helpers.ts +0 -21
  248. package/src/utils/graphql.ts +0 -301
  249. package/src/utils/index.ts +0 -90
  250. package/src/utils/logger.ts +0 -38
  251. package/src/utils/migrations.ts +0 -58
  252. package/src/utils/run-asap.ts +0 -156
@@ -1,653 +0,0 @@
1
- import { Prisma, PrismaClient } from "@prisma/client";
2
- import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
3
- import {
4
- DocumentDriveAction,
5
- DocumentDriveLocalState,
6
- DocumentDriveState,
7
- } from "document-model-libs/document-drive";
8
- import type {
9
- Action,
10
- AttachmentInput,
11
- BaseAction,
12
- Document,
13
- DocumentHeader,
14
- DocumentOperations,
15
- ExtendedState,
16
- FileRegistry,
17
- Operation,
18
- OperationScope,
19
- State,
20
- } from "document-model/document";
21
- import { IBackOffOptions, backOff } from "exponential-backoff";
22
- import { ConflictOperationError, DriveNotFoundError } from "../server/error";
23
- import type { SynchronizationUnitQuery } from "../server/types";
24
- import { logger } from "../utils/logger";
25
- import {
26
- DocumentDriveStorage,
27
- DocumentStorage,
28
- IDriveStorage,
29
- IStorageDelegate,
30
- } from "./types";
31
-
32
- type Transaction =
33
- | Omit<
34
- PrismaClient<Prisma.PrismaClientOptions, never>,
35
- "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"
36
- >
37
- | ExtendedPrismaClient;
38
-
39
- function storageToOperation(
40
- op: Prisma.$OperationPayload["scalars"] & {
41
- attachments?: AttachmentInput[];
42
- },
43
- ): Operation {
44
- const operation: Operation = {
45
- id: op.opId || undefined,
46
- skip: op.skip,
47
- hash: op.hash,
48
- index: op.index,
49
- timestamp: new Date(op.timestamp).toISOString(),
50
- input: JSON.parse(op.input),
51
- type: op.type,
52
- scope: op.scope as OperationScope,
53
- resultingState: op.resultingState
54
- ? op.resultingState.toString()
55
- : undefined,
56
- attachments: op.attachments,
57
- };
58
- if (op.context) {
59
- operation.context = op.context as Prisma.JsonObject;
60
- }
61
- return operation;
62
- }
63
-
64
- export type PrismaStorageOptions = {
65
- transactionRetryBackoff?: IBackOffOptions;
66
- };
67
-
68
- function getRetryTransactionsClient<T extends PrismaClient>(
69
- prisma: T,
70
- backOffOptions?: Partial<IBackOffOptions>,
71
- ) {
72
- return prisma.$extends({
73
- client: {
74
- $transaction: (...args: Parameters<T["$transaction"]>) => {
75
- // eslint-disable-next-line prefer-spread
76
- return backOff(() => prisma.$transaction.apply(prisma, args), {
77
- retry: (e) => {
78
- const code = (e as { code: string }).code;
79
- // Retry the transaction only if the error was due to a write conflict or deadlock
80
- // See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034
81
- if (code !== "P2034") {
82
- logger.error("TRANSACTION ERROR", e);
83
- }
84
- return code === "P2034";
85
- },
86
- ...backOffOptions,
87
- });
88
- },
89
- },
90
- });
91
- }
92
-
93
- type ExtendedPrismaClient = ReturnType<
94
- typeof getRetryTransactionsClient<PrismaClient>
95
- >;
96
-
97
- export class PrismaStorage implements IDriveStorage {
98
- private db: ExtendedPrismaClient;
99
- private delegate: IStorageDelegate | undefined;
100
-
101
- constructor(db: PrismaClient, options?: PrismaStorageOptions) {
102
- const backOffOptions = options?.transactionRetryBackoff;
103
- this.db = getRetryTransactionsClient(db, {
104
- ...backOffOptions,
105
- jitter: backOffOptions?.jitter ?? "full",
106
- });
107
- }
108
-
109
- setStorageDelegate(delegate: IStorageDelegate): void {
110
- this.delegate = delegate;
111
- }
112
-
113
- async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
114
- // drive for all drive documents
115
- await this.createDocument("drives", id, drive as DocumentStorage);
116
- await this.db.drive.upsert({
117
- where: {
118
- slug: drive.initialState.state.global.slug ?? id,
119
- },
120
- create: {
121
- id: id,
122
- slug: drive.initialState.state.global.slug ?? id,
123
- },
124
- update: {
125
- id,
126
- },
127
- });
128
- }
129
- async addDriveOperations(
130
- id: string,
131
- operations: Operation[],
132
- header: DocumentHeader,
133
- ): Promise<void> {
134
- await this.addDocumentOperations("drives", id, operations, header);
135
- }
136
-
137
- async addDriveOperationsWithTransaction(
138
- drive: string,
139
- callback: (document: DocumentDriveStorage) => Promise<{
140
- operations: Operation<DocumentDriveAction | BaseAction>[];
141
- header: DocumentHeader;
142
- }>,
143
- ) {
144
- return this.addDocumentOperationsWithTransaction(
145
- "drives",
146
- drive,
147
- (document) => callback(document as DocumentDriveStorage),
148
- );
149
- }
150
-
151
- async createDocument(
152
- drive: string,
153
- id: string,
154
- document: DocumentStorage,
155
- ): Promise<void> {
156
- await this.db.document.upsert({
157
- where: {
158
- id_driveId: {
159
- id,
160
- driveId: drive,
161
- },
162
- },
163
- update: {},
164
- create: {
165
- name: document.name,
166
- documentType: document.documentType,
167
- driveId: drive,
168
- initialState: JSON.stringify(document.initialState),
169
- lastModified: document.lastModified,
170
- revision: JSON.stringify(document.revision),
171
- id,
172
- },
173
- });
174
- }
175
-
176
- private async _addDocumentOperations(
177
- tx: Transaction,
178
- drive: string,
179
- id: string,
180
- operations: Operation[],
181
- header: DocumentHeader,
182
- ): Promise<void> {
183
- try {
184
- await tx.operation.createMany({
185
- data: operations.map((op) => ({
186
- driveId: drive,
187
- documentId: id,
188
- hash: op.hash,
189
- index: op.index,
190
- input: JSON.stringify(op.input),
191
- timestamp: op.timestamp,
192
- type: op.type,
193
- scope: op.scope,
194
- branch: "main",
195
- opId: op.id,
196
- skip: op.skip,
197
- context: op.context,
198
- resultingState: op.resultingState
199
- ? Buffer.from(JSON.stringify(op.resultingState))
200
- : undefined,
201
- })),
202
- });
203
-
204
- await tx.document.updateMany({
205
- where: {
206
- id,
207
- driveId: drive,
208
- },
209
- data: {
210
- lastModified: header.lastModified,
211
- revision: JSON.stringify(header.revision),
212
- },
213
- });
214
-
215
- await Promise.all(
216
- operations
217
- .filter((o) => o.attachments?.length)
218
- .map((op) => {
219
- return tx.operation.update({
220
- where: {
221
- unique_operation: {
222
- driveId: drive,
223
- documentId: id,
224
- index: op.index,
225
- scope: op.scope,
226
- branch: "main",
227
- },
228
- },
229
- data: {
230
- attachments: {
231
- createMany: {
232
- data: op.attachments ?? [],
233
- },
234
- },
235
- },
236
- });
237
- }),
238
- );
239
- } catch (e) {
240
- // P2002: Unique constraint failed
241
- // Operation with existing index
242
- if (e instanceof PrismaClientKnownRequestError && e.code === "P2002") {
243
- const existingOperation = await this.db.operation.findFirst({
244
- where: {
245
- AND: operations.map((op) => ({
246
- driveId: drive,
247
- documentId: id,
248
- scope: op.scope,
249
- branch: "main",
250
- index: op.index,
251
- })),
252
- },
253
- });
254
-
255
- const conflictOp = operations.find(
256
- (op) =>
257
- existingOperation?.index === op.index &&
258
- existingOperation.scope === op.scope,
259
- );
260
-
261
- if (!existingOperation || !conflictOp) {
262
- console.error(e);
263
- throw e;
264
- } else {
265
- throw new ConflictOperationError(
266
- storageToOperation(existingOperation),
267
- conflictOp,
268
- );
269
- }
270
- } else {
271
- throw e;
272
- }
273
- }
274
- }
275
-
276
- async addDocumentOperationsWithTransaction(
277
- drive: string,
278
- id: string,
279
- callback: (document: DocumentStorage) => Promise<{
280
- operations: Operation[];
281
- header: DocumentHeader;
282
- newState?: State<any, any> | undefined;
283
- }>,
284
- ) {
285
- let result: {
286
- operations: Operation[];
287
- header: DocumentHeader;
288
- newState?: State<any, any> | undefined;
289
- } | null = null;
290
-
291
- await this.db.$transaction(
292
- async (tx) => {
293
- const document = await this.getDocument(drive, id, tx);
294
- if (!document) {
295
- throw new Error(`Document with id ${id} not found`);
296
- }
297
- result = await callback(document);
298
-
299
- const { operations, header, newState } = result;
300
- return this._addDocumentOperations(tx, drive, id, operations, header);
301
- },
302
- { isolationLevel: "Serializable", maxWait: 10000, timeout: 20000 },
303
- );
304
-
305
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
306
- if (!result) {
307
- throw new Error("No operations were provided");
308
- }
309
-
310
- return result;
311
- }
312
-
313
- async addDocumentOperations(
314
- drive: string,
315
- id: string,
316
- operations: Operation[],
317
- header: DocumentHeader,
318
- ): Promise<void> {
319
- return this._addDocumentOperations(this.db, drive, id, operations, header);
320
- }
321
-
322
- async getDocuments(drive: string) {
323
- const docs = await this.db.document.findMany({
324
- select: {
325
- id: true,
326
- },
327
- where: {
328
- AND: {
329
- driveId: drive,
330
- NOT: {
331
- id: "drives",
332
- },
333
- },
334
- },
335
- });
336
-
337
- return docs.map((doc) => doc.id);
338
- }
339
-
340
- async checkDocumentExists(driveId: string, id: string) {
341
- const count = await this.db.document.count({
342
- where: {
343
- id: id,
344
- driveId: driveId,
345
- },
346
- });
347
- return count > 0;
348
- }
349
-
350
- async getDocument(driveId: string, id: string, tx?: Transaction) {
351
- const prisma = tx ?? this.db;
352
- const result = await prisma.document.findUnique({
353
- where: {
354
- id_driveId: {
355
- driveId,
356
- id,
357
- },
358
- },
359
- });
360
-
361
- if (result === null) {
362
- throw new Error(`Document with id ${id} not found`);
363
- }
364
-
365
- const cachedOperations = (await this.delegate?.getCachedOperations(
366
- driveId,
367
- id,
368
- )) ?? {
369
- global: [],
370
- local: [],
371
- };
372
- const scopeIndex = Object.keys(cachedOperations).reduceRight<
373
- Record<OperationScope, number>
374
- >(
375
- (acc, value) => {
376
- const scope = value as OperationScope;
377
- const lastIndex = cachedOperations[scope].at(-1)?.index ?? -1;
378
- acc[scope] = lastIndex;
379
- return acc;
380
- },
381
- { global: -1, local: -1 },
382
- );
383
-
384
- const conditions = Object.entries(scopeIndex).map(
385
- ([scope, index]) => `("scope" = '${scope}' AND "index" > ${index})`,
386
- );
387
- conditions.push(
388
- `("scope" NOT IN (${Object.keys(cachedOperations)
389
- .map((s) => `'${s}'`)
390
- .join(", ")}))`,
391
- );
392
-
393
- // retrieves operations with resulting state
394
- // for the last operation of each scope
395
- // TODO prevent SQL injection
396
- const queryOperations = await prisma.$queryRawUnsafe<
397
- Prisma.$OperationPayload["scalars"][]
398
- >(
399
- `WITH ranked_operations AS (
400
- SELECT
401
- *,
402
- ROW_NUMBER() OVER (PARTITION BY scope ORDER BY index DESC) AS rn
403
- FROM "Operation"
404
- )
405
- SELECT
406
- "id",
407
- "opId",
408
- "scope",
409
- "branch",
410
- "index",
411
- "skip",
412
- "hash",
413
- "timestamp",
414
- "input",
415
- "type",
416
- "context",
417
- CASE
418
- WHEN rn = 1 THEN "resultingState"
419
- ELSE NULL
420
- END AS "resultingState"
421
- FROM ranked_operations
422
- WHERE "driveId" = $1 AND "documentId" = $2
423
- AND (${conditions.join(" OR ")})
424
- ORDER BY scope, index;
425
- `,
426
- driveId,
427
- id,
428
- );
429
- const operationIds = queryOperations.map((o) => o.id);
430
- const attachments = await prisma.attachment.findMany({
431
- where: {
432
- operationId: {
433
- in: operationIds,
434
- },
435
- },
436
- });
437
-
438
- // TODO add attachments from cached operations
439
- const fileRegistry: FileRegistry = {};
440
-
441
- const operationsByScope = queryOperations.reduce<
442
- DocumentOperations<Action>
443
- >((acc, operation) => {
444
- const scope = operation.scope as OperationScope;
445
- if (!acc[scope]) {
446
- acc[scope] = [];
447
- }
448
- const result = storageToOperation(operation);
449
- result.attachments = attachments.filter(
450
- (a) => a.operationId === operation.id,
451
- );
452
- result.attachments.forEach(({ hash, ...file }) => {
453
- fileRegistry[hash] = file;
454
- });
455
- acc[scope].push(result);
456
- return acc;
457
- }, cachedOperations);
458
-
459
- const dbDoc = result;
460
- const doc: Document = {
461
- created: dbDoc.created.toISOString(),
462
- name: dbDoc.name ? dbDoc.name : "",
463
- documentType: dbDoc.documentType,
464
- initialState: JSON.parse(dbDoc.initialState) as ExtendedState<
465
- DocumentDriveState,
466
- DocumentDriveLocalState
467
- >,
468
- // @ts-expect-error TODO: fix as this should not be undefined
469
- state: undefined,
470
- lastModified: new Date(dbDoc.lastModified).toISOString(),
471
- operations: operationsByScope,
472
- clipboard: [],
473
- revision: JSON.parse(dbDoc.revision) as Record<OperationScope, number>,
474
- attachments: {},
475
- };
476
- return doc;
477
- }
478
-
479
- async deleteDocument(drive: string, id: string) {
480
- try {
481
- await this.db.document.deleteMany({
482
- where: {
483
- driveId: drive,
484
- id: id,
485
- },
486
- });
487
- } catch (e: unknown) {
488
- const prismaError = e as { code?: string; message?: string };
489
- // Ignore Error: P2025: An operation failed because it depends on one or more records that were required but not found.
490
- if (
491
- (prismaError.code && prismaError.code === "P2025") ||
492
- prismaError.message?.includes(
493
- "An operation failed because it depends on one or more records that were required but not found.",
494
- )
495
- ) {
496
- return;
497
- }
498
-
499
- throw e;
500
- }
501
- }
502
-
503
- async getDrives() {
504
- return this.getDocuments("drives");
505
- }
506
-
507
- async getDrive(id: string) {
508
- try {
509
- const doc = await this.getDocument("drives", id);
510
- return doc as DocumentDriveStorage;
511
- } catch (e) {
512
- logger.error(e);
513
- throw new DriveNotFoundError(id);
514
- }
515
- }
516
-
517
- async getDriveBySlug(slug: string) {
518
- const driveEntity = await this.db.drive.findFirst({
519
- where: {
520
- slug,
521
- },
522
- });
523
-
524
- if (!driveEntity) {
525
- throw new Error(`Drive with slug ${slug} not found`);
526
- }
527
-
528
- return this.getDrive(driveEntity.id);
529
- }
530
-
531
- async deleteDrive(id: string) {
532
- // delete drive and associated slug
533
- await this.db.drive.deleteMany({
534
- where: {
535
- id,
536
- },
537
- });
538
-
539
- // delete drive document and its operations
540
- await this.deleteDocument("drives", id);
541
-
542
- // deletes all documents of the drive
543
- await this.db.document.deleteMany({
544
- where: {
545
- driveId: id,
546
- },
547
- });
548
- }
549
-
550
- async getOperationResultingState(
551
- driveId: string,
552
- documentId: string,
553
- index: number,
554
- scope: string,
555
- branch: string,
556
- ): Promise<unknown> {
557
- const operation = await this.db.operation.findUnique({
558
- where: {
559
- unique_operation: {
560
- driveId,
561
- documentId,
562
- index,
563
- scope,
564
- branch,
565
- },
566
- },
567
- });
568
- return operation?.resultingState?.toString();
569
- }
570
-
571
- getDriveOperationResultingState(
572
- drive: string,
573
- index: number,
574
- scope: string,
575
- branch: string,
576
- ): Promise<unknown> {
577
- return this.getOperationResultingState(
578
- "drives",
579
- drive,
580
- index,
581
- scope,
582
- branch,
583
- );
584
- }
585
-
586
- async getSynchronizationUnitsRevision(
587
- units: SynchronizationUnitQuery[],
588
- ): Promise<
589
- {
590
- driveId: string;
591
- documentId: string;
592
- scope: string;
593
- branch: string;
594
- lastUpdated: string;
595
- revision: number;
596
- }[]
597
- > {
598
- // TODO add branch condition
599
- const whereClauses = units
600
- .map((_, index) => {
601
- return `("driveId" = $${index * 3 + 1} AND "documentId" = $${index * 3 + 2} AND "scope" = $${index * 3 + 3})`;
602
- })
603
- .join(" OR ");
604
-
605
- const query = `
606
- SELECT "driveId", "documentId", "scope", "branch", MAX("timestamp") as "lastUpdated", MAX("index") as revision FROM "Operation"
607
- WHERE ${whereClauses}
608
- GROUP BY "driveId", "documentId", "scope", "branch"
609
- `;
610
-
611
- const params = units
612
- .map((unit) => [
613
- unit.documentId ? unit.driveId : "drives",
614
- unit.documentId || unit.driveId,
615
- unit.scope,
616
- ])
617
- .flat();
618
- const results = await this.db.$queryRawUnsafe<
619
- {
620
- driveId: string;
621
- documentId: string;
622
- lastUpdated: string;
623
- scope: OperationScope;
624
- branch: string;
625
- revision: number;
626
- }[]
627
- >(query, ...params);
628
- return results.map((row) => ({
629
- ...row,
630
- driveId: row.driveId === "drives" ? row.documentId : row.driveId,
631
- documentId: row.driveId === "drives" ? "" : row.documentId,
632
- lastUpdated: new Date(row.lastUpdated).toISOString(),
633
- }));
634
- }
635
-
636
- // migrates all stored operations from legacy signature to signatures array
637
- async migrateOperationSignatures() {
638
- const count = await this.db.$executeRaw`
639
- UPDATE "Operation"
640
- SET context = jsonb_set(
641
- context #- '{signer,signature}', -- Remove the old 'signature' field
642
- '{signer,signatures}', -- Path to the new 'signatures' field
643
- CASE
644
- WHEN context->'signer'->>'signature' = '' THEN '[]'::jsonb
645
- ELSE to_jsonb(array[context->'signer'->>'signature'])
646
- END
647
- )
648
- WHERE context->'signer' ? 'signature' -- Check if the 'signature' key exists
649
- `;
650
- logger.info(`Migrated ${count} operations`);
651
- return;
652
- }
653
- }