@oml/language 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) 2026 Modelware. All rights reserved.
2
2
  import { URI } from 'langium';
3
3
  import { RequestType } from 'vscode-languageserver-protocol';
4
- import { isAnnotation, isConceptInstance, isDescription, isImport, isOntology, isPropertyValueAssertion, isRelationInstance, isRule, isTypeAssertion, } from './generated/ast.js';
4
+ import { isAnnotation, isConceptInstance, isDescription, isDescriptionBox, isImport, isOntology, isPropertyValueAssertion, isRelationInstance, isRule, isTypeAssertion, isVocabulary, isVocabularyBundle, } from './generated/ast.js';
5
5
  import { getOntologyModelIndex } from './oml-index.js';
6
6
  import { serializeOntology } from './oml-serializer.js';
7
7
  import { collectOntologyMembers, findOntologyMemberByName, getIriForNode, normalizeNamespace, } from './oml-utils.js';
@@ -85,8 +85,7 @@ async function executeOperation(shared, context, operation, contexts) {
85
85
  ownedPropertyValues: [],
86
86
  ownedTypes: []
87
87
  };
88
- statement.$container = context.ontology;
89
- context.ontology.ownedStatements.push(statement);
88
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
90
89
  }
91
90
  return new Set([context.ontologyIri]);
92
91
  case 'createRelationInstance':
@@ -102,15 +101,14 @@ async function executeOperation(shared, context, operation, contexts) {
102
101
  sources: [],
103
102
  targets: []
104
103
  };
105
- statement.$container = context.ontology;
106
- context.ontology.ownedStatements.push(statement);
104
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
107
105
  }
108
106
  return new Set([context.ontologyIri]);
109
107
  case 'createInstanceRef':
110
108
  ensureDescriptionOntology(context.ontology, operation.kind);
111
- ensureReferenceImport(context.ontology, operation.instanceIri);
109
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
112
110
  if (operation.typeIri) {
113
- ensureReferenceImport(context.ontology, operation.typeIri);
111
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
114
112
  }
115
113
  {
116
114
  const ownedTypes = [];
@@ -128,15 +126,17 @@ async function executeOperation(shared, context, operation, contexts) {
128
126
  ownedTypes
129
127
  };
130
128
  statement.__targetIri = normalizeIri(operation.instanceIri);
131
- statement.$container = context.ontology;
132
- context.ontology.ownedStatements.push(statement);
129
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
130
+ for (const typeAssertion of ownedTypes) {
131
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
132
+ }
133
133
  }
134
134
  return new Set([context.ontologyIri]);
135
135
  case 'createRelationInstanceRef':
136
136
  ensureDescriptionOntology(context.ontology, operation.kind);
137
- ensureReferenceImport(context.ontology, operation.instanceIri);
137
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
138
138
  if (operation.typeIri) {
139
- ensureReferenceImport(context.ontology, operation.typeIri);
139
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
140
140
  }
141
141
  {
142
142
  const ownedTypes = [];
@@ -156,13 +156,20 @@ async function executeOperation(shared, context, operation, contexts) {
156
156
  targets: []
157
157
  };
158
158
  statement.__targetIri = normalizeIri(operation.instanceIri);
159
- statement.$container = context.ontology;
160
- context.ontology.ownedStatements.push(statement);
159
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
160
+ for (const typeAssertion of ownedTypes) {
161
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
162
+ }
161
163
  }
162
164
  return new Set([context.ontologyIri]);
163
165
  case 'addAssertion':
164
166
  ensureDescriptionOntology(context.ontology, operation.kind);
