document-drive 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -9
- package/src/storage/prisma.ts +5 -10
- package/src/storage/sequelize.ts +375 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-drive",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -25,29 +25,33 @@
|
|
|
25
25
|
"test:watch": "vitest watch"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"
|
|
29
|
-
"document-model
|
|
30
|
-
"
|
|
28
|
+
"@prisma/client": "5.7.1",
|
|
29
|
+
"document-model": "^1.0.22",
|
|
30
|
+
"document-model-libs": "^1.1.31",
|
|
31
|
+
"localforage": "^1.10.0",
|
|
32
|
+
"sequelize": "^6.35.2",
|
|
33
|
+
"sqlite3": "^5.1.7"
|
|
31
34
|
},
|
|
32
35
|
"dependencies": {
|
|
33
|
-
"@prisma/client": "5.7.1",
|
|
34
36
|
"sanitize-filename": "^1.6.3"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
39
|
+
"@prisma/client": "5.7.1",
|
|
37
40
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
38
41
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
39
42
|
"@typescript-eslint/parser": "^6.18.1",
|
|
40
43
|
"@vitest/coverage-v8": "^0.34.6",
|
|
41
|
-
"document-model": "^1.0.
|
|
42
|
-
"document-model-libs": "^1.1.
|
|
44
|
+
"document-model": "^1.0.22",
|
|
45
|
+
"document-model-libs": "^1.1.31",
|
|
43
46
|
"eslint": "^8.56.0",
|
|
44
47
|
"eslint-config-prettier": "^9.1.0",
|
|
45
48
|
"fake-indexeddb": "^5.0.1",
|
|
46
49
|
"localforage": "^1.10.0",
|
|
47
50
|
"prettier": "^3.1.1",
|
|
48
51
|
"prettier-plugin-organize-imports": "^3.2.4",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
52
|
+
"sequelize": "^6.35.2",
|
|
53
|
+
"sqlite3": "^5.1.7",
|
|
54
|
+
"typescript": "^5.3.2",
|
|
51
55
|
"vitest": "^0.34.6"
|
|
52
56
|
}
|
|
53
57
|
}
|
package/src/storage/prisma.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Prisma } from '@prisma/client';
|
|
2
2
|
import {
|
|
3
3
|
DocumentDriveLocalState,
|
|
4
4
|
DocumentDriveState
|
|
5
5
|
} from 'document-model-libs/document-drive';
|
|
6
6
|
import {
|
|
7
|
-
Document,
|
|
8
7
|
DocumentHeader,
|
|
9
8
|
ExtendedState,
|
|
10
9
|
Operation,
|
|
@@ -13,17 +12,13 @@ import {
|
|
|
13
12
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
14
13
|
|
|
15
14
|
export class PrismaStorage implements IDriveStorage {
|
|
16
|
-
private db:
|
|
15
|
+
private db: Prisma.TransactionClient;
|
|
17
16
|
|
|
18
|
-
constructor(db:
|
|
17
|
+
constructor(db: Prisma.TransactionClient) {
|
|
19
18
|
this.db = db;
|
|
20
19
|
}
|
|
21
20
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
22
|
-
await this.createDocument(
|
|
23
|
-
'drives',
|
|
24
|
-
id,
|
|
25
|
-
drive as DocumentStorage
|
|
26
|
-
);
|
|
21
|
+
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
27
22
|
}
|
|
28
23
|
async addDriveOperations(
|
|
29
24
|
id: string,
|
|
@@ -214,7 +209,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
214
209
|
// attachments: fileRegistry
|
|
215
210
|
}))
|
|
216
211
|
},
|
|
217
|
-
revision: dbDoc.revision
|
|
212
|
+
revision: dbDoc.revision as Required<Record<OperationScope, number>>
|
|
218
213
|
};
|
|
219
214
|
|
|
220
215
|
return doc;
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentDriveLocalState,
|
|
3
|
+
DocumentDriveState
|
|
4
|
+
} from 'document-model-libs/document-drive';
|
|
5
|
+
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 { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
14
|
+
|
|
15
|
+
export class SequelizeStorage implements IDriveStorage {
|
|
16
|
+
private db: Sequelize;
|
|
17
|
+
|
|
18
|
+
constructor(options: Options) {
|
|
19
|
+
this.db = new Sequelize(options);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public syncModels() {
|
|
23
|
+
const Document = this.db.define('document', {
|
|
24
|
+
id: {
|
|
25
|
+
type: DataTypes.STRING,
|
|
26
|
+
primaryKey: true
|
|
27
|
+
},
|
|
28
|
+
driveId: {
|
|
29
|
+
type: DataTypes.STRING,
|
|
30
|
+
primaryKey: true
|
|
31
|
+
},
|
|
32
|
+
name: DataTypes.STRING,
|
|
33
|
+
documentType: DataTypes.STRING,
|
|
34
|
+
initialState: DataTypes.JSON,
|
|
35
|
+
lastModified: DataTypes.DATE,
|
|
36
|
+
revision: DataTypes.JSON
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const Operation = this.db.define('operation', {
|
|
40
|
+
driveId: {
|
|
41
|
+
type: DataTypes.STRING,
|
|
42
|
+
primaryKey: true
|
|
43
|
+
},
|
|
44
|
+
documentId: {
|
|
45
|
+
type: DataTypes.STRING,
|
|
46
|
+
primaryKey: true
|
|
47
|
+
},
|
|
48
|
+
hash: DataTypes.STRING,
|
|
49
|
+
index: {
|
|
50
|
+
type: DataTypes.INTEGER,
|
|
51
|
+
primaryKey: true
|
|
52
|
+
},
|
|
53
|
+
input: DataTypes.JSON,
|
|
54
|
+
timestamp: DataTypes.DATE,
|
|
55
|
+
type: DataTypes.STRING,
|
|
56
|
+
scope: {
|
|
57
|
+
type: DataTypes.STRING,
|
|
58
|
+
primaryKey: true
|
|
59
|
+
},
|
|
60
|
+
branch: {
|
|
61
|
+
type: DataTypes.STRING,
|
|
62
|
+
primaryKey: true
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const Attachment = this.db.define('attachment', {
|
|
67
|
+
driveId: {
|
|
68
|
+
type: DataTypes.STRING,
|
|
69
|
+
primaryKey: true
|
|
70
|
+
},
|
|
71
|
+
documentId: {
|
|
72
|
+
type: DataTypes.STRING,
|
|
73
|
+
primaryKey: true
|
|
74
|
+
},
|
|
75
|
+
scope: {
|
|
76
|
+
type: DataTypes.STRING,
|
|
77
|
+
primaryKey: true
|
|
78
|
+
},
|
|
79
|
+
branch: {
|
|
80
|
+
type: DataTypes.STRING,
|
|
81
|
+
primaryKey: true
|
|
82
|
+
},
|
|
83
|
+
index: {
|
|
84
|
+
type: DataTypes.STRING,
|
|
85
|
+
primaryKey: true
|
|
86
|
+
},
|
|
87
|
+
hash: {
|
|
88
|
+
type: DataTypes.STRING,
|
|
89
|
+
primaryKey: true
|
|
90
|
+
},
|
|
91
|
+
mimeType: DataTypes.STRING,
|
|
92
|
+
fileName: DataTypes.STRING,
|
|
93
|
+
extension: DataTypes.STRING,
|
|
94
|
+
data: DataTypes.BLOB
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
Operation.hasMany(Attachment, {
|
|
98
|
+
onDelete: 'CASCADE'
|
|
99
|
+
});
|
|
100
|
+
Attachment.belongsTo(Operation);
|
|
101
|
+
Document.hasMany(Operation, {
|
|
102
|
+
onDelete: 'CASCADE'
|
|
103
|
+
});
|
|
104
|
+
Operation.belongsTo(Document);
|
|
105
|
+
|
|
106
|
+
return this.db.sync({ force: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
110
|
+
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
111
|
+
}
|
|
112
|
+
async addDriveOperations(
|
|
113
|
+
id: string,
|
|
114
|
+
operations: Operation[],
|
|
115
|
+
header: DocumentHeader
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
await this.addDocumentOperations('drives', id, operations, header);
|
|
118
|
+
}
|
|
119
|
+
async createDocument(
|
|
120
|
+
drive: string,
|
|
121
|
+
id: string,
|
|
122
|
+
document: DocumentStorage
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const Document = this.db.models.document;
|
|
125
|
+
|
|
126
|
+
if (!Document) {
|
|
127
|
+
throw new Error('Document model not found');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await Document.create({
|
|
131
|
+
id: id,
|
|
132
|
+
driveId: drive,
|
|
133
|
+
name: document.name,
|
|
134
|
+
documentType: document.documentType,
|
|
135
|
+
initialState: document.initialState,
|
|
136
|
+
lastModified: document.lastModified,
|
|
137
|
+
revision: document.revision
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async addDocumentOperations(
|
|
141
|
+
drive: string,
|
|
142
|
+
id: string,
|
|
143
|
+
operations: Operation[],
|
|
144
|
+
header: DocumentHeader
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
const document = await this.getDocument(drive, id);
|
|
147
|
+
if (!document) {
|
|
148
|
+
throw new Error(`Document with id ${id} not found`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const Operation = this.db.models.operation;
|
|
152
|
+
if (!Operation) {
|
|
153
|
+
throw new Error('Operation model not found');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await Promise.all(
|
|
157
|
+
operations.map(async op => {
|
|
158
|
+
return Operation.create({
|
|
159
|
+
driveId: drive,
|
|
160
|
+
documentId: id,
|
|
161
|
+
hash: op.hash,
|
|
162
|
+
index: op.index,
|
|
163
|
+
input: op.input,
|
|
164
|
+
timestamp: op.timestamp,
|
|
165
|
+
type: op.type,
|
|
166
|
+
scope: op.scope,
|
|
167
|
+
branch: 'main'
|
|
168
|
+
}).then(async () => {
|
|
169
|
+
if (op.attachments) {
|
|
170
|
+
await this._addDocumentOperationAttachments(
|
|
171
|
+
drive,
|
|
172
|
+
id,
|
|
173
|
+
op,
|
|
174
|
+
op.attachments
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const Document = this.db.models.document;
|
|
182
|
+
if (!Document) {
|
|
183
|
+
throw new Error('Document model not found');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await Document.update(
|
|
187
|
+
{
|
|
188
|
+
lastModified: header.lastModified,
|
|
189
|
+
revision: header.revision
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
where: {
|
|
193
|
+
id: id,
|
|
194
|
+
driveId: drive
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async _addDocumentOperationAttachments(
|
|
201
|
+
driveId: string,
|
|
202
|
+
documentId: string,
|
|
203
|
+
operation: Operation,
|
|
204
|
+
attachments: AttachmentInput[]
|
|
205
|
+
) {
|
|
206
|
+
const Attachment = this.db.models.attachment;
|
|
207
|
+
if (!Attachment) {
|
|
208
|
+
throw new Error('Attachment model not found');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await Promise.all(
|
|
212
|
+
attachments.map(async attachment => {
|
|
213
|
+
return Attachment.create({
|
|
214
|
+
driveId: driveId,
|
|
215
|
+
documentId: documentId,
|
|
216
|
+
scope: operation.scope,
|
|
217
|
+
branch: 'main',
|
|
218
|
+
index: operation.index,
|
|
219
|
+
mimeType: attachment.mimeType,
|
|
220
|
+
fileName: attachment.fileName,
|
|
221
|
+
extension: attachment.extension,
|
|
222
|
+
data: attachment.data,
|
|
223
|
+
hash: attachment.hash
|
|
224
|
+
});
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getDocuments(drive: string) {
|
|
230
|
+
const Document = this.db.models.document;
|
|
231
|
+
if (!Document) {
|
|
232
|
+
throw new Error('Document model not found');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const result = await Document.findAll({
|
|
236
|
+
attributes: ['id'],
|
|
237
|
+
where: {
|
|
238
|
+
driveId: drive
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const ids = result.map((e: { dataValues: { id: string } }) => {
|
|
243
|
+
const { id } = e.dataValues;
|
|
244
|
+
return id;
|
|
245
|
+
});
|
|
246
|
+
return ids;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async getDocument(driveId: string, id: string) {
|
|
250
|
+
const Document = this.db.models.document;
|
|
251
|
+
if (!Document) {
|
|
252
|
+
throw new Error('Document model not found');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const entry = await Document.findOne({
|
|
256
|
+
where: {
|
|
257
|
+
id: id,
|
|
258
|
+
driveId: driveId
|
|
259
|
+
},
|
|
260
|
+
include: [
|
|
261
|
+
{
|
|
262
|
+
model: this.db.models.operation,
|
|
263
|
+
as: 'operations'
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (entry === null) {
|
|
269
|
+
throw new Error(`Document with id ${id} not found`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
273
|
+
const document: {
|
|
274
|
+
operations: [
|
|
275
|
+
{
|
|
276
|
+
hash: string;
|
|
277
|
+
index: number;
|
|
278
|
+
timestamp: Date;
|
|
279
|
+
input: JSON;
|
|
280
|
+
type: string;
|
|
281
|
+
scope: string;
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
revision: Required<Record<OperationScope, number>>;
|
|
285
|
+
createdAt: Date;
|
|
286
|
+
name: string;
|
|
287
|
+
updatedAt: Date;
|
|
288
|
+
documentType: string;
|
|
289
|
+
initialState: ExtendedState<
|
|
290
|
+
DocumentDriveState,
|
|
291
|
+
DocumentDriveLocalState
|
|
292
|
+
>;
|
|
293
|
+
} = entry.dataValues;
|
|
294
|
+
const Operation = this.db.models.operation;
|
|
295
|
+
if (!Operation) {
|
|
296
|
+
throw new Error('Operation model not found');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const operations = document.operations.map(
|
|
300
|
+
(op: {
|
|
301
|
+
hash: string;
|
|
302
|
+
index: number;
|
|
303
|
+
timestamp: Date;
|
|
304
|
+
input: JSON;
|
|
305
|
+
type: string;
|
|
306
|
+
scope: string;
|
|
307
|
+
}) => ({
|
|
308
|
+
hash: op.hash,
|
|
309
|
+
index: op.index,
|
|
310
|
+
timestamp: new Date(op.timestamp).toISOString(),
|
|
311
|
+
input: op.input,
|
|
312
|
+
type: op.type,
|
|
313
|
+
scope: op.scope as OperationScope
|
|
314
|
+
// attachments: fileRegistry
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const doc = {
|
|
319
|
+
created: document.createdAt.toISOString(),
|
|
320
|
+
name: document.name ? document.name : '',
|
|
321
|
+
documentType: document.documentType,
|
|
322
|
+
initialState: document.initialState,
|
|
323
|
+
lastModified: document.updatedAt.toISOString(),
|
|
324
|
+
operations: {
|
|
325
|
+
global: operations.filter(
|
|
326
|
+
(op: Operation) => op.scope === 'global'
|
|
327
|
+
),
|
|
328
|
+
local: operations.filter(
|
|
329
|
+
(op: Operation) => op.scope === 'local'
|
|
330
|
+
)
|
|
331
|
+
},
|
|
332
|
+
revision: document.revision
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
return doc;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async deleteDocument(drive: string, id: string) {
|
|
339
|
+
const Document = this.db.models.document;
|
|
340
|
+
if (!Document) {
|
|
341
|
+
throw new Error('Document model not found');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
await Document.destroy({
|
|
345
|
+
where: {
|
|
346
|
+
id: id,
|
|
347
|
+
driveId: drive
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async getDrives() {
|
|
353
|
+
return this.getDocuments('drives');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async getDrive(id: string) {
|
|
357
|
+
const doc = await this.getDocument('drives', id);
|
|
358
|
+
return doc as DocumentDriveStorage;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async deleteDrive(id: string) {
|
|
362
|
+
await this.deleteDocument('drives', id);
|
|
363
|
+
|
|
364
|
+
const Document = this.db.models.document;
|
|
365
|
+
if (!Document) {
|
|
366
|
+
throw new Error('Document model not found');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
await Document.destroy({
|
|
370
|
+
where: {
|
|
371
|
+
driveId: id
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|