document-drive 1.0.0-websockets.1 → 1.0.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 +1 -0
- package/package.json +74 -88
- package/src/cache/index.ts +2 -2
- package/src/cache/memory.ts +22 -13
- package/src/cache/redis.ts +43 -16
- package/src/cache/types.ts +4 -4
- package/src/index.ts +6 -3
- package/src/queue/base.ts +276 -214
- package/src/queue/index.ts +2 -2
- package/src/queue/redis.ts +138 -127
- package/src/queue/types.ts +44 -38
- package/src/read-mode/errors.ts +19 -0
- package/src/read-mode/index.ts +125 -0
- package/src/read-mode/service.ts +207 -0
- package/src/read-mode/types.ts +108 -0
- package/src/server/error.ts +61 -26
- package/src/server/index.ts +2160 -1785
- package/src/server/listener/index.ts +2 -2
- package/src/server/listener/manager.ts +475 -437
- package/src/server/listener/transmitter/index.ts +4 -5
- package/src/server/listener/transmitter/internal.ts +77 -79
- package/src/server/listener/transmitter/pull-responder.ts +363 -329
- package/src/server/listener/transmitter/switchboard-push.ts +72 -55
- package/src/server/listener/transmitter/types.ts +19 -25
- package/src/server/types.ts +536 -349
- package/src/server/utils.ts +26 -27
- package/src/storage/base.ts +81 -0
- package/src/storage/browser.ts +233 -216
- package/src/storage/filesystem.ts +257 -256
- package/src/storage/index.ts +2 -1
- package/src/storage/memory.ts +206 -214
- package/src/storage/prisma.ts +575 -568
- package/src/storage/sequelize.ts +460 -471
- package/src/storage/types.ts +83 -67
- package/src/utils/default-drives-manager.ts +341 -0
- package/src/utils/document-helpers.ts +19 -18
- package/src/utils/graphql.ts +288 -34
- package/src/utils/index.ts +61 -59
- package/src/utils/logger.ts +39 -37
- package/src/utils/migrations.ts +58 -0
- package/src/utils/run-asap.ts +156 -0
- package/CHANGELOG.md +0 -818
- package/src/server/listener/transmitter/subscription.ts +0 -364
package/src/storage/sequelize.ts
CHANGED
|
@@ -1,509 +1,498 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from
|
|
2
|
+
DocumentDriveLocalState,
|
|
3
|
+
DocumentDriveState,
|
|
4
|
+
} from "document-model-libs/document-drive";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from
|
|
12
|
-
import { DataTypes, Options, Sequelize } from
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
6
|
+
AttachmentInput,
|
|
7
|
+
DocumentHeader,
|
|
8
|
+
ExtendedState,
|
|
9
|
+
Operation,
|
|
10
|
+
OperationScope,
|
|
11
|
+
} from "document-model/document";
|
|
12
|
+
import { DataTypes, Options, Sequelize } from "sequelize";
|
|
13
|
+
import type { SynchronizationUnitQuery } from "../server/types";
|
|
14
|
+
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from "./types";
|
|
15
15
|
|
|
16
16
|
export class SequelizeStorage implements IDriveStorage {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
private db: Sequelize;
|
|
18
|
+
|
|
19
|
+
constructor(options: Options) {
|
|
20
|
+
this.db = new Sequelize(options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public syncModels() {
|
|
24
|
+
const Drive = this.db.define("drive", {
|
|
25
|
+
slug: {
|
|
26
|
+
type: DataTypes.STRING,
|
|
27
|
+
primaryKey: true,
|
|
28
|
+
},
|
|
29
|
+
id: DataTypes.STRING,
|
|
30
|
+
});
|
|
31
|
+
const Document = this.db.define("document", {
|
|
32
|
+
id: {
|
|
33
|
+
type: DataTypes.STRING,
|
|
34
|
+
primaryKey: true,
|
|
35
|
+
},
|
|
36
|
+
driveId: {
|
|
37
|
+
type: DataTypes.STRING,
|
|
38
|
+
primaryKey: true,
|
|
39
|
+
},
|
|
40
|
+
name: DataTypes.STRING,
|
|
41
|
+
documentType: DataTypes.STRING,
|
|
42
|
+
initialState: DataTypes.JSON,
|
|
43
|
+
lastModified: DataTypes.DATE,
|
|
44
|
+
revision: DataTypes.JSON,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const Operation = this.db.define("operation", {
|
|
48
|
+
driveId: {
|
|
49
|
+
type: DataTypes.STRING,
|
|
50
|
+
primaryKey: true,
|
|
51
|
+
unique: "unique_operation",
|
|
52
|
+
},
|
|
53
|
+
documentId: {
|
|
54
|
+
type: DataTypes.STRING,
|
|
55
|
+
primaryKey: true,
|
|
56
|
+
unique: "unique_operation",
|
|
57
|
+
},
|
|
58
|
+
hash: DataTypes.STRING,
|
|
59
|
+
index: {
|
|
60
|
+
type: DataTypes.INTEGER,
|
|
61
|
+
primaryKey: true,
|
|
62
|
+
unique: "unique_operation",
|
|
63
|
+
},
|
|
64
|
+
input: DataTypes.JSON,
|
|
65
|
+
timestamp: DataTypes.DATE,
|
|
66
|
+
type: DataTypes.STRING,
|
|
67
|
+
scope: {
|
|
68
|
+
type: DataTypes.STRING,
|
|
69
|
+
primaryKey: true,
|
|
70
|
+
unique: "unique_operation",
|
|
71
|
+
},
|
|
72
|
+
branch: {
|
|
73
|
+
type: DataTypes.STRING,
|
|
74
|
+
primaryKey: true,
|
|
75
|
+
unique: "unique_operation",
|
|
76
|
+
},
|
|
77
|
+
skip: {
|
|
78
|
+
type: DataTypes.INTEGER,
|
|
79
|
+
defaultValue: 0,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const Attachment = this.db.define("attachment", {
|
|
84
|
+
driveId: {
|
|
85
|
+
type: DataTypes.STRING,
|
|
86
|
+
primaryKey: true,
|
|
87
|
+
},
|
|
88
|
+
documentId: {
|
|
89
|
+
type: DataTypes.STRING,
|
|
90
|
+
primaryKey: true,
|
|
91
|
+
},
|
|
92
|
+
scope: {
|
|
93
|
+
type: DataTypes.STRING,
|
|
94
|
+
primaryKey: true,
|
|
95
|
+
},
|
|
96
|
+
branch: {
|
|
97
|
+
type: DataTypes.STRING,
|
|
98
|
+
primaryKey: true,
|
|
99
|
+
},
|
|
100
|
+
index: {
|
|
101
|
+
type: DataTypes.STRING,
|
|
102
|
+
primaryKey: true,
|
|
103
|
+
},
|
|
104
|
+
hash: {
|
|
105
|
+
type: DataTypes.STRING,
|
|
106
|
+
primaryKey: true,
|
|
107
|
+
},
|
|
108
|
+
mimeType: DataTypes.STRING,
|
|
109
|
+
fileName: DataTypes.STRING,
|
|
110
|
+
extension: DataTypes.STRING,
|
|
111
|
+
data: DataTypes.BLOB,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
Operation.hasMany(Attachment, {
|
|
115
|
+
onDelete: "CASCADE",
|
|
116
|
+
});
|
|
117
|
+
Attachment.belongsTo(Operation);
|
|
118
|
+
Document.hasMany(Operation, {
|
|
119
|
+
onDelete: "CASCADE",
|
|
120
|
+
});
|
|
121
|
+
Operation.belongsTo(Document);
|
|
122
|
+
|
|
123
|
+
return this.db.sync({ force: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
127
|
+
await this.createDocument("drives", id, drive as DocumentStorage);
|
|
128
|
+
const Drive = this.db.models.drive;
|
|
129
|
+
await Drive?.upsert({ id, slug: drive.initialState.state.global.slug });
|
|
130
|
+
}
|
|
131
|
+
async addDriveOperations(
|
|
132
|
+
id: string,
|
|
133
|
+
operations: Operation[],
|
|
134
|
+
header: DocumentHeader,
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
await this.addDocumentOperations("drives", id, operations, header);
|
|
137
|
+
}
|
|
138
|
+
async createDocument(
|
|
139
|
+
drive: string,
|
|
140
|
+
id: string,
|
|
141
|
+
document: DocumentStorage,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const Document = this.db.models.document;
|
|
144
|
+
|
|
145
|
+
if (!Document) {
|
|
146
|
+
throw new Error("Document model not found");
|
|
21
147
|
}
|
|
22
148
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
initialState: DataTypes.JSON,
|
|
43
|
-
lastModified: DataTypes.DATE,
|
|
44
|
-
revision: DataTypes.JSON
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const Operation = this.db.define('operation', {
|
|
48
|
-
driveId: {
|
|
49
|
-
type: DataTypes.STRING,
|
|
50
|
-
primaryKey: true,
|
|
51
|
-
unique: 'unique_operation'
|
|
52
|
-
},
|
|
53
|
-
documentId: {
|
|
54
|
-
type: DataTypes.STRING,
|
|
55
|
-
primaryKey: true,
|
|
56
|
-
unique: 'unique_operation'
|
|
57
|
-
},
|
|
58
|
-
hash: DataTypes.STRING,
|
|
59
|
-
index: {
|
|
60
|
-
type: DataTypes.INTEGER,
|
|
61
|
-
primaryKey: true,
|
|
62
|
-
unique: 'unique_operation'
|
|
63
|
-
},
|
|
64
|
-
input: DataTypes.JSON,
|
|
65
|
-
timestamp: DataTypes.DATE,
|
|
66
|
-
type: DataTypes.STRING,
|
|
67
|
-
scope: {
|
|
68
|
-
type: DataTypes.STRING,
|
|
69
|
-
primaryKey: true,
|
|
70
|
-
unique: 'unique_operation'
|
|
71
|
-
},
|
|
72
|
-
branch: {
|
|
73
|
-
type: DataTypes.STRING,
|
|
74
|
-
primaryKey: true,
|
|
75
|
-
unique: 'unique_operation'
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const Attachment = this.db.define('attachment', {
|
|
80
|
-
driveId: {
|
|
81
|
-
type: DataTypes.STRING,
|
|
82
|
-
primaryKey: true
|
|
83
|
-
},
|
|
84
|
-
documentId: {
|
|
85
|
-
type: DataTypes.STRING,
|
|
86
|
-
primaryKey: true
|
|
87
|
-
},
|
|
88
|
-
scope: {
|
|
89
|
-
type: DataTypes.STRING,
|
|
90
|
-
primaryKey: true
|
|
91
|
-
},
|
|
92
|
-
branch: {
|
|
93
|
-
type: DataTypes.STRING,
|
|
94
|
-
primaryKey: true
|
|
95
|
-
},
|
|
96
|
-
index: {
|
|
97
|
-
type: DataTypes.STRING,
|
|
98
|
-
primaryKey: true
|
|
99
|
-
},
|
|
100
|
-
hash: {
|
|
101
|
-
type: DataTypes.STRING,
|
|
102
|
-
primaryKey: true
|
|
103
|
-
},
|
|
104
|
-
mimeType: DataTypes.STRING,
|
|
105
|
-
fileName: DataTypes.STRING,
|
|
106
|
-
extension: DataTypes.STRING,
|
|
107
|
-
data: DataTypes.BLOB
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
Operation.hasMany(Attachment, {
|
|
111
|
-
onDelete: 'CASCADE'
|
|
112
|
-
});
|
|
113
|
-
Attachment.belongsTo(Operation);
|
|
114
|
-
Document.hasMany(Operation, {
|
|
115
|
-
onDelete: 'CASCADE'
|
|
116
|
-
});
|
|
117
|
-
Operation.belongsTo(Document);
|
|
118
|
-
|
|
119
|
-
return this.db.sync({ force: true });
|
|
149
|
+
await Document.create({
|
|
150
|
+
id: id,
|
|
151
|
+
driveId: drive,
|
|
152
|
+
name: document.name,
|
|
153
|
+
documentType: document.documentType,
|
|
154
|
+
initialState: document.initialState,
|
|
155
|
+
lastModified: document.lastModified,
|
|
156
|
+
revision: document.revision,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async addDocumentOperations(
|
|
160
|
+
drive: string,
|
|
161
|
+
id: string,
|
|
162
|
+
operations: Operation[],
|
|
163
|
+
header: DocumentHeader,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const document = await this.getDocument(drive, id);
|
|
166
|
+
if (!document) {
|
|
167
|
+
throw new Error(`Document with id ${id} not found`);
|
|
120
168
|
}
|
|
121
169
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
await Drive?.upsert({ id, slug: drive.initialState.state.global.slug });
|
|
126
|
-
}
|
|
127
|
-
async addDriveOperations(
|
|
128
|
-
id: string,
|
|
129
|
-
operations: Operation[],
|
|
130
|
-
header: DocumentHeader
|
|
131
|
-
): Promise<void> {
|
|
132
|
-
await this.addDocumentOperations('drives', id, operations, header);
|
|
170
|
+
const Operation = this.db.models.operation;
|
|
171
|
+
if (!Operation) {
|
|
172
|
+
throw new Error("Operation model not found");
|
|
133
173
|
}
|
|
134
|
-
async createDocument(
|
|
135
|
-
drive: string,
|
|
136
|
-
id: string,
|
|
137
|
-
document: DocumentStorage
|
|
138
|
-
): Promise<void> {
|
|
139
|
-
const Document = this.db.models.document;
|
|
140
|
-
|
|
141
|
-
if (!Document) {
|
|
142
|
-
throw new Error('Document model not found');
|
|
143
|
-
}
|
|
144
174
|
|
|
145
|
-
|
|
146
|
-
|
|
175
|
+
await Operation.bulkCreate(
|
|
176
|
+
operations.map((op) => ({
|
|
177
|
+
driveId: drive,
|
|
178
|
+
documentId: id,
|
|
179
|
+
hash: op.hash,
|
|
180
|
+
index: op.index,
|
|
181
|
+
input: op.input,
|
|
182
|
+
timestamp: op.timestamp,
|
|
183
|
+
type: op.type,
|
|
184
|
+
scope: op.scope,
|
|
185
|
+
branch: "main",
|
|
186
|
+
opId: op.id,
|
|
187
|
+
})),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const attachments = operations.reduce<AttachmentInput[]>((acc, op) => {
|
|
191
|
+
if (op.attachments?.length) {
|
|
192
|
+
return acc.concat(
|
|
193
|
+
op.attachments.map((attachment) => ({
|
|
147
194
|
driveId: drive,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
operations: Operation[],
|
|
159
|
-
header: DocumentHeader
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
const document = await this.getDocument(drive, id);
|
|
162
|
-
if (!document) {
|
|
163
|
-
throw new Error(`Document with id ${id} not found`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const Operation = this.db.models.operation;
|
|
167
|
-
if (!Operation) {
|
|
168
|
-
throw new Error('Operation model not found');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await Operation.bulkCreate(
|
|
172
|
-
operations.map(op => ({
|
|
173
|
-
driveId: drive,
|
|
174
|
-
documentId: id,
|
|
175
|
-
hash: op.hash,
|
|
176
|
-
index: op.index,
|
|
177
|
-
input: op.input,
|
|
178
|
-
timestamp: op.timestamp,
|
|
179
|
-
type: op.type,
|
|
180
|
-
scope: op.scope,
|
|
181
|
-
branch: 'main',
|
|
182
|
-
opId: op.id
|
|
183
|
-
}))
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const attachments = operations.reduce<AttachmentInput[]>((acc, op) => {
|
|
187
|
-
if (op.attachments?.length) {
|
|
188
|
-
return acc.concat(
|
|
189
|
-
op.attachments.map(attachment => ({
|
|
190
|
-
driveId: drive,
|
|
191
|
-
documentId: id,
|
|
192
|
-
scope: op.scope,
|
|
193
|
-
branch: 'main',
|
|
194
|
-
index: op.index,
|
|
195
|
-
mimeType: attachment.mimeType,
|
|
196
|
-
fileName: attachment.fileName,
|
|
197
|
-
extension: attachment.extension,
|
|
198
|
-
data: attachment.data,
|
|
199
|
-
hash: attachment.hash
|
|
200
|
-
}))
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
return acc;
|
|
204
|
-
}, []);
|
|
205
|
-
if (attachments.length) {
|
|
206
|
-
const Attachment = this.db.models.attachment;
|
|
207
|
-
if (!Attachment) {
|
|
208
|
-
throw new Error('Attachment model not found');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
await Attachment.bulkCreate(attachments);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const Document = this.db.models.document;
|
|
215
|
-
if (!Document) {
|
|
216
|
-
throw new Error('Document model not found');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
await Document.update(
|
|
220
|
-
{
|
|
221
|
-
lastModified: header.lastModified,
|
|
222
|
-
revision: header.revision
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
where: {
|
|
226
|
-
id: id,
|
|
227
|
-
driveId: drive
|
|
228
|
-
}
|
|
229
|
-
}
|
|
195
|
+
documentId: id,
|
|
196
|
+
scope: op.scope,
|
|
197
|
+
branch: "main",
|
|
198
|
+
index: op.index,
|
|
199
|
+
mimeType: attachment.mimeType,
|
|
200
|
+
fileName: attachment.fileName,
|
|
201
|
+
extension: attachment.extension,
|
|
202
|
+
data: attachment.data,
|
|
203
|
+
hash: attachment.hash,
|
|
204
|
+
})),
|
|
230
205
|
);
|
|
206
|
+
}
|
|
207
|
+
return acc;
|
|
208
|
+
}, []);
|
|
209
|
+
if (attachments.length) {
|
|
210
|
+
const Attachment = this.db.models.attachment;
|
|
211
|
+
if (!Attachment) {
|
|
212
|
+
throw new Error("Attachment model not found");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await Attachment.bulkCreate(attachments);
|
|
231
216
|
}
|
|
232
217
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
operation: Operation,
|
|
237
|
-
attachments: AttachmentInput[]
|
|
238
|
-
) {
|
|
239
|
-
const Attachment = this.db.models.attachment;
|
|
240
|
-
if (!Attachment) {
|
|
241
|
-
throw new Error('Attachment model not found');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return Attachment.bulkCreate(
|
|
245
|
-
attachments.map(attachment => ({
|
|
246
|
-
driveId: driveId,
|
|
247
|
-
documentId: documentId,
|
|
248
|
-
scope: operation.scope,
|
|
249
|
-
branch: 'main',
|
|
250
|
-
index: operation.index,
|
|
251
|
-
mimeType: attachment.mimeType,
|
|
252
|
-
fileName: attachment.fileName,
|
|
253
|
-
extension: attachment.extension,
|
|
254
|
-
data: attachment.data,
|
|
255
|
-
hash: attachment.hash
|
|
256
|
-
}))
|
|
257
|
-
);
|
|
218
|
+
const Document = this.db.models.document;
|
|
219
|
+
if (!Document) {
|
|
220
|
+
throw new Error("Document model not found");
|
|
258
221
|
}
|
|
259
222
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
223
|
+
await Document.update(
|
|
224
|
+
{
|
|
225
|
+
lastModified: header.lastModified,
|
|
226
|
+
revision: header.revision,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
where: {
|
|
230
|
+
id: id,
|
|
231
|
+
driveId: drive,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async _addDocumentOperationAttachments(
|
|
238
|
+
driveId: string,
|
|
239
|
+
documentId: string,
|
|
240
|
+
operation: Operation,
|
|
241
|
+
attachments: AttachmentInput[],
|
|
242
|
+
) {
|
|
243
|
+
const Attachment = this.db.models.attachment;
|
|
244
|
+
if (!Attachment) {
|
|
245
|
+
throw new Error("Attachment model not found");
|
|
278
246
|
}
|
|
279
247
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
248
|
+
return Attachment.bulkCreate(
|
|
249
|
+
attachments.map((attachment) => ({
|
|
250
|
+
driveId: driveId,
|
|
251
|
+
documentId: documentId,
|
|
252
|
+
scope: operation.scope,
|
|
253
|
+
branch: "main",
|
|
254
|
+
index: operation.index,
|
|
255
|
+
mimeType: attachment.mimeType,
|
|
256
|
+
fileName: attachment.fileName,
|
|
257
|
+
extension: attachment.extension,
|
|
258
|
+
data: attachment.data,
|
|
259
|
+
hash: attachment.hash,
|
|
260
|
+
})),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async getDocuments(drive: string) {
|
|
265
|
+
const Document = this.db.models.document;
|
|
266
|
+
if (!Document) {
|
|
267
|
+
throw new Error("Document model not found");
|
|
293
268
|
}
|
|
294
269
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (entry === null) {
|
|
315
|
-
throw new Error(`Document with id ${id} not found`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
319
|
-
const document: {
|
|
320
|
-
operations: [
|
|
321
|
-
{
|
|
322
|
-
hash: string;
|
|
323
|
-
index: number;
|
|
324
|
-
timestamp: Date;
|
|
325
|
-
input: JSON;
|
|
326
|
-
type: string;
|
|
327
|
-
scope: string;
|
|
328
|
-
opId?: string;
|
|
329
|
-
}
|
|
330
|
-
];
|
|
331
|
-
revision: Required<Record<OperationScope, number>>;
|
|
332
|
-
createdAt: Date;
|
|
333
|
-
name: string;
|
|
334
|
-
updatedAt: Date;
|
|
335
|
-
documentType: string;
|
|
336
|
-
initialState: ExtendedState<
|
|
337
|
-
DocumentDriveState,
|
|
338
|
-
DocumentDriveLocalState
|
|
339
|
-
>;
|
|
340
|
-
} = entry.dataValues;
|
|
341
|
-
const Operation = this.db.models.operation;
|
|
342
|
-
if (!Operation) {
|
|
343
|
-
throw new Error('Operation model not found');
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const operations = document.operations.map(
|
|
347
|
-
(op: {
|
|
348
|
-
hash: string;
|
|
349
|
-
index: number;
|
|
350
|
-
timestamp: Date;
|
|
351
|
-
input: JSON;
|
|
352
|
-
type: string;
|
|
353
|
-
scope: string;
|
|
354
|
-
opId?: string;
|
|
355
|
-
}) => ({
|
|
356
|
-
hash: op.hash,
|
|
357
|
-
index: op.index,
|
|
358
|
-
timestamp: new Date(op.timestamp).toISOString(),
|
|
359
|
-
input: op.input,
|
|
360
|
-
type: op.type,
|
|
361
|
-
scope: op.scope as OperationScope,
|
|
362
|
-
id: op.opId
|
|
363
|
-
// attachments: fileRegistry
|
|
364
|
-
})
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
const doc = {
|
|
368
|
-
created: document.createdAt.toISOString(),
|
|
369
|
-
name: document.name ? document.name : '',
|
|
370
|
-
documentType: document.documentType,
|
|
371
|
-
initialState: document.initialState,
|
|
372
|
-
lastModified: document.updatedAt.toISOString(),
|
|
373
|
-
operations: {
|
|
374
|
-
global: operations.filter(
|
|
375
|
-
(op: Operation) => op.scope === 'global'
|
|
376
|
-
),
|
|
377
|
-
local: operations.filter(
|
|
378
|
-
(op: Operation) => op.scope === 'local'
|
|
379
|
-
)
|
|
380
|
-
},
|
|
381
|
-
revision: document.revision
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
return doc;
|
|
270
|
+
const result = await Document.findAll({
|
|
271
|
+
attributes: ["id"],
|
|
272
|
+
where: {
|
|
273
|
+
driveId: drive,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const ids = result.map((e: { dataValues: { id: string } }) => {
|
|
278
|
+
const { id } = e.dataValues;
|
|
279
|
+
return id;
|
|
280
|
+
});
|
|
281
|
+
return ids;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async checkDocumentExists(driveId: string, id: string): Promise<boolean> {
|
|
285
|
+
const Document = this.db.models.document;
|
|
286
|
+
if (!Document) {
|
|
287
|
+
throw new Error("Document model not found");
|
|
385
288
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
289
|
+
const count = await Document.count({
|
|
290
|
+
where: {
|
|
291
|
+
id: id,
|
|
292
|
+
driveId: driveId,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return count > 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// @ts-expect-error TODO: fix as this should not be undefined
|
|
300
|
+
async getDocument(driveId: string, id: string) {
|
|
301
|
+
const Document = this.db.models.document;
|
|
302
|
+
if (!Document) {
|
|
303
|
+
throw new Error("Document model not found");
|
|
399
304
|
}
|
|
400
305
|
|
|
401
|
-
|
|
402
|
-
|
|
306
|
+
const entry = await Document.findOne({
|
|
307
|
+
where: {
|
|
308
|
+
id: id,
|
|
309
|
+
driveId: driveId,
|
|
310
|
+
},
|
|
311
|
+
include: [
|
|
312
|
+
{
|
|
313
|
+
model: this.db.models.operation,
|
|
314
|
+
as: "operations",
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (entry === null) {
|
|
320
|
+
throw new Error(`Document with id ${id} not found`);
|
|
403
321
|
}
|
|
404
322
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
324
|
+
const document: {
|
|
325
|
+
operations: [
|
|
326
|
+
{
|
|
327
|
+
hash: string;
|
|
328
|
+
index: number;
|
|
329
|
+
timestamp: Date;
|
|
330
|
+
input: JSON;
|
|
331
|
+
type: string;
|
|
332
|
+
scope: string;
|
|
333
|
+
opId?: string;
|
|
334
|
+
skip: number;
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
revision: Required<Record<OperationScope, number>>;
|
|
338
|
+
createdAt: Date;
|
|
339
|
+
name: string;
|
|
340
|
+
updatedAt: Date;
|
|
341
|
+
documentType: string;
|
|
342
|
+
initialState: ExtendedState<DocumentDriveState, DocumentDriveLocalState>;
|
|
343
|
+
} = entry.dataValues;
|
|
344
|
+
const Operation = this.db.models.operation;
|
|
345
|
+
if (!Operation) {
|
|
346
|
+
throw new Error("Operation model not found");
|
|
408
347
|
}
|
|
409
348
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
349
|
+
const operations: Operation[] = document.operations.map((op) => ({
|
|
350
|
+
hash: op.hash,
|
|
351
|
+
index: op.index,
|
|
352
|
+
timestamp: new Date(op.timestamp).toISOString(),
|
|
353
|
+
input: op.input,
|
|
354
|
+
type: op.type,
|
|
355
|
+
scope: op.scope as OperationScope,
|
|
356
|
+
id: op.opId,
|
|
357
|
+
skip: op.skip,
|
|
358
|
+
// attachments: fileRegistry
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
const doc = {
|
|
362
|
+
created: document.createdAt.toISOString(),
|
|
363
|
+
name: document.name ? document.name : "",
|
|
364
|
+
documentType: document.documentType,
|
|
365
|
+
initialState: document.initialState,
|
|
366
|
+
lastModified: document.updatedAt.toISOString(),
|
|
367
|
+
operations: {
|
|
368
|
+
global: operations.filter((op: Operation) => op.scope === "global"),
|
|
369
|
+
local: operations.filter((op: Operation) => op.scope === "local"),
|
|
370
|
+
},
|
|
371
|
+
revision: document.revision,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return doc;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async deleteDocument(drive: string, id: string) {
|
|
378
|
+
const Document = this.db.models.document;
|
|
379
|
+
if (!Document) {
|
|
380
|
+
throw new Error("Document model not found");
|
|
381
|
+
}
|
|
415
382
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
383
|
+
await Document.destroy({
|
|
384
|
+
where: {
|
|
385
|
+
id: id,
|
|
386
|
+
driveId: drive,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async getDrives() {
|
|
392
|
+
return this.getDocuments("drives");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async getDrive(id: string) {
|
|
396
|
+
const doc = await this.getDocument("drives", id);
|
|
397
|
+
return doc as DocumentDriveStorage;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async getDriveBySlug(slug: string) {
|
|
401
|
+
const Drive = this.db.models.drive;
|
|
402
|
+
if (!Drive) {
|
|
403
|
+
throw new Error("Drive model not found");
|
|
404
|
+
}
|
|
421
405
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
406
|
+
const driveEntity = await Drive.findOne({
|
|
407
|
+
where: {
|
|
408
|
+
slug,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
425
411
|
|
|
426
|
-
|
|
412
|
+
if (!driveEntity) {
|
|
413
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
427
414
|
}
|
|
428
415
|
|
|
429
|
-
|
|
430
|
-
|
|
416
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
417
|
+
return this.getDrive(driveEntity.dataValues.id);
|
|
418
|
+
}
|
|
431
419
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
throw new Error('Document model not found');
|
|
435
|
-
}
|
|
420
|
+
async deleteDrive(id: string) {
|
|
421
|
+
await this.deleteDocument("drives", id);
|
|
436
422
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
const Drive = this.db.models.drive;
|
|
444
|
-
if (Drive) {
|
|
445
|
-
await Drive.destroy({
|
|
446
|
-
where: {
|
|
447
|
-
id: id
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
423
|
+
const Document = this.db.models.document;
|
|
424
|
+
if (!Document) {
|
|
425
|
+
throw new Error("Document model not found");
|
|
451
426
|
}
|
|
452
427
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
units.map(async unit => {
|
|
467
|
-
try {
|
|
468
|
-
const document = await (unit.documentId
|
|
469
|
-
? this.getDocument(unit.driveId, unit.documentId)
|
|
470
|
-
: this.getDrive(unit.driveId));
|
|
471
|
-
if (!document) {
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
474
|
-
const operation =
|
|
475
|
-
document.operations[unit.scope as OperationScope]?.at(
|
|
476
|
-
-1
|
|
477
|
-
);
|
|
478
|
-
if (operation) {
|
|
479
|
-
return {
|
|
480
|
-
driveId: unit.driveId,
|
|
481
|
-
documentId: unit.documentId,
|
|
482
|
-
scope: unit.scope,
|
|
483
|
-
branch: unit.branch,
|
|
484
|
-
lastUpdated: operation.timestamp,
|
|
485
|
-
revision: operation.index
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
} catch {
|
|
489
|
-
return undefined;
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
);
|
|
493
|
-
return results.reduce<
|
|
494
|
-
{
|
|
495
|
-
driveId: string;
|
|
496
|
-
documentId: string;
|
|
497
|
-
scope: string;
|
|
498
|
-
branch: string;
|
|
499
|
-
lastUpdated: string;
|
|
500
|
-
revision: number;
|
|
501
|
-
}[]
|
|
502
|
-
>((acc, curr) => {
|
|
503
|
-
if (curr.status === 'fulfilled' && curr.value !== undefined) {
|
|
504
|
-
acc.push(curr.value);
|
|
505
|
-
}
|
|
506
|
-
return acc;
|
|
507
|
-
}, []);
|
|
428
|
+
await Document.destroy({
|
|
429
|
+
where: {
|
|
430
|
+
driveId: id,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const Drive = this.db.models.drive;
|
|
435
|
+
if (Drive) {
|
|
436
|
+
await Drive.destroy({
|
|
437
|
+
where: {
|
|
438
|
+
id: id,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
508
441
|
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async getSynchronizationUnitsRevision(
|
|
445
|
+
units: SynchronizationUnitQuery[],
|
|
446
|
+
): Promise<
|
|
447
|
+
{
|
|
448
|
+
driveId: string;
|
|
449
|
+
documentId: string;
|
|
450
|
+
scope: string;
|
|
451
|
+
branch: string;
|
|
452
|
+
lastUpdated: string;
|
|
453
|
+
revision: number;
|
|
454
|
+
}[]
|
|
455
|
+
> {
|
|
456
|
+
const results = await Promise.allSettled(
|
|
457
|
+
units.map(async (unit) => {
|
|
458
|
+
try {
|
|
459
|
+
const document = await (unit.documentId
|
|
460
|
+
? this.getDocument(unit.driveId, unit.documentId)
|
|
461
|
+
: this.getDrive(unit.driveId));
|
|
462
|
+
if (!document) {
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
const operation =
|
|
466
|
+
document.operations[unit.scope as OperationScope].at(-1);
|
|
467
|
+
if (operation) {
|
|
468
|
+
return {
|
|
469
|
+
driveId: unit.driveId,
|
|
470
|
+
documentId: unit.documentId,
|
|
471
|
+
scope: unit.scope,
|
|
472
|
+
branch: unit.branch,
|
|
473
|
+
lastUpdated: operation.timestamp,
|
|
474
|
+
revision: operation.index,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
} catch {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
}),
|
|
481
|
+
);
|
|
482
|
+
return results.reduce<
|
|
483
|
+
{
|
|
484
|
+
driveId: string;
|
|
485
|
+
documentId: string;
|
|
486
|
+
scope: string;
|
|
487
|
+
branch: string;
|
|
488
|
+
lastUpdated: string;
|
|
489
|
+
revision: number;
|
|
490
|
+
}[]
|
|
491
|
+
>((acc, curr) => {
|
|
492
|
+
if (curr.status === "fulfilled" && curr.value !== undefined) {
|
|
493
|
+
acc.push(curr.value);
|
|
494
|
+
}
|
|
495
|
+
return acc;
|
|
496
|
+
}, []);
|
|
497
|
+
}
|
|
509
498
|
}
|