@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.
- package/README.md +38 -38
- package/dist/src/factory.d.ts +13 -1
- package/dist/src/ws/index.d.ts +1 -1
- package/package.json +54 -54
- package/src/adapters/database.ts +21 -28
- package/src/adapters/minio.ts +69 -69
- package/src/adapters/mssql.ts +171 -195
- package/src/adapters/storage.ts +4 -4
- package/src/ai/fetch.ts +31 -31
- package/src/ai/helpers.ts +18 -22
- package/src/ai/index.ts +101 -113
- package/src/ai/interceptors/resolve-captured-files.ts +42 -49
- package/src/ai/prompt.ts +80 -83
- package/src/ai/tools/call-endpoint.tool.ts +75 -82
- package/src/ai/tools/capture-files.tool.ts +15 -17
- package/src/ai/tools/execute-code.tool.ts +73 -80
- package/src/ai/tools/query-graph.tool.ts +17 -17
- package/src/auth/middleware.ts +51 -51
- package/src/cli/extract-endpoints.ts +436 -474
- package/src/config.ts +124 -134
- package/src/db/migrate.ts +13 -13
- package/src/db/migrations/20260309012148_cloudy_maria_hill/snapshot.json +303 -303
- package/src/db/schema.ts +46 -58
- package/src/factory.ts +136 -139
- package/src/graph/generate-cypher.ts +97 -97
- package/src/graph/helpers.ts +37 -37
- package/src/graph/index.ts +20 -20
- package/src/graph/neo4j.ts +82 -89
- package/src/graph/resolver.ts +201 -211
- package/src/graph/seed.ts +101 -114
- package/src/graph/types.ts +88 -88
- package/src/graph/validate.ts +55 -57
- package/src/index.ts +5 -5
- package/src/routes/chat.ts +23 -23
- package/src/routes/files.ts +75 -80
- package/src/routes/threads.ts +52 -54
- package/src/routes/ws.ts +22 -22
- package/src/types.ts +30 -30
- package/src/ws/connections.ts +11 -11
- package/src/ws/events.ts +2 -2
- package/src/ws/index.ts +1 -5
- 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
|
-
|
|
18
|
-
|
|
17
|
+
nodes: string[];
|
|
18
|
+
edges: string[];
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
function esc(s: string) {
|
|
22
|
-
|
|
22
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function jsonStr(value: unknown) {
|
|
26
|
-
|
|
26
|
+
return esc(JSON.stringify(value));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function generateCypher(data: DomainDef) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const nodes: string[] = [];
|
|
31
|
+
const edges: string[] = [];
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
105
|
-
|
|
104
|
+
for (const ret of ep.returns ?? []) {
|
|
105
|
+
const fieldClause = ret.field ? ` SET r.field = '${esc(ret.field)}'` : "";
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
163
|
+
return { nodes, edges } satisfies GeneratedCypher;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
export function toCypherScript(data: DomainDef) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
}
|
package/src/graph/helpers.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
19
|
+
return { __brand: "concept" as const, ...def };
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function defineRule(def: Omit<RuleDef, "__brand">) {
|
|
23
|
-
|
|
23
|
+
return { __brand: "rule" as const, ...def };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function defineService(def: Omit<ServiceDef, "__brand">) {
|
|
27
|
-
|
|
27
|
+
return { __brand: "service" as const, ...def };
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function defineDomain(def: Omit<DomainDef, "__brand">) {
|
|
31
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
47
|
+
const normalized = input.autoGenerated;
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
}
|
package/src/graph/index.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
// Types
|
|
2
2
|
export type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
RerankerConfig,
|
|
42
|
+
ResolverConfig,
|
|
43
|
+
ResolvedEndpoint,
|
|
44
|
+
ResolvedService,
|
|
45
|
+
ResolvedContext,
|
|
46
46
|
} from "./resolver.ts";
|
|
47
47
|
export { resolveFromGraph } from "./resolver.ts";
|
package/src/graph/neo4j.ts
CHANGED
|
@@ -13,105 +13,98 @@ import { embed } from "ai";
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
|
|
15
15
|
export type Neo4jConfig = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
url: string;
|
|
17
|
+
user: string;
|
|
18
|
+
password: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export type Neo4jClient = {
|
|
22
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
}
|