document-drive 0.0.27 → 0.0.29
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 +18 -10
- package/src/server/error.ts +18 -0
- package/src/server/index.ts +753 -85
- package/src/server/listener/index.ts +2 -0
- package/src/server/listener/manager.ts +382 -0
- package/src/server/listener/transmitter/index.ts +3 -0
- package/src/server/listener/transmitter/pull-responder.ts +325 -0
- package/src/server/listener/transmitter/switchboard-push.ts +63 -0
- package/src/server/listener/transmitter/types.ts +18 -0
- package/src/server/types.ts +209 -23
- package/src/storage/browser.ts +9 -3
- package/src/storage/filesystem.ts +11 -5
- package/src/storage/index.ts +0 -4
- package/src/storage/memory.ts +14 -5
- package/src/storage/prisma.ts +79 -24
- package/src/storage/types.ts +2 -1
- package/src/utils/graphql.ts +46 -0
- package/src/utils/index.ts +77 -0
- package/src/utils.ts +0 -32
package/src/storage/prisma.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Prisma } from '@prisma/client';
|
|
1
|
+
import { PrismaClient, type Prisma } from '@prisma/client';
|
|
2
2
|
import {
|
|
3
3
|
DocumentDriveLocalState,
|
|
4
4
|
DocumentDriveState
|
|
5
5
|
} from 'document-model-libs/document-drive';
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
DocumentHeader,
|
|
8
8
|
ExtendedState,
|
|
9
9
|
Operation,
|
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
import { DocumentDriveStorage, DocumentStorage, IDriveStorage } from './types';
|
|
13
13
|
|
|
14
14
|
export class PrismaStorage implements IDriveStorage {
|
|
15
|
-
private db:
|
|
15
|
+
private db: PrismaClient;
|
|
16
16
|
|
|
17
|
-
constructor(db:
|
|
17
|
+
constructor(db: PrismaClient) {
|
|
18
18
|
this.db = db;
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
async createDrive(id: string, drive: DocumentDriveStorage): Promise<void> {
|
|
22
|
+
// drive for all drive documents
|
|
21
23
|
await this.createDocument('drives', id, drive as DocumentStorage);
|
|
22
24
|
}
|
|
23
25
|
async addDriveOperations(
|
|
@@ -27,6 +29,7 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
27
29
|
): Promise<void> {
|
|
28
30
|
await this.addDocumentOperations('drives', id, operations, header);
|
|
29
31
|
}
|
|
32
|
+
|
|
30
33
|
async createDocument(
|
|
31
34
|
drive: string,
|
|
32
35
|
id: string,
|
|
@@ -84,7 +87,8 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
84
87
|
timestamp: op.timestamp,
|
|
85
88
|
type: op.type,
|
|
86
89
|
scope: op.scope,
|
|
87
|
-
branch: 'main'
|
|
90
|
+
branch: 'main',
|
|
91
|
+
skip: op.skip
|
|
88
92
|
},
|
|
89
93
|
update: {
|
|
90
94
|
driveId: drive,
|
|
@@ -95,18 +99,17 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
95
99
|
timestamp: op.timestamp,
|
|
96
100
|
type: op.type,
|
|
97
101
|
scope: op.scope,
|
|
98
|
-
branch: 'main'
|
|
102
|
+
branch: 'main',
|
|
103
|
+
skip: op.skip
|
|
99
104
|
}
|
|
100
105
|
});
|
|
101
106
|
})
|
|
102
107
|
);
|
|
103
108
|
|
|
104
|
-
await this.db.document.
|
|
109
|
+
await this.db.document.updateMany({
|
|
105
110
|
where: {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
driveId: 'drives'
|
|
109
|
-
}
|
|
111
|
+
id,
|
|
112
|
+
driveId: drive
|
|
110
113
|
},
|
|
111
114
|
data: {
|
|
112
115
|
lastModified: header.lastModified,
|
|
@@ -163,6 +166,9 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
163
166
|
},
|
|
164
167
|
include: {
|
|
165
168
|
operations: {
|
|
169
|
+
orderBy: {
|
|
170
|
+
index: 'asc'
|
|
171
|
+
},
|
|
166
172
|
include: {
|
|
167
173
|
attachments: true
|
|
168
174
|
}
|
|
@@ -175,7 +181,6 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
const dbDoc = result;
|
|
178
|
-
|
|
179
184
|
const doc = {
|
|
180
185
|
created: dbDoc.created.toISOString(),
|
|
181
186
|
name: dbDoc.name ? dbDoc.name : '',
|
|
@@ -184,11 +189,12 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
184
189
|
DocumentDriveState,
|
|
185
190
|
DocumentDriveLocalState
|
|
186
191
|
>,
|
|
187
|
-
lastModified: dbDoc.lastModified.toISOString(),
|
|
192
|
+
lastModified: new Date(dbDoc.lastModified).toISOString(),
|
|
188
193
|
operations: {
|
|
189
194
|
global: dbDoc.operations
|
|
190
|
-
.filter(op => op.scope === 'global')
|
|
195
|
+
.filter(op => op.scope === 'global' && !op.clipboard)
|
|
191
196
|
.map(op => ({
|
|
197
|
+
skip: op.skip,
|
|
192
198
|
hash: op.hash,
|
|
193
199
|
index: op.index,
|
|
194
200
|
timestamp: new Date(op.timestamp).toISOString(),
|
|
@@ -198,8 +204,9 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
198
204
|
// attachments: fileRegistry
|
|
199
205
|
})),
|
|
200
206
|
local: dbDoc.operations
|
|
201
|
-
.filter(op => op.scope === 'local')
|
|
207
|
+
.filter(op => op.scope === 'local' && !op.clipboard)
|
|
202
208
|
.map(op => ({
|
|
209
|
+
skip: op.skip,
|
|
203
210
|
hash: op.hash,
|
|
204
211
|
index: op.index,
|
|
205
212
|
timestamp: new Date(op.timestamp).toISOString(),
|
|
@@ -209,19 +216,67 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
209
216
|
// attachments: fileRegistry
|
|
210
217
|
}))
|
|
211
218
|
},
|
|
212
|
-
|
|
219
|
+
clipboard: dbDoc.operations
|
|
220
|
+
.filter(op => op.clipboard)
|
|
221
|
+
.map(op => ({
|
|
222
|
+
skip: op.skip,
|
|
223
|
+
hash: op.hash,
|
|
224
|
+
index: op.index,
|
|
225
|
+
timestamp: new Date(op.timestamp).toISOString(),
|
|
226
|
+
input: op.input,
|
|
227
|
+
type: op.type,
|
|
228
|
+
scope: op.scope as OperationScope
|
|
229
|
+
// attachments: fileRegistry
|
|
230
|
+
})),
|
|
231
|
+
revision: dbDoc.revision as Record<OperationScope, number>
|
|
213
232
|
};
|
|
214
233
|
|
|
215
234
|
return doc;
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
async deleteDocument(drive: string, id: string) {
|
|
219
|
-
await this.db.
|
|
238
|
+
await this.db.attachment.deleteMany({
|
|
220
239
|
where: {
|
|
221
240
|
driveId: drive,
|
|
222
|
-
|
|
241
|
+
documentId: id
|
|
223
242
|
}
|
|
224
243
|
});
|
|
244
|
+
|
|
245
|
+
await this.db.operation.deleteMany({
|
|
246
|
+
where: {
|
|
247
|
+
driveId: drive,
|
|
248
|
+
documentId: id
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await this.db.document.delete({
|
|
253
|
+
where: {
|
|
254
|
+
id_driveId: {
|
|
255
|
+
driveId: drive,
|
|
256
|
+
id: id
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (drive === 'drives') {
|
|
262
|
+
await this.db.attachment.deleteMany({
|
|
263
|
+
where: {
|
|
264
|
+
driveId: id
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await this.db.operation.deleteMany({
|
|
269
|
+
where: {
|
|
270
|
+
driveId: id
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await this.db.document.deleteMany({
|
|
275
|
+
where: {
|
|
276
|
+
driveId: id
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
225
280
|
}
|
|
226
281
|
|
|
227
282
|
async getDrives() {
|
|
@@ -229,15 +284,15 @@ export class PrismaStorage implements IDriveStorage {
|
|
|
229
284
|
}
|
|
230
285
|
|
|
231
286
|
async getDrive(id: string) {
|
|
232
|
-
|
|
287
|
+
try {
|
|
288
|
+
const doc = await this.getDocument('drives', id);
|
|
289
|
+
return doc as DocumentDriveStorage;
|
|
290
|
+
} catch (e) {
|
|
291
|
+
throw new Error(`Drive with id ${id} not found`);
|
|
292
|
+
}
|
|
233
293
|
}
|
|
234
294
|
|
|
235
295
|
async deleteDrive(id: string) {
|
|
236
296
|
await this.deleteDocument('drives', id);
|
|
237
|
-
await this.db.document.deleteMany({
|
|
238
|
-
where: {
|
|
239
|
-
driveId: id
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
297
|
}
|
|
243
298
|
}
|
package/src/storage/types.ts
CHANGED
|
@@ -27,7 +27,8 @@ export interface IStorage {
|
|
|
27
27
|
drive: string,
|
|
28
28
|
id: string,
|
|
29
29
|
operations: Operation[],
|
|
30
|
-
header: DocumentHeader
|
|
30
|
+
header: DocumentHeader,
|
|
31
|
+
updatedOperations?: Operation[]
|
|
31
32
|
): Promise<void>;
|
|
32
33
|
deleteDocument(drive: string, id: string): Promise<void>;
|
|
33
34
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import request, { GraphQLClient, gql } from 'graphql-request';
|
|
2
|
+
|
|
3
|
+
export { gql } from 'graphql-request';
|
|
4
|
+
|
|
5
|
+
export type DriveInfo = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// replaces fetch so it can be used in Node and Browser envs
|
|
13
|
+
export async function requestGraphql<T>(...args: Parameters<typeof request>) {
|
|
14
|
+
const [url, ...requestArgs] = args;
|
|
15
|
+
const client = new GraphQLClient(url, { fetch });
|
|
16
|
+
return client.request<T>(...requestArgs);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function requestPublicDrive(url: string): Promise<DriveInfo> {
|
|
20
|
+
let drive: DriveInfo;
|
|
21
|
+
try {
|
|
22
|
+
const result = await requestGraphql<{ drive: DriveInfo }>(
|
|
23
|
+
url,
|
|
24
|
+
gql`
|
|
25
|
+
query getDrive {
|
|
26
|
+
drive {
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
icon
|
|
30
|
+
slug
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`
|
|
34
|
+
);
|
|
35
|
+
drive = result.drive;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(e);
|
|
38
|
+
throw new Error("Couldn't find drive info");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!drive) {
|
|
42
|
+
throw new Error('Drive not found');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return drive;
|
|
46
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentDriveDocument,
|
|
3
|
+
documentModel as DocumentDriveModel,
|
|
4
|
+
z
|
|
5
|
+
} from 'document-model-libs/document-drive';
|
|
6
|
+
import {
|
|
7
|
+
Action,
|
|
8
|
+
BaseAction,
|
|
9
|
+
Document,
|
|
10
|
+
DocumentOperations,
|
|
11
|
+
Operation
|
|
12
|
+
} from 'document-model/document';
|
|
13
|
+
|
|
14
|
+
export function isDocumentDrive(
|
|
15
|
+
document: Document
|
|
16
|
+
): document is DocumentDriveDocument {
|
|
17
|
+
return (
|
|
18
|
+
document.documentType === DocumentDriveModel.id &&
|
|
19
|
+
z.DocumentDriveStateSchema().safeParse(document.state.global).success
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function mergeOperations<A extends Action = Action>(
|
|
24
|
+
currentOperations: DocumentOperations<A>,
|
|
25
|
+
newOperations: Operation<A | BaseAction>[]
|
|
26
|
+
): DocumentOperations<A> {
|
|
27
|
+
return newOperations.reduce((acc, curr) => {
|
|
28
|
+
const operations = acc[curr.scope] ?? [];
|
|
29
|
+
acc[curr.scope] = [...operations, curr] as Operation<A>[];
|
|
30
|
+
return acc;
|
|
31
|
+
}, currentOperations);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function generateUUID(): string {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
36
|
+
const crypto =
|
|
37
|
+
typeof window !== 'undefined' ? window.crypto : require('crypto');
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
39
|
+
return crypto.randomUUID() as string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function applyUpdatedOperations<A extends Action = Action>(
|
|
43
|
+
currentOperations: DocumentOperations<A>,
|
|
44
|
+
updatedOperations: Operation<A | BaseAction>[]
|
|
45
|
+
): DocumentOperations<A> {
|
|
46
|
+
return updatedOperations.reduce(
|
|
47
|
+
(acc, curr) => {
|
|
48
|
+
const operations = acc[curr.scope] ?? [];
|
|
49
|
+
acc[curr.scope] = operations.map(op => {
|
|
50
|
+
return op.index === curr.index ? curr : op;
|
|
51
|
+
});
|
|
52
|
+
return acc;
|
|
53
|
+
},
|
|
54
|
+
{ ...currentOperations }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isNoopUpdate(
|
|
59
|
+
operation: Operation,
|
|
60
|
+
latestOperation?: Operation
|
|
61
|
+
) {
|
|
62
|
+
if (!latestOperation) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isNoopOp = operation.type === 'NOOP';
|
|
67
|
+
const isNoopLatestOp = latestOperation.type === 'NOOP';
|
|
68
|
+
const isSameIndexOp = operation.index === latestOperation.index;
|
|
69
|
+
const isSkipOpGreaterThanLatestOp = operation.skip > latestOperation.skip;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
isNoopOp &&
|
|
73
|
+
isNoopLatestOp &&
|
|
74
|
+
isSameIndexOp &&
|
|
75
|
+
isSkipOpGreaterThanLatestOp
|
|
76
|
+
);
|
|
77
|
+
}
|
package/src/utils.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DocumentDriveDocument,
|
|
3
|
-
documentModel as DocumentDriveModel,
|
|
4
|
-
z
|
|
5
|
-
} from 'document-model-libs/document-drive';
|
|
6
|
-
import {
|
|
7
|
-
Action,
|
|
8
|
-
BaseAction,
|
|
9
|
-
Document,
|
|
10
|
-
DocumentOperations,
|
|
11
|
-
Operation
|
|
12
|
-
} from 'document-model/document';
|
|
13
|
-
|
|
14
|
-
export function isDocumentDrive(
|
|
15
|
-
document: Document
|
|
16
|
-
): document is DocumentDriveDocument {
|
|
17
|
-
return (
|
|
18
|
-
document.documentType === DocumentDriveModel.id &&
|
|
19
|
-
z.DocumentDriveStateSchema().safeParse(document.state.global).success
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function mergeOperations<A extends Action = Action>(
|
|
24
|
-
currentOperations: DocumentOperations<A>,
|
|
25
|
-
newOperations: Operation<A | BaseAction>[]
|
|
26
|
-
): DocumentOperations<A> {
|
|
27
|
-
return newOperations.reduce((acc, curr) => {
|
|
28
|
-
const operations = acc[curr.scope] ?? [];
|
|
29
|
-
acc[curr.scope] = [...operations, curr] as Operation<A>[];
|
|
30
|
-
return acc;
|
|
31
|
-
}, currentOperations);
|
|
32
|
-
}
|