@geodedb/client 1.0.0-alpha.11
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 +201 -0
- package/README.md +594 -0
- package/dist/index.d.mts +2430 -0
- package/dist/index.d.ts +2430 -0
- package/dist/index.js +5202 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5101 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
# Geode Node.js Client
|
|
2
|
+
|
|
3
|
+
A high-performance Node.js client library for the [Geode](https://gitlab.com/devnw/geode) graph database, implementing the ISO/IEC 39075:2024 GQL standard over QUIC + TLS 1.3.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Full GQL Support**: Implements ISO/IEC 39075:2024 Graph Query Language standard
|
|
8
|
+
- **QUIC Transport**: High-performance transport with TLS 1.3 encryption
|
|
9
|
+
- **Connection Pooling**: Efficient connection management for high-throughput applications
|
|
10
|
+
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
|
11
|
+
- **Query Builder**: Fluent API for building GQL queries programmatically
|
|
12
|
+
- **Transaction Support**: Full transaction management with savepoints
|
|
13
|
+
- **Async/Await**: Modern async API with proper cancellation support
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @geodedb/client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Requirements:**
|
|
22
|
+
|
|
23
|
+
- Node.js 20.0.0 or later
|
|
24
|
+
- A running Geode server (default port: 3141)
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { createClient } from '@geodedb/client';
|
|
30
|
+
|
|
31
|
+
// Connect to Geode
|
|
32
|
+
const client = await createClient('geode://localhost:3141');
|
|
33
|
+
|
|
34
|
+
// Execute a query
|
|
35
|
+
const rows = await client.queryAll('MATCH (n:Person) RETURN n.name, n.age');
|
|
36
|
+
console.log(rows);
|
|
37
|
+
// [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
|
|
38
|
+
|
|
39
|
+
// Close connection
|
|
40
|
+
await client.close();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
### DSN Format
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
geode://[username:password@]host[:port][?options]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
|
|
53
|
+
| Option | Description | Default |
|
|
54
|
+
| ----------------- | --------------------------------- | ------------ |
|
|
55
|
+
| `page_size` | Results per page | 1000 |
|
|
56
|
+
| `hello_name` | Client name | geode-nodejs |
|
|
57
|
+
| `hello_ver` | Client version | 1.0.0 |
|
|
58
|
+
| `conformance` | GQL conformance level | min |
|
|
59
|
+
| `insecure` | Skip TLS verification (dev only) | false |
|
|
60
|
+
| `ca` | Path to CA certificate | - |
|
|
61
|
+
| `cert` | Path to client certificate (mTLS) | - |
|
|
62
|
+
| `key` | Path to client key (mTLS) | - |
|
|
63
|
+
| `server_name` | SNI server name | - |
|
|
64
|
+
| `connect_timeout` | Connection timeout (ms) | 30000 |
|
|
65
|
+
| `request_timeout` | Request timeout (ms) | 120000 |
|
|
66
|
+
|
|
67
|
+
### Environment Variables
|
|
68
|
+
|
|
69
|
+
| Variable | Description |
|
|
70
|
+
| ---------------- | --------------------------- |
|
|
71
|
+
| `GEODE_HOST` | Default host |
|
|
72
|
+
| `GEODE_PORT` | Default port |
|
|
73
|
+
| `GEODE_TLS_CA` | Default CA certificate path |
|
|
74
|
+
| `GEODE_USERNAME` | Default username |
|
|
75
|
+
| `GEODE_PASSWORD` | Default password |
|
|
76
|
+
|
|
77
|
+
### Example Configurations
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Simple connection
|
|
81
|
+
const client = await createClient('localhost:3141');
|
|
82
|
+
|
|
83
|
+
// With authentication
|
|
84
|
+
const client = await createClient('geode://admin:secret@localhost:3141');
|
|
85
|
+
|
|
86
|
+
// With TLS
|
|
87
|
+
const client = await createClient('geode://localhost:3141?ca=/path/to/ca.crt');
|
|
88
|
+
|
|
89
|
+
// With mTLS
|
|
90
|
+
const client = await createClient(
|
|
91
|
+
'geode://localhost:3141?ca=/path/to/ca.crt&cert=/path/to/client.crt&key=/path/to/client.key'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// With connection pool
|
|
95
|
+
const client = await createClient('geode://localhost:3141', {
|
|
96
|
+
pooling: true,
|
|
97
|
+
pool: {
|
|
98
|
+
min: 2,
|
|
99
|
+
max: 10,
|
|
100
|
+
acquireTimeout: 30000,
|
|
101
|
+
idleTimeout: 60000,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage Examples
|
|
107
|
+
|
|
108
|
+
### Basic Queries
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Query with iteration
|
|
112
|
+
const result = await client.query('MATCH (n:Person) RETURN n');
|
|
113
|
+
for await (const row of result) {
|
|
114
|
+
console.log(row.get('n')?.asNode);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Query all rows at once
|
|
118
|
+
const rows = await client.queryAll('MATCH (n:Person) RETURN n.name AS name');
|
|
119
|
+
|
|
120
|
+
// Get first row
|
|
121
|
+
const first = await client.queryFirst('MATCH (n:Person) RETURN n.name LIMIT 1');
|
|
122
|
+
|
|
123
|
+
// Get scalar value
|
|
124
|
+
const count = await client.queryScalar<number>('MATCH (n:Person) RETURN count(n) AS cnt', 'cnt');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Parameterized Queries
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Named parameters
|
|
131
|
+
const rows = await client.queryAll('MATCH (n:Person) WHERE n.age > $minAge RETURN n', {
|
|
132
|
+
params: { minAge: 21 },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Positional parameters (converted to $p1, $p2, etc.)
|
|
136
|
+
const rows = await client.queryAll('MATCH (n:Person) WHERE n.age > ? AND n.name = ? RETURN n', {
|
|
137
|
+
params: [21, 'Alice'],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Create, Update, Delete
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Create nodes
|
|
145
|
+
await client.exec("CREATE (n:Person {name: 'Alice', age: 30})");
|
|
146
|
+
|
|
147
|
+
// Create with helper method
|
|
148
|
+
const node = await client.createNode('Person', { name: 'Bob', age: 25 });
|
|
149
|
+
|
|
150
|
+
// Update
|
|
151
|
+
await client.exec("MATCH (n:Person {name: 'Alice'}) SET n.age = 31");
|
|
152
|
+
|
|
153
|
+
// Delete
|
|
154
|
+
await client.exec("MATCH (n:Person {name: 'Alice'}) DELETE n");
|
|
155
|
+
|
|
156
|
+
// Delete with relationships
|
|
157
|
+
await client.exec("MATCH (n:Person {name: 'Alice'}) DETACH DELETE n");
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Relationships
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Create relationship
|
|
164
|
+
await client.exec(`
|
|
165
|
+
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
|
|
166
|
+
CREATE (a)-[:KNOWS {since: 2020}]->(b)
|
|
167
|
+
`);
|
|
168
|
+
|
|
169
|
+
// Query relationships
|
|
170
|
+
const rows = await client.queryAll(`
|
|
171
|
+
MATCH (a:Person)-[r:KNOWS]->(b:Person)
|
|
172
|
+
RETURN a.name AS from, b.name AS to, r.since AS since
|
|
173
|
+
`);
|
|
174
|
+
|
|
175
|
+
// Variable-length paths
|
|
176
|
+
const rows = await client.queryAll(`
|
|
177
|
+
MATCH (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(friend)
|
|
178
|
+
RETURN friend.name
|
|
179
|
+
`);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Transactions
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Basic transaction
|
|
186
|
+
await client.withTransaction(async (tx) => {
|
|
187
|
+
await tx.exec("CREATE (n:Person {name: 'Alice'})");
|
|
188
|
+
await tx.exec("CREATE (n:Person {name: 'Bob'})");
|
|
189
|
+
// Auto-commits on success
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Manual transaction control
|
|
193
|
+
const conn = await client.getConnection();
|
|
194
|
+
const tx = await conn.begin();
|
|
195
|
+
try {
|
|
196
|
+
await tx.exec('CREATE (n:Test {value: 1})');
|
|
197
|
+
await tx.savepoint('sp1');
|
|
198
|
+
await tx.exec('CREATE (n:Test {value: 2})');
|
|
199
|
+
await tx.rollbackTo('sp1'); // Undo second create
|
|
200
|
+
await tx.commit();
|
|
201
|
+
} catch (e) {
|
|
202
|
+
await tx.rollback();
|
|
203
|
+
throw e;
|
|
204
|
+
} finally {
|
|
205
|
+
await client.releaseConnection(conn);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Query Builder
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { query, pattern, predicate } from '@geodedb/client';
|
|
213
|
+
|
|
214
|
+
// Build complex queries
|
|
215
|
+
const q = query()
|
|
216
|
+
.match(
|
|
217
|
+
pattern()
|
|
218
|
+
.node((n) => n.as('a').label('Person'))
|
|
219
|
+
.outgoing((e) => e.type('KNOWS'))
|
|
220
|
+
.node((n) => n.as('b').label('Person'))
|
|
221
|
+
)
|
|
222
|
+
.where(predicate().gt('a.age', 21).isNotNull('b.email'))
|
|
223
|
+
.return('a.name AS from', 'b.name AS to')
|
|
224
|
+
.orderBy('a.name')
|
|
225
|
+
.limit(10);
|
|
226
|
+
|
|
227
|
+
const { query: gql, params } = q.build();
|
|
228
|
+
const rows = await client.queryAll(gql, { params });
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Prepared Statements
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { PreparedStatement } from '@geodedb/client';
|
|
235
|
+
|
|
236
|
+
// Prepare a statement once
|
|
237
|
+
const stmt = await client.prepare('MATCH (n:Person {name: $name}) RETURN n');
|
|
238
|
+
|
|
239
|
+
// Execute multiple times with different parameters
|
|
240
|
+
const alice = await stmt.executeAll({ name: 'Alice' });
|
|
241
|
+
const bob = await stmt.executeAll({ name: 'Bob' });
|
|
242
|
+
|
|
243
|
+
// Validate parameters without executing
|
|
244
|
+
stmt.validate({ name: 'Charlie' });
|
|
245
|
+
|
|
246
|
+
// Check parameter info
|
|
247
|
+
console.log(stmt.parameterCount); // 1
|
|
248
|
+
console.log(stmt.namedParameters); // ['name']
|
|
249
|
+
|
|
250
|
+
// Close when done
|
|
251
|
+
stmt.close();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Query Explain/Profile
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { explain, profile, formatPlan, formatProfile } from '@geodedb/client';
|
|
258
|
+
|
|
259
|
+
// Get query execution plan
|
|
260
|
+
const plan = await client.explain('MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m');
|
|
261
|
+
console.log('Estimated cost:', plan.totalCost);
|
|
262
|
+
console.log('Estimated rows:', plan.totalRows);
|
|
263
|
+
|
|
264
|
+
// Format plan for display
|
|
265
|
+
console.log(formatPlan(plan));
|
|
266
|
+
|
|
267
|
+
// Profile actual execution
|
|
268
|
+
const prof = await client.profile('MATCH (n:Person) RETURN count(n)');
|
|
269
|
+
console.log('Execution time:', prof.totalTimeMs, 'ms');
|
|
270
|
+
console.log('Rows processed:', prof.totalRows);
|
|
271
|
+
console.log('Planning time:', prof.planningTimeMs, 'ms');
|
|
272
|
+
console.log('Memory used:', prof.memoryBytes, 'bytes');
|
|
273
|
+
|
|
274
|
+
// Format profile for display
|
|
275
|
+
console.log(formatProfile(prof));
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Batch Operations
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { batch, batchMap, batchParallel } from '@geodedb/client';
|
|
282
|
+
|
|
283
|
+
// Execute multiple queries in a batch
|
|
284
|
+
const summary = await client.batch([
|
|
285
|
+
{ query: "CREATE (n:Person {name: 'Alice'})" },
|
|
286
|
+
{ query: "CREATE (n:Person {name: 'Bob'})" },
|
|
287
|
+
{ query: 'MATCH (n:Person) RETURN count(n) AS cnt' },
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
console.log(`${summary.successful}/${summary.total} queries succeeded`);
|
|
291
|
+
console.log('Total time:', summary.totalDurationMs, 'ms');
|
|
292
|
+
|
|
293
|
+
// Stop on first error
|
|
294
|
+
const summary2 = await client.batch(queries, { stopOnError: true });
|
|
295
|
+
|
|
296
|
+
// Execute in a transaction (all or nothing)
|
|
297
|
+
const summary3 = await client.batch(queries, { transaction: true });
|
|
298
|
+
|
|
299
|
+
// Map over data
|
|
300
|
+
const people = [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }];
|
|
301
|
+
await batchMap(conn, 'CREATE (n:Person {name: $name})', people);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Authentication Client
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { AuthClient } from '@geodedb/client';
|
|
308
|
+
|
|
309
|
+
const auth = await client.auth();
|
|
310
|
+
|
|
311
|
+
// User management
|
|
312
|
+
await auth.createUser('alice', { password: 'securePass123', roles: ['analyst'] });
|
|
313
|
+
await auth.changePassword('alice', 'newPassword456');
|
|
314
|
+
await auth.deactivateUser('alice');
|
|
315
|
+
const users = await auth.listUsers();
|
|
316
|
+
|
|
317
|
+
// Role management
|
|
318
|
+
await auth.createRole('analyst', {
|
|
319
|
+
description: 'Data analyst role',
|
|
320
|
+
permissions: [{ resource: 'NODE', action: 'READ' }],
|
|
321
|
+
});
|
|
322
|
+
await auth.assignRole('alice', 'analyst');
|
|
323
|
+
await auth.grantPermission('analyst', { resource: 'NODE', action: 'READ', label: 'Person' });
|
|
324
|
+
|
|
325
|
+
// Row-level security policies
|
|
326
|
+
await auth.createRLSPolicy(
|
|
327
|
+
'tenant_isolation',
|
|
328
|
+
'Document',
|
|
329
|
+
'ALL',
|
|
330
|
+
'n.tenant_id = current_user().tenant_id',
|
|
331
|
+
{ roles: ['user', 'analyst'] }
|
|
332
|
+
);
|
|
333
|
+
await auth.enableRLSPolicy('tenant_isolation', 'Document');
|
|
334
|
+
|
|
335
|
+
// Session info
|
|
336
|
+
const currentUser = await auth.currentUser();
|
|
337
|
+
const roles = await auth.currentRoles();
|
|
338
|
+
const canRead = await auth.hasPermission('NODE', 'READ', 'Person');
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Abort/Cancellation
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
const controller = new AbortController();
|
|
345
|
+
|
|
346
|
+
// Cancel after 5 seconds
|
|
347
|
+
setTimeout(() => controller.abort(), 5000);
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const rows = await client.queryAll('MATCH (n) RETURN n', { signal: controller.signal });
|
|
351
|
+
} catch (e) {
|
|
352
|
+
if (e.message.includes('Aborted')) {
|
|
353
|
+
console.log('Query was cancelled');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Type System
|
|
359
|
+
|
|
360
|
+
The client provides a comprehensive GQL type system:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { GQLValue, fromJSON } from '@geodedb/client';
|
|
364
|
+
|
|
365
|
+
// Create typed values
|
|
366
|
+
const intVal = GQLValue.int(42);
|
|
367
|
+
const strVal = GQLValue.string('hello');
|
|
368
|
+
const arrVal = GQLValue.array([GQLValue.int(1), GQLValue.int(2)]);
|
|
369
|
+
const nodeVal = GQLValue.node({
|
|
370
|
+
id: '123',
|
|
371
|
+
labels: ['Person'],
|
|
372
|
+
properties: { name: 'Alice' },
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Type-safe access
|
|
376
|
+
intVal.asNumber; // 42
|
|
377
|
+
intVal.asInt; // 42n (bigint)
|
|
378
|
+
strVal.asString; // 'hello'
|
|
379
|
+
arrVal.asArray; // [GQLValue, GQLValue]
|
|
380
|
+
nodeVal.asNode; // { id, labels, properties }
|
|
381
|
+
|
|
382
|
+
// Convert to plain JS
|
|
383
|
+
intVal.toJS(); // 42
|
|
384
|
+
nodeVal.toJS(); // { id: '123', labels: ['Person'], ... }
|
|
385
|
+
|
|
386
|
+
// Parse from JSON
|
|
387
|
+
const value = fromJSON({ name: 'Alice', age: 30 });
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Supported Types
|
|
391
|
+
|
|
392
|
+
| GQL Type | Node.js Type |
|
|
393
|
+
| ---------- | ------------- |
|
|
394
|
+
| INT | number/bigint |
|
|
395
|
+
| FLOAT | number |
|
|
396
|
+
| DECIMAL | Decimal |
|
|
397
|
+
| STRING | string |
|
|
398
|
+
| BOOL | boolean |
|
|
399
|
+
| NULL | null |
|
|
400
|
+
| ARRAY/LIST | Array |
|
|
401
|
+
| OBJECT/MAP | Object/Map |
|
|
402
|
+
| NODE | GQLNode |
|
|
403
|
+
| EDGE | GQLEdge |
|
|
404
|
+
| PATH | GQLPath |
|
|
405
|
+
| BYTEA | Uint8Array |
|
|
406
|
+
| DATE | Date |
|
|
407
|
+
| TIME | Date |
|
|
408
|
+
| TIMESTAMP | Date |
|
|
409
|
+
| UUID | string |
|
|
410
|
+
| JSON/JSONB | any |
|
|
411
|
+
|
|
412
|
+
## Error Handling
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import {
|
|
416
|
+
DriverError,
|
|
417
|
+
TransportError,
|
|
418
|
+
ConfigError,
|
|
419
|
+
SecurityError,
|
|
420
|
+
isRetryableError,
|
|
421
|
+
} from '@geodedb/client';
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
await client.queryAll('INVALID QUERY');
|
|
425
|
+
} catch (e) {
|
|
426
|
+
if (e instanceof DriverError) {
|
|
427
|
+
console.log('Server error:', e.code, e.message);
|
|
428
|
+
console.log('Status class:', e.statusClass); // ISO 39075 code
|
|
429
|
+
|
|
430
|
+
if (isRetryableError(e)) {
|
|
431
|
+
// Serialization or deadlock - can retry
|
|
432
|
+
}
|
|
433
|
+
} else if (e instanceof TransportError) {
|
|
434
|
+
console.log('Network error:', e.operation, e.message);
|
|
435
|
+
} else if (e instanceof ConfigError) {
|
|
436
|
+
console.log('Configuration error:', e.field, e.message);
|
|
437
|
+
} else if (e instanceof SecurityError) {
|
|
438
|
+
console.log('Security error:', e.type, e.message);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### ISO 39075 Status Classes
|
|
444
|
+
|
|
445
|
+
| Class | Meaning | Retryable |
|
|
446
|
+
| ----- | ------------------------- | --------- |
|
|
447
|
+
| 00000 | Success | - |
|
|
448
|
+
| 01000 | Warning | No |
|
|
449
|
+
| 02000 | No data | No |
|
|
450
|
+
| 25000 | Invalid transaction state | No |
|
|
451
|
+
| 28000 | Authorization error | No |
|
|
452
|
+
| 40001 | Serialization failure | Yes |
|
|
453
|
+
| 40502 | Transaction deadlock | Yes |
|
|
454
|
+
| 42000 | Syntax error | No |
|
|
455
|
+
| 23000 | Constraint violation | No |
|
|
456
|
+
| 58000 | System error | No |
|
|
457
|
+
|
|
458
|
+
## Connection Pool
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const client = await createClient('geode://localhost:3141', {
|
|
462
|
+
pooling: true,
|
|
463
|
+
pool: {
|
|
464
|
+
min: 2, // Minimum connections to maintain
|
|
465
|
+
max: 10, // Maximum connections
|
|
466
|
+
acquireTimeout: 30000, // Max wait time to acquire
|
|
467
|
+
idleTimeout: 60000, // Close idle connections after
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Check pool stats
|
|
472
|
+
console.log(client.poolStats);
|
|
473
|
+
// { total: 5, available: 3, inUse: 2, waiting: 0 }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Testing
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
# Run unit tests
|
|
480
|
+
npm test
|
|
481
|
+
|
|
482
|
+
# Run with coverage
|
|
483
|
+
npm run test:coverage
|
|
484
|
+
|
|
485
|
+
# Run integration tests (requires Docker)
|
|
486
|
+
npm run test:integration
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## API Reference
|
|
490
|
+
|
|
491
|
+
### GeodeClient
|
|
492
|
+
|
|
493
|
+
| Method | Description |
|
|
494
|
+
| --------------------------------- | ------------------------------------ |
|
|
495
|
+
| `query(gql, options?)` | Execute query, return async iterator |
|
|
496
|
+
| `queryAll(gql, options?)` | Execute query, return all rows |
|
|
497
|
+
| `queryFirst(gql, options?)` | Get first row |
|
|
498
|
+
| `queryScalar(gql, col, options?)` | Get single value |
|
|
499
|
+
| `exec(gql, options?)` | Execute statement (no results) |
|
|
500
|
+
| `withTransaction(fn)` | Execute in transaction |
|
|
501
|
+
| `prepare(gql)` | Create prepared statement |
|
|
502
|
+
| `explain(gql, options?)` | Get query execution plan |
|
|
503
|
+
| `profile(gql, options?)` | Profile query execution |
|
|
504
|
+
| `batch(queries, options?)` | Execute multiple queries |
|
|
505
|
+
| `auth()` | Get authentication client |
|
|
506
|
+
| `ping()` | Check connection health |
|
|
507
|
+
| `close()` | Close client |
|
|
508
|
+
|
|
509
|
+
### Connection
|
|
510
|
+
|
|
511
|
+
| Method | Description |
|
|
512
|
+
| -------------------------- | ------------------------- |
|
|
513
|
+
| `query(gql, options?)` | Execute query |
|
|
514
|
+
| `exec(gql, options?)` | Execute statement |
|
|
515
|
+
| `begin()` | Start transaction |
|
|
516
|
+
| `prepare(gql)` | Create prepared statement |
|
|
517
|
+
| `explain(gql, options?)` | Get query plan |
|
|
518
|
+
| `profile(gql, options?)` | Profile execution |
|
|
519
|
+
| `batch(queries, options?)` | Execute batch |
|
|
520
|
+
| `ping()` | Health check |
|
|
521
|
+
| `reset()` | Reset session |
|
|
522
|
+
| `close()` | Close connection |
|
|
523
|
+
|
|
524
|
+
### Transaction
|
|
525
|
+
|
|
526
|
+
| Method | Description |
|
|
527
|
+
| ---------------------- | --------------------- |
|
|
528
|
+
| `query(gql, options?)` | Execute query |
|
|
529
|
+
| `exec(gql, options?)` | Execute statement |
|
|
530
|
+
| `savepoint(name)` | Create savepoint |
|
|
531
|
+
| `rollbackTo(name)` | Rollback to savepoint |
|
|
532
|
+
| `commit()` | Commit transaction |
|
|
533
|
+
| `rollback()` | Rollback transaction |
|
|
534
|
+
|
|
535
|
+
### PreparedStatement
|
|
536
|
+
|
|
537
|
+
| Method | Description |
|
|
538
|
+
| ------------------------------- | ------------------------- |
|
|
539
|
+
| `execute(params?, options?)` | Execute, return iterator |
|
|
540
|
+
| `executeAll(params?, options?)` | Execute, return all rows |
|
|
541
|
+
| `exec(params?, options?)` | Execute (no results) |
|
|
542
|
+
| `validate(params?)` | Validate parameters |
|
|
543
|
+
| `close()` | Close statement |
|
|
544
|
+
| `query` | Get query text |
|
|
545
|
+
| `parameterCount` | Get parameter count |
|
|
546
|
+
| `namedParameters` | Get named parameter names |
|
|
547
|
+
|
|
548
|
+
### AuthClient
|
|
549
|
+
|
|
550
|
+
| Method | Description |
|
|
551
|
+
| ------------------------------------ | --------------------- |
|
|
552
|
+
| `createUser(name, options)` | Create user |
|
|
553
|
+
| `deleteUser(name)` | Delete user |
|
|
554
|
+
| `getUser(name)` | Get user info |
|
|
555
|
+
| `listUsers()` | List all users |
|
|
556
|
+
| `changePassword(name, password)` | Change password |
|
|
557
|
+
| `activateUser(name)` | Activate user |
|
|
558
|
+
| `deactivateUser(name)` | Deactivate user |
|
|
559
|
+
| `createRole(name, options?)` | Create role |
|
|
560
|
+
| `deleteRole(name)` | Delete role |
|
|
561
|
+
| `assignRole(user, role)` | Assign role to user |
|
|
562
|
+
| `revokeRole(user, role)` | Revoke role from user |
|
|
563
|
+
| `grantPermission(role, perm)` | Grant permission |
|
|
564
|
+
| `revokePermission(role, perm)` | Revoke permission |
|
|
565
|
+
| `createRLSPolicy(...)` | Create RLS policy |
|
|
566
|
+
| `deleteRLSPolicy(name, label)` | Delete RLS policy |
|
|
567
|
+
| `currentUser()` | Get current user |
|
|
568
|
+
| `currentRoles()` | Get current roles |
|
|
569
|
+
| `hasPermission(res, action, label?)` | Check permission |
|
|
570
|
+
|
|
571
|
+
## Contributing
|
|
572
|
+
|
|
573
|
+
See [CLAUDE.md](./CLAUDE.md) for development guidelines.
|
|
574
|
+
|
|
575
|
+
### Development Setup
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
git clone https://gitlab.com/devnw/geode/geode-client-nodejs.git
|
|
579
|
+
cd geode-client-nodejs
|
|
580
|
+
npm install
|
|
581
|
+
npm test
|
|
582
|
+
npm run build
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## License
|
|
586
|
+
|
|
587
|
+
Apache License 2.0
|
|
588
|
+
|
|
589
|
+
## Related Projects
|
|
590
|
+
|
|
591
|
+
- [Geode Server](https://gitlab.com/devnw/geode/geode) - The Geode database server
|
|
592
|
+
- [Go Client](https://gitlab.com/devnw/geode/geode-client-go) - Go client library
|
|
593
|
+
- [Python Client](https://gitlab.com/devnw/geode/geode-client-python) - Python client library
|
|
594
|
+
- [Rust Client](https://gitlab.com/devnw/geode/geode-client-rust) - Rust client library
|