leangraph 1.0.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +456 -0
  3. package/dist/auth.d.ts +66 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +148 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/backup.d.ts +51 -0
  8. package/dist/backup.d.ts.map +1 -0
  9. package/dist/backup.js +201 -0
  10. package/dist/backup.js.map +1 -0
  11. package/dist/cli-helpers.d.ts +17 -0
  12. package/dist/cli-helpers.d.ts.map +1 -0
  13. package/dist/cli-helpers.js +121 -0
  14. package/dist/cli-helpers.js.map +1 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +660 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/db.d.ts +118 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +720 -0
  22. package/dist/db.js.map +1 -0
  23. package/dist/executor.d.ts +663 -0
  24. package/dist/executor.d.ts.map +1 -0
  25. package/dist/executor.js +8578 -0
  26. package/dist/executor.js.map +1 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +86 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/local.d.ts +7 -0
  32. package/dist/local.d.ts.map +1 -0
  33. package/dist/local.js +119 -0
  34. package/dist/local.js.map +1 -0
  35. package/dist/parser.d.ts +365 -0
  36. package/dist/parser.d.ts.map +1 -0
  37. package/dist/parser.js +2711 -0
  38. package/dist/parser.js.map +1 -0
  39. package/dist/property-value.d.ts +3 -0
  40. package/dist/property-value.d.ts.map +1 -0
  41. package/dist/property-value.js +30 -0
  42. package/dist/property-value.js.map +1 -0
  43. package/dist/remote.d.ts +6 -0
  44. package/dist/remote.d.ts.map +1 -0
  45. package/dist/remote.js +93 -0
  46. package/dist/remote.js.map +1 -0
  47. package/dist/routes.d.ts +31 -0
  48. package/dist/routes.d.ts.map +1 -0
  49. package/dist/routes.js +202 -0
  50. package/dist/routes.js.map +1 -0
  51. package/dist/server.d.ts +16 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/server.js +25 -0
  54. package/dist/server.js.map +1 -0
  55. package/dist/translator.d.ts +330 -0
  56. package/dist/translator.d.ts.map +1 -0
  57. package/dist/translator.js +13712 -0
  58. package/dist/translator.js.map +1 -0
  59. package/dist/types.d.ts +136 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +21 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +77 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Conrad Lelubre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,456 @@
