@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,440 @@
1
+ """
2
+ A node in the code graph representing a code entity.
3
+ """
4
+ type Node {
5
+ """Unique identifier for the node (e.g., "fn:src/api.ts:login:42")"""
6
+ id: ID!
7
+
8
+ """Node type (e.g., FUNCTION, CLASS, MODULE, http:route)"""
9
+ type: String!
10
+
11
+ """Human-readable name"""
12
+ name: String!
13
+
14
+ """Source file path (relative to project root)"""
15
+ file: String
16
+
17
+ """Line number in source file (1-indexed)"""
18
+ line: Int
19
+
20
+ """Column number in source file (1-indexed)"""
21
+ column: Int
22
+
23
+ """Whether this entity is exported from its module"""
24
+ exported: Boolean
25
+
26
+ """Arbitrary metadata as JSON object"""
27
+ metadata: JSON
28
+
29
+ # Relationship fields
30
+
31
+ """
32
+ Outgoing edges from this node.
33
+ Optional filter by edge types.
34
+ """
35
+ outgoingEdges(
36
+ types: [String!]
37
+ first: Int
38
+ after: String
39
+ ): GraphEdgeConnection!
40
+
41
+ """
42
+ Incoming edges to this node.
43
+ Optional filter by edge types.
44
+ """
45
+ incomingEdges(
46
+ types: [String!]
47
+ first: Int
48
+ after: String
49
+ ): GraphEdgeConnection!
50
+
51
+ """
52
+ Child nodes (via CONTAINS edges).
53
+ For hierarchical traversal: SERVICE -> MODULE -> FUNCTION -> SCOPE
54
+ """
55
+ children(first: Int, after: String): NodeConnection!
56
+
57
+ """
58
+ Parent node (via incoming CONTAINS edge).
59
+ Returns null for root nodes (SERVICE, PROJECT).
60
+ """
61
+ parent: Node
62
+ }
63
+
64
+ """
65
+ An edge in the code graph representing a relationship between nodes.
66
+ """
67
+ type Edge {
68
+ """Source node"""
69
+ src: Node!
70
+
71
+ """Destination node"""
72
+ dst: Node!
73
+
74
+ """Edge type (e.g., CALLS, CONTAINS, IMPORTS)"""
75
+ type: String!
76
+
77
+ """Ordering index for ordered relationships"""
78
+ index: Int
79
+
80
+ """Arbitrary metadata as JSON object"""
81
+ metadata: JSON
82
+ }
83
+
84
+ # ============================================================================
85
+ # Pagination Types (Relay Connection Spec)
86
+ # ============================================================================
87
+
88
+ """
89
+ Connection type for paginated node results (Relay spec).
90
+ """
91
+ type NodeConnection {
92
+ """Edges containing nodes and cursors"""
93
+ edges: [NodeEdge!]!
94
+
95
+ """Pagination info"""
96
+ pageInfo: PageInfo!
97
+
98
+ """Total count of matching nodes"""
99
+ totalCount: Int!
100
+ }
101
+
102
+ """
103
+ Edge wrapper for cursor-based pagination.
104
+ """
105
+ type NodeEdge {
106
+ """The node"""
107
+ node: Node!
108
+
109
+ """Cursor for this node (use with after/before args)"""
110
+ cursor: String!
111
+ }
112
+
113
+ """
114
+ Connection type for paginated graph edge results.
115
+ """
116
+ type GraphEdgeConnection {
117
+ """Edges containing graph edges and cursors"""
118
+ edges: [GraphEdgeEdge!]!
119
+
120
+ """Pagination info"""
121
+ pageInfo: PageInfo!
122
+
123
+ """Total count of matching edges"""
124
+ totalCount: Int!
125
+ }
126
+
127
+ """
128
+ Edge wrapper for graph edges.
129
+ """
130
+ type GraphEdgeEdge {
131
+ """The graph edge"""
132
+ node: Edge!
133
+
134
+ """Cursor for this edge"""
135
+ cursor: String!
136
+ }
137
+
138
+ """
139
+ Pagination info (Relay spec).
140
+ """
141
+ type PageInfo {
142
+ """Has more items after endCursor"""
143
+ hasNextPage: Boolean!
144
+
145
+ """Has more items before startCursor"""
146
+ hasPreviousPage: Boolean!
147
+
148
+ """Cursor of first item in current page"""
149
+ startCursor: String
150
+
151
+ """Cursor of last item in current page"""
152
+ endCursor: String
153
+ }
154
+
155
+ # ============================================================================
156
+ # Query Result Types
157
+ # ============================================================================
158
+
159
+ """
160
+ Result of a Datalog query.
161
+ """
162
+ type DatalogResult {
163
+ """Whether the query executed successfully"""
164
+ success: Boolean!
165
+
166
+ """Number of results"""
167
+ count: Int!
168
+
169
+ """Query results with variable bindings"""
170
+ results: [DatalogBinding!]!
171
+
172
+ """Error message if query failed"""
173
+ error: String
174
+ }
175
+
176
+ """
177
+ Variable bindings from a Datalog query result.
178
+ """
179
+ type DatalogBinding {
180
+ """Map of variable names to values"""
181
+ bindings: JSON!
182
+
183
+ """Enriched node data if X binding is a node ID"""
184
+ node: Node
185
+ }
186
+
187
+ """
188
+ Function details including call graph information.
189
+ """
190
+ type FunctionDetails {
191
+ """The function node"""
192
+ function: Node!
193
+
194
+ """Functions/methods this function calls"""
195
+ calls: [CallInfo!]!
196
+
197
+ """Functions that call this function"""
198
+ calledBy: [CallerInfo!]!
199
+ }
200
+
201
+ """
202
+ Information about a call made by a function.
203
+ """
204
+ type CallInfo {
205
+ """The CALL node"""
206
+ call: Node!
207
+
208
+ """Name of the called function/method"""
209
+ name: String!
210
+
211
+ """Object name for method calls (e.g., "console" in console.log)"""
212
+ object: String
213
+
214
+ """Whether the target was resolved"""
215
+ resolved: Boolean!
216
+
217
+ """Target function if resolved"""
218
+ target: Node
219
+
220
+ """Call type: CALL or METHOD_CALL"""
221
+ callType: String!
222
+
223
+ """Depth in transitive call chain (0 = direct)"""
224
+ depth: Int!
225
+ }
226
+
227
+ """
228
+ Information about a function that calls another function.
229
+ """
230
+ type CallerInfo {
231
+ """The calling function"""
232
+ function: Node!
233
+
234
+ """File containing the caller"""
235
+ file: String!
236
+
237
+ """Line number of the call"""
238
+ line: Int!
239
+ }
240
+
241
+ """
242
+ Guard (conditional scope) protecting a node.
243
+ """
244
+ type GuardInfo {
245
+ """The SCOPE node ID"""
246
+ scopeId: ID!
247
+
248
+ """Type of conditional (if_statement, else_statement, etc.)"""
249
+ scopeType: String!
250
+
251
+ """Raw condition text"""
252
+ condition: String
253
+
254
+ """Parsed constraints as JSON"""
255
+ constraints: JSON
256
+
257
+ """Source file"""
258
+ file: String!
259
+
260
+ """Line number"""
261
+ line: Int!
262
+ }
263
+
264
+ """
265
+ Guarantee definition.
266
+ """
267
+ type Guarantee {
268
+ """Unique identifier"""
269
+ id: ID!
270
+
271
+ """Human-readable name"""
272
+ name: String!
273
+
274
+ """Datalog rule or contract condition"""
275
+ rule: String
276
+
277
+ """Severity level"""
278
+ severity: String
279
+
280
+ """Guarantee type for contract-based"""
281
+ type: String
282
+
283
+ """Priority level"""
284
+ priority: String
285
+
286
+ """Lifecycle status"""
287
+ status: String
288
+
289
+ """Description"""
290
+ description: String
291
+ }
292
+
293
+ """
294
+ Result of checking guarantees.
295
+ """
296
+ type GuaranteeCheckResult {
297
+ """Total guarantees checked"""
298
+ total: Int!
299
+
300
+ """Number that passed"""
301
+ passed: Int!
302
+
303
+ """Number that failed"""
304
+ failed: Int!
305
+
306
+ """Individual results"""
307
+ results: [GuaranteeResult!]!
308
+ }
309
+
310
+ """
311
+ Result of checking a single guarantee.
312
+ """
313
+ type GuaranteeResult {
314
+ """Guarantee ID"""
315
+ guaranteeId: ID!
316
+
317
+ """Whether the guarantee passed"""
318
+ passed: Boolean!
319
+
320
+ """Number of violations (for Datalog guarantees)"""
321
+ violationCount: Int
322
+
323
+ """Sample violations"""
324
+ violations: [Violation!]
325
+ }
326
+
327
+ """
328
+ A violation of a guarantee.
329
+ """
330
+ type Violation {
331
+ """Node that violated the guarantee"""
332
+ node: Node
333
+
334
+ """File containing the violation"""
335
+ file: String
336
+
337
+ """Line number"""
338
+ line: Int
339
+ }
340
+
341
+ """
342
+ Graph statistics.
343
+ """
344
+ type GraphStats {
345
+ """Total node count"""
346
+ nodeCount: Int!
347
+
348
+ """Total edge count"""
349
+ edgeCount: Int!
350
+
351
+ """Nodes grouped by type"""
352
+ nodesByType: JSON!
353
+
354
+ """Edges grouped by type"""
355
+ edgesByType: JSON!
356
+ }
357
+
358
+ """
359
+ Analysis status.
360
+ """
361
+ type AnalysisStatus {
362
+ """Whether analysis is currently running"""
363
+ running: Boolean!
364
+
365
+ """Current phase"""
366
+ phase: String
367
+
368
+ """Status message"""
369
+ message: String
370
+
371
+ """Number of services discovered"""
372
+ servicesDiscovered: Int!
373
+
374
+ """Number of services analyzed"""
375
+ servicesAnalyzed: Int!
376
+
377
+ """Error message if analysis failed"""
378
+ error: String
379
+ }
380
+
381
+ """
382
+ Result of analyze mutation.
383
+ """
384
+ type AnalysisResult {
385
+ """Whether analysis succeeded"""
386
+ success: Boolean!
387
+
388
+ """Status after analysis"""
389
+ status: AnalysisStatus!
390
+ }
391
+
392
+ # ============================================================================
393
+ # Input Types
394
+ # ============================================================================
395
+
396
+ """
397
+ Filter for node queries.
398
+ """
399
+ input NodeFilter {
400
+ """Filter by node type"""
401
+ type: String
402
+
403
+ """Filter by name (exact match)"""
404
+ name: String
405
+
406
+ """Filter by file path (partial match)"""
407
+ file: String
408
+
409
+ """Filter by exported status"""
410
+ exported: Boolean
411
+ }
412
+
413
+ """
414
+ Input for creating a guarantee.
415
+ """
416
+ input CreateGuaranteeInput {
417
+ """Unique name"""
418
+ name: String!
419
+
420
+ """Datalog rule defining violation/1"""
421
+ rule: String
422
+
423
+ """Severity: error, warning, info"""
424
+ severity: Severity
425
+
426
+ """Type: guarantee:queue, guarantee:api, guarantee:permission"""
427
+ type: String
428
+
429
+ """Priority: critical, important, observed, tracked"""
430
+ priority: Priority
431
+
432
+ """Description"""
433
+ description: String
434
+
435
+ """Owner (team or person)"""
436
+ owner: String
437
+
438
+ """Node IDs this guarantee governs"""
439
+ governs: [String!]
440
+ }
package/src/server.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * GraphQL API Server using graphql-yoga
3
+ *
4
+ * Provides a GraphQL endpoint on top of Grafema's graph database.
5
+ * Supports cursor-based pagination and query complexity limiting.
6
+ */
7
+
8
+ import { createServer, type IncomingMessage } from 'node:http';
9
+ import { createYoga, createSchema } from 'graphql-yoga';
10
+ import { readFileSync } from 'node:fs';
11
+ import { join, dirname } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ import { resolvers } from './resolvers/index.js';
15
+ import { createContext } from './context.js';
16
+ import type { RFDBServerBackend } from '@grafema/core';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // Load schema files
21
+ function loadTypeDefs(): string {
22
+ const schemaDir = join(__dirname, 'schema');
23
+ const files = [
24
+ 'scalars.graphql',
25
+ 'enums.graphql',
26
+ 'types.graphql',
27
+ 'queries.graphql',
28
+ 'mutations.graphql',
29
+ 'subscriptions.graphql',
30
+ ];
31
+
32
+ return files
33
+ .map((file) => {
34
+ try {
35
+ return readFileSync(join(schemaDir, file), 'utf-8');
36
+ } catch {
37
+ // File might not exist in dist yet, try src
38
+ const srcPath = join(__dirname, '..', 'src', 'schema', file);
39
+ return readFileSync(srcPath, 'utf-8');
40
+ }
41
+ })
42
+ .join('\n');
43
+ }
44
+
45
+ export interface GraphQLServerOptions {
46
+ /** Graph backend (RFDBServerBackend) */
47
+ backend: RFDBServerBackend;
48
+ /** Port to listen on (default: 4000) */
49
+ port?: number;
50
+ /** Hostname to bind to (default: localhost) */
51
+ hostname?: string;
52
+ /** Maximum query depth (default: 10) */
53
+ maxDepth?: number;
54
+ /** Maximum query complexity cost (default: 1000) */
55
+ maxComplexity?: number;
56
+ /** Request timeout in ms (default: 30000) */
57
+ timeout?: number;
58
+ }
59
+
60
+ export function createGraphQLServer(options: GraphQLServerOptions) {
61
+ const { backend } = options;
62
+
63
+ const typeDefs = loadTypeDefs();
64
+
65
+ const schema = createSchema({
66
+ typeDefs,
67
+ resolvers,
68
+ });
69
+
70
+ const yoga = createYoga({
71
+ schema,
72
+ context: ({ request }) => {
73
+ // Create a minimal request object for context
74
+ const req = {
75
+ headers: Object.fromEntries(request.headers.entries()),
76
+ } as IncomingMessage;
77
+ return createContext(backend, req);
78
+ },
79
+ graphiql: {
80
+ title: 'Grafema GraphQL API',
81
+ defaultQuery: `# Welcome to Grafema GraphQL API
82
+ #
83
+ # Example queries:
84
+ #
85
+ # Get all functions:
86
+ # query { nodes(filter: {type: "FUNCTION"}, first: 10) {
87
+ # edges { node { id name file line } }
88
+ # pageInfo { hasNextPage endCursor }
89
+ # totalCount
90
+ # }}
91
+ #
92
+ # Find a specific node:
93
+ # query { node(id: "your-node-id") { id name type file } }
94
+ #
95
+ # Execute Datalog:
96
+ # query { datalog(query: "violation(X) :- node(X, \\"FUNCTION\\").") {
97
+ # count
98
+ # results { node { name } }
99
+ # }}
100
+
101
+ query Stats {
102
+ stats {
103
+ nodeCount
104
+ edgeCount
105
+ nodesByType
106
+ }
107
+ }
108
+ `,
109
+ },
110
+ // Subscriptions enabled for streaming
111
+ // (graphql-yoga uses SSE by default)
112
+ });
113
+
114
+ return yoga;
115
+ }
116
+
117
+ /**
118
+ * Start a standalone GraphQL server.
119
+ *
120
+ * @param options Server options
121
+ * @returns HTTP server instance
122
+ */
123
+ export function startServer(
124
+ options: GraphQLServerOptions
125
+ ): ReturnType<typeof createServer> {
126
+ const { port = 4000, hostname = 'localhost' } = options;
127
+
128
+ const yoga = createGraphQLServer(options);
129
+
130
+ const server = createServer((req, res) => {
131
+ yoga(req, res);
132
+ });
133
+
134
+ server.listen(port, hostname, () => {
135
+ console.log(`Grafema GraphQL API running at http://${hostname}:${port}/graphql`);
136
+ console.log(`GraphiQL IDE available at http://${hostname}:${port}/graphql`);
137
+ });
138
+
139
+ return server;
140
+ }