165
- return addAssertion(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
167
+ return await addAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
168
+ ? new Set([context.ontologyIri])
169
+ : new Set();
170
+ case 'updateAssertion':
171
+ ensureDescriptionOntology(context.ontology, operation.kind);
172
+ return await updateAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
166
173
  ? new Set([context.ontologyIri])
167
174
  : new Set();
168
175
  case 'removeAssertion':
@@ -171,7 +178,11 @@ async function executeOperation(shared, context, operation, contexts) {
171
178
  ? new Set([context.ontologyIri])
172
179
  : new Set();
173
180
  case 'addAnnotation':
174
- return addAnnotation(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
181
+ return await addAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
182
+ ? new Set([context.ontologyIri])
183
+ : new Set();
184
+ case 'updateAnnotation':
185
+ return await updateAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
175
186
  ? new Set([context.ontologyIri])
176
187
  : new Set();
177
188
  case 'removeAnnotation':
@@ -225,7 +236,7 @@ function ensureUniqueLocalName(ontology, memberName) {
225
236
  throw new Error(`A member named '${memberName}' already exists in the ontology.`);
226
237
  }
227
238
  }
228
- function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
239
+ async function addAssertion(shared, ontology, subjectIri, predicateIri, objectValue) {
229
240
  const normalizedPredicate = normalizeIri(predicateIri);
230
241
  const subject = findNamedInstanceByIri(ontology, subjectIri);
231
242
  if (!subject) {
@@ -236,9 +247,9 @@ function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
236
247
  if ((subject.ownedTypes ?? []).some((entry) => matchesPredicateRef(ontology, entry?.type, normalizedTypeIri))) {
237
248
  return false;
238
249
  }
239
- ensureReferenceImport(ontology, normalizedTypeIri);
250
+ await ensureReferenceImport(shared, ontology, normalizedTypeIri);
240
251
  subject.ownedTypes ?? (subject.ownedTypes = []);
241
- subject.ownedTypes.push({
252
+ pushAstArrayChild(subject, 'ownedTypes', {
242
253
  $type: 'TypeAssertion',
243
254
  type: asRef(ontology, normalizedTypeIri)
244
255
  });
@@ -249,7 +260,7 @@ function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
249
260
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
250
261
  }
251
262
  const sourceIri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
252
- ensureReferenceImport(ontology, sourceIri);
263
+ await ensureReferenceImport(shared, ontology, sourceIri);
253
264
  subject.sources ?? (subject.sources = []);
254
265
  subject.sources.push(asRef(ontology, sourceIri));
255
266
  return true;
@@ -259,7 +270,7 @@ function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
259
270
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
260
271
  }
261
272
  const targetIri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
262
- ensureReferenceImport(ontology, targetIri);
273
+ await ensureReferenceImport(shared, ontology, targetIri);
263
274
  subject.targets ?? (subject.targets = []);
264
275
  subject.targets.push(asRef(ontology, targetIri));
265
276
  return true;
@@ -267,6 +278,7 @@ function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
267
278
  subject.ownedPropertyValues ?? (subject.ownedPropertyValues = []);
268
279
  let propertyAssertion = subject.ownedPropertyValues.find((assertion) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
269
280
  if (!propertyAssertion) {
281
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
270
282
  propertyAssertion = {
271
283
  $type: 'PropertyValueAssertion',
272
284
  property: asRef(ontology, normalizedPredicate),
@@ -274,7 +286,13 @@ function addAssertion(ontology, subjectIri, predicateIri, objectValue) {
274
286
  referencedValues: [],
275
287
  containedValues: []
276
288
  };
277
- subject.ownedPropertyValues.push(propertyAssertion);
289
+ pushAstArrayChild(subject, 'ownedPropertyValues', propertyAssertion);
290
+ }
291
+ if (isIriLike(objectValue)) {
292
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
293
+ }
294
+ else {
295
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
278
296
  }
279
297
  appendAssertionValue(ontology, propertyAssertion, objectValue);
280
298
  return true;
@@ -367,20 +385,56 @@ function removeAssertion(ontology, subjectIri, predicateIri, objectValue) {
367
385
  subject.ownedPropertyValues = remaining;
368
386
  return changed;
369
387
  }
370
- function addAnnotation(ontology, subjectIri, predicateIri, objectValue) {
388
+ async function updateAssertion(shared, ontology, subjectIri, predicateIri, objectValue) {
389
+ const normalizedPredicate = normalizeIri(predicateIri);
390
+ const subject = findNamedInstanceByIri(ontology, subjectIri);
391
+ if (!subject) {
392
+ throw new Error(`Subject '${subjectIri}' was not found in the ontology.`);
393
+ }
394
+ if (normalizedPredicate === RDF_TYPE_IRI || isSourcePredicate(normalizedPredicate) || isTargetPredicate(normalizedPredicate)) {
395
+ const removed = removeAssertion(ontology, subjectIri, predicateIri, undefined);
396
+ const added = await addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
397
+ return removed || added;
398
+ }
399
+ subject.ownedPropertyValues ?? (subject.ownedPropertyValues = []);
400
+ let propertyAssertion = subject.ownedPropertyValues.find((assertion) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
401
+ if (!propertyAssertion) {
402
+ return addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
403
+ }
404
+ if (isIriLike(objectValue)) {
405
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
406
+ }
407
+ else {
408
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
409
+ }
410
+ propertyAssertion.literalValues = [];
411
+ propertyAssertion.referencedValues = [];
412
+ propertyAssertion.containedValues = [];
413
+ appendAssertionValue(ontology, propertyAssertion, objectValue);
414
+ return true;
415
+ }
416
+ async function addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue) {
371
417
  const subject = findAnnotationSubjectByIri(ontology, subjectIri);
372
418
  if (!subject) {
373
419
  throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
374
420
  }
421
+ const normalizedPredicate = normalizeIri(predicateIri);
422
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
375
423
  subject.ownedAnnotations ?? (subject.ownedAnnotations = []);
376
424
  const annotation = {
377
425
  $type: 'Annotation',
378
- property: asRef(ontology, predicateIri),
426
+ property: asRef(ontology, normalizedPredicate),
379
427
  literalValues: [],
380
428
  referencedValues: []
381
429
  };
430
+ if (isIriLike(objectValue)) {
431
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
432
+ }
433
+ else {
434
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
435
+ }
382
436
  appendAnnotationValue(ontology, annotation, objectValue);
383
- subject.ownedAnnotations.push(annotation);
437
+ pushAstArrayChild(subject, 'ownedAnnotations', annotation);
384
438
  return true;
385
439
  }
386
440
  function removeAnnotation(ontology, subjectIri, predicateIri, objectValue) {
@@ -420,6 +474,28 @@ function removeAnnotation(ontology, subjectIri, predicateIri, objectValue) {
420
474
  subject.ownedAnnotations = kept;
421
475
  return changed;
422
476
  }
477
+ async function updateAnnotation(shared, ontology, subjectIri, predicateIri, objectValue) {
478
+ const subject = findAnnotationSubjectByIri(ontology, subjectIri);
479
+ if (!subject) {
480
+ throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
481
+ }
482
+ const normalizedPredicate = normalizeIri(predicateIri);
483
+ subject.ownedAnnotations ?? (subject.ownedAnnotations = []);
484
+ const annotation = subject.ownedAnnotations.find((entry) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
485
+ if (!annotation) {
486
+ return addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue);
487
+ }
488
+ if (isIriLike(objectValue)) {
489
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
490
+ }
491
+ else {
492
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
493
+ }
494
+ annotation.literalValues = [];
495
+ annotation.referencedValues = [];
496
+ appendAnnotationValue(ontology, annotation, objectValue);
497
+ return true;
498
+ }
423
499
  function deleteMemberRef(ontology, memberIri, typeIri) {
424
500
  if (!Array.isArray(ontology.ownedStatements)) {
425
501
  return false;
@@ -718,9 +794,7 @@ function matchesPredicateRef(ontology, ref, iri) {
718
794
  return false;
719
795
  }
720
796
  const normalizedWanted = normalizeIri(iri);
721
- const refText = typeof ref === 'string'
722
- ? ref
723
- : (typeof ref.$refText === 'string' ? ref.$refText : undefined);
797
+ const refText = getRefText(ref);
724
798
  if (refText) {
725
799
  const normalizedRefText = normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
726
800
  if (isSameIriTarget(normalizedRefText, normalizedWanted)) {
@@ -734,13 +808,12 @@ function matchesPredicateRef(ontology, ref, iri) {
734
808
  function appendAssertionValue(ontology, assertion, objectValue) {
735
809
  if (isIriLike(objectValue)) {
736
810
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'assertion object'));
737
- ensureReferenceImport(ontology, normalizedObjectIri);
738
811
  assertion.referencedValues ?? (assertion.referencedValues = []);
739
812
  assertion.referencedValues.push(asRef(ontology, normalizedObjectIri));
740
813
  return;
741
814
  }
742
815
  assertion.literalValues ?? (assertion.literalValues = []);
743
- assertion.literalValues.push(asLiteral(objectValue));
816
+ pushAstArrayChild(assertion, 'literalValues', asLiteral(ontology, objectValue));
744
817
  }
745
818
  function removeAssertionValue(ontology, assertion, objectValue) {
746
819
  if (isIriLike(objectValue)) {
@@ -748,19 +821,18 @@ function removeAssertionValue(ontology, assertion, objectValue) {
748
821
  assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref) => !matchesPredicateRef(ontology, ref, normalizedIri));
749
822
  return;
750
823
  }
751
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
824
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
752
825
  assertion.literalValues = (assertion.literalValues ?? []).filter((literal) => normalizeLiteralValue(literal) !== normalizedLiteral);
753
826
  }
754
827
  function appendAnnotationValue(ontology, annotation, objectValue) {
755
828
  if (isIriLike(objectValue)) {
756
829
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'annotation object'));
757
- ensureReferenceImport(ontology, normalizedObjectIri);
758
830
  annotation.referencedValues ?? (annotation.referencedValues = []);
759
831
  annotation.referencedValues.push(asRef(ontology, normalizedObjectIri));
760
832
  return;
761
833
  }
762
834
  annotation.literalValues ?? (annotation.literalValues = []);
763
- annotation.literalValues.push(asLiteral(objectValue));
835
+ pushAstArrayChild(annotation, 'literalValues', asLiteral(ontology, objectValue));
764
836
  }
765
837
  function removeAnnotationValue(ontology, annotation, objectValue) {
766
838
  if (isIriLike(objectValue)) {
@@ -768,7 +840,7 @@ function removeAnnotationValue(ontology, annotation, objectValue) {
768
840
  annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref) => !matchesPredicateRef(ontology, ref, normalizedIri));
769
841
  return;
770
842
  }
771
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
843
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
772
844
  annotation.literalValues = (annotation.literalValues ?? []).filter((literal) => normalizeLiteralValue(literal) !== normalizedLiteral);
773
845
  }
774
846
  function normalizeLiteralValue(literal) {
@@ -893,7 +965,7 @@ function expandLocalRefText(rawNamespace, localName) {
893
965
  }
894
966
  return `${namespace}#${local}`;
895
967
  }
896
- function ensureReferenceImport(ontology, iri) {
968
+ async function ensureReferenceImport(shared, ontology, iri) {
897
969
  const target = parseIriParts(iri);
898
970
  if (!target) {
899
971
  return;
@@ -923,17 +995,53 @@ function ensureReferenceImport(ontology, iri) {
923
995
  }
924
996
  const prefix = pickImportPrefix(target.namespace, usedPrefixes);
925
997
  const importedRef = `<${target.namespace}${target.separator}>`;
998
+ const importKind = await resolveImportKind(shared, ontology, target.namespace);
926
999
  const importStatement = {
927
1000
  $type: 'Import',
928
- kind: 'extends',
1001
+ kind: importKind,
929
1002
  prefix,
930
1003
  imported: { $refText: importedRef }
931
1004
  };
932
- importStatement.$container = ontology;
933
- if (!Array.isArray(ontology.ownedImports)) {
934
- ontology.ownedImports = [];
1005
+ pushAstArrayChild(ontology, 'ownedImports', importStatement);
1006
+ }
1007
+ async function resolveImportKind(shared, importing, importedNamespace) {
1008
+ const imported = await findOntologyByNamespace(shared, importedNamespace);
1009
+ if (imported) {
1010
+ if (importing.$type === imported.$type) {
1011
+ return 'extends';
1012
+ }
1013
+ if (isVocabulary(importing) && isDescription(imported)) {
1014
+ return 'uses';
1015
+ }
1016
+ if (isDescriptionBox(importing) && isVocabulary(imported)) {
1017
+ return 'uses';
1018
+ }
1019
+ if (isVocabularyBundle(importing) && isVocabulary(imported)) {
1020
+ return 'includes';
1021
+ }
935
1022
  }
936
- ontology.ownedImports.push(importStatement);
1023
+ if (isVocabularyBundle(importing)) {
1024
+ return 'includes';
1025
+ }
1026
+ if (isDescriptionBox(importing)) {
1027
+ return 'uses';
1028
+ }
1029
+ return 'extends';
1030
+ }
1031
+ async function findOntologyByNamespace(shared, namespace) {
1032
+ const index = getOntologyModelIndex(shared);
1033
+ const modelUri = index.resolveModelUri(normalizeNamespace(namespace));
1034
+ if (!modelUri) {
1035
+ return undefined;
1036
+ }
1037
+ const uri = URI.parse(modelUri);
1038
+ const langiumDocuments = shared.workspace.LangiumDocuments;
1039
+ const builder = shared.workspace.DocumentBuilder;
1040
+ const document = langiumDocuments.getDocument(uri)
1041
+ ?? await langiumDocuments.getOrCreateDocument(uri);
1042
+ await builder.build([document], { validation: false });
1043
+ const ontology = document?.parseResult?.value;
1044
+ return ontology && isOntology(ontology) ? ontology : undefined;
937
1045
  }
938
1046
  function parseIriParts(iri) {
939
1047
  const normalized = normalizeIri(iri);
@@ -1070,7 +1178,17 @@ function pickImportPrefix(namespace, usedPrefixes) {
1070
1178
  }
1071
1179
  return `${candidateBase}${suffix}`;
1072
1180
  }
1073
- function asLiteral(value) {
1181
+ function asLiteral(ontology, value) {
1182
+ if (isTypedQuotedLiteralTransport(value)) {
1183
+ const literalValue = String(value.value ?? '');
1184
+ const datatypeIri = typeof value.datatypeIri === 'string' ? value.datatypeIri.trim() : '';
1185
+ const datatypeRefText = typeof value.datatypeRefText === 'string' ? value.datatypeRefText.trim() : '';
1186
+ return datatypeIri
1187
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: toRefText(ontology, normalizeIri(datatypeIri)) } }
1188
+ : datatypeRefText
1189
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: datatypeRefText } }
1190
+ : { $type: 'QuotedLiteral', value: literalValue };
1191
+ }
1074
1192
  if (typeof value === 'boolean') {
1075
1193
  return { $type: 'BooleanLiteral', value };
1076
1194
  }
@@ -1082,6 +1200,38 @@ function asLiteral(value) {
1082
1200
  }
1083
1201
  return { $type: 'QuotedLiteral', value: String(value ?? '') };
1084
1202
  }
1203
+ async function ensureTypedLiteralDatatypeImport(shared, ontology, value) {
1204
+ if (!isTypedQuotedLiteralTransport(value)) {
1205
+ return;
1206
+ }
1207
+ const datatypeIri = typeof value.datatypeIri === 'string' ? normalizeIri(value.datatypeIri) : '';
1208
+ if (!datatypeIri) {
1209
+ return;
1210
+ }
1211
+ await ensureReferenceImport(shared, ontology, datatypeIri);
1212
+ }
1213
+ function isTypedQuotedLiteralTransport(value) {
1214
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1215
+ return false;
1216
+ }
1217
+ const record = value;
1218
+ if (record.$type === 'QuotedLiteral') {
1219
+ return true;
1220
+ }
1221
+ return (typeof record.datatypeIri === 'string' || typeof record.datatypeRefText === 'string') && 'value' in record;
1222
+ }
1223
+ function attachAstNode(node, parent, containerProperty) {
1224
+ node.$container = parent;
1225
+ node.$containerProperty = containerProperty;
1226
+ return node;
1227
+ }
1228
+ function pushAstArrayChild(parent, property, node) {
1229
+ if (!Array.isArray(parent[property])) {
1230
+ parent[property] = [];
1231
+ }
1232
+ attachAstNode(node, parent, property);
1233
+ parent[property].push(node);
1234
+ }
1085
1235
  function asRequiredIri(value, context) {
1086
1236
  if (!isIriLike(value)) {
1087
1237
  throw new Error(`Expected IRI for ${context}.`);