1
+ # LeanGraph
2
+
3
+ [![npm version](https://img.shields.io/npm/v/leangraph.svg)](https://www.npmjs.com/package/leangraph)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TCK](https://img.shields.io/badge/openCypher_TCK-100%25-brightgreen.svg)](https://opencypher.org/)
6
+
7
+ A lightweight, embeddable graph database with **full Cypher query support**, powered by SQLite.
8
+
9
+ > **100% openCypher TCK Compliance** — LeanGraph passes all 2,684 test scenarios from the openCypher Technology Compatibility Kit (Neo4j 3.5 baseline). Every Cypher feature that Neo4j 3.5 supports, LeanGraph supports.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install leangraph
15
+ ```
16
+
17
+ ### Native Dependencies
18
+
19
+ The `better-sqlite3` native module is an **optional dependency**:
20
+
21
+ - **Production mode** (remote HTTP client): No native dependencies, no compilation required
22
+ - **Development mode** (local SQLite): Requires `better-sqlite3`
23
+
24
+ If you only connect to a remote GraphDB server, you can skip native compilation entirely:
25
+
26
+ ```bash
27
+ npm install leangraph --ignore-optional
28
+ ```
29
+
30
+ For local/embedded mode, ensure `better-sqlite3` is installed:
31
+
32
+ ```bash
33
+ npm install leangraph better-sqlite3
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ```typescript
39
+ import { GraphDB } from 'leangraph';
40
+
41
+ const db = await GraphDB({
42
+ project: 'myapp', // or process.env.GRAPHDB_PROJECT
43
+ apiKey: 'lg_xxx', // or process.env.GRAPHDB_API_KEY
44
+ });
45
+
46
+ // Create nodes and relationships
47
+ await db.execute(`
48
+ CREATE (alice:User {name: 'Alice'})-[:FOLLOWS]->(bob:User {name: 'Bob'})
49
+ `);
50
+
51
+ // Query the graph
52
+ const users = await db.query('MATCH (u:User) RETURN u.name AS name');
53
+ console.log(users); // [{ name: 'Alice' }, { name: 'Bob' }]
54
+
55
+ db.close();
56
+ ```
57
+
58
+ ## Development vs Production Mode
59
+
60
+ LeanGraph automatically adapts based on the `NODE_ENV` environment variable:
61
+
62
+ | Mode | `NODE_ENV` | Behavior |
63
+ |------|-----------|----------|
64
+ | **Development** | `development` | Uses local SQLite database. `url` and `apiKey` are ignored. |
65
+ | **Production** | `production` (or unset) | Connects to remote server via HTTP. `url` and `apiKey` are required. |
66
+
67
+ This means you can use the **exact same code** in both environments:
68
+
69
+ ```typescript
70
+ // Works in both development and production!
71
+ const db = await GraphDB({ project: 'myapp' });
72
+ ```
73
+
74
+ ### Development Mode
75
+
76
+ When `NODE_ENV=development`:
77
+ - A local SQLite database is created automatically
78
+ - No server setup required
79
+ - `url` and `apiKey` parameters are ignored
80
+ - Data persists at `./data/{env}/{project}.db` by default
81
+
82
+ ```bash
83
+ # Run your app in development mode
84
+ NODE_ENV=development node app.js
85
+ ```
86
+
87
+ ### Production Mode
88
+
89
+ When `NODE_ENV=production` (or unset):
90
+ - Connects to a remote GraphDB server via HTTP
91
+ - `url` and `apiKey` are required
92
+
93
+ ```bash
94
+ # Run your app in production mode
95
+ NODE_ENV=production GRAPHDB_API_KEY=xxx node app.js
96
+ ```
97
+
98
+ ## Configuration Options
99
+
100
+ | Option | Type | Required | Default | Description |
101
+ |--------|------|----------|---------|-------------|
102
+ | `url` | `string` | No | `GRAPHDB_URL` or `https://leangraph.io` | Base URL of the GraphDB server (production only) |
103
+ | `project` | `string` | Yes | `GRAPHDB_PROJECT` | Project name |
104
+ | `apiKey` | `string` | No | `GRAPHDB_API_KEY` | API key for authentication (production only) |
105
+ | `env` | `string` | No | `NODE_ENV` or `production` | Environment (determines database isolation) |
106
+ | `dataPath` | `string` | No | `GRAPHDB_DATA_PATH` or `./data` | Path for local data storage (development only). Use `':memory:'` for in-memory database |
107
+
108
+ ### Examples
109
+
110
+ **Production** (default when `NODE_ENV` is unset or `production`):
111
+ ```typescript
112
+ const db = await GraphDB({
113
+ project: 'myapp', // or process.env.GRAPHDB_PROJECT
114
+ apiKey: 'lg_xxx', // or process.env.GRAPHDB_API_KEY
115
+ url: 'https://my-server', // or process.env.GRAPHDB_URL (default: leangraph.io)
116
+ });
117
+ ```
118
+
119
+ **Development** (when `NODE_ENV=development`):
120
+ ```typescript
121
+ const db = await GraphDB({
122
+ project: 'myapp', // or process.env.GRAPHDB_PROJECT
123
+ dataPath: './local-data', // or process.env.GRAPHDB_DATA_PATH (default: ./data)
124
+ });
125
+ // url and apiKey are ignored - uses local SQLite
126
+ ```
127
+
128
+ **Testing** (when `NODE_ENV=development`):
129
+ ```typescript
130
+ const db = await GraphDB({
131
+ project: 'test-project',
132
+ dataPath: ':memory:', // in-memory database, resets on each run
133
+ });
134
+ ```
135
+
136
+ ## API Reference
137
+
138
+ ### `GraphDB(options): Promise<GraphDBClient>`
139
+
140
+ Create a new GraphDB client. Returns a promise that resolves to a client instance.
141
+
142
+ ### `db.query<T>(cypher, params?): Promise<T[]>`
143
+
144
+ Execute a Cypher query and return results as an array.
145
+
146
+ ```typescript
147
+ const users = await db.query<{ name: string; age: number }>(
148
+ 'MATCH (u:User) WHERE u.age > $minAge RETURN u.name AS name, u.age AS age',
149
+ { minAge: 21 }
150
+ );
151
+ // users = [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }]
152
+ ```
153
+
154
+ ### `db.execute(cypher, params?): Promise<void>`
155
+
156
+ Execute a mutating query (CREATE, SET, DELETE, MERGE) without expecting return data.
157
+
158
+ ```typescript
159
+ await db.execute('CREATE (n:User {name: $name, email: $email})', {
160
+ name: 'Alice',
161
+ email: 'alice@example.com'
162
+ });
163
+ ```
164
+
165
+ ### `db.queryRaw<T>(cypher, params?): Promise<QueryResponse<T>>`
166
+
167
+ Execute a query and return the full response including metadata.
168
+
169
+ ```typescript
170
+ const response = await db.queryRaw('MATCH (n) RETURN n LIMIT 10');
171
+ console.log(response.meta.count); // Number of rows
172
+ console.log(response.meta.time_ms); // Query execution time in ms
173
+ console.log(response.data); // Array of results
174
+ ```
175
+
176
+ ### `db.createNode(label, properties?): Promise<string>`
177
+
178
+ Create a node and return its ID.
179
+
180
+ ```typescript
181
+ const userId = await db.createNode('User', { name: 'Alice', email: 'alice@example.com' });
182
+ ```
183
+
184
+ ### `db.getNode(label, filter): Promise<Record<string, unknown> | null>`
185
+
186
+ Find a node by label and properties.
187
+
188
+ ```typescript
189
+ const user = await db.getNode('User', { email: 'alice@example.com' });
190
+ if (user) {
191
+ console.log(user.name); // 'Alice'
192
+ }
193
+ ```
194
+
195
+ ### `db.updateNode(id, properties): Promise<void>`
196
+
197
+ Update properties on a node.
198
+
199
+ ```typescript
200
+ await db.updateNode(userId, { name: 'Alice Smith', verified: true });
201
+ ```
202
+
203
+ ### `db.deleteNode(id): Promise<void>`
204
+
205
+ Delete a node and all its relationships (DETACH DELETE).
206
+
207
+ ```typescript
208
+ await db.deleteNode(userId);
209
+ ```
210
+
211
+ ### `db.createEdge(sourceId, type, targetId, properties?): Promise<void>`
212
+
213
+ Create a relationship between two nodes.
214
+
215
+ ```typescript
216
+ await db.createEdge(aliceId, 'FOLLOWS', bobId, { since: '2024-01-01' });
217
+ ```
218
+
219
+ ### `db.health(): Promise<{ status: string; timestamp: string }>`
220
+
221
+ Check server health. In development mode, always returns `{ status: 'ok', ... }`.
222
+
223
+ ### `db.close(): void`
224
+
225
+ Close the client and release resources. **Always call this when done.**
226
+
227
+ ```typescript
228
+ const db = await GraphDB({ ... });
229
+ try {
230
+ // ... use db
231
+ } finally {
232
+ db.close();
233
+ }
234
+ ```
235
+
236
+ ## Cypher Quick Reference
237
+
238
+ ### Supported Clauses
239
+
240
+ | Clause | Example |
241
+ |--------|---------|
242
+ | `CREATE` | `CREATE (n:User {name: 'Alice'})` |
243
+ | `MATCH` | `MATCH (n:User) RETURN n` |
244
+ | `OPTIONAL MATCH` | `OPTIONAL MATCH (n)-[:KNOWS]->(m) RETURN m` |
245
+ | `MERGE` | `MERGE (n:User {email: $email})` |
246
+ | `WHERE` | `WHERE n.age > 21 AND n.active = true` |
247
+ | `SET` | `SET n.name = 'Bob', n.updated = true` |
248
+ | `DELETE` | `DELETE n` |
249
+ | `DETACH DELETE` | `DETACH DELETE n` |
250
+ | `RETURN` | `RETURN n.name AS name, count(*) AS total` |
251
+ | `WITH` | `WITH n, count(*) AS cnt WHERE cnt > 1` |
252
+ | `UNWIND` | `UNWIND $list AS item CREATE (n {value: item})` |
253
+ | `UNION / UNION ALL` | `MATCH (n:A) RETURN n UNION MATCH (m:B) RETURN m` |
254
+ | `ORDER BY` | `ORDER BY n.name DESC` |
255
+ | `SKIP / LIMIT` | `SKIP 10 LIMIT 5` |
256
+ | `DISTINCT` | `RETURN DISTINCT n.category` |
257
+ | `CASE/WHEN` | `RETURN CASE WHEN n.age > 18 THEN 'adult' ELSE 'minor' END` |
258
+ | `CALL` | `CALL db.labels() YIELD label RETURN label` |
259
+
260
+ ### Operators
261
+
262
+ | Category | Operators |
263
+ |----------|-----------|
264
+ | Comparison | `=`, `<>`, `<`, `>`, `<=`, `>=` |
265
+ | Logical | `AND`, `OR`, `NOT` |
266
+ | String | `CONTAINS`, `STARTS WITH`, `ENDS WITH` |
267
+ | List | `IN` |
268
+ | Null | `IS NULL`, `IS NOT NULL` |
269
+ | Pattern | `EXISTS` |
270
+ | Arithmetic | `+`, `-`, `*`, `/`, `%` |
271
+
272
+ ### Functions
273
+
274
+ **Aggregation:** `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `COLLECT`
275
+
276
+ **Scalar:** `ID`, `coalesce`
277
+
278
+ **String:** `toUpper`, `toLower`, `trim`, `substring`, `replace`, `toString`, `split`
279
+
280
+ **List:** `size`, `head`, `last`, `tail`, `keys`, `range`
281
+
282
+ **Node/Relationship:** `labels`, `type`, `properties`
283
+
284
+ **Math:** `abs`, `ceil`, `floor`, `round`, `rand`, `sqrt`
285
+
286
+ **Date/Time:** `date`, `datetime`, `timestamp`
287
+
288
+ ### Variable-Length Paths
289
+
290
+ ```cypher
291
+ -- Find friends of friends (1 to 3 hops)
292
+ MATCH (a:User {name: 'Alice'})-[:KNOWS*1..3]->(b:User)
293
+ RETURN DISTINCT b.name
294
+ ```
295
+
296
+ ### Procedures
297
+
298
+ ```cypher
299
+ -- List all labels
300
+ CALL db.labels() YIELD label RETURN label
301
+
302
+ -- List all relationship types
303
+ CALL db.relationshipTypes() YIELD type RETURN type
304
+
305
+ -- List all property keys
306
+ CALL db.propertyKeys() YIELD key RETURN key
307
+ ```
308
+
309
+ ## Running the Server (Production)
310
+
311
+ For production deployments, run a dedicated server:
312
+
313
+ ```bash
314
+ # Start the server
315
+ npx leangraph serve --port 3000 --data ./data
316
+
317
+ # Or with custom host binding
318
+ npx leangraph serve --port 3000 --host 0.0.0.0 --data ./data
319
+ ```
320
+
321
+ ### Creating Projects
322
+
323
+ ```bash
324
+ # Create a new project (generates API key)
325
+ npx leangraph create myapp --data ./data
326
+
327
+ # Output:
328
+ # [created] production/myapp.db
329
+ # API Key: lg_abc123...
330
+ ```
331
+
332
+ ### CLI Reference
333
+
334
+ ```bash
335
+ # Server
336
+ leangraph serve [options]
337
+ -p, --port <port> Port to listen on (default: 3000)
338
+ -d, --data <path> Data directory (default: /var/data/leangraph)
339
+ -H, --host <host> Host to bind to (default: localhost)
340
+ -b, --backup <path> Backup directory (enables backup endpoints)
341
+
342
+ # Project management
343
+ leangraph create <project> Create new project with API keys
344
+ leangraph delete <project> Delete project (use --force)
345
+ leangraph list List all projects
346
+
347
+ # Environment management
348
+ leangraph clone <project> --from <env> --to <env> Copy between environments
349
+ leangraph wipe <project> --env <env> Clear environment database
350
+
351
+ # Direct queries
352
+ leangraph query <env> <project> "CYPHER"
353
+
354
+ # Backup
355
+ leangraph backup [options]
356
+ -o, --output <path> Backup directory
357
+ -p, --project <name> Backup specific project
358
+ --status Show backup status
359
+
360
+ # API keys
361
+ leangraph apikey add <project>
362
+ leangraph apikey list
363
+ leangraph apikey remove <prefix>
364
+ ```
365
+
366
+ ## Why LeanGraph?
367
+
368
+ | Feature | LeanGraph | Neo4j |
369
+ |---------|-----------------|-------|
370
+ | **Deployment** | Single package, zero config | Complex setup, JVM required |
371
+ | **Development** | Local SQLite, no server needed | Server required |
372
+ | **Backup** | Just copy the SQLite file | Enterprise license required |
373
+ | **Resource usage** | ~50MB RAM | 1GB+ RAM minimum |
374
+ | **Cypher support** | Full (Neo4j 3.5 parity) | Full |
375
+ | **Cost** | Free, MIT license | Free tier limited |
376
+
377
+ LeanGraph is ideal for:
378
+ - Applications needing graph queries without ops burden
379
+ - Projects that outgrow JSON but don't need a full graph database
380
+ - Self-hosted deployments where simplicity matters
381
+ - Development and testing with instant local databases
382
+
383
+ ## Advanced Usage
384
+
385
+ ### Direct Database Access
386
+
387
+ For advanced use cases, you can access the underlying components:
388
+
389
+ ```typescript
390
+ import { GraphDatabase, Executor, parse, translate } from 'leangraph';
391
+
392
+ // Direct database access
393
+ const db = new GraphDatabase('./my-database.db');
394
+ db.initialize();
395
+
396
+ const executor = new Executor(db);
397
+ const result = executor.execute('MATCH (n) RETURN n LIMIT 10');
398
+
399
+ db.close();
400
+
401
+ // Parse Cypher to AST
402
+ const parseResult = parse('MATCH (n:User) RETURN n');
403
+ if (parseResult.success) {
404
+ console.log(parseResult.query);
405
+ }
406
+
407
+ // Translate AST to SQL
408
+ const translation = translate(parseResult.query, {});
409
+ console.log(translation.statements);
410
+ ```
411
+
412
+ ### Running a Custom Server
413
+
414
+ ```typescript
415
+ import { createServer } from 'leangraph';
416
+ import { serve } from '@hono/node-server';
417
+
418
+ const { app, dbManager } = createServer({
419
+ dataPath: './data',
420
+ apiKeys: {
421
+ 'my-api-key': { project: 'myapp', env: 'production' }
422
+ }
423
+ });
424
+
425
+ serve({ fetch: app.fetch, port: 3000 });
426
+ ```
427
+
428
+ ## Known Limitations
429
+
430
+ ### Large Integer Precision
431
+
432
+ JavaScript cannot precisely represent integers larger than `Number.MAX_SAFE_INTEGER` (9,007,199,254,740,991). Integers beyond this range will lose precision, which can cause unexpected behavior when comparing values.
433
+
434
+ **Example of the problem:**
435
+ ```javascript
436
+ // These two different numbers become equal in JavaScript!
437
+ const a = 4611686018427387905;
438
+ const b = 4611686018427387900;
439
+ console.log(a === b); // true (both round to 4611686018427388000)
440
+ ```
441
+
442
+ **Workaround:** Use strings for large integer IDs:
443
+ ```cypher
444
+ // Instead of:
445
+ CREATE (u:User {id: 4611686018427387905})
446
+
447
+ // Use strings:
448
+ CREATE (u:User {id: '4611686018427387905'})
449
+ MATCH (u:User {id: '4611686018427387905'}) RETURN u
450
+ ```
451
+
452
+ This limitation affects all JavaScript-based systems, including Neo4j's JavaScript driver. For IDs that may exceed the safe integer range, string representation is the recommended approach.
453
+
454
+ ## License
455
+
456
+ [MIT](https://github.com/co-l/leangraph/blob/main/LICENSE) - Conrad Lelubre
package/dist/auth.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { Context, Next } from "hono";
2
+ export interface ApiKeyConfig {
3
+ project?: string;
4
+ env?: string;
5
+ admin?: boolean;
6
+ }
7
+ export interface ValidationResult {
8
+ valid: boolean;
9
+ project?: string;
10
+ env?: string;
11
+ admin?: boolean;
12
+ }
13
+ export interface KeyInfo {
14
+ prefix: string;
15
+ project?: string;
16
+ env?: string;
17
+ admin?: boolean;
18
+ }
19
+ export declare class ApiKeyStore {
20
+ private keys;
21
+ /**
22
+ * Add an API key with its configuration.
23
+ */
24
+ addKey(key: string, config: ApiKeyConfig): void;
25
+ /**
26
+ * Remove an API key.
27
+ */
28
+ removeKey(key: string): void;
29
+ /**
30
+ * Validate an API key and return its permissions.
31
+ */
32
+ validate(key: string): ValidationResult;
33
+ /**
34
+ * List all keys (with prefixes only for security).
35
+ */
36
+ listKeys(): KeyInfo[];
37
+ /**
38
+ * Check if any keys are configured.
39
+ */
40
+ hasKeys(): boolean;
41
+ /**
42
+ * Load keys from an object (for initialization from config).
43
+ */
44
+ loadKeys(keys: Record<string, ApiKeyConfig>): void;
45
+ }
46
+ /**
47
+ * Hono middleware for API key authentication.
48
+ *
49
+ * - Skips authentication for /health endpoint
50
+ * - Requires Bearer token in Authorization header
51
+ * - Checks project/env restrictions for /query endpoints
52
+ * - Requires admin flag for /admin endpoints
53
+ */
54
+ export declare function authMiddleware(store: ApiKeyStore): (c: Context, next: Next) => Promise<void | (Response & import("hono").TypedResponse<{
55
+ success: false;
56
+ error: {
57
+ message: string;
58
+ };
59
+ }, 401, "json">) | (Response & import("hono").TypedResponse<{
60
+ success: false;
61
+ error: {
62
+ message: string;
63
+ };
64
+ }, 403, "json">)>;
65
+ export declare function generateApiKey(): string;
66
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMrC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAwC;IAEpD;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAI/C;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI5B;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;IAevC;;OAEG;IACH,QAAQ,IAAI,OAAO,EAAE;IAerB;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI;CAKnD;AAMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,IACjC,GAAG,OAAO,EAAE,MAAM,IAAI;;;;;;;;;;kBA0FrC;AAMD,wBAAgB,cAAc,IAAI,MAAM,CASvC"}
package/dist/auth.js ADDED
@@ -0,0 +1,148 @@
1
+ // API Key Authentication for LeanGraph
2
+ // ============================================================================
3
+ // ApiKeyStore
4
+ // ============================================================================
5
+ export class ApiKeyStore {
6
+ keys = new Map();
7
+ /**
8
+ * Add an API key with its configuration.
9
+ */
10
+ addKey(key, config) {
11
+ this.keys.set(key, config);
12
+ }
13
+ /**
14
+ * Remove an API key.
15
+ */
16
+ removeKey(key) {
17
+ this.keys.delete(key);
18
+ }
19
+ /**
20
+ * Validate an API key and return its permissions.
21
+ */
22
+ validate(key) {
23
+ const config = this.keys.get(key);
24
+ if (!config) {
25
+ return { valid: false };
26
+ }
27
+ return {
28
+ valid: true,
29
+ project: config.project,
30
+ env: config.env,
31
+ admin: config.admin,
32
+ };
33
+ }
34
+ /**
35
+ * List all keys (with prefixes only for security).
36
+ */
37
+ listKeys() {
38
+ const result = [];
39
+ for (const [key, config] of this.keys) {
40
+ result.push({
41
+ prefix: key.slice(0, 4) + "...",
42
+ project: config.project,
43
+ env: config.env,
44
+ admin: config.admin,
45
+ });
46
+ }
47
+ return result;
48
+ }
49
+ /**
50
+ * Check if any keys are configured.
51
+ */
52
+ hasKeys() {
53
+ return this.keys.size > 0;
54
+ }
55
+ /**
56
+ * Load keys from an object (for initialization from config).
57
+ */
58
+ loadKeys(keys) {
59
+ for (const [key, config] of Object.entries(keys)) {
60
+ this.addKey(key, config);
61
+ }
62
+ }
63
+ }
64
+ // ============================================================================
65
+ // Auth Middleware
66
+ // ============================================================================
67
+ /**
68
+ * Hono middleware for API key authentication.
69
+ *
70
+ * - Skips authentication for /health endpoint
71
+ * - Requires Bearer token in Authorization header
72
+ * - Checks project/env restrictions for /query endpoints
73
+ * - Requires admin flag for /admin endpoints
74
+ */
75
+ export function authMiddleware(store) {
76
+ return async (c, next) => {
77
+ const path = c.req.path;
78
+ // Skip auth for health endpoint
79
+ if (path === "/health") {
80
+ return next();
81
+ }
82
+ // Get authorization header
83
+ const authHeader = c.req.header("Authorization");
84
+ if (!authHeader) {
85
+ return c.json({
86
+ success: false,
87
+ error: { message: "Missing Authorization header" },
88
+ }, 401);
89
+ }
90
+ // Check Bearer format
91
+ if (!authHeader.startsWith("Bearer ")) {
92
+ return c.json({
93
+ success: false,
94
+ error: { message: "Authorization header must use Bearer scheme" },
95
+ }, 401);
96
+ }
97
+ const apiKey = authHeader.slice(7); // Remove "Bearer "
98
+ const validation = store.validate(apiKey);
99
+ if (!validation.valid) {
100
+ return c.json({
101
+ success: false,
102
+ error: { message: "Invalid API key" },
103
+ }, 401);
104
+ }
105
+ // Check admin access for /admin endpoints
106
+ if (path.startsWith("/admin") && !validation.admin) {
107
+ return c.json({
108
+ success: false,
109
+ error: { message: "Admin access required for this endpoint" },
110
+ }, 403);
111
+ }
112
+ // Check project/env restrictions for query endpoints
113
+ if (path.startsWith("/query/")) {
114
+ const parts = path.split("/");
115
+ const env = parts[2];
116
+ const project = parts[3];
117
+ // Check project restriction
118
+ if (validation.project && validation.project !== project) {
119
+ return c.json({
120
+ success: false,
121
+ error: { message: `Access denied for project: ${project}` },
122
+ }, 403);
123
+ }
124
+ // Check environment restriction
125
+ if (validation.env && validation.env !== env) {
126
+ return c.json({
127
+ success: false,
128
+ error: { message: `Access denied for environment: ${env}` },
129
+ }, 403);
130
+ }
131
+ }
132
+ // Store validation result in context for later use
133
+ c.set("auth", validation);
134
+ return next();
135
+ };
136
+ }
137
+ // ============================================================================
138
+ // Helper: Generate a random API key
139
+ // ============================================================================
140
+ export function generateApiKey() {
141
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
142
+ let key = "";
143
+ for (let i = 0; i < 32; i++) {
144
+ key += chars.charAt(Math.floor(Math.random() * chars.length));
145
+ }
146
+ return key;
147
+ }
148
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,uCAAuC;AA4BvC,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,MAAM,OAAO,WAAW;IACd,IAAI,GAA8B,IAAI,GAAG,EAAE,CAAC;IAEpD;;OAEG;IACH,MAAM,CAAC,GAAW,EAAE,MAAoB;QACtC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,GAAW;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1B,CAAC;QAED,OAAO;YACL,KAAK,EAAE,IAAI;YACX,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,MAAM,MAAM,GAAc,EAAE,CAAC;QAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC;gBACV,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK;gBAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,IAAkC;QACzC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;CACF;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,KAAK,EAAE,CAAU,EAAE,IAAU,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QAExB,gCAAgC;QAChC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,2BAA2B;QAC3B,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,CAAC,IAAI,CACX;gBACE,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,8BAA8B,EAAE;aACnD,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,OAAO,CAAC,CAAC,IAAI,CACX;gBACE,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,6CAA6C,EAAE;aAClE,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;QACvD,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE1C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC,IAAI,CACX;gBACE,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE;aACtC,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,0CAA0C;QAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACnD,OAAO,CAAC,CAAC,IAAI,CACX;gBACE,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,yCAAyC,EAAE;aAC9D,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,qDAAqD;QACrD,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAEzB,4BAA4B;YAC5B,IAAI,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;gBACzD,OAAO,CAAC,CAAC,IAAI,CACX;oBACE,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,EAAE,OAAO,EAAE,8BAA8B,OAAO,EAAE,EAAE;iBAC5D,EACD,GAAG,CACJ,CAAC;YACJ,CAAC;YAED,gCAAgC;YAChC,IAAI,UAAU,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAC7C,OAAO,CAAC,CAAC,IAAI,CACX;oBACE,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,EAAE,OAAO,EAAE,kCAAkC,GAAG,EAAE,EAAE;iBAC5D,EACD,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAE1B,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,oCAAoC;AACpC,+EAA+E;AAE/E,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,gEAAgE,CAAC;IAC/E,IAAI,GAAG,GAAG,EAAE,CAAC;IAEb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}