@layer-ai/core 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.
Files changed (49) hide show
  1. package/dist/index.d.ts +13 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +15 -0
  4. package/dist/lib/db/postgres.d.ts +27 -0
  5. package/dist/lib/db/postgres.d.ts.map +1 -0
  6. package/dist/lib/db/postgres.js +168 -0
  7. package/dist/lib/db/redis.d.ts +12 -0
  8. package/dist/lib/db/redis.d.ts.map +1 -0
  9. package/dist/lib/db/redis.js +95 -0
  10. package/dist/middleware/auth.d.ts +22 -0
  11. package/dist/middleware/auth.d.ts.map +1 -0
  12. package/dist/middleware/auth.js +89 -0
  13. package/dist/routes/auth.d.ts +4 -0
  14. package/dist/routes/auth.d.ts.map +1 -0
  15. package/dist/routes/auth.js +82 -0
  16. package/dist/routes/complete.d.ts +4 -0
  17. package/dist/routes/complete.d.ts.map +1 -0
  18. package/dist/routes/complete.js +223 -0
  19. package/dist/routes/gates.d.ts +4 -0
  20. package/dist/routes/gates.d.ts.map +1 -0
  21. package/dist/routes/gates.js +262 -0
  22. package/dist/routes/keys.d.ts +4 -0
  23. package/dist/routes/keys.d.ts.map +1 -0
  24. package/dist/routes/keys.js +70 -0
  25. package/dist/routes/logs.d.ts +4 -0
  26. package/dist/routes/logs.d.ts.map +1 -0
  27. package/dist/routes/logs.js +120 -0
  28. package/dist/services/providers/anthropic.d.ts +18 -0
  29. package/dist/services/providers/anthropic.d.ts.map +1 -0
  30. package/dist/services/providers/anthropic.js +55 -0
  31. package/dist/services/providers/base-adapter.d.ts +32 -0
  32. package/dist/services/providers/base-adapter.d.ts.map +1 -0
  33. package/dist/services/providers/base-adapter.js +89 -0
  34. package/dist/services/providers/google.d.ts +18 -0
  35. package/dist/services/providers/google.d.ts.map +1 -0
  36. package/dist/services/providers/google.js +39 -0
  37. package/dist/services/providers/openai-adapter.d.ts +19 -0
  38. package/dist/services/providers/openai-adapter.d.ts.map +1 -0
  39. package/dist/services/providers/openai-adapter.js +240 -0
  40. package/dist/services/providers/openai.d.ts +17 -0
  41. package/dist/services/providers/openai.d.ts.map +1 -0
  42. package/dist/services/providers/openai.js +43 -0
  43. package/dist/services/providers/test-openai-adapter.d.ts +2 -0
  44. package/dist/services/providers/test-openai-adapter.d.ts.map +1 -0
  45. package/dist/services/providers/test-openai-adapter.js +118 -0
  46. package/dist/services/task-analysis.d.ts +7 -0
  47. package/dist/services/task-analysis.d.ts.map +1 -0
  48. package/dist/services/task-analysis.js +74 -0
  49. package/package.json +53 -0
@@ -0,0 +1,13 @@
1
+ export { default as authRouter } from './routes/auth.js';
2
+ export { default as gatesRouter } from './routes/gates.js';
3
+ export { default as keysRouter } from './routes/keys.js';
4
+ export { default as logsRouter } from './routes/logs.js';
5
+ export { default as completeRouter } from './routes/complete.js';
6
+ export { authenticate } from './middleware/auth.js';
7
+ export { db } from './lib/db/postgres.js';
8
+ export { default as redis } from './lib/db/redis.js';
9
+ export { createCompletion as createOpenAICompletion } from './services/providers/openai.js';
10
+ export { createCompletion as createAnthropicCompletion } from './services/providers/anthropic.js';
11
+ export { createCompletion as createGoogleCompletion } from './services/providers/google.js';
12
+ export type { ProviderResponse } from './services/providers/openai.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,EAAE,gBAAgB,IAAI,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAClG,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // Routes
2
+ export { default as authRouter } from './routes/auth.js';
3
+ export { default as gatesRouter } from './routes/gates.js';
4
+ export { default as keysRouter } from './routes/keys.js';
5
+ export { default as logsRouter } from './routes/logs.js';
6
+ export { default as completeRouter } from './routes/complete.js';
7
+ // Middleware
8
+ export { authenticate } from './middleware/auth.js';
9
+ // Database
10
+ export { db } from './lib/db/postgres.js';
11
+ export { default as redis } from './lib/db/redis.js';
12
+ // Services - only export specific items to avoid conflicts
13
+ export { createCompletion as createOpenAICompletion } from './services/providers/openai.js';
14
+ export { createCompletion as createAnthropicCompletion } from './services/providers/anthropic.js';
15
+ export { createCompletion as createGoogleCompletion } from './services/providers/google.js';
@@ -0,0 +1,27 @@
1
+ import pg from 'pg';
2
+ import type { User, ApiKey, Gate } from '@layer-ai/sdk';
3
+ declare function getPool(): pg.Pool;
4
+ export declare const db: {
5
+ query(text: string, params?: any[]): Promise<pg.QueryResult<any>>;
6
+ getUserByEmail(email: string): Promise<User | null>;
7
+ getUserById(id: string): Promise<User | null>;
8
+ createUser(email: string, passwordHash: string): Promise<User>;
9
+ getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
10
+ createApiKey(userId: string, keyHash: string, keyPrefix: string, name: string): Promise<ApiKey>;
11
+ updateApiKeyLastUsed(keyHash: string): Promise<void>;
12
+ getApiKeysForUser(userId: string): Promise<ApiKey[]>;
13
+ deleteApiKey(id: string, userId: string): Promise<boolean>;
14
+ getGateByUserAndName(userId: string, gateName: string): Promise<Gate | null>;
15
+ getGatesForUser(userId: string): Promise<Gate[]>;
16
+ createGate(userId: string, data: any): Promise<Gate>;
17
+ getGateById(id: string): Promise<Gate | null>;
18
+ updateGate(id: string, data: any): Promise<Gate | null>;
19
+ deleteGate(id: string): Promise<boolean>;
20
+ logRequest(data: any): Promise<void>;
21
+ getSessionKeyByHash(keyHash: string): Promise<{
22
+ userId: string;
23
+ expiresAt: Date;
24
+ } | null>;
25
+ };
26
+ export default getPool;
27
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/lib/db/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAyB,MAAM,eAAe,CAAC;AAO/E,iBAAS,OAAO,IAAI,EAAE,CAAC,IAAI,CAqB1B;AA0BD,eAAO,MAAM,EAAE;gBAEK,MAAM,WAAW,GAAG,EAAE;0BASZ,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;oBAQnC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;sBAQ3B,MAAM,gBAAgB,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;6BASrC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;yBAQnC,MAAM,WAAW,MAAM,aAAa,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;kCAQjE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;8BAO1B,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;qBAQnC,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;iCAS7B,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;4BAQpD,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;uBAQ7B,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;oBAsBpC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;mBAQ9B,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;mBAgCxC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;qBASvB,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;iCAgBP,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAOhG,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,168 @@
1
+ import pg from 'pg';
2
+ const { Pool } = pg;
3
+ // Lazy-initialize connection pool
4
+ let pool = null;
5
+ function getPool() {
6
+ if (!pool) {
7
+ pool = new Pool({
8
+ connectionString: process.env.DATABASE_URL,
9
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
10
+ max: 20, // max num of connections
11
+ idleTimeoutMillis: 30000,
12
+ connectionTimeoutMillis: 2000,
13
+ });
14
+ // test connection on startup
15
+ pool.on('connect', () => {
16
+ console.log('Connected to PostgreSQL database');
17
+ });
18
+ pool.on('error', (err) => {
19
+ console.error('Unexpected database error:', err);
20
+ process.exit(-1);
21
+ });
22
+ }
23
+ return pool;
24
+ }
25
+ // function to convert snake_case DB cols to camelCase TypeScript
26
+ function toCamelCase(obj) {
27
+ if (!obj)
28
+ return obj;
29
+ const converted = {};
30
+ for (const key in obj) {
31
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
32
+ let value = obj[key];
33
+ // Convert numeric strings to numbers for specific fields
34
+ if ((camelKey === 'temperature' || camelKey === 'topP') && typeof value === 'string') {
35
+ value = parseFloat(value);
36
+ }
37
+ if (camelKey === 'maxTokens' && typeof value === 'string') {
38
+ value = parseInt(value, 10);
39
+ }
40
+ converted[camelKey] = value;
41
+ }
42
+ return converted;
43
+ }
44
+ // Database query functions
45
+ export const db = {
46
+ // generic query function
47
+ async query(text, params) {
48
+ const start = Date.now();
49
+ const res = await getPool().query(text, params);
50
+ const duration = Date.now() - start;
51
+ console.log('Executed query', { text, duration, rows: res.rowCount });
52
+ return res;
53
+ },
54
+ // Users
55
+ async getUserByEmail(email) {
56
+ const result = await getPool().query('SELECT * FROM users WHERE email = $1', [email]);
57
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
58
+ },
59
+ async getUserById(id) {
60
+ const result = await getPool().query('SELECT * FROM users WHERE id = $1', [id]);
61
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
62
+ },
63
+ async createUser(email, passwordHash) {
64
+ const result = await getPool().query('INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING *', [email, passwordHash]);
65
+ return toCamelCase(result.rows[0]);
66
+ },
67
+ // API Keys
68
+ async getApiKeyByHash(keyHash) {
69
+ const result = await getPool().query('SELECT * FROM api_keys WHERE key_hash = $1 AND is_active = true', [keyHash]);
70
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
71
+ },
72
+ async createApiKey(userId, keyHash, keyPrefix, name) {
73
+ const result = await getPool().query('INSERT INTO api_keys (user_id, key_hash, key_prefix, name) VALUES ($1, $2, $3, $4) RETURNING *', [userId, keyHash, keyPrefix, name]);
74
+ return toCamelCase(result.rows[0]);
75
+ },
76
+ async updateApiKeyLastUsed(keyHash) {
77
+ await getPool().query('UPDATE api_keys SET last_used_at = NOW() WHERE key_hash = $1', [keyHash]);
78
+ },
79
+ async getApiKeysForUser(userId) {
80
+ const result = await getPool().query('SELECT id, user_id, key_prefix, name, created_at, last_used_at FROM api_keys WHERE user_id = $1 AND is_active = true ORDER BY created_at DESC', [userId]);
81
+ return result.rows.map(toCamelCase);
82
+ },
83
+ async deleteApiKey(id, userId) {
84
+ const result = await getPool().query('UPDATE api_keys SET is_active = false WHERE id = $1 AND user_id = $2', [id, userId]);
85
+ return (result.rowCount ?? 0) > 0;
86
+ },
87
+ // Gates
88
+ async getGateByUserAndName(userId, gateName) {
89
+ const result = await getPool().query('SELECT * FROM gates WHERE user_id = $1 AND name = $2', [userId, gateName]);
90
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
91
+ },
92
+ async getGatesForUser(userId) {
93
+ const result = await getPool().query('SELECT * FROM gates WHERE user_id = $1 ORDER BY created_at DESC', [userId]);
94
+ return result.rows.map(toCamelCase);
95
+ },
96
+ async createGate(userId, data) {
97
+ const result = await getPool().query(`INSERT INTO gates (user_id, name, description, model, system_prompt, allow_overrides, temperature, max_tokens, top_p, tags, routing_strategy, fallback_models)
98
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`, [
99
+ userId,
100
+ data.name,
101
+ data.description,
102
+ data.model,
103
+ data.systemPrompt,
104
+ data.allowOverrides ? JSON.stringify(data.allowOverrides) : null,
105
+ data.temperature,
106
+ data.maxTokens,
107
+ data.topP,
108
+ JSON.stringify(data.tags || []),
109
+ data.routingStrategy || 'single',
110
+ JSON.stringify(data.fallbackModels || [])
111
+ ]);
112
+ return toCamelCase(result.rows[0]);
113
+ },
114
+ async getGateById(id) {
115
+ const result = await getPool().query('SELECT * FROM gates WHERE id = $1', [id]);
116
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
117
+ },
118
+ async updateGate(id, data) {
119
+ const result = await getPool().query(`UPDATE gates SET
120
+ description = COALESCE($2, description),
121
+ model = COALESCE($3, model),
122
+ system_prompt = COALESCE($4, system_prompt),
123
+ allow_overrides = COALESCE($5, allow_overrides),
124
+ temperature = COALESCE($6, temperature),
125
+ max_tokens = COALESCE($7, max_tokens),
126
+ top_p = COALESCE($8, top_p),
127
+ tags = COALESCE($9, tags),
128
+ routing_strategy = COALESCE($10, routing_strategy),
129
+ fallback_models = COALESCE($11, fallback_models),
130
+ updated_at = NOW()
131
+ WHERE id = $1 RETURNING *`, [
132
+ id,
133
+ data.description,
134
+ data.model,
135
+ data.systemPrompt,
136
+ data.allowOverrides ? JSON.stringify(data.allowOverrides) : null,
137
+ data.temperature,
138
+ data.maxTokens,
139
+ data.topP,
140
+ data.tags ? JSON.stringify(data.tags) : null,
141
+ data.routingStrategy,
142
+ data.fallbackModels ? JSON.stringify(data.fallbackModels) : null,
143
+ ]);
144
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
145
+ },
146
+ async deleteGate(id) {
147
+ const result = await getPool().query('DELETE FROM gates WHERE id = $1', [id]);
148
+ return (result.rowCount ?? 0) > 0;
149
+ },
150
+ // Request Logging
151
+ async logRequest(data) {
152
+ await getPool().query(`INSERT INTO requests (
153
+ user_id, gate_id, gate_name, model_requested, model_used, prompt_tokens,
154
+ completion_tokens, total_tokens, cost_usd, latency_ms, success,
155
+ error_message, user_agent, ip_address)
156
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, [
157
+ data.userId, data.gateId, data.gateName, data.modelRequested, data.modelUsed, data.promptTokens,
158
+ data.completionTokens, data.totalTokens, data.costUsd, data.latencyMs, data.success,
159
+ data.errorMessage, data.userAgent, data.ipAddress
160
+ ]);
161
+ },
162
+ // Session Keys
163
+ async getSessionKeyByHash(keyHash) {
164
+ const result = await getPool().query('SELECT user_id, expires_at FROM session_keys WHERE key_hash = $1 AND expires_at > NOW()', [keyHash]);
165
+ return result.rows[0] ? toCamelCase(result.rows[0]) : null;
166
+ }
167
+ };
168
+ export default getPool;
@@ -0,0 +1,12 @@
1
+ import Redis from 'ioredis';
2
+ import type { Gate } from '@layer-ai/sdk';
3
+ declare const redis: Redis;
4
+ export declare const cache: {
5
+ getGate(userId: string, gateName: string): Promise<Gate | null>;
6
+ setGate(userId: string, gateName: string, gate: Gate): Promise<void>;
7
+ invalidateGate(userId: string, gateName: string): Promise<void>;
8
+ invalidateUserGates(userId: string): Promise<void>;
9
+ ping(): Promise<boolean>;
10
+ };
11
+ export default redis;
12
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../src/lib/db/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,SAAS,CAAC;AAC5B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,QAAA,MAAM,KAAK,OAcT,CAAC;AAmBH,eAAO,MAAM,KAAK;oBAEM,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;oBAqB/C,MAAM,YAAY,MAAM,QAAQ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;2BAW7C,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gCAUnC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc1C,OAAO,CAAC,OAAO,CAAC;CAQ/B,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,95 @@
1
+ import Redis from 'ioredis';
2
+ // Create redis client
3
+ const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
4
+ maxRetriesPerRequest: 3,
5
+ retryStrategy(times) {
6
+ const delay = Math.min(times * 50, 2000);
7
+ return delay;
8
+ },
9
+ reconnectOnError(err) {
10
+ const targetError = 'READONLY';
11
+ if (err.message.includes(targetError)) {
12
+ // Reconnect when redis is in readonly mode
13
+ return true;
14
+ }
15
+ return false;
16
+ },
17
+ });
18
+ // Connection event handlers
19
+ redis.on('connect', () => {
20
+ console.log('Connected to Redis cache');
21
+ });
22
+ redis.on('error', (err) => {
23
+ console.error('Redis connection error:', err);
24
+ });
25
+ // Cache key builders
26
+ const CACHE_TTL = 300;
27
+ function getGateCacheKey(userId, gateName) {
28
+ return `gate:${userId}:${gateName}`;
29
+ }
30
+ // Cache operations
31
+ export const cache = {
32
+ // get the gate
33
+ async getGate(userId, gateName) {
34
+ try {
35
+ const key = getGateCacheKey(userId, gateName);
36
+ const cached = await redis.get(key);
37
+ if (!cached) {
38
+ return null;
39
+ }
40
+ const gate = JSON.parse(cached);
41
+ gate.createdAt = new Date(gate.createdAt);
42
+ gate.updatedAt = new Date(gate.updatedAt);
43
+ return gate;
44
+ }
45
+ catch (error) {
46
+ console.error('Redis get error:', error);
47
+ return null; // if we fail, then we fetch from the db
48
+ }
49
+ },
50
+ // Set gate in cache
51
+ async setGate(userId, gateName, gate) {
52
+ try {
53
+ const key = getGateCacheKey(userId, gateName);
54
+ await redis.setex(key, CACHE_TTL, JSON.stringify(gate));
55
+ }
56
+ catch (error) {
57
+ console.error('Redis set error:', error);
58
+ // cache miss here is okay
59
+ }
60
+ },
61
+ // Invalidate gate cache
62
+ async invalidateGate(userId, gateName) {
63
+ try {
64
+ const key = getGateCacheKey(userId, gateName);
65
+ await redis.del(key);
66
+ }
67
+ catch (error) {
68
+ console.error('Redis delete error:', error);
69
+ }
70
+ },
71
+ // Invalidate all gates for a user
72
+ async invalidateUserGates(userId) {
73
+ try {
74
+ const pattern = `gate:${userId}:*`;
75
+ const keys = await redis.keys(pattern);
76
+ if (keys.length > 0) {
77
+ await redis.del(...keys);
78
+ }
79
+ }
80
+ catch (error) {
81
+ console.error('Redis bulk delete error:', error);
82
+ }
83
+ },
84
+ // health check
85
+ async ping() {
86
+ try {
87
+ const result = await redis.ping();
88
+ return result === 'PONG';
89
+ }
90
+ catch (error) {
91
+ return false;
92
+ }
93
+ },
94
+ };
95
+ export default redis;
@@ -0,0 +1,22 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ declare global {
3
+ namespace Express {
4
+ interface Request {
5
+ userId?: string;
6
+ apiKeyHash?: string;
7
+ }
8
+ }
9
+ }
10
+ /**
11
+ * Auth middleware for api key validation
12
+ *
13
+ * Expected header format:
14
+ * Authorization: Bearer layer_abc123...
15
+ */
16
+ export declare function authenticate(req: Request, res: Response, next: NextFunction): Promise<void>;
17
+ /**
18
+ * Optional middleware for endpoints that don't require auth
19
+ * like the health check public endpoints etc.
20
+ */
21
+ export declare function optionalAuth(req: Request, res: Response, next: NextFunction): void;
22
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB;KACF;CACF;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,IAAI,CAWN"}
@@ -0,0 +1,89 @@
1
+ import crypto from 'crypto';
2
+ import { db } from '../lib/db/postgres.js';
3
+ /**
4
+ * Auth middleware for api key validation
5
+ *
6
+ * Expected header format:
7
+ * Authorization: Bearer layer_abc123...
8
+ */
9
+ export async function authenticate(req, res, next) {
10
+ try {
11
+ // Extract Authorization header
12
+ const authHeader = req.headers.authorization;
13
+ if (!authHeader) {
14
+ res.status(401).json({
15
+ error: 'unauthorized',
16
+ message: 'Missing Authorization header',
17
+ });
18
+ return;
19
+ }
20
+ // check format: "Bearer layer_..."
21
+ if (!authHeader.startsWith('Bearer ')) {
22
+ res.status(401).json({
23
+ error: 'unauthorized',
24
+ message: 'Invalid Authorization header format. Expected: Bearer <api_key>',
25
+ });
26
+ return;
27
+ }
28
+ const apiKey = authHeader.substring(7); // Remove "Bearer "
29
+ if (!apiKey || !apiKey.startsWith('layer_')) {
30
+ res.status(401).json({
31
+ error: 'unauthorized',
32
+ message: 'Invalid API key format. Must start with "layer_"',
33
+ });
34
+ return;
35
+ }
36
+ const keyHash = crypto
37
+ .createHash('sha256')
38
+ .update(apiKey)
39
+ .digest('hex');
40
+ const apiKeyRecord = await db.getApiKeyByHash(keyHash);
41
+ if (!apiKeyRecord) {
42
+ // Not an API key (it's potentially a session key)
43
+ const sessionKey = await db.getSessionKeyByHash(keyHash);
44
+ if (!sessionKey) {
45
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid API key' });
46
+ return;
47
+ }
48
+ req.userId = sessionKey.userId;
49
+ next();
50
+ return;
51
+ }
52
+ if (!apiKeyRecord.isActive) {
53
+ res.status(401).json({
54
+ error: 'unauthorized',
55
+ message: 'API key has been revoked',
56
+ });
57
+ return;
58
+ }
59
+ // Attach userId to request for downstream handlers
60
+ req.userId = apiKeyRecord.userId;
61
+ req.apiKeyHash = keyHash;
62
+ // Update last_used_at timestamp (async, dont await)
63
+ db.updateApiKeyLastUsed(keyHash).catch((err) => {
64
+ console.error('Failed to update API key last_used_at:', err);
65
+ });
66
+ next();
67
+ }
68
+ catch (error) {
69
+ console.error('Authentication error:', error);
70
+ res.status(500).json({
71
+ error: 'internal_error',
72
+ message: 'Authentication failed'
73
+ });
74
+ }
75
+ }
76
+ /**
77
+ * Optional middleware for endpoints that don't require auth
78
+ * like the health check public endpoints etc.
79
+ */
80
+ export function optionalAuth(req, res, next) {
81
+ const authHeader = req.headers.authorization;
82
+ if (!authHeader) {
83
+ // No auth header = proceed without userId
84
+ next();
85
+ return;
86
+ }
87
+ // if auth header exists, validate it
88
+ authenticate(req, res, next);
89
+ }
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAKpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA4FpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { Router } from 'express';
2
+ import bcrypt from 'bcryptjs';
3
+ import crypto from 'crypto';
4
+ import { db } from '../lib/db/postgres.js';
5
+ const router = Router();
6
+ // POST /auth/signup
7
+ router.post('/signup', async (req, res) => {
8
+ try {
9
+ const { email, password } = req.body;
10
+ if (!email || !password) {
11
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
12
+ return;
13
+ }
14
+ const existing = await db.getUserByEmail(email);
15
+ if (existing) {
16
+ res.status(409).json({ error: 'conflict', message: 'Email already registered' });
17
+ return;
18
+ }
19
+ const passwordHash = await bcrypt.hash(password, 10);
20
+ const user = await db.createUser(email, passwordHash);
21
+ res.status(201).json({ id: user.id, email: user.email });
22
+ }
23
+ catch (error) {
24
+ console.error('Signup error:', error);
25
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create account ' });
26
+ }
27
+ });
28
+ // POST /auth/login
29
+ router.post('/login', async (req, res) => {
30
+ try {
31
+ const { email, password } = req.body;
32
+ if (!email || !password) {
33
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
34
+ return;
35
+ }
36
+ const user = await db.getUserByEmail(email);
37
+ if (!user) {
38
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
39
+ return;
40
+ }
41
+ const valid = await bcrypt.compare(password, user.passwordHash);
42
+ if (!valid) {
43
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
44
+ return;
45
+ }
46
+ res.json({ id: user.id, email: user.email });
47
+ }
48
+ catch (error) {
49
+ console.error('Login error', error);
50
+ res.status(500).json({ error: 'internal_error', message: 'Failed to login' });
51
+ }
52
+ });
53
+ // POST /auth/token
54
+ router.post('/token', async (req, res) => {
55
+ try {
56
+ const { email, password } = req.body;
57
+ if (!email || !password) {
58
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
59
+ return;
60
+ }
61
+ const user = await db.getUserByEmail(email);
62
+ if (!user) {
63
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
64
+ return;
65
+ }
66
+ const valid = await bcrypt.compare(password, user.passwordHash);
67
+ if (!valid) {
68
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
69
+ return;
70
+ }
71
+ const rawKey = `layer_${crypto.randomBytes(32).toString('hex')}`;
72
+ const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');
73
+ const keyPrefix = rawKey.substring(0, 12); // "layer_xxxxxx"
74
+ await db.createApiKey(user.id, keyHash, keyPrefix, 'CLI');
75
+ res.status(201).json({ apiKey: rawKey });
76
+ }
77
+ catch (error) {
78
+ console.error('api key creation error', error);
79
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create api key' });
80
+ }
81
+ });
82
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=complete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../src/routes/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAWpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA2QpC,eAAe,MAAM,CAAC"}