ont-run 0.0.4 → 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.
@@ -32,10 +32,32 @@ export interface FieldOption {
32
32
  /** Human-readable label for display */
33
33
  label: string;
34
34
  }
35
+ /**
36
+ * Context passed to resolvers
37
+ */
38
+ export interface ResolverContext {
39
+ /** Current environment name */
40
+ env: string;
41
+ /** Environment configuration */
42
+ envConfig: EnvironmentConfig;
43
+ /** Logger instance */
44
+ logger: {
45
+ info: (message: string, ...args: unknown[]) => void;
46
+ warn: (message: string, ...args: unknown[]) => void;
47
+ error: (message: string, ...args: unknown[]) => void;
48
+ debug: (message: string, ...args: unknown[]) => void;
49
+ };
50
+ /** Access groups for the current request */
51
+ accessGroups: string[];
52
+ }
53
+ /**
54
+ * Resolver function signature
55
+ */
56
+ export type ResolverFunction<TArgs = unknown, TResult = unknown> = (ctx: ResolverContext, args: TArgs) => Promise<TResult> | TResult;
35
57
  /**
36
58
  * Definition of a function in the ontology
37
59
  */
38
- export interface FunctionDefinition<TGroups extends string = string, TEntities extends string = string> {
60
+ export interface FunctionDefinition<TGroups extends string = string, TEntities extends string = string, TInputs extends z.ZodType = z.ZodType<unknown>, TOutputs extends z.ZodType = z.ZodType<unknown>> {
39
61
  /** Human-readable description of what this function does */
40
62
  description: string;
41
63
  /** Which access groups can call this function */
@@ -43,11 +65,11 @@ export interface FunctionDefinition<TGroups extends string = string, TEntities e
43
65
  /** Which entities this function relates to (use empty array [] if none) */
44
66
  entities: TEntities[];
45
67
  /** Zod schema for input validation */
46
- inputs: z.ZodType<unknown>;
68
+ inputs: TInputs;
47
69
  /** Zod schema for output validation/documentation */
48
- outputs?: z.ZodType<unknown>;
49
- /** Path to the resolver file (relative to ontology.config.ts) */
50
- resolver: string;
70
+ outputs?: TOutputs;
71
+ /** Resolver function that handles this function's logic */
72
+ resolver: ResolverFunction<z.infer<TInputs>, z.infer<TOutputs>>;
51
73
  }
52
74
  /**
53
75
  * Result returned by the auth function
@@ -82,25 +104,3 @@ export interface OntologyConfig<TGroups extends string = string, TEntities exten
82
104
  /** Function definitions */
83
105
  functions: TFunctions;
84
106
  }
85
- /**
86
- * Context passed to resolvers
87
- */
88
- export interface ResolverContext {
89
- /** Current environment name */
90
- env: string;
91
- /** Environment configuration */
92
- envConfig: EnvironmentConfig;
93
- /** Logger instance */
94
- logger: {
95
- info: (message: string, ...args: unknown[]) => void;
96
- warn: (message: string, ...args: unknown[]) => void;
97
- error: (message: string, ...args: unknown[]) => void;
98
- debug: (message: string, ...args: unknown[]) => void;
99
- };
100
- /** Access groups for the current request */
101
- accessGroups: string[];
102
- }
103
- /**
104
- * Resolver function signature
105
- */
106
- export type ResolverFunction<TArgs = unknown, TResult = unknown> = (ctx: ResolverContext, args: TArgs) => Promise<TResult> | TResult;
@@ -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,13 +24,13 @@
23
24
  * access: ['public'],
24
25
  * entities: [],
25
26
  * inputs: z.object({ name: z.string() }),
26
- * resolver: './resolvers/hello.ts',
27
+ * resolver: hello, // Direct function reference for type safety
27
28
  * },
28
29
  * },
29
30
  * });
30
31
  * ```
31
32
  */
32
- export { defineOntology } from "./config/define.js";
33
+ export { defineOntology, defineFunction } from "./config/define.js";
33
34
  export { fieldFrom, userContext } from "./config/categorical.js";
34
35
  export { startOnt } from "./server/start.js";
35
36
  export type { StartOntOptions, StartOntResult } from "./server/start.js";
@@ -4,8 +4,6 @@ import { type OntologyVariables } from "./middleware.js";
4
4
  export interface ApiServerOptions {
5
5
  /** The ontology configuration */
6
6
  config: OntologyConfig;
7
- /** Directory containing the ontology.config.ts (for resolving resolver paths) */
8
- configDir: string;
9
7
  /** Environment to use (e.g., 'dev', 'prod') */
10
8
  env: string;
11
9
  /** Enable CORS (default: true) */
@@ -4,7 +4,7 @@ import { type OntologyVariables } from "./middleware.js";
4
4
  /**
5
5
  * Create API routes from function definitions
6
6
  */
7
- export declare function createApiRoutes(config: OntologyConfig, configDir: string): Hono<{
7
+ export declare function createApiRoutes(config: OntologyConfig): Hono<{
8
8
  Variables: OntologyVariables;
9
9
  }>;
10
10
  /**
@@ -3,8 +3,6 @@ import type { OntologyConfig } from "../../config/types.js";
3
3
  export interface McpServerOptions {
4
4
  /** The ontology configuration */
5
5
  config: OntologyConfig;
6
- /** Directory containing the ontology.config.ts */
7
- configDir: string;
8
6
  /** Environment to use */
9
7
  env: string;
10
8
  /** Port for the MCP HTTP server */
@@ -1,5 +1,5 @@
1
1
  import type { OntologyConfig, EnvironmentConfig, AuthResult } from "../../config/types.js";
2
- import { type Logger } from "../resolver.js";
2
+ import type { Logger } from "../resolver.js";
3
3
  /**
4
4
  * Field reference info for MCP tools
5
5
  */
@@ -32,4 +32,4 @@ export declare function filterToolsByAccess(tools: McpTool[], accessGroups: stri
32
32
  /**
33
33
  * Create a tool executor function that accepts per-request auth result
34
34
  */
35
- export declare function createToolExecutor(config: OntologyConfig, configDir: string, env: string, envConfig: EnvironmentConfig, logger: Logger): (toolName: string, args: unknown, authResult: AuthResult) => Promise<unknown>;
35
+ export declare function createToolExecutor(config: OntologyConfig, env: string, envConfig: EnvironmentConfig, logger: Logger): (toolName: string, args: unknown, authResult: AuthResult) => Promise<unknown>;
@@ -1,20 +1,9 @@
1
- import type { ResolverFunction, OntologyConfig } from "../config/types.js";
1
+ import type { ResolverFunction } from "../config/types.js";
2
2
  /**
3
- * Load a resolver from a file path.
4
- * The path is relative to the config file location.
5
- *
6
- * @param resolverPath - Path to the resolver file (relative to configDir)
7
- * @param configDir - Directory containing the ontology.config.ts
3
+ * Get a resolver function. Since resolvers are now passed directly as functions,
4
+ * this is a simple passthrough that could be removed in the future.
8
5
  */
9
- export declare function loadResolver(resolverPath: string, configDir: string): Promise<ResolverFunction>;
10
- /**
11
- * Clear the resolver cache (useful for hot reloading)
12
- */
13
- export declare function clearResolverCache(): void;
14
- /**
15
- * Check which resolvers are missing and return their paths
16
- */
17
- export declare function findMissingResolvers(config: OntologyConfig, configDir: string): string[];
6
+ export declare function loadResolver(resolver: ResolverFunction): ResolverFunction;
18
7
  /**
19
8
  * Logger type returned by createLogger
20
9
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ont-run",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Ontology-enforced API framework for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
 
@@ -705,6 +729,89 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
705
729
  color: var(--change-added);
706
730
  }
707
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
+
708
815
  /* No Changes State */
709
816
  .no-changes {
710
817
  text-align: center;
@@ -1433,6 +1540,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
1433
1540
  <div class="view-tabs">
1434
1541
  <button class="view-tab active" data-view="graph">Graph</button>
1435
1542
  <button class="view-tab" data-view="table">Table</button>
1543
+ <button class="view-tab" data-view="source">Source</button>
1436
1544
  </div>
1437
1545
 
1438
1546
  <div class="filter-buttons" id="graphFilters">
@@ -1553,6 +1661,20 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
1553
1661
  <div class="table-view" id="tableView">
1554
1662
  <div id="tableContent"></div>
1555
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>
1556
1678
  </div>
1557
1679
 
1558
1680
  <!-- Review Footer -->
@@ -2429,6 +2551,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
2429
2551
  const graphContainer = document.querySelector('.graph-container');
2430
2552
  const detailPanel = document.getElementById('detailPanel');
2431
2553
  const tableView = document.getElementById('tableView');
2554
+ const sourceView = document.getElementById('sourceView');
2432
2555
  const graphFilters = document.getElementById('graphFilters');
2433
2556
  const layoutSelector = document.querySelector('.layout-selector');
2434
2557
 
@@ -2436,15 +2559,25 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
2436
2559
  graphContainer.style.display = 'block';
2437
2560
  detailPanel.style.display = 'block';
2438
2561
  tableView.classList.remove('active');
2562
+ sourceView.classList.remove('active');
2439
2563
  if (graphFilters) graphFilters.style.display = 'flex';
2440
2564
  if (layoutSelector) layoutSelector.style.display = 'flex';
2441
- } else {
2565
+ } else if (view === 'table') {
2442
2566
  graphContainer.style.display = 'none';
2443
2567
  detailPanel.style.display = 'none';
2444
2568
  tableView.classList.add('active');
2569
+ sourceView.classList.remove('active');
2445
2570
  if (graphFilters) graphFilters.style.display = 'none';
2446
2571
  if (layoutSelector) layoutSelector.style.display = 'none';
2447
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();
2448
2581
  }
2449
2582
  }
2450
2583
 
@@ -2486,6 +2619,74 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
2486
2619
  });
2487
2620
  }
2488
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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
+
2489
2690
  function renderTableSection(title, items, type) {
2490
2691
  const changedCount = items.filter(n => n.changeStatus !== 'unchanged').length;
2491
2692
 
@@ -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
  };
@@ -173,7 +172,6 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
173
172
  metadata: {
174
173
  inputs: safeZodToJsonSchema(fn.inputs),
175
174
  outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
176
- resolver: fn.resolver,
177
175
  usesUserContext: usesUserContext || undefined,
178
176
  },
179
177
  });
@@ -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) {
@@ -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: './resolvers/getUser.ts',
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
+ }
@@ -53,7 +53,7 @@ export const FunctionDefinitionSchema = z.object({
53
53
  message: "outputs must be a Zod schema",
54
54
  })
55
55
  .optional(),
56
- resolver: z.string(),
56
+ resolver: z.function(),
57
57
  });
58
58
 
59
59
  /**
@@ -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: z.ZodType<unknown>;
83
+ inputs: TInputs;
55
84
  /** Zod schema for output validation/documentation */
56
- outputs?: z.ZodType<unknown>;
57
- /** Path to the resolver file (relative to ontology.config.ts) */
58
- resolver: string;
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;