@oml/server 0.14.13 → 0.14.14

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.
@@ -11,6 +11,15 @@ export interface OmlRestServerOptions {
11
11
  runtime?: OmlLanguageServerRuntime;
12
12
  featureGate?: OmlFeatureGateLike;
13
13
  }
14
+ export type OmlWorkspaceLocalOperation = 'lint' | 'validate' | 'assertions' | 'reason' | 'export' | 'render';
15
+ export declare function runOmlWorkspaceLocalOperation<T = Record<string, unknown>>(options: {
16
+ workspaceRoot?: string;
17
+ operation: OmlWorkspaceLocalOperation;
18
+ params?: Record<string, unknown>;
19
+ requestTimeoutMs?: number;
20
+ watchWorkspace?: boolean;
21
+ runtime?: OmlLanguageServerRuntime;
22
+ }): Promise<T>;
14
23
  export declare function startOmlRestServer(options: OmlRestServerOptions): Promise<{
15
24
  server: http.Server;
16
25
  updateToken: (token: string) => Promise<void>;
@@ -12,9 +12,6 @@ import { NodeFileSystem } from 'langium/node';
12
12
  import { createConnection } from 'vscode-languageserver/node.js';
13
13
  import { DataFactory, Writer } from 'n3';
14
14
  import uFuzzy from '@leeoniya/ufuzzy';
15
- // swagger-ui-dist does not ship TypeScript declarations.
16
- // @ts-expect-error Untyped CommonJS module import.
17
- import swaggerUiDist from 'swagger-ui-dist';
18
15
  import { MarkdownHandlerRegistry, MarkdownPreviewRuntime, buildTemplateCatalog as buildNavigationTemplateCatalog, extractLeadingFrontMatter, resolveTemplateForNavigation, renderTemplate, MarkdownExecutor, } from '@oml/markdown';
19
16
  import { STATIC_MARKDOWN_RUNTIME_BUNDLE_FILE, STATIC_MARKDOWN_RUNTIME_CSS } from '@oml/markdown/static';
20
17
  import { applyOmlUpdate, collectOntologyMembers, getIriForNode, getOntologyModelIndex, iriFragment, isDescription, isOntology, isVocabulary, tokenizeForFuzzy, } from '@oml/language';
@@ -43,9 +40,9 @@ const SWAGGER_CONTENT_TYPES = {
43
40
  '.js': 'application/javascript; charset=utf-8',
44
41
  '.png': 'image/png',
45
42
  };
46
- let swaggerDistDirCache;
47
43
  let swaggerAssetValidationError;
48
44
  let markdownStaticEntryFileCache;
45
+ const swaggerAssetPathCache = new Map();
49
46
  function createModuleRequire() {
50
47
  if (typeof __filename === 'string' && path.isAbsolute(__filename)) {
51
48
  return createRequire(__filename);
@@ -105,22 +102,6 @@ function textResponse(res, status, contentType, body) {
105
102
  res.setHeader('content-length', Buffer.byteLength(body, 'utf-8'));
106
103
  res.end(body);
107
104
  }
108
- function resolveSwaggerDistDir() {
109
- if (!swaggerDistDirCache) {
110
- if (typeof swaggerUiDist?.getAbsoluteFSPath !== 'function') {
111
- throw new Error("Swagger UI package is unavailable: missing 'swagger-ui-dist.getAbsoluteFSPath()'.");
112
- }
113
- const resolved = swaggerUiDist.getAbsoluteFSPath();
114
- if (!resolved || resolved.trim().length === 0) {
115
- throw new Error("Swagger UI package returned an invalid asset directory.");
116
- }
117
- swaggerDistDirCache = resolved;
118
- }
119
- if (!swaggerDistDirCache) {
120
- throw new Error('Swagger UI package returned an invalid asset directory.');
121
- }
122
- return swaggerDistDirCache;
123
- }
124
105
  function resolveMarkdownStaticEntryFile() {
125
106
  if (!markdownStaticEntryFileCache) {
126
107
  const entryFile = require.resolve('@oml/markdown/static');
@@ -132,13 +113,8 @@ function resolveMarkdownStaticEntryFile() {
132
113
  return markdownStaticEntryFileCache;
133
114
  }
134
115
  function validateSwaggerAssetsOrThrow() {
135
- const distDir = resolveSwaggerDistDir();
136
- for (const fileName of SWAGGER_ASSET_FILES) {
137
- const filePath = path.join(distDir, fileName);
138
- const stat = fsSync.statSync(filePath, { throwIfNoEntry: false });
139
- if (!stat || !stat.isFile()) {
140
- throw new Error(`Swagger UI asset missing: ${fileName}`);
141
- }
116
+ for (const asset of ['swagger-ui-bundle.js', 'swagger-ui-standalone-preset.js']) {
117
+ resolveSwaggerAssetPath(asset);
142
118
  }
143
119
  }
144
120
  function getSwaggerValidationError() {
@@ -159,7 +135,7 @@ async function serveSwaggerAsset(res, fileName) {
159
135
  jsonResponse(res, 404, { error: 'Swagger asset not found.' });
160
136
  return;
161
137
  }
162
- const filePath = path.join(resolveSwaggerDistDir(), fileName);
138
+ const filePath = resolveSwaggerAssetPath(fileName);
163
139
  const extension = path.extname(fileName).toLowerCase();
164
140
  const contentType = SWAGGER_CONTENT_TYPES[extension] ?? 'application/octet-stream';
165
141
  const content = await fs.readFile(filePath);
@@ -169,6 +145,22 @@ async function serveSwaggerAsset(res, fileName) {
169
145
  res.setHeader('content-length', content.length);
170
146
  res.end(content);
171
147
  }
148
+ function resolveSwaggerAssetPath(fileName) {
149
+ if (!SWAGGER_ASSET_FILES.has(fileName)) {
150
+ throw new Error(`Swagger UI asset not allowed: ${fileName}`);
151
+ }
152
+ const cached = swaggerAssetPathCache.get(fileName);
153
+ if (cached) {
154
+ return cached;
155
+ }
156
+ const resolvedPath = require.resolve(`swagger-ui-dist/${fileName}`);
157
+ const resolvedStat = fsSync.statSync(resolvedPath, { throwIfNoEntry: false });
158
+ if (resolvedStat?.isFile()) {
159
+ swaggerAssetPathCache.set(fileName, resolvedPath);
160
+ return resolvedPath;
161
+ }
162
+ throw new Error(`Swagger UI asset missing: ${fileName}`);
163
+ }
172
164
  function createSwaggerUiPage() {
173
165
  return `<!doctype html>
174
166
  <html lang="en">
@@ -789,7 +781,7 @@ function createSparqlWorkbenchPage(defaultWorkspaceRoot) {
789
781
  <section class="hero">
790
782
  <div class="hero-header">
791
783
  <h1 class="page-title">OML Server</h1>
792
- <a id="swaggerLink" class="swagger-link" href="#" target="_blank" rel="noopener noreferrer" title="Open Swagger Docs">
784
+ <a id="swaggerLink" class="swagger-link" href="docs" target="_blank" rel="noopener noreferrer" title="Open Swagger Docs">
793
785
  <svg viewBox="0 0 24 24" aria-hidden="true">
794
786
  <path d="M13.8 2.6a2.1 2.1 0 0 0-3.6 1.5c0 .2 0 .4.1.6a6 6 0 0 0-2.2 1.3l-.4-.2a2.1 2.1 0 1 0-1.8 3.8l.4.2a6.3 6.3 0 0 0 0 2.6l-.4.2a2.1 2.1 0 1 0 1.8 3.8l.4-.2a6 6 0 0 0 2.2 1.3 2.1 2.1 0 1 0 3.5 0 6 6 0 0 0 2.2-1.3l.4.2a2.1 2.1 0 1 0 1.8-3.8l-.4-.2a6.3 6.3 0 0 0 0-2.6l.4-.2a2.1 2.1 0 1 0-1.8-3.8l-.4.2a6 6 0 0 0-2.2-1.3c.1-.2.1-.4.1-.6 0-.6-.2-1.1-.6-1.5Zm-2.6 6.3a3.3 3.3 0 1 1 0 6.6 3.3 3.3 0 0 1 0-6.6Zm4.8 9.6a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm-9.6 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm11-8.7a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm-12.4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm7.2-7.1a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"/>
795
787
  </svg>
@@ -897,7 +889,6 @@ LIMIT 25</textarea>
897
889
  const browseStatus = document.getElementById('browseStatus');
898
890
  const browseList = document.getElementById('browseList');
899
891
  const browseFilter = document.getElementById('browseFilter');
900
- const swaggerLink = document.getElementById('swaggerLink');
901
892
 
902
893
  let browseEntries = [];
903
894
  let selectedModelId = '';
@@ -916,10 +907,6 @@ LIMIT 25</textarea>
916
907
  return baseRoutePath() + normalized;
917
908
  }
918
909
 
919
- if (swaggerLink) {
920
- swaggerLink.setAttribute('href', routeUrl('docs'));
921
- }
922
-
923
910
  function escapeClientHtml(value) {
924
911
  return String(value)
925
912
  .replace(/&/g, '&amp;')
@@ -2044,7 +2031,7 @@ class InMemoryJsonRpcLspClient {
2044
2031
  listener.dispose();
2045
2032
  }
2046
2033
  }
2047
- async lintWorkspace(params = {}) {
2034
+ async lintWorkspace(_params = {}) {
2048
2035
  const preDocs = this.getWorkspaceOmlDocuments();
2049
2036
  const preSummary = this.summarizeDiagnostics(preDocs);
2050
2037
  const preStates = this.summarizeDocumentStates(preDocs);
@@ -2072,16 +2059,14 @@ class InMemoryJsonRpcLspClient {
2072
2059
  getWorkspaceOmlDocuments: () => this.getWorkspaceOmlDocuments(),
2073
2060
  writeWorkspaceAssertedOwl: (outputDir, format, pretty) => this.writeWorkspaceAssertedOwl(outputDir, format, pretty),
2074
2061
  };
2075
- const result = await lintWorkspace(context, params);
2062
+ const result = await lintWorkspace(context);
2076
2063
  const postDocs = this.getWorkspaceOmlDocuments();
2077
2064
  const postStates = this.summarizeDocumentStates(postDocs);
2078
2065
  debugRest('lint.end', {
2079
2066
  filesChecked: result.filesChecked,
2080
2067
  errors: result.errors,
2081
2068
  warnings: result.warnings,
2082
- totalProblems: result.totalProblems,
2083
- returnedProblems: result.returnedProblems,
2084
- truncated: result.truncated,
2069
+ problems: result.problems.length,
2085
2070
  elapsedMs: result.elapsedMs,
2086
2071
  stateValidated: postStates.validated,
2087
2072
  stateLinked: postStates.linked,
@@ -2177,6 +2162,12 @@ class InMemoryJsonRpcLspClient {
2177
2162
  async updateWorkspace(params) {
2178
2163
  await this.ensureInitialized();
2179
2164
  await this.ensureWorkspaceCurrent();
2165
+ const lint = await this.lintWorkspace({});
2166
+ if (lint.errors > 0 || lint.warnings > 0) {
2167
+ return {
2168
+ error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2169
+ };
2170
+ }
2180
2171
  return await applyOmlUpdate(this.runtime.shared, params, (message) => this.logError(message));
2181
2172
  }
2182
2173
  async fuzzySearchWorkspace(params) {
@@ -2278,6 +2269,52 @@ class InMemoryJsonRpcLspClient {
2278
2269
  };
2279
2270
  }
2280
2271
  }
2272
+ async assertionsWorkspace(params) {
2273
+ await this.ensureInitialized();
2274
+ await this.ensureWorkspaceCurrent();
2275
+ const lint = await this.lintWorkspace({});
2276
+ if (lint.errors > 0 || lint.warnings > 0) {
2277
+ return {
2278
+ success: false,
2279
+ files: [],
2280
+ error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2281
+ };
2282
+ }
2283
+ const modelUriFilter = typeof params.modelUri === 'string' ? params.modelUri.trim() : '';
2284
+ const docs = this.getWorkspaceOmlDocuments();
2285
+ const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
2286
+ const store = reasoningService.getStore().getStore();
2287
+ const files = [];
2288
+ for (const doc of docs) {
2289
+ const modelUri = String(doc?.uri ?? '').trim();
2290
+ if (modelUriFilter.length > 0 && modelUri !== modelUriFilter) {
2291
+ continue;
2292
+ }
2293
+ const root = doc?.parseResult?.value;
2294
+ const ontologyIri = normalizeOntologyNamespace(root?.namespace)?.replace(/[\/#]+$/, '');
2295
+ if (!ontologyIri || BUILT_IN_ONTOLOGIES.has(ontologyIri)) {
2296
+ continue;
2297
+ }
2298
+ await reasoningService.ensureQueryContext(modelUri);
2299
+ const quads = store.getQuads(null, null, null, DataFactory.namedNode(modelUri))
2300
+ .map((quad) => ({
2301
+ subject: quad.subject,
2302
+ predicate: quad.predicate,
2303
+ object: quad.object,
2304
+ }));
2305
+ files.push({
2306
+ modelUri,
2307
+ ontologyIri,
2308
+ path: resolveOutputPathFromOntologyIriString(ontologyIri, 'nt').split(path.sep).join('/'),
2309
+ content: await serializeQuads(quads, 'nt', false),
2310
+ });
2311
+ }
2312
+ files.sort((left, right) => left.ontologyIri.localeCompare(right.ontologyIri));
2313
+ return {
2314
+ success: true,
2315
+ files,
2316
+ };
2317
+ }
2281
2318
  async writeWorkspaceAssertedOwl(outputDir, format, pretty) {
2282
2319
  const entries = [];
2283
2320
  const docs = this.getWorkspaceOmlDocuments();
@@ -2324,14 +2361,11 @@ class InMemoryJsonRpcLspClient {
2324
2361
  return await validateWorkspace(context, params);
2325
2362
  }
2326
2363
  async exportWorkspace(params) {
2327
- const lint = await this.lintWorkspace({
2328
- limit: typeof params.lintLimit === 'number' ? params.lintLimit : undefined,
2329
- });
2364
+ const lint = await this.lintWorkspace({});
2330
2365
  if (lint.errors > 0 || lint.warnings > 0) {
2331
2366
  return {
2332
2367
  success: false,
2333
2368
  error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2334
- lint,
2335
2369
  };
2336
2370
  }
2337
2371
  const context = {
@@ -2342,10 +2376,7 @@ class InMemoryJsonRpcLspClient {
2342
2376
  exportAssertedWorkspace: (options) => exportAssertedWorkspace(context, options),
2343
2377
  };
2344
2378
  const result = await exportWorkspace(context, params);
2345
- return {
2346
- ...result,
2347
- lint,
2348
- };
2379
+ return result;
2349
2380
  }
2350
2381
  async close() {
2351
2382
  this.stopWorkspaceWatcher();
@@ -2459,39 +2490,45 @@ class InMemoryJsonRpcLspClient {
2459
2490
  async renderWorkspace(params) {
2460
2491
  await this.ensureInitialized();
2461
2492
  await this.ensureWorkspaceCurrent();
2462
- const lint = await this.lintWorkspace({
2463
- limit: typeof params.lintLimit === 'number' ? params.lintLimit : undefined,
2464
- });
2493
+ const lint = await this.lintWorkspace({});
2465
2494
  if (lint.errors > 0 || lint.warnings > 0) {
2466
2495
  return {
2467
2496
  success: false,
2468
2497
  filesRendered: 0,
2469
- outputDir: '',
2470
2498
  error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2471
- lint,
2472
2499
  };
2473
2500
  }
2474
2501
  const workspaceRoot = path.resolve(this.workspaceRoot);
2475
- const inputFromOptions = typeof params.inputDir === 'string' && params.inputDir.trim().length > 0
2476
- ? params.inputDir.trim()
2477
- : (typeof params.md === 'string' && params.md.trim().length > 0 ? params.md.trim() : 'src/md');
2478
- const outputFromOptions = typeof params.outputDir === 'string' && params.outputDir.trim().length > 0
2479
- ? params.outputDir.trim()
2480
- : (typeof params.web === 'string' && params.web.trim().length > 0 ? params.web.trim() : 'build/web');
2502
+ const inputFromOptions = typeof params.md === 'string' && params.md.trim().length > 0 ? params.md.trim() : 'src/md';
2503
+ const outputFromOptions = typeof params.web === 'string' && params.web.trim().length > 0 ? params.web.trim() : 'build/web';
2481
2504
  const inputDir = path.resolve(workspaceRoot, inputFromOptions);
2482
2505
  const outputDir = path.resolve(workspaceRoot, outputFromOptions);
2483
- const contextOption = typeof params.contextOntologyIri === 'string' && params.contextOntologyIri.trim().length > 0
2484
- ? params.contextOntologyIri.trim()
2485
- : (typeof params.context === 'string' && params.context.trim().length > 0 ? params.context.trim() : undefined);
2486
- const defaultContextOntologyIri = contextOption
2487
- ? normalizeContextOntologyIri(contextOption)
2488
- : undefined;
2506
+ if (params.clean === true) {
2507
+ await fs.rm(outputDir, { recursive: true, force: true });
2508
+ }
2489
2509
  const runtime = new MarkdownPreviewRuntime(new MarkdownHandlerRegistry());
2490
2510
  const templateCatalog = await buildTemplateCatalog(workspaceRoot);
2491
2511
  const navigationTemplateCatalog = buildNavigationTemplateCatalog(Array.from(templateCatalog.values()).flatMap((entries) => entries.map((entry) => entry.definition)));
2492
2512
  const staticAssets = await writeStaticAssets(outputDir);
2493
2513
  const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
2494
2514
  const ontologyIndex = getOntologyModelIndex(this.runtime.shared);
2515
+ const contextOption = typeof params.context === 'string' && params.context.trim().length > 0
2516
+ ? params.context.trim()
2517
+ : undefined;
2518
+ const defaultContextOntologyIri = contextOption
2519
+ ? (() => {
2520
+ const contextModelPath = path.resolve(workspaceRoot, contextOption);
2521
+ const contextModelUri = pathToFileURL(contextModelPath).toString();
2522
+ return ontologyIndex.resolveOntologyIri(contextModelUri);
2523
+ })()
2524
+ : undefined;
2525
+ if (contextOption && !defaultContextOntologyIri) {
2526
+ return {
2527
+ success: false,
2528
+ filesRendered: 0,
2529
+ error: `Unable to resolve ontology IRI for context model path '${contextOption}'.`,
2530
+ };
2531
+ }
2495
2532
  const executor = new MarkdownExecutor({
2496
2533
  ensureContext: (modelUri) => reasoningService.ensureQueryContext(modelUri),
2497
2534
  resolveContextIri: (modelUri) => reasoningService.getContextIri(modelUri),
@@ -2707,7 +2744,7 @@ class InMemoryJsonRpcLspClient {
2707
2744
  }
2708
2745
  }
2709
2746
  }
2710
- return { success: true, filesRendered, outputDir, blockArtifactFiles, lint };
2747
+ return { success: true, filesRendered, outputDir, blockArtifactFiles };
2711
2748
  }
2712
2749
  async ensureWorkspaceCurrent() {
2713
2750
  debugRest('workspace.current.begin', {
@@ -2835,6 +2872,31 @@ class InMemoryJsonRpcLspClient {
2835
2872
  await this.initPromise;
2836
2873
  }
2837
2874
  }
2875
+ export async function runOmlWorkspaceLocalOperation(options) {
2876
+ const client = new InMemoryJsonRpcLspClient(options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd(), options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, options.watchWorkspace === true, options.runtime);
2877
+ try {
2878
+ const params = options.params ?? {};
2879
+ switch (options.operation) {
2880
+ case 'lint':
2881
+ return await client.lintWorkspace(params);
2882
+ case 'validate':
2883
+ return await client.validateWorkspace(params);
2884
+ case 'assertions':
2885
+ return await client.assertionsWorkspace(params);
2886
+ case 'reason':
2887
+ return await client.reasonWorkspace(params);
2888
+ case 'export':
2889
+ return await client.exportWorkspace(params);
2890
+ case 'render':
2891
+ return await client.renderWorkspace(params);
2892
+ default:
2893
+ throw new Error(`Unsupported local operation: ${String(options.operation)}`);
2894
+ }
2895
+ }
2896
+ finally {
2897
+ await client.close();
2898
+ }
2899
+ }
2838
2900
  export async function startOmlRestServer(options) {
2839
2901
  const workspaceRoot = options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd();
2840
2902
  debugRest('rest.start.options', {