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.
package/README.md CHANGED
@@ -7,6 +7,8 @@ A web framework designed for the era of coding agents. You define the ontology
7
7
  ```typescript
8
8
  // ontology.config.ts
9
9
  import { defineOntology, z } from 'ont-run';
10
+ import getTicket from './resolvers/getTicket.js';
11
+ import assignTicket from './resolvers/assignTicket.js';
10
12
 
11
13
  export default defineOntology({
12
14
  name: 'support-desk',
@@ -23,14 +25,14 @@ export default defineOntology({
23
25
  access: ['support', 'admin'],
24
26
  entities: ['Ticket'],
25
27
  inputs: z.object({ ticketId: z.string().uuid() }),
26
- resolver: './resolvers/getTicket.ts',
28
+ resolver: getTicket,
27
29
  },
28
30
  assignTicket: {
29
31
  description: 'Assign ticket to an agent',
30
32
  access: ['admin'], // If AI tries to add 'public' here, review is triggered
31
33
  entities: ['Ticket'],
32
34
  inputs: z.object({ ticketId: z.string().uuid(), assignee: z.string() }),
33
- resolver: './resolvers/assignTicket.ts',
35
+ resolver: assignTicket,
34
36
  },
35
37
  },
36
38
  // ...
@@ -141,6 +143,7 @@ The resolver context provides:
141
143
 
142
144
  ```typescript
143
145
  import { defineOntology, z } from 'ont-run';
146
+ import getUser from './resolvers/getUser.js';
144
147
 
145
148
  export default defineOntology({
146
149
  name: 'my-api',
@@ -179,7 +182,7 @@ export default defineOntology({
179
182
  access: ['user', 'admin'],
180
183
  entities: ['User'],
181
184
  inputs: z.object({ userId: z.string().uuid() }),
182
- resolver: './resolvers/getUser.ts',
185
+ resolver: getUser,
183
186
  },
184
187
  },
185
188
  });
@@ -191,6 +194,7 @@ The framework handles **group-based access** (user → group → function) out o
191
194
 
192
195
  ```typescript
193
196
  import { defineOntology, userContext, z } from 'ont-run';
197
+ import editPost from './resolvers/editPost.js';
194
198
 
195
199
  export default defineOntology({
196
200
  // Auth must return user identity for userContext to work
@@ -216,7 +220,7 @@ export default defineOntology({
216
220
  email: z.string(),
217
221
  })),
218
222
  }),
219
- resolver: './resolvers/editPost.ts',
223
+ resolver: editPost,
220
224
  },
221
225
  },
222
226
  });
package/dist/bin/ont.js CHANGED
@@ -10233,6 +10233,10 @@ var Hono2 = class extends Hono {
10233
10233
  }
10234
10234
  };
10235
10235
 
10236
+ // src/browser/server.ts
10237
+ import { readFileSync as readFileSync2 } from "fs";
10238
+ import { basename } from "path";
10239
+
10236
10240
  // node_modules/open/index.js
10237
10241
  import process7 from "node:process";
10238
10242
  import { Buffer as Buffer2 } from "node:buffer";
@@ -10869,7 +10873,6 @@ function transformToGraphData(config) {
10869
10873
  metadata: {
10870
10874
  inputs: safeZodToJsonSchema(fn.inputs),
10871
10875
  outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
10872
- resolver: fn.resolver,
10873
10876
  usesUserContext: usesUserContext || undefined
10874
10877
  }
10875
10878
  });
@@ -11103,7 +11106,7 @@ function enhanceWithDiff(graphData, diff) {
11103
11106
 
11104
11107
  // src/browser/server.ts
11105
11108
  async function startBrowserServer(options) {
11106
- const { config, diff = null, configDir, port: preferredPort, openBrowser = true } = options;
11109
+ const { config, diff = null, configDir, configPath, port: preferredPort, openBrowser = true } = options;
11107
11110
  const baseGraphData = transformToGraphData(config);
11108
11111
  const graphData = enhanceWithDiff(baseGraphData, diff);
11109
11112
  return new Promise(async (resolve2) => {
@@ -11150,6 +11153,21 @@ async function startBrowserServer(options) {
11150
11153
  }, 500);
11151
11154
  return c3.json({ success: true });
11152
11155
  });
11156
+ app.get("/api/source", (c3) => {
11157
+ if (!configPath) {
11158
+ return c3.json({ error: "Config path not available" }, 400);
11159
+ }
11160
+ try {
11161
+ const source = readFileSync2(configPath, "utf-8");
11162
+ const filename = basename(configPath);
11163
+ return c3.json({ source, filename, path: configPath });
11164
+ } catch (error) {
11165
+ return c3.json({
11166
+ error: "Failed to read config file",
11167
+ message: error instanceof Error ? error.message : "Unknown error"
11168
+ }, 500);
11169
+ }
11170
+ });
11153
11171
  app.get("/", (c3) => c3.html(generateBrowserUI(graphData)));
11154
11172
  const port = preferredPort || await findAvailablePort(3457);
11155
11173
  const server = await serve2(app, port);
@@ -11758,6 +11776,89 @@ function generateBrowserUI(graphData) {
11758
11776
  color: var(--change-added);
11759
11777
  }
11760
11778
 
11779
+ /* Source View */
11780
+ .source-view {
11781
+ display: none;
11782
+ grid-column: 2 / 4;
11783
+ padding: 24px;
11784
+ overflow-y: auto;
11785
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(231, 225, 207, 0.3));
11786
+ }
11787
+
11788
+ .source-view.active {
11789
+ display: flex;
11790
+ flex-direction: column;
11791
+ }
11792
+
11793
+ .source-header {
11794
+ display: flex;
11795
+ align-items: center;
11796
+ justify-content: space-between;
11797
+ padding: 12px 20px;
11798
+ background: rgba(2, 61, 96, 0.95);
11799
+ border-radius: 12px 12px 0 0;
11800
+ color: white;
11801
+ }
11802
+
11803
+ .source-filename {
11804
+ font-family: 'Space Mono', monospace;
11805
+ font-size: 13px;
11806
+ font-weight: 500;
11807
+ }
11808
+
11809
+ .copy-btn {
11810
+ display: flex;
11811
+ align-items: center;
11812
+ gap: 6px;
11813
+ padding: 6px 12px;
11814
+ background: rgba(255, 255, 255, 0.1);
11815
+ border: 1px solid rgba(255, 255, 255, 0.2);
11816
+ border-radius: 6px;
11817
+ color: white;
11818
+ font-family: 'Space Grotesk', sans-serif;
11819
+ font-size: 12px;
11820
+ cursor: pointer;
11821
+ transition: all 0.2s ease;
11822
+ }
11823
+
11824
+ .copy-btn:hover {
11825
+ background: rgba(255, 255, 255, 0.2);
11826
+ }
11827
+
11828
+ .copy-btn.copied {
11829
+ background: rgba(21, 168, 168, 0.3);
11830
+ border-color: var(--vanna-teal);
11831
+ }
11832
+
11833
+ .source-code {
11834
+ flex: 1;
11835
+ margin: 0;
11836
+ padding: 20px;
11837
+ background: #1e1e1e;
11838
+ border-radius: 0 0 12px 12px;
11839
+ overflow: auto;
11840
+ font-family: 'Space Mono', monospace;
11841
+ font-size: 13px;
11842
+ line-height: 1.6;
11843
+ color: #d4d4d4;
11844
+ tab-size: 2;
11845
+ }
11846
+
11847
+ .source-code code {
11848
+ display: block;
11849
+ white-space: pre;
11850
+ }
11851
+
11852
+ /* Syntax highlighting classes */
11853
+ .source-code .keyword { color: #569cd6; }
11854
+ .source-code .string { color: #ce9178; }
11855
+ .source-code .number { color: #b5cea8; }
11856
+ .source-code .comment { color: #6a9955; }
11857
+ .source-code .function { color: #dcdcaa; }
11858
+ .source-code .type { color: #4ec9b0; }
11859
+ .source-code .property { color: #9cdcfe; }
11860
+ .source-code .punctuation { color: #d4d4d4; }
11861
+
11761
11862
  /* No Changes State */
11762
11863
  .no-changes {
11763
11864
  text-align: center;
@@ -12486,6 +12587,7 @@ function generateBrowserUI(graphData) {
12486
12587
  <div class="view-tabs">
12487
12588
  <button class="view-tab active" data-view="graph">Graph</button>
12488
12589
  <button class="view-tab" data-view="table">Table</button>
12590
+ <button class="view-tab" data-view="source">Source</button>
12489
12591
  </div>
12490
12592
 
12491
12593
  <div class="filter-buttons" id="graphFilters">
@@ -12606,6 +12708,20 @@ function generateBrowserUI(graphData) {
12606
12708
  <div class="table-view" id="tableView">
12607
12709
  <div id="tableContent"></div>
12608
12710
  </div>
12711
+
12712
+ <!-- Source View -->
12713
+ <div class="source-view" id="sourceView">
12714
+ <div class="source-header">
12715
+ <span class="source-filename" id="sourceFilename">ontology.config.ts</span>
12716
+ <button class="copy-btn" id="copySourceBtn" title="Copy to clipboard">
12717
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
12718
+ <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"/>
12719
+ </svg>
12720
+ Copy
12721
+ </button>
12722
+ </div>
12723
+ <pre class="source-code" id="sourceCode"><code>Loading...</code></pre>
12724
+ </div>
12609
12725
  </div>
12610
12726
 
12611
12727
  <!-- Review Footer -->
@@ -13482,6 +13598,7 @@ function generateBrowserUI(graphData) {
13482
13598
  const graphContainer = document.querySelector('.graph-container');
13483
13599
  const detailPanel = document.getElementById('detailPanel');
13484
13600
  const tableView = document.getElementById('tableView');
13601
+ const sourceView = document.getElementById('sourceView');
13485
13602
  const graphFilters = document.getElementById('graphFilters');
13486
13603
  const layoutSelector = document.querySelector('.layout-selector');
13487
13604
 
@@ -13489,15 +13606,25 @@ function generateBrowserUI(graphData) {
13489
13606
  graphContainer.style.display = 'block';
13490
13607
  detailPanel.style.display = 'block';
13491
13608
  tableView.classList.remove('active');
13609
+ sourceView.classList.remove('active');
13492
13610
  if (graphFilters) graphFilters.style.display = 'flex';
13493
13611
  if (layoutSelector) layoutSelector.style.display = 'flex';
13494
- } else {
13612
+ } else if (view === 'table') {
13495
13613
  graphContainer.style.display = 'none';
13496
13614
  detailPanel.style.display = 'none';
13497
13615
  tableView.classList.add('active');
13616
+ sourceView.classList.remove('active');
13498
13617
  if (graphFilters) graphFilters.style.display = 'none';
13499
13618
  if (layoutSelector) layoutSelector.style.display = 'none';
13500
13619
  renderTableView();
13620
+ } else if (view === 'source') {
13621
+ graphContainer.style.display = 'none';
13622
+ detailPanel.style.display = 'none';
13623
+ tableView.classList.remove('active');
13624
+ sourceView.classList.add('active');
13625
+ if (graphFilters) graphFilters.style.display = 'none';
13626
+ if (layoutSelector) layoutSelector.style.display = 'none';
13627
+ loadSourceView();
13501
13628
  }
13502
13629
  }
13503
13630
 
@@ -13539,6 +13666,74 @@ function generateBrowserUI(graphData) {
13539
13666
  });
13540
13667
  }
13541
13668
 
13669
+ // Source view
13670
+ let sourceLoaded = false;
13671
+ let sourceContent = '';
13672
+
13673
+ async function loadSourceView() {
13674
+ if (sourceLoaded) return;
13675
+
13676
+ const codeEl = document.getElementById('sourceCode').querySelector('code');
13677
+ const filenameEl = document.getElementById('sourceFilename');
13678
+
13679
+ try {
13680
+ const res = await fetch('/api/source');
13681
+ if (!res.ok) throw new Error('Failed to load source');
13682
+ const data = await res.json();
13683
+
13684
+ sourceContent = data.source;
13685
+ filenameEl.textContent = data.filename;
13686
+ codeEl.innerHTML = highlightTypeScript(data.source);
13687
+ sourceLoaded = true;
13688
+ } catch (err) {
13689
+ codeEl.textContent = 'Error loading source: ' + err.message;
13690
+ }
13691
+ }
13692
+
13693
+ function highlightTypeScript(code) {
13694
+ // Escape HTML first
13695
+ code = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
13696
+
13697
+ // Comments (single and multi-line)
13698
+ code = code.replace(/(\\/\\/.*$)/gm, '<span class="comment">$1</span>');
13699
+ code = code.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g, '<span class="comment">$1</span>');
13700
+
13701
+ // Strings (double, single, and template)
13702
+ code = code.replace(/("(?:[^"\\\\]|\\\\.)*")/g, '<span class="string">$1</span>');
13703
+ code = code.replace(/('(?:[^'\\\\]|\\\\.)*')/g, '<span class="string">$1</span>');
13704
+ code = code.replace(/(\`(?:[^\`\\\\]|\\\\.)*\`)/g, '<span class="string">$1</span>');
13705
+
13706
+ // Keywords
13707
+ 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'];
13708
+ keywords.forEach(kw => {
13709
+ code = code.replace(new RegExp('\\\\b(' + kw + ')\\\\b', 'g'), '<span class="keyword">$1</span>');
13710
+ });
13711
+
13712
+ // Numbers
13713
+ code = code.replace(/\\b(\\d+\\.?\\d*)\\b/g, '<span class="number">$1</span>');
13714
+
13715
+ // Function calls
13716
+ code = code.replace(/\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(/g, '<span class="function">$1</span>(');
13717
+
13718
+ return code;
13719
+ }
13720
+
13721
+ // Copy source button
13722
+ document.getElementById('copySourceBtn').addEventListener('click', async () => {
13723
+ const btn = document.getElementById('copySourceBtn');
13724
+ try {
13725
+ await navigator.clipboard.writeText(sourceContent);
13726
+ btn.classList.add('copied');
13727
+ 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!';
13728
+ setTimeout(() => {
13729
+ btn.classList.remove('copied');
13730
+ 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';
13731
+ }, 2000);
13732
+ } catch (err) {
13733
+ console.error('Failed to copy:', err);
13734
+ }
13735
+ });
13736
+
13542
13737
  function renderTableSection(title, items, type) {
13543
13738
  const changedCount = items.filter(n => n.changeStatus !== 'unchanged').length;
13544
13739
 
@@ -13712,7 +13907,7 @@ var reviewCommand = defineCommand({
13712
13907
  process.exit(0);
13713
13908
  }
13714
13909
  consola.info("Loading ontology config...");
13715
- const { config, configDir } = await loadConfig();
13910
+ const { config, configDir, configPath } = await loadConfig();
13716
13911
  const { ontology: newOntology, hash: newHash } = computeOntologyHash(config);
13717
13912
  const lockfile = await readLockfile(configDir);
13718
13913
  const oldOntology = lockfile?.ontology || null;
@@ -13760,7 +13955,8 @@ var reviewCommand = defineCommand({
13760
13955
  const result = await startBrowserServer({
13761
13956
  config,
13762
13957
  diff: diff.hasChanges ? diff : null,
13763
- configDir
13958
+ configDir,
13959
+ configPath
13764
13960
  });
13765
13961
  if (diff.hasChanges) {
13766
13962
  if (result.approved) {
@@ -13780,7 +13976,7 @@ var reviewCommand = defineCommand({
13780
13976
  // package.json
13781
13977
  var package_default = {
13782
13978
  name: "ont-run",
13783
- version: "0.0.4",
13979
+ version: "0.0.5",
13784
13980
  description: "Ontology-enforced API framework for AI coding agents",
13785
13981
  type: "module",
13786
13982
  bin: {
package/dist/index.js CHANGED
@@ -13489,7 +13489,7 @@ var FunctionDefinitionSchema = exports_external.object({
13489
13489
  outputs: exports_external.custom(isZodSchema, {
13490
13490
  message: "outputs must be a Zod schema"
13491
13491
  }).optional(),
13492
- resolver: exports_external.string()
13492
+ resolver: exports_external.function()
13493
13493
  });
13494
13494
  var AuthFunctionSchema = exports_external.function().args(exports_external.custom()).returns(exports_external.union([exports_external.array(exports_external.string()), exports_external.promise(exports_external.array(exports_external.string()))]));
13495
13495
  var OntologyConfigSchema = exports_external.object({
@@ -13606,6 +13606,13 @@ function defineOntology(config) {
13606
13606
  validateFieldFromReferences(parsed);
13607
13607
  return config;
13608
13608
  }
13609
+ function defineFunction(config) {
13610
+ return {
13611
+ ...config,
13612
+ access: [...config.access],
13613
+ entities: [...config.entities]
13614
+ };
13615
+ }
13609
13616
 
13610
13617
  // src/index.ts
13611
13618
  init_categorical();
@@ -16301,39 +16308,6 @@ var Factory = class {
16301
16308
  var createMiddleware = (middleware) => middleware;
16302
16309
 
16303
16310
  // src/server/resolver.ts
16304
- import { join as join3, isAbsolute } from "path";
16305
- import { existsSync as existsSync3 } from "fs";
16306
- var resolverCache = new Map;
16307
- async function loadResolver(resolverPath, configDir) {
16308
- const fullPath = isAbsolute(resolverPath) ? resolverPath : join3(configDir, resolverPath);
16309
- if (resolverCache.has(fullPath)) {
16310
- return resolverCache.get(fullPath);
16311
- }
16312
- try {
16313
- const module = await import(fullPath);
16314
- const resolver = module.default;
16315
- if (typeof resolver !== "function") {
16316
- throw new Error(`Resolver at ${resolverPath} must export a default function`);
16317
- }
16318
- resolverCache.set(fullPath, resolver);
16319
- return resolver;
16320
- } catch (error) {
16321
- if (error.code === "ERR_MODULE_NOT_FOUND") {
16322
- throw new Error(`Resolver not found: ${resolverPath}`);
16323
- }
16324
- throw error;
16325
- }
16326
- }
16327
- function findMissingResolvers(config, configDir) {
16328
- const missing = [];
16329
- for (const [name, fn] of Object.entries(config.functions)) {
16330
- const fullPath = isAbsolute(fn.resolver) ? fn.resolver : join3(configDir, fn.resolver);
16331
- if (!existsSync3(fullPath)) {
16332
- missing.push(fn.resolver);
16333
- }
16334
- }
16335
- return missing;
16336
- }
16337
16311
  function createLogger(debug = false) {
16338
16312
  return {
16339
16313
  info: (message, ...args) => {
@@ -16422,7 +16396,7 @@ function errorHandler2() {
16422
16396
 
16423
16397
  // src/server/api/router.ts
16424
16398
  init_categorical();
16425
- function createApiRoutes(config, configDir) {
16399
+ function createApiRoutes(config) {
16426
16400
  const router = new Hono2;
16427
16401
  for (const [name, fn] of Object.entries(config.functions)) {
16428
16402
  const path = `/${name}`;
@@ -16465,8 +16439,7 @@ function createApiRoutes(config, configDir) {
16465
16439
  args = parsed.data;
16466
16440
  }
16467
16441
  try {
16468
- const resolver = await loadResolver(fn.resolver, configDir);
16469
- const result = await resolver(resolverContext, args);
16442
+ const result = await fn.resolver(resolverContext, args);
16470
16443
  return c3.json(result);
16471
16444
  } catch (error) {
16472
16445
  console.error(`Error in resolver ${name}:`, error);
@@ -16490,18 +16463,11 @@ function getFunctionsInfo(config) {
16490
16463
 
16491
16464
  // src/server/api/index.ts
16492
16465
  function createApiApp(options) {
16493
- const { config, configDir, env: env2, cors: enableCors = true } = options;
16466
+ const { config, env: env2, cors: enableCors = true } = options;
16494
16467
  const envConfig = config.environments[env2];
16495
16468
  if (!envConfig) {
16496
16469
  throw new Error(`Unknown environment "${env2}". Available: ${Object.keys(config.environments).join(", ")}`);
16497
16470
  }
16498
- const missingResolvers = findMissingResolvers(config, configDir);
16499
- if (missingResolvers.length > 0) {
16500
- consola.warn(`Missing resolvers (${missingResolvers.length}):`);
16501
- for (const resolver of missingResolvers) {
16502
- consola.warn(` - ${resolver}`);
16503
- }
16504
- }
16505
16471
  const app = new Hono2;
16506
16472
  if (enableCors) {
16507
16473
  app.use("*", cors());
@@ -16527,7 +16493,7 @@ function createApiApp(options) {
16527
16493
  functions: accessibleFunctions
16528
16494
  });
16529
16495
  });
16530
- const apiRoutes = createApiRoutes(config, configDir);
16496
+ const apiRoutes = createApiRoutes(config);
16531
16497
  app.route("/api", apiRoutes);
16532
16498
  return app;
16533
16499
  }
@@ -23017,7 +22983,7 @@ function generateMcpTools(config2) {
23017
22983
  function filterToolsByAccess(tools, accessGroups) {
23018
22984
  return tools.filter((tool) => tool.access.some((group) => accessGroups.includes(group)));
23019
22985
  }
23020
- function createToolExecutor(config2, configDir, env2, envConfig, logger) {
22986
+ function createToolExecutor(config2, env2, envConfig, logger) {
23021
22987
  const userContextFieldsCache = new Map;
23022
22988
  for (const [name, fn] of Object.entries(config2.functions)) {
23023
22989
  userContextFieldsCache.set(name, getUserContextFields(fn.inputs));
@@ -23049,8 +23015,7 @@ function createToolExecutor(config2, configDir, env2, envConfig, logger) {
23049
23015
  logger,
23050
23016
  accessGroups: authResult.groups
23051
23017
  };
23052
- const resolver = await loadResolver(fn.resolver, configDir);
23053
- return resolver(resolverContext, parsed.data);
23018
+ return fn.resolver(resolverContext, parsed.data);
23054
23019
  };
23055
23020
  }
23056
23021
 
@@ -23103,14 +23068,14 @@ function getAuthResult(authInfo) {
23103
23068
  return authInfo.extra.authResult;
23104
23069
  }
23105
23070
  function createMcpServer(options) {
23106
- const { config: config2, configDir, env: env2 } = options;
23071
+ const { config: config2, env: env2 } = options;
23107
23072
  const envConfig = config2.environments[env2];
23108
23073
  if (!envConfig) {
23109
23074
  throw new Error(`Unknown environment "${env2}". Available: ${Object.keys(config2.environments).join(", ")}`);
23110
23075
  }
23111
23076
  const logger = createLogger(envConfig.debug);
23112
23077
  const allTools = generateMcpTools(config2);
23113
- const executeToolWithAccess = createToolExecutor(config2, configDir, env2, envConfig, logger);
23078
+ const executeToolWithAccess = createToolExecutor(config2, env2, envConfig, logger);
23114
23079
  const server = new Server({
23115
23080
  name: config2.name,
23116
23081
  version: "1.0.0"
@@ -23267,7 +23232,6 @@ Run \`bun run review\` to approve the changes.`;
23267
23232
  if (!mcpOnly) {
23268
23233
  const api2 = createApiApp({
23269
23234
  config: config2,
23270
- configDir,
23271
23235
  env: env2
23272
23236
  });
23273
23237
  const server = await serve2(api2, port);
@@ -23286,7 +23250,6 @@ Run \`bun run review\` to approve the changes.`;
23286
23250
  if (!apiOnly) {
23287
23251
  const mcpServer = await startMcpServer({
23288
23252
  config: config2,
23289
- configDir,
23290
23253
  env: env2,
23291
23254
  port: mcpPort
23292
23255
  });
@@ -23305,5 +23268,6 @@ export {
23305
23268
  userContext,
23306
23269
  startOnt,
23307
23270
  fieldFrom,
23308
- defineOntology
23271
+ defineOntology,
23272
+ defineFunction
23309
23273
  };
@@ -6,6 +6,8 @@ export interface BrowserServerOptions {
6
6
  diff?: OntologyDiff | null;
7
7
  /** Directory to write the lockfile to on approval */
8
8
  configDir?: string;
9
+ /** Path to the ontology.config.ts file */
10
+ configPath?: string;
9
11
  port?: number;
10
12
  openBrowser?: boolean;
11
13
  }
@@ -11,7 +11,6 @@ export interface GraphNode {
11
11
  metadata: {
12
12
  inputs?: Record<string, unknown>;
13
13
  outputs?: Record<string, unknown>;
14
- resolver?: string;
15
14
  functionCount?: number;
16
15
  usesUserContext?: boolean;
17
16
  };
@@ -1,4 +1,5 @@
1
- import type { OntologyConfig, FunctionDefinition, AccessGroupConfig, EnvironmentConfig, EntityDefinition, AuthFunction } from "./types.js";
1
+ import { z } from "zod";
2
+ import type { OntologyConfig, FunctionDefinition, AccessGroupConfig, EnvironmentConfig, EntityDefinition, AuthFunction, ResolverFunction } from "./types.js";
2
3
  /**
3
4
  * Define an Ontology configuration with full type inference.
4
5
  *
@@ -6,6 +7,7 @@ import type { OntologyConfig, FunctionDefinition, AccessGroupConfig, Environment
6
7
  * ```ts
7
8
  * import { defineOntology, fieldFrom } from 'ont-run';
8
9
  * import { z } from 'zod';
10
+ * import { getUser } from './resolvers/getUser.js';
9
11
  *
10
12
  * export default defineOntology({
11
13
  * name: 'my-api',
@@ -30,7 +32,7 @@ import type { OntologyConfig, FunctionDefinition, AccessGroupConfig, Environment
30
32
  * access: ['public', 'admin'],
31
33
  * entities: ['User'],
32
34
  * inputs: z.object({ id: z.string() }),
33
- * resolver: './resolvers/getUser.ts',
35
+ * resolver: getUser, // Direct function reference for type safety
34
36
  * },
35
37
  * },
36
38
  * });
@@ -44,3 +46,36 @@ export declare function defineOntology<TGroups extends string, TEntities extends
44
46
  entities?: Record<TEntities, EntityDefinition>;
45
47
  functions: TFunctions;
46
48
  }): OntologyConfig<TGroups, TEntities, TFunctions>;
49
+ /**
50
+ * Define a function with full type inference for resolver type safety.
51
+ *
52
+ * This helper ensures that the resolver function's return type matches
53
+ * the outputs Zod schema at compile time.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * import { defineFunction, z } from 'ont-run';
58
+ * import type { ResolverContext } from 'ont-run';
59
+ *
60
+ * const getUser = defineFunction({
61
+ * description: 'Get a user by ID',
62
+ * access: ['public', 'admin'] as const,
63
+ * entities: ['User'] as const,
64
+ * inputs: z.object({ id: z.string() }),
65
+ * outputs: z.object({ id: z.string(), name: z.string() }),
66
+ * resolver: async (ctx, args) => {
67
+ * // TypeScript knows args is { id: string }
68
+ * // TypeScript enforces return type is { id: string, name: string }
69
+ * return { id: args.id, name: 'Example User' };
70
+ * },
71
+ * });
72
+ * ```
73
+ */
74
+ export declare function defineFunction<TGroups extends string, TEntities extends string, TInputs extends z.ZodType, TOutputs extends z.ZodType>(config: {
75
+ description: string;
76
+ access: readonly TGroups[];
77
+ entities: readonly TEntities[];
78
+ inputs: TInputs;
79
+ outputs?: TOutputs;
80
+ resolver: ResolverFunction<z.infer<TInputs>, z.infer<TOutputs>>;
81
+ }): FunctionDefinition<TGroups, TEntities, TInputs, TOutputs>;
@@ -39,20 +39,20 @@ export declare const FunctionDefinitionSchema: z.ZodObject<{
39
39
  entities: z.ZodArray<z.ZodString, "many">;
40
40
  inputs: z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>;
41
41
  outputs: z.ZodOptional<z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>>;
42
- resolver: z.ZodString;
42
+ resolver: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
43
43
  }, "strip", z.ZodTypeAny, {
44
44
  description: string;
45
45
  access: string[];
46
46
  entities: string[];
47
47
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
48
- resolver: string;
48
+ resolver: (...args: unknown[]) => unknown;
49
49
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
50
50
  }, {
51
51
  description: string;
52
52
  access: string[];
53
53
  entities: string[];
54
54
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
55
- resolver: string;
55
+ resolver: (...args: unknown[]) => unknown;
56
56
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
57
57
  }>;
58
58
  /**
@@ -92,20 +92,20 @@ export declare const OntologyConfigSchema: z.ZodObject<{
92
92
  entities: z.ZodArray<z.ZodString, "many">;
93
93
  inputs: z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>;
94
94
  outputs: z.ZodOptional<z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>>;
95
- resolver: z.ZodString;
95
+ resolver: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
96
96
  }, "strip", z.ZodTypeAny, {
97
97
  description: string;
98
98
  access: string[];
99
99
  entities: string[];
100
100
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
101
- resolver: string;
101
+ resolver: (...args: unknown[]) => unknown;
102
102
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
103
103
  }, {
104
104
  description: string;
105
105
  access: string[];
106
106
  entities: string[];
107
107
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
108
- resolver: string;
108
+ resolver: (...args: unknown[]) => unknown;
109
109
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
110
110
  }>>;
111
111
  }, "strip", z.ZodTypeAny, {
@@ -122,7 +122,7 @@ export declare const OntologyConfigSchema: z.ZodObject<{
122
122
  access: string[];
123
123
  entities: string[];
124
124
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
125
- resolver: string;
125
+ resolver: (...args: unknown[]) => unknown;
126
126
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
127
127
  }>;
128
128
  entities?: Record<string, {
@@ -142,7 +142,7 @@ export declare const OntologyConfigSchema: z.ZodObject<{
142
142
  access: string[];
143
143
  entities: string[];
144
144
  inputs: z.ZodType<any, z.ZodTypeDef, any>;
145
- resolver: string;
145
+ resolver: (...args: unknown[]) => unknown;
146
146
  outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
147
147
  }>;
148
148
  entities?: Record<string, {