@oml/server 0.14.13 → 0.14.15

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' | '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';
@@ -23,7 +20,7 @@ import { exportAssertedWorkspace, exportWorkspace } from './export.js';
23
20
  import { startOmlLanguageServer } from '../lsp/language-server.js';
24
21
  import { createOpenApiSpec, dispatchRestOperation, resolveRestOperationId } from './routes.js';
25
22
  import { buildTemplateCatalog, expandTemplateComposeBlocks, findFilesByExtension, frontMatterString, isTemplateMarkdownFile, normalizeContextOntologyIri, } from './template.js';
26
- import { lintWorkspace, reasonWorkspace, validateWorkspace } from './validation.js';
23
+ import { lintWorkspace, validateWorkspace } from './validation.js';
27
24
  import { OmlAccessError, requiredFeatureForRestOperation, } from '../auth/feature-policy.js';
28
25
  const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
29
26
  const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
@@ -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);
@@ -2070,18 +2057,15 @@ class InMemoryJsonRpcLspClient {
2070
2057
  ensureInitialized: () => this.ensureInitialized(),
2071
2058
  ensureWorkspaceCurrent: () => this.ensureWorkspaceCurrent(),
2072
2059
  getWorkspaceOmlDocuments: () => this.getWorkspaceOmlDocuments(),
2073
- writeWorkspaceAssertedOwl: (outputDir, format, pretty) => this.writeWorkspaceAssertedOwl(outputDir, format, pretty),
2074
2060
  };
2075
- const result = await lintWorkspace(context, params);
2061
+ const result = await lintWorkspace(context);
2076
2062
  const postDocs = this.getWorkspaceOmlDocuments();
2077
2063
  const postStates = this.summarizeDocumentStates(postDocs);
