ont-run 0.0.1
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 +228 -0
- package/bin/ont.ts +5 -0
- package/dist/bin/ont.d.ts +2 -0
- package/dist/bin/ont.js +13667 -0
- package/dist/index.js +23152 -0
- package/dist/src/browser/server.d.ts +16 -0
- package/dist/src/browser/transform.d.ts +87 -0
- package/dist/src/cli/commands/init.d.ts +12 -0
- package/dist/src/cli/commands/review.d.ts +17 -0
- package/dist/src/cli/index.d.ts +1 -0
- package/dist/src/cli/utils/config-loader.d.ts +13 -0
- package/dist/src/config/categorical.d.ts +76 -0
- package/dist/src/config/define.d.ts +46 -0
- package/dist/src/config/index.d.ts +4 -0
- package/dist/src/config/schema.d.ts +162 -0
- package/dist/src/config/types.d.ts +94 -0
- package/dist/src/index.d.ts +37 -0
- package/dist/src/lockfile/differ.d.ts +11 -0
- package/dist/src/lockfile/hasher.d.ts +31 -0
- package/dist/src/lockfile/index.d.ts +53 -0
- package/dist/src/lockfile/types.d.ts +90 -0
- package/dist/src/runtime/index.d.ts +28 -0
- package/dist/src/server/api/index.d.ts +20 -0
- package/dist/src/server/api/middleware.d.ts +34 -0
- package/dist/src/server/api/router.d.ts +18 -0
- package/dist/src/server/mcp/index.d.ts +23 -0
- package/dist/src/server/mcp/tools.d.ts +35 -0
- package/dist/src/server/resolver.d.ts +30 -0
- package/dist/src/server/start.d.ts +37 -0
- package/package.json +63 -0
- package/src/browser/server.ts +2567 -0
- package/src/browser/transform.ts +473 -0
- package/src/cli/commands/init.ts +226 -0
- package/src/cli/commands/review.ts +126 -0
- package/src/cli/index.ts +19 -0
- package/src/cli/utils/config-loader.ts +78 -0
- package/src/config/categorical.ts +101 -0
- package/src/config/define.ts +78 -0
- package/src/config/index.ts +23 -0
- package/src/config/schema.ts +196 -0
- package/src/config/types.ts +121 -0
- package/src/index.ts +53 -0
- package/src/lockfile/differ.ts +242 -0
- package/src/lockfile/hasher.ts +175 -0
- package/src/lockfile/index.ts +159 -0
- package/src/lockfile/types.ts +95 -0
- package/src/runtime/index.ts +114 -0
- package/src/server/api/index.ts +92 -0
- package/src/server/api/middleware.ts +118 -0
- package/src/server/api/router.ts +102 -0
- package/src/server/mcp/index.ts +182 -0
- package/src/server/mcp/tools.ts +199 -0
- package/src/server/resolver.ts +109 -0
- package/src/server/start.ts +151 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
3
|
+
import type { OntologyConfig } from "../config/types.js";
|
|
4
|
+
import type { OntologyDiff, FunctionChange } from "../lockfile/types.js";
|
|
5
|
+
import { getFieldFromMetadata } from "../config/categorical.js";
|
|
6
|
+
|
|
7
|
+
export type NodeType = "entity" | "function" | "accessGroup";
|
|
8
|
+
export type EdgeType = "operates-on" | "requires-access" | "depends-on";
|
|
9
|
+
export type ChangeStatus = "added" | "removed" | "modified" | "unchanged";
|
|
10
|
+
|
|
11
|
+
export interface GraphNode {
|
|
12
|
+
id: string;
|
|
13
|
+
type: NodeType;
|
|
14
|
+
label: string;
|
|
15
|
+
description: string;
|
|
16
|
+
metadata: {
|
|
17
|
+
inputs?: Record<string, unknown>;
|
|
18
|
+
outputs?: Record<string, unknown>;
|
|
19
|
+
resolver?: string;
|
|
20
|
+
functionCount?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GraphEdge {
|
|
25
|
+
id: string;
|
|
26
|
+
source: string;
|
|
27
|
+
target: string;
|
|
28
|
+
type: EdgeType;
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GraphData {
|
|
33
|
+
nodes: GraphNode[];
|
|
34
|
+
edges: GraphEdge[];
|
|
35
|
+
meta: {
|
|
36
|
+
ontologyName: string;
|
|
37
|
+
totalFunctions: number;
|
|
38
|
+
totalEntities: number;
|
|
39
|
+
totalAccessGroups: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EnhancedGraphNode extends GraphNode {
|
|
44
|
+
changeStatus: ChangeStatus;
|
|
45
|
+
changeDetails?: FunctionChange;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface EnhancedGraphData {
|
|
49
|
+
nodes: EnhancedGraphNode[];
|
|
50
|
+
edges: GraphEdge[];
|
|
51
|
+
meta: GraphData["meta"] & {
|
|
52
|
+
hasChanges: boolean;
|
|
53
|
+
addedCount: number;
|
|
54
|
+
removedCount: number;
|
|
55
|
+
modifiedCount: number;
|
|
56
|
+
};
|
|
57
|
+
diff: OntologyDiff | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface FieldReference {
|
|
61
|
+
path: string;
|
|
62
|
+
functionName: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Recursively extract fieldFrom references from a Zod schema
|
|
67
|
+
*/
|
|
68
|
+
function extractFieldReferences(
|
|
69
|
+
schema: z.ZodType<unknown>,
|
|
70
|
+
path: string = ""
|
|
71
|
+
): FieldReference[] {
|
|
72
|
+
const results: FieldReference[] = [];
|
|
73
|
+
|
|
74
|
+
const metadata = getFieldFromMetadata(schema);
|
|
75
|
+
if (metadata) {
|
|
76
|
+
results.push({
|
|
77
|
+
path: path || "(root)",
|
|
78
|
+
functionName: metadata.functionName,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (schema instanceof z.ZodObject) {
|
|
83
|
+
const shape = schema.shape;
|
|
84
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
85
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
86
|
+
results.push(
|
|
87
|
+
...extractFieldReferences(value as z.ZodType<unknown>, fieldPath)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (schema instanceof z.ZodOptional) {
|
|
93
|
+
results.push(...extractFieldReferences(schema.unwrap(), path));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (schema instanceof z.ZodNullable) {
|
|
97
|
+
results.push(...extractFieldReferences(schema.unwrap(), path));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (schema instanceof z.ZodArray) {
|
|
101
|
+
results.push(...extractFieldReferences(schema.element, `${path}[]`));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (schema instanceof z.ZodDefault) {
|
|
105
|
+
results.push(...extractFieldReferences(schema._def.innerType, path));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert Zod schema to JSON Schema safely
|
|
113
|
+
*/
|
|
114
|
+
function safeZodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown> | undefined {
|
|
115
|
+
try {
|
|
116
|
+
const result = zodToJsonSchema(schema, { $refStrategy: "none" }) as Record<string, unknown>;
|
|
117
|
+
delete result.$schema;
|
|
118
|
+
return result;
|
|
119
|
+
} catch {
|
|
120
|
+
return { type: "unknown" };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Transform OntologyConfig into graph data for visualization
|
|
126
|
+
*/
|
|
127
|
+
export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
128
|
+
const nodes: GraphNode[] = [];
|
|
129
|
+
const edges: GraphEdge[] = [];
|
|
130
|
+
|
|
131
|
+
// Track function counts for access groups and entities
|
|
132
|
+
const accessGroupCounts: Record<string, number> = {};
|
|
133
|
+
const entityCounts: Record<string, number> = {};
|
|
134
|
+
|
|
135
|
+
// Initialize counts
|
|
136
|
+
for (const groupName of Object.keys(config.accessGroups)) {
|
|
137
|
+
accessGroupCounts[groupName] = 0;
|
|
138
|
+
}
|
|
139
|
+
if (config.entities) {
|
|
140
|
+
for (const entityName of Object.keys(config.entities)) {
|
|
141
|
+
entityCounts[entityName] = 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create function nodes and count relationships
|
|
146
|
+
for (const [name, fn] of Object.entries(config.functions)) {
|
|
147
|
+
// Count access group usage
|
|
148
|
+
for (const group of fn.access) {
|
|
149
|
+
accessGroupCounts[group] = (accessGroupCounts[group] || 0) + 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Count entity usage
|
|
153
|
+
for (const entity of fn.entities) {
|
|
154
|
+
entityCounts[entity] = (entityCounts[entity] || 0) + 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Create function node
|
|
158
|
+
nodes.push({
|
|
159
|
+
id: `function:${name}`,
|
|
160
|
+
type: "function",
|
|
161
|
+
label: name,
|
|
162
|
+
description: fn.description,
|
|
163
|
+
metadata: {
|
|
164
|
+
inputs: safeZodToJsonSchema(fn.inputs),
|
|
165
|
+
outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
|
|
166
|
+
resolver: fn.resolver,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Create edges: function -> access groups
|
|
171
|
+
for (const group of fn.access) {
|
|
172
|
+
edges.push({
|
|
173
|
+
id: `function:${name}->accessGroup:${group}`,
|
|
174
|
+
source: `function:${name}`,
|
|
175
|
+
target: `accessGroup:${group}`,
|
|
176
|
+
type: "requires-access",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Create edges: function -> entities
|
|
181
|
+
for (const entity of fn.entities) {
|
|
182
|
+
edges.push({
|
|
183
|
+
id: `function:${name}->entity:${entity}`,
|
|
184
|
+
source: `function:${name}`,
|
|
185
|
+
target: `entity:${entity}`,
|
|
186
|
+
type: "operates-on",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create edges: function -> function (fieldFrom dependencies)
|
|
191
|
+
const fieldRefs = extractFieldReferences(fn.inputs);
|
|
192
|
+
for (const ref of fieldRefs) {
|
|
193
|
+
edges.push({
|
|
194
|
+
id: `function:${name}->function:${ref.functionName}:${ref.path}`,
|
|
195
|
+
source: `function:${name}`,
|
|
196
|
+
target: `function:${ref.functionName}`,
|
|
197
|
+
type: "depends-on",
|
|
198
|
+
label: ref.path,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Create access group nodes
|
|
204
|
+
for (const [name, group] of Object.entries(config.accessGroups)) {
|
|
205
|
+
nodes.push({
|
|
206
|
+
id: `accessGroup:${name}`,
|
|
207
|
+
type: "accessGroup",
|
|
208
|
+
label: name,
|
|
209
|
+
description: group.description,
|
|
210
|
+
metadata: {
|
|
211
|
+
functionCount: accessGroupCounts[name] || 0,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create entity nodes
|
|
217
|
+
if (config.entities) {
|
|
218
|
+
for (const [name, entity] of Object.entries(config.entities)) {
|
|
219
|
+
nodes.push({
|
|
220
|
+
id: `entity:${name}`,
|
|
221
|
+
type: "entity",
|
|
222
|
+
label: name,
|
|
223
|
+
description: entity.description,
|
|
224
|
+
metadata: {
|
|
225
|
+
functionCount: entityCounts[name] || 0,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
nodes,
|
|
233
|
+
edges,
|
|
234
|
+
meta: {
|
|
235
|
+
ontologyName: config.name,
|
|
236
|
+
totalFunctions: Object.keys(config.functions).length,
|
|
237
|
+
totalEntities: config.entities ? Object.keys(config.entities).length : 0,
|
|
238
|
+
totalAccessGroups: Object.keys(config.accessGroups).length,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Search nodes by label or description
|
|
245
|
+
*/
|
|
246
|
+
export function searchNodes(
|
|
247
|
+
graphData: GraphData,
|
|
248
|
+
query: string
|
|
249
|
+
): Array<{ id: string; type: NodeType; label: string; matchType: "label" | "description" }> {
|
|
250
|
+
const lowerQuery = query.toLowerCase();
|
|
251
|
+
const results: Array<{ id: string; type: NodeType; label: string; matchType: "label" | "description" }> = [];
|
|
252
|
+
|
|
253
|
+
for (const node of graphData.nodes) {
|
|
254
|
+
if (node.label.toLowerCase().includes(lowerQuery)) {
|
|
255
|
+
results.push({ id: node.id, type: node.type, label: node.label, matchType: "label" });
|
|
256
|
+
} else if (node.description.toLowerCase().includes(lowerQuery)) {
|
|
257
|
+
results.push({ id: node.id, type: node.type, label: node.label, matchType: "description" });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return results;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get detailed node information including connections
|
|
266
|
+
*/
|
|
267
|
+
export function getNodeDetails(
|
|
268
|
+
graphData: GraphData,
|
|
269
|
+
nodeId: string
|
|
270
|
+
): {
|
|
271
|
+
node: GraphNode | null;
|
|
272
|
+
connections: {
|
|
273
|
+
accessGroups: string[];
|
|
274
|
+
entities: string[];
|
|
275
|
+
dependsOn: Array<{ functionName: string; path: string }>;
|
|
276
|
+
dependedOnBy: string[];
|
|
277
|
+
functions: Array<{
|
|
278
|
+
name: string;
|
|
279
|
+
description: string;
|
|
280
|
+
inputs?: Record<string, unknown>;
|
|
281
|
+
outputs?: Record<string, unknown>;
|
|
282
|
+
}>;
|
|
283
|
+
};
|
|
284
|
+
} {
|
|
285
|
+
const node = graphData.nodes.find((n) => n.id === nodeId) || null;
|
|
286
|
+
|
|
287
|
+
const connections = {
|
|
288
|
+
accessGroups: [] as string[],
|
|
289
|
+
entities: [] as string[],
|
|
290
|
+
dependsOn: [] as Array<{ functionName: string; path: string }>,
|
|
291
|
+
dependedOnBy: [] as string[],
|
|
292
|
+
functions: [] as Array<{
|
|
293
|
+
name: string;
|
|
294
|
+
description: string;
|
|
295
|
+
inputs?: Record<string, unknown>;
|
|
296
|
+
outputs?: Record<string, unknown>;
|
|
297
|
+
}>,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
if (!node) return { node, connections };
|
|
301
|
+
|
|
302
|
+
for (const edge of graphData.edges) {
|
|
303
|
+
if (edge.source === nodeId) {
|
|
304
|
+
if (edge.type === "requires-access") {
|
|
305
|
+
connections.accessGroups.push(edge.target.replace("accessGroup:", ""));
|
|
306
|
+
} else if (edge.type === "operates-on") {
|
|
307
|
+
connections.entities.push(edge.target.replace("entity:", ""));
|
|
308
|
+
} else if (edge.type === "depends-on") {
|
|
309
|
+
connections.dependsOn.push({
|
|
310
|
+
functionName: edge.target.replace("function:", ""),
|
|
311
|
+
path: edge.label || "",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (edge.target === nodeId) {
|
|
317
|
+
if (edge.type === "depends-on") {
|
|
318
|
+
connections.dependedOnBy.push(edge.source.replace("function:", ""));
|
|
319
|
+
} else if (edge.type === "requires-access" && node.type === "accessGroup") {
|
|
320
|
+
const funcName = edge.source.replace("function:", "");
|
|
321
|
+
const funcNode = graphData.nodes.find((n) => n.id === edge.source);
|
|
322
|
+
if (funcNode) {
|
|
323
|
+
connections.functions.push({
|
|
324
|
+
name: funcName,
|
|
325
|
+
description: funcNode.description,
|
|
326
|
+
inputs: funcNode.metadata.inputs,
|
|
327
|
+
outputs: funcNode.metadata.outputs,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
} else if (edge.type === "operates-on" && node.type === "entity") {
|
|
331
|
+
const funcName = edge.source.replace("function:", "");
|
|
332
|
+
const funcNode = graphData.nodes.find((n) => n.id === edge.source);
|
|
333
|
+
if (funcNode) {
|
|
334
|
+
connections.functions.push({
|
|
335
|
+
name: funcName,
|
|
336
|
+
description: funcNode.description,
|
|
337
|
+
inputs: funcNode.metadata.inputs,
|
|
338
|
+
outputs: funcNode.metadata.outputs,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { node, connections };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Enhance graph data with diff information for review mode
|
|
350
|
+
*/
|
|
351
|
+
export function enhanceWithDiff(
|
|
352
|
+
graphData: GraphData,
|
|
353
|
+
diff: OntologyDiff | null
|
|
354
|
+
): EnhancedGraphData {
|
|
355
|
+
if (!diff) {
|
|
356
|
+
// No diff - all nodes are unchanged
|
|
357
|
+
return {
|
|
358
|
+
nodes: graphData.nodes.map((node) => ({
|
|
359
|
+
...node,
|
|
360
|
+
changeStatus: "unchanged" as ChangeStatus,
|
|
361
|
+
})),
|
|
362
|
+
edges: graphData.edges,
|
|
363
|
+
meta: {
|
|
364
|
+
...graphData.meta,
|
|
365
|
+
hasChanges: false,
|
|
366
|
+
addedCount: 0,
|
|
367
|
+
removedCount: 0,
|
|
368
|
+
modifiedCount: 0,
|
|
369
|
+
},
|
|
370
|
+
diff: null,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Build lookup maps for changes
|
|
375
|
+
const functionChanges = new Map<string, FunctionChange>();
|
|
376
|
+
for (const fn of diff.functions) {
|
|
377
|
+
functionChanges.set(fn.name, fn);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const addedGroups = new Set(diff.addedGroups);
|
|
381
|
+
const removedGroups = new Set(diff.removedGroups);
|
|
382
|
+
const addedEntities = new Set(diff.addedEntities);
|
|
383
|
+
const removedEntities = new Set(diff.removedEntities);
|
|
384
|
+
|
|
385
|
+
// Enhance existing nodes with change status
|
|
386
|
+
const enhancedNodes: EnhancedGraphNode[] = graphData.nodes.map((node) => {
|
|
387
|
+
let changeStatus: ChangeStatus = "unchanged";
|
|
388
|
+
let changeDetails: FunctionChange | undefined;
|
|
389
|
+
|
|
390
|
+
if (node.type === "function") {
|
|
391
|
+
const fnName = node.label;
|
|
392
|
+
const change = functionChanges.get(fnName);
|
|
393
|
+
if (change) {
|
|
394
|
+
changeStatus = change.type;
|
|
395
|
+
changeDetails = change;
|
|
396
|
+
}
|
|
397
|
+
} else if (node.type === "accessGroup") {
|
|
398
|
+
if (addedGroups.has(node.label)) {
|
|
399
|
+
changeStatus = "added";
|
|
400
|
+
}
|
|
401
|
+
} else if (node.type === "entity") {
|
|
402
|
+
if (addedEntities.has(node.label)) {
|
|
403
|
+
changeStatus = "added";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
...node,
|
|
409
|
+
changeStatus,
|
|
410
|
+
changeDetails,
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Add ghost nodes for removed items
|
|
415
|
+
for (const group of removedGroups) {
|
|
416
|
+
enhancedNodes.push({
|
|
417
|
+
id: `accessGroup:${group}`,
|
|
418
|
+
type: "accessGroup",
|
|
419
|
+
label: group,
|
|
420
|
+
description: "Removed access group",
|
|
421
|
+
metadata: { functionCount: 0 },
|
|
422
|
+
changeStatus: "removed",
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const entity of removedEntities) {
|
|
427
|
+
enhancedNodes.push({
|
|
428
|
+
id: `entity:${entity}`,
|
|
429
|
+
type: "entity",
|
|
430
|
+
label: entity,
|
|
431
|
+
description: "Removed entity",
|
|
432
|
+
metadata: { functionCount: 0 },
|
|
433
|
+
changeStatus: "removed",
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Add ghost nodes for removed functions
|
|
438
|
+
for (const fn of diff.functions.filter((f) => f.type === "removed")) {
|
|
439
|
+
enhancedNodes.push({
|
|
440
|
+
id: `function:${fn.name}`,
|
|
441
|
+
type: "function",
|
|
442
|
+
label: fn.name,
|
|
443
|
+
description: fn.oldDescription || "Removed function",
|
|
444
|
+
metadata: {},
|
|
445
|
+
changeStatus: "removed",
|
|
446
|
+
changeDetails: fn,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Count changes
|
|
451
|
+
const addedCount =
|
|
452
|
+
diff.addedGroups.length +
|
|
453
|
+
diff.addedEntities.length +
|
|
454
|
+
diff.functions.filter((f) => f.type === "added").length;
|
|
455
|
+
const removedCount =
|
|
456
|
+
diff.removedGroups.length +
|
|
457
|
+
diff.removedEntities.length +
|
|
458
|
+
diff.functions.filter((f) => f.type === "removed").length;
|
|
459
|
+
const modifiedCount = diff.functions.filter((f) => f.type === "modified").length;
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
nodes: enhancedNodes,
|
|
463
|
+
edges: graphData.edges,
|
|
464
|
+
meta: {
|
|
465
|
+
...graphData.meta,
|
|
466
|
+
hasChanges: diff.hasChanges,
|
|
467
|
+
addedCount,
|
|
468
|
+
removedCount,
|
|
469
|
+
modifiedCount,
|
|
470
|
+
},
|
|
471
|
+
diff,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import consola from "consola";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Templates are bundled in the package
|
|
11
|
+
const TEMPLATES_DIR = join(__dirname, "..", "..", "..", "templates");
|
|
12
|
+
|
|
13
|
+
export const initCommand = defineCommand({
|
|
14
|
+
meta: {
|
|
15
|
+
name: "init",
|
|
16
|
+
description: "Initialize a new Ontology project",
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
dir: {
|
|
20
|
+
type: "positional",
|
|
21
|
+
description: "Directory to initialize (default: current directory)",
|
|
22
|
+
default: ".",
|
|
23
|
+
},
|
|
24
|
+
force: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
description: "Overwrite existing files",
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
async run({ args }) {
|
|
31
|
+
const targetDir = args.dir === "." ? process.cwd() : join(process.cwd(), args.dir);
|
|
32
|
+
|
|
33
|
+
consola.info(`Initializing Ontology project in ${targetDir}`);
|
|
34
|
+
|
|
35
|
+
// Create directory if needed
|
|
36
|
+
if (!existsSync(targetDir)) {
|
|
37
|
+
mkdirSync(targetDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for existing config
|
|
41
|
+
const configPath = join(targetDir, "ontology.config.ts");
|
|
42
|
+
if (existsSync(configPath) && !args.force) {
|
|
43
|
+
consola.error("ontology.config.ts already exists. Use --force to overwrite.");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create resolvers directory
|
|
48
|
+
const resolversDir = join(targetDir, "resolvers");
|
|
49
|
+
if (!existsSync(resolversDir)) {
|
|
50
|
+
mkdirSync(resolversDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Write ontology.config.ts
|
|
54
|
+
const configTemplate = `import { defineOntology } from 'ont-run';
|
|
55
|
+
import { z } from 'zod';
|
|
56
|
+
|
|
57
|
+
export default defineOntology({
|
|
58
|
+
name: 'my-api',
|
|
59
|
+
|
|
60
|
+
environments: {
|
|
61
|
+
dev: { debug: true },
|
|
62
|
+
prod: { debug: false },
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Pluggable auth - customize this for your use case
|
|
66
|
+
auth: async (req) => {
|
|
67
|
+
const token = req.headers.get('Authorization');
|
|
68
|
+
// Return access groups based on auth
|
|
69
|
+
// This is where you'd verify JWTs, API keys, etc.
|
|
70
|
+
if (!token) return ['public'];
|
|
71
|
+
if (token === 'admin-secret') return ['admin', 'support', 'public'];
|
|
72
|
+
return ['support', 'public'];
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
accessGroups: {
|
|
76
|
+
public: { description: 'Unauthenticated users' },
|
|
77
|
+
support: { description: 'Support agents' },
|
|
78
|
+
admin: { description: 'Administrators' },
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
functions: {
|
|
82
|
+
// Example: Public function
|
|
83
|
+
healthCheck: {
|
|
84
|
+
description: 'Check API health status',
|
|
85
|
+
access: ['public', 'support', 'admin'],
|
|
86
|
+
inputs: z.object({}),
|
|
87
|
+
resolver: './resolvers/healthCheck.ts',
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Example: Restricted function
|
|
91
|
+
getUser: {
|
|
92
|
+
description: 'Get user details by ID',
|
|
93
|
+
access: ['support', 'admin'],
|
|
94
|
+
inputs: z.object({
|
|
95
|
+
userId: z.string().uuid(),
|
|
96
|
+
}),
|
|
97
|
+
resolver: './resolvers/getUser.ts',
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Example: Admin-only function
|
|
101
|
+
deleteUser: {
|
|
102
|
+
description: 'Delete a user account',
|
|
103
|
+
access: ['admin'],
|
|
104
|
+
inputs: z.object({
|
|
105
|
+
userId: z.string().uuid(),
|
|
106
|
+
reason: z.string().optional(),
|
|
107
|
+
}),
|
|
108
|
+
resolver: './resolvers/deleteUser.ts',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
writeFileSync(configPath, configTemplate);
|
|
115
|
+
consola.success("Created ontology.config.ts");
|
|
116
|
+
|
|
117
|
+
// Write example resolvers
|
|
118
|
+
const healthCheckResolver = `import type { ResolverContext } from 'ont-run';
|
|
119
|
+
|
|
120
|
+
export default async function healthCheck(ctx: ResolverContext, args: {}) {
|
|
121
|
+
ctx.logger.info('Health check called');
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
status: 'ok',
|
|
125
|
+
env: ctx.env,
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
const getUserResolver = `import type { ResolverContext } from 'ont-run';
|
|
132
|
+
|
|
133
|
+
interface GetUserArgs {
|
|
134
|
+
userId: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default async function getUser(ctx: ResolverContext, args: GetUserArgs) {
|
|
138
|
+
ctx.logger.info(\`Getting user: \${args.userId}\`);
|
|
139
|
+
|
|
140
|
+
// This is where you'd query your database
|
|
141
|
+
// Example response:
|
|
142
|
+
return {
|
|
143
|
+
id: args.userId,
|
|
144
|
+
name: 'Example User',
|
|
145
|
+
email: 'user@example.com',
|
|
146
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
const deleteUserResolver = `import type { ResolverContext } from 'ont-run';
|
|
152
|
+
|
|
153
|
+
interface DeleteUserArgs {
|
|
154
|
+
userId: string;
|
|
155
|
+
reason?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default async function deleteUser(ctx: ResolverContext, args: DeleteUserArgs) {
|
|
159
|
+
ctx.logger.warn(\`Deleting user: \${args.userId}, reason: \${args.reason || 'none'}\`);
|
|
160
|
+
|
|
161
|
+
// This is where you'd delete from your database
|
|
162
|
+
// Example response:
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
deletedUserId: args.userId,
|
|
166
|
+
deletedAt: new Date().toISOString(),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
writeFileSync(join(resolversDir, "healthCheck.ts"), healthCheckResolver);
|
|
172
|
+
writeFileSync(join(resolversDir, "getUser.ts"), getUserResolver);
|
|
173
|
+
writeFileSync(join(resolversDir, "deleteUser.ts"), deleteUserResolver);
|
|
174
|
+
consola.success("Created example resolvers in resolvers/");
|
|
175
|
+
|
|
176
|
+
// Write server.ts
|
|
177
|
+
const serverTemplate = `import { startOnt } from 'ont-run';
|
|
178
|
+
|
|
179
|
+
await startOnt();
|
|
180
|
+
`;
|
|
181
|
+
|
|
182
|
+
writeFileSync(join(targetDir, "server.ts"), serverTemplate);
|
|
183
|
+
consola.success("Created server.ts");
|
|
184
|
+
|
|
185
|
+
// Write package.json
|
|
186
|
+
const packageJsonPath = join(targetDir, "package.json");
|
|
187
|
+
let packageJson: Record<string, unknown> = {};
|
|
188
|
+
|
|
189
|
+
if (existsSync(packageJsonPath)) {
|
|
190
|
+
try {
|
|
191
|
+
packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
192
|
+
} catch {
|
|
193
|
+
// If parsing fails, start fresh
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Merge in our scripts and dependencies
|
|
198
|
+
packageJson.type = "module";
|
|
199
|
+
packageJson.scripts = {
|
|
200
|
+
...(packageJson.scripts as Record<string, string> || {}),
|
|
201
|
+
dev: "bun run server.ts",
|
|
202
|
+
start: "NODE_ENV=production bun run server.ts",
|
|
203
|
+
review: "bunx ont-run review",
|
|
204
|
+
};
|
|
205
|
+
packageJson.dependencies = {
|
|
206
|
+
...(packageJson.dependencies as Record<string, string> || {}),
|
|
207
|
+
"ont-run": "latest",
|
|
208
|
+
zod: "^3.24.0",
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
212
|
+
consola.success("Updated package.json with scripts and dependencies");
|
|
213
|
+
|
|
214
|
+
// Instructions
|
|
215
|
+
console.log("\n");
|
|
216
|
+
consola.box(
|
|
217
|
+
"Ontology project initialized!\n\n" +
|
|
218
|
+
"Next steps:\n" +
|
|
219
|
+
" 1. Run `bun install` to install dependencies\n" +
|
|
220
|
+
" 2. Review ontology.config.ts and customize\n" +
|
|
221
|
+
" 3. Run `bun run review` to approve the initial ontology\n" +
|
|
222
|
+
" 4. Run `bun run dev` to start the servers\n\n" +
|
|
223
|
+
"Your API will be available at http://localhost:3000"
|
|
224
|
+
);
|
|
225
|
+
},
|
|
226
|
+
});
|