@oml/language 0.9.0 → 0.11.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,17 +1,20 @@
1
1
  // Copyright (c) 2026 Modelware. All rights reserved.
2
2
 
3
- import { URI } from 'langium';
3
+ import { DocumentState, URI } from 'langium';
4
4
  import { RequestType, type WorkspaceEdit } from 'vscode-languageserver-protocol';
5
5
  import {
6
6
  isAnnotation,
7
7
  isConceptInstance,
8
8
  isDescription,
9
+ isDescriptionBox,
9
10
  isImport,
10
11
  isOntology,
11
12
  isPropertyValueAssertion,
12
13
  isRelationInstance,
13
14
  isRule,
14
15
  isTypeAssertion,
16
+ isVocabulary,
17
+ isVocabularyBundle,
15
18
  } from './generated/ast.js';
16
19
  import { getOntologyModelIndex } from './oml-index.js';
17
20
  import { serializeOntology } from './oml-serializer.js';
@@ -40,8 +43,10 @@ type OmlEditOperation =
40
43
  | { kind: 'createInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
41
44
  | { kind: 'createRelationInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
42
45
  | { kind: 'addAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
46
+ | { kind: 'updateAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
43
47
  | { kind: 'removeAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
44
48
  | { kind: 'addAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
49
+ | { kind: 'updateAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
45
50
  | { kind: 'removeAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
46
51
  | { kind: 'deleteMemberCascade'; ontologyIri: string; memberIri: string }
47
52
  | { kind: 'deleteMemberRef'; ontologyIri: string; memberIri: string; typeIri?: string };
@@ -116,7 +121,12 @@ async function resolveOntologyContext(shared: any, ontologyIri: string, cache: M
116
121
  const builder: any = shared.workspace.DocumentBuilder;
117
122
  const document = langiumDocuments.getDocument(uri)
118
123
  ?? await langiumDocuments.getOrCreateDocument(uri);
119
- await builder.build([document], { validation: true });
124
+ // Only build if not yet Linked. Forcing validation:true when the workspace
125
+ // is already Validated causes a document-state conflict in Langium on Windows.
126
+ const docState: number | undefined = document?.state;
127
+ if (docState === undefined || docState < DocumentState.Linked) {
128
+ await builder.build([document], { validation: false });
129
+ }
120
130
  const ontology = document?.parseResult?.value;
121
131
  if (!ontology || !isOntology(ontology)) {
122
132
  throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
@@ -149,8 +159,7 @@ async function executeOperation(
149
159
  ownedPropertyValues: [],
150
160
  ownedTypes: []
151
161
  };
152
- statement.$container = context.ontology;
153
- context.ontology.ownedStatements.push(statement);
162
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
154
163
  }
155
164
  return new Set([context.ontologyIri]);
156
165
  case 'createRelationInstance':
@@ -166,15 +175,14 @@ async function executeOperation(
166
175
  sources: [],
167
176
  targets: []
168
177
  };
169
- statement.$container = context.ontology;
170
- context.ontology.ownedStatements.push(statement);
178
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
171
179
  }
172
180
  return new Set([context.ontologyIri]);
173
181
  case 'createInstanceRef':
174
182
  ensureDescriptionOntology(context.ontology, operation.kind);
175
- ensureReferenceImport(context.ontology, operation.instanceIri);
183
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
176
184
  if (operation.typeIri) {
177
- ensureReferenceImport(context.ontology, operation.typeIri);
185
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
178
186
  }
179
187
  {
180
188
  const ownedTypes: any[] = [];
@@ -192,15 +200,17 @@ async function executeOperation(
192
200
  ownedTypes
193
201
  };
194
202
  statement.__targetIri = normalizeIri(operation.instanceIri);
195
- statement.$container = context.ontology;
196
- context.ontology.ownedStatements.push(statement);
203
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
204
+ for (const typeAssertion of ownedTypes) {
205
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
206
+ }
197
207
  }
198
208
  return new Set([context.ontologyIri]);
199
209
  case 'createRelationInstanceRef':
200
210
  ensureDescriptionOntology(context.ontology, operation.kind);
201
- ensureReferenceImport(context.ontology, operation.instanceIri);
211
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
202
212
  if (operation.typeIri) {
203
- ensureReferenceImport(context.ontology, operation.typeIri);
213
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
204
214
  }
205
215
  {
206
216
  const ownedTypes: any[] = [];
@@ -220,13 +230,20 @@ async function executeOperation(
220
230
  targets: []
221
231
  };
222
232
  statement.__targetIri = normalizeIri(operation.instanceIri);
223
- statement.$container = context.ontology;
224
- context.ontology.ownedStatements.push(statement);
233
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
234
+ for (const typeAssertion of ownedTypes) {
235
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
236
+ }
225
237
  }
226
238
  return new Set([context.ontologyIri]);
227
239
  case 'addAssertion':
228
240
  ensureDescriptionOntology(context.ontology, operation.kind);
229
- return addAssertion(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
241
+ return await addAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
242
+ ? new Set([context.ontologyIri])
243
+ : new Set();
244
+ case 'updateAssertion':
245
+ ensureDescriptionOntology(context.ontology, operation.kind);
246
+ return await updateAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
230
247
  ? new Set([context.ontologyIri])
231
248
  : new Set();
232
249
  case 'removeAssertion':
@@ -235,7 +252,11 @@ async function executeOperation(
235
252
  ? new Set([context.ontologyIri])
236
253
  : new Set();
237
254
  case 'addAnnotation':
238
- return addAnnotation(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
255
+ return await addAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
256
+ ? new Set([context.ontologyIri])
257
+ : new Set();
258
+ case 'updateAnnotation':
259
+ return await updateAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
239
260
  ? new Set([context.ontologyIri])
240
261
  : new Set();
241
262
  case 'removeAnnotation':
@@ -269,7 +290,10 @@ async function resolveOntologyContextByModelUri(
269
290
  const builder: any = shared.workspace.DocumentBuilder;
270
291
  const document = langiumDocuments.getDocument(uri)
271
292
  ?? await langiumDocuments.getOrCreateDocument(uri);
272
- await builder.build([document], { validation: true });
293
+ const docState2: number | undefined = document?.state;
294
+ if (docState2 === undefined || docState2 < DocumentState.Linked) {
295
+ await builder.build([document], { validation: false });
296
+ }
273
297
  const ontology = document?.parseResult?.value;
274
298
  if (!ontology || !isOntology(ontology)) {
275
299
  throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
@@ -297,7 +321,7 @@ function ensureUniqueLocalName(ontology: any, memberName: string): void {
297
321
  }
298
322
  }
299
323
 
300
- function addAssertion(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
324
+ async function addAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
301
325
  const normalizedPredicate = normalizeIri(predicateIri);
302
326
  const subject = findNamedInstanceByIri(ontology, subjectIri);
303
327
  if (!subject) {
@@ -309,9 +333,9 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
309
333
  if ((subject.ownedTypes ?? []).some((entry: any) => matchesPredicateRef(ontology, entry?.type, normalizedTypeIri))) {
310
334
  return false;
311
335
  }
312
- ensureReferenceImport(ontology, normalizedTypeIri);
336
+ await ensureReferenceImport(shared, ontology, normalizedTypeIri);
313
337
  subject.ownedTypes ??= [];
314
- subject.ownedTypes.push({
338
+ pushAstArrayChild(subject, 'ownedTypes', {
315
339
  $type: 'TypeAssertion',
316
340
  type: asRef(ontology, normalizedTypeIri)
317
341
  });
@@ -323,7 +347,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
323
347
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
324
348
  }
325
349
  const sourceIri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
326
- ensureReferenceImport(ontology, sourceIri);
350
+ await ensureReferenceImport(shared, ontology, sourceIri);
327
351
  subject.sources ??= [];
328
352
  subject.sources.push(asRef(ontology, sourceIri));
329
353
  return true;
@@ -334,7 +358,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
334
358
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
335
359
  }
336
360
  const targetIri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
337
- ensureReferenceImport(ontology, targetIri);
361
+ await ensureReferenceImport(shared, ontology, targetIri);
338
362
  subject.targets ??= [];
339
363
  subject.targets.push(asRef(ontology, targetIri));
340
364
  return true;
@@ -343,6 +367,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
343
367
  subject.ownedPropertyValues ??= [];
344
368
  let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
345
369
  if (!propertyAssertion) {
370
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
346
371
  propertyAssertion = {
347
372
  $type: 'PropertyValueAssertion',
348
373
  property: asRef(ontology, normalizedPredicate),
@@ -350,7 +375,12 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
350
375
  referencedValues: [],
351
376
  containedValues: []
352
377
  };
353
- subject.ownedPropertyValues.push(propertyAssertion);
378
+ pushAstArrayChild(subject, 'ownedPropertyValues', propertyAssertion);
379
+ }
380
+ if (isIriLike(objectValue)) {
381
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
382
+ } else {
383
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
354
384
  }
355
385
  appendAssertionValue(ontology, propertyAssertion, objectValue);
356
386
  return true;
@@ -447,20 +477,57 @@ function removeAssertion(ontology: any, subjectIri: string, predicateIri: string
447
477
  return changed;
448
478
  }
449
479
 
450
- function addAnnotation(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
480
+ async function updateAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
481
+ const normalizedPredicate = normalizeIri(predicateIri);
482
+ const subject = findNamedInstanceByIri(ontology, subjectIri);
483
+ if (!subject) {
484
+ throw new Error(`Subject '${subjectIri}' was not found in the ontology.`);
485
+ }
486
+
487
+ if (normalizedPredicate === RDF_TYPE_IRI || isSourcePredicate(normalizedPredicate) || isTargetPredicate(normalizedPredicate)) {
488
+ const removed = removeAssertion(ontology, subjectIri, predicateIri, undefined);
489
+ const added = await addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
490
+ return removed || added;
491
+ }
492
+
493
+ subject.ownedPropertyValues ??= [];
494
+ let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
495
+ if (!propertyAssertion) {
496
+ return addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
497
+ }
498
+ if (isIriLike(objectValue)) {
499
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
500
+ } else {
501
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
502
+ }
503
+ propertyAssertion.literalValues = [];
504
+ propertyAssertion.referencedValues = [];
505
+ propertyAssertion.containedValues = [];
506
+ appendAssertionValue(ontology, propertyAssertion, objectValue);
507
+ return true;
508
+ }
509
+
510
+ async function addAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
451
511
  const subject = findAnnotationSubjectByIri(ontology, subjectIri);
452
512
  if (!subject) {
453
513
  throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
454
514
  }
515
+ const normalizedPredicate = normalizeIri(predicateIri);
516
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
455
517
  subject.ownedAnnotations ??= [];
456
518
  const annotation: any = {
457
519
  $type: 'Annotation',
458
- property: asRef(ontology, predicateIri),
520
+ property: asRef(ontology, normalizedPredicate),
459
521
  literalValues: [],
460
522
  referencedValues: []
461
523
  };
524
+ if (isIriLike(objectValue)) {
525
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
526
+ } else {
527
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
528
+ }
462
529
  appendAnnotationValue(ontology, annotation, objectValue);
463
- subject.ownedAnnotations.push(annotation);
530
+ pushAstArrayChild(subject, 'ownedAnnotations', annotation);
464
531
  return true;
465
532
  }
466
533
 
@@ -502,6 +569,28 @@ function removeAnnotation(ontology: any, subjectIri: string, predicateIri: strin
502
569
  return changed;
503
570
  }
504
571
 
572
+ async function updateAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
573
+ const subject = findAnnotationSubjectByIri(ontology, subjectIri);
574
+ if (!subject) {
575
+ throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
576
+ }
577
+ const normalizedPredicate = normalizeIri(predicateIri);
578
+ subject.ownedAnnotations ??= [];
579
+ const annotation = subject.ownedAnnotations.find((entry: any) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
580
+ if (!annotation) {
581
+ return addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue);
582
+ }
583
+ if (isIriLike(objectValue)) {
584
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
585
+ } else {
586
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
587
+ }
588
+ annotation.literalValues = [];
589
+ annotation.referencedValues = [];
590
+ appendAnnotationValue(ontology, annotation, objectValue);
591
+ return true;
592
+ }
593
+
505
594
  function deleteMemberRef(ontology: any, memberIri: string, typeIri: string | undefined): boolean {
506
595
  if (!Array.isArray(ontology.ownedStatements)) {
507
596
  return false;
@@ -818,9 +907,7 @@ function matchesPredicateRef(ontology: any, ref: any, iri: string): boolean {
818
907
  return false;
819
908
  }
820
909
  const normalizedWanted = normalizeIri(iri);
821
- const refText = typeof ref === 'string'
822
- ? ref
823
- : (typeof ref.$refText === 'string' ? ref.$refText : undefined);
910
+ const refText = getRefText(ref);
824
911
  if (refText) {
825
912
  const normalizedRefText = normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
826
913
  if (isSameIriTarget(normalizedRefText, normalizedWanted)) {
@@ -835,13 +922,12 @@ function matchesPredicateRef(ontology: any, ref: any, iri: string): boolean {
835
922
  function appendAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
836
923
  if (isIriLike(objectValue)) {
837
924
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'assertion object'));
838
- ensureReferenceImport(ontology, normalizedObjectIri);
839
925
  assertion.referencedValues ??= [];
840
926
  assertion.referencedValues.push(asRef(ontology, normalizedObjectIri));
841
927
  return;
842
928
  }
843
929
  assertion.literalValues ??= [];
844
- assertion.literalValues.push(asLiteral(objectValue));
930
+ pushAstArrayChild(assertion, 'literalValues', asLiteral(ontology, objectValue));
845
931
  }
846
932
 
847
933
  function removeAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
@@ -850,20 +936,19 @@ function removeAssertionValue(ontology: any, assertion: any, objectValue: unknow
850
936
  assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
851
937
  return;
852
938
  }
853
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
939
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
854
940
  assertion.literalValues = (assertion.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
855
941
  }
856
942
 
857
943
  function appendAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
858
944
  if (isIriLike(objectValue)) {
859
945
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'annotation object'));
860
- ensureReferenceImport(ontology, normalizedObjectIri);
861
946
  annotation.referencedValues ??= [];
862
947
  annotation.referencedValues.push(asRef(ontology, normalizedObjectIri));
863
948
  return;
864
949
  }
865
950
  annotation.literalValues ??= [];
866
- annotation.literalValues.push(asLiteral(objectValue));
951
+ pushAstArrayChild(annotation, 'literalValues', asLiteral(ontology, objectValue));
867
952
  }
868
953
 
869
954
  function removeAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
@@ -872,7 +957,7 @@ function removeAnnotationValue(ontology: any, annotation: any, objectValue: unkn
872
957
  annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
873
958
  return;
874
959
  }
875
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
960
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
876
961
  annotation.literalValues = (annotation.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
877
962
  }
878
963
 
@@ -1006,7 +1091,7 @@ function expandLocalRefText(rawNamespace: string | undefined, localName: string)
1006
1091
  return `${namespace}#${local}`;
1007
1092
  }
1008
1093
 
1009
- function ensureReferenceImport(ontology: any, iri: string): void {
1094
+ async function ensureReferenceImport(shared: any, ontology: any, iri: string): Promise<void> {
1010
1095
  const target = parseIriParts(iri);
1011
1096
  if (!target) {
1012
1097
  return;
@@ -1036,17 +1121,55 @@ function ensureReferenceImport(ontology: any, iri: string): void {
1036
1121
  }
1037
1122
  const prefix = pickImportPrefix(target.namespace, usedPrefixes);
1038
1123
  const importedRef = `<${target.namespace}${target.separator}>`;
1124
+ const importKind = await resolveImportKind(shared, ontology, target.namespace);
1039
1125
  const importStatement: any = {
1040
1126
  $type: 'Import',
1041
- kind: 'extends',
1127
+ kind: importKind,
1042
1128
  prefix,
1043
1129
  imported: { $refText: importedRef }
1044
1130
  };
1045
- importStatement.$container = ontology;
1046
- if (!Array.isArray(ontology.ownedImports)) {
1047
- ontology.ownedImports = [];
1131
+ pushAstArrayChild(ontology, 'ownedImports', importStatement);
1132
+ }
1133
+
1134
+ async function resolveImportKind(shared: any, importing: any, importedNamespace: string): Promise<'extends' | 'includes' | 'uses'> {
1135
+ const imported = await findOntologyByNamespace(shared, importedNamespace);
1136
+ if (imported) {
1137
+ if (importing.$type === imported.$type) {
1138
+ return 'extends';
1139
+ }
1140
+ if (isVocabulary(importing) && isDescription(imported)) {
1141
+ return 'uses';
1142
+ }
1143
+ if (isDescriptionBox(importing) && isVocabulary(imported)) {
1144
+ return 'uses';
1145
+ }
1146
+ if (isVocabularyBundle(importing) && isVocabulary(imported)) {
1147
+ return 'includes';
1148
+ }
1149
+ }
1150
+ if (isVocabularyBundle(importing)) {
1151
+ return 'includes';
1152
+ }
1153
+ if (isDescriptionBox(importing)) {
1154
+ return 'uses';
1048
1155
  }
1049
- ontology.ownedImports.push(importStatement);
1156
+ return 'extends';
1157
+ }
1158
+
1159
+ async function findOntologyByNamespace(shared: any, namespace: string): Promise<any | undefined> {
1160
+ const index = getOntologyModelIndex(shared);
1161
+ const modelUri = index.resolveModelUri(normalizeNamespace(namespace));
1162
+ if (!modelUri) {
1163
+ return undefined;
1164
+ }
1165
+ const uri = URI.parse(modelUri);
1166
+ const langiumDocuments: any = shared.workspace.LangiumDocuments;
1167
+ const builder: any = shared.workspace.DocumentBuilder;
1168
+ const document = langiumDocuments.getDocument(uri)
1169
+ ?? await langiumDocuments.getOrCreateDocument(uri);
1170
+ await builder.build([document], { validation: false });
1171
+ const ontology = document?.parseResult?.value;
1172
+ return ontology && isOntology(ontology) ? ontology : undefined;
1050
1173
  }
1051
1174
 
1052
1175
  function parseIriParts(iri: string): { namespace: string; fragment: string; separator: '#' | '/' } | undefined {
@@ -1198,7 +1321,17 @@ function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>):
1198
1321
  return `${candidateBase}${suffix}`;
1199
1322
  }
1200
1323
 
1201
- function asLiteral(value: unknown): any {
1324
+ function asLiteral(ontology: any, value: unknown): any {
1325
+ if (isTypedQuotedLiteralTransport(value)) {
1326
+ const literalValue = String(value.value ?? '');
1327
+ const datatypeIri = typeof value.datatypeIri === 'string' ? value.datatypeIri.trim() : '';
1328
+ const datatypeRefText = typeof value.datatypeRefText === 'string' ? value.datatypeRefText.trim() : '';
1329
+ return datatypeIri
1330
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: toRefText(ontology, normalizeIri(datatypeIri)) } }
1331
+ : datatypeRefText
1332
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: datatypeRefText } }
1333
+ : { $type: 'QuotedLiteral', value: literalValue };
1334
+ }
1202
1335
  if (typeof value === 'boolean') {
1203
1336
  return { $type: 'BooleanLiteral', value };
1204
1337
  }
@@ -1211,6 +1344,42 @@ function asLiteral(value: unknown): any {
1211
1344
  return { $type: 'QuotedLiteral', value: String(value ?? '') };
1212
1345
  }
1213
1346
 
1347
+ async function ensureTypedLiteralDatatypeImport(shared: any, ontology: any, value: unknown): Promise<void> {
1348
+ if (!isTypedQuotedLiteralTransport(value)) {
1349
+ return;
1350
+ }
1351
+ const datatypeIri = typeof value.datatypeIri === 'string' ? normalizeIri(value.datatypeIri) : '';
1352
+ if (!datatypeIri) {
1353
+ return;
1354
+ }
1355
+ await ensureReferenceImport(shared, ontology, datatypeIri);
1356
+ }
1357
+
1358
+ function isTypedQuotedLiteralTransport(value: unknown): value is { $type?: unknown; value?: unknown; datatypeIri?: unknown; datatypeRefText?: unknown } {
1359
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1360
+ return false;
1361
+ }
1362
+ const record = value as Record<string, unknown>;
1363
+ if (record.$type === 'QuotedLiteral') {
1364
+ return true;
1365
+ }
1366
+ return (typeof record.datatypeIri === 'string' || typeof record.datatypeRefText === 'string') && 'value' in record;
1367
+ }
1368
+
1369
+ function attachAstNode<T extends Record<string, any>>(node: T, parent: any, containerProperty: string): T {
1370
+ (node as any).$container = parent;
1371
+ (node as any).$containerProperty = containerProperty;
1372
+ return node;
1373
+ }
1374
+
1375
+ function pushAstArrayChild(parent: any, property: string, node: any): void {
1376
+ if (!Array.isArray(parent[property])) {
1377
+ parent[property] = [];
1378
+ }
1379
+ attachAstNode(node, parent, property);
1380
+ parent[property].push(node);
1381
+ }
1382
+
1214
1383
  function asRequiredIri(value: unknown, context: string): string {
1215
1384
  if (!isIriLike(value)) {
1216
1385
  throw new Error(`Expected IRI for ${context}.`);