@grafema/api 0.2.5-beta

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.
Files changed (70) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +219 -0
  3. package/dist/context.d.ts +22 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/context.js +18 -0
  6. package/dist/context.js.map +1 -0
  7. package/dist/dataloaders/index.d.ts +18 -0
  8. package/dist/dataloaders/index.d.ts.map +1 -0
  9. package/dist/dataloaders/index.js +17 -0
  10. package/dist/dataloaders/index.js.map +1 -0
  11. package/dist/dataloaders/nodeLoader.d.ts +19 -0
  12. package/dist/dataloaders/nodeLoader.d.ts.map +1 -0
  13. package/dist/dataloaders/nodeLoader.js +31 -0
  14. package/dist/dataloaders/nodeLoader.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +9 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/pagination.d.ts +50 -0
  20. package/dist/pagination.d.ts.map +1 -0
  21. package/dist/pagination.js +71 -0
  22. package/dist/pagination.js.map +1 -0
  23. package/dist/resolvers/edge.d.ts +22 -0
  24. package/dist/resolvers/edge.d.ts.map +1 -0
  25. package/dist/resolvers/edge.js +36 -0
  26. package/dist/resolvers/edge.js.map +1 -0
  27. package/dist/resolvers/index.d.ts +159 -0
  28. package/dist/resolvers/index.d.ts.map +1 -0
  29. package/dist/resolvers/index.js +21 -0
  30. package/dist/resolvers/index.js.map +1 -0
  31. package/dist/resolvers/mutation.d.ts +69 -0
  32. package/dist/resolvers/mutation.d.ts.map +1 -0
  33. package/dist/resolvers/mutation.js +82 -0
  34. package/dist/resolvers/mutation.js.map +1 -0
  35. package/dist/resolvers/node.d.ts +50 -0
  36. package/dist/resolvers/node.d.ts.map +1 -0
  37. package/dist/resolvers/node.js +69 -0
  38. package/dist/resolvers/node.js.map +1 -0
  39. package/dist/resolvers/query.d.ts +169 -0
  40. package/dist/resolvers/query.d.ts.map +1 -0
  41. package/dist/resolvers/query.js +188 -0
  42. package/dist/resolvers/query.js.map +1 -0
  43. package/dist/schema/enums.graphql +27 -0
  44. package/dist/schema/mutations.graphql +53 -0
  45. package/dist/schema/queries.graphql +213 -0
  46. package/dist/schema/scalars.graphql +2 -0
  47. package/dist/schema/subscriptions.graphql +84 -0
  48. package/dist/schema/types.graphql +440 -0
  49. package/dist/server.d.ts +31 -0
  50. package/dist/server.d.ts.map +1 -0
  51. package/dist/server.js +109 -0
  52. package/dist/server.js.map +1 -0
  53. package/package.json +51 -0
  54. package/src/context.ts +33 -0
  55. package/src/dataloaders/index.ts +24 -0
  56. package/src/dataloaders/nodeLoader.ts +41 -0
  57. package/src/index.ts +11 -0
  58. package/src/pagination.ts +109 -0
  59. package/src/resolvers/edge.ts +39 -0
  60. package/src/resolvers/index.ts +24 -0
  61. package/src/resolvers/mutation.ts +108 -0
  62. package/src/resolvers/node.ts +118 -0
  63. package/src/resolvers/query.ts +307 -0
  64. package/src/schema/enums.graphql +27 -0
  65. package/src/schema/mutations.graphql +53 -0
  66. package/src/schema/queries.graphql +213 -0
  67. package/src/schema/scalars.graphql +2 -0
  68. package/src/schema/subscriptions.graphql +84 -0
  69. package/src/schema/types.graphql +440 -0
  70. package/src/server.ts +140 -0
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Query Resolvers
3
+ *
4
+ * Implements all Query type fields.
5
+ */
6
+
7
+ import type { BaseNodeRecord } from '@grafema/types';
8
+ import type { GraphQLContext } from '../context.js';
9
+ import { paginateArray } from '../pagination.js';
10
+
11
+ export interface NodeFilter {
12
+ type?: string | null;
13
+ name?: string | null;
14
+ file?: string | null;
15
+ exported?: boolean | null;
16
+ }
17
+
18
+ export const queryResolvers = {
19
+ /**
20
+ * Get node by ID.
21
+ *
22
+ * Complexity: O(1)
23
+ */
24
+ async node(
25
+ _: unknown,
26
+ args: { id: string },
27
+ context: GraphQLContext
28
+ ) {
29
+ return context.loaders.node.load(args.id);
30
+ },
31
+
32
+ /**
33
+ * Find nodes matching criteria with cursor-based pagination.
34
+ *
35
+ * Complexity: O(n) where n = nodes matching type filter
36
+ * This is acceptable because:
37
+ * - We filter by type first (uses RFDB's type index)
38
+ * - Results are paginated
39
+ */
40
+ async nodes(
41
+ _: unknown,
42
+ args: {
43
+ filter?: NodeFilter | null;
44
+ first?: number | null;
45
+ after?: string | null;
46
+ },
47
+ context: GraphQLContext
48
+ ) {
49
+ const filter = args.filter || {};
50
+
51
+ // Build query for backend
52
+ const query: Record<string, unknown> = {};
53
+ if (filter.type) query.type = filter.type;
54
+ if (filter.name) query.name = filter.name;
55
+ if (filter.file) query.file = filter.file;
56
+
57
+ // Get all matching nodes
58
+ const nodes = await context.backend.getAllNodes(query);
59
+
60
+ // Apply exported filter (not supported by backend query)
61
+ let filteredNodes = nodes;
62
+ if (filter.exported !== null && filter.exported !== undefined) {
63
+ filteredNodes = nodes.filter((n) => n.exported === filter.exported);
64
+ }
65
+
66
+ return paginateArray(
67
+ filteredNodes,
68
+ args.first,
69
+ args.after,
70
+ (n: BaseNodeRecord) => n.id
71
+ );
72
+ },
73
+
74
+ /**
75
+ * BFS traversal.
76
+ *
77
+ * Complexity: O(V + E) for reachable subgraph
78
+ * Bounded by maxDepth parameter.
79
+ */
80
+ async bfs(
81
+ _: unknown,
82
+ args: { startIds: string[]; maxDepth: number; edgeTypes: string[] },
83
+ context: GraphQLContext
84
+ ) {
85
+ return context.backend.bfs(args.startIds, args.maxDepth, args.edgeTypes);
86
+ },
87
+
88
+ /**
89
+ * DFS traversal.
90
+ *
91
+ * Complexity: O(V + E) for reachable subgraph
92
+ */
93
+ async dfs(
94
+ _: unknown,
95
+ args: { startIds: string[]; maxDepth: number; edgeTypes?: string[] | null },
96
+ context: GraphQLContext
97
+ ) {
98
+ return context.backend.dfs(
99
+ args.startIds,
100
+ args.maxDepth,
101
+ args.edgeTypes || []
102
+ );
103
+ },
104
+
105
+ /**
106
+ * Reachability check.
107
+ *
108
+ * Complexity: O(V + E) worst case, often O(d) with early termination
109
+ */
110
+ async reachability(
111
+ _: unknown,
112
+ args: {
113
+ from: string;
114
+ to: string;
115
+ edgeTypes?: string[] | null;
116
+ maxDepth?: number | null;
117
+ },
118
+ context: GraphQLContext
119
+ ) {
120
+ const maxDepth = args.maxDepth ?? 10;
121
+ const reachable = await context.backend.bfs(
122
+ [args.from],
123
+ maxDepth,
124
+ args.edgeTypes || []
125
+ );
126
+ return reachable.includes(args.to);
127
+ },
128
+
129
+ /**
130
+ * Execute Datalog query.
131
+ *
132
+ * Complexity: Depends on query, bounded by RFDB's timeout
133
+ */
134
+ async datalog(
135
+ _: unknown,
136
+ args: { query: string; limit?: number | null; offset?: number | null },
137
+ context: GraphQLContext
138
+ ) {
139
+ const limit = args.limit ?? 50;
140
+ const offset = args.offset ?? 0;
141
+
142
+ try {
143
+ const results = await context.backend.checkGuarantee(args.query);
144
+ const total = results.length;
145
+ const paginatedResults = results.slice(offset, offset + limit);
146
+
147
+ // Enrich with node data
148
+ const enrichedResults = await Promise.all(
149
+ paginatedResults.map(async (r) => {
150
+ const bindings = r.bindings || [];
151
+ const xBinding = bindings.find((b) => b.name === 'X');
152
+ const nodeId = xBinding?.value;
153
+ const node = nodeId
154
+ ? await context.loaders.node.load(String(nodeId))
155
+ : null;
156
+ return {
157
+ bindings: Object.fromEntries(
158
+ bindings.map((b) => [b.name, b.value])
159
+ ),
160
+ node,
161
+ };
162
+ })
163
+ );
164
+
165
+ return {
166
+ success: true,
167
+ count: total,
168
+ results: enrichedResults,
169
+ error: null,
170
+ };
171
+ } catch (error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ return {
174
+ success: false,
175
+ count: 0,
176
+ results: [],
177
+ error: message,
178
+ };
179
+ }
180
+ },
181
+
182
+ /**
183
+ * Get graph statistics.
184
+ *
185
+ * Complexity: O(1) - cached in backend
186
+ */
187
+ async stats(_: unknown, _args: unknown, context: GraphQLContext) {
188
+ return context.backend.getStats();
189
+ },
190
+
191
+ /**
192
+ * Get analysis status.
193
+ * Placeholder - actual implementation depends on analysis state tracking.
194
+ */
195
+ async analysisStatus(_: unknown, _args: unknown, _context: GraphQLContext) {
196
+ // Placeholder - would need to track analysis state
197
+ return {
198
+ running: false,
199
+ phase: null,
200
+ message: null,
201
+ servicesDiscovered: 0,
202
+ servicesAnalyzed: 0,
203
+ error: null,
204
+ };
205
+ },
206
+
207
+ /**
208
+ * List all guarantees.
209
+ * Placeholder - actual implementation depends on GuaranteeManager.
210
+ */
211
+ async guarantees(_: unknown, _args: unknown, _context: GraphQLContext) {
212
+ // Placeholder - would need GuaranteeManager integration
213
+ return [];
214
+ },
215
+
216
+ /**
217
+ * Get guarantee by ID.
218
+ * Placeholder.
219
+ */
220
+ async guarantee(
221
+ _: unknown,
222
+ _args: { id: string },
223
+ _context: GraphQLContext
224
+ ) {
225
+ return null;
226
+ },
227
+
228
+ /**
229
+ * Find calls to a function/method.
230
+ * Placeholder - would reuse MCP handler logic.
231
+ */
232
+ async findCalls(
233
+ _: unknown,
234
+ _args: {
235
+ target: string;
236
+ className?: string | null;
237
+ limit?: number | null;
238
+ offset?: number | null;
239
+ },
240
+ _context: GraphQLContext
241
+ ) {
242
+ // Placeholder - would reuse MCP find_calls handler
243
+ return [];
244
+ },
245
+
246
+ /**
247
+ * Get function details.
248
+ * Placeholder - would reuse MCP handler logic.
249
+ */
250
+ async getFunctionDetails(
251
+ _: unknown,
252
+ _args: {
253
+ name: string;
254
+ file?: string | null;
255
+ transitive?: boolean | null;
256
+ },
257
+ _context: GraphQLContext
258
+ ) {
259
+ // Placeholder - would reuse MCP get_function_details handler
260
+ return null;
261
+ },
262
+
263
+ /**
264
+ * Find guards protecting a node.
265
+ * Placeholder.
266
+ */
267
+ async findGuards(
268
+ _: unknown,
269
+ _args: { nodeId: string },
270
+ _context: GraphQLContext
271
+ ) {
272
+ return [];
273
+ },
274
+
275
+ /**
276
+ * Trace alias chain.
277
+ * Placeholder.
278
+ */
279
+ async traceAlias(
280
+ _: unknown,
281
+ _args: {
282
+ variableName: string;
283
+ file: string;
284
+ maxDepth?: number | null;
285
+ },
286
+ _context: GraphQLContext
287
+ ) {
288
+ return [];
289
+ },
290
+
291
+ /**
292
+ * Trace data flow.
293
+ * Placeholder.
294
+ */
295
+ async traceDataFlow(
296
+ _: unknown,
297
+ _args: {
298
+ source: string;
299
+ file?: string | null;
300
+ direction?: string | null;
301
+ maxDepth?: number | null;
302
+ },
303
+ _context: GraphQLContext
304
+ ) {
305
+ return [];
306
+ },
307
+ };
@@ -0,0 +1,27 @@
1
+ """
2
+ Direction for graph traversal.
3
+ """
4
+ enum TraversalDirection {
5
+ FORWARD
6
+ BACKWARD
7
+ BOTH
8
+ }
9
+
10
+ """
11
+ Severity levels for guarantees.
12
+ """
13
+ enum Severity {
14
+ ERROR
15
+ WARNING
16
+ INFO
17
+ }
18
+
19
+ """
20
+ Priority levels for contract guarantees.
21
+ """
22
+ enum Priority {
23
+ CRITICAL
24
+ IMPORTANT
25
+ OBSERVED
26
+ TRACKED
27
+ }
@@ -0,0 +1,53 @@
1
+ type Mutation {
2
+ # ============================================================================
3
+ # Analysis
4
+ # ============================================================================
5
+
6
+ """
7
+ Run project analysis.
8
+ Blocks until complete (with timeout).
9
+ """
10
+ analyzeProject(
11
+ """Optional: analyze only this service"""
12
+ service: String
13
+
14
+ """Force re-analysis even if already analyzed"""
15
+ force: Boolean
16
+ ): AnalysisResult!
17
+
18
+ # ============================================================================
19
+ # Guarantees
20
+ # ============================================================================
21
+
22
+ """
23
+ Create a new guarantee.
24
+
25
+ For Datalog-based: provide name + rule
26
+ For contract-based: provide name + type + priority
27
+ """
28
+ createGuarantee(input: CreateGuaranteeInput!): Guarantee!
29
+
30
+ """
31
+ Delete a guarantee by name.
32
+ """
33
+ deleteGuarantee(name: String!): Boolean!
34
+
35
+ """
36
+ Check all guarantees or specific ones.
37
+ """
38
+ checkGuarantees(
39
+ """Specific guarantee names to check (null = all)"""
40
+ names: [String!]
41
+ ): GuaranteeCheckResult!
42
+
43
+ """
44
+ Check a single ad-hoc invariant without persisting.
45
+ """
46
+ checkInvariant(
47
+ """Datalog rule"""
48
+ rule: String!
49
+
50
+ """Description for error messages"""
51
+ description: String
52
+ ): GuaranteeResult!
53
+ }
@@ -0,0 +1,213 @@
1
+ type Query {
2
+ # ============================================================================
3
+ # Node Lookups
4
+ # ============================================================================
5
+
6
+ """
7
+ Get a single node by ID.
8
+ Returns null if not found.
9
+ """
10
+ node(id: ID!): Node
11
+
12
+ """
13
+ Find nodes matching filter criteria.
14
+ Uses cursor-based pagination (Relay spec).
15
+ """
16
+ nodes(
17
+ """Filter criteria"""
18
+ filter: NodeFilter
19
+
20
+ """Number of items to return (default: 50, max: 250)"""
21
+ first: Int
22
+
23
+ """Cursor to start after"""
24
+ after: String
25
+ ): NodeConnection!
26
+
27
+ # ============================================================================
28
+ # Graph Traversal
29
+ # ============================================================================
30
+
31
+ """
32
+ BFS traversal from starting nodes.
33
+ Returns all node IDs reachable within maxDepth.
34
+
35
+ Complexity: O(V + E) where V = reachable nodes, E = traversed edges
36
+ """
37
+ bfs(
38
+ """Starting node IDs"""
39
+ startIds: [ID!]!
40
+
41
+ """Maximum traversal depth"""
42
+ maxDepth: Int!
43
+
44
+ """Edge types to traverse (empty = all)"""
45
+ edgeTypes: [String!]!
46
+ ): [ID!]!
47
+
48
+ """
49
+ DFS traversal from starting nodes.
50
+ Returns all node IDs reachable within maxDepth.
51
+
52
+ Complexity: O(V + E) where V = reachable nodes, E = traversed edges
53
+ """
54
+ dfs(
55
+ """Starting node IDs"""
56
+ startIds: [ID!]!
57
+
58
+ """Maximum traversal depth"""
59
+ maxDepth: Int!
60
+
61
+ """Edge types to traverse (null = all)"""
62
+ edgeTypes: [String!]
63
+ ): [ID!]!
64
+
65
+ """
66
+ Check if a path exists between two nodes.
67
+ Uses BFS internally with early termination.
68
+
69
+ Complexity: O(V + E) worst case, often much faster with early termination
70
+ """
71
+ reachability(
72
+ """Source node ID"""
73
+ from: ID!
74
+
75
+ """Target node ID"""
76
+ to: ID!
77
+
78
+ """Edge types to traverse (null = all)"""
79
+ edgeTypes: [String!]
80
+
81
+ """Maximum depth to search (default: 10)"""
82
+ maxDepth: Int
83
+ ): Boolean!
84
+
85
+ # ============================================================================
86
+ # Datalog Queries
87
+ # ============================================================================
88
+
89
+ """
90
+ Execute a Datalog query on the code graph.
91
+
92
+ The query must define a violation/1 predicate.
93
+
94
+ Available predicates:
95
+ - node(Id, Type) - match nodes by type
96
+ - edge(Src, Dst, Type) - match edges
97
+ - attr(Id, Name, Value) - match node attributes
98
+
99
+ Example: violation(X) :- node(X, "FUNCTION"), attr(X, "async", true).
100
+ """
101
+ datalog(
102
+ """Datalog query defining violation/1"""
103
+ query: String!
104
+
105
+ """Maximum results (default: 50)"""
106
+ limit: Int
107
+
108
+ """Results to skip (for simple pagination)"""
109
+ offset: Int
110
+ ): DatalogResult!
111
+
112
+ # ============================================================================
113
+ # High-Level Queries (from MCP handlers)
114
+ # ============================================================================
115
+
116
+ """
117
+ Find all calls to a function/method.
118
+ """
119
+ findCalls(
120
+ """Function or method name"""
121
+ target: String!
122
+
123
+ """Optional class name for method calls"""
124
+ className: String
125
+
126
+ """Maximum results"""
127
+ limit: Int
128
+
129
+ """Results to skip"""
130
+ offset: Int
131
+ ): [CallInfo!]!
132
+
133
+ """
134
+ Get comprehensive function details including calls and callers.
135
+ """
136
+ getFunctionDetails(
137
+ """Function name"""
138
+ name: String!
139
+
140
+ """File path for disambiguation"""
141
+ file: String
142
+
143
+ """Follow transitive call chains"""
144
+ transitive: Boolean
145
+ ): FunctionDetails
146
+
147
+ """
148
+ Find guards (conditional scopes) protecting a node.
149
+ """
150
+ findGuards(
151
+ """Node ID to find guards for"""
152
+ nodeId: ID!
153
+ ): [GuardInfo!]!
154
+
155
+ """
156
+ Trace variable alias chain to original source.
157
+ """
158
+ traceAlias(
159
+ """Variable name"""
160
+ variableName: String!
161
+
162
+ """File where variable is defined"""
163
+ file: String!
164
+
165
+ """Maximum trace depth (default: 20)"""
166
+ maxDepth: Int
167
+ ): [Node!]!
168
+
169
+ """
170
+ Trace data flow from/to a node.
171
+ """
172
+ traceDataFlow(
173
+ """Source node ID or variable name"""
174
+ source: String!
175
+
176
+ """File path"""
177
+ file: String
178
+
179
+ """Direction of trace"""
180
+ direction: TraversalDirection
181
+
182
+ """Maximum depth (default: 10)"""
183
+ maxDepth: Int
184
+ ): [[String!]!]!
185
+
186
+ # ============================================================================
187
+ # Guarantees
188
+ # ============================================================================
189
+
190
+ """
191
+ List all defined guarantees.
192
+ """
193
+ guarantees: [Guarantee!]!
194
+
195
+ """
196
+ Get a specific guarantee by ID.
197
+ """
198
+ guarantee(id: ID!): Guarantee
199
+
200
+ # ============================================================================
201
+ # Statistics
202
+ # ============================================================================
203
+
204
+ """
205
+ Get graph statistics.
206
+ """
207
+ stats: GraphStats!
208
+
209
+ """
210
+ Get current analysis status.
211
+ """
212
+ analysisStatus: AnalysisStatus!
213
+ }
@@ -0,0 +1,2 @@
1
+ """Custom JSON scalar for arbitrary metadata."""
2
+ scalar JSON
@@ -0,0 +1,84 @@
1
+ type Subscription {
2
+ """
3
+ Stream nodes matching filter as they're found.
4
+ Useful for UI visualization of large datasets.
5
+ """
6
+ nodesStream(
7
+ """Filter criteria"""
8
+ filter: NodeFilter
9
+
10
+ """Batch size for streaming (default: 100)"""
11
+ batchSize: Int
12
+ ): NodeBatch!
13
+
14
+ """
15
+ Stream BFS traversal results level by level.
16
+ Each batch contains all nodes at one depth level.
17
+ """
18
+ bfsStream(
19
+ """Starting node IDs"""
20
+ startIds: [ID!]!
21
+
22
+ """Maximum traversal depth"""
23
+ maxDepth: Int!
24
+
25
+ """Edge types to traverse"""
26
+ edgeTypes: [String!]!
27
+ ): TraversalBatch!
28
+
29
+ """
30
+ Stream analysis progress events.
31
+ """
32
+ analysisProgress(
33
+ """Optional: filter to specific service"""
34
+ service: String
35
+ ): AnalysisEvent!
36
+ }
37
+
38
+ """
39
+ Batch of nodes for streaming responses.
40
+ """
41
+ type NodeBatch {
42
+ """Nodes in this batch"""
43
+ nodes: [Node!]!
44
+
45
+ """Progress from 0.0 to 1.0"""
46
+ progress: Float!
47
+
48
+ """Whether streaming is complete"""
49
+ done: Boolean!
50
+ }
51
+
52
+ """
53
+ Batch of traversal results at a specific depth.
54
+ """
55
+ type TraversalBatch {
56
+ """Current depth level"""
57
+ depth: Int!
58
+
59
+ """Node IDs at this depth"""
60
+ nodeIds: [ID!]!
61
+
62
+ """Whether traversal is complete"""
63
+ done: Boolean!
64
+ }
65
+
66
+ """
67
+ Analysis progress event.
68
+ """
69
+ type AnalysisEvent {
70
+ """Current phase name"""
71
+ phase: String!
72
+
73
+ """Human-readable message"""
74
+ message: String!
75
+
76
+ """Progress from 0.0 to 1.0"""
77
+ progress: Float!
78
+
79
+ """Number of services completed"""
80
+ servicesCompleted: Int!
81
+
82
+ """Total number of services"""
83
+ servicesTotal: Int!
84
+ }