@twin.org/auditable-item-graph-service 0.0.2-next.3 → 0.0.2-next.5
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/cjs/index.cjs +129 -34
- package/dist/esm/index.mjs +129 -34
- package/dist/types/auditableItemGraphService.d.ts +9 -1
- package/dist/types/entities/auditableItemGraphEdge.d.ts +4 -0
- package/docs/changelog.md +28 -0
- package/docs/open-api/spec.json +25 -4
- package/docs/reference/classes/AuditableItemGraphEdge.md +8 -0
- package/docs/reference/classes/AuditableItemGraphService.md +14 -0
- package/locales/en.json +4 -2
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -89,7 +89,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
89
89
|
],
|
|
90
90
|
edges: [
|
|
91
91
|
{
|
|
92
|
-
|
|
92
|
+
targetId: "aig:1234567890",
|
|
93
93
|
edgeRelationships: ["frenemy"],
|
|
94
94
|
annotationObject: {
|
|
95
95
|
"@context": "https://schema.org",
|
|
@@ -98,7 +98,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
|
-
|
|
101
|
+
targetId: "aig:45678901234",
|
|
102
102
|
edgeRelationships: ["end"],
|
|
103
103
|
annotationObject: {
|
|
104
104
|
"@context": "https://schema.org",
|
|
@@ -297,6 +297,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
297
297
|
edges: [
|
|
298
298
|
{
|
|
299
299
|
id: "edge1",
|
|
300
|
+
targetId: "aig:1234567890",
|
|
300
301
|
edgeRelationships: ["frenemy"],
|
|
301
302
|
annotationObject: {
|
|
302
303
|
"@context": "https://schema.org",
|
|
@@ -306,6 +307,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
306
307
|
},
|
|
307
308
|
{
|
|
308
309
|
id: "edge2",
|
|
310
|
+
targetId: "aig:45678901234",
|
|
309
311
|
edgeRelationships: ["end"],
|
|
310
312
|
annotationObject: {
|
|
311
313
|
"@context": "https://schema.org",
|
|
@@ -521,6 +523,7 @@ async function auditableItemGraphList(httpRequestContext, componentName, request
|
|
|
521
523
|
const result = await component.query({
|
|
522
524
|
id: request.query?.id,
|
|
523
525
|
idMode: request.query?.idMode,
|
|
526
|
+
idExact: core.Coerce.boolean(request.query?.idExact),
|
|
524
527
|
resourceTypes: apiModels.HttpParameterHelper.arrayFromString(request.query?.resourceTypes)
|
|
525
528
|
}, apiModels.HttpParameterHelper.objectFromString(request.query?.conditions), request.query?.orderBy, request.query?.orderByDirection, apiModels.HttpParameterHelper.arrayFromString(request.query?.properties), request.query?.cursor, core.Coerce.integer(request.query?.pageSize));
|
|
526
529
|
return {
|
|
@@ -537,15 +540,19 @@ async function auditableItemGraphList(httpRequestContext, componentName, request
|
|
|
537
540
|
* Class for performing auditable item graph operations.
|
|
538
541
|
*/
|
|
539
542
|
class AuditableItemGraphService {
|
|
543
|
+
/**
|
|
544
|
+
* The namespace for the service.
|
|
545
|
+
* @internal
|
|
546
|
+
*/
|
|
547
|
+
static NAMESPACE = "aig";
|
|
540
548
|
/**
|
|
541
549
|
* The namespace for the service changeset.
|
|
542
550
|
*/
|
|
543
551
|
static NAMESPACE_CHANGESET = "changeset";
|
|
544
552
|
/**
|
|
545
|
-
* The namespace for the service.
|
|
546
|
-
* @internal
|
|
553
|
+
* The namespace for the service edge.
|
|
547
554
|
*/
|
|
548
|
-
static
|
|
555
|
+
static NAMESPACE_EDGE = "edge";
|
|
549
556
|
/**
|
|
550
557
|
* The keys to pick when creating the proof for the stream.
|
|
551
558
|
* @internal
|
|
@@ -639,7 +646,7 @@ class AuditableItemGraphService {
|
|
|
639
646
|
...vertexModel,
|
|
640
647
|
...this.buildIndexes(vertexModel)
|
|
641
648
|
});
|
|
642
|
-
const fullId = new core.Urn(AuditableItemGraphService.
|
|
649
|
+
const fullId = new core.Urn(AuditableItemGraphService.NAMESPACE, id).toString();
|
|
643
650
|
await this._eventBusComponent?.publish(auditableItemGraphModels.AuditableItemGraphTopics.VertexCreated, { id: fullId });
|
|
644
651
|
return fullId;
|
|
645
652
|
}
|
|
@@ -660,9 +667,9 @@ class AuditableItemGraphService {
|
|
|
660
667
|
async get(id, options) {
|
|
661
668
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
662
669
|
const urnParsed = core.Urn.fromValidString(id);
|
|
663
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
670
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
664
671
|
throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
665
|
-
namespace: AuditableItemGraphService.
|
|
672
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
666
673
|
id
|
|
667
674
|
});
|
|
668
675
|
}
|
|
@@ -735,9 +742,9 @@ class AuditableItemGraphService {
|
|
|
735
742
|
core.Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
736
743
|
core.Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
737
744
|
const urnParsed = core.Urn.fromValidString(vertex.id);
|
|
738
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
745
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
739
746
|
throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
740
|
-
namespace: AuditableItemGraphService.
|
|
747
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
741
748
|
id: vertex.id
|
|
742
749
|
});
|
|
743
750
|
}
|
|
@@ -790,9 +797,9 @@ class AuditableItemGraphService {
|
|
|
790
797
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
791
798
|
core.Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
792
799
|
const urnParsed = core.Urn.fromValidString(id);
|
|
793
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
800
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
794
801
|
throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
795
|
-
namespace: AuditableItemGraphService.
|
|
802
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
796
803
|
id
|
|
797
804
|
});
|
|
798
805
|
}
|
|
@@ -832,6 +839,7 @@ class AuditableItemGraphService {
|
|
|
832
839
|
* @param options The query options.
|
|
833
840
|
* @param options.id The optional id to look for.
|
|
834
841
|
* @param options.idMode Look in id, alias or both, defaults to both.
|
|
842
|
+
* @param options.idExact Find only exact matches, default to false meaning partial matching.
|
|
835
843
|
* @param options.includesResourceTypes Include vertices with specific resource types.
|
|
836
844
|
* @param conditions Conditions to use in the query.
|
|
837
845
|
* @param orderBy The order for the results, defaults to created.
|
|
@@ -853,13 +861,14 @@ class AuditableItemGraphService {
|
|
|
853
861
|
const combinedConditions = conditions ?? [];
|
|
854
862
|
const orderProperty = orderBy ?? "dateCreated";
|
|
855
863
|
const orderDirection = orderByDirection ?? entity.SortDirection.Descending;
|
|
864
|
+
const idExact = options?.idExact ?? false;
|
|
856
865
|
const idOrAlias = options?.id;
|
|
857
866
|
if (core.Is.stringValue(idOrAlias)) {
|
|
858
867
|
const idMode = options?.idMode ?? "both";
|
|
859
868
|
if (idMode === "id" || idMode === "both") {
|
|
860
869
|
combinedConditions.push({
|
|
861
870
|
property: "id",
|
|
862
|
-
comparison: entity.ComparisonOperator.Includes,
|
|
871
|
+
comparison: idExact ? entity.ComparisonOperator.Equals : entity.ComparisonOperator.Includes,
|
|
863
872
|
value: idOrAlias
|
|
864
873
|
});
|
|
865
874
|
}
|
|
@@ -867,7 +876,7 @@ class AuditableItemGraphService {
|
|
|
867
876
|
combinedConditions.push({
|
|
868
877
|
property: "aliasIndex",
|
|
869
878
|
comparison: entity.ComparisonOperator.Includes,
|
|
870
|
-
value: idOrAlias.toLowerCase()
|
|
879
|
+
value: idExact ? `||${idOrAlias.toLowerCase()}||` : idOrAlias.toLowerCase()
|
|
871
880
|
});
|
|
872
881
|
}
|
|
873
882
|
}
|
|
@@ -876,7 +885,7 @@ class AuditableItemGraphService {
|
|
|
876
885
|
combinedConditions.push({
|
|
877
886
|
property: "resourceTypeIndex",
|
|
878
887
|
comparison: entity.ComparisonOperator.Includes,
|
|
879
|
-
value: resourceType.toLowerCase()
|
|
888
|
+
value: `||${resourceType.toLowerCase()}||`
|
|
880
889
|
});
|
|
881
890
|
}
|
|
882
891
|
}
|
|
@@ -925,7 +934,7 @@ class AuditableItemGraphService {
|
|
|
925
934
|
standardsSchemaOrg.SchemaOrgContexts.ContextRoot
|
|
926
935
|
],
|
|
927
936
|
type: auditableItemGraphModels.AuditableItemGraphTypes.Vertex,
|
|
928
|
-
id: new core.Urn(AuditableItemGraphService.
|
|
937
|
+
id: new core.Urn(AuditableItemGraphService.NAMESPACE, vertexEntity.id).toString(),
|
|
929
938
|
dateCreated: vertexEntity.dateCreated,
|
|
930
939
|
dateModified: vertexEntity.dateModified,
|
|
931
940
|
nodeIdentity: vertexEntity.nodeIdentity,
|
|
@@ -980,7 +989,8 @@ class AuditableItemGraphService {
|
|
|
980
989
|
standardsSchemaOrg.SchemaOrgContexts.ContextRoot
|
|
981
990
|
],
|
|
982
991
|
type: auditableItemGraphModels.AuditableItemGraphTypes.Edge,
|
|
983
|
-
id: edgeEntity.id,
|
|
992
|
+
id: this.fullEdgeId(vertexEntity.id, edgeEntity.id),
|
|
993
|
+
targetId: edgeEntity.targetId,
|
|
984
994
|
dateCreated: edgeEntity.dateCreated,
|
|
985
995
|
dateModified: edgeEntity.dateModified,
|
|
986
996
|
dateDeleted: edgeEntity.dateDeleted,
|
|
@@ -994,11 +1004,12 @@ class AuditableItemGraphService {
|
|
|
994
1004
|
}
|
|
995
1005
|
/**
|
|
996
1006
|
* Map the changeset entity to a JSON-LD.
|
|
1007
|
+
* @param vertexId The id of the vertex the changeset belongs to.
|
|
997
1008
|
* @param changesetEntity The changeset entity.
|
|
998
1009
|
* @returns The model.
|
|
999
1010
|
* @internal
|
|
1000
1011
|
*/
|
|
1001
|
-
changesetEntityToJsonLd(changesetEntity) {
|
|
1012
|
+
changesetEntityToJsonLd(vertexId, changesetEntity) {
|
|
1002
1013
|
const model = {
|
|
1003
1014
|
"@context": [
|
|
1004
1015
|
auditableItemGraphModels.AuditableItemGraphContexts.ContextRoot,
|
|
@@ -1006,7 +1017,11 @@ class AuditableItemGraphService {
|
|
|
1006
1017
|
standardsSchemaOrg.SchemaOrgContexts.ContextRoot
|
|
1007
1018
|
],
|
|
1008
1019
|
type: auditableItemGraphModels.AuditableItemGraphTypes.Changeset,
|
|
1009
|
-
id:
|
|
1020
|
+
id: new core.Urn(AuditableItemGraphService.NAMESPACE, [
|
|
1021
|
+
vertexId,
|
|
1022
|
+
AuditableItemGraphService.NAMESPACE_CHANGESET,
|
|
1023
|
+
changesetEntity.id
|
|
1024
|
+
]).toString(),
|
|
1010
1025
|
dateCreated: changesetEntity.dateCreated,
|
|
1011
1026
|
userIdentity: changesetEntity.userIdentity,
|
|
1012
1027
|
patches: changesetEntity.patches.map(p => ({
|
|
@@ -1058,6 +1073,12 @@ class AuditableItemGraphService {
|
|
|
1058
1073
|
async updateAlias(context, vertex, alias) {
|
|
1059
1074
|
core.Guards.object(this.CLASS_NAME, "alias", alias);
|
|
1060
1075
|
core.Guards.stringValue(this.CLASS_NAME, "alias.id", alias.id);
|
|
1076
|
+
if (alias.unique ?? false) {
|
|
1077
|
+
const existingVertices = await this.findMatchingVertices(vertex.id, alias.id);
|
|
1078
|
+
if (existingVertices) {
|
|
1079
|
+
throw new core.GeneralError(this.CLASS_NAME, "aliasNotUnique", { aliasId: alias.id });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1061
1082
|
if (core.Is.object(alias.annotationObject)) {
|
|
1062
1083
|
const validationFailures = [];
|
|
1063
1084
|
await dataJsonLd.JsonLdHelper.validate(alias.annotationObject, validationFailures);
|
|
@@ -1159,7 +1180,7 @@ class AuditableItemGraphService {
|
|
|
1159
1180
|
// The active edges that are not in the update list should be marked as deleted.
|
|
1160
1181
|
if (core.Is.arrayValue(active)) {
|
|
1161
1182
|
for (const edge of active) {
|
|
1162
|
-
if (!edges?.find(
|
|
1183
|
+
if (!edges?.find(e => core.Is.stringValue(e.id) && this.reduceEdgeId(e.id) === edge.id)) {
|
|
1163
1184
|
edge.dateDeleted = context.now;
|
|
1164
1185
|
}
|
|
1165
1186
|
}
|
|
@@ -1179,15 +1200,15 @@ class AuditableItemGraphService {
|
|
|
1179
1200
|
*/
|
|
1180
1201
|
async updateEdge(context, vertex, edge) {
|
|
1181
1202
|
core.Guards.object(this.CLASS_NAME, "edge", edge);
|
|
1182
|
-
core.Guards.stringValue(this.CLASS_NAME, "edge.
|
|
1203
|
+
core.Guards.stringValue(this.CLASS_NAME, "edge.targetId", edge.targetId);
|
|
1183
1204
|
core.Guards.arrayValue(this.CLASS_NAME, "edge.edgeRelationships", edge.edgeRelationships);
|
|
1184
1205
|
const validationFailures = [];
|
|
1185
|
-
if (edge.
|
|
1206
|
+
if (edge.targetId === vertex.id) {
|
|
1186
1207
|
validationFailures.push({
|
|
1187
1208
|
property: "id",
|
|
1188
1209
|
reason: `validation.${core.StringHelper.camelCase(this.CLASS_NAME)}.edgeIdSameAsVertexId`,
|
|
1189
1210
|
properties: {
|
|
1190
|
-
|
|
1211
|
+
targetId: edge.targetId
|
|
1191
1212
|
}
|
|
1192
1213
|
});
|
|
1193
1214
|
}
|
|
@@ -1195,22 +1216,29 @@ class AuditableItemGraphService {
|
|
|
1195
1216
|
await dataJsonLd.JsonLdHelper.validate(edge.annotationObject, validationFailures);
|
|
1196
1217
|
}
|
|
1197
1218
|
core.Validation.asValidationError(this.CLASS_NAME, "edge.annotationObject", validationFailures);
|
|
1219
|
+
let findId = core.Is.stringValue(edge.id) ? this.reduceEdgeId(edge.id) : undefined;
|
|
1220
|
+
if (core.Is.empty(findId)) {
|
|
1221
|
+
findId = core.Converter.bytesToHex(core.RandomHelper.generate(32), false);
|
|
1222
|
+
}
|
|
1198
1223
|
// Try to find an existing edge with the same id.
|
|
1199
|
-
const existing = vertex.edges?.find(r => r.id ===
|
|
1224
|
+
const existing = vertex.edges?.find(r => r.id === findId);
|
|
1200
1225
|
if (core.Is.empty(existing) || !core.Is.empty(existing?.dateDeleted)) {
|
|
1201
1226
|
// Did not find a matching item, or found one which is deleted.
|
|
1202
1227
|
vertex.edges ??= [];
|
|
1203
1228
|
const model = {
|
|
1204
|
-
id:
|
|
1229
|
+
id: findId,
|
|
1230
|
+
targetId: edge.targetId,
|
|
1205
1231
|
dateCreated: context.now,
|
|
1206
1232
|
annotationObject: edge.annotationObject,
|
|
1207
1233
|
edgeRelationships: edge.edgeRelationships
|
|
1208
1234
|
};
|
|
1209
1235
|
vertex.edges.push(model);
|
|
1210
1236
|
}
|
|
1211
|
-
else if (
|
|
1237
|
+
else if (existing.targetId !== edge.targetId ||
|
|
1238
|
+
!core.ArrayHelper.matches(existing.edgeRelationships, edge.edgeRelationships) ||
|
|
1212
1239
|
!core.ObjectHelper.equal(existing.annotationObject, edge.annotationObject, false)) {
|
|
1213
|
-
// Existing
|
|
1240
|
+
// Existing edge found, update the properties.
|
|
1241
|
+
existing.targetId = edge.targetId;
|
|
1214
1242
|
existing.dateModified = context.now;
|
|
1215
1243
|
existing.edgeRelationships = edge.edgeRelationships;
|
|
1216
1244
|
existing.annotationObject = edge.annotationObject;
|
|
@@ -1238,10 +1266,7 @@ class AuditableItemGraphService {
|
|
|
1238
1266
|
};
|
|
1239
1267
|
// Create the JSON-LD object we want to use for the proof
|
|
1240
1268
|
// this is a subset of fixed properties from the changeset object.
|
|
1241
|
-
const reducedChangesetJsonLd = await this.changesetEntityToJsonLd(
|
|
1242
|
-
...core.ObjectHelper.pick(changesetEntity, AuditableItemGraphService._PROOF_KEYS_CHANGESET),
|
|
1243
|
-
id: `${AuditableItemGraphService._NAMESPACE}:${updated.id}:${AuditableItemGraphService.NAMESPACE_CHANGESET}:${changesetEntity.id}`
|
|
1244
|
-
});
|
|
1269
|
+
const reducedChangesetJsonLd = await this.changesetEntityToJsonLd(original.id, core.ObjectHelper.pick(changesetEntity, AuditableItemGraphService._PROOF_KEYS_CHANGESET));
|
|
1245
1270
|
// Create the proof for the changeset object
|
|
1246
1271
|
changesetEntity.proofId = await this._immutableProofComponent.create(reducedChangesetJsonLd, context.userIdentity, context.nodeIdentity);
|
|
1247
1272
|
// Link the verifiable storage id to the changeset
|
|
@@ -1277,7 +1302,7 @@ class AuditableItemGraphService {
|
|
|
1277
1302
|
if (core.Is.arrayValue(storedChangesets)) {
|
|
1278
1303
|
for (let i = 0; i < storedChangesets.length; i++) {
|
|
1279
1304
|
const storedChangeset = storedChangesets[i];
|
|
1280
|
-
const storedChangesetJsonLd = this.changesetEntityToJsonLd(storedChangeset);
|
|
1305
|
+
const storedChangesetJsonLd = this.changesetEntityToJsonLd(vertexId.namespaceSpecific(), storedChangeset);
|
|
1281
1306
|
changesets.push(storedChangesetJsonLd);
|
|
1282
1307
|
// If we are verifying all signatures
|
|
1283
1308
|
// or this is the last changeset (cursor is empty)
|
|
@@ -1345,10 +1370,72 @@ class AuditableItemGraphService {
|
|
|
1345
1370
|
}
|
|
1346
1371
|
const resourceTypeIndex = resourceTypes.join("||").toLowerCase();
|
|
1347
1372
|
return {
|
|
1348
|
-
aliasIndex: core.Is.stringValue(aliasIndex) ? aliasIndex : undefined,
|
|
1349
|
-
resourceTypeIndex: core.Is.stringValue(resourceTypeIndex) ? resourceTypeIndex : undefined
|
|
1373
|
+
aliasIndex: core.Is.stringValue(aliasIndex) ? `||${aliasIndex}||` : undefined,
|
|
1374
|
+
resourceTypeIndex: core.Is.stringValue(resourceTypeIndex) ? `||${resourceTypeIndex}||` : undefined
|
|
1350
1375
|
};
|
|
1351
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Find vertices with matching aliases.
|
|
1379
|
+
* @param vertexId The id of the vertex to exclude from the search.
|
|
1380
|
+
* @param aliasId The alias id to try and find.
|
|
1381
|
+
* @returns True if any other vertices have matching aliases.
|
|
1382
|
+
* @internal
|
|
1383
|
+
*/
|
|
1384
|
+
async findMatchingVertices(vertexId, aliasId) {
|
|
1385
|
+
const results = await this._vertexStorage.query({
|
|
1386
|
+
conditions: [
|
|
1387
|
+
{
|
|
1388
|
+
property: "aliasIndex",
|
|
1389
|
+
comparison: entity.ComparisonOperator.Includes,
|
|
1390
|
+
value: `||${aliasId.toLowerCase()}||`
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
property: "id",
|
|
1394
|
+
value: vertexId,
|
|
1395
|
+
comparison: entity.ComparisonOperator.NotEquals
|
|
1396
|
+
}
|
|
1397
|
+
],
|
|
1398
|
+
logicalOperator: entity.LogicalOperator.And
|
|
1399
|
+
});
|
|
1400
|
+
return results.entities.length > 0;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Reduce the edge ID from a URN.
|
|
1404
|
+
* @param urn The URN to reduce.
|
|
1405
|
+
* @returns The edge ID.
|
|
1406
|
+
* @throws GeneralError if the URN is not valid or not an edge URN.
|
|
1407
|
+
* @internal
|
|
1408
|
+
*/
|
|
1409
|
+
reduceEdgeId(urn) {
|
|
1410
|
+
const urnParsed = core.Urn.fromValidString(urn);
|
|
1411
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
1412
|
+
throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
1413
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
1414
|
+
id: urn
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
if (urnParsed.namespaceSpecificParts().length !== 3) {
|
|
1418
|
+
throw new core.GeneralError(this.CLASS_NAME, "invalidEdgeId", { id: urn });
|
|
1419
|
+
}
|
|
1420
|
+
if (urnParsed.namespaceSpecificParts()[1] !== AuditableItemGraphService.NAMESPACE_EDGE) {
|
|
1421
|
+
throw new core.GeneralError(this.CLASS_NAME, "invalidEdgeId", { id: urn });
|
|
1422
|
+
}
|
|
1423
|
+
return urnParsed.namespaceSpecificParts()[2];
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Create a full edge ID URN from a vertex ID and edge ID.
|
|
1427
|
+
* @param vertexId The vertex id the edge belongs to.
|
|
1428
|
+
* @param edgeId The edge id.
|
|
1429
|
+
* @returns The full edge ID URN.
|
|
1430
|
+
* @internal
|
|
1431
|
+
*/
|
|
1432
|
+
fullEdgeId(vertexId, edgeId) {
|
|
1433
|
+
return new core.Urn(AuditableItemGraphService.NAMESPACE, [
|
|
1434
|
+
vertexId,
|
|
1435
|
+
AuditableItemGraphService.NAMESPACE_EDGE,
|
|
1436
|
+
edgeId
|
|
1437
|
+
]).toString();
|
|
1438
|
+
}
|
|
1352
1439
|
}
|
|
1353
1440
|
|
|
1354
1441
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -1491,6 +1578,10 @@ exports.AuditableItemGraphEdge = class AuditableItemGraphEdge {
|
|
|
1491
1578
|
* The timestamp of when the edge was deleted, as we never actually remove items.
|
|
1492
1579
|
*/
|
|
1493
1580
|
dateDeleted;
|
|
1581
|
+
/**
|
|
1582
|
+
* The target id of the edge.
|
|
1583
|
+
*/
|
|
1584
|
+
targetId;
|
|
1494
1585
|
/**
|
|
1495
1586
|
* The relationships between the two vertices.
|
|
1496
1587
|
*/
|
|
@@ -1516,6 +1607,10 @@ __decorate([
|
|
|
1516
1607
|
entity.property({ type: "string", format: "date-time", optional: true }),
|
|
1517
1608
|
__metadata("design:type", String)
|
|
1518
1609
|
], exports.AuditableItemGraphEdge.prototype, "dateDeleted", void 0);
|
|
1610
|
+
__decorate([
|
|
1611
|
+
entity.property({ type: "string" }),
|
|
1612
|
+
__metadata("design:type", String)
|
|
1613
|
+
], exports.AuditableItemGraphEdge.prototype, "targetId", void 0);
|
|
1519
1614
|
__decorate([
|
|
1520
1615
|
entity.property({ type: "array" }),
|
|
1521
1616
|
__metadata("design:type", Array)
|
package/dist/esm/index.mjs
CHANGED
|
@@ -87,7 +87,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
87
87
|
],
|
|
88
88
|
edges: [
|
|
89
89
|
{
|
|
90
|
-
|
|
90
|
+
targetId: "aig:1234567890",
|
|
91
91
|
edgeRelationships: ["frenemy"],
|
|
92
92
|
annotationObject: {
|
|
93
93
|
"@context": "https://schema.org",
|
|
@@ -96,7 +96,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
|
-
|
|
99
|
+
targetId: "aig:45678901234",
|
|
100
100
|
edgeRelationships: ["end"],
|
|
101
101
|
annotationObject: {
|
|
102
102
|
"@context": "https://schema.org",
|
|
@@ -295,6 +295,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
295
295
|
edges: [
|
|
296
296
|
{
|
|
297
297
|
id: "edge1",
|
|
298
|
+
targetId: "aig:1234567890",
|
|
298
299
|
edgeRelationships: ["frenemy"],
|
|
299
300
|
annotationObject: {
|
|
300
301
|
"@context": "https://schema.org",
|
|
@@ -304,6 +305,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
|
|
|
304
305
|
},
|
|
305
306
|
{
|
|
306
307
|
id: "edge2",
|
|
308
|
+
targetId: "aig:45678901234",
|
|
307
309
|
edgeRelationships: ["end"],
|
|
308
310
|
annotationObject: {
|
|
309
311
|
"@context": "https://schema.org",
|
|
@@ -519,6 +521,7 @@ async function auditableItemGraphList(httpRequestContext, componentName, request
|
|
|
519
521
|
const result = await component.query({
|
|
520
522
|
id: request.query?.id,
|
|
521
523
|
idMode: request.query?.idMode,
|
|
524
|
+
idExact: Coerce.boolean(request.query?.idExact),
|
|
522
525
|
resourceTypes: HttpParameterHelper.arrayFromString(request.query?.resourceTypes)
|
|
523
526
|
}, HttpParameterHelper.objectFromString(request.query?.conditions), request.query?.orderBy, request.query?.orderByDirection, HttpParameterHelper.arrayFromString(request.query?.properties), request.query?.cursor, Coerce.integer(request.query?.pageSize));
|
|
524
527
|
return {
|
|
@@ -535,15 +538,19 @@ async function auditableItemGraphList(httpRequestContext, componentName, request
|
|
|
535
538
|
* Class for performing auditable item graph operations.
|
|
536
539
|
*/
|
|
537
540
|
class AuditableItemGraphService {
|
|
541
|
+
/**
|
|
542
|
+
* The namespace for the service.
|
|
543
|
+
* @internal
|
|
544
|
+
*/
|
|
545
|
+
static NAMESPACE = "aig";
|
|
538
546
|
/**
|
|
539
547
|
* The namespace for the service changeset.
|
|
540
548
|
*/
|
|
541
549
|
static NAMESPACE_CHANGESET = "changeset";
|
|
542
550
|
/**
|
|
543
|
-
* The namespace for the service.
|
|
544
|
-
* @internal
|
|
551
|
+
* The namespace for the service edge.
|
|
545
552
|
*/
|
|
546
|
-
static
|
|
553
|
+
static NAMESPACE_EDGE = "edge";
|
|
547
554
|
/**
|
|
548
555
|
* The keys to pick when creating the proof for the stream.
|
|
549
556
|
* @internal
|
|
@@ -637,7 +644,7 @@ class AuditableItemGraphService {
|
|
|
637
644
|
...vertexModel,
|
|
638
645
|
...this.buildIndexes(vertexModel)
|
|
639
646
|
});
|
|
640
|
-
const fullId = new Urn(AuditableItemGraphService.
|
|
647
|
+
const fullId = new Urn(AuditableItemGraphService.NAMESPACE, id).toString();
|
|
641
648
|
await this._eventBusComponent?.publish(AuditableItemGraphTopics.VertexCreated, { id: fullId });
|
|
642
649
|
return fullId;
|
|
643
650
|
}
|
|
@@ -658,9 +665,9 @@ class AuditableItemGraphService {
|
|
|
658
665
|
async get(id, options) {
|
|
659
666
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
660
667
|
const urnParsed = Urn.fromValidString(id);
|
|
661
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
668
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
662
669
|
throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
663
|
-
namespace: AuditableItemGraphService.
|
|
670
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
664
671
|
id
|
|
665
672
|
});
|
|
666
673
|
}
|
|
@@ -733,9 +740,9 @@ class AuditableItemGraphService {
|
|
|
733
740
|
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
734
741
|
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
735
742
|
const urnParsed = Urn.fromValidString(vertex.id);
|
|
736
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
743
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
737
744
|
throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
738
|
-
namespace: AuditableItemGraphService.
|
|
745
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
739
746
|
id: vertex.id
|
|
740
747
|
});
|
|
741
748
|
}
|
|
@@ -788,9 +795,9 @@ class AuditableItemGraphService {
|
|
|
788
795
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
789
796
|
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
790
797
|
const urnParsed = Urn.fromValidString(id);
|
|
791
|
-
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.
|
|
798
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
792
799
|
throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
793
|
-
namespace: AuditableItemGraphService.
|
|
800
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
794
801
|
id
|
|
795
802
|
});
|
|
796
803
|
}
|
|
@@ -830,6 +837,7 @@ class AuditableItemGraphService {
|
|
|
830
837
|
* @param options The query options.
|
|
831
838
|
* @param options.id The optional id to look for.
|
|
832
839
|
* @param options.idMode Look in id, alias or both, defaults to both.
|
|
840
|
+
* @param options.idExact Find only exact matches, default to false meaning partial matching.
|
|
833
841
|
* @param options.includesResourceTypes Include vertices with specific resource types.
|
|
834
842
|
* @param conditions Conditions to use in the query.
|
|
835
843
|
* @param orderBy The order for the results, defaults to created.
|
|
@@ -851,13 +859,14 @@ class AuditableItemGraphService {
|
|
|
851
859
|
const combinedConditions = conditions ?? [];
|
|
852
860
|
const orderProperty = orderBy ?? "dateCreated";
|
|
853
861
|
const orderDirection = orderByDirection ?? SortDirection.Descending;
|
|
862
|
+
const idExact = options?.idExact ?? false;
|
|
854
863
|
const idOrAlias = options?.id;
|
|
855
864
|
if (Is.stringValue(idOrAlias)) {
|
|
856
865
|
const idMode = options?.idMode ?? "both";
|
|
857
866
|
if (idMode === "id" || idMode === "both") {
|
|
858
867
|
combinedConditions.push({
|
|
859
868
|
property: "id",
|
|
860
|
-
comparison: ComparisonOperator.Includes,
|
|
869
|
+
comparison: idExact ? ComparisonOperator.Equals : ComparisonOperator.Includes,
|
|
861
870
|
value: idOrAlias
|
|
862
871
|
});
|
|
863
872
|
}
|
|
@@ -865,7 +874,7 @@ class AuditableItemGraphService {
|
|
|
865
874
|
combinedConditions.push({
|
|
866
875
|
property: "aliasIndex",
|
|
867
876
|
comparison: ComparisonOperator.Includes,
|
|
868
|
-
value: idOrAlias.toLowerCase()
|
|
877
|
+
value: idExact ? `||${idOrAlias.toLowerCase()}||` : idOrAlias.toLowerCase()
|
|
869
878
|
});
|
|
870
879
|
}
|
|
871
880
|
}
|
|
@@ -874,7 +883,7 @@ class AuditableItemGraphService {
|
|
|
874
883
|
combinedConditions.push({
|
|
875
884
|
property: "resourceTypeIndex",
|
|
876
885
|
comparison: ComparisonOperator.Includes,
|
|
877
|
-
value: resourceType.toLowerCase()
|
|
886
|
+
value: `||${resourceType.toLowerCase()}||`
|
|
878
887
|
});
|
|
879
888
|
}
|
|
880
889
|
}
|
|
@@ -923,7 +932,7 @@ class AuditableItemGraphService {
|
|
|
923
932
|
SchemaOrgContexts.ContextRoot
|
|
924
933
|
],
|
|
925
934
|
type: AuditableItemGraphTypes.Vertex,
|
|
926
|
-
id: new Urn(AuditableItemGraphService.
|
|
935
|
+
id: new Urn(AuditableItemGraphService.NAMESPACE, vertexEntity.id).toString(),
|
|
927
936
|
dateCreated: vertexEntity.dateCreated,
|
|
928
937
|
dateModified: vertexEntity.dateModified,
|
|
929
938
|
nodeIdentity: vertexEntity.nodeIdentity,
|
|
@@ -978,7 +987,8 @@ class AuditableItemGraphService {
|
|
|
978
987
|
SchemaOrgContexts.ContextRoot
|
|
979
988
|
],
|
|
980
989
|
type: AuditableItemGraphTypes.Edge,
|
|
981
|
-
id: edgeEntity.id,
|
|
990
|
+
id: this.fullEdgeId(vertexEntity.id, edgeEntity.id),
|
|
991
|
+
targetId: edgeEntity.targetId,
|
|
982
992
|
dateCreated: edgeEntity.dateCreated,
|
|
983
993
|
dateModified: edgeEntity.dateModified,
|
|
984
994
|
dateDeleted: edgeEntity.dateDeleted,
|
|
@@ -992,11 +1002,12 @@ class AuditableItemGraphService {
|
|
|
992
1002
|
}
|
|
993
1003
|
/**
|
|
994
1004
|
* Map the changeset entity to a JSON-LD.
|
|
1005
|
+
* @param vertexId The id of the vertex the changeset belongs to.
|
|
995
1006
|
* @param changesetEntity The changeset entity.
|
|
996
1007
|
* @returns The model.
|
|
997
1008
|
* @internal
|
|
998
1009
|
*/
|
|
999
|
-
changesetEntityToJsonLd(changesetEntity) {
|
|
1010
|
+
changesetEntityToJsonLd(vertexId, changesetEntity) {
|
|
1000
1011
|
const model = {
|
|
1001
1012
|
"@context": [
|
|
1002
1013
|
AuditableItemGraphContexts.ContextRoot,
|
|
@@ -1004,7 +1015,11 @@ class AuditableItemGraphService {
|
|
|
1004
1015
|
SchemaOrgContexts.ContextRoot
|
|
1005
1016
|
],
|
|
1006
1017
|
type: AuditableItemGraphTypes.Changeset,
|
|
1007
|
-
id:
|
|
1018
|
+
id: new Urn(AuditableItemGraphService.NAMESPACE, [
|
|
1019
|
+
vertexId,
|
|
1020
|
+
AuditableItemGraphService.NAMESPACE_CHANGESET,
|
|
1021
|
+
changesetEntity.id
|
|
1022
|
+
]).toString(),
|
|
1008
1023
|
dateCreated: changesetEntity.dateCreated,
|
|
1009
1024
|
userIdentity: changesetEntity.userIdentity,
|
|
1010
1025
|
patches: changesetEntity.patches.map(p => ({
|
|
@@ -1056,6 +1071,12 @@ class AuditableItemGraphService {
|
|
|
1056
1071
|
async updateAlias(context, vertex, alias) {
|
|
1057
1072
|
Guards.object(this.CLASS_NAME, "alias", alias);
|
|
1058
1073
|
Guards.stringValue(this.CLASS_NAME, "alias.id", alias.id);
|
|
1074
|
+
if (alias.unique ?? false) {
|
|
1075
|
+
const existingVertices = await this.findMatchingVertices(vertex.id, alias.id);
|
|
1076
|
+
if (existingVertices) {
|
|
1077
|
+
throw new GeneralError(this.CLASS_NAME, "aliasNotUnique", { aliasId: alias.id });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1059
1080
|
if (Is.object(alias.annotationObject)) {
|
|
1060
1081
|
const validationFailures = [];
|
|
1061
1082
|
await JsonLdHelper.validate(alias.annotationObject, validationFailures);
|
|
@@ -1157,7 +1178,7 @@ class AuditableItemGraphService {
|
|
|
1157
1178
|
// The active edges that are not in the update list should be marked as deleted.
|
|
1158
1179
|
if (Is.arrayValue(active)) {
|
|
1159
1180
|
for (const edge of active) {
|
|
1160
|
-
if (!edges?.find(
|
|
1181
|
+
if (!edges?.find(e => Is.stringValue(e.id) && this.reduceEdgeId(e.id) === edge.id)) {
|
|
1161
1182
|
edge.dateDeleted = context.now;
|
|
1162
1183
|
}
|
|
1163
1184
|
}
|
|
@@ -1177,15 +1198,15 @@ class AuditableItemGraphService {
|
|
|
1177
1198
|
*/
|
|
1178
1199
|
async updateEdge(context, vertex, edge) {
|
|
1179
1200
|
Guards.object(this.CLASS_NAME, "edge", edge);
|
|
1180
|
-
Guards.stringValue(this.CLASS_NAME, "edge.
|
|
1201
|
+
Guards.stringValue(this.CLASS_NAME, "edge.targetId", edge.targetId);
|
|
1181
1202
|
Guards.arrayValue(this.CLASS_NAME, "edge.edgeRelationships", edge.edgeRelationships);
|
|
1182
1203
|
const validationFailures = [];
|
|
1183
|
-
if (edge.
|
|
1204
|
+
if (edge.targetId === vertex.id) {
|
|
1184
1205
|
validationFailures.push({
|
|
1185
1206
|
property: "id",
|
|
1186
1207
|
reason: `validation.${StringHelper.camelCase(this.CLASS_NAME)}.edgeIdSameAsVertexId`,
|
|
1187
1208
|
properties: {
|
|
1188
|
-
|
|
1209
|
+
targetId: edge.targetId
|
|
1189
1210
|
}
|
|
1190
1211
|
});
|
|
1191
1212
|
}
|
|
@@ -1193,22 +1214,29 @@ class AuditableItemGraphService {
|
|
|
1193
1214
|
await JsonLdHelper.validate(edge.annotationObject, validationFailures);
|
|
1194
1215
|
}
|
|
1195
1216
|
Validation.asValidationError(this.CLASS_NAME, "edge.annotationObject", validationFailures);
|
|
1217
|
+
let findId = Is.stringValue(edge.id) ? this.reduceEdgeId(edge.id) : undefined;
|
|
1218
|
+
if (Is.empty(findId)) {
|
|
1219
|
+
findId = Converter.bytesToHex(RandomHelper.generate(32), false);
|
|
1220
|
+
}
|
|
1196
1221
|
// Try to find an existing edge with the same id.
|
|
1197
|
-
const existing = vertex.edges?.find(r => r.id ===
|
|
1222
|
+
const existing = vertex.edges?.find(r => r.id === findId);
|
|
1198
1223
|
if (Is.empty(existing) || !Is.empty(existing?.dateDeleted)) {
|
|
1199
1224
|
// Did not find a matching item, or found one which is deleted.
|
|
1200
1225
|
vertex.edges ??= [];
|
|
1201
1226
|
const model = {
|
|
1202
|
-
id:
|
|
1227
|
+
id: findId,
|
|
1228
|
+
targetId: edge.targetId,
|
|
1203
1229
|
dateCreated: context.now,
|
|
1204
1230
|
annotationObject: edge.annotationObject,
|
|
1205
1231
|
edgeRelationships: edge.edgeRelationships
|
|
1206
1232
|
};
|
|
1207
1233
|
vertex.edges.push(model);
|
|
1208
1234
|
}
|
|
1209
|
-
else if (
|
|
1235
|
+
else if (existing.targetId !== edge.targetId ||
|
|
1236
|
+
!ArrayHelper.matches(existing.edgeRelationships, edge.edgeRelationships) ||
|
|
1210
1237
|
!ObjectHelper.equal(existing.annotationObject, edge.annotationObject, false)) {
|
|
1211
|
-
// Existing
|
|
1238
|
+
// Existing edge found, update the properties.
|
|
1239
|
+
existing.targetId = edge.targetId;
|
|
1212
1240
|
existing.dateModified = context.now;
|
|
1213
1241
|
existing.edgeRelationships = edge.edgeRelationships;
|
|
1214
1242
|
existing.annotationObject = edge.annotationObject;
|
|
@@ -1236,10 +1264,7 @@ class AuditableItemGraphService {
|
|
|
1236
1264
|
};
|
|
1237
1265
|
// Create the JSON-LD object we want to use for the proof
|
|
1238
1266
|
// this is a subset of fixed properties from the changeset object.
|
|
1239
|
-
const reducedChangesetJsonLd = await this.changesetEntityToJsonLd(
|
|
1240
|
-
...ObjectHelper.pick(changesetEntity, AuditableItemGraphService._PROOF_KEYS_CHANGESET),
|
|
1241
|
-
id: `${AuditableItemGraphService._NAMESPACE}:${updated.id}:${AuditableItemGraphService.NAMESPACE_CHANGESET}:${changesetEntity.id}`
|
|
1242
|
-
});
|
|
1267
|
+
const reducedChangesetJsonLd = await this.changesetEntityToJsonLd(original.id, ObjectHelper.pick(changesetEntity, AuditableItemGraphService._PROOF_KEYS_CHANGESET));
|
|
1243
1268
|
// Create the proof for the changeset object
|
|
1244
1269
|
changesetEntity.proofId = await this._immutableProofComponent.create(reducedChangesetJsonLd, context.userIdentity, context.nodeIdentity);
|
|
1245
1270
|
// Link the verifiable storage id to the changeset
|
|
@@ -1275,7 +1300,7 @@ class AuditableItemGraphService {
|
|
|
1275
1300
|
if (Is.arrayValue(storedChangesets)) {
|
|
1276
1301
|
for (let i = 0; i < storedChangesets.length; i++) {
|
|
1277
1302
|
const storedChangeset = storedChangesets[i];
|
|
1278
|
-
const storedChangesetJsonLd = this.changesetEntityToJsonLd(storedChangeset);
|
|
1303
|
+
const storedChangesetJsonLd = this.changesetEntityToJsonLd(vertexId.namespaceSpecific(), storedChangeset);
|
|
1279
1304
|
changesets.push(storedChangesetJsonLd);
|
|
1280
1305
|
// If we are verifying all signatures
|
|
1281
1306
|
// or this is the last changeset (cursor is empty)
|
|
@@ -1343,10 +1368,72 @@ class AuditableItemGraphService {
|
|
|
1343
1368
|
}
|
|
1344
1369
|
const resourceTypeIndex = resourceTypes.join("||").toLowerCase();
|
|
1345
1370
|
return {
|
|
1346
|
-
aliasIndex: Is.stringValue(aliasIndex) ? aliasIndex : undefined,
|
|
1347
|
-
resourceTypeIndex: Is.stringValue(resourceTypeIndex) ? resourceTypeIndex : undefined
|
|
1371
|
+
aliasIndex: Is.stringValue(aliasIndex) ? `||${aliasIndex}||` : undefined,
|
|
1372
|
+
resourceTypeIndex: Is.stringValue(resourceTypeIndex) ? `||${resourceTypeIndex}||` : undefined
|
|
1348
1373
|
};
|
|
1349
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Find vertices with matching aliases.
|
|
1377
|
+
* @param vertexId The id of the vertex to exclude from the search.
|
|
1378
|
+
* @param aliasId The alias id to try and find.
|
|
1379
|
+
* @returns True if any other vertices have matching aliases.
|
|
1380
|
+
* @internal
|
|
1381
|
+
*/
|
|
1382
|
+
async findMatchingVertices(vertexId, aliasId) {
|
|
1383
|
+
const results = await this._vertexStorage.query({
|
|
1384
|
+
conditions: [
|
|
1385
|
+
{
|
|
1386
|
+
property: "aliasIndex",
|
|
1387
|
+
comparison: ComparisonOperator.Includes,
|
|
1388
|
+
value: `||${aliasId.toLowerCase()}||`
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
property: "id",
|
|
1392
|
+
value: vertexId,
|
|
1393
|
+
comparison: ComparisonOperator.NotEquals
|
|
1394
|
+
}
|
|
1395
|
+
],
|
|
1396
|
+
logicalOperator: LogicalOperator.And
|
|
1397
|
+
});
|
|
1398
|
+
return results.entities.length > 0;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Reduce the edge ID from a URN.
|
|
1402
|
+
* @param urn The URN to reduce.
|
|
1403
|
+
* @returns The edge ID.
|
|
1404
|
+
* @throws GeneralError if the URN is not valid or not an edge URN.
|
|
1405
|
+
* @internal
|
|
1406
|
+
*/
|
|
1407
|
+
reduceEdgeId(urn) {
|
|
1408
|
+
const urnParsed = Urn.fromValidString(urn);
|
|
1409
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
1410
|
+
throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
|
|
1411
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
1412
|
+
id: urn
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
if (urnParsed.namespaceSpecificParts().length !== 3) {
|
|
1416
|
+
throw new GeneralError(this.CLASS_NAME, "invalidEdgeId", { id: urn });
|
|
1417
|
+
}
|
|
1418
|
+
if (urnParsed.namespaceSpecificParts()[1] !== AuditableItemGraphService.NAMESPACE_EDGE) {
|
|
1419
|
+
throw new GeneralError(this.CLASS_NAME, "invalidEdgeId", { id: urn });
|
|
1420
|
+
}
|
|
1421
|
+
return urnParsed.namespaceSpecificParts()[2];
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Create a full edge ID URN from a vertex ID and edge ID.
|
|
1425
|
+
* @param vertexId The vertex id the edge belongs to.
|
|
1426
|
+
* @param edgeId The edge id.
|
|
1427
|
+
* @returns The full edge ID URN.
|
|
1428
|
+
* @internal
|
|
1429
|
+
*/
|
|
1430
|
+
fullEdgeId(vertexId, edgeId) {
|
|
1431
|
+
return new Urn(AuditableItemGraphService.NAMESPACE, [
|
|
1432
|
+
vertexId,
|
|
1433
|
+
AuditableItemGraphService.NAMESPACE_EDGE,
|
|
1434
|
+
edgeId
|
|
1435
|
+
]).toString();
|
|
1436
|
+
}
|
|
1350
1437
|
}
|
|
1351
1438
|
|
|
1352
1439
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -1489,6 +1576,10 @@ let AuditableItemGraphEdge = class AuditableItemGraphEdge {
|
|
|
1489
1576
|
* The timestamp of when the edge was deleted, as we never actually remove items.
|
|
1490
1577
|
*/
|
|
1491
1578
|
dateDeleted;
|
|
1579
|
+
/**
|
|
1580
|
+
* The target id of the edge.
|
|
1581
|
+
*/
|
|
1582
|
+
targetId;
|
|
1492
1583
|
/**
|
|
1493
1584
|
* The relationships between the two vertices.
|
|
1494
1585
|
*/
|
|
@@ -1514,6 +1605,10 @@ __decorate([
|
|
|
1514
1605
|
property({ type: "string", format: "date-time", optional: true }),
|
|
1515
1606
|
__metadata("design:type", String)
|
|
1516
1607
|
], AuditableItemGraphEdge.prototype, "dateDeleted", void 0);
|
|
1608
|
+
__decorate([
|
|
1609
|
+
property({ type: "string" }),
|
|
1610
|
+
__metadata("design:type", String)
|
|
1611
|
+
], AuditableItemGraphEdge.prototype, "targetId", void 0);
|
|
1517
1612
|
__decorate([
|
|
1518
1613
|
property({ type: "array" }),
|
|
1519
1614
|
__metadata("design:type", Array)
|
|
@@ -10,6 +10,10 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
10
10
|
* The namespace for the service changeset.
|
|
11
11
|
*/
|
|
12
12
|
static readonly NAMESPACE_CHANGESET: string;
|
|
13
|
+
/**
|
|
14
|
+
* The namespace for the service edge.
|
|
15
|
+
*/
|
|
16
|
+
static readonly NAMESPACE_EDGE: string;
|
|
13
17
|
/**
|
|
14
18
|
* Runtime name for the class.
|
|
15
19
|
*/
|
|
@@ -35,6 +39,7 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
35
39
|
aliases?: {
|
|
36
40
|
id: string;
|
|
37
41
|
aliasFormat?: string;
|
|
42
|
+
unique?: boolean;
|
|
38
43
|
annotationObject?: IJsonLdNodeObject;
|
|
39
44
|
}[];
|
|
40
45
|
resources?: {
|
|
@@ -42,7 +47,7 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
42
47
|
resourceObject?: IJsonLdNodeObject;
|
|
43
48
|
}[];
|
|
44
49
|
edges?: {
|
|
45
|
-
|
|
50
|
+
targetId: string;
|
|
46
51
|
edgeRelationships: string[];
|
|
47
52
|
annotationObject?: IJsonLdNodeObject;
|
|
48
53
|
}[];
|
|
@@ -88,6 +93,7 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
88
93
|
}[];
|
|
89
94
|
edges?: {
|
|
90
95
|
id: string;
|
|
96
|
+
targetId: string;
|
|
91
97
|
edgeRelationships: string[];
|
|
92
98
|
annotationObject?: IJsonLdNodeObject;
|
|
93
99
|
}[];
|
|
@@ -105,6 +111,7 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
105
111
|
* @param options The query options.
|
|
106
112
|
* @param options.id The optional id to look for.
|
|
107
113
|
* @param options.idMode Look in id, alias or both, defaults to both.
|
|
114
|
+
* @param options.idExact Find only exact matches, default to false meaning partial matching.
|
|
108
115
|
* @param options.includesResourceTypes Include vertices with specific resource types.
|
|
109
116
|
* @param conditions Conditions to use in the query.
|
|
110
117
|
* @param orderBy The order for the results, defaults to created.
|
|
@@ -117,6 +124,7 @@ export declare class AuditableItemGraphService implements IAuditableItemGraphCom
|
|
|
117
124
|
query(options?: {
|
|
118
125
|
id?: string;
|
|
119
126
|
idMode?: "id" | "alias" | "both";
|
|
127
|
+
idExact?: boolean;
|
|
120
128
|
includesResourceTypes?: string[];
|
|
121
129
|
}, conditions?: IComparator[], orderBy?: keyof Pick<IAuditableItemGraphVertex, "dateCreated" | "dateModified">, orderByDirection?: SortDirection, properties?: (keyof IAuditableItemGraphVertex)[], cursor?: string, pageSize?: number): Promise<IAuditableItemGraphVertexList>;
|
|
122
130
|
/**
|
|
@@ -19,6 +19,10 @@ export declare class AuditableItemGraphEdge {
|
|
|
19
19
|
* The timestamp of when the edge was deleted, as we never actually remove items.
|
|
20
20
|
*/
|
|
21
21
|
dateDeleted?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The target id of the edge.
|
|
24
|
+
*/
|
|
25
|
+
targetId: string;
|
|
22
26
|
/**
|
|
23
27
|
* The relationships between the two vertices.
|
|
24
28
|
*/
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @twin.org/auditable-item-graph-service - Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.2-next.5](https://github.com/twinfoundation/auditable-item-graph/compare/auditable-item-graph-service-v0.0.2-next.4...auditable-item-graph-service-v0.0.2-next.5) (2025-09-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* update edges to use targetId instead of id ([6c5d0e3](https://github.com/twinfoundation/auditable-item-graph/commit/6c5d0e31b6e2ea74bfa2f3344e4be628e5f237af))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/auditable-item-graph-models bumped from 0.0.2-next.4 to 0.0.2-next.5
|
|
16
|
+
|
|
17
|
+
## [0.0.2-next.4](https://github.com/twinfoundation/auditable-item-graph/compare/auditable-item-graph-service-v0.0.2-next.3...auditable-item-graph-service-v0.0.2-next.4) (2025-09-24)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add unique flag for alias, and exact match option for id query ([33dbd19](https://github.com/twinfoundation/auditable-item-graph/commit/33dbd19cabd9fbfaba81032f1d1a6527584c3f5a))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
* The following workspace dependencies were updated
|
|
28
|
+
* dependencies
|
|
29
|
+
* @twin.org/auditable-item-graph-models bumped from 0.0.2-next.3 to 0.0.2-next.4
|
|
30
|
+
|
|
3
31
|
## [0.0.2-next.3](https://github.com/twinfoundation/auditable-item-graph/compare/auditable-item-graph-service-v0.0.2-next.2...auditable-item-graph-service-v0.0.2-next.3) (2025-08-29)
|
|
4
32
|
|
|
5
33
|
|
package/docs/open-api/spec.json
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
],
|
|
88
88
|
"edges": [
|
|
89
89
|
{
|
|
90
|
-
"
|
|
90
|
+
"targetId": "aig:1234567890",
|
|
91
91
|
"edgeRelationships": [
|
|
92
92
|
"frenemy"
|
|
93
93
|
],
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
|
-
"
|
|
101
|
+
"targetId": "aig:45678901234",
|
|
102
102
|
"edgeRelationships": [
|
|
103
103
|
"end"
|
|
104
104
|
],
|
|
@@ -216,6 +216,15 @@
|
|
|
216
216
|
]
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
|
+
{
|
|
220
|
+
"name": "idExact",
|
|
221
|
+
"description": "Find only exact matches, default to false meaning partial matching.",
|
|
222
|
+
"in": "query",
|
|
223
|
+
"required": false,
|
|
224
|
+
"schema": {
|
|
225
|
+
"type": "boolean"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
219
228
|
{
|
|
220
229
|
"name": "resourceTypes",
|
|
221
230
|
"description": "Include vertices with specific resource types, comma separated.",
|
|
@@ -759,6 +768,7 @@
|
|
|
759
768
|
"edges": [
|
|
760
769
|
{
|
|
761
770
|
"id": "edge1",
|
|
771
|
+
"targetId": "aig:1234567890",
|
|
762
772
|
"edgeRelationships": [
|
|
763
773
|
"frenemy"
|
|
764
774
|
],
|
|
@@ -770,6 +780,7 @@
|
|
|
770
780
|
},
|
|
771
781
|
{
|
|
772
782
|
"id": "edge2",
|
|
783
|
+
"targetId": "aig:45678901234",
|
|
773
784
|
"edgeRelationships": [
|
|
774
785
|
"end"
|
|
775
786
|
],
|
|
@@ -873,6 +884,9 @@
|
|
|
873
884
|
"aliasFormat": {
|
|
874
885
|
"type": "string"
|
|
875
886
|
},
|
|
887
|
+
"unique": {
|
|
888
|
+
"type": "boolean"
|
|
889
|
+
},
|
|
876
890
|
"annotationObject": {
|
|
877
891
|
"$ref": "https://schema.twindev.org/json-ld/JsonLdNodeObject"
|
|
878
892
|
}
|
|
@@ -911,7 +925,7 @@
|
|
|
911
925
|
{
|
|
912
926
|
"type": "object",
|
|
913
927
|
"properties": {
|
|
914
|
-
"
|
|
928
|
+
"targetId": {
|
|
915
929
|
"type": "string"
|
|
916
930
|
},
|
|
917
931
|
"edgeRelationships": {
|
|
@@ -925,7 +939,7 @@
|
|
|
925
939
|
}
|
|
926
940
|
},
|
|
927
941
|
"required": [
|
|
928
|
-
"
|
|
942
|
+
"targetId",
|
|
929
943
|
"edgeRelationships"
|
|
930
944
|
],
|
|
931
945
|
"additionalProperties": false
|
|
@@ -956,6 +970,9 @@
|
|
|
956
970
|
"aliasFormat": {
|
|
957
971
|
"type": "string"
|
|
958
972
|
},
|
|
973
|
+
"unique": {
|
|
974
|
+
"type": "boolean"
|
|
975
|
+
},
|
|
959
976
|
"annotationObject": {
|
|
960
977
|
"$ref": "https://schema.twindev.org/json-ld/JsonLdNodeObject"
|
|
961
978
|
}
|
|
@@ -997,6 +1014,9 @@
|
|
|
997
1014
|
"id": {
|
|
998
1015
|
"type": "string"
|
|
999
1016
|
},
|
|
1017
|
+
"targetId": {
|
|
1018
|
+
"type": "string"
|
|
1019
|
+
},
|
|
1000
1020
|
"edgeRelationships": {
|
|
1001
1021
|
"type": "array",
|
|
1002
1022
|
"items": {
|
|
@@ -1009,6 +1029,7 @@
|
|
|
1009
1029
|
},
|
|
1010
1030
|
"required": [
|
|
1011
1031
|
"id",
|
|
1032
|
+
"targetId",
|
|
1012
1033
|
"edgeRelationships"
|
|
1013
1034
|
],
|
|
1014
1035
|
"additionalProperties": false
|
|
@@ -46,6 +46,14 @@ The timestamp of when the edge was deleted, as we never actually remove items.
|
|
|
46
46
|
|
|
47
47
|
***
|
|
48
48
|
|
|
49
|
+
### targetId
|
|
50
|
+
|
|
51
|
+
> **targetId**: `string`
|
|
52
|
+
|
|
53
|
+
The target id of the edge.
|
|
54
|
+
|
|
55
|
+
***
|
|
56
|
+
|
|
49
57
|
### edgeRelationships
|
|
50
58
|
|
|
51
59
|
> **edgeRelationships**: `string`[]
|
|
@@ -36,6 +36,14 @@ The namespace for the service changeset.
|
|
|
36
36
|
|
|
37
37
|
***
|
|
38
38
|
|
|
39
|
+
### NAMESPACE\_EDGE
|
|
40
|
+
|
|
41
|
+
> `readonly` `static` **NAMESPACE\_EDGE**: `string` = `"edge"`
|
|
42
|
+
|
|
43
|
+
The namespace for the service edge.
|
|
44
|
+
|
|
45
|
+
***
|
|
46
|
+
|
|
39
47
|
### CLASS\_NAME
|
|
40
48
|
|
|
41
49
|
> `readonly` **CLASS\_NAME**: `string`
|
|
@@ -286,6 +294,12 @@ The optional id to look for.
|
|
|
286
294
|
|
|
287
295
|
Look in id, alias or both, defaults to both.
|
|
288
296
|
|
|
297
|
+
###### idExact?
|
|
298
|
+
|
|
299
|
+
`boolean`
|
|
300
|
+
|
|
301
|
+
Find only exact matches, default to false meaning partial matching.
|
|
302
|
+
|
|
289
303
|
###### includesResourceTypes?
|
|
290
304
|
|
|
291
305
|
`string`[]
|
package/locales/en.json
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
"queryingFailed": "Querying the Auditable Item Graph failed",
|
|
9
9
|
"removeVerifiableFailed": "Removing the verifiable data the Auditable Item Graph vertex failed",
|
|
10
10
|
"vertexNotFound": "The vertex with the Id \"{notFoundId}\" was not found",
|
|
11
|
-
"resourceIdMissing": "You must provide either the id, or the resourceObject must contain an id property for index \"{index}\""
|
|
11
|
+
"resourceIdMissing": "You must provide either the id, or the resourceObject must contain an id property for index \"{index}\"",
|
|
12
|
+
"aliasNotUnique": "The alias id \"{aliasId}\" is already in use on another vertex and the unique flag was set",
|
|
13
|
+
"invalidEdgeId": "The edge id \"{edgeId}\" is not valid"
|
|
12
14
|
},
|
|
13
15
|
"validation": {
|
|
14
16
|
"auditableItemGraphService": {
|
|
15
|
-
"edgeIdSameAsVertexId": "The edge id \"{
|
|
17
|
+
"edgeIdSameAsVertexId": "The edge with target id \"{targetId}\" can not point to the vertex it belongs to"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/auditable-item-graph-service",
|
|
3
|
-
"version": "0.0.2-next.
|
|
3
|
+
"version": "0.0.2-next.5",
|
|
4
4
|
"description": "Auditable Item Graph contract implementation and REST endpoint definitions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@twin.org/api-models": "next",
|
|
18
|
-
"@twin.org/auditable-item-graph-models": "0.0.2-next.
|
|
18
|
+
"@twin.org/auditable-item-graph-models": "0.0.2-next.5",
|
|
19
19
|
"@twin.org/core": "next",
|
|
20
20
|
"@twin.org/crypto": "next",
|
|
21
21
|
"@twin.org/data-json-ld": "next",
|