document-drive 1.0.0-websockets → 1.0.0
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/utils/graphql.ts
CHANGED
|
@@ -1,47 +1,301 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { pascalCase } from "change-case";
|
|
2
|
+
import {
|
|
3
|
+
DocumentDriveLocalState,
|
|
4
|
+
FileNode,
|
|
5
|
+
FolderNode,
|
|
6
|
+
} from "document-model-libs/document-drive";
|
|
7
|
+
import { Document, DocumentModel, Operation } from "document-model/document";
|
|
8
|
+
import { DocumentModelState } from "document-model/document-model";
|
|
9
|
+
import {
|
|
10
|
+
BuildSchemaOptions,
|
|
11
|
+
GraphQLError,
|
|
12
|
+
GraphQLList,
|
|
13
|
+
GraphQLNonNull,
|
|
14
|
+
GraphQLObjectType,
|
|
15
|
+
GraphQLOutputType,
|
|
16
|
+
GraphQLScalarType,
|
|
17
|
+
GraphQLUnionType,
|
|
18
|
+
ParseOptions,
|
|
19
|
+
buildSchema,
|
|
20
|
+
} from "graphql";
|
|
21
|
+
import request, { GraphQLClient, gql } from "graphql-request";
|
|
22
|
+
import {
|
|
23
|
+
InferDocumentLocalState,
|
|
24
|
+
InferDocumentOperation,
|
|
25
|
+
InferDocumentState,
|
|
26
|
+
} from "../read-mode/types";
|
|
27
|
+
import { logger } from "./logger";
|
|
3
28
|
|
|
4
|
-
export { gql } from
|
|
29
|
+
export { gql } from "graphql-request";
|
|
5
30
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
31
|
+
type ReqGraphQLError = {
|
|
32
|
+
message: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type GraphQLResult<T> = { [K in keyof T]: T[K] | null } & {
|
|
36
|
+
errors?: GraphQLError[];
|
|
11
37
|
};
|
|
12
38
|
|
|
13
39
|
// replaces fetch so it can be used in Node and Browser envs
|
|
14
|
-
export async function requestGraphql<T>(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
40
|
+
export async function requestGraphql<T>(
|
|
41
|
+
...args: Parameters<typeof request>
|
|
42
|
+
): Promise<GraphQLResult<T>> {
|
|
43
|
+
const [url, ...requestArgs] = args;
|
|
44
|
+
const client = new GraphQLClient(url, { fetch });
|
|
45
|
+
const { errors, ...response } = await client.request<
|
|
46
|
+
{ [K in keyof T]: T[K] | null } & { errors?: ReqGraphQLError[] }
|
|
47
|
+
>(...requestArgs);
|
|
48
|
+
|
|
49
|
+
const result = { ...response } as GraphQLResult<T>;
|
|
50
|
+
if (errors?.length) {
|
|
51
|
+
result.errors = errors.map(
|
|
52
|
+
({ message, ...options }) => new GraphQLError(message, options),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type DriveInfo = {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
slug: string;
|
|
62
|
+
icon?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function getFields(type: GraphQLOutputType): string {
|
|
66
|
+
if (type instanceof GraphQLObjectType) {
|
|
67
|
+
return Object.entries(type.getFields())
|
|
68
|
+
.map(([fieldName, field]) => {
|
|
69
|
+
const fieldType =
|
|
70
|
+
field.type instanceof GraphQLNonNull ? field.type.ofType : field.type;
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
fieldType instanceof GraphQLObjectType ||
|
|
74
|
+
fieldType instanceof GraphQLUnionType
|
|
75
|
+
) {
|
|
76
|
+
return `${fieldName} { ${getFields(fieldType)} }`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (fieldType instanceof GraphQLList) {
|
|
80
|
+
const listItemType =
|
|
81
|
+
fieldType.ofType instanceof GraphQLNonNull
|
|
82
|
+
? fieldType.ofType.ofType
|
|
83
|
+
: fieldType.ofType;
|
|
84
|
+
|
|
85
|
+
if (listItemType instanceof GraphQLScalarType) {
|
|
86
|
+
return fieldName;
|
|
87
|
+
} else if (
|
|
88
|
+
listItemType instanceof GraphQLObjectType ||
|
|
89
|
+
listItemType instanceof GraphQLUnionType
|
|
90
|
+
) {
|
|
91
|
+
return `${fieldName} { ${getFields(listItemType)} }`;
|
|
92
|
+
} else {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`List item type ${listItemType.toString()} is not handled`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return fieldName;
|
|
100
|
+
})
|
|
101
|
+
.join(" ");
|
|
102
|
+
} else if (type instanceof GraphQLUnionType) {
|
|
103
|
+
return type
|
|
104
|
+
.getTypes()
|
|
105
|
+
.map((unionType) => {
|
|
106
|
+
return `... on ${unionType.name} { ${getFields(unionType)} }`;
|
|
107
|
+
})
|
|
108
|
+
.join(" ");
|
|
109
|
+
}
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function generateDocumentStateQueryFields(
|
|
114
|
+
documentModel: DocumentModelState,
|
|
115
|
+
options?: BuildSchemaOptions & ParseOptions,
|
|
116
|
+
): string {
|
|
117
|
+
const name = pascalCase(documentModel.name);
|
|
118
|
+
const spec = documentModel.specifications.at(-1);
|
|
119
|
+
if (!spec) {
|
|
120
|
+
throw new Error("No document model specification found");
|
|
121
|
+
}
|
|
122
|
+
const source = `${spec.state.global.schema} type Query { ${name}: ${name}State }`;
|
|
123
|
+
const schema = buildSchema(source, options);
|
|
124
|
+
const queryType = schema.getQueryType();
|
|
125
|
+
if (!queryType) {
|
|
126
|
+
throw new Error("No query type found");
|
|
127
|
+
}
|
|
128
|
+
const fields = queryType.getFields();
|
|
129
|
+
const stateQuery = fields[name];
|
|
130
|
+
|
|
131
|
+
if (!stateQuery) {
|
|
132
|
+
throw new Error("No state query found");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const queryFields = getFields(stateQuery.type);
|
|
136
|
+
return queryFields;
|
|
18
137
|
}
|
|
19
138
|
|
|
20
139
|
export async function requestPublicDrive(url: string): Promise<DriveInfo> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
140
|
+
let drive: DriveInfo;
|
|
141
|
+
try {
|
|
142
|
+
const result = await requestGraphql<{ drive: DriveInfo }>(
|
|
143
|
+
url,
|
|
144
|
+
gql`
|
|
145
|
+
query getDrive {
|
|
146
|
+
drive {
|
|
147
|
+
id
|
|
148
|
+
name
|
|
149
|
+
icon
|
|
150
|
+
slug
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
`,
|
|
154
|
+
);
|
|
155
|
+
if (result.errors?.length || !result.drive) {
|
|
156
|
+
throw result.errors?.at(0) ?? new Error("Drive not found");
|
|
157
|
+
}
|
|
158
|
+
drive = result.drive;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
logger.error(e);
|
|
161
|
+
throw new Error("Couldn't find drive info");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return drive;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type DriveState = DriveInfo &
|
|
168
|
+
Pick<DocumentDriveLocalState, "availableOffline" | "sharingType"> & {
|
|
169
|
+
nodes: Array<FolderNode | Omit<FileNode, "synchronizationUnits">>;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export type DocumentGraphQLResult<D extends Document> = Pick<
|
|
173
|
+
D,
|
|
174
|
+
"name" | "created" | "documentType" | "lastModified"
|
|
175
|
+
> & {
|
|
176
|
+
id: string;
|
|
177
|
+
revision: number;
|
|
178
|
+
state: InferDocumentState<D>;
|
|
179
|
+
initialState: InferDocumentState<D>;
|
|
180
|
+
operations: (Pick<
|
|
181
|
+
Operation,
|
|
182
|
+
| "id"
|
|
183
|
+
| "hash"
|
|
184
|
+
| "index"
|
|
185
|
+
| "skip"
|
|
186
|
+
| "timestamp"
|
|
187
|
+
| "type"
|
|
188
|
+
| "error"
|
|
189
|
+
| "context"
|
|
190
|
+
> & { inputText: string })[];
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export async function fetchDocument<D extends Document>(
|
|
194
|
+
url: string,
|
|
195
|
+
documentId: string,
|
|
196
|
+
documentModelLib: DocumentModel<
|
|
197
|
+
InferDocumentState<D>,
|
|
198
|
+
InferDocumentOperation<D>,
|
|
199
|
+
InferDocumentLocalState<D>
|
|
200
|
+
>,
|
|
201
|
+
): Promise<
|
|
202
|
+
GraphQLResult<{
|
|
203
|
+
document: Document<
|
|
204
|
+
InferDocumentState<D>,
|
|
205
|
+
InferDocumentOperation<D>,
|
|
206
|
+
InferDocumentLocalState<D>
|
|
207
|
+
>;
|
|
208
|
+
}>
|
|
209
|
+
> {
|
|
210
|
+
const { documentModel, utils } = documentModelLib;
|
|
211
|
+
const stateFields = generateDocumentStateQueryFields(documentModel);
|
|
212
|
+
const name = pascalCase(documentModel.name);
|
|
213
|
+
const result = await requestGraphql<{
|
|
214
|
+
document: DocumentGraphQLResult<D>;
|
|
215
|
+
}>(
|
|
216
|
+
url,
|
|
217
|
+
gql`
|
|
218
|
+
query ($id: String!) {
|
|
219
|
+
document(id: $id) {
|
|
220
|
+
id
|
|
221
|
+
name
|
|
222
|
+
created
|
|
223
|
+
documentType
|
|
224
|
+
lastModified
|
|
225
|
+
revision
|
|
226
|
+
operations {
|
|
28
227
|
id
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
228
|
+
error
|
|
229
|
+
hash
|
|
230
|
+
index
|
|
231
|
+
skip
|
|
232
|
+
timestamp
|
|
233
|
+
type
|
|
234
|
+
inputText
|
|
235
|
+
context {
|
|
236
|
+
signer {
|
|
237
|
+
user {
|
|
238
|
+
address
|
|
239
|
+
networkId
|
|
240
|
+
chainId
|
|
241
|
+
}
|
|
242
|
+
app {
|
|
243
|
+
name
|
|
244
|
+
key
|
|
245
|
+
}
|
|
246
|
+
signatures
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
... on ${name} {
|
|
251
|
+
state {
|
|
252
|
+
${stateFields}
|
|
253
|
+
}
|
|
254
|
+
initialState {
|
|
255
|
+
${stateFields}
|
|
256
|
+
}
|
|
32
257
|
}
|
|
33
258
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
259
|
+
}
|
|
260
|
+
`,
|
|
261
|
+
{ id: documentId },
|
|
262
|
+
);
|
|
263
|
+
const document: Document<
|
|
264
|
+
InferDocumentState<D>,
|
|
265
|
+
InferDocumentOperation<D>,
|
|
266
|
+
InferDocumentLocalState<D>
|
|
267
|
+
> | null = result.document
|
|
268
|
+
? {
|
|
269
|
+
...result.document,
|
|
270
|
+
revision: {
|
|
271
|
+
global: result.document.revision,
|
|
272
|
+
local: 0,
|
|
273
|
+
},
|
|
274
|
+
state: utils.createState({ global: result.document.state }),
|
|
275
|
+
operations: {
|
|
276
|
+
global: result.document.operations.map(({ inputText, ...o }) => ({
|
|
277
|
+
...o,
|
|
278
|
+
error: o.error ?? undefined,
|
|
279
|
+
scope: "global",
|
|
280
|
+
input: JSON.parse(inputText) as D,
|
|
281
|
+
})),
|
|
282
|
+
local: [],
|
|
283
|
+
},
|
|
284
|
+
attachments: {},
|
|
285
|
+
initialState: utils.createExtendedState({
|
|
286
|
+
// TODO: getDocument should return all the initial state fields
|
|
287
|
+
created: result.document.created,
|
|
288
|
+
lastModified: result.document.created,
|
|
289
|
+
state: utils.createState({
|
|
290
|
+
global: result.document.initialState,
|
|
291
|
+
}),
|
|
292
|
+
}),
|
|
293
|
+
clipboard: [],
|
|
294
|
+
}
|
|
295
|
+
: null;
|
|
45
296
|
|
|
46
|
-
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
document,
|
|
300
|
+
};
|
|
47
301
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,89 +1,91 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from 'document-model-libs/document-drive';
|
|
2
|
+
DocumentDriveDocument,
|
|
3
|
+
documentModel as DocumentDriveModel,
|
|
4
|
+
} from "document-model-libs/document-drive";
|
|
7
5
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
6
|
+
Action,
|
|
7
|
+
BaseAction,
|
|
8
|
+
Document,
|
|
9
|
+
DocumentOperations,
|
|
10
|
+
Operation,
|
|
11
|
+
OperationScope,
|
|
12
|
+
} from "document-model/document";
|
|
13
|
+
// import setAsap from 'setasap';
|
|
14
|
+
import { v4 as uuidv4 } from "uuid";
|
|
15
|
+
import { OperationError } from "../server/error";
|
|
16
|
+
import { DocumentDriveStorage, DocumentStorage } from "../storage";
|
|
17
|
+
import { RunAsap } from "./run-asap";
|
|
18
|
+
export * from "./run-asap";
|
|
19
|
+
|
|
20
|
+
export const runAsap = RunAsap.runAsap;
|
|
21
|
+
export const runAsapAsync = RunAsap.runAsapAsync;
|
|
17
22
|
|
|
18
23
|
export function isDocumentDriveStorage(
|
|
19
|
-
|
|
24
|
+
document: DocumentStorage,
|
|
20
25
|
): document is DocumentDriveStorage {
|
|
21
|
-
|
|
22
|
-
document.documentType === DocumentDriveModel.id
|
|
23
|
-
);
|
|
26
|
+
return document.documentType === DocumentDriveModel.id;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export function isDocumentDrive(
|
|
27
|
-
|
|
30
|
+
document: Document,
|
|
28
31
|
): document is DocumentDriveDocument {
|
|
29
|
-
|
|
30
|
-
document.documentType === DocumentDriveModel.id
|
|
31
|
-
);
|
|
32
|
+
return document.documentType === DocumentDriveModel.id;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function mergeOperations<A extends Action = Action>(
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
currentOperations: DocumentOperations<A>,
|
|
37
|
+
newOperations: Operation<A | BaseAction>[],
|
|
37
38
|
): DocumentOperations<A> {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const minIndexByScope = Object.keys(currentOperations).reduce<
|
|
40
|
+
Partial<Record<OperationScope, number>>
|
|
41
|
+
>((acc, curr) => {
|
|
42
|
+
const scope = curr as OperationScope;
|
|
43
|
+
acc[scope] = currentOperations[scope].at(-1)?.index ?? 0;
|
|
44
|
+
return acc;
|
|
45
|
+
}, {});
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
const conflictOp = newOperations.find(
|
|
48
|
+
(op) => op.index < (minIndexByScope[op.scope] ?? 0),
|
|
49
|
+
);
|
|
50
|
+
if (conflictOp) {
|
|
51
|
+
throw new OperationError(
|
|
52
|
+
"ERROR",
|
|
53
|
+
conflictOp,
|
|
54
|
+
`Tried to add operation with index ${conflictOp.index} and document is at index ${minIndexByScope[conflictOp.scope]}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
return newOperations
|
|
59
|
+
.sort((a, b) => a.index - b.index)
|
|
60
|
+
.reduce<DocumentOperations<A>>((acc, curr) => {
|
|
61
|
+
const existingOperations = acc[curr.scope] || [];
|
|
62
|
+
return { ...acc, [curr.scope]: [...existingOperations, curr] };
|
|
57
63
|
}, currentOperations);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export function generateUUID(): string {
|
|
61
|
-
|
|
67
|
+
return uuidv4();
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export function isNoopUpdate(
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
operation: Operation,
|
|
72
|
+
latestOperation?: Operation,
|
|
67
73
|
) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
if (!latestOperation) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
const isNoopOp = operation.type === "NOOP";
|
|
79
|
+
const isNoopLatestOp = latestOperation.type === "NOOP";
|
|
80
|
+
const isSameIndexOp = operation.index === latestOperation.index;
|
|
81
|
+
const isSkipOpGreaterThanLatestOp = operation.skip > latestOperation.skip;
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
isSameIndexOp &&
|
|
81
|
-
isSkipOpGreaterThanLatestOp
|
|
82
|
-
);
|
|
83
|
+
return (
|
|
84
|
+
isNoopOp && isNoopLatestOp && isSameIndexOp && isSkipOpGreaterThanLatestOp
|
|
85
|
+
);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
// return true if dateA is before dateB
|
|
86
89
|
export function isBefore(dateA: Date | string, dateB: Date | string) {
|
|
87
|
-
|
|
90
|
+
return new Date(dateA) < new Date(dateB);
|
|
88
91
|
}
|
|
89
|
-
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,41 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export type ILogger = Pick<
|
|
2
|
+
Console,
|
|
3
|
+
"log" | "info" | "warn" | "error" | "debug" | "trace"
|
|
4
|
+
>;
|
|
3
5
|
class Logger implements ILogger {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
6
|
+
#logger: ILogger = console;
|
|
7
|
+
|
|
8
|
+
set logger(logger: ILogger) {
|
|
9
|
+
this.#logger = logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
log(...data: any[]): void {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
14
|
+
return this.#logger.log(...data);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
info(...data: any[]): void {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
19
|
+
return this.#logger.info(...data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
warn(...data: any[]): void {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
24
|
+
return this.#logger.warn(...data);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
error(...data: any[]): void {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
29
|
+
return this.#logger.error(...data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
debug(...data: any[]): void {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
34
|
+
return this.#logger.debug(...data);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
trace(...data: any[]): void {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
39
|
+
return this.#logger.trace(...data);
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const loggerInstance = new Logger();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Action,
|
|
3
|
+
Document,
|
|
4
|
+
DocumentOperations,
|
|
5
|
+
Operation,
|
|
6
|
+
OperationScope,
|
|
7
|
+
} from "document-model/document";
|
|
8
|
+
import { DocumentStorage } from "../storage/types";
|
|
9
|
+
|
|
10
|
+
export function migrateDocumentOperationSigatures<D extends Document>(
|
|
11
|
+
document: DocumentStorage<D>,
|
|
12
|
+
): DocumentStorage<D> | undefined {
|
|
13
|
+
let legacy = false;
|
|
14
|
+
const operations = Object.entries(document.operations).reduce<
|
|
15
|
+
DocumentOperations<Action>
|
|
16
|
+
>(
|
|
17
|
+
(acc, [key, operations]) => {
|
|
18
|
+
const scope = key as unknown as OperationScope;
|
|
19
|
+
for (const op of operations) {
|
|
20
|
+
const newOp = migrateLegacyOperationSignature(op);
|
|
21
|
+
acc[scope].push(newOp);
|
|
22
|
+
if (newOp !== op) {
|
|
23
|
+
legacy = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return acc;
|
|
27
|
+
},
|
|
28
|
+
{ global: [], local: [] },
|
|
29
|
+
);
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
31
|
+
return legacy ? { ...document, operations } : document;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function migrateLegacyOperationSignature<A extends Action>(
|
|
35
|
+
operation: Operation<A>,
|
|
36
|
+
): Operation<A> {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
38
|
+
if (!operation.context?.signer || operation.context.signer.signatures) {
|
|
39
|
+
return operation;
|
|
40
|
+
}
|
|
41
|
+
const { signer } = operation.context;
|
|
42
|
+
if ("signature" in signer) {
|
|
43
|
+
const signature = signer.signature as string | undefined;
|
|
44
|
+
return {
|
|
45
|
+
...operation,
|
|
46
|
+
context: {
|
|
47
|
+
...operation.context,
|
|
48
|
+
signer: {
|
|
49
|
+
user: signer.user,
|
|
50
|
+
app: signer.app,
|
|
51
|
+
signatures: signature?.length ? [signature] : [],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
return operation;
|
|
57
|
+
}
|
|
58
|
+
}
|