ont-run 0.0.3 → 0.0.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.
- package/README.md +77 -6
- package/dist/bin/ont.js +303 -10
- package/dist/index.js +21 -56
- package/dist/src/browser/server.d.ts +2 -0
- package/dist/src/browser/transform.d.ts +1 -1
- package/dist/src/config/categorical.d.ts +3 -0
- package/dist/src/config/define.d.ts +37 -2
- package/dist/src/config/schema.d.ts +8 -8
- package/dist/src/config/types.d.ts +27 -27
- package/dist/src/index.d.ts +3 -2
- package/dist/src/server/api/index.d.ts +0 -2
- package/dist/src/server/api/router.d.ts +1 -1
- package/dist/src/server/mcp/index.d.ts +0 -2
- package/dist/src/server/mcp/tools.d.ts +2 -2
- package/dist/src/server/resolver.d.ts +4 -15
- package/package.json +1 -1
- package/src/browser/server.ts +234 -2
- package/src/browser/transform.ts +6 -2
- package/src/cli/commands/review.ts +2 -1
- package/src/cli/index.ts +2 -1
- package/src/config/categorical.ts +7 -2
- package/src/config/define.ts +49 -1
- package/src/config/schema.ts +1 -1
- package/src/config/types.ts +33 -31
- package/src/index.ts +3 -2
- package/src/server/api/index.ts +2 -15
- package/src/server/api/router.ts +3 -6
- package/src/server/mcp/index.ts +2 -4
- package/src/server/mcp/tools.ts +3 -5
- package/src/server/resolver.ts +5 -78
- package/src/server/start.ts +0 -2
package/src/browser/server.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { basename } from "path";
|
|
2
4
|
import open from "open";
|
|
3
5
|
import type { OntologyConfig } from "../config/types.js";
|
|
4
6
|
import type { OntologyDiff } from "../lockfile/types.js";
|
|
@@ -12,6 +14,8 @@ export interface BrowserServerOptions {
|
|
|
12
14
|
diff?: OntologyDiff | null;
|
|
13
15
|
/** Directory to write the lockfile to on approval */
|
|
14
16
|
configDir?: string;
|
|
17
|
+
/** Path to the ontology.config.ts file */
|
|
18
|
+
configPath?: string;
|
|
15
19
|
port?: number;
|
|
16
20
|
openBrowser?: boolean;
|
|
17
21
|
}
|
|
@@ -22,7 +26,7 @@ export interface BrowserServerResult {
|
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export async function startBrowserServer(options: BrowserServerOptions): Promise<BrowserServerResult> {
|
|
25
|
-
const { config, diff = null, configDir, port: preferredPort, openBrowser = true } = options;
|
|
29
|
+
const { config, diff = null, configDir, configPath, port: preferredPort, openBrowser = true } = options;
|
|
26
30
|
|
|
27
31
|
// Transform config to graph data and enhance with diff info
|
|
28
32
|
const baseGraphData = transformToGraphData(config);
|
|
@@ -90,6 +94,26 @@ export async function startBrowserServer(options: BrowserServerOptions): Promise
|
|
|
90
94
|
return c.json({ success: true });
|
|
91
95
|
});
|
|
92
96
|
|
|
97
|
+
// API: Get raw TypeScript source
|
|
98
|
+
app.get("/api/source", (c) => {
|
|
99
|
+
if (!configPath) {
|
|
100
|
+
return c.json({ error: "Config path not available" }, 400);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const source = readFileSync(configPath, "utf-8");
|
|
104
|
+
const filename = basename(configPath);
|
|
105
|
+
return c.json({ source, filename, path: configPath });
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return c.json(
|
|
108
|
+
{
|
|
109
|
+
error: "Failed to read config file",
|
|
110
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
111
|
+
},
|
|
112
|
+
500
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
93
117
|
// Serve UI
|
|
94
118
|
app.get("/", (c) => c.html(generateBrowserUI(graphData)));
|
|
95
119
|
|
|
@@ -122,6 +146,12 @@ export async function startBrowserServer(options: BrowserServerOptions): Promise
|
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
149
|
+
const userContextFilterBtn = graphData.meta.totalUserContextFunctions > 0
|
|
150
|
+
? `<button class="filter-btn" data-filter="userContext" title="Functions using userContext()">
|
|
151
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>User Context (${graphData.meta.totalUserContextFunctions})
|
|
152
|
+
</button>`
|
|
153
|
+
: '';
|
|
154
|
+
|
|
125
155
|
return `<!DOCTYPE html>
|
|
126
156
|
<html lang="en">
|
|
127
157
|
<head>
|
|
@@ -699,6 +729,89 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
699
729
|
color: var(--change-added);
|
|
700
730
|
}
|
|
701
731
|
|
|
732
|
+
/* Source View */
|
|
733
|
+
.source-view {
|
|
734
|
+
display: none;
|
|
735
|
+
grid-column: 2 / 4;
|
|
736
|
+
padding: 24px;
|
|
737
|
+
overflow-y: auto;
|
|
738
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(231, 225, 207, 0.3));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.source-view.active {
|
|
742
|
+
display: flex;
|
|
743
|
+
flex-direction: column;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.source-header {
|
|
747
|
+
display: flex;
|
|
748
|
+
align-items: center;
|
|
749
|
+
justify-content: space-between;
|
|
750
|
+
padding: 12px 20px;
|
|
751
|
+
background: rgba(2, 61, 96, 0.95);
|
|
752
|
+
border-radius: 12px 12px 0 0;
|
|
753
|
+
color: white;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.source-filename {
|
|
757
|
+
font-family: 'Space Mono', monospace;
|
|
758
|
+
font-size: 13px;
|
|
759
|
+
font-weight: 500;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.copy-btn {
|
|
763
|
+
display: flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
gap: 6px;
|
|
766
|
+
padding: 6px 12px;
|
|
767
|
+
background: rgba(255, 255, 255, 0.1);
|
|
768
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
769
|
+
border-radius: 6px;
|
|
770
|
+
color: white;
|
|
771
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
772
|
+
font-size: 12px;
|
|
773
|
+
cursor: pointer;
|
|
774
|
+
transition: all 0.2s ease;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.copy-btn:hover {
|
|
778
|
+
background: rgba(255, 255, 255, 0.2);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.copy-btn.copied {
|
|
782
|
+
background: rgba(21, 168, 168, 0.3);
|
|
783
|
+
border-color: var(--vanna-teal);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.source-code {
|
|
787
|
+
flex: 1;
|
|
788
|
+
margin: 0;
|
|
789
|
+
padding: 20px;
|
|
790
|
+
background: #1e1e1e;
|
|
791
|
+
border-radius: 0 0 12px 12px;
|
|
792
|
+
overflow: auto;
|
|
793
|
+
font-family: 'Space Mono', monospace;
|
|
794
|
+
font-size: 13px;
|
|
795
|
+
line-height: 1.6;
|
|
796
|
+
color: #d4d4d4;
|
|
797
|
+
tab-size: 2;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.source-code code {
|
|
801
|
+
display: block;
|
|
802
|
+
white-space: pre;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* Syntax highlighting classes */
|
|
806
|
+
.source-code .keyword { color: #569cd6; }
|
|
807
|
+
.source-code .string { color: #ce9178; }
|
|
808
|
+
.source-code .number { color: #b5cea8; }
|
|
809
|
+
.source-code .comment { color: #6a9955; }
|
|
810
|
+
.source-code .function { color: #dcdcaa; }
|
|
811
|
+
.source-code .type { color: #4ec9b0; }
|
|
812
|
+
.source-code .property { color: #9cdcfe; }
|
|
813
|
+
.source-code .punctuation { color: #d4d4d4; }
|
|
814
|
+
|
|
702
815
|
/* No Changes State */
|
|
703
816
|
.no-changes {
|
|
704
817
|
text-align: center;
|
|
@@ -1427,6 +1540,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1427
1540
|
<div class="view-tabs">
|
|
1428
1541
|
<button class="view-tab active" data-view="graph">Graph</button>
|
|
1429
1542
|
<button class="view-tab" data-view="table">Table</button>
|
|
1543
|
+
<button class="view-tab" data-view="source">Source</button>
|
|
1430
1544
|
</div>
|
|
1431
1545
|
|
|
1432
1546
|
<div class="filter-buttons" id="graphFilters">
|
|
@@ -1440,6 +1554,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1440
1554
|
<button class="filter-btn" data-filter="accessGroup">
|
|
1441
1555
|
<span class="dot access"></span> Access
|
|
1442
1556
|
</button>
|
|
1557
|
+
${userContextFilterBtn}
|
|
1443
1558
|
</div>
|
|
1444
1559
|
|
|
1445
1560
|
<div class="layout-selector">
|
|
@@ -1546,6 +1661,20 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1546
1661
|
<div class="table-view" id="tableView">
|
|
1547
1662
|
<div id="tableContent"></div>
|
|
1548
1663
|
</div>
|
|
1664
|
+
|
|
1665
|
+
<!-- Source View -->
|
|
1666
|
+
<div class="source-view" id="sourceView">
|
|
1667
|
+
<div class="source-header">
|
|
1668
|
+
<span class="source-filename" id="sourceFilename">ontology.config.ts</span>
|
|
1669
|
+
<button class="copy-btn" id="copySourceBtn" title="Copy to clipboard">
|
|
1670
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1671
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
1672
|
+
</svg>
|
|
1673
|
+
Copy
|
|
1674
|
+
</button>
|
|
1675
|
+
</div>
|
|
1676
|
+
<pre class="source-code" id="sourceCode"><code>Loading...</code></pre>
|
|
1677
|
+
</div>
|
|
1549
1678
|
</div>
|
|
1550
1679
|
|
|
1551
1680
|
<!-- Review Footer -->
|
|
@@ -1584,6 +1713,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1584
1713
|
metadata: node.metadata,
|
|
1585
1714
|
changeStatus: node.changeStatus || 'unchanged',
|
|
1586
1715
|
changeDetails: node.changeDetails || null,
|
|
1716
|
+
usesUserContext: node.metadata?.usesUserContext || false,
|
|
1587
1717
|
},
|
|
1588
1718
|
});
|
|
1589
1719
|
}
|
|
@@ -1636,6 +1766,19 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1636
1766
|
'height': 55,
|
|
1637
1767
|
},
|
|
1638
1768
|
},
|
|
1769
|
+
// Function nodes with userContext - show indicator below label
|
|
1770
|
+
{
|
|
1771
|
+
selector: 'node[type="function"][?usesUserContext]',
|
|
1772
|
+
style: {
|
|
1773
|
+
'background-image': 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="14" fill="#e8f4f8" stroke="#023d60" stroke-width="1.5"/><g transform="translate(4, 4)" fill="none" stroke="#023d60" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></g></svg>'),
|
|
1774
|
+
'background-width': '18px',
|
|
1775
|
+
'background-height': '18px',
|
|
1776
|
+
'background-position-x': '50%',
|
|
1777
|
+
'background-position-y': '75%',
|
|
1778
|
+
'text-valign': 'center',
|
|
1779
|
+
'text-margin-y': -8,
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1639
1782
|
// Entity nodes - Teal
|
|
1640
1783
|
{
|
|
1641
1784
|
selector: 'node[type="entity"]',
|
|
@@ -2177,6 +2320,16 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
2177
2320
|
if (filter === 'all') {
|
|
2178
2321
|
cy.nodes().removeClass('hidden');
|
|
2179
2322
|
cy.edges().removeClass('hidden');
|
|
2323
|
+
} else if (filter === 'userContext') {
|
|
2324
|
+
// Special filter: show only functions with userContext
|
|
2325
|
+
cy.nodes().forEach(node => {
|
|
2326
|
+
if (node.data('type') === 'function' && node.data('usesUserContext')) {
|
|
2327
|
+
node.removeClass('hidden');
|
|
2328
|
+
} else {
|
|
2329
|
+
node.addClass('hidden');
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
cy.edges().addClass('hidden');
|
|
2180
2333
|
} else {
|
|
2181
2334
|
cy.nodes().forEach(node => {
|
|
2182
2335
|
if (node.data('type') === filter) {
|
|
@@ -2398,6 +2551,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
2398
2551
|
const graphContainer = document.querySelector('.graph-container');
|
|
2399
2552
|
const detailPanel = document.getElementById('detailPanel');
|
|
2400
2553
|
const tableView = document.getElementById('tableView');
|
|
2554
|
+
const sourceView = document.getElementById('sourceView');
|
|
2401
2555
|
const graphFilters = document.getElementById('graphFilters');
|
|
2402
2556
|
const layoutSelector = document.querySelector('.layout-selector');
|
|
2403
2557
|
|
|
@@ -2405,15 +2559,25 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
2405
2559
|
graphContainer.style.display = 'block';
|
|
2406
2560
|
detailPanel.style.display = 'block';
|
|
2407
2561
|
tableView.classList.remove('active');
|
|
2562
|
+
sourceView.classList.remove('active');
|
|
2408
2563
|
if (graphFilters) graphFilters.style.display = 'flex';
|
|
2409
2564
|
if (layoutSelector) layoutSelector.style.display = 'flex';
|
|
2410
|
-
} else {
|
|
2565
|
+
} else if (view === 'table') {
|
|
2411
2566
|
graphContainer.style.display = 'none';
|
|
2412
2567
|
detailPanel.style.display = 'none';
|
|
2413
2568
|
tableView.classList.add('active');
|
|
2569
|
+
sourceView.classList.remove('active');
|
|
2414
2570
|
if (graphFilters) graphFilters.style.display = 'none';
|
|
2415
2571
|
if (layoutSelector) layoutSelector.style.display = 'none';
|
|
2416
2572
|
renderTableView();
|
|
2573
|
+
} else if (view === 'source') {
|
|
2574
|
+
graphContainer.style.display = 'none';
|
|
2575
|
+
detailPanel.style.display = 'none';
|
|
2576
|
+
tableView.classList.remove('active');
|
|
2577
|
+
sourceView.classList.add('active');
|
|
2578
|
+
if (graphFilters) graphFilters.style.display = 'none';
|
|
2579
|
+
if (layoutSelector) layoutSelector.style.display = 'none';
|
|
2580
|
+
loadSourceView();
|
|
2417
2581
|
}
|
|
2418
2582
|
}
|
|
2419
2583
|
|
|
@@ -2455,6 +2619,74 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
2455
2619
|
});
|
|
2456
2620
|
}
|
|
2457
2621
|
|
|
2622
|
+
// Source view
|
|
2623
|
+
let sourceLoaded = false;
|
|
2624
|
+
let sourceContent = '';
|
|
2625
|
+
|
|
2626
|
+
async function loadSourceView() {
|
|
2627
|
+
if (sourceLoaded) return;
|
|
2628
|
+
|
|
2629
|
+
const codeEl = document.getElementById('sourceCode').querySelector('code');
|
|
2630
|
+
const filenameEl = document.getElementById('sourceFilename');
|
|
2631
|
+
|
|
2632
|
+
try {
|
|
2633
|
+
const res = await fetch('/api/source');
|
|
2634
|
+
if (!res.ok) throw new Error('Failed to load source');
|
|
2635
|
+
const data = await res.json();
|
|
2636
|
+
|
|
2637
|
+
sourceContent = data.source;
|
|
2638
|
+
filenameEl.textContent = data.filename;
|
|
2639
|
+
codeEl.innerHTML = highlightTypeScript(data.source);
|
|
2640
|
+
sourceLoaded = true;
|
|
2641
|
+
} catch (err) {
|
|
2642
|
+
codeEl.textContent = 'Error loading source: ' + err.message;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
function highlightTypeScript(code) {
|
|
2647
|
+
// Escape HTML first
|
|
2648
|
+
code = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
2649
|
+
|
|
2650
|
+
// Comments (single and multi-line)
|
|
2651
|
+
code = code.replace(/(\\/\\/.*$)/gm, '<span class="comment">$1</span>');
|
|
2652
|
+
code = code.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g, '<span class="comment">$1</span>');
|
|
2653
|
+
|
|
2654
|
+
// Strings (double, single, and template)
|
|
2655
|
+
code = code.replace(/("(?:[^"\\\\]|\\\\.)*")/g, '<span class="string">$1</span>');
|
|
2656
|
+
code = code.replace(/('(?:[^'\\\\]|\\\\.)*')/g, '<span class="string">$1</span>');
|
|
2657
|
+
code = code.replace(/(\`(?:[^\`\\\\]|\\\\.)*\`)/g, '<span class="string">$1</span>');
|
|
2658
|
+
|
|
2659
|
+
// Keywords
|
|
2660
|
+
const keywords = ['import', 'export', 'from', 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'class', 'extends', 'new', 'this', 'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'async', 'await', 'default', 'as', 'type', 'interface'];
|
|
2661
|
+
keywords.forEach(kw => {
|
|
2662
|
+
code = code.replace(new RegExp('\\\\b(' + kw + ')\\\\b', 'g'), '<span class="keyword">$1</span>');
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2665
|
+
// Numbers
|
|
2666
|
+
code = code.replace(/\\b(\\d+\\.?\\d*)\\b/g, '<span class="number">$1</span>');
|
|
2667
|
+
|
|
2668
|
+
// Function calls
|
|
2669
|
+
code = code.replace(/\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(/g, '<span class="function">$1</span>(');
|
|
2670
|
+
|
|
2671
|
+
return code;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// Copy source button
|
|
2675
|
+
document.getElementById('copySourceBtn').addEventListener('click', async () => {
|
|
2676
|
+
const btn = document.getElementById('copySourceBtn');
|
|
2677
|
+
try {
|
|
2678
|
+
await navigator.clipboard.writeText(sourceContent);
|
|
2679
|
+
btn.classList.add('copied');
|
|
2680
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg> Copied!';
|
|
2681
|
+
setTimeout(() => {
|
|
2682
|
+
btn.classList.remove('copied');
|
|
2683
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy';
|
|
2684
|
+
}, 2000);
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
console.error('Failed to copy:', err);
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2458
2690
|
function renderTableSection(title, items, type) {
|
|
2459
2691
|
const changedCount = items.filter(n => n.changeStatus !== 'unchanged').length;
|
|
2460
2692
|
|
package/src/browser/transform.ts
CHANGED
|
@@ -16,7 +16,6 @@ export interface GraphNode {
|
|
|
16
16
|
metadata: {
|
|
17
17
|
inputs?: Record<string, unknown>;
|
|
18
18
|
outputs?: Record<string, unknown>;
|
|
19
|
-
resolver?: string;
|
|
20
19
|
functionCount?: number;
|
|
21
20
|
usesUserContext?: boolean;
|
|
22
21
|
};
|
|
@@ -38,6 +37,7 @@ export interface GraphData {
|
|
|
38
37
|
totalFunctions: number;
|
|
39
38
|
totalEntities: number;
|
|
40
39
|
totalAccessGroups: number;
|
|
40
|
+
totalUserContextFunctions: number;
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -132,6 +132,7 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
132
132
|
// Track function counts for access groups and entities
|
|
133
133
|
const accessGroupCounts: Record<string, number> = {};
|
|
134
134
|
const entityCounts: Record<string, number> = {};
|
|
135
|
+
let userContextFunctionCount = 0;
|
|
135
136
|
|
|
136
137
|
// Initialize counts
|
|
137
138
|
for (const groupName of Object.keys(config.accessGroups)) {
|
|
@@ -158,6 +159,9 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
158
159
|
// Check if function uses userContext
|
|
159
160
|
const userContextFields = getUserContextFields(fn.inputs);
|
|
160
161
|
const usesUserContext = userContextFields.length > 0;
|
|
162
|
+
if (usesUserContext) {
|
|
163
|
+
userContextFunctionCount++;
|
|
164
|
+
}
|
|
161
165
|
|
|
162
166
|
// Create function node
|
|
163
167
|
nodes.push({
|
|
@@ -168,7 +172,6 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
168
172
|
metadata: {
|
|
169
173
|
inputs: safeZodToJsonSchema(fn.inputs),
|
|
170
174
|
outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
|
|
171
|
-
resolver: fn.resolver,
|
|
172
175
|
usesUserContext: usesUserContext || undefined,
|
|
173
176
|
},
|
|
174
177
|
});
|
|
@@ -242,6 +245,7 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
242
245
|
totalFunctions: Object.keys(config.functions).length,
|
|
243
246
|
totalEntities: config.entities ? Object.keys(config.entities).length : 0,
|
|
244
247
|
totalAccessGroups: Object.keys(config.accessGroups).length,
|
|
248
|
+
totalUserContextFunctions: userContextFunctionCount,
|
|
245
249
|
},
|
|
246
250
|
};
|
|
247
251
|
}
|
|
@@ -48,7 +48,7 @@ export const reviewCommand = defineCommand({
|
|
|
48
48
|
|
|
49
49
|
// Load config
|
|
50
50
|
consola.info("Loading ontology config...");
|
|
51
|
-
const { config, configDir } = await loadConfig();
|
|
51
|
+
const { config, configDir, configPath } = await loadConfig();
|
|
52
52
|
|
|
53
53
|
// Compute current ontology snapshot
|
|
54
54
|
const { ontology: newOntology, hash: newHash } = computeOntologyHash(config);
|
|
@@ -107,6 +107,7 @@ export const reviewCommand = defineCommand({
|
|
|
107
107
|
config,
|
|
108
108
|
diff: diff.hasChanges ? diff : null,
|
|
109
109
|
configDir,
|
|
110
|
+
configPath,
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
if (diff.hasChanges) {
|
package/src/cli/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { defineCommand, runMain } from "citty";
|
|
2
2
|
import { initCommand } from "./commands/init.js";
|
|
3
3
|
import { reviewCommand } from "./commands/review.js";
|
|
4
|
+
import pkg from "../../package.json";
|
|
4
5
|
|
|
5
6
|
const main = defineCommand({
|
|
6
7
|
meta: {
|
|
7
8
|
name: "ont",
|
|
8
9
|
description: "Ontology - Ontology-first backends with human-approved AI access & edits",
|
|
9
|
-
version:
|
|
10
|
+
version: pkg.version,
|
|
10
11
|
},
|
|
11
12
|
subCommands: {
|
|
12
13
|
init: initCommand,
|
|
@@ -174,12 +174,17 @@ export function hasUserContextMetadata(
|
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
176
|
* Get all userContext field names from a Zod object schema
|
|
177
|
+
*
|
|
178
|
+
* Note: Uses _def.typeName check instead of instanceof to work across
|
|
179
|
+
* module boundaries in bundled CLI.
|
|
177
180
|
*/
|
|
178
181
|
export function getUserContextFields(schema: z.ZodType): string[] {
|
|
179
182
|
const fields: string[] = [];
|
|
180
183
|
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
// Use _def.typeName check for bundler compatibility (instanceof fails across module boundaries)
|
|
185
|
+
const def = (schema as unknown as { _def?: { typeName?: string; shape?: () => Record<string, unknown> } })._def;
|
|
186
|
+
if (def?.typeName === "ZodObject" && typeof def.shape === "function") {
|
|
187
|
+
const shape = def.shape();
|
|
183
188
|
for (const [key, value] of Object.entries(shape)) {
|
|
184
189
|
if (hasUserContextMetadata(value)) {
|
|
185
190
|
fields.push(key);
|
package/src/config/define.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import {
|
|
2
3
|
OntologyConfigSchema,
|
|
3
4
|
validateAccessGroups,
|
|
@@ -11,6 +12,7 @@ import type {
|
|
|
11
12
|
EnvironmentConfig,
|
|
12
13
|
EntityDefinition,
|
|
13
14
|
AuthFunction,
|
|
15
|
+
ResolverFunction,
|
|
14
16
|
} from "./types.js";
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -20,6 +22,7 @@ import type {
|
|
|
20
22
|
* ```ts
|
|
21
23
|
* import { defineOntology, fieldFrom } from 'ont-run';
|
|
22
24
|
* import { z } from 'zod';
|
|
25
|
+
* import { getUser } from './resolvers/getUser.js';
|
|
23
26
|
*
|
|
24
27
|
* export default defineOntology({
|
|
25
28
|
* name: 'my-api',
|
|
@@ -44,7 +47,7 @@ import type {
|
|
|
44
47
|
* access: ['public', 'admin'],
|
|
45
48
|
* entities: ['User'],
|
|
46
49
|
* inputs: z.object({ id: z.string() }),
|
|
47
|
-
* resolver:
|
|
50
|
+
* resolver: getUser, // Direct function reference for type safety
|
|
48
51
|
* },
|
|
49
52
|
* },
|
|
50
53
|
* });
|
|
@@ -76,3 +79,48 @@ export function defineOntology<
|
|
|
76
79
|
|
|
77
80
|
return config as OntologyConfig<TGroups, TEntities, TFunctions>;
|
|
78
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Define a function with full type inference for resolver type safety.
|
|
85
|
+
*
|
|
86
|
+
* This helper ensures that the resolver function's return type matches
|
|
87
|
+
* the outputs Zod schema at compile time.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { defineFunction, z } from 'ont-run';
|
|
92
|
+
* import type { ResolverContext } from 'ont-run';
|
|
93
|
+
*
|
|
94
|
+
* const getUser = defineFunction({
|
|
95
|
+
* description: 'Get a user by ID',
|
|
96
|
+
* access: ['public', 'admin'] as const,
|
|
97
|
+
* entities: ['User'] as const,
|
|
98
|
+
* inputs: z.object({ id: z.string() }),
|
|
99
|
+
* outputs: z.object({ id: z.string(), name: z.string() }),
|
|
100
|
+
* resolver: async (ctx, args) => {
|
|
101
|
+
* // TypeScript knows args is { id: string }
|
|
102
|
+
* // TypeScript enforces return type is { id: string, name: string }
|
|
103
|
+
* return { id: args.id, name: 'Example User' };
|
|
104
|
+
* },
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function defineFunction<
|
|
109
|
+
TGroups extends string,
|
|
110
|
+
TEntities extends string,
|
|
111
|
+
TInputs extends z.ZodType,
|
|
112
|
+
TOutputs extends z.ZodType,
|
|
113
|
+
>(config: {
|
|
114
|
+
description: string;
|
|
115
|
+
access: readonly TGroups[];
|
|
116
|
+
entities: readonly TEntities[];
|
|
117
|
+
inputs: TInputs;
|
|
118
|
+
outputs?: TOutputs;
|
|
119
|
+
resolver: ResolverFunction<z.infer<TInputs>, z.infer<TOutputs>>;
|
|
120
|
+
}): FunctionDefinition<TGroups, TEntities, TInputs, TOutputs> {
|
|
121
|
+
return {
|
|
122
|
+
...config,
|
|
123
|
+
access: [...config.access],
|
|
124
|
+
entities: [...config.entities],
|
|
125
|
+
};
|
|
126
|
+
}
|
package/src/config/schema.ts
CHANGED
package/src/config/types.ts
CHANGED
|
@@ -37,12 +37,41 @@ export interface FieldOption {
|
|
|
37
37
|
label: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Context passed to resolvers
|
|
42
|
+
*/
|
|
43
|
+
export interface ResolverContext {
|
|
44
|
+
/** Current environment name */
|
|
45
|
+
env: string;
|
|
46
|
+
/** Environment configuration */
|
|
47
|
+
envConfig: EnvironmentConfig;
|
|
48
|
+
/** Logger instance */
|
|
49
|
+
logger: {
|
|
50
|
+
info: (message: string, ...args: unknown[]) => void;
|
|
51
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
52
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
53
|
+
debug: (message: string, ...args: unknown[]) => void;
|
|
54
|
+
};
|
|
55
|
+
/** Access groups for the current request */
|
|
56
|
+
accessGroups: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolver function signature
|
|
61
|
+
*/
|
|
62
|
+
export type ResolverFunction<TArgs = unknown, TResult = unknown> = (
|
|
63
|
+
ctx: ResolverContext,
|
|
64
|
+
args: TArgs
|
|
65
|
+
) => Promise<TResult> | TResult;
|
|
66
|
+
|
|
40
67
|
/**
|
|
41
68
|
* Definition of a function in the ontology
|
|
42
69
|
*/
|
|
43
70
|
export interface FunctionDefinition<
|
|
44
71
|
TGroups extends string = string,
|
|
45
72
|
TEntities extends string = string,
|
|
73
|
+
TInputs extends z.ZodType = z.ZodType<unknown>,
|
|
74
|
+
TOutputs extends z.ZodType = z.ZodType<unknown>,
|
|
46
75
|
> {
|
|
47
76
|
/** Human-readable description of what this function does */
|
|
48
77
|
description: string;
|
|
@@ -51,11 +80,11 @@ export interface FunctionDefinition<
|
|
|
51
80
|
/** Which entities this function relates to (use empty array [] if none) */
|
|
52
81
|
entities: TEntities[];
|
|
53
82
|
/** Zod schema for input validation */
|
|
54
|
-
inputs:
|
|
83
|
+
inputs: TInputs;
|
|
55
84
|
/** Zod schema for output validation/documentation */
|
|
56
|
-
outputs?:
|
|
57
|
-
/**
|
|
58
|
-
resolver:
|
|
85
|
+
outputs?: TOutputs;
|
|
86
|
+
/** Resolver function that handles this function's logic */
|
|
87
|
+
resolver: ResolverFunction<z.infer<TInputs>, z.infer<TOutputs>>;
|
|
59
88
|
}
|
|
60
89
|
|
|
61
90
|
/**
|
|
@@ -105,30 +134,3 @@ export interface OntologyConfig<
|
|
|
105
134
|
/** Function definitions */
|
|
106
135
|
functions: TFunctions;
|
|
107
136
|
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Context passed to resolvers
|
|
111
|
-
*/
|
|
112
|
-
export interface ResolverContext {
|
|
113
|
-
/** Current environment name */
|
|
114
|
-
env: string;
|
|
115
|
-
/** Environment configuration */
|
|
116
|
-
envConfig: EnvironmentConfig;
|
|
117
|
-
/** Logger instance */
|
|
118
|
-
logger: {
|
|
119
|
-
info: (message: string, ...args: unknown[]) => void;
|
|
120
|
-
warn: (message: string, ...args: unknown[]) => void;
|
|
121
|
-
error: (message: string, ...args: unknown[]) => void;
|
|
122
|
-
debug: (message: string, ...args: unknown[]) => void;
|
|
123
|
-
};
|
|
124
|
-
/** Access groups for the current request */
|
|
125
|
-
accessGroups: string[];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Resolver function signature
|
|
130
|
-
*/
|
|
131
|
-
export type ResolverFunction<TArgs = unknown, TResult = unknown> = (
|
|
132
|
-
ctx: ResolverContext,
|
|
133
|
-
args: TArgs
|
|
134
|
-
) => Promise<TResult> | TResult;
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* ```ts
|
|
6
6
|
* import { defineOntology, fieldFrom } from 'ont-run';
|
|
7
7
|
* import { z } from 'zod';
|
|
8
|
+
* import { hello } from './resolvers/hello.js';
|
|
8
9
|
*
|
|
9
10
|
* export default defineOntology({
|
|
10
11
|
* name: 'my-api',
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
* access: ['public'],
|
|
24
25
|
* entities: [],
|
|
25
26
|
* inputs: z.object({ name: z.string() }),
|
|
26
|
-
* resolver:
|
|
27
|
+
* resolver: hello, // Direct function reference for type safety
|
|
27
28
|
* },
|
|
28
29
|
* },
|
|
29
30
|
* });
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
*/
|
|
32
33
|
|
|
33
34
|
// Main API
|
|
34
|
-
export { defineOntology } from "./config/define.js";
|
|
35
|
+
export { defineOntology, defineFunction } from "./config/define.js";
|
|
35
36
|
export { fieldFrom, userContext } from "./config/categorical.js";
|
|
36
37
|
export { startOnt } from "./server/start.js";
|
|
37
38
|
export type { StartOntOptions, StartOntResult } from "./server/start.js";
|