@hames-ai/connectors 0.1.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/app-tools/registry.ts +185 -0
- package/graph/graph-auth.ts +29 -0
- package/graph/graph-tools.server.ts +1921 -0
- package/index.ts +40 -0
- package/mcp-catalog.ts +149 -0
- package/neo4j/client.ts +112 -0
- package/neo4j/graph-edit.server.ts +127 -0
- package/neo4j/index.ts +34 -0
- package/neo4j/plain.ts +153 -0
- package/neo4j/queries.ts +369 -0
- package/neo4j/transform.ts +437 -0
- package/package.json +66 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Data Transformation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Transforms Neo4j query results into Cytoscape.js-compatible format
|
|
5
|
+
* for graph visualization in the UI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local stand-in for cytoscape's `ElementDefinition` — the fields this
|
|
10
|
+
* module's output actually touches (`data` and `classes`). Declared here
|
|
11
|
+
* rather than imported so a consumer who wants these helpers is never forced
|
|
12
|
+
* to install a graph rendering library (these packages ship raw TypeScript,
|
|
13
|
+
* so a consumer's `tsc` compiles this file directly and would report TS2307
|
|
14
|
+
* on an unresolved import). Structurally compatible with cytoscape's own
|
|
15
|
+
* `ElementDefinition` (its data is `{ id?: string; [key: string]: any }`),
|
|
16
|
+
* so a consumer holding the real type can consume ours unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export interface ElementDefinition {
|
|
19
|
+
/** Element payload — cytoscape allows arbitrary extra fields here. */
|
|
20
|
+
data: { [key: string]: unknown; id?: string }
|
|
21
|
+
/** Which collection the element belongs to (cytoscape: an explicit group always wins over its inference). */
|
|
22
|
+
group?: 'nodes' | 'edges'
|
|
23
|
+
/** A space-separated list of class names, for cytoscape styling selectors. */
|
|
24
|
+
classes?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Type Definitions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Neo4j Node structure from query results
|
|
33
|
+
*/
|
|
34
|
+
export interface Neo4jNode {
|
|
35
|
+
identity: string | number
|
|
36
|
+
labels: string[]
|
|
37
|
+
properties: Record<string, unknown>
|
|
38
|
+
elementId?: string // Neo4j 5.x element ID
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Neo4j Relationship structure from query results
|
|
43
|
+
*/
|
|
44
|
+
export interface Neo4jRelationship {
|
|
45
|
+
identity: string | number
|
|
46
|
+
type: string
|
|
47
|
+
start: string | number
|
|
48
|
+
end: string | number
|
|
49
|
+
startNode?: string // Neo4j 5.x element ID (deprecated naming)
|
|
50
|
+
endNode?: string // Neo4j 5.x element ID (deprecated naming)
|
|
51
|
+
startNodeElementId?: string // Neo4j 5.x element ID
|
|
52
|
+
endNodeElementId?: string // Neo4j 5.x element ID
|
|
53
|
+
properties: Record<string, unknown>
|
|
54
|
+
elementId?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Neo4j query result structure
|
|
59
|
+
*/
|
|
60
|
+
export interface Neo4jQueryResult {
|
|
61
|
+
nodes?: Neo4jNode[]
|
|
62
|
+
relationships?: Neo4jRelationship[]
|
|
63
|
+
records?: unknown[] // Raw Neo4j records
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Transform Functions
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Transform Neo4j nodes and relationships to Cytoscape elements
|
|
72
|
+
*
|
|
73
|
+
* @param nodes - Array of Neo4j nodes
|
|
74
|
+
* @param relationships - Array of Neo4j relationships
|
|
75
|
+
* @returns Array of Cytoscape element definitions
|
|
76
|
+
*/
|
|
77
|
+
export function transformNeo4jToCytoscape(
|
|
78
|
+
nodes: Neo4jNode[],
|
|
79
|
+
relationships: Neo4jRelationship[],
|
|
80
|
+
): ElementDefinition[] {
|
|
81
|
+
const elements: ElementDefinition[] = []
|
|
82
|
+
|
|
83
|
+
// Transform nodes
|
|
84
|
+
for (const node of nodes) {
|
|
85
|
+
elements.push(transformNode(node))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Transform relationships
|
|
89
|
+
for (const rel of relationships) {
|
|
90
|
+
elements.push(transformRelationship(rel))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return elements
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Transform a single Neo4j node to Cytoscape node
|
|
98
|
+
*/
|
|
99
|
+
function transformNode(node: Neo4jNode): ElementDefinition {
|
|
100
|
+
// Use elementId if available (Neo4j 5.x), otherwise use identity
|
|
101
|
+
const id = node.elementId?.toString() || node.identity.toString()
|
|
102
|
+
|
|
103
|
+
// Determine node label (prefer name, title, or first label)
|
|
104
|
+
const label = getNodeLabel(node)
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
data: {
|
|
108
|
+
id,
|
|
109
|
+
label,
|
|
110
|
+
type: node.labels.join(','),
|
|
111
|
+
labels: node.labels,
|
|
112
|
+
properties: node.properties,
|
|
113
|
+
// Store original identity for reference
|
|
114
|
+
neo4jId: node.identity,
|
|
115
|
+
},
|
|
116
|
+
classes: node.labels.map((l) => `label-${l.toLowerCase()}`).join(' '),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Transform a single Neo4j relationship to Cytoscape edge
|
|
122
|
+
*/
|
|
123
|
+
function transformRelationship(rel: Neo4jRelationship): ElementDefinition {
|
|
124
|
+
// Use elementId if available (Neo4j 5.x), otherwise use identity
|
|
125
|
+
const id = rel.elementId?.toString() || rel.identity.toString()
|
|
126
|
+
|
|
127
|
+
// For source/target, we need to match the node IDs
|
|
128
|
+
// Neo4j 5.x uses startNodeElementId/endNodeElementId (full element IDs)
|
|
129
|
+
// Fall back to startNode/start for older formats
|
|
130
|
+
const source =
|
|
131
|
+
rel.startNodeElementId?.toString() || rel.startNode?.toString() || rel.start.toString()
|
|
132
|
+
const target = rel.endNodeElementId?.toString() || rel.endNode?.toString() || rel.end.toString()
|
|
133
|
+
|
|
134
|
+
console.log('[transform] Relationship source:', source, 'target:', target)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
data: {
|
|
138
|
+
id,
|
|
139
|
+
source,
|
|
140
|
+
target,
|
|
141
|
+
label: formatRelationshipLabel(rel.type),
|
|
142
|
+
type: rel.type,
|
|
143
|
+
properties: rel.properties,
|
|
144
|
+
// Store original identity for reference
|
|
145
|
+
neo4jId: rel.identity,
|
|
146
|
+
},
|
|
147
|
+
classes: `rel-${rel.type.toLowerCase().replace(/_/g, '-')}`,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Extract a meaningful label from a Neo4j node
|
|
153
|
+
* Prefers: name > title > id > first property > first label
|
|
154
|
+
*/
|
|
155
|
+
function getNodeLabel(node: Neo4jNode): string {
|
|
156
|
+
const props = node.properties
|
|
157
|
+
|
|
158
|
+
// Check common label properties
|
|
159
|
+
if (props.name) return String(props.name)
|
|
160
|
+
if (props.title) return String(props.title)
|
|
161
|
+
if (props.id) return String(props.id)
|
|
162
|
+
if (props.label) return String(props.label)
|
|
163
|
+
|
|
164
|
+
// Use first string property value
|
|
165
|
+
for (const [_key, value] of Object.entries(props)) {
|
|
166
|
+
if (typeof value === 'string' && value.length > 0 && value.length < 50) {
|
|
167
|
+
return value
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Fall back to first label or 'Node'
|
|
172
|
+
return node.labels[0] || 'Node'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Format relationship type for display
|
|
177
|
+
* Converts SNAKE_CASE to Title Case
|
|
178
|
+
*/
|
|
179
|
+
function formatRelationshipLabel(type: string): string {
|
|
180
|
+
return type
|
|
181
|
+
.split('_')
|
|
182
|
+
.map((word) => word.charAt(0) + word.slice(1).toLowerCase())
|
|
183
|
+
.join(' ')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Result Parsers
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Parse Neo4j query results from MCP response
|
|
192
|
+
* Handles different result formats from neo4j-cypher MCP server
|
|
193
|
+
*/
|
|
194
|
+
export function parseNeo4jResults(mcpResponse: unknown): Neo4jQueryResult {
|
|
195
|
+
console.log('[transform] parseNeo4jResults called')
|
|
196
|
+
|
|
197
|
+
// Type guard for response object
|
|
198
|
+
const response = mcpResponse as Record<string, unknown>
|
|
199
|
+
|
|
200
|
+
// If response is already in expected format
|
|
201
|
+
if (response?.nodes && response?.relationships) {
|
|
202
|
+
console.log('[transform] Response already in expected format')
|
|
203
|
+
return response as unknown as Neo4jQueryResult
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If response has records array
|
|
207
|
+
if (response?.records && Array.isArray(response.records)) {
|
|
208
|
+
console.log('[transform] Found records array, length:', response.records.length)
|
|
209
|
+
if (response.records.length > 0) {
|
|
210
|
+
// Log the structure of the first record
|
|
211
|
+
const firstRecord = response.records[0]
|
|
212
|
+
console.log('[transform] First record type:', typeof firstRecord)
|
|
213
|
+
console.log('[transform] First record constructor:', firstRecord?.constructor?.name)
|
|
214
|
+
// If it has keys() method (Neo4j Record), log keys
|
|
215
|
+
if (firstRecord && typeof firstRecord.keys === 'function') {
|
|
216
|
+
console.log('[transform] Record keys:', firstRecord.keys())
|
|
217
|
+
}
|
|
218
|
+
if (firstRecord && typeof firstRecord.toObject === 'function') {
|
|
219
|
+
console.log('[transform] Record toObject:', JSON.stringify(firstRecord.toObject(), null, 2))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return extractNodesAndRelsFromRecords(response.records)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If response is a simple array
|
|
226
|
+
if (Array.isArray(mcpResponse)) {
|
|
227
|
+
console.log('[transform] Response is array, length:', mcpResponse.length)
|
|
228
|
+
return extractNodesAndRelsFromRecords(mcpResponse)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Empty result
|
|
232
|
+
console.log('[transform] No valid format found, returning empty')
|
|
233
|
+
return { nodes: [], relationships: [] }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Extract nodes and relationships from Neo4j records
|
|
238
|
+
* Handles the case where MCP returns raw record arrays
|
|
239
|
+
*/
|
|
240
|
+
function extractNodesAndRelsFromRecords(records: unknown[]): Neo4jQueryResult {
|
|
241
|
+
console.log('[transform] extractNodesAndRelsFromRecords called with', records.length, 'records')
|
|
242
|
+
|
|
243
|
+
const nodes = new Map<string, Neo4jNode>()
|
|
244
|
+
const relationships: Neo4jRelationship[] = []
|
|
245
|
+
|
|
246
|
+
for (const record of records) {
|
|
247
|
+
// Neo4j Record objects have a toObject() method
|
|
248
|
+
const recordObj =
|
|
249
|
+
record && typeof (record as { toObject?: () => unknown }).toObject === 'function'
|
|
250
|
+
? (record as { toObject: () => unknown }).toObject()
|
|
251
|
+
: record
|
|
252
|
+
|
|
253
|
+
// Record can be an object with keys or an array
|
|
254
|
+
const values = Array.isArray(recordObj)
|
|
255
|
+
? recordObj
|
|
256
|
+
: Object.values(recordObj as Record<string, unknown>)
|
|
257
|
+
|
|
258
|
+
console.log('[transform] Processing record with', values.length, 'values')
|
|
259
|
+
|
|
260
|
+
for (const value of values) {
|
|
261
|
+
console.log('[transform] Checking value:', typeof value, value?.constructor?.name)
|
|
262
|
+
|
|
263
|
+
if (isNode(value)) {
|
|
264
|
+
const id = value.elementId?.toString() || value.identity.toString()
|
|
265
|
+
console.log('[transform] Found node:', id)
|
|
266
|
+
nodes.set(id, value)
|
|
267
|
+
} else if (isRelationship(value)) {
|
|
268
|
+
console.log('[transform] Found relationship')
|
|
269
|
+
relationships.push(value)
|
|
270
|
+
|
|
271
|
+
// Also extract connected nodes if present
|
|
272
|
+
if (value.start && value.end) {
|
|
273
|
+
// These are node references - we might get the full nodes separately
|
|
274
|
+
}
|
|
275
|
+
} else if (isPath(value)) {
|
|
276
|
+
console.log('[transform] Found path')
|
|
277
|
+
// Path contains nodes and relationships
|
|
278
|
+
extractFromPath(value, nodes, relationships)
|
|
279
|
+
} else {
|
|
280
|
+
console.log('[transform] Value not recognized as node/rel/path')
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(
|
|
286
|
+
'[transform] Extracted',
|
|
287
|
+
nodes.size,
|
|
288
|
+
'nodes and',
|
|
289
|
+
relationships.length,
|
|
290
|
+
'relationships',
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
nodes: Array.from(nodes.values()),
|
|
295
|
+
relationships,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Check if value is a Neo4j node
|
|
301
|
+
*/
|
|
302
|
+
function isNode(value: unknown): value is Neo4jNode {
|
|
303
|
+
const v = value as Record<string, unknown>
|
|
304
|
+
return !!v && (v.labels !== undefined || v.label !== undefined) && v.properties !== undefined
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if value is a Neo4j relationship
|
|
309
|
+
*/
|
|
310
|
+
function isRelationship(value: unknown): value is Neo4jRelationship {
|
|
311
|
+
const v = value as Record<string, unknown>
|
|
312
|
+
return (
|
|
313
|
+
!!v &&
|
|
314
|
+
v.type !== undefined &&
|
|
315
|
+
(v.start !== undefined || v.startNode !== undefined) &&
|
|
316
|
+
(v.end !== undefined || v.endNode !== undefined)
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Check if value is a Neo4j path
|
|
322
|
+
*/
|
|
323
|
+
function isPath(value: unknown): boolean {
|
|
324
|
+
const v = value as Record<string, unknown>
|
|
325
|
+
return !!v && v.segments !== undefined
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extract nodes and relationships from a Neo4j path
|
|
330
|
+
*/
|
|
331
|
+
interface Neo4jPath {
|
|
332
|
+
start?: Neo4jNode
|
|
333
|
+
end?: Neo4jNode
|
|
334
|
+
segments?: Array<{
|
|
335
|
+
start?: Neo4jNode
|
|
336
|
+
end?: Neo4jNode
|
|
337
|
+
relationship?: Neo4jRelationship
|
|
338
|
+
}>
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function extractFromPath(
|
|
342
|
+
path: Neo4jPath,
|
|
343
|
+
nodes: Map<string, Neo4jNode>,
|
|
344
|
+
relationships: Neo4jRelationship[],
|
|
345
|
+
): void {
|
|
346
|
+
if (path.start && isNode(path.start)) {
|
|
347
|
+
const id = path.start.elementId?.toString() || path.start.identity.toString()
|
|
348
|
+
nodes.set(id, path.start)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (path.end && isNode(path.end)) {
|
|
352
|
+
const id = path.end.elementId?.toString() || path.end.identity.toString()
|
|
353
|
+
nodes.set(id, path.end)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (path.segments && Array.isArray(path.segments)) {
|
|
357
|
+
for (const segment of path.segments) {
|
|
358
|
+
if (segment.start && isNode(segment.start)) {
|
|
359
|
+
const id = segment.start.elementId?.toString() || segment.start.identity.toString()
|
|
360
|
+
nodes.set(id, segment.start)
|
|
361
|
+
}
|
|
362
|
+
if (segment.end && isNode(segment.end)) {
|
|
363
|
+
const id = segment.end.elementId?.toString() || segment.end.identity.toString()
|
|
364
|
+
nodes.set(id, segment.end)
|
|
365
|
+
}
|
|
366
|
+
if (segment.relationship && isRelationship(segment.relationship)) {
|
|
367
|
+
relationships.push(segment.relationship)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Utility Functions
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Create a sample graph for testing
|
|
379
|
+
* Useful for development and demonstrations
|
|
380
|
+
*/
|
|
381
|
+
export function createSampleGraph(): ElementDefinition[] {
|
|
382
|
+
return [
|
|
383
|
+
// Nodes
|
|
384
|
+
{
|
|
385
|
+
data: {
|
|
386
|
+
id: '1',
|
|
387
|
+
label: 'Alice',
|
|
388
|
+
type: 'Person',
|
|
389
|
+
properties: { name: 'Alice', age: 30 },
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
data: {
|
|
394
|
+
id: '2',
|
|
395
|
+
label: 'Bob',
|
|
396
|
+
type: 'Person',
|
|
397
|
+
properties: { name: 'Bob', age: 25 },
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
data: {
|
|
402
|
+
id: '3',
|
|
403
|
+
label: 'Company X',
|
|
404
|
+
type: 'Company',
|
|
405
|
+
properties: { name: 'Company X', founded: 2020 },
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
// Relationships
|
|
409
|
+
{
|
|
410
|
+
data: {
|
|
411
|
+
id: 'e1',
|
|
412
|
+
source: '1',
|
|
413
|
+
target: '2',
|
|
414
|
+
label: 'Knows',
|
|
415
|
+
type: 'KNOWS',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
data: {
|
|
420
|
+
id: 'e2',
|
|
421
|
+
source: '1',
|
|
422
|
+
target: '3',
|
|
423
|
+
label: 'Works At',
|
|
424
|
+
type: 'WORKS_AT',
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
data: {
|
|
429
|
+
id: 'e3',
|
|
430
|
+
source: '2',
|
|
431
|
+
target: '3',
|
|
432
|
+
label: 'Works At',
|
|
433
|
+
type: 'WORKS_AT',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
]
|
|
437
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hames-ai/connectors",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The connectors companion for @hames-ai/harness-patterns — Microsoft Graph app-side tools, the Neo4j non-agentic layer, and the MCP-gateway namespace catalog, all behind injected seams.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"connectors",
|
|
7
|
+
"neo4j",
|
|
8
|
+
"cypher",
|
|
9
|
+
"graph",
|
|
10
|
+
"mcp",
|
|
11
|
+
"tools",
|
|
12
|
+
"harness"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/mknw/hames-playground/tree/main/packages/connectors#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/mknw/hames-playground/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/mknw/hames-playground.git",
|
|
21
|
+
"directory": "packages/connectors"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Michael Accetto",
|
|
26
|
+
"url": "https://github.com/mknw"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.ts",
|
|
32
|
+
"./package.json": "./package.json",
|
|
33
|
+
"./neo4j": "./neo4j/index.ts",
|
|
34
|
+
"./*": "./*.ts"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"*.ts",
|
|
38
|
+
"!vitest.config.ts",
|
|
39
|
+
"neo4j",
|
|
40
|
+
"app-tools",
|
|
41
|
+
"graph",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"neo4j-driver": "^6.0.1"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@hames-ai/harness-patterns": "^0.1.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"seroval": "~1.5.0",
|
|
59
|
+
"vitest": "^4.1.5",
|
|
60
|
+
"@hames-ai/harness-patterns": "^0.1.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"test:watch": "vitest"
|
|
65
|
+
}
|
|
66
|
+
}
|