2078
2064
  debugRest('lint.end', {
2079
2065
  filesChecked: result.filesChecked,
2080
2066
  errors: result.errors,
2081
2067
  warnings: result.warnings,
2082
- totalProblems: result.totalProblems,
2083
- returnedProblems: result.returnedProblems,
2084
- truncated: result.truncated,
2068
+ problems: result.problems.length,
2085
2069
  elapsedMs: result.elapsedMs,
2086
2070
  stateValidated: postStates.validated,
2087
2071
  stateLinked: postStates.linked,
@@ -2177,6 +2161,12 @@ class InMemoryJsonRpcLspClient {
2177
2161
  async updateWorkspace(params) {
2178
2162
  await this.ensureInitialized();
2179
2163
  await this.ensureWorkspaceCurrent();
2164
+ const lint = await this.lintWorkspace({});
2165
+ if (lint.errors > 0 || lint.warnings > 0) {
2166
+ return {
2167
+ error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2168
+ };
2169
+ }
2180
2170
  return await applyOmlUpdate(this.runtime.shared, params, (message) => this.logError(message));
2181
2171
  }
2182
2172
  async fuzzySearchWorkspace(params) {
@@ -2278,6 +2268,52 @@ class InMemoryJsonRpcLspClient {
2278
2268
  };
2279
2269
  }
2280
2270
  }
2271
+ async assertionsWorkspace(params) {
2272
+ await this.ensureInitialized();
2273
+ await this.ensureWorkspaceCurrent();
2274
+ const lint = await this.lintWorkspace({});
2275
+ if (lint.errors > 0 || lint.warnings > 0) {
2276
+ return {
2277
+ success: false,
2278
+ files: [],
2279
+ error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2280
+ };
2281
+ }
2282
+ const modelUriFilter = typeof params.modelUri === 'string' ? params.modelUri.trim() : '';
2283
+ const docs = this.getWorkspaceOmlDocuments();
2284
+ const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
2285
+ const store = reasoningService.getStore().getStore();
2286
+ const files = [];
2287
+ for (const doc of docs) {
2288
+ const modelUri = String(doc?.uri ?? '').trim();
2289
+ if (modelUriFilter.length > 0 && modelUri !== modelUriFilter) {
2290
+ continue;
2291
+ }
2292
+ const root = doc?.parseResult?.value;
2293
+ const ontologyIri = normalizeOntologyNamespace(root?.namespace)?.replace(/[\/#]+$/, '');
2294
+ if (!ontologyIri || BUILT_IN_ONTOLOGIES.has(ontologyIri)) {
2295
+ continue;
2296
+ }
2297
+ await reasoningService.ensureQueryContext(modelUri);
2298
+ const quads = store.getQuads(null, null, null, DataFactory.namedNode(modelUri))
2299
+ .map((quad) => ({
2300
+ subject: quad.subject,
2301
+ predicate: quad.predicate,
2302
+ object: quad.object,
2303
+ }));
2304
+ files.push({
2305
+ modelUri,
2306
+ ontologyIri,
2307
+ path: resolveOutputPathFromOntologyIriString(ontologyIri, 'nt').split(path.sep).join('/'),
2308
+ content: await serializeQuads(quads, 'nt', false),
2309
+ });
2310
+ }
2311
+ files.sort((left, right) => left.ontologyIri.localeCompare(right.ontologyIri));
2312
+ return {
2313
+ success: true,
2314
+ files,
2315
+ };
2316
+ }
2281
2317
  async writeWorkspaceAssertedOwl(outputDir, format, pretty) {
2282
2318
  const entries = [];
2283
2319
  const docs = this.getWorkspaceOmlDocuments();
@@ -2301,17 +2337,6 @@ class InMemoryJsonRpcLspClient {
2301
2337
  entries.sort((left, right) => left.ontologyIri.localeCompare(right.ontologyIri));
2302
2338
  return entries;
2303
2339
  }
2304
- async reasonWorkspace(params) {
2305
- const context = {
2306
- workspaceRoot: this.workspaceRoot,
2307
- runtime: this.runtime,
2308
- ensureInitialized: () => this.ensureInitialized(),
2309
- ensureWorkspaceCurrent: () => this.ensureWorkspaceCurrent(),
2310
- getWorkspaceOmlDocuments: () => this.getWorkspaceOmlDocuments(),
2311
- writeWorkspaceAssertedOwl: (outputDir, format, pretty) => this.writeWorkspaceAssertedOwl(outputDir, format, pretty),
2312
- };
2313
- return await reasonWorkspace(context, params);
2314
- }
2315
2340
  async validateWorkspace(params) {
2316
2341
  const context = {
2317
2342
  workspaceRoot: this.workspaceRoot,
@@ -2319,19 +2344,15 @@ class InMemoryJsonRpcLspClient {
2319
2344
  ensureInitialized: () => this.ensureInitialized(),
2320
2345
  ensureWorkspaceCurrent: () => this.ensureWorkspaceCurrent(),
2321
2346
  getWorkspaceOmlDocuments: () => this.getWorkspaceOmlDocuments(),
2322
- writeWorkspaceAssertedOwl: (outputDir, format, pretty) => this.writeWorkspaceAssertedOwl(outputDir, format, pretty),
2323
2347
  };
2324
2348
  return await validateWorkspace(context, params);
2325
2349
  }
2326
2350
  async exportWorkspace(params) {
2327
- const lint = await this.lintWorkspace({
2328
- limit: typeof params.lintLimit === 'number' ? params.lintLimit : undefined,
2329
- });
2351
+ const lint = await this.lintWorkspace({});
2330
2352
  if (lint.errors > 0 || lint.warnings > 0) {
2331
2353
  return {
2332
2354
  success: false,
2333
2355
  error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2334
- lint,
2335
2356
  };
2336
2357
  }
2337
2358
  const context = {
@@ -2342,10 +2363,7 @@ class InMemoryJsonRpcLspClient {
2342
2363
  exportAssertedWorkspace: (options) => exportAssertedWorkspace(context, options),
2343
2364
  };
2344
2365
  const result = await exportWorkspace(context, params);
2345
- return {
2346
- ...result,
2347
- lint,
2348
- };
2366
+ return result;
2349
2367
  }
2350
2368
  async close() {
2351
2369
  this.stopWorkspaceWatcher();
@@ -2459,39 +2477,45 @@ class InMemoryJsonRpcLspClient {
2459
2477
  async renderWorkspace(params) {
2460
2478
  await this.ensureInitialized();
2461
2479
  await this.ensureWorkspaceCurrent();
2462
- const lint = await this.lintWorkspace({
2463
- limit: typeof params.lintLimit === 'number' ? params.lintLimit : undefined,
2464
- });
2480
+ const lint = await this.lintWorkspace({});
2465
2481
  if (lint.errors > 0 || lint.warnings > 0) {
2466
2482
  return {
2467
2483
  success: false,
2468
2484
  filesRendered: 0,
2469
- outputDir: '',
2470
2485
  error: `lint failed with ${lint.errors} error(s) and ${lint.warnings} warning(s).`,
2471
- lint,
2472
2486
  };
2473
2487
  }
2474
2488
  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');
2489
+ const inputFromOptions = typeof params.md === 'string' && params.md.trim().length > 0 ? params.md.trim() : 'src/md';
2490
+ const outputFromOptions = typeof params.web === 'string' && params.web.trim().length > 0 ? params.web.trim() : 'build/web';
2481
2491
  const inputDir = path.resolve(workspaceRoot, inputFromOptions);
2482
2492
  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;
2493
+ if (params.clean === true) {
2494
+ await fs.rm(outputDir, { recursive: true, force: true });
2495
+ }
2489
2496
  const runtime = new MarkdownPreviewRuntime(new MarkdownHandlerRegistry());
2490
2497
  const templateCatalog = await buildTemplateCatalog(workspaceRoot);
2491
2498
  const navigationTemplateCatalog = buildNavigationTemplateCatalog(Array.from(templateCatalog.values()).flatMap((entries) => entries.map((entry) => entry.definition)));
2492
2499
  const staticAssets = await writeStaticAssets(outputDir);
2493
2500
  const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
2494
2501
  const ontologyIndex = getOntologyModelIndex(this.runtime.shared);
2502
+ const contextOption = typeof params.context === 'string' && params.context.trim().length > 0
2503
+ ? params.context.trim()
2504
+ : undefined;
2505
+ const defaultContextOntologyIri = contextOption
2506
+ ? (() => {
2507
+ const contextModelPath = path.resolve(workspaceRoot, contextOption);
2508
+ const contextModelUri = pathToFileURL(contextModelPath).toString();
2509
+ return ontologyIndex.resolveOntologyIri(contextModelUri);
2510
+ })()
2511
+ : undefined;
2512
+ if (contextOption && !defaultContextOntologyIri) {
2513
+ return {
2514
+ success: false,
2515
+ filesRendered: 0,
2516
+ error: `Unable to resolve ontology IRI for context model path '${contextOption}'.`,
2517
+ };
2518
+ }
2495
2519
  const executor = new MarkdownExecutor({
2496
2520
  ensureContext: (modelUri) => reasoningService.ensureQueryContext(modelUri),
2497
2521
  resolveContextIri: (modelUri) => reasoningService.getContextIri(modelUri),
@@ -2707,7 +2731,7 @@ class InMemoryJsonRpcLspClient {
2707
2731
  }
2708
2732
  }
2709
2733
  }
2710
- return { success: true, filesRendered, outputDir, blockArtifactFiles, lint };
2734
+ return { success: true, filesRendered, outputDir, blockArtifactFiles };
2711
2735
  }
2712
2736
  async ensureWorkspaceCurrent() {
2713
2737
  debugRest('workspace.current.begin', {
@@ -2835,6 +2859,29 @@ class InMemoryJsonRpcLspClient {
2835
2859
  await this.initPromise;
2836
2860
  }
2837
2861
  }
2862
+ export async function runOmlWorkspaceLocalOperation(options) {
2863
+ const client = new InMemoryJsonRpcLspClient(options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd(), options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, options.watchWorkspace === true, options.runtime);
2864
+ try {
2865
+ const params = options.params ?? {};
2866
+ switch (options.operation) {
2867
+ case 'lint':
2868
+ return await client.lintWorkspace(params);
2869
+ case 'validate':
2870
+ return await client.validateWorkspace(params);
2871
+ case 'assertions':
2872
+ return await client.assertionsWorkspace(params);
2873
+ case 'export':
2874
+ return await client.exportWorkspace(params);
2875
+ case 'render':
2876
+ return await client.renderWorkspace(params);
2877
+ default:
2878
+ throw new Error(`Unsupported local operation: ${String(options.operation)}`);
2879
+ }
2880
+ }
2881
+ finally {
2882
+ await client.close();
2883
+ }
2884
+ }
2838
2885
  export async function startOmlRestServer(options) {
2839
2886
  const workspaceRoot = options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd();
2840
2887
  debugRest('rest.start.options', {