@twin.org/document-management-service 0.0.1-next.7 → 0.0.1-next.9

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.
@@ -1,5 +1,4 @@
1
- import { HttpParameterHelper } from '@twin.org/api-models';
2
- import { Guards, ComponentFactory, Converter, Coerce, Is, ObjectHelper, BaseError, GeneralError, Urn, NotFoundError } from '@twin.org/core';
1
+ import { Guards, ComponentFactory, Converter, Coerce, Is, BaseError, GeneralError, Urn, NotFoundError, ObjectHelper } from '@twin.org/core';
3
2
  import { DocumentTypes, DocumentContexts } from '@twin.org/document-management-models';
4
3
  import { SchemaOrgContexts, SchemaOrgDataTypes } from '@twin.org/standards-schema-org';
5
4
  import { UneceDocumentCodes } from '@twin.org/standards-unece';
@@ -10,8 +9,6 @@ import { BlobStorageContexts } from '@twin.org/blob-storage-models';
10
9
  import { Sha256 } from '@twin.org/crypto';
11
10
  import { JsonLdProcessor } from '@twin.org/data-json-ld';
12
11
 
13
- // Copyright 2024 IOTA Stiftung.
14
- // SPDX-License-Identifier: Apache-2.0.
15
12
  /**
16
13
  * The source used when communicating about these routes.
17
14
  */
@@ -32,22 +29,19 @@ const tagsDocumentManagement = [
32
29
  * @returns The generated routes.
33
30
  */
34
31
  function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
35
- const documentManagementSetRoute = {
32
+ const documentManagementCreateRoute = {
36
33
  operationId: "DocumentManagementSet",
37
34
  summary: "Store a document in an auditable item graph vertex and add its content to blob storage.",
38
35
  tag: tagsDocumentManagement[0].name,
39
36
  method: "POST",
40
- path: `${baseRouteName}/:auditableItemGraphId`,
41
- handler: async (httpRequestContext, request) => documentManagementSet(httpRequestContext, componentName, request),
37
+ path: `${baseRouteName}/`,
38
+ handler: async (httpRequestContext, request) => documentManagementCreate(httpRequestContext, componentName, request),
42
39
  requestType: {
43
- type: "IDocumentManagementSetRequest",
40
+ type: "IDocumentManagementCreateRequest",
44
41
  examples: [
45
42
  {
46
- id: "DocumentManagementSetRequestExample",
43
+ id: "DocumentManagementCreateRequestExample",
47
44
  request: {
48
- pathParams: {
49
- auditableItemGraphId: "aig:123456"
50
- },
51
45
  body: {
52
46
  documentId: "2721000",
53
47
  documentIdFormat: "bol",
@@ -69,11 +63,11 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
69
63
  type: "ICreatedResponse",
70
64
  examples: [
71
65
  {
72
- id: "DocumentManagementSetResponseExample",
66
+ id: "DocumentManagementCreateResponseExample",
73
67
  response: {
74
68
  statusCode: HttpStatusCode.created,
75
69
  headers: {
76
- [HeaderTypes.Location]: "documents:123456:705:2721000"
70
+ [HeaderTypes.Location]: "aig:123456"
77
71
  }
78
72
  }
79
73
  }
@@ -81,22 +75,29 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
81
75
  }
82
76
  ]
83
77
  };
84
- const documentManagementGetRoute = {
85
- operationId: "DocumentManagementGet",
86
- summary: "Get the data for a document from document management",
78
+ const documentManagementUpdateRoute = {
79
+ operationId: "DocumentManagementUpdate",
80
+ summary: "Update a document in an auditable item graph vertex and add its content to blob storage.",
87
81
  tag: tagsDocumentManagement[0].name,
88
- method: "GET",
89
- path: `${baseRouteName}/:auditableItemGraphId/:documentId`,
90
- handler: async (httpRequestContext, request) => documentManagementGet(httpRequestContext, componentName, request),
82
+ method: "PUT",
83
+ path: `${baseRouteName}/:auditableItemGraphDocumentId`,
84
+ handler: async (httpRequestContext, request) => documentManagementUpdate(httpRequestContext, componentName, request),
91
85
  requestType: {
92
- type: "IDocumentManagementGetRequest",
86
+ type: "IDocumentManagementUpdateRequest",
93
87
  examples: [
94
88
  {
95
- id: "DocumentManagementGetRequestExample",
89
+ id: "DocumentManagementUpdateRequestExample",
96
90
  request: {
97
91
  pathParams: {
98
- auditableItemGraphId: "aig:1234",
99
- documentId: "documents:123456:705:2721000"
92
+ auditableItemGraphDocumentId: "aig:123456"
93
+ },
94
+ body: {
95
+ blob: "SGVsbG8gV29ybGQ=",
96
+ annotationObject: {
97
+ "@context": "https://schema.org",
98
+ "@type": "DigitalDocument",
99
+ name: "myfile.pdf"
100
+ }
100
101
  }
101
102
  }
102
103
  }
@@ -104,122 +105,33 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
104
105
  },
105
106
  responseType: [
106
107
  {
107
- type: "IDocumentManagementGetResponse",
108
+ type: "INoContentResponse",
108
109
  examples: [
109
110
  {
110
- id: "DocumentManagementGetResponseExample",
111
+ id: "DocumentManagementCreateResponseExample",
111
112
  response: {
112
- body: {
113
- "@context": [
114
- DocumentContexts.ContextRoot,
115
- DocumentContexts.ContextRootCommon,
116
- SchemaOrgContexts.ContextRoot
117
- ],
118
- type: DocumentTypes.Document,
119
- id: "documents:705:2721000:rev-0",
120
- documentId: "2721000",
121
- documentIdFormat: "bol",
122
- documentCode: UneceDocumentCodes.BillOfLading,
123
- documentRevision: 0,
124
- blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
125
- blobHash: "sha256:123456",
126
- dateCreated: "2024-01-01T00:00:00Z",
127
- annotationObject: {
128
- "@context": "https://schema.org",
129
- "@type": "DigitalDocument",
130
- name: "myfile.pdf"
131
- },
132
- nodeIdentity: "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363",
133
- userIdentity: "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363"
134
- }
113
+ statusCode: HttpStatusCode.noContent
135
114
  }
136
115
  }
137
116
  ]
138
- },
139
- {
140
- type: "IDocumentManagementGetResponse",
141
- mimeType: MimeTypes.JsonLd,
142
- examples: [
143
- {
144
- id: "DocumentManagementGetResponseExample",
145
- response: {
146
- body: {
147
- "@context": [
148
- DocumentContexts.ContextRoot,
149
- DocumentContexts.ContextRootCommon,
150
- SchemaOrgContexts.ContextRoot
151
- ],
152
- type: DocumentTypes.Document,
153
- id: "documents:705:2721000:rev-0",
154
- documentId: "2721000",
155
- documentIdFormat: "bol",
156
- documentCode: UneceDocumentCodes.BillOfLading,
157
- documentRevision: 0,
158
- blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
159
- blobHash: "sha256:123456",
160
- dateCreated: "2024-01-01T00:00:00Z",
161
- annotationObject: {
162
- "@context": "https://schema.org",
163
- "@type": "DigitalDocument",
164
- name: "myfile.pdf"
165
- },
166
- nodeIdentity: "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363",
167
- userIdentity: "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363"
168
- }
169
- }
170
- }
171
- ]
172
- },
173
- {
174
- type: "INotFoundResponse"
175
117
  }
176
118
  ]
177
119
  };
178
- const documentManagementRemoveRoute = {
179
- operationId: "DocumentManagementRemove",
180
- summary: "Remove an document from an auditable item graph vertex",
181
- tag: tagsDocumentManagement[0].name,
182
- method: "DELETE",
183
- path: `${baseRouteName}/:auditableItemGraphId/:documentId`,
184
- handler: async (httpRequestContext, request) => documentManagementRemove(httpRequestContext, componentName, request),
185
- requestType: {
186
- type: "IDocumentManagementRemoveRequest",
187
- examples: [
188
- {
189
- id: "DocumentManagementRemoveRequestExample",
190
- request: {
191
- pathParams: {
192
- auditableItemGraphId: "aig:1234",
193
- documentId: "documents:123456:705:2721000"
194
- }
195
- }
196
- }
197
- ]
198
- },
199
- responseType: [
200
- {
201
- type: "INoContentResponse"
202
- },
203
- {
204
- type: "INotFoundResponse"
205
- }
206
- ]
207
- };
208
- const documentManagementQueryRoute = {
209
- operationId: "DocumentManagementQuery",
210
- summary: "Query the items from an auditable item graph vertex",
120
+ const documentManagementGetRoute = {
121
+ operationId: "DocumentManagementGet",
122
+ summary: "Get the data for a document from document management",
211
123
  tag: tagsDocumentManagement[0].name,
212
124
  method: "GET",
213
- path: `${baseRouteName}/:auditableItemGraphId`,
214
- handler: async (httpRequestContext, request) => documentManagementQuery(httpRequestContext, componentName, request),
125
+ path: `${baseRouteName}/:auditableItemGraphDocumentId`,
126
+ handler: async (httpRequestContext, request) => documentManagementGet(httpRequestContext, componentName, request),
215
127
  requestType: {
216
- type: "IDocumentManagementQueryRequest",
128
+ type: "IDocumentManagementGetRequest",
217
129
  examples: [
218
130
  {
219
- id: "DocumentManagementQueryRequestExample",
131
+ id: "DocumentManagementGetRequestExample",
220
132
  request: {
221
133
  pathParams: {
222
- auditableItemGraphId: "aig:123456"
134
+ auditableItemGraphDocumentId: "aig:123456"
223
135
  }
224
136
  }
225
137
  }
@@ -227,10 +139,10 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
227
139
  },
228
140
  responseType: [
229
141
  {
230
- type: "IDocumentManagementQueryResponse",
142
+ type: "IDocumentManagementGetResponse",
231
143
  examples: [
232
144
  {
233
- id: "DocumentManagementQueryResponseExample",
145
+ id: "DocumentManagementGetResponseExample",
234
146
  response: {
235
147
  body: {
236
148
  "@context": [DocumentContexts.ContextRoot, DocumentContexts.ContextRootCommon],
@@ -243,7 +155,7 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
243
155
  SchemaOrgContexts.ContextRoot
244
156
  ],
245
157
  type: DocumentTypes.Document,
246
- id: "documents:705:2721000:rev-0",
158
+ id: "2721000:0",
247
159
  documentId: "2721000",
248
160
  documentIdFormat: "bol",
249
161
  documentCode: UneceDocumentCodes.BillOfLading,
@@ -266,11 +178,11 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
266
178
  ]
267
179
  },
268
180
  {
269
- type: "IDocumentManagementQueryResponse",
181
+ type: "IDocumentManagementGetResponse",
270
182
  mimeType: MimeTypes.JsonLd,
271
183
  examples: [
272
184
  {
273
- id: "DocumentManagementListResponseJsonLdExample",
185
+ id: "DocumentManagementGetResponseExample",
274
186
  response: {
275
187
  body: {
276
188
  "@context": [DocumentContexts.ContextRoot, DocumentContexts.ContextRootCommon],
@@ -283,7 +195,7 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
283
195
  SchemaOrgContexts.ContextRoot
284
196
  ],
285
197
  type: DocumentTypes.Document,
286
- id: "documents:705:2721000:rev-0",
198
+ id: "2721000:0",
287
199
  documentId: "2721000",
288
200
  documentIdFormat: "bol",
289
201
  documentCode: UneceDocumentCodes.BillOfLading,
@@ -304,33 +216,173 @@ function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
304
216
  }
305
217
  }
306
218
  ]
219
+ },
220
+ {
221
+ type: "INotFoundResponse"
222
+ }
223
+ ]
224
+ };
225
+ const documentManagementRemoveRoute = {
226
+ operationId: "DocumentManagementRemove",
227
+ summary: "Remove an document from an auditable item graph vertex",
228
+ tag: tagsDocumentManagement[0].name,
229
+ method: "DELETE",
230
+ path: `${baseRouteName}/:auditableItemGraphDocumentId/:revision`,
231
+ handler: async (httpRequestContext, request) => documentManagementRemove(httpRequestContext, componentName, request),
232
+ requestType: {
233
+ type: "IDocumentManagementRemoveRequest",
234
+ examples: [
235
+ {
236
+ id: "DocumentManagementRemoveRequestExample",
237
+ request: {
238
+ pathParams: {
239
+ auditableItemGraphDocumentId: "aig:1234",
240
+ revision: "1"
241
+ }
242
+ }
243
+ }
244
+ ]
245
+ },
246
+ responseType: [
247
+ {
248
+ type: "INoContentResponse"
249
+ },
250
+ {
251
+ type: "INotFoundResponse"
307
252
  }
308
253
  ]
309
254
  };
255
+ const documentManagementQueryRoute = {
256
+ operationId: "DocumentManagementQuery",
257
+ summary: "Query the items from an auditable item graph vertex",
258
+ tag: tagsDocumentManagement[0].name,
259
+ method: "GET",
260
+ path: `${baseRouteName}/`,
261
+ handler: async (httpRequestContext, request) => documentManagementQuery(httpRequestContext, componentName, request),
262
+ requestType: {
263
+ type: "IDocumentManagementQueryRequest",
264
+ examples: [
265
+ {
266
+ id: "DocumentManagementQueryRequestExample",
267
+ request: {
268
+ query: {
269
+ documentId: "2721000"
270
+ }
271
+ }
272
+ }
273
+ ]
274
+ },
275
+ responseType: [
276
+ // {
277
+ // type: nameof<IDocumentManagementQueryResponse>(),
278
+ // examples: [
279
+ // {
280
+ // id: "DocumentManagementQueryResponseExample",
281
+ // response: {
282
+ // body: {
283
+ // "@context": [DocumentContexts.ContextRoot, DocumentContexts.ContextRootCommon],
284
+ // type: DocumentTypes.DocumentList,
285
+ // documents: [
286
+ // {
287
+ // "@context": [
288
+ // DocumentContexts.ContextRoot,
289
+ // DocumentContexts.ContextRootCommon,
290
+ // SchemaOrgContexts.ContextRoot
291
+ // ],
292
+ // type: DocumentTypes.Document,
293
+ // id: "2721000:0",
294
+ // documentId: "2721000",
295
+ // documentIdFormat: "bol",
296
+ // documentCode: UneceDocumentCodes.BillOfLading,
297
+ // documentRevision: 0,
298
+ // blobStorageId:
299
+ // "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
300
+ // blobHash: "sha256:123456",
301
+ // dateCreated: "2024-01-01T00:00:00Z",
302
+ // annotationObject: {
303
+ // "@context": "https://schema.org",
304
+ // "@type": "DigitalDocument",
305
+ // name: "myfile.pdf"
306
+ // },
307
+ // nodeIdentity:
308
+ // "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363",
309
+ // userIdentity:
310
+ // "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363"
311
+ // }
312
+ // ]
313
+ // }
314
+ // }
315
+ // }
316
+ // ]
317
+ // },
318
+ // {
319
+ // type: nameof<IDocumentManagementQueryResponse>(),
320
+ // mimeType: MimeTypes.JsonLd,
321
+ // examples: [
322
+ // {
323
+ // id: "DocumentManagementListResponseJsonLdExample",
324
+ // response: {
325
+ // body: {
326
+ // "@context": [DocumentContexts.ContextRoot, DocumentContexts.ContextRootCommon],
327
+ // type: DocumentTypes.DocumentList,
328
+ // documents: [
329
+ // {
330
+ // "@context": [
331
+ // DocumentContexts.ContextRoot,
332
+ // DocumentContexts.ContextRootCommon,
333
+ // SchemaOrgContexts.ContextRoot
334
+ // ],
335
+ // type: DocumentTypes.Document,
336
+ // id: "2721000:0",
337
+ // documentId: "2721000",
338
+ // documentIdFormat: "bol",
339
+ // documentCode: UneceDocumentCodes.BillOfLading,
340
+ // documentRevision: 0,
341
+ // blobStorageId:
342
+ // "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
343
+ // blobHash: "sha256:123456",
344
+ // dateCreated: "2024-01-01T00:00:00Z",
345
+ // annotationObject: {
346
+ // "@context": "https://schema.org",
347
+ // "@type": "DigitalDocument",
348
+ // name: "myfile.pdf"
349
+ // },
350
+ // nodeIdentity:
351
+ // "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363",
352
+ // userIdentity:
353
+ // "did:entity-storage:0x6363636363636363636363636363636363636363636363636363636363636363"
354
+ // }
355
+ // ]
356
+ // }
357
+ // }
358
+ // }
359
+ // ]
360
+ // }
361
+ ]
362
+ };
310
363
  return [
311
- documentManagementSetRoute,
364
+ documentManagementCreateRoute,
365
+ documentManagementUpdateRoute,
312
366
  documentManagementGetRoute,
313
367
  documentManagementRemoveRoute,
314
368
  documentManagementQueryRoute
315
369
  ];
316
370
  }
317
371
  /**
318
- * Set a document in to an auditable item graph vertex.
372
+ * Create a document as an auditable item graph vertex.
319
373
  * @param httpRequestContext The request context for the API.
320
374
  * @param componentName The name of the component to use in the routes.
321
375
  * @param request The request.
322
376
  * @returns The response object with additional http response properties.
323
377
  */
324
- async function documentManagementSet(httpRequestContext, componentName, request) {
378
+ async function documentManagementCreate(httpRequestContext, componentName, request) {
325
379
  Guards.object(ROUTES_SOURCE, "request", request);
326
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
327
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
328
380
  Guards.object(ROUTES_SOURCE, "request.body", request.body);
329
381
  Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
330
382
  const component = ComponentFactory.get(componentName);
331
- const id = await component.set(request.pathParams.auditableItemGraphId, request.body.documentId, request.body.documentIdFormat, request.body.documentCode, Converter.base64ToBytes(request.body.blob), request.body.annotationObject, {
383
+ const id = await component.create(request.body.documentId, request.body.documentIdFormat, request.body.documentCode, Converter.base64ToBytes(request.body.blob), request.body.annotationObject, request.body.auditableItemGraphEdges, {
332
384
  createAttestation: request.body.createAttestation,
333
- includeIdAsAlias: request.body.includeIdAsAlias,
385
+ addAlias: request.body.addAlias,
334
386
  aliasAnnotationObject: request.body.aliasAnnotationObject
335
387
  }, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
336
388
  return {
@@ -350,17 +402,15 @@ async function documentManagementSet(httpRequestContext, componentName, request)
350
402
  async function documentManagementGet(httpRequestContext, componentName, request) {
351
403
  Guards.object(ROUTES_SOURCE, "request", request);
352
404
  Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
353
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
354
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.documentId", request.pathParams.documentId);
405
+ Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphDocumentId", request.pathParams.auditableItemGraphDocumentId);
355
406
  const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
356
407
  const component = ComponentFactory.get(componentName);
357
- const result = await component.get(request.pathParams.auditableItemGraphId, request.pathParams.documentId, {
408
+ const result = await component.get(request.pathParams.auditableItemGraphDocumentId, {
358
409
  includeBlobStorageMetadata: Coerce.boolean(request.query?.includeBlobStorageMetadata),
359
410
  includeBlobStorageData: Coerce.boolean(request.query?.includeBlobStorageData),
360
411
  includeAttestation: Coerce.boolean(request.query?.includeAttestation),
361
- includeRemoved: Coerce.boolean(request.query?.includeRemoved),
362
- maxRevisionCount: Coerce.integer(request.query?.maxRevisionCount)
363
- }, request.query?.revisionCursor, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
412
+ includeRemoved: Coerce.boolean(request.query?.includeRemoved)
413
+ }, request.query?.cursor, Coerce.integer(request.query?.pageSize), httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
364
414
  return {
365
415
  headers: {
366
416
  [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
@@ -368,6 +418,23 @@ async function documentManagementGet(httpRequestContext, componentName, request)
368
418
  body: result
369
419
  };
370
420
  }
421
+ /**
422
+ * UPdate the document from the auditable item graph vertex.
423
+ * @param httpRequestContext The request context for the API.
424
+ * @param componentName The name of the component to use in the routes.
425
+ * @param request The request.
426
+ * @returns The response object with additional http response properties.
427
+ */
428
+ async function documentManagementUpdate(httpRequestContext, componentName, request) {
429
+ Guards.object(ROUTES_SOURCE, "request", request);
430
+ Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
431
+ Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphDocumentId", request.pathParams.auditableItemGraphDocumentId);
432
+ const component = ComponentFactory.get(componentName);
433
+ await component.update(request.pathParams.auditableItemGraphDocumentId, Is.stringValue(request.body.blob) ? Converter.base64ToBytes(request.body.blob) : undefined, request.body.annotationObject, request.body.auditableItemGraphEdges, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
434
+ return {
435
+ statusCode: HttpStatusCode.noContent
436
+ };
437
+ }
371
438
  /**
372
439
  * Remove the document from the auditable item graph vertex.
373
440
  * @param httpRequestContext The request context for the API.
@@ -378,12 +445,11 @@ async function documentManagementGet(httpRequestContext, componentName, request)
378
445
  async function documentManagementRemove(httpRequestContext, componentName, request) {
379
446
  Guards.object(ROUTES_SOURCE, "request", request);
380
447
  Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
381
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
382
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.documentId", request.pathParams.documentId);
448
+ Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphDocumentId", request.pathParams.auditableItemGraphDocumentId);
449
+ const revision = Coerce.number(request.pathParams.revision);
450
+ Guards.integer(ROUTES_SOURCE, "request.pathParams.revision", revision);
383
451
  const component = ComponentFactory.get(componentName);
384
- await component.remove(request.pathParams.auditableItemGraphId, request.pathParams.documentId, {
385
- removeAllRevisions: request.query?.removeAllRevisions
386
- }, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
452
+ await component.removeRevision(request.pathParams.auditableItemGraphDocumentId, revision, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
387
453
  return {
388
454
  statusCode: HttpStatusCode.noContent
389
455
  };
@@ -397,14 +463,11 @@ async function documentManagementRemove(httpRequestContext, componentName, reque
397
463
  */
398
464
  async function documentManagementQuery(httpRequestContext, componentName, request) {
399
465
  Guards.object(ROUTES_SOURCE, "request", request);
400
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
401
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
466
+ Guards.object(ROUTES_SOURCE, "request.query", request.query);
467
+ Guards.stringValue(ROUTES_SOURCE, "request.query.documentId", request.query.documentId);
402
468
  const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
403
469
  const component = ComponentFactory.get(componentName);
404
- const result = await component.query(request.pathParams.auditableItemGraphId, HttpParameterHelper.arrayFromString(request.query?.documentCodes), {
405
- includeRemoved: Coerce.boolean(request.query?.includeRemoved),
406
- includeMostRecentRevisions: Coerce.boolean(request.query?.includeMostRecentRevisions)
407
- }, request.query?.cursor, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
470
+ const result = await component.query(request.query.documentId, request.query?.cursor, Coerce.integer(request.query?.pageSize), httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
408
471
  return {
409
472
  headers: {
410
473
  [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
@@ -421,11 +484,6 @@ class DocumentManagementService {
421
484
  * The namespace supported by the document management service.
422
485
  */
423
486
  static NAMESPACE = "documents";
424
- /**
425
- * Default Page Size for cursor.
426
- * @internal
427
- */
428
- static _DEFAULT_PAGE_SIZE = 20;
429
487
  /**
430
488
  * Runtime name for the class.
431
489
  */
@@ -456,166 +514,211 @@ class DocumentManagementService {
456
514
  SchemaOrgDataTypes.registerRedirects();
457
515
  }
458
516
  /**
459
- * Store a document in an auditable item graph vertex and add its content to blob storage.
517
+ * Store a document as an auditable item graph vertex and add its content to blob storage.
460
518
  * If the document id already exists and the blob data is different a new revision will be created.
461
519
  * For any other changes the current revision will be updated.
462
- * @param auditableItemGraphId The auditable item graph vertex id to create the document on.
463
520
  * @param documentId The document id to create.
464
521
  * @param documentIdFormat The format of the document identifier.
465
522
  * @param documentCode The code for the document type.
466
- * @param blob The data to create the document.
523
+ * @param blob The data to create the document with.
467
524
  * @param annotationObject Additional information to associate with the document.
525
+ * @param auditableItemGraphEdges The auditable item graph vertices to connect the document to.
468
526
  * @param options Additional options for the set operation.
469
527
  * @param options.createAttestation Flag to create an attestation for the document, defaults to false.
470
- * @param options.includeIdAsAlias Include the document id as an alias to the aig vertex, defaults to false.
471
- * @param options.aliasAnnotationObject Additional information to associate with the alias.
528
+ * @param options.addAlias Flag to add the document id as an alias to the aig vertex, defaults to true.
529
+ * @param options.aliasAnnotationObject Annotation object for the alias.
472
530
  * @param userIdentity The identity to perform the auditable item graph operation with.
473
531
  * @param nodeIdentity The node identity to use for vault operations.
474
- * @returns The identifier for the document which includes the auditable item graph identifier.
532
+ * @returns The auditable item graph vertex created for the document including its revision.
475
533
  */
476
- async set(auditableItemGraphId, documentId, documentIdFormat, documentCode, blob, annotationObject, options, userIdentity, nodeIdentity) {
477
- Guards.stringValue(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
534
+ async create(documentId, documentIdFormat, documentCode, blob, annotationObject, auditableItemGraphEdges, options, userIdentity, nodeIdentity) {
478
535
  Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
479
536
  Guards.arrayOneOf(this.CLASS_NAME, "documentCode", documentCode, Object.values(UneceDocumentCodes));
480
537
  Guards.uint8Array(this.CLASS_NAME, "blob", blob);
481
538
  Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
482
539
  Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
483
540
  try {
484
- const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
485
- vertex.resources = vertex.resources ?? [];
486
- if (options?.includeIdAsAlias ?? false) {
487
- vertex.aliases ??= [];
488
- const found = vertex.aliases.find(a => a.id === documentId);
489
- if (found) {
490
- found.annotationObject = options?.aliasAnnotationObject ?? found.annotationObject;
491
- found.aliasFormat = documentIdFormat ?? found.aliasFormat;
492
- }
493
- else {
494
- vertex.aliases.push({
495
- "@context": AuditableItemGraphContexts.ContextRoot,
496
- type: AuditableItemGraphTypes.Alias,
497
- id: documentId,
498
- aliasFormat: documentIdFormat,
499
- annotationObject: options?.aliasAnnotationObject
500
- });
541
+ // Get the connected vertices first, if one fails we abort the create
542
+ const connectedVertices = {};
543
+ if (Is.arrayValue(auditableItemGraphEdges)) {
544
+ for (const edge of auditableItemGraphEdges) {
545
+ connectedVertices[edge.id] = await this._auditableItemGraphComponent.get(edge.id);
501
546
  }
502
547
  }
503
- // Get all the docs from the AIG vertex
504
- const vertexDocs = this.filterDocumentsFromVertex(vertex);
505
- // Reduce the list to those with a matching id and code
506
- const matchingDocIds = this.findMatchingDocs(vertexDocs, documentId, documentCode, true);
507
- const currentRevision = matchingDocIds[0];
508
- let createAttestation = options?.createAttestation ?? false;
509
- // If the create attestation flag is not defined we check to see if any previous
510
- // revisions have an attestation and if so we create one for the new revision.
511
- if (Is.undefined(options?.createAttestation)) {
512
- createAttestation = matchingDocIds.some(d => Is.stringValue(d.attestationId));
548
+ const documentVertex = {};
549
+ if (options?.addAlias ?? true) {
550
+ documentVertex.aliases ??= [];
551
+ documentVertex.aliases.push({
552
+ "@context": AuditableItemGraphContexts.ContextRoot,
553
+ type: AuditableItemGraphTypes.Alias,
554
+ id: documentId,
555
+ aliasFormat: documentIdFormat,
556
+ annotationObject: options?.aliasAnnotationObject
557
+ });
513
558
  }
514
- // Calculate the hash for the blob.
515
- const blobHash = this.generateBlobHash(blob);
516
- // Is the blob data the same as the current revision ?
517
- if (currentRevision?.blobHash === blobHash) {
518
- // Blob data matches so no need to create a new revision
519
- // We update the current object if the annotation or createAttestation flag has changed.
520
- let updated = false;
521
- if (!ObjectHelper.equal(currentRevision.annotationObject, annotationObject, false)) {
522
- currentRevision.annotationObject = annotationObject;
523
- updated = true;
524
- }
525
- if (createAttestation && Is.empty(currentRevision.attestationId)) {
526
- currentRevision.attestationId = await this.createAttestation(currentRevision, userIdentity, nodeIdentity);
527
- updated = true;
528
- }
529
- if (updated) {
530
- currentRevision.dateModified = new Date(Date.now()).toISOString();
531
- await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
532
- }
533
- return currentRevision.id;
534
- }
535
- // Nothing matches the current blob hash so upload it to blob storage
559
+ // Add the blob to blob storage
536
560
  const blobStorageId = await this._blobStorageComponent.create(Converter.bytesToBase64(blob), undefined, undefined, undefined, undefined, userIdentity, nodeIdentity);
537
- const documentRevision = matchingDocIds.length;
538
- // We are creating a new document, if there is already docs with the same id and code we use the list length
539
- // to determine the next revision number.
540
- const document = {
561
+ const currentRevision = {
541
562
  "@context": [
542
563
  DocumentContexts.ContextRoot,
543
564
  DocumentContexts.ContextRootCommon,
544
565
  SchemaOrgContexts.ContextRoot
545
566
  ],
546
567
  type: DocumentTypes.Document,
547
- id: this.createIdentifier(documentCode, documentId, documentRevision),
568
+ id: `${documentId}:0`,
548
569
  documentId,
549
570
  documentIdFormat,
550
571
  documentCode,
551
- documentRevision,
552
- blobStorageId,
553
- blobHash,
572
+ documentRevision: 0,
554
573
  annotationObject,
574
+ blobHash: this.generateBlobHash(blob),
575
+ blobStorageId,
555
576
  dateCreated: new Date(Date.now()).toISOString(),
556
577
  nodeIdentity,
557
578
  userIdentity
558
579
  };
559
- // If the attestation flag is set then create it
560
- if (createAttestation ?? false) {
561
- document.attestationId = await this.createAttestation(document, userIdentity, nodeIdentity);
580
+ if (options?.createAttestation ?? false) {
581
+ currentRevision.attestationId = await this.createAttestation(currentRevision, userIdentity, nodeIdentity);
562
582
  }
563
- // Add the new revision in to the AIG
564
- vertex.resources.push({
583
+ // Add the new revision in to the vertex
584
+ documentVertex.resources ??= [];
585
+ documentVertex.resources.push({
565
586
  "@context": AuditableItemGraphContexts.ContextRoot,
566
587
  type: AuditableItemGraphTypes.Resource,
567
- resourceObject: document
588
+ resourceObject: currentRevision
568
589
  });
569
- await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
570
- return document.id;
590
+ // Add the edges from the document to the items
591
+ this.updateEdges(documentVertex, auditableItemGraphEdges);
592
+ // And create the vertex
593
+ const vertexId = await this._auditableItemGraphComponent.create(documentVertex, userIdentity, nodeIdentity);
594
+ // Now add the edges to the connected vertices
595
+ await this.updateConnectedEdges(connectedVertices, vertexId, [], auditableItemGraphEdges, documentId, documentIdFormat, userIdentity, nodeIdentity);
596
+ return vertexId;
597
+ }
598
+ catch (error) {
599
+ if (BaseError.someErrorName(error, "NotFoundError")) {
600
+ throw error;
601
+ }
602
+ throw new GeneralError(this.CLASS_NAME, "createFailed", undefined, error);
603
+ }
604
+ }
605
+ /**
606
+ * Update a document as an auditable item graph vertex and add its content to blob storage.
607
+ * If the blob data is different a new revision will be created.
608
+ * For any other changes the current revision will be updated.
609
+ * @param auditableItemGraphDocumentId The auditable item graph vertex id which contains the document.
610
+ * @param blob The data to update the document with.
611
+ * @param annotationObject Additional information to associate with the document.
612
+ * @param auditableItemGraphEdges The auditable item graph vertices to connect the document to, if undefined retains current connections.
613
+ * @param userIdentity The identity to perform the auditable item graph operation with.
614
+ * @param nodeIdentity The node identity to use for vault operations.
615
+ * @returns Nothing.
616
+ */
617
+ async update(auditableItemGraphDocumentId, blob, annotationObject, auditableItemGraphEdges, userIdentity, nodeIdentity) {
618
+ Urn.guard(this.CLASS_NAME, "auditableItemGraphDocumentId", auditableItemGraphDocumentId);
619
+ Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
620
+ Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
621
+ try {
622
+ const documentVertex = await this._auditableItemGraphComponent.get(auditableItemGraphDocumentId);
623
+ if (Is.empty(documentVertex.resources)) {
624
+ throw new NotFoundError(this.CLASS_NAME, "documentRevisionNone");
625
+ }
626
+ const documents = await this.getDocumentsFromVertex(documentVertex);
627
+ const latestRevision = documents.documents[0];
628
+ if (Is.empty(latestRevision)) {
629
+ throw new NotFoundError(this.CLASS_NAME, "documentRevisionNone");
630
+ }
631
+ // If auditableItemGraphEdges is undefined we are not updating the edges
632
+ // an empty array can be passed to remove all edges
633
+ const connectedVertices = {};
634
+ if (Is.array(auditableItemGraphEdges)) {
635
+ // Get the updated connected vertices first, if one fails we abort the update
636
+ for (const edge of auditableItemGraphEdges) {
637
+ connectedVertices[edge.id] = await this._auditableItemGraphComponent.get(edge.id);
638
+ }
639
+ // Also get the current edges in case some need disconnecting
640
+ if (Is.arrayValue(documents.edges)) {
641
+ for (const edgeId of documents.edges) {
642
+ // If we haven't retrieved the edge then it must be one that needs removing
643
+ if (Is.empty(connectedVertices[edgeId])) {
644
+ connectedVertices[edgeId] = await this._auditableItemGraphComponent.get(edgeId);
645
+ }
646
+ }
647
+ }
648
+ }
649
+ let updatedVertex = false;
650
+ // If the blob is set and its hash has changed then we create a new revision
651
+ if (Is.uint8Array(blob)) {
652
+ const newBlobHash = this.generateBlobHash(blob);
653
+ if (latestRevision.blobHash !== newBlobHash) {
654
+ // Add the blob to blob storage
655
+ const blobStorageId = await this._blobStorageComponent.create(Converter.bytesToBase64(blob), undefined, undefined, undefined, undefined, userIdentity, nodeIdentity);
656
+ const newRevision = ObjectHelper.clone(latestRevision);
657
+ newRevision.documentRevision++;
658
+ newRevision.id = `${newRevision.documentId}:${newRevision.documentRevision}`;
659
+ newRevision.blobHash = newBlobHash;
660
+ newRevision.blobStorageId = blobStorageId;
661
+ newRevision.annotationObject = annotationObject;
662
+ if (Is.stringValue(latestRevision.attestationId)) {
663
+ newRevision.attestationId = await this.createAttestation(newRevision, userIdentity, nodeIdentity);
664
+ }
665
+ documentVertex.resources.push({
666
+ "@context": AuditableItemGraphContexts.ContextRoot,
667
+ type: AuditableItemGraphTypes.Resource,
668
+ resourceObject: newRevision
669
+ });
670
+ updatedVertex = true;
671
+ }
672
+ }
673
+ // If the blob wasn't updated but the annotation object has then update the current revision
674
+ // instead of creating a new one
675
+ if (!updatedVertex &&
676
+ !ObjectHelper.equal(latestRevision.annotationObject, annotationObject)) {
677
+ updatedVertex = true;
678
+ latestRevision.annotationObject = annotationObject;
679
+ latestRevision.dateModified = new Date(Date.now()).toISOString();
680
+ }
681
+ const existingEdgeIds = documentVertex.edges?.map(e => e.id) ?? [];
682
+ // Update the edges from the document to the items
683
+ const edgesUpdated = this.updateEdges(documentVertex, auditableItemGraphEdges);
684
+ if (edgesUpdated) {
685
+ updatedVertex = true;
686
+ }
687
+ if (updatedVertex) {
688
+ await this._auditableItemGraphComponent.update(documentVertex, userIdentity, nodeIdentity);
689
+ }
690
+ if (edgesUpdated) {
691
+ await this.updateConnectedEdges(connectedVertices, auditableItemGraphDocumentId, existingEdgeIds, auditableItemGraphEdges, latestRevision.documentId, latestRevision.documentIdFormat, userIdentity, nodeIdentity);
692
+ }
571
693
  }
572
694
  catch (error) {
573
695
  if (BaseError.someErrorName(error, "NotFoundError")) {
574
696
  throw error;
575
697
  }
576
- throw new GeneralError(this.CLASS_NAME, "setFailed", undefined, error);
698
+ throw new GeneralError(this.CLASS_NAME, "updateFailed", undefined, error);
577
699
  }
578
700
  }
579
701
  /**
580
- * Get a specific document from an auditable item graph vertex.
581
- * @param auditableItemGraphId The auditable item graph vertex id to get the document from.
582
- * @param identifier The identifier of the document to get.
702
+ * Get a document using it's auditable item graph vertex id and optional revision.
703
+ * @param auditableItemGraphDocumentId The auditable item graph vertex id which contains the document.
583
704
  * @param options Additional options for the get operation.
584
705
  * @param options.includeBlobStorageMetadata Flag to include the blob storage metadata for the document, defaults to false.
585
706
  * @param options.includeBlobStorageData Flag to include the blob storage data for the document, defaults to false.
586
707
  * @param options.includeAttestation Flag to include the attestation information for the document, defaults to false.
587
708
  * @param options.includeRemoved Flag to include deleted documents, defaults to false.
588
- * @param options.maxRevisionCount Max number of revisions to return, defaults to 0.
589
- * @param revisionCursor The cursor to get the next chunk of revisions.
709
+ * @param cursor The cursor to get the next chunk of revisions.
710
+ * @param pageSize Page size of items to return, defaults to 1 so only most recent is returned.
590
711
  * @param userIdentity The identity to perform the auditable item graph operation with.
591
712
  * @param nodeIdentity The node identity to use for vault operations.
592
713
  * @returns The documents and revisions if requested, ordered by revision descending, cursor is set if there are more document revisions.
593
714
  */
594
- async get(auditableItemGraphId, identifier, options, revisionCursor, userIdentity, nodeIdentity) {
595
- Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
596
- Urn.guard(this.CLASS_NAME, "identifier", identifier);
715
+ async get(auditableItemGraphDocumentId, options, cursor, pageSize, userIdentity, nodeIdentity) {
716
+ Urn.guard(this.CLASS_NAME, "auditableItemGraphDocumentId", auditableItemGraphDocumentId);
597
717
  try {
598
- const includeBlobStorageMetadata = options?.includeBlobStorageMetadata ?? false;
599
- const includeBlobStorageData = options?.includeBlobStorageData ?? false;
600
- const includeAttestation = options?.includeAttestation ?? false;
601
- const includeRemoved = options?.includeRemoved ?? false;
602
- const revCursor = Math.max(Coerce.integer(revisionCursor) ?? 0, 0);
603
- const maxRevisionCount = Math.max(Coerce.integer(options?.maxRevisionCount) ?? 0);
604
- const documentIdParts = this.parseDocumentId(identifier);
605
- const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
606
- // Get all the docs from the AIG vertex
607
- const vertexDocs = this.filterDocumentsFromVertex(vertex);
608
- // Reduce the list to those with a matching id and code
609
- const matchingDocIds = this.findMatchingDocs(vertexDocs, documentIdParts.documentId, documentIdParts.documentCode, includeRemoved);
718
+ const documentVertex = await this._auditableItemGraphComponent.get(auditableItemGraphDocumentId, { includeDeleted: options?.includeRemoved });
610
719
  // Populate the document and revisions with the options set
611
- const document = await this.getDocumentAndRevisions(matchingDocIds, identifier, {
612
- includeBlobStorageMetadata,
613
- includeBlobStorageData,
614
- includeAttestation,
615
- includeRemoved,
616
- maxRevisionCount
617
- }, revCursor, userIdentity, nodeIdentity);
618
- return JsonLdProcessor.compact(document, document["@context"]);
720
+ const documents = await this.getDocumentsFromVertex(documentVertex, options, cursor, pageSize, userIdentity, nodeIdentity);
721
+ return JsonLdProcessor.compact(documents, documents["@context"]);
619
722
  }
620
723
  catch (error) {
621
724
  if (BaseError.someErrorName(error, "NotFoundError")) {
@@ -625,112 +728,53 @@ class DocumentManagementService {
625
728
  }
626
729
  }
627
730
  /**
628
- * Remove a specific document from an auditable item graph vertex.
629
- * The documents dateDeleted will be set, but can still be queried with the includeRemoved flag.
630
- * @param auditableItemGraphId The auditable item graph vertex id to remove the document from.
631
- * @param identifier The identifier of the document to remove.
632
- * @param options Additional options for the remove operation.
633
- * @param options.removeAllRevisions Flag to remove all revisions of the document, defaults to false.
731
+ * Remove an auditable item graph vertex using it's id.
732
+ * The document dateDeleted will be set, but can still be queried with the includeRemoved flag.
733
+ * @param auditableItemGraphDocumentId The auditable item graph vertex id which contains the document.
734
+ * @param revision The revision of the document to remove.
634
735
  * @param userIdentity The identity to perform the auditable item graph operation with.
635
736
  * @param nodeIdentity The node identity to use for vault operations.
636
737
  * @returns Nothing.
637
738
  */
638
- async remove(auditableItemGraphId, identifier, options, userIdentity, nodeIdentity) {
639
- Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
640
- Urn.guard(this.CLASS_NAME, "identifier", identifier);
739
+ async removeRevision(auditableItemGraphDocumentId, revision, userIdentity, nodeIdentity) {
740
+ Urn.guard(this.CLASS_NAME, "auditableItemGraphDocumentId", auditableItemGraphDocumentId);
741
+ Guards.number(this.CLASS_NAME, "revision", revision);
641
742
  try {
642
- const documentIdParts = this.parseDocumentId(identifier);
643
- const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
644
- // Get all the docs from the AIG vertex
645
- const vertexDocs = this.filterDocumentsFromVertex(vertex);
646
- // Reduce the list to those with a matching id and code
647
- const matchingDocIds = this.findMatchingDocs(vertexDocs, documentIdParts.documentId, documentIdParts.documentCode, false);
648
- const removeAllRevisions = options?.removeAllRevisions ?? false;
649
- const now = Date.now();
650
- if (removeAllRevisions) {
651
- for (const doc of matchingDocIds) {
652
- doc.dateDeleted = new Date(now).toISOString();
653
- }
743
+ const documentVertex = await this._auditableItemGraphComponent.get(auditableItemGraphDocumentId);
744
+ if (Is.empty(documentVertex.resources)) {
745
+ throw new NotFoundError(this.CLASS_NAME, "documentRevisionNone");
654
746
  }
655
- else {
656
- const matchingRevision = matchingDocIds.find(d => d.documentRevision === documentIdParts.documentRevision);
657
- if (matchingRevision) {
658
- matchingRevision.dateDeleted = new Date(now).toISOString();
659
- }
660
- else {
661
- throw new NotFoundError(this.CLASS_NAME, "documentRevisionNotFound", identifier);
662
- }
747
+ const docRevisionIndex = documentVertex.resources.findIndex(d => d.resourceObject?.documentRevision === revision);
748
+ if (docRevisionIndex === -1) {
749
+ throw new NotFoundError(this.CLASS_NAME, "documentRevisionNotFound", revision.toString());
663
750
  }
664
- await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
751
+ documentVertex.resources.splice(docRevisionIndex, 1);
752
+ await this._auditableItemGraphComponent.update(documentVertex, userIdentity, nodeIdentity);
665
753
  }
666
754
  catch (error) {
667
755
  if (BaseError.someErrorName(error, "NotFoundError")) {
668
756
  throw error;
669
757
  }
670
- throw new GeneralError(this.CLASS_NAME, "removeFailed", undefined, error);
758
+ throw new GeneralError(this.CLASS_NAME, "removeRevisionFailed", undefined, error);
671
759
  }
672
760
  }
673
761
  /**
674
- * Query an auditable item graph vertex for documents.
675
- * @param auditableItemGraphId The auditable item graph vertex to get the documents from.
676
- * @param documentCodes The document codes to query for, if undefined gets all document codes.
677
- * @param options Additional options for the query operation.
678
- * @param options.includeMostRecentRevisions Include the most recent 5 revisions, use the individual get to retrieve more.
679
- * @param options.includeRemoved Flag to include deleted documents, defaults to false.
762
+ * Find all the document with a specific id.
763
+ * @param documentId The document id to find in the graph.
680
764
  * @param cursor The cursor to get the next chunk of documents.
765
+ * @param pageSize The page size to get the next chunk of documents.
681
766
  * @param userIdentity The identity to perform the auditable item graph operation with.
682
767
  * @param nodeIdentity The node identity to use for vault operations.
683
- * @returns The most recent revisions of each document, cursor is set if there are more documents.
768
+ * @returns The graph vertices that contain documents referencing the specified document id.
684
769
  */
685
- async query(auditableItemGraphId, documentCodes, options, cursor, userIdentity, nodeIdentity) {
686
- Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
770
+ async query(documentId, cursor, pageSize, userIdentity, nodeIdentity) {
771
+ Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
687
772
  try {
688
- const includeRemoved = options?.includeRemoved ?? false;
689
- const includeMostRecentRevisions = options?.includeMostRecentRevisions ?? false;
690
- const docCursor = Math.max(Coerce.integer(cursor) ?? 0, 0);
691
- const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
692
- // Get all the docs from the AIG vertex
693
- const vertexDocs = this.filterDocumentsFromVertex(vertex);
694
- let matchingDocIds = vertexDocs;
695
- if (Is.arrayValue(documentCodes)) {
696
- matchingDocIds = vertexDocs.filter(d => documentCodes.includes(d.documentCode));
697
- }
698
- const documentIdGroups = {};
699
- let docGroupIds = [];
700
- for (const doc of matchingDocIds) {
701
- const docId = `${doc.documentId}:${doc.documentCode}`;
702
- if (!docGroupIds.includes(docId)) {
703
- docGroupIds.push(docId);
704
- }
705
- documentIdGroups[docId] ??= [];
706
- documentIdGroups[docId].push(doc);
707
- }
708
- let nextDocCursor;
709
- if (docGroupIds.length > docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE) {
710
- nextDocCursor = (docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE).toString();
711
- }
712
- docGroupIds = docGroupIds.slice(docCursor, docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE);
713
- const finalDocs = [];
714
- for (const docId of docGroupIds) {
715
- finalDocs.push(await this.getDocumentAndRevisions(documentIdGroups[docId], docId, {
716
- includeAttestation: false,
717
- includeBlobStorageData: false,
718
- includeBlobStorageMetadata: false,
719
- includeRemoved,
720
- maxRevisionCount: includeMostRecentRevisions ? 5 : 0
721
- }, 0, userIdentity, nodeIdentity));
722
- }
723
- const docList = {
724
- "@context": [
725
- DocumentContexts.ContextRoot,
726
- DocumentContexts.ContextRootCommon,
727
- SchemaOrgContexts.ContextRoot
728
- ],
729
- type: DocumentTypes.DocumentList,
730
- documents: finalDocs,
731
- cursor: nextDocCursor
732
- };
733
- return JsonLdProcessor.compact(docList, docList["@context"]);
773
+ return this._auditableItemGraphComponent.query({
774
+ id: documentId,
775
+ idMode: "both",
776
+ resourceTypes: [DocumentTypes.Document]
777
+ }, undefined, undefined, undefined, ["id", "dateCreated", "dateModified", "aliases", "annotationObject", "resources", "edges"], cursor, pageSize);
734
778
  }
735
779
  catch (error) {
736
780
  if (BaseError.someErrorName(error, "NotFoundError")) {
@@ -740,103 +784,144 @@ class DocumentManagementService {
740
784
  }
741
785
  }
742
786
  /**
743
- * Encode the document id.
744
- * @param documentId The document identifier.
745
- * @returns The encoded identifier.
787
+ * Update the edges of the document vertex.
788
+ * @param documentVertex The document vertex to update.
789
+ * @param auditableItemGraphEdges The list of edges to use.
790
+ * @returns True if the edges were updated.
746
791
  * @internal
747
792
  */
748
- encodeDocumentIdentifier(documentId, documentRevision) {
749
- return `${documentId}:rev-${documentRevision}`;
750
- }
751
- /**
752
- * Decode the document id.
753
- * @param documentId The document identifier.
754
- * @returns The decoded identifier.
755
- * @internal
756
- */
757
- decodeDocumentIdentifier(documentId) {
758
- const parts = documentId.split(":");
759
- const lastPart = parts[parts.length - 1];
760
- let revision;
761
- if (lastPart.startsWith("rev-")) {
762
- revision = Number.parseInt(lastPart.slice(4), 10);
763
- parts.pop();
793
+ updateEdges(documentVertex, auditableItemGraphEdges) {
794
+ let changed = false;
795
+ const existingEdgeIds = documentVertex.edges?.map(e => e.id) ?? [];
796
+ if (Is.array(auditableItemGraphEdges)) {
797
+ for (const aigEdge of auditableItemGraphEdges) {
798
+ const existingIndex = existingEdgeIds.indexOf(aigEdge.id);
799
+ if (existingIndex !== -1) {
800
+ // If the edge already exists then we don't need to add it again
801
+ // We just need to remove it from the list of existing ids
802
+ // any remaining after this loop will be need to be removed
803
+ existingEdgeIds.splice(existingIndex, 1);
804
+ }
805
+ else {
806
+ const vertexEdge = {
807
+ "@context": AuditableItemGraphContexts.ContextRoot,
808
+ type: AuditableItemGraphTypes.Edge,
809
+ id: aigEdge.id,
810
+ edgeRelationships: ["document"]
811
+ };
812
+ documentVertex.edges ??= [];
813
+ documentVertex.edges?.push(vertexEdge);
814
+ changed = true;
815
+ }
816
+ }
817
+ // Anything left in the existingEdgeIds array means they need to be removed
818
+ if (existingEdgeIds.length > 0 && Is.array(documentVertex.edges)) {
819
+ for (const existingEdgeId of existingEdgeIds) {
820
+ const existingIndex = documentVertex.edges.findIndex(e => e.id === existingEdgeId);
821
+ if (existingIndex !== -1) {
822
+ documentVertex.edges.splice(existingIndex, 1);
823
+ changed = true;
824
+ }
825
+ }
826
+ }
764
827
  }
765
- return { documentId: parts.join(":"), documentRevision: revision };
828
+ return changed;
766
829
  }
767
830
  /**
768
- * Create a full identifier for a document.
769
- * @param documentCode The document code.
831
+ * Update the edges.
832
+ * @param connectedVertices The connected vertices for the edges.
833
+ * @param auditableItemGraphDocumentId The document id to use.
834
+ * @param documentVertex The document vertex to update.
835
+ * @param auditableItemGraphEdges The list of edges to use.
770
836
  * @param documentId The document identifier.
771
- * @param documentRevision The document revision.
772
- * @returns The full identifier.
773
- * @internal
774
- */
775
- createIdentifier(documentCode, documentId, documentRevision) {
776
- const docCode = this.parseDocumentCode(documentCode);
777
- return `documents:${docCode}:${this.encodeDocumentIdentifier(documentId, documentRevision)}`;
778
- }
779
- /**
780
- * Parse the document identifier from the full identifier.
781
- * @param identifier The full identifier to parse.
782
- * @returns The document identifier.
783
- * @internal
784
- */
785
- parseDocumentId(identifier) {
786
- const urn = Urn.fromValidString(identifier);
787
- const remainingParts = urn.namespaceSpecificParts();
788
- if (remainingParts.length < 2) {
789
- throw new GeneralError(this.CLASS_NAME, "invalidDocumentId", { identifier });
790
- }
791
- const documentCode = `unece:DocumentCodeList#${remainingParts[0]}`;
792
- const { documentId, documentRevision } = this.decodeDocumentIdentifier(urn.namespaceSpecific(1));
793
- return { documentCode, documentId, documentRevision };
794
- }
795
- /**
796
- * Parse the document code from the full identifier.
797
- * @param documentCode The document code to parse.
798
- * @returns The document code.
837
+ * @param documentIdFormat The format of the document identifier.
838
+ * @param userIdentity The identity to perform the auditable item graph operation with.
839
+ * @param nodeIdentity The node identity to use for vault operations.
799
840
  * @internal
800
841
  */
801
- parseDocumentCode(documentCode) {
802
- // Document codes are in the format unece:DocumentCodeList#1, so we need to split the string to get the code.
803
- const documentCodeParts = documentCode.split("#");
804
- if (documentCodeParts.length !== 2) {
805
- throw new GeneralError(this.CLASS_NAME, "invalidDocumentCode", { documentCode });
842
+ async updateConnectedEdges(connectedVertices, auditableItemGraphDocumentId, existingEdgeIds, auditableItemGraphEdges, documentId, documentIdFormat, userIdentity, nodeIdentity) {
843
+ if (Is.array(auditableItemGraphEdges)) {
844
+ for (const aigEdge of auditableItemGraphEdges) {
845
+ const connected = connectedVertices[aigEdge.id];
846
+ if (!Is.empty(connected)) {
847
+ let updatedConnected = false;
848
+ const existingIndex = existingEdgeIds.indexOf(aigEdge.id);
849
+ if (existingIndex !== -1) {
850
+ // If the edge already exists we remove it from the list of existing ids
851
+ // any remaining after this loop will be need to be disconnected
852
+ existingEdgeIds.splice(existingIndex, 1);
853
+ }
854
+ // Add the edge with the document vertex id if it doesn't already exist
855
+ const hasEdge = connected.edges?.some(e => e.id === auditableItemGraphDocumentId);
856
+ if (!hasEdge) {
857
+ const vertexEdge = {
858
+ "@context": AuditableItemGraphContexts.ContextRoot,
859
+ type: AuditableItemGraphTypes.Edge,
860
+ id: auditableItemGraphDocumentId,
861
+ edgeRelationships: ["document"]
862
+ };
863
+ connected.edges ??= [];
864
+ connected.edges?.push(vertexEdge);
865
+ updatedConnected = true;
866
+ }
867
+ // Add alias with the document id if option flag is set and it doesn't already exist
868
+ if (aigEdge.addAlias) {
869
+ const alias = connected.aliases?.find(a => a.id === documentId);
870
+ if (Is.empty(alias)) {
871
+ // No existing alias, so create one
872
+ const vertexAlias = {
873
+ "@context": AuditableItemGraphContexts.ContextRoot,
874
+ type: AuditableItemGraphTypes.Alias,
875
+ id: documentId,
876
+ aliasFormat: documentIdFormat,
877
+ annotationObject: aigEdge.aliasAnnotationObject
878
+ };
879
+ connected.aliases ??= [];
880
+ connected.aliases?.push(vertexAlias);
881
+ updatedConnected = true;
882
+ }
883
+ else if (!ObjectHelper.equal(alias.annotationObject, aigEdge.aliasAnnotationObject) ||
884
+ documentIdFormat !== alias.aliasFormat) {
885
+ // The alias already exists, but the format or annotation object has changed
886
+ alias.annotationObject = aigEdge.aliasAnnotationObject;
887
+ alias.aliasFormat = documentIdFormat;
888
+ updatedConnected = true;
889
+ }
890
+ }
891
+ if (updatedConnected) {
892
+ await this._auditableItemGraphComponent.update(connected, userIdentity, nodeIdentity);
893
+ }
894
+ }
895
+ }
806
896
  }
807
- const docCode = Number.parseInt(documentCodeParts[1], 10);
808
- if (!Is.number(docCode)) {
809
- throw new GeneralError(this.CLASS_NAME, "invalidDocumentCode", { documentCode });
897
+ // Anything left in the existingEdgeIds array means they need to be removed
898
+ if (existingEdgeIds.length > 0) {
899
+ for (const existingEdgeId of existingEdgeIds) {
900
+ const connected = connectedVertices[existingEdgeId];
901
+ if (!Is.empty(connected)) {
902
+ let updatedConnected = false;
903
+ // Remove the edge from the connected vertex
904
+ if (Is.arrayValue(connected.edges)) {
905
+ const existingIndex = connected.edges.findIndex(e => e.id === auditableItemGraphDocumentId);
906
+ if (existingIndex !== -1) {
907
+ connected.edges.splice(existingIndex, 1);
908
+ updatedConnected = true;
909
+ }
910
+ }
911
+ // Remove the alias from the connected vertex
912
+ if (Is.arrayValue(connected.aliases)) {
913
+ const existingIndex = connected.aliases.findIndex(e => e.id === documentId);
914
+ if (existingIndex !== -1) {
915
+ connected.aliases.splice(existingIndex, 1);
916
+ updatedConnected = true;
917
+ }
918
+ }
919
+ if (updatedConnected) {
920
+ await this._auditableItemGraphComponent.update(connected, userIdentity, nodeIdentity);
921
+ }
922
+ }
923
+ }
810
924
  }
811
- return docCode;
812
- }
813
- /**
814
- * Get the documents from a vertex.
815
- * @param vertex The vertex to get the documents from.
816
- * @returns The documents.
817
- * @internal
818
- */
819
- filterDocumentsFromVertex(vertex) {
820
- return (vertex.resources
821
- ?.filter(resource => ObjectHelper.extractProperty(resource.resourceObject, ["@type", "type"], false) ===
822
- DocumentTypes.Document)
823
- .map(resource => resource.resourceObject) ?? []);
824
- }
825
- /**
826
- * Find matching documents in the list of existing documents.
827
- * @param documents The documents to search.
828
- * @param documentId The document id.
829
- * @param documentCode The document code.
830
- * @param includeRemoved Include deleted documents.
831
- * @returns The matching documents.
832
- * @internal
833
- */
834
- findMatchingDocs(documents, documentId, documentCode, includeRemoved) {
835
- return documents
836
- .filter(d => d.documentId === documentId &&
837
- d.documentCode === documentCode &&
838
- (includeRemoved || Is.empty(d.dateDeleted)))
839
- .sort((a, b) => b.documentRevision - a.documentRevision);
840
925
  }
841
926
  /**
842
927
  * Generate a hash for the blob data.
@@ -849,47 +934,71 @@ class DocumentManagementService {
849
934
  }
850
935
  /**
851
936
  * Get the documents from the auditable item graph vertex.
852
- * @param matchingDocIds The documents which match document type and id.
853
- * @param identifier The full document identifier.
937
+ * @param documentVertex The vertex containing the documents.
854
938
  * @param options Additional options for the get operation.
855
939
  * @param options.includeBlobStorageMetadata Flag to include the blob storage metadata for the document, defaults to false.
856
940
  * @param options.includeBlobStorageData Flag to include the blob storage data for the document, defaults to false.
857
941
  * @param options.includeAttestation Flag to include the attestation information for the document, defaults to false.
858
942
  * @param options.includeRemoved Flag to include deleted documents, defaults to false.
859
- * @param options.maxRevisionCount Max number of revisions to return, defaults to 0.
860
- * @param revisionCursor The cursor to get the next chunk of revisions.
943
+ * @param cursor The cursor to get the next chunk of revisions.
944
+ * @param pageSize Page size of items to return, defaults to 1 so only most recent is returned.
861
945
  * @param userIdentity The identity to perform the auditable item graph operation with.
862
946
  * @param nodeIdentity The node identity to use for vault operations.
863
947
  * @returns The finalised list of documents.
864
948
  * @internal
865
949
  */
866
- async getDocumentAndRevisions(matchingDocIds, identifier, options, revisionCursor, userIdentity, nodeIdentity) {
867
- const document = matchingDocIds.shift();
868
- if (Is.empty(document)) {
869
- throw new NotFoundError(this.CLASS_NAME, "documentRevisionNotFound", identifier);
870
- }
871
- let revisions;
872
- let nextRevisionCursor;
873
- if (options.maxRevisionCount > 0) {
874
- revisions = matchingDocIds.slice(revisionCursor, revisionCursor + options.maxRevisionCount);
875
- nextRevisionCursor =
876
- matchingDocIds.length > revisionCursor + options.maxRevisionCount
877
- ? (revisionCursor + options.maxRevisionCount).toString()
878
- : undefined;
879
- }
880
- if (options.includeBlobStorageMetadata || options.includeBlobStorageData) {
881
- const blobEntry = await this._blobStorageComponent.get(document.blobStorageId, options.includeBlobStorageData, userIdentity, nodeIdentity);
882
- document.blobStorageEntry = blobEntry;
883
- document["@context"].push(BlobStorageContexts.ContextRoot);
950
+ async getDocumentsFromVertex(documentVertex, options, cursor, pageSize, userIdentity, nodeIdentity) {
951
+ const docList = {
952
+ "@context": [
953
+ DocumentContexts.ContextRoot,
954
+ DocumentContexts.ContextRootCommon,
955
+ SchemaOrgContexts.ContextRoot
956
+ ],
957
+ type: DocumentTypes.DocumentList,
958
+ documents: []
959
+ };
960
+ if (Is.arrayValue(documentVertex.resources)) {
961
+ // Sort by newest revision first
962
+ documentVertex.resources.sort((a, b) => (Coerce.number(b.resourceObject?.documentRevision) ?? 0) -
963
+ (Coerce.number(a.resourceObject?.documentRevision) ?? 0));
964
+ const startIndex = Coerce.integer(cursor) ?? 0;
965
+ const endIndex = Math.min(startIndex + (pageSize ?? 1), documentVertex.resources.length);
966
+ const slicedResources = documentVertex.resources.slice(startIndex, endIndex);
967
+ docList.cursor =
968
+ documentVertex.resources.length > endIndex ? (endIndex + 1).toString() : undefined;
969
+ const includeBlobStorageMetadata = options?.includeBlobStorageMetadata ?? false;
970
+ const includeBlobStorageData = options?.includeBlobStorageData ?? false;
971
+ const includeAttestation = options?.includeAttestation ?? false;
972
+ for (let i = 0; i < slicedResources.length; i++) {
973
+ const document = slicedResources[i].resourceObject;
974
+ if (Is.object(document)) {
975
+ docList.documents.push(document);
976
+ if (includeBlobStorageMetadata || includeBlobStorageData) {
977
+ const blobEntry = await this._blobStorageComponent.get(document.blobStorageId, includeBlobStorageData, userIdentity, nodeIdentity);
978
+ document.blobStorageEntry = blobEntry;
979
+ if (!docList["@context"].includes(BlobStorageContexts.ContextRoot)) {
980
+ docList["@context"].push(BlobStorageContexts.ContextRoot);
981
+ }
982
+ }
983
+ if (includeAttestation && Is.stringValue(document.attestationId)) {
984
+ const attestationInformation = await this._attestationComponent.get(document.attestationId);
985
+ document.attestationInformation = attestationInformation;
986
+ if (!docList["@context"].includes(AttestationContexts.ContextRoot)) {
987
+ docList["@context"].push(AttestationContexts.ContextRoot);
988
+ }
989
+ }
990
+ }
991
+ }
884
992
  }
885
- if (options.includeAttestation && Is.stringValue(document.attestationId)) {
886
- const attestationInformation = await this._attestationComponent.get(document.attestationId);
887
- document.attestationInformation = attestationInformation;
888
- document["@context"].push(AttestationContexts.ContextRoot);
993
+ if (Is.arrayValue(documentVertex.edges)) {
994
+ docList.edges ??= [];
995
+ for (const edge of documentVertex.edges) {
996
+ if (Is.object(edge)) {
997
+ docList.edges.push(edge.id);
998
+ }
999
+ }
889
1000
  }
890
- document.revisions = Is.arrayValue(revisions) ? revisions : undefined;
891
- document.revisionCursor = nextRevisionCursor;
892
- return document;
1001
+ return docList;
893
1002
  }
894
1003
  /**
895
1004
  * Create an attestation for the document.
@@ -906,6 +1015,7 @@ class DocumentManagementService {
906
1015
  SchemaOrgContexts.ContextRoot
907
1016
  ],
908
1017
  type: DocumentTypes.DocumentAttestation,
1018
+ id: document.id,
909
1019
  documentId: document.documentId,
910
1020
  documentCode: document.documentCode,
911
1021
  documentRevision: document.documentRevision,
@@ -925,4 +1035,4 @@ const restEntryPoints = [
925
1035
  }
926
1036
  ];
927
1037
 
928
- export { DocumentManagementService, documentManagementGet, documentManagementQuery, documentManagementRemove, documentManagementSet, generateRestRoutesDocumentManagement, restEntryPoints, tagsDocumentManagement };
1038
+ export { DocumentManagementService, documentManagementCreate, documentManagementGet, documentManagementQuery, documentManagementRemove, documentManagementUpdate, generateRestRoutesDocumentManagement, restEntryPoints, tagsDocumentManagement };