@m6d/cortex-server 1.1.1 → 1.1.2

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 (42) hide show
  1. package/README.md +38 -38
  2. package/dist/src/factory.d.ts +13 -1
  3. package/dist/src/ws/index.d.ts +1 -1
  4. package/package.json +54 -54
  5. package/src/adapters/database.ts +21 -28
  6. package/src/adapters/minio.ts +69 -69
  7. package/src/adapters/mssql.ts +171 -195
  8. package/src/adapters/storage.ts +4 -4
  9. package/src/ai/fetch.ts +31 -31
  10. package/src/ai/helpers.ts +18 -22
  11. package/src/ai/index.ts +101 -113
  12. package/src/ai/interceptors/resolve-captured-files.ts +42 -49
  13. package/src/ai/prompt.ts +80 -83
  14. package/src/ai/tools/call-endpoint.tool.ts +75 -82
  15. package/src/ai/tools/capture-files.tool.ts +15 -17
  16. package/src/ai/tools/execute-code.tool.ts +73 -80
  17. package/src/ai/tools/query-graph.tool.ts +17 -17
  18. package/src/auth/middleware.ts +51 -51
  19. package/src/cli/extract-endpoints.ts +436 -474
  20. package/src/config.ts +124 -134
  21. package/src/db/migrate.ts +13 -13
  22. package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +303 -303
  23. package/src/db/schema.ts +46 -58
  24. package/src/factory.ts +136 -139
  25. package/src/graph/generate-cypher.ts +97 -97
  26. package/src/graph/helpers.ts +37 -37
  27. package/src/graph/index.ts +20 -20
  28. package/src/graph/neo4j.ts +82 -89
  29. package/src/graph/resolver.ts +201 -211
  30. package/src/graph/seed.ts +101 -114
  31. package/src/graph/types.ts +88 -88
  32. package/src/graph/validate.ts +55 -57
  33. package/src/index.ts +5 -5
  34. package/src/routes/chat.ts +23 -23
  35. package/src/routes/files.ts +75 -80
  36. package/src/routes/threads.ts +52 -54
  37. package/src/routes/ws.ts +22 -22
  38. package/src/types.ts +30 -30
  39. package/src/ws/connections.ts +11 -11
  40. package/src/ws/events.ts +2 -2
  41. package/src/ws/index.ts +1 -5
  42. package/src/ws/notify.ts +4 -4
@@ -14,67 +14,67 @@
14
14
  import type { DomainDef } from "./types.ts";
15
15
 
16
16
  export type GeneratedCypher = {
17
- nodes: string[];
18
- edges: string[];
17
+ nodes: string[];
18
+ edges: string[];
19
19
  };
20
20
 
21
21
  function esc(s: string) {
22
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
22
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
23
23
  }
24
24
 
25
25
  function jsonStr(value: unknown) {
26
- return esc(JSON.stringify(value));
26
+ return esc(JSON.stringify(value));
27
27
  }
28
28
 
29
29
  export function generateCypher(data: DomainDef) {
30
- const nodes: string[] = [];
31
- const edges: string[] = [];
30
+ const nodes: string[] = [];
31
+ const edges: string[] = [];
32
32
 
33
- // -- Domain node --
34
- nodes.push(
35
- `MERGE (d:Domain {name: '${esc(data.name)}'})
33
+ // -- Domain node --
34
+ nodes.push(
35
+ `MERGE (d:Domain {name: '${esc(data.name)}'})
36
36
  SET d.description = '${esc(data.description)}'`,
37
- );
37
+ );
38
38
 
39
- // -- Concept nodes --
40
- for (const c of data.concepts ?? []) {
41
- const aliasClause =
42
- c.aliases && c.aliases.length > 0
43
- ? `, c.aliases = [${c.aliases.map((a) => `'${esc(a)}'`).join(", ")}]`
44
- : "";
39
+ // -- Concept nodes --
40
+ for (const c of data.concepts ?? []) {
41
+ const aliasClause =
42
+ c.aliases && c.aliases.length > 0
43
+ ? `, c.aliases = [${c.aliases.map((a) => `'${esc(a)}'`).join(", ")}]`
44
+ : "";
45
45
 
46
- nodes.push(
47
- `MERGE (c:Concept {name: '${esc(c.name)}'})
46
+ nodes.push(
47
+ `MERGE (c:Concept {name: '${esc(c.name)}'})
48
48
  SET c.description = '${esc(c.description)}'${aliasClause}`,
49
- );
49
+ );
50
50
 
51
- edges.push(
52
- `MATCH (c:Concept {name: '${esc(c.name)}'})
51
+ edges.push(
52
+ `MATCH (c:Concept {name: '${esc(c.name)}'})
53
53
  MATCH (d:Domain {name: '${esc(data.name)}'})
54
54
  MERGE (c)-[:BELONGS_TO]->(d)`,
55
- );
55
+ );
56
56
 
57
- if (c.parentConcept) {
58
- edges.push(
59
- `MATCH (child:Concept {name: '${esc(c.name)}'})
57
+ if (c.parentConcept) {
58
+ edges.push(
59
+ `MATCH (child:Concept {name: '${esc(c.name)}'})
60
60
  MATCH (parent:Concept {name: '${esc(c.parentConcept.name)}'})
61
61
  MERGE (child)-[:SPECIALIZES]->(parent)`,
62
- );
63
- }
62
+ );
63
+ }
64
64
 
65
- c.governedBy?.forEach((rule) => {
66
- edges.push(
67
- `MATCH (r:Rule {name: '${esc(rule.name)}'})
65
+ c.governedBy?.forEach((rule) => {
66
+ edges.push(
67
+ `MATCH (r:Rule {name: '${esc(rule.name)}'})
68
68
  MATCH (t:Concept {name: '${esc(c.name)}'})
69
69
  MERGE (r)-[:GOVERNS]->(t)`,
70
- );
71
- });
72
- }
70
+ );
71
+ });
72
+ }
73
73
 
74
- // -- Endpoint nodes --
75
- for (const ep of data.endpoints ?? []) {
76
- nodes.push(
77
- `MERGE (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
74
+ // -- Endpoint nodes --
75
+ for (const ep of data.endpoints ?? []) {
76
+ nodes.push(
77
+ `MERGE (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
78
78
  SET e.name = '${esc(ep.name)}',
79
79
  e.description = '${esc(ep.description)}',
80
80
  e.params = '${jsonStr(ep.params)}',
@@ -83,97 +83,97 @@ export function generateCypher(data: DomainDef) {
83
83
  e.propertiesDescriptions = '${jsonStr(ep.propertiesDescriptions)}',
84
84
  e.successStatus = ${ep.successStatus},
85
85
  e.errorStatuses = [${ep.errorStatuses.join(", ")}]`,
86
- );
86
+ );
87
87
 
88
- for (const concept of ep.queries ?? []) {
89
- edges.push(
90
- `MATCH (c:Concept {name: '${esc(concept.name)}'})
88
+ for (const concept of ep.queries ?? []) {
89
+ edges.push(
90
+ `MATCH (c:Concept {name: '${esc(concept.name)}'})
91
91
  MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
92
92
  MERGE (c)-[:QUERIED_VIA]->(e)`,
93
- );
94
- }
93
+ );
94
+ }
95
95
 
96
- for (const concept of ep.mutates ?? []) {
97
- edges.push(
98
- `MATCH (c:Concept {name: '${esc(concept.name)}'})
96
+ for (const concept of ep.mutates ?? []) {
97
+ edges.push(
98
+ `MATCH (c:Concept {name: '${esc(concept.name)}'})
99
99
  MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
100
100
  MERGE (c)-[:MUTATED_VIA]->(e)`,
101
- );
102
- }
101
+ );
102
+ }
103
103
 
104
- for (const ret of ep.returns ?? []) {
105
- const fieldClause = ret.field ? ` SET r.field = '${esc(ret.field)}'` : "";
104
+ for (const ret of ep.returns ?? []) {
105
+ const fieldClause = ret.field ? ` SET r.field = '${esc(ret.field)}'` : "";
106
106
 
107
- edges.push(
108
- `MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
107
+ edges.push(
108
+ `MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
109
109
  MATCH (c:Concept {name: '${esc(ret.concept.name)}'})
110
110
  MERGE (e)-[r:RETURNS]->(c)${fieldClause}`,
111
- );
112
- }
111
+ );
112
+ }
113
113
 
114
- for (const rule of ep.governedBy ?? []) {
115
- edges.push(
116
- `MATCH (r:Rule {name: '${esc(rule.name)}'})
114
+ for (const rule of ep.governedBy ?? []) {
115
+ edges.push(
116
+ `MATCH (r:Rule {name: '${esc(rule.name)}'})
117
117
  MATCH (t:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
118
118
  MERGE (r)-[:GOVERNS]->(t)`,
119
- );
120
- }
119
+ );
120
+ }
121
121
 
122
- for (const dep of ep.dependsOn ?? []) {
123
- edges.push(
124
- `MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
122
+ for (const dep of ep.dependsOn ?? []) {
123
+ edges.push(
124
+ `MATCH (e:Endpoint {path: '${esc(ep.path)}', method: '${ep.method}'})
125
125
  MATCH (dep:Endpoint {path: '${esc(dep.endpoint.path)}', method: '${dep.endpoint.method}'})
126
126
  MERGE (e)-[d:DEPENDS_ON]->(dep)
127
127
  SET d.paramName = '${esc(dep.paramName)}', d.fromField = '${esc(dep.fromField)}'`,
128
- );
128
+ );
129
+ }
129
130
  }
130
- }
131
131
 
132
- // -- Service nodes --
133
- for (const svc of data.services ?? []) {
134
- nodes.push(
135
- `MERGE (s:Service {builtInId: '${esc(svc.builtInId)}'})
132
+ // -- Service nodes --
133
+ for (const svc of data.services ?? []) {
134
+ nodes.push(
135
+ `MERGE (s:Service {builtInId: '${esc(svc.builtInId)}'})
136
136
  SET s.name = '${esc(svc.name)}',
137
137
  s.description = '${esc(svc.description)}'`,
138
- );
138
+ );
139
139
 
140
- edges.push(
141
- `MATCH (c:Concept {name: '${esc(svc.belongsTo.name)}'})
140
+ edges.push(
141
+ `MATCH (c:Concept {name: '${esc(svc.belongsTo.name)}'})
142
142
  MATCH (s:Service {builtInId: '${esc(svc.builtInId)}'})
143
143
  MERGE (c)-[:REQUESTED_VIA]->(s)`,
144
- );
144
+ );
145
145
 
146
- svc.governedBy?.forEach((rule) => {
147
- edges.push(
148
- `MATCH (r:Rule {name: '${esc(rule.name)}'})
146
+ svc.governedBy?.forEach((rule) => {
147
+ edges.push(
148
+ `MATCH (r:Rule {name: '${esc(rule.name)}'})
149
149
  MATCH (t:Service {builtInId: '${esc(svc.builtInId)}'})
150
150
  MERGE (r)-[:GOVERNS]->(t)`,
151
- );
152
- });
153
- }
151
+ );
152
+ });
153
+ }
154
154
 
155
- // -- Rule nodes --
156
- for (const rule of data.rules ?? []) {
157
- nodes.push(
158
- `MERGE (r:Rule {name: '${esc(rule.name)}'})
155
+ // -- Rule nodes --
156
+ for (const rule of data.rules ?? []) {
157
+ nodes.push(
158
+ `MERGE (r:Rule {name: '${esc(rule.name)}'})
159
159
  SET r.description = '${esc(rule.description)}'`,
160
- );
161
- }
160
+ );
161
+ }
162
162
 
163
- return { nodes, edges } satisfies GeneratedCypher;
163
+ return { nodes, edges } satisfies GeneratedCypher;
164
164
  }
165
165
 
166
166
  export function toCypherScript(data: DomainDef) {
167
- const { nodes, edges } = generateCypher(data);
168
- return (
169
- `// Auto-generated seed script for module: ${data.name}\n` +
170
- `// Generated at: ${new Date().toISOString()}\n` +
171
- `// Safe to re-run (uses MERGE)\n\n` +
172
- `// --- Nodes ---\n` +
173
- nodes.join(";\n\n") +
174
- ";\n\n" +
175
- `// --- Edges ---\n` +
176
- edges.join(";\n\n") +
177
- ";\n"
178
- );
167
+ const { nodes, edges } = generateCypher(data);
168
+ return (
169
+ `// Auto-generated seed script for module: ${data.name}\n` +
170
+ `// Generated at: ${new Date().toISOString()}\n` +
171
+ `// Safe to re-run (uses MERGE)\n\n` +
172
+ `// --- Nodes ---\n` +
173
+ nodes.join(";\n\n") +
174
+ ";\n\n" +
175
+ `// --- Edges ---\n` +
176
+ edges.join(";\n\n") +
177
+ ";\n"
178
+ );
179
179
  }
@@ -3,12 +3,12 @@
3
3
  */
4
4
 
5
5
  import type {
6
- ConceptDef,
7
- DomainDef,
8
- EndpointDef,
9
- EndpointInput,
10
- RuleDef,
11
- ServiceDef,
6
+ ConceptDef,
7
+ DomainDef,
8
+ EndpointDef,
9
+ EndpointInput,
10
+ RuleDef,
11
+ ServiceDef,
12
12
  } from "./types.ts";
13
13
 
14
14
  // ---------------------------------------------------------------------------
@@ -16,19 +16,19 @@ import type {
16
16
  // ---------------------------------------------------------------------------
17
17
 
18
18
  export function defineConcept(def: Omit<ConceptDef, "__brand">) {
19
- return { __brand: "concept" as const, ...def };
19
+ return { __brand: "concept" as const, ...def };
20
20
  }
21
21
 
22
22
  export function defineRule(def: Omit<RuleDef, "__brand">) {
23
- return { __brand: "rule" as const, ...def };
23
+ return { __brand: "rule" as const, ...def };
24
24
  }
25
25
 
26
26
  export function defineService(def: Omit<ServiceDef, "__brand">) {
27
- return { __brand: "service" as const, ...def };
27
+ return { __brand: "service" as const, ...def };
28
28
  }
29
29
 
30
30
  export function defineDomain(def: Omit<DomainDef, "__brand">) {
31
- return { __brand: "domain" as const, ...def };
31
+ return { __brand: "domain" as const, ...def };
32
32
  }
33
33
 
34
34
  // ---------------------------------------------------------------------------
@@ -36,33 +36,33 @@ export function defineDomain(def: Omit<DomainDef, "__brand">) {
36
36
  // ---------------------------------------------------------------------------
37
37
 
38
38
  export function defineEndpoint(input: EndpointInput) {
39
- const propertiesDescriptions = Object.fromEntries(
40
- Object.entries({
41
- ...(input.propertiesDescriptions ?? {}),
42
- ...(input.paramDescriptions ?? {}),
43
- ...(input.responseDescriptions ?? {}),
44
- }).filter(([, value]) => value != null),
45
- ) as Record<string, string>;
39
+ const propertiesDescriptions = Object.fromEntries(
40
+ Object.entries({
41
+ ...(input.propertiesDescriptions ?? {}),
42
+ ...(input.paramDescriptions ?? {}),
43
+ ...(input.responseDescriptions ?? {}),
44
+ }).filter(([, value]) => value != null),
45
+ ) as Record<string, string>;
46
46
 
47
- const normalized = input.autoGenerated;
47
+ const normalized = input.autoGenerated;
48
48
 
49
- return {
50
- __brand: "endpoint" as const,
51
- name: input.name,
52
- description: input.description ?? input.name,
53
- path: input.path,
54
- method: input.method,
55
- propertiesDescriptions,
56
- params: normalized ? [...normalized.params] : [],
57
- body: normalized ? [...normalized.body] : [],
58
- response: normalized ? [...normalized.response] : [],
59
- successStatus: normalized?.successStatus ?? 200,
60
- errorStatuses: normalized ? [...normalized.errorStatuses] : [],
61
- responseKind: normalized?.responseKind ?? "object",
62
- queries: input.queries,
63
- mutates: input.mutates,
64
- returns: input.returns,
65
- dependsOn: input.dependsOn,
66
- governedBy: input.governedBy,
67
- } satisfies EndpointDef;
49
+ return {
50
+ __brand: "endpoint" as const,
51
+ name: input.name,
52
+ description: input.description ?? input.name,
53
+ path: input.path,
54
+ method: input.method,
55
+ propertiesDescriptions,
56
+ params: normalized ? [...normalized.params] : [],
57
+ body: normalized ? [...normalized.body] : [],
58
+ response: normalized ? [...normalized.response] : [],
59
+ successStatus: normalized?.successStatus ?? 200,
60
+ errorStatuses: normalized ? [...normalized.errorStatuses] : [],
61
+ responseKind: normalized?.responseKind ?? "object",
62
+ queries: input.queries,
63
+ mutates: input.mutates,
64
+ returns: input.returns,
65
+ dependsOn: input.dependsOn,
66
+ governedBy: input.governedBy,
67
+ } satisfies EndpointDef;
68
68
  }
@@ -1,24 +1,24 @@
1
1
  // Types
2
2
  export type {
3
- EndpointScalarType,
4
- EndpointProperty,
5
- ResponseKind,
6
- AutoGenerated,
7
- ConceptDef,
8
- EndpointDef,
9
- EndpointInput,
10
- ServiceDef,
11
- RuleDef,
12
- DomainDef,
3
+ EndpointScalarType,
4
+ EndpointProperty,
5
+ ResponseKind,
6
+ AutoGenerated,
7
+ ConceptDef,
8
+ EndpointDef,
9
+ EndpointInput,
10
+ ServiceDef,
11
+ RuleDef,
12
+ DomainDef,
13
13
  } from "./types.ts";
14
14
 
15
15
  // Helpers
16
16
  export {
17
- defineConcept,
18
- defineRule,
19
- defineService,
20
- defineDomain,
21
- defineEndpoint,
17
+ defineConcept,
18
+ defineRule,
19
+ defineService,
20
+ defineDomain,
21
+ defineEndpoint,
22
22
  } from "./helpers.ts";
23
23
 
24
24
  // Cypher generation
@@ -38,10 +38,10 @@ export { seedGraph } from "./seed.ts";
38
38
 
39
39
  // Graph resolver
40
40
  export type {
41
- RerankerConfig,
42
- ResolverConfig,
43
- ResolvedEndpoint,
44
- ResolvedService,
45
- ResolvedContext,
41
+ RerankerConfig,
42
+ ResolverConfig,
43
+ ResolvedEndpoint,
44
+ ResolvedService,
45
+ ResolvedContext,
46
46
  } from "./resolver.ts";
47
47
  export { resolveFromGraph } from "./resolver.ts";
@@ -13,105 +13,98 @@ import { embed } from "ai";
13
13
  // ---------------------------------------------------------------------------
14
14
 
15
15
  export type Neo4jConfig = {
16
- url: string;
17
- user: string;
18
- password: string;
16
+ url: string;
17
+ user: string;
18
+ password: string;
19
19
  };
20
20
 
21
21
  export type Neo4jClient = {
22
- query(cypher: string, parameters?: Record<string, unknown>): Promise<string>;
22
+ query(cypher: string, parameters?: Record<string, unknown>): Promise<string>;
23
23
  };
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
26
  // Client factory
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
- export function createNeo4jClient(
30
- config: Neo4jConfig,
31
- embeddingModel?: EmbeddingModel,
32
- ) {
33
- async function query(cypher: string, parameters?: Record<string, unknown>) {
34
- // Auto-embed parameters whose keys start with '#'
35
- const embeddedParams = Object.keys(parameters ?? {}).filter((x) =>
36
- x.startsWith("#"),
37
- );
38
-
39
- if (embeddedParams.length) {
40
- if (!embeddingModel) {
41
- throw new Error(
42
- "An embedding model is required when using # parameters",
43
- );
44
- }
45
-
46
- const embeddingResults = await Promise.all(
47
- embeddedParams.map(
48
- async (x) =>
49
- await embed({
50
- model: embeddingModel,
51
- value: parameters![x] as string,
29
+ export function createNeo4jClient(config: Neo4jConfig, embeddingModel?: EmbeddingModel) {
30
+ async function query(cypher: string, parameters?: Record<string, unknown>) {
31
+ // Auto-embed parameters whose keys start with '#'
32
+ const embeddedParams = Object.keys(parameters ?? {}).filter((x) => x.startsWith("#"));
33
+
34
+ if (embeddedParams.length) {
35
+ if (!embeddingModel) {
36
+ throw new Error("An embedding model is required when using # parameters");
37
+ }
38
+
39
+ const embeddingResults = await Promise.all(
40
+ embeddedParams.map(
41
+ async (x) =>
42
+ await embed({
43
+ model: embeddingModel,
44
+ value: parameters![x] as string,
45
+ }),
46
+ ),
47
+ );
48
+
49
+ embeddedParams.forEach((k, idx) => {
50
+ delete parameters![k];
51
+ parameters![k.slice(1)] = embeddingResults[idx]!.embedding;
52
+ });
53
+ }
54
+
55
+ const response = await fetch(`${config.url}/db/neo4j/tx/commit`, {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ Authorization: `Basic ${btoa(`${config.user}:${config.password}`)}`,
60
+ },
61
+ body: JSON.stringify({
62
+ statements: [
63
+ {
64
+ statement: cypher,
65
+ parameters: parameters ?? {},
66
+ },
67
+ ],
52
68
  }),
53
- ),
54
- );
55
-
56
- embeddedParams.forEach((k, idx) => {
57
- delete parameters![k];
58
- parameters![k.slice(1)] = embeddingResults[idx]!.embedding;
59
- });
60
- }
61
-
62
- const response = await fetch(`${config.url}/db/neo4j/tx/commit`, {
63
- method: "POST",
64
- headers: {
65
- "Content-Type": "application/json",
66
- Authorization: `Basic ${btoa(`${config.user}:${config.password}`)}`,
67
- },
68
- body: JSON.stringify({
69
- statements: [
70
- {
71
- statement: cypher,
72
- parameters: parameters ?? {},
73
- },
74
- ],
75
- }),
76
- });
77
-
78
- if (!response.ok) {
79
- return JSON.stringify({
80
- error: true,
81
- status: response.status,
82
- message: `Neo4j request failed with status ${response.status}`,
83
- });
84
- }
85
-
86
- const result = (await response.json()) as {
87
- errors?: { message: string }[];
88
- results: { columns: string[]; data: { row: unknown[] }[] }[];
89
- };
90
-
91
- if (result.errors && result.errors.length > 0) {
92
- return JSON.stringify({
93
- error: true,
94
- message: result.errors.map((e) => e.message).join("; "),
95
- });
96
- }
97
-
98
- // Transform Neo4j response into a clean array of objects
99
- const queryResult = result.results[0];
100
- if (!queryResult) {
101
- return JSON.stringify([]);
69
+ });
70
+
71
+ if (!response.ok) {
72
+ return JSON.stringify({
73
+ error: true,
74
+ status: response.status,
75
+ message: `Neo4j request failed with status ${response.status}`,
76
+ });
77
+ }
78
+
79
+ const result = (await response.json()) as {
80
+ errors?: { message: string }[];
81
+ results: { columns: string[]; data: { row: unknown[] }[] }[];
82
+ };
83
+
84
+ if (result.errors && result.errors.length > 0) {
85
+ return JSON.stringify({
86
+ error: true,
87
+ message: result.errors.map((e) => e.message).join("; "),
88
+ });
89
+ }
90
+
91
+ // Transform Neo4j response into a clean array of objects
92
+ const queryResult = result.results[0];
93
+ if (!queryResult) {
94
+ return JSON.stringify([]);
95
+ }
96
+
97
+ const { columns, data } = queryResult;
98
+ const rows = data.map((entry) => {
99
+ const obj: Record<string, unknown> = {};
100
+ columns.forEach((col, i) => {
101
+ obj[col] = entry.row[i];
102
+ });
103
+ return obj;
104
+ });
105
+
106
+ return JSON.stringify(rows);
102
107
  }
103
108
 
104
- const { columns, data } = queryResult;
105
- const rows = data.map((entry) => {
106
- const obj: Record<string, unknown> = {};
107
- columns.forEach((col, i) => {
108
- obj[col] = entry.row[i];
109
- });
110
- return obj;
111
- });
112
-
113
- return JSON.stringify(rows);
114
- }
115
-
116
- return { query } satisfies Neo4jClient;
109
+ return { query } satisfies Neo4jClient;
117
110
  }