@twin.org/auditable-item-graph-service 0.9.2 → 0.10.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/dist/es/auditableItemGraphService.js +187 -27
- package/dist/es/auditableItemGraphService.js.map +1 -1
- package/dist/es/entities/auditableItemGraphVertex.js +9 -3
- package/dist/es/entities/auditableItemGraphVertex.js.map +1 -1
- package/dist/es/entities/auditableItemGraphVertexV0.js +107 -0
- package/dist/es/entities/auditableItemGraphVertexV0.js.map +1 -0
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/schema.js +2 -0
- package/dist/es/schema.js.map +1 -1
- package/dist/types/auditableItemGraphService.d.ts +6 -2
- package/dist/types/entities/auditableItemGraphVertex.d.ts +5 -0
- package/dist/types/entities/auditableItemGraphVertexV0.d.ts +53 -0
- package/dist/types/index.d.ts +1 -0
- package/docs/changelog.md +87 -0
- package/docs/examples.md +32 -0
- package/docs/open-api/spec.json +15 -6
- package/docs/reference/classes/AuditableItemGraphService.md +11 -2
- package/docs/reference/classes/AuditableItemGraphVertex.md +8 -0
- package/docs/reference/classes/AuditableItemGraphVertexV0.md +101 -0
- package/docs/reference/index.md +1 -0
- package/locales/en.json +2 -1
- package/package.json +16 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
import { HealthCategory, HealthStatus } from "@twin.org/api-models";
|
|
4
|
-
import { AuditableItemGraphContexts, AuditableItemGraphDataTypes, AuditableItemGraphMetricIds, AuditableItemGraphMetrics, AuditableItemGraphTopics, AuditableItemGraphTypes, VerifyDepth } from "@twin.org/auditable-item-graph-models";
|
|
4
|
+
import { AuditableItemGraphAuditMode, AuditableItemGraphContexts, AuditableItemGraphDataTypes, AuditableItemGraphMetricIds, AuditableItemGraphMetrics, AuditableItemGraphTopics, AuditableItemGraphTypes, VerifyDepth } from "@twin.org/auditable-item-graph-models";
|
|
5
5
|
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
6
6
|
import { ArrayHelper, BaseError, Coerce, ComponentFactory, GeneralError, Guards, Is, JsonHelper, Mutex, NotFoundError, ObjectHelper, RandomHelper, StringHelper, Urn, Validation } from "@twin.org/core";
|
|
7
7
|
import { DataTypeHelper } from "@twin.org/data-core";
|
|
@@ -151,6 +151,7 @@ export class AuditableItemGraphService {
|
|
|
151
151
|
/**
|
|
152
152
|
* Create a new graph vertex.
|
|
153
153
|
* @param vertex The vertex to create.
|
|
154
|
+
* @param vertex.auditMode How the mutations of the vertex are recorded, defaults to audited.
|
|
154
155
|
* @param vertex.annotationObject The annotation object for the vertex as JSON-LD.
|
|
155
156
|
* @param vertex.aliases Alternative aliases that can be used to identify the vertex.
|
|
156
157
|
* @param vertex.resources The resources attached to the vertex.
|
|
@@ -159,6 +160,9 @@ export class AuditableItemGraphService {
|
|
|
159
160
|
*/
|
|
160
161
|
async create(vertex) {
|
|
161
162
|
Guards.object(AuditableItemGraphService.CLASS_NAME, "vertex", vertex);
|
|
163
|
+
if (Is.notEmpty(vertex.auditMode)) {
|
|
164
|
+
Guards.arrayOneOf(AuditableItemGraphService.CLASS_NAME, "vertex.auditMode", vertex.auditMode, Object.values(AuditableItemGraphAuditMode));
|
|
165
|
+
}
|
|
162
166
|
const contextIds = await ContextIdStore.getContextIds();
|
|
163
167
|
ContextIdHelper.guard(contextIds, ContextIdKeys.Organization);
|
|
164
168
|
try {
|
|
@@ -186,14 +190,18 @@ export class AuditableItemGraphService {
|
|
|
186
190
|
dateCreated: context.now
|
|
187
191
|
};
|
|
188
192
|
const originalEntity = ObjectHelper.clone(vertexModel);
|
|
193
|
+
vertexModel.auditMode = vertex.auditMode;
|
|
189
194
|
vertexModel.annotationObject = vertex.annotationObject;
|
|
190
195
|
await this.updateAliasList(context, vertexModel, vertex.aliases);
|
|
191
196
|
await this.updateResourceList(context, vertexModel, vertex.resources);
|
|
192
197
|
await this.updateEdgeList(context, vertexModel, vertex.edges);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
// Bypass vertices keep no changeset history, so there is no baseline version to record.
|
|
199
|
+
if (vertex.auditMode !== AuditableItemGraphAuditMode.Bypass) {
|
|
200
|
+
delete originalEntity.aliasIndex;
|
|
201
|
+
delete originalEntity.resourceTypeIndex;
|
|
202
|
+
await this.addChangeset(context, originalEntity, vertexModel, true, 0);
|
|
203
|
+
vertexModel.version = 0;
|
|
204
|
+
}
|
|
197
205
|
await this._vertexStorage.set({
|
|
198
206
|
...vertexModel,
|
|
199
207
|
...this.buildIndexes(vertexModel)
|
|
@@ -212,10 +220,14 @@ export class AuditableItemGraphService {
|
|
|
212
220
|
* Concurrent updates for the same vertex are serialized via `Mutex` on the vertex id.
|
|
213
221
|
* @param vertex The vertex to update.
|
|
214
222
|
* @returns A promise that resolves when the vertex has been updated.
|
|
223
|
+
* @throws GeneralError If a bypass vertex is being switched back to audited.
|
|
215
224
|
*/
|
|
216
225
|
async update(vertex) {
|
|
217
226
|
Guards.object(AuditableItemGraphService.CLASS_NAME, "vertex", vertex);
|
|
218
227
|
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "vertex.id", vertex.id);
|
|
228
|
+
if (Is.notEmpty(vertex.auditMode)) {
|
|
229
|
+
Guards.arrayOneOf(AuditableItemGraphService.CLASS_NAME, "vertex.auditMode", vertex.auditMode, Object.values(AuditableItemGraphAuditMode));
|
|
230
|
+
}
|
|
219
231
|
const vertexId = this.parseVertexId(vertex.id);
|
|
220
232
|
await Mutex.lock(vertexId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
221
233
|
try {
|
|
@@ -241,6 +253,7 @@ export class AuditableItemGraphService {
|
|
|
241
253
|
organizationIdentity: ownerOrganizationId,
|
|
242
254
|
userIdentity: contextIds?.[ContextIdKeys.User]
|
|
243
255
|
};
|
|
256
|
+
const auditModeTransition = this.resolveAuditModeTransition(vertexEntity, vertex.auditMode);
|
|
244
257
|
delete vertexEntity.aliasIndex;
|
|
245
258
|
const originalEntity = ObjectHelper.clone(vertexEntity);
|
|
246
259
|
const newEntity = ObjectHelper.clone(vertexEntity);
|
|
@@ -248,7 +261,7 @@ export class AuditableItemGraphService {
|
|
|
248
261
|
await this.updateAliasList(context, newEntity, vertex.aliases);
|
|
249
262
|
await this.updateResourceList(context, newEntity, vertex.resources);
|
|
250
263
|
await this.updateEdgeList(context, newEntity, vertex.edges);
|
|
251
|
-
await this.persistVertexChanges(context, vertexId, vertex.id, originalEntity, newEntity);
|
|
264
|
+
await this.persistVertexChanges(context, vertexId, vertex.id, originalEntity, newEntity, auditModeTransition);
|
|
252
265
|
}
|
|
253
266
|
catch (error) {
|
|
254
267
|
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "updatingFailed", undefined, error);
|
|
@@ -263,10 +276,14 @@ export class AuditableItemGraphService {
|
|
|
263
276
|
* Serialized with `update` via `Mutex` on the same vertex id within this instance.
|
|
264
277
|
* @param partial The partial vertex update.
|
|
265
278
|
* @returns A promise that resolves when the partial update has been applied.
|
|
279
|
+
* @throws GeneralError If a bypass vertex is being switched back to audited.
|
|
266
280
|
*/
|
|
267
281
|
async updatePartial(partial) {
|
|
268
282
|
Guards.object(AuditableItemGraphService.CLASS_NAME, "partial", partial);
|
|
269
283
|
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "partial.id", partial.id);
|
|
284
|
+
if (Is.notEmpty(partial.auditMode)) {
|
|
285
|
+
Guards.arrayOneOf(AuditableItemGraphService.CLASS_NAME, "partial.auditMode", partial.auditMode, Object.values(AuditableItemGraphAuditMode));
|
|
286
|
+
}
|
|
270
287
|
const vertexId = this.parseVertexId(partial.id);
|
|
271
288
|
await Mutex.lock(vertexId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
272
289
|
try {
|
|
@@ -289,6 +306,7 @@ export class AuditableItemGraphService {
|
|
|
289
306
|
organizationIdentity: ownerOrganizationId,
|
|
290
307
|
userIdentity: contextIds?.[ContextIdKeys.User]
|
|
291
308
|
};
|
|
309
|
+
const auditModeTransition = this.resolveAuditModeTransition(vertexEntity, partial.auditMode);
|
|
292
310
|
delete vertexEntity.aliasIndex;
|
|
293
311
|
const originalEntity = ObjectHelper.clone(vertexEntity);
|
|
294
312
|
const newEntity = ObjectHelper.clone(vertexEntity);
|
|
@@ -307,7 +325,7 @@ export class AuditableItemGraphService {
|
|
|
307
325
|
const edgePatch = this.validateListPatch("partial.edgePatches", partial.edgePatches);
|
|
308
326
|
await this.applyEdgePatch(context, newEntity, edgePatch);
|
|
309
327
|
}
|
|
310
|
-
await this.persistVertexChanges(context, vertexId, partial.id, originalEntity, newEntity);
|
|
328
|
+
await this.persistVertexChanges(context, vertexId, partial.id, originalEntity, newEntity, auditModeTransition);
|
|
311
329
|
}
|
|
312
330
|
catch (error) {
|
|
313
331
|
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "updatingFailed", undefined, error);
|
|
@@ -342,7 +360,10 @@ export class AuditableItemGraphService {
|
|
|
342
360
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
343
361
|
}
|
|
344
362
|
const vertexModel = this.vertexEntityToJsonLd(vertexEntity);
|
|
345
|
-
|
|
363
|
+
// Bypass vertices have no changesets to verify, so verification is never reported for them.
|
|
364
|
+
const verifySignatureDepth = this.isBypass(vertexEntity)
|
|
365
|
+
? VerifyDepth.None
|
|
366
|
+
: (options?.verifySignatureDepth ?? VerifyDepth.None);
|
|
346
367
|
let verified;
|
|
347
368
|
if (verifySignatureDepth === VerifyDepth.Current ||
|
|
348
369
|
verifySignatureDepth === VerifyDepth.All) {
|
|
@@ -407,8 +428,12 @@ export class AuditableItemGraphService {
|
|
|
407
428
|
if (Is.empty(vertexEntity)) {
|
|
408
429
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
409
430
|
}
|
|
410
|
-
|
|
411
|
-
|
|
431
|
+
// Bypass vertices maintain no changeset history.
|
|
432
|
+
const chunk = this.isBypass(vertexEntity)
|
|
433
|
+
? { changesets: [], verified: true, cursor: undefined }
|
|
434
|
+
: await this.verifyChangesetChunk(vertexId, options?.verifySignatureDepth ?? VerifyDepth.None, cursor, limit);
|
|
435
|
+
if (!this.isBypass(vertexEntity) &&
|
|
436
|
+
(options?.verifySignatureDepth ?? VerifyDepth.None) !== VerifyDepth.None) {
|
|
412
437
|
if (chunk.verified) {
|
|
413
438
|
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemGraphMetricIds.VerificationsSucceeded);
|
|
414
439
|
}
|
|
@@ -462,6 +487,10 @@ export class AuditableItemGraphService {
|
|
|
462
487
|
if (Is.empty(vertexEntity)) {
|
|
463
488
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
464
489
|
}
|
|
490
|
+
// Bypass vertices maintain no changeset history.
|
|
491
|
+
if (this.isBypass(vertexEntity)) {
|
|
492
|
+
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "changesetNotFound", id);
|
|
493
|
+
}
|
|
465
494
|
const changesetEntity = await this._changesetStorage.get(changesetId);
|
|
466
495
|
if (Is.empty(changesetEntity)) {
|
|
467
496
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "changesetNotFound", id);
|
|
@@ -494,7 +523,7 @@ export class AuditableItemGraphService {
|
|
|
494
523
|
* @param id The id of the vertex.
|
|
495
524
|
* @param version The version number to retrieve.
|
|
496
525
|
* @returns The vertex reconstructed at that version.
|
|
497
|
-
* @throws NotFoundError if the vertex or version is not found.
|
|
526
|
+
* @throws NotFoundError if the vertex or version is not found, a bypass vertex never has versions.
|
|
498
527
|
*/
|
|
499
528
|
async getVersion(id, version) {
|
|
500
529
|
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "id", id);
|
|
@@ -512,6 +541,10 @@ export class AuditableItemGraphService {
|
|
|
512
541
|
if (Is.empty(vertexEntity)) {
|
|
513
542
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
514
543
|
}
|
|
544
|
+
// Bypass vertices maintain no version chain.
|
|
545
|
+
if (this.isBypass(vertexEntity)) {
|
|
546
|
+
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "versionNotFound", version.toString());
|
|
547
|
+
}
|
|
515
548
|
const currentVersion = vertexEntity.version ?? 0;
|
|
516
549
|
if (version > currentVersion || version < 0) {
|
|
517
550
|
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "versionNotFound", version.toString());
|
|
@@ -552,7 +585,8 @@ export class AuditableItemGraphService {
|
|
|
552
585
|
* @param options Additional options for the operation.
|
|
553
586
|
* @param options.after Only return versions created after this ISO 8601 timestamp (exclusive).
|
|
554
587
|
* @param options.before Only return versions created before this ISO 8601 timestamp (exclusive).
|
|
555
|
-
* @returns The list of vertex versions
|
|
588
|
+
* @returns The list of vertex versions, a bypass vertex returns a single baseline entry for its
|
|
589
|
+
* current state when that falls within the requested range.
|
|
556
590
|
* @throws NotFoundError if the vertex is not found.
|
|
557
591
|
*/
|
|
558
592
|
async getVersions(id, options) {
|
|
@@ -572,20 +606,9 @@ export class AuditableItemGraphService {
|
|
|
572
606
|
}
|
|
573
607
|
const beforeDate = Coerce.dateTime(options?.before);
|
|
574
608
|
const afterDate = Coerce.dateTime(options?.after);
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const versions = [];
|
|
579
|
-
for (const changeset of allChangesets) {
|
|
580
|
-
const changesetDate = Coerce.dateTime(changeset.dateCreated);
|
|
581
|
-
const afterExcluded = !Is.empty(afterDate) && !Is.empty(changesetDate) && changesetDate <= afterDate;
|
|
582
|
-
if (!afterExcluded) {
|
|
583
|
-
versions.push({
|
|
584
|
-
version: changeset.version ?? 0,
|
|
585
|
-
dateCreated: changeset.dateCreated
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
}
|
|
609
|
+
const versions = this.isBypass(vertexEntity)
|
|
610
|
+
? this.bypassVersions(vertexEntity, afterDate, beforeDate)
|
|
611
|
+
: await this.auditedVersions(vertexId, afterDate, beforeDate);
|
|
589
612
|
const versionList = {
|
|
590
613
|
"@context": [
|
|
591
614
|
SchemaOrgContexts.Context,
|
|
@@ -795,6 +818,47 @@ export class AuditableItemGraphService {
|
|
|
795
818
|
Guards.object(AuditableItemGraphService.CLASS_NAME, propertyName, patch);
|
|
796
819
|
return patch;
|
|
797
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Resolve the audit mode for an update, bypass is terminal so it can not return to audited.
|
|
823
|
+
* @param vertexEntity The stored vertex entity.
|
|
824
|
+
* @param requestedMode The audit mode requested by the caller, when absent the stored mode is kept.
|
|
825
|
+
* @returns The audit mode to apply and whether the existing history must be compacted.
|
|
826
|
+
* @throws GeneralError If a bypass vertex is being switched back to audited.
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
resolveAuditModeTransition(vertexEntity, requestedMode) {
|
|
830
|
+
const currentMode = vertexEntity.auditMode ?? AuditableItemGraphAuditMode.Audited;
|
|
831
|
+
if (Is.empty(requestedMode)) {
|
|
832
|
+
return { auditMode: currentMode, compactHistory: false };
|
|
833
|
+
}
|
|
834
|
+
if (currentMode === AuditableItemGraphAuditMode.Bypass &&
|
|
835
|
+
requestedMode === AuditableItemGraphAuditMode.Audited) {
|
|
836
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "auditModeTransitionNotAllowed", {
|
|
837
|
+
currentMode,
|
|
838
|
+
requestedMode
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
auditMode: requestedMode,
|
|
843
|
+
compactHistory: currentMode === AuditableItemGraphAuditMode.Audited &&
|
|
844
|
+
requestedMode === AuditableItemGraphAuditMode.Bypass
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Discard the changeset history for a vertex along with any proofs the changesets reference.
|
|
849
|
+
* @param vertexId The compact vertex id.
|
|
850
|
+
* @returns A promise that resolves when the history and its proofs have been removed.
|
|
851
|
+
* @internal
|
|
852
|
+
*/
|
|
853
|
+
async compactVertexHistory(vertexId) {
|
|
854
|
+
const changesets = await this.internalGetChangesets(vertexId);
|
|
855
|
+
for (const changeset of changesets) {
|
|
856
|
+
await this._changesetStorage.remove(changeset.id);
|
|
857
|
+
if (Is.stringValue(changeset.proofId)) {
|
|
858
|
+
await this._immutableProofComponent.remove(changeset.proofId);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
798
862
|
/**
|
|
799
863
|
* Persist vertex changes after update or partial update.
|
|
800
864
|
* @param context The context for the operation.
|
|
@@ -802,10 +866,17 @@ export class AuditableItemGraphService {
|
|
|
802
866
|
* @param vertexUrn The vertex URN for events.
|
|
803
867
|
* @param originalEntity The entity before changes.
|
|
804
868
|
* @param newEntity The entity after changes.
|
|
869
|
+
* @param auditModeTransition The audit mode to apply and whether the history must be compacted.
|
|
870
|
+
* @param auditModeTransition.auditMode The audit mode to apply.
|
|
871
|
+
* @param auditModeTransition.compactHistory Whether the existing history must be discarded.
|
|
805
872
|
* @returns A promise that resolves when the changes have been persisted and events published.
|
|
806
873
|
* @internal
|
|
807
874
|
*/
|
|
808
|
-
async persistVertexChanges(context, vertexId, vertexUrn, originalEntity, newEntity) {
|
|
875
|
+
async persistVertexChanges(context, vertexId, vertexUrn, originalEntity, newEntity, auditModeTransition) {
|
|
876
|
+
if (auditModeTransition.auditMode === AuditableItemGraphAuditMode.Bypass) {
|
|
877
|
+
await this.persistBypassChanges(context, vertexId, vertexUrn, originalEntity, newEntity, auditModeTransition.compactHistory);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
809
880
|
const nextVersion = Is.empty(originalEntity.version)
|
|
810
881
|
? (await this.internalGetChangesets(vertexId)).length
|
|
811
882
|
: originalEntity.version + 1;
|
|
@@ -824,6 +895,47 @@ export class AuditableItemGraphService {
|
|
|
824
895
|
await this._eventBusComponent?.publish(AuditableItemGraphTopics.VertexUpdated, { id: vertexUrn, patches });
|
|
825
896
|
}
|
|
826
897
|
}
|
|
898
|
+
/**
|
|
899
|
+
* Persist vertex changes for a bypass vertex, overwriting the stored state with no changeset.
|
|
900
|
+
* @param context The context for the operation.
|
|
901
|
+
* @param vertexId The compact vertex id.
|
|
902
|
+
* @param vertexUrn The vertex URN for events.
|
|
903
|
+
* @param originalEntity The entity before changes.
|
|
904
|
+
* @param newEntity The entity after changes.
|
|
905
|
+
* @param compactHistory Whether the vertex is switching from audited and must discard its history.
|
|
906
|
+
* @returns A promise that resolves when the changes have been persisted and events published.
|
|
907
|
+
* @internal
|
|
908
|
+
*/
|
|
909
|
+
async persistBypassChanges(context, vertexId, vertexUrn, originalEntity, newEntity, compactHistory) {
|
|
910
|
+
newEntity.auditMode = AuditableItemGraphAuditMode.Bypass;
|
|
911
|
+
delete newEntity.version;
|
|
912
|
+
const patches = JsonHelper.diff(originalEntity, newEntity);
|
|
913
|
+
if (patches.length === 0) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (compactHistory) {
|
|
917
|
+
await this.compactVertexHistory(vertexId);
|
|
918
|
+
}
|
|
919
|
+
newEntity.dateModified = context.now;
|
|
920
|
+
const indexes = this.buildIndexes(newEntity);
|
|
921
|
+
await this._vertexStorage.set({
|
|
922
|
+
...newEntity,
|
|
923
|
+
...indexes
|
|
924
|
+
});
|
|
925
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemGraphMetricIds.VerticesUpdated, {
|
|
926
|
+
patchCount: patches.length
|
|
927
|
+
});
|
|
928
|
+
await this._eventBusComponent?.publish(AuditableItemGraphTopics.VertexUpdated, { id: vertexUrn, patches });
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Whether the vertex records its mutations in place with no audit trail.
|
|
932
|
+
* @param vertexEntity The stored vertex entity.
|
|
933
|
+
* @returns True if the vertex is in bypass mode.
|
|
934
|
+
* @internal
|
|
935
|
+
*/
|
|
936
|
+
isBypass(vertexEntity) {
|
|
937
|
+
return vertexEntity.auditMode === AuditableItemGraphAuditMode.Bypass;
|
|
938
|
+
}
|
|
827
939
|
/**
|
|
828
940
|
* Map the vertex entity to JSON-LD.
|
|
829
941
|
* @param vertexEntity The vertex entity.
|
|
@@ -842,6 +954,7 @@ export class AuditableItemGraphService {
|
|
|
842
954
|
dateCreated: vertexEntity.dateCreated,
|
|
843
955
|
dateModified: vertexEntity.dateModified,
|
|
844
956
|
organizationIdentity: vertexEntity.organizationIdentity,
|
|
957
|
+
auditMode: vertexEntity.auditMode,
|
|
845
958
|
annotationObject: vertexEntity.annotationObject
|
|
846
959
|
};
|
|
847
960
|
if (Is.arrayValue(vertexEntity.aliases)) {
|
|
@@ -981,6 +1094,53 @@ export class AuditableItemGraphService {
|
|
|
981
1094
|
} while (Is.stringValue(cursor));
|
|
982
1095
|
return all;
|
|
983
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Build the version list for an audited vertex from its changesets.
|
|
1099
|
+
* @param vertexId The compact vertex id.
|
|
1100
|
+
* @param afterDate Only include versions created after this date, exclusive.
|
|
1101
|
+
* @param beforeDate Only include versions created before this date, exclusive.
|
|
1102
|
+
* @returns The matching versions in ascending order.
|
|
1103
|
+
* @internal
|
|
1104
|
+
*/
|
|
1105
|
+
async auditedVersions(vertexId, afterDate, beforeDate) {
|
|
1106
|
+
const allChangesets = await this.internalGetChangesets(vertexId, {
|
|
1107
|
+
before: beforeDate?.toISOString()
|
|
1108
|
+
});
|
|
1109
|
+
const versions = [];
|
|
1110
|
+
for (const changeset of allChangesets) {
|
|
1111
|
+
const changesetDate = Coerce.dateTime(changeset.dateCreated);
|
|
1112
|
+
const afterExcluded = !Is.empty(afterDate) && !Is.empty(changesetDate) && changesetDate <= afterDate;
|
|
1113
|
+
if (!afterExcluded) {
|
|
1114
|
+
versions.push({
|
|
1115
|
+
version: changeset.version ?? 0,
|
|
1116
|
+
dateCreated: changeset.dateCreated
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return versions;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Build the version list for a bypass vertex, which only exposes its current state as
|
|
1124
|
+
* a single baseline entry numbered zero.
|
|
1125
|
+
* @param vertexEntity The stored vertex entity.
|
|
1126
|
+
* @param afterDate Only include the state when it was modified after this date, exclusive.
|
|
1127
|
+
* @param beforeDate Only include the state when it was modified before this date, exclusive.
|
|
1128
|
+
* @returns The baseline entry when the state falls within the range, otherwise empty.
|
|
1129
|
+
* @internal
|
|
1130
|
+
*/
|
|
1131
|
+
bypassVersions(vertexEntity, afterDate, beforeDate) {
|
|
1132
|
+
const stateDate = vertexEntity.dateModified ?? vertexEntity.dateCreated;
|
|
1133
|
+
const stateDateTime = Coerce.dateTime(stateDate);
|
|
1134
|
+
if (!Is.empty(stateDateTime)) {
|
|
1135
|
+
if (!Is.empty(beforeDate) && stateDateTime >= beforeDate) {
|
|
1136
|
+
return [];
|
|
1137
|
+
}
|
|
1138
|
+
if (!Is.empty(afterDate) && stateDateTime <= afterDate) {
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return [{ version: 0, dateCreated: stateDate }];
|
|
1143
|
+
}
|
|
984
1144
|
/**
|
|
985
1145
|
* Replace the aliases of a vertex model (PUT).
|
|
986
1146
|
* @param context The context for the operation.
|