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.
- package/README.md +4 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/src/cache/memory.d.ts +10 -0
- package/dist/src/cache/memory.d.ts.map +1 -0
- package/dist/src/cache/memory.js +26 -0
- package/dist/src/cache/redis.d.ts +14 -0
- package/dist/src/cache/redis.d.ts.map +1 -0
- package/dist/src/cache/redis.js +40 -0
- package/dist/src/cache/types.d.ts +7 -0
- package/dist/src/cache/types.d.ts.map +1 -0
- package/dist/src/cache/types.js +1 -0
- package/dist/src/drive-document-model/constants.d.ts +2 -0
- package/dist/src/drive-document-model/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/constants.js +1 -0
- package/dist/src/drive-document-model/gen/actions.d.ts +7 -0
- package/dist/src/drive-document-model/gen/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/actions.js +2 -0
- package/dist/src/drive-document-model/gen/constants.d.ts +7 -0
- package/dist/src/drive-document-model/gen/constants.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/constants.js +16 -0
- package/dist/src/drive-document-model/gen/creators.d.ts +3 -0
- package/dist/src/drive-document-model/gen/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/creators.js +2 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts +3 -0
- package/dist/src/drive-document-model/gen/document-model.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/document-model.js +210 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts +12 -0
- package/dist/src/drive-document-model/gen/drive/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/actions.js +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts +11 -0
- package/dist/src/drive-document-model/gen/drive/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/creators.js +10 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/drive/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/error.js +1 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/object.js +28 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts +14 -0
- package/dist/src/drive-document-model/gen/drive/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/drive/operations.js +1 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts +11 -0
- package/dist/src/drive-document-model/gen/node/actions.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/actions.js +1 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts +10 -0
- package/dist/src/drive-document-model/gen/node/creators.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/creators.js +9 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts +2 -0
- package/dist/src/drive-document-model/gen/node/error.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/error.js +1 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/object.js +25 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts +13 -0
- package/dist/src/drive-document-model/gen/node/operations.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/node/operations.js +1 -0
- package/dist/src/drive-document-model/gen/object.d.ts +21 -0
- package/dist/src/drive-document-model/gen/object.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/object.js +28 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts +4 -0
- package/dist/src/drive-document-model/gen/reducer.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/reducer.js +74 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts +176 -0
- package/dist/src/drive-document-model/gen/schema/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/types.js +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts +87 -0
- package/dist/src/drive-document-model/gen/schema/zod.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/schema/zod.js +203 -0
- package/dist/src/drive-document-model/gen/types.d.ts +9 -0
- package/dist/src/drive-document-model/gen/types.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/types.js +1 -0
- package/dist/src/drive-document-model/gen/utils.d.ts +10 -0
- package/dist/src/drive-document-model/gen/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/gen/utils.js +27 -0
- package/dist/src/drive-document-model/index.d.ts +2 -0
- package/dist/src/drive-document-model/index.d.ts.map +1 -0
- package/dist/src/drive-document-model/index.js +1 -0
- package/dist/src/drive-document-model/module.d.ts +3 -0
- package/dist/src/drive-document-model/module.d.ts.map +1 -0
- package/dist/src/drive-document-model/module.js +12 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/drive.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/drive.js +37 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts +8 -0
- package/dist/src/drive-document-model/src/reducers/node.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/reducers/node.js +185 -0
- package/dist/src/drive-document-model/src/utils.d.ts +34 -0
- package/dist/src/drive-document-model/src/utils.d.ts.map +1 -0
- package/dist/src/drive-document-model/src/utils.js +146 -0
- package/dist/src/queue/base.d.ts +43 -0
- package/dist/src/queue/base.d.ts.map +1 -0
- package/dist/src/queue/base.js +241 -0
- package/dist/src/queue/redis.d.ts +28 -0
- package/dist/src/queue/redis.d.ts.map +1 -0
- package/dist/src/queue/redis.js +110 -0
- package/dist/src/queue/types.d.ts +55 -0
- package/dist/src/queue/types.d.ts.map +1 -0
- package/dist/src/queue/types.js +6 -0
- package/dist/src/read-mode/errors.d.ts +12 -0
- package/dist/src/read-mode/errors.d.ts.map +1 -0
- package/dist/src/read-mode/errors.js +17 -0
- package/dist/src/read-mode/server.d.ts +4 -0
- package/dist/src/read-mode/server.d.ts.map +1 -0
- package/dist/src/read-mode/server.js +78 -0
- package/dist/src/read-mode/service.d.ts +18 -0
- package/dist/src/read-mode/service.d.ts.map +1 -0
- package/dist/src/read-mode/service.js +112 -0
- package/dist/src/read-mode/types.d.ts +35 -0
- package/dist/src/read-mode/types.d.ts.map +1 -0
- package/dist/src/read-mode/types.js +1 -0
- package/dist/src/server/base-server.d.ts +112 -0
- package/dist/src/server/base-server.d.ts.map +1 -0
- package/dist/src/server/base-server.js +1280 -0
- package/dist/src/server/builder.d.ts +30 -0
- package/dist/src/server/builder.d.ts.map +1 -0
- package/dist/src/server/builder.js +89 -0
- package/dist/src/server/constants.d.ts +2 -0
- package/dist/src/server/constants.d.ts.map +1 -0
- package/dist/src/server/constants.js +1 -0
- package/dist/src/server/error.d.ts +30 -0
- package/dist/src/server/error.d.ts.map +1 -0
- package/dist/src/server/error.js +47 -0
- package/dist/src/server/event-emitter.d.ts +8 -0
- package/dist/src/server/event-emitter.d.ts.map +1 -0
- package/dist/src/server/event-emitter.js +10 -0
- package/dist/src/server/listener/index.d.ts +2 -0
- package/dist/src/server/listener/index.d.ts.map +1 -0
- package/dist/src/server/listener/index.js +1 -0
- package/dist/src/server/listener/listener-manager.d.ts +27 -0
- package/dist/src/server/listener/listener-manager.d.ts.map +1 -0
- package/dist/src/server/listener/listener-manager.js +401 -0
- package/dist/src/server/listener/transmitter/factory.d.ts +8 -0
- package/dist/src/server/listener/transmitter/factory.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/factory.js +25 -0
- package/dist/src/server/listener/transmitter/internal.d.ts +34 -0
- package/dist/src/server/listener/transmitter/internal.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/internal.js +87 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts +38 -0
- package/dist/src/server/listener/transmitter/pull-responder.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/pull-responder.js +256 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts +9 -0
- package/dist/src/server/listener/transmitter/switchboard-push.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/switchboard-push.js +77 -0
- package/dist/src/server/listener/transmitter/types.d.ts +20 -0
- package/dist/src/server/listener/transmitter/types.d.ts.map +1 -0
- package/dist/src/server/listener/transmitter/types.js +1 -0
- package/dist/src/server/listener/util.d.ts +2 -0
- package/dist/src/server/listener/util.d.ts.map +1 -0
- package/dist/src/server/listener/util.js +22 -0
- package/dist/src/server/sync-manager.d.ts +30 -0
- package/dist/src/server/sync-manager.d.ts.map +1 -0
- package/dist/src/server/sync-manager.js +287 -0
- package/dist/src/server/types.d.ts +308 -0
- package/dist/src/server/types.d.ts.map +1 -0
- package/dist/src/server/types.js +12 -0
- package/dist/src/server/utils.d.ts +8 -0
- package/dist/src/server/utils.d.ts.map +1 -0
- package/dist/src/server/utils.js +47 -0
- package/dist/src/storage/base.d.ts +36 -0
- package/dist/src/storage/base.d.ts.map +1 -0
- package/dist/src/storage/base.js +4 -0
- package/dist/src/storage/browser.d.ts +36 -0
- package/dist/src/storage/browser.d.ts.map +1 -0
- package/dist/src/storage/browser.js +155 -0
- package/dist/src/storage/filesystem.d.ts +33 -0
- package/dist/src/storage/filesystem.d.ts.map +1 -0
- package/dist/src/storage/filesystem.js +197 -0
- package/dist/src/storage/memory.d.ts +33 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +139 -0
- package/dist/src/storage/prisma.d.ts +67 -0
- package/dist/src/storage/prisma.d.ts.map +1 -0
- package/dist/src/storage/prisma.js +445 -0
- package/dist/src/storage/sequelize.d.ts +32 -0
- package/dist/src/storage/sequelize.d.ts.map +1 -0
- package/dist/src/storage/sequelize.js +373 -0
- package/dist/src/storage/types.d.ts +43 -0
- package/dist/src/storage/types.d.ts.map +1 -0
- package/dist/src/storage/types.js +1 -0
- package/dist/src/utils/default-drives-manager.d.ts +29 -0
- package/dist/src/utils/default-drives-manager.d.ts.map +1 -0
- package/dist/src/utils/default-drives-manager.js +208 -0
- package/dist/src/utils/graphql.d.ts +34 -0
- package/dist/src/utils/graphql.d.ts.map +1 -0
- package/dist/src/utils/graphql.js +183 -0
- package/dist/src/utils/logger.d.ts +27 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +105 -0
- package/dist/src/utils/migrations.d.ts +4 -0
- package/dist/src/utils/migrations.d.ts.map +1 -0
- package/dist/src/utils/migrations.js +41 -0
- package/dist/src/utils/misc.d.ts +11 -0
- package/dist/src/utils/misc.d.ts.map +1 -0
- package/dist/src/utils/misc.js +43 -0
- package/dist/src/utils/run-asap.d.ts +12 -0
- package/dist/src/utils/run-asap.d.ts.map +1 -0
- package/dist/src/utils/run-asap.js +131 -0
- package/dist/test/document-helpers/utils.d.ts +8 -0
- package/dist/test/document-helpers/utils.d.ts.map +1 -0
- package/dist/test/document-helpers/utils.js +21 -0
- package/dist/test/utils.d.ts +48 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +132 -0
- package/dist/test/vitest-setup.d.ts +2 -0
- package/dist/test/vitest-setup.d.ts.map +1 -0
- package/dist/test/vitest-setup.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +20 -0
- package/package.json +20 -38
- package/src/cache/index.ts +0 -2
- package/src/cache/memory.ts +0 -33
- package/src/cache/redis.ts +0 -56
- package/src/cache/types.ts +0 -9
- package/src/index.ts +0 -4
- package/src/queue/base.ts +0 -320
- package/src/queue/index.ts +0 -2
- package/src/queue/redis.ts +0 -144
- package/src/queue/types.ts +0 -79
- package/src/read-mode/errors.ts +0 -19
- package/src/read-mode/index.ts +0 -125
- package/src/read-mode/service.ts +0 -207
- package/src/read-mode/types.ts +0 -108
- package/src/server/error.ts +0 -70
- package/src/server/index.ts +0 -2444
- package/src/server/listener/index.ts +0 -2
- package/src/server/listener/manager.ts +0 -652
- package/src/server/listener/transmitter/index.ts +0 -4
- package/src/server/listener/transmitter/internal.ts +0 -143
- package/src/server/listener/transmitter/pull-responder.ts +0 -462
- package/src/server/listener/transmitter/switchboard-push.ts +0 -125
- package/src/server/listener/transmitter/types.ts +0 -27
- package/src/server/types.ts +0 -596
- package/src/server/utils.ts +0 -82
- package/src/storage/base.ts +0 -81
- package/src/storage/browser.ts +0 -238
- package/src/storage/filesystem.ts +0 -297
- package/src/storage/index.ts +0 -2
- package/src/storage/memory.ts +0 -211
- package/src/storage/prisma.ts +0 -653
- package/src/storage/sequelize.ts +0 -498
- package/src/storage/types.ts +0 -97
- package/src/utils/default-drives-manager.ts +0 -341
- package/src/utils/document-helpers.ts +0 -21
- package/src/utils/graphql.ts +0 -301
- package/src/utils/index.ts +0 -90
- package/src/utils/logger.ts +0 -38
- package/src/utils/migrations.ts +0 -58
- package/src/utils/run-asap.ts +0 -156
package/src/storage/prisma.ts
DELETED
|
@@ -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
|
-
}
|