document-drive 1.0.0-alpha.52 → 1.0.0-alpha.53
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 +1 -1
- package/src/server/index.ts +75 -45
- package/src/storage/browser.ts +13 -0
- package/src/storage/filesystem.ts +12 -0
- package/src/storage/memory.ts +13 -0
- package/src/storage/prisma.ts +26 -1
- package/src/storage/sequelize.ts +37 -0
- package/src/storage/types.ts +2 -0
- package/src/utils/index.ts +1 -0
package/package.json
CHANGED
package/src/server/index.ts
CHANGED
|
@@ -122,16 +122,16 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
122
122
|
|
|
123
123
|
const result = await (!strand.documentId
|
|
124
124
|
? this.addDriveOperations(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
strand.driveId,
|
|
126
|
+
operations as Operation<DocumentDriveAction | BaseAction>[],
|
|
127
|
+
false
|
|
128
|
+
)
|
|
129
129
|
: this.addOperations(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
strand.driveId,
|
|
131
|
+
strand.documentId,
|
|
132
|
+
operations,
|
|
133
|
+
false
|
|
134
|
+
));
|
|
135
135
|
|
|
136
136
|
if (result.status === 'ERROR') {
|
|
137
137
|
this.updateSyncStatus(strand.driveId, result.status, result.error);
|
|
@@ -299,14 +299,14 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
299
299
|
const nodeUnits =
|
|
300
300
|
scope?.length || branch?.length
|
|
301
301
|
? node.synchronizationUnits.filter(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
302
|
+
unit =>
|
|
303
|
+
(!scope?.length ||
|
|
304
|
+
scope.includes(unit.scope) ||
|
|
305
|
+
scope.includes('*')) &&
|
|
306
|
+
(!branch?.length ||
|
|
307
|
+
branch.includes(unit.branch) ||
|
|
308
|
+
branch.includes('*'))
|
|
309
|
+
)
|
|
310
310
|
: node.synchronizationUnits;
|
|
311
311
|
if (!nodeUnits.length) {
|
|
312
312
|
continue;
|
|
@@ -494,19 +494,7 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
494
494
|
logger.error('Error getting drive from cache', e);
|
|
495
495
|
}
|
|
496
496
|
const driveStorage = await this.storage.getDrive(drive);
|
|
497
|
-
const
|
|
498
|
-
const document = baseUtils.replayDocument(
|
|
499
|
-
driveStorage.initialState,
|
|
500
|
-
filterOperationsByRevision(
|
|
501
|
-
driveStorage.operations,
|
|
502
|
-
options?.revisions
|
|
503
|
-
),
|
|
504
|
-
documentModel.reducer,
|
|
505
|
-
undefined,
|
|
506
|
-
driveStorage,
|
|
507
|
-
undefined,
|
|
508
|
-
{ checkHashes: false }
|
|
509
|
-
);
|
|
497
|
+
const document = this._replayDocument(driveStorage, options);
|
|
510
498
|
if (!isDocumentDrive(document)) {
|
|
511
499
|
throw new Error(
|
|
512
500
|
`Document with id ${drive} is not a Document Drive`
|
|
@@ -519,6 +507,30 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
519
507
|
}
|
|
520
508
|
}
|
|
521
509
|
|
|
510
|
+
async getDriveBySlug(slug: string, options?: GetDocumentOptions) {
|
|
511
|
+
try {
|
|
512
|
+
const document = await this.cache.getDocument('drives', slug);
|
|
513
|
+
if (document && isDocumentDrive(document)) {
|
|
514
|
+
return document;
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
logger.error('Error getting drive from cache', e);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const driveStorage = await this.storage.getDriveBySlug(slug);
|
|
521
|
+
const document = this._replayDocument(driveStorage, options);
|
|
522
|
+
if (!isDocumentDrive(document)) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`Document with slug ${slug} is not a Document Drive`
|
|
525
|
+
);
|
|
526
|
+
} else {
|
|
527
|
+
this.cache
|
|
528
|
+
.setDocument('drives', slug, document)
|
|
529
|
+
.catch(logger.error);
|
|
530
|
+
return document;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
522
534
|
async getDocument(drive: string, id: string, options?: GetDocumentOptions) {
|
|
523
535
|
try {
|
|
524
536
|
const document = await this.cache.getDocument(drive, id);
|
|
@@ -664,11 +676,11 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
664
676
|
e instanceof OperationError
|
|
665
677
|
? e
|
|
666
678
|
: new OperationError(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
679
|
+
'ERROR',
|
|
680
|
+
nextOperation,
|
|
681
|
+
(e as Error).message,
|
|
682
|
+
(e as Error).cause
|
|
683
|
+
);
|
|
672
684
|
|
|
673
685
|
// TODO: don't break on errors...
|
|
674
686
|
break;
|
|
@@ -925,11 +937,11 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
925
937
|
error instanceof OperationError
|
|
926
938
|
? error
|
|
927
939
|
: new OperationError(
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
940
|
+
'ERROR',
|
|
941
|
+
undefined,
|
|
942
|
+
(error as Error).message,
|
|
943
|
+
(error as Error).cause
|
|
944
|
+
);
|
|
933
945
|
|
|
934
946
|
return {
|
|
935
947
|
status: operationError.status,
|
|
@@ -1097,11 +1109,11 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
1097
1109
|
error instanceof OperationError
|
|
1098
1110
|
? error
|
|
1099
1111
|
: new OperationError(
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1112
|
+
'ERROR',
|
|
1113
|
+
undefined,
|
|
1114
|
+
(error as Error).message,
|
|
1115
|
+
(error as Error).cause
|
|
1116
|
+
);
|
|
1105
1117
|
|
|
1106
1118
|
return {
|
|
1107
1119
|
status: operationError.status,
|
|
@@ -1268,4 +1280,22 @@ export class DocumentDriveServer extends BaseDocumentDriveServer {
|
|
|
1268
1280
|
logger.debug(`Emitting event ${event}`, args);
|
|
1269
1281
|
return this.emitter.emit(event, ...args);
|
|
1270
1282
|
}
|
|
1283
|
+
|
|
1284
|
+
private _replayDocument(documentStorage: DocumentStorage, options?: GetDocumentOptions) {
|
|
1285
|
+
const documentModel = this._getDocumentModel(documentStorage.documentType);
|
|
1286
|
+
const document = baseUtils.replayDocument(
|
|
1287
|
+
documentStorage.initialState,
|
|
1288
|
+
filterOperationsByRevision(
|
|
1289
|
+
documentStorage.operations,
|
|
1290
|
+
options?.revisions
|
|
1291
|
+
),
|
|
1292
|
+
documentModel.reducer,
|
|
1293
|
+
undefined,
|
|
1294
|
+
documentStorage,
|
|
1295
|
+
undefined,
|
|
1296
|
+
{ checkHashes: false }
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
return document;
|
|
1300
|
+
}
|
|
1271
1301
|
}
|
package/src/storage/browser.ts
CHANGED
|
@@ -111,6 +111,19 @@ export class BrowserStorage implements IDriveStorage {
|
|
|
111
111
|
return drive;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
async getDriveBySlug(slug: string) {
|
|
115
|
+
// get oldes drives first
|
|
116
|
+
const drives = (await this.getDrives()).reverse();
|
|
117
|
+
for (const drive of drives) {
|
|
118
|
+
const driveData = await this.getDrive(drive);
|
|
119
|
+
if (driveData.initialState.state.global.slug === slug) {
|
|
120
|
+
return this.getDrive(drive);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
async createDrive(id: string, drive: DocumentDriveStorage) {
|
|
115
128
|
const db = await this.db;
|
|
116
129
|
await db.setItem(this.buildKey(BrowserStorage.DRIVES_KEY, id), drive);
|
|
@@ -197,6 +197,18 @@ export class FilesystemStorage implements IDriveStorage {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
async getDriveBySlug(slug: string) {
|
|
201
|
+
// get oldes drives first
|
|
202
|
+
const drives = (await this.getDrives()).reverse();
|
|
203
|
+
for (const drive of drives) {
|
|
204
|
+
const { initialState: { state: { global: { slug: driveSlug } } } } = await this.getDrive(drive);
|
|
205
|
+
if (driveSlug === slug) {
|
|
206
|
+
return this.getDrive(drive);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
210
|
+
}
|
|
211
|
+
|
|
200
212
|
createDrive(id: string, drive: DocumentDriveStorage) {
|
|
201
213
|
return this.createDocument(FilesystemStorage.DRIVES_DIR, id, drive);
|
|
202
214
|
}
|
package/src/storage/memory.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
|
11
11
|
export class MemoryStorage implements IDriveStorage {
|
|
12
12
|
private documents: Record<string, Record<string, DocumentStorage>>;
|
|
13
13
|
private drives: Record<string, DocumentDriveStorage>;
|
|
14
|
+
private slugToDriveId: Record<string, string> = {};
|
|
14
15
|
|
|
15
16
|
constructor() {
|
|
16
17
|
this.documents = {};
|
|
@@ -116,8 +117,20 @@ export class MemoryStorage implements IDriveStorage {
|
|
|
116
117
|
return drive;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
async getDriveBySlug(slug: string) {
|
|
121
|
+
const driveId = this.slugToDriveId[slug];
|
|
122
|
+
if (!driveId) {
|
|
123
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
124
|
+
}
|
|
125
|
+
return this.getDrive(driveId);
|
|
126
|
+
}
|
|
127
|
+
|
|
119
128
|
async createDrive(id: string, drive: DocumentDriveStorage) {
|
|
120
129
|
this.drives[id] = drive;
|
|
130
|
+
const { slug } = drive.initialState.state.global;
|
|
131
|
+
if (slug) {
|
|
132
|
+
this.slugToDriveId[slug] = id;
|
|
133
|
+
}
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
async addDriveOperations(
|
package/src/storage/prisma.ts
CHANGED
|
@@ -17,7 +17,6 @@ import type {
|
|
|
17
17
|
import { ConflictOperationError } from '../server/error';
|
|
18
18
|
import { logger } from '../utils/logger';
|
|
19
19
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
20
|
-
import { JitterType } from 'exponential-backoff/dist/options';
|
|
21
20
|
|
|
22
21
|
type Transaction = Omit<
|
|
23
22
|
PrismaClient<Prisma.PrismaClientOptions, never>,
|
|
@@ -79,6 +78,18 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
79
78
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
80
79
|
// drive for all drive documents
|
|
81
80
|
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
81
|
+
const count = await this.db.drive.upsert({
|
|
82
|
+
where: {
|
|
83
|
+
slug: drive.initialState.state.global.slug ?? id
|
|
84
|
+
},
|
|
85
|
+
create: {
|
|
86
|
+
id: id,
|
|
87
|
+
slug: drive.initialState.state.global.slug ?? id
|
|
88
|
+
},
|
|
89
|
+
update: {
|
|
90
|
+
id
|
|
91
|
+
}
|
|
92
|
+
});
|
|
82
93
|
}
|
|
83
94
|
async addDriveOperations(
|
|
84
95
|
id: string,
|
|
@@ -391,6 +402,20 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
391
402
|
}
|
|
392
403
|
}
|
|
393
404
|
|
|
405
|
+
async getDriveBySlug(slug: string) {
|
|
406
|
+
const driveEntity = await this.db.drive.findFirst({
|
|
407
|
+
where: {
|
|
408
|
+
slug
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
if (!driveEntity) {
|
|
413
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return this.getDrive(driveEntity.id);
|
|
417
|
+
}
|
|
418
|
+
|
|
394
419
|
async deleteDrive(id: string) {
|
|
395
420
|
await this.db.document.deleteMany({
|
|
396
421
|
where: {
|
package/src/storage/sequelize.ts
CHANGED
|
@@ -20,6 +20,13 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
public syncModels() {
|
|
23
|
+
const Drive = this.db.define('drive', {
|
|
24
|
+
slug: {
|
|
25
|
+
type: DataTypes.STRING,
|
|
26
|
+
primaryKey: true
|
|
27
|
+
},
|
|
28
|
+
id: DataTypes.STRING,
|
|
29
|
+
})
|
|
23
30
|
const Document = this.db.define('document', {
|
|
24
31
|
id: {
|
|
25
32
|
type: DataTypes.STRING,
|
|
@@ -113,6 +120,8 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
113
120
|
|
|
114
121
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
115
122
|
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
123
|
+
const Drive = this.db.models.drive;
|
|
124
|
+
await Drive?.upsert({ id, slug: drive.initialState.state.global.slug });
|
|
116
125
|
}
|
|
117
126
|
async addDriveOperations(
|
|
118
127
|
id: string,
|
|
@@ -379,6 +388,25 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
379
388
|
return doc as DocumentDriveStorage;
|
|
380
389
|
}
|
|
381
390
|
|
|
391
|
+
async getDriveBySlug(slug: string) {
|
|
392
|
+
const Drive = this.db.models.drive;
|
|
393
|
+
if (!Drive) {
|
|
394
|
+
throw new Error('Drive model not found');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const driveEntity = await Drive.findOne({
|
|
398
|
+
where: {
|
|
399
|
+
slug
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
if (!driveEntity) {
|
|
404
|
+
throw new Error(`Drive with slug ${slug} not found`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return this.getDrive(driveEntity.dataValues.id);
|
|
408
|
+
}
|
|
409
|
+
|
|
382
410
|
async deleteDrive(id: string) {
|
|
383
411
|
await this.deleteDocument('drives', id);
|
|
384
412
|
|
|
@@ -392,5 +420,14 @@ export class SequelizeStorage implements IDriveStorage {
|
|
|
392
420
|
driveId: id
|
|
393
421
|
}
|
|
394
422
|
});
|
|
423
|
+
|
|
424
|
+
const Drive = this.db.models.drive;
|
|
425
|
+
if (Drive) {
|
|
426
|
+
await Drive.destroy({
|
|
427
|
+
where: {
|
|
428
|
+
id: id
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
395
432
|
}
|
|
396
433
|
}
|
package/src/storage/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
DocumentHeader,
|
|
9
9
|
Operation
|
|
10
10
|
} from 'document-model/document';
|
|
11
|
+
import { GetDocumentOptions } from '../server';
|
|
11
12
|
|
|
12
13
|
export type DocumentStorage<D extends Document = Document> = Omit<
|
|
13
14
|
D,
|
|
@@ -45,6 +46,7 @@ export interface IStorage {
|
|
|
45
46
|
export interface IDriveStorage extends IStorage {
|
|
46
47
|
getDrives(): Promise<string[]>;
|
|
47
48
|
getDrive(id: string): Promise<DocumentDriveStorage>;
|
|
49
|
+
getDriveBySlug(slug: string): Promise<DocumentDriveStorage>;
|
|
48
50
|
createDrive(id: string, drive: DocumentDriveStorage): Promise<void>;
|
|
49
51
|
deleteDrive(id: string): Promise<void>;
|
|
50
52
|
clearStorage?(): Promise<void>;
|
package/src/utils/index.ts
CHANGED