@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.
@@ -89,7 +89,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
89
89
  ],
90
90
  edges: [
91
91
  {
92
- id: "edge1",
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
- id: "edge2",
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 _NAMESPACE = "aig";
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._NAMESPACE, id).toString();
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._NAMESPACE) {
670
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
664
671
  throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
665
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE) {
745
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
739
746
  throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
740
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE) {
800
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
794
801
  throw new core.GeneralError(this.CLASS_NAME, "namespaceMismatch", {
795
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE, vertexEntity.id).toString(),
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: changesetEntity.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(a => a.id === edge.id)) {
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.id", edge.id);
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.id === vertex.id) {
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
- id: edge.id
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 === edge.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: edge.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 (!core.ArrayHelper.matches(existing.edgeRelationships, edge.edgeRelationships) ||
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 resource found, update the annotationObject.
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)
@@ -87,7 +87,7 @@ function generateRestRoutesAuditableItemGraph(baseRouteName, componentName) {
87
87
  ],
88
88
  edges: [
89
89
  {
90
- id: "edge1",
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
- id: "edge2",
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 _NAMESPACE = "aig";
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._NAMESPACE, id).toString();
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._NAMESPACE) {
668
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
662
669
  throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
663
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE) {
743
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
737
744
  throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
738
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE) {
798
+ if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
792
799
  throw new GeneralError(this.CLASS_NAME, "namespaceMismatch", {
793
- namespace: AuditableItemGraphService._NAMESPACE,
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._NAMESPACE, vertexEntity.id).toString(),
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: changesetEntity.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(a => a.id === edge.id)) {
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.id", edge.id);
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.id === vertex.id) {
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
- id: edge.id
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 === edge.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: edge.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 (!ArrayHelper.matches(existing.edgeRelationships, edge.edgeRelationships) ||
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 resource found, update the annotationObject.
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
- id: string;
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
 
@@ -87,7 +87,7 @@
87
87
  ],
88
88
  "edges": [
89
89
  {
90
- "id": "edge1",
90
+ "targetId": "aig:1234567890",
91
91
  "edgeRelationships": [
92
92
  "frenemy"
93
93
  ],
@@ -98,7 +98,7 @@
98
98
  }
99
99
  },
100
100
  {
101
- "id": "edge2",
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
- "id": {
928
+ "targetId": {
915
929
  "type": "string"
916
930
  },
917
931
  "edgeRelationships": {
@@ -925,7 +939,7 @@
925
939
  }
926
940
  },
927
941
  "required": [
928
- "id",
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 \"{id}\"can not point to the vertex it belongs to"
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",
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.3",
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",