@mcp-ts/sdk 1.6.1 → 2.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.
- package/README.md +12 -6
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-adapter.js +4 -5
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +4 -5
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +9 -3
- package/dist/adapters/ai-adapter.d.ts +9 -3
- package/dist/adapters/ai-adapter.js +20 -6
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +20 -6
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.js +9 -6
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +9 -6
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.js +5 -1
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +5 -1
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +7 -1
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +7 -1
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +9 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +9 -13
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +7 -7
- package/dist/client/react.d.ts +7 -7
- package/dist/client/react.js +111 -63
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +111 -63
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +7 -7
- package/dist/client/vue.d.ts +7 -7
- package/dist/client/vue.js +14 -18
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +14 -18
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-DhA-OEAe.d.ts → index-C9gvpxy5.d.ts} +5 -5
- package/dist/{index-bFL4ZF2N.d.mts → index-eaH14_5u.d.mts} +5 -5
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +616 -370
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +615 -370
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CHE8QpVE.d.ts → multi-session-client-BYtguGJm.d.ts} +22 -22
- package/dist/{multi-session-client-CQsRbxYI.d.mts → multi-session-client-DYNe6az3.d.mts} +22 -22
- package/dist/server/index.d.mts +31 -34
- package/dist/server/index.d.ts +31 -34
- package/dist/server/index.js +531 -256
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +530 -256
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +5 -5
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +76 -101
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +76 -101
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-Dh2804tM.d.ts → tool-router-Ddtybmr0.d.ts} +71 -73
- package/dist/{tool-router-BVaV1udm.d.mts → tool-router-Dnd6IOKC.d.mts} +71 -73
- package/dist/{types-rIuN1CQi.d.mts → types-BCAG20P6.d.mts} +4 -4
- package/dist/{types-rIuN1CQi.d.ts → types-BCAG20P6.d.ts} +4 -4
- package/dist/{utils-0qmYrqoa.d.mts → utils-DELRKQPU.d.mts} +1 -1
- package/dist/{utils-0qmYrqoa.d.ts → utils-DELRKQPU.d.ts} +1 -1
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +69 -0
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
- package/{supabase/migrations → migrations/supabase}/20260330195700_install_mcp_sessions.sql +7 -9
- package/package.json +14 -5
- package/src/adapters/ai-adapter.ts +30 -1
- package/src/adapters/langchain-adapter.ts +6 -2
- package/src/adapters/mastra-adapter.ts +6 -2
- package/src/bin/mcp-ts.ts +8 -1
- package/src/client/core/app-host.ts +1 -1
- package/src/client/core/sse-client.ts +12 -14
- package/src/client/core/types.ts +1 -1
- package/src/client/react/oauth-popup.tsx +111 -51
- package/src/client/react/use-mcp-apps.tsx +1 -1
- package/src/client/react/use-mcp.ts +11 -11
- package/src/client/vue/use-mcp.ts +10 -10
- package/src/server/handlers/nextjs-handler.ts +18 -15
- package/src/server/handlers/sse-handler.ts +29 -29
- package/src/server/index.ts +1 -1
- package/src/server/mcp/multi-session-client.ts +17 -17
- package/src/server/mcp/oauth-client.ts +37 -37
- package/src/server/mcp/storage-oauth-provider.ts +17 -17
- package/src/server/storage/file-backend.ts +25 -25
- package/src/server/storage/index.ts +67 -10
- package/src/server/storage/memory-backend.ts +34 -34
- package/src/server/storage/neon-backend.ts +281 -0
- package/src/server/storage/redis-backend.ts +64 -64
- package/src/server/storage/sqlite-backend.ts +33 -33
- package/src/server/storage/supabase-backend.ts +23 -24
- package/src/server/storage/types.ts +18 -21
- package/src/shared/errors.ts +1 -1
- package/src/shared/index.ts +1 -2
- package/src/shared/meta-tools.ts +4 -6
- package/src/shared/schema-compressor.ts +2 -42
- package/src/shared/tool-index.ts +89 -84
- package/src/shared/tool-router.ts +0 -24
- package/src/shared/types.ts +4 -4
- /package/{supabase/migrations → migrations/supabase}/20260421010000_add_session_cleanup_cron.sql +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SessionStore, Session, SetClientOptions } from './types.js';
|
|
2
2
|
import { generateSessionId } from '../../shared/utils.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* In-memory implementation of
|
|
5
|
+
* In-memory implementation of SessionStore
|
|
6
6
|
* Useful for local development or testing
|
|
7
7
|
*/
|
|
8
|
-
export class MemoryStorageBackend implements
|
|
9
|
-
// Map<
|
|
10
|
-
private sessions = new Map<string,
|
|
8
|
+
export class MemoryStorageBackend implements SessionStore {
|
|
9
|
+
// Map<userId:sessionId, Session>
|
|
10
|
+
private sessions = new Map<string, Session>();
|
|
11
11
|
|
|
12
|
-
// Map<
|
|
13
|
-
private
|
|
12
|
+
// Map<userId, Set<sessionId>>
|
|
13
|
+
private userIdSessions = new Map<string, Set<string>>();
|
|
14
14
|
|
|
15
15
|
constructor() { }
|
|
16
16
|
|
|
@@ -18,19 +18,19 @@ export class MemoryStorageBackend implements StorageBackend {
|
|
|
18
18
|
console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
private getSessionKey(
|
|
22
|
-
return `${
|
|
21
|
+
private getSessionKey(userId: string, sessionId: string): string {
|
|
22
|
+
return `${userId}:${sessionId}`;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
generateSessionId(): string {
|
|
26
26
|
return generateSessionId();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async
|
|
30
|
-
const { sessionId,
|
|
31
|
-
if (!sessionId || !
|
|
29
|
+
async create(session: Session, ttl?: number): Promise<void> {
|
|
30
|
+
const { sessionId, userId } = session;
|
|
31
|
+
if (!sessionId || !userId) throw new Error('userId and sessionId required');
|
|
32
32
|
|
|
33
|
-
const sessionKey = this.getSessionKey(
|
|
33
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
34
34
|
if (this.sessions.has(sessionKey)) {
|
|
35
35
|
throw new Error(`Session ${sessionId} already exists`);
|
|
36
36
|
}
|
|
@@ -38,17 +38,17 @@ export class MemoryStorageBackend implements StorageBackend {
|
|
|
38
38
|
this.sessions.set(sessionKey, session);
|
|
39
39
|
|
|
40
40
|
// Update index
|
|
41
|
-
if (!this.
|
|
42
|
-
this.
|
|
41
|
+
if (!this.userIdSessions.has(userId)) {
|
|
42
|
+
this.userIdSessions.set(userId, new Set());
|
|
43
43
|
}
|
|
44
|
-
this.
|
|
44
|
+
this.userIdSessions.get(userId)!.add(sessionId);
|
|
45
45
|
// Note: TTL is ignored in memory backend - sessions don't auto-expire
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async
|
|
49
|
-
if (!
|
|
48
|
+
async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
|
|
49
|
+
if (!userId || !sessionId) throw new Error('userId and sessionId required');
|
|
50
50
|
|
|
51
|
-
const sessionKey = this.getSessionKey(
|
|
51
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
52
52
|
const current = this.sessions.get(sessionKey);
|
|
53
53
|
|
|
54
54
|
if (!current) {
|
|
@@ -65,23 +65,23 @@ export class MemoryStorageBackend implements StorageBackend {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
async
|
|
69
|
-
const sessionKey = this.getSessionKey(
|
|
68
|
+
async get(userId: string, sessionId: string): Promise<Session | null> {
|
|
69
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
70
70
|
return this.sessions.get(sessionKey) || null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async
|
|
74
|
-
const set = this.
|
|
73
|
+
async listIds(userId: string): Promise<string[]> {
|
|
74
|
+
const set = this.userIdSessions.get(userId);
|
|
75
75
|
return set ? Array.from(set) : [];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
async
|
|
79
|
-
const set = this.
|
|
78
|
+
async list(userId: string): Promise<Session[]> {
|
|
79
|
+
const set = this.userIdSessions.get(userId);
|
|
80
80
|
if (!set) return [];
|
|
81
81
|
|
|
82
|
-
const results:
|
|
82
|
+
const results: Session[] = [];
|
|
83
83
|
for (const sessionId of set) {
|
|
84
|
-
const session = this.sessions.get(this.getSessionKey(
|
|
84
|
+
const session = this.sessions.get(this.getSessionKey(userId, sessionId));
|
|
85
85
|
if (session) {
|
|
86
86
|
results.push(session);
|
|
87
87
|
}
|
|
@@ -89,29 +89,29 @@ export class MemoryStorageBackend implements StorageBackend {
|
|
|
89
89
|
return results;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
async
|
|
93
|
-
const sessionKey = this.getSessionKey(
|
|
92
|
+
async delete(userId: string, sessionId: string): Promise<void> {
|
|
93
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
94
94
|
this.sessions.delete(sessionKey);
|
|
95
95
|
|
|
96
|
-
const set = this.
|
|
96
|
+
const set = this.userIdSessions.get(userId);
|
|
97
97
|
if (set) {
|
|
98
98
|
set.delete(sessionId);
|
|
99
99
|
if (set.size === 0) {
|
|
100
|
-
this.
|
|
100
|
+
this.userIdSessions.delete(userId);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
async
|
|
105
|
+
async listAllIds(): Promise<string[]> {
|
|
106
106
|
return Array.from(this.sessions.values()).map(s => s.sessionId);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
async clearAll(): Promise<void> {
|
|
110
110
|
this.sessions.clear();
|
|
111
|
-
this.
|
|
111
|
+
this.userIdSessions.clear();
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
async
|
|
114
|
+
async cleanupExpired(): Promise<void> {
|
|
115
115
|
// In-memory doesn't implement TTL automatically,
|
|
116
116
|
// but we could check createdAt + TTL here if needed.
|
|
117
117
|
// For now, no-op.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type { SessionStore, Session } from './types.js';
|
|
2
|
+
import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
|
|
3
|
+
import { generateSessionId } from '../../shared/utils.js';
|
|
4
|
+
import { encryptObject, decryptObject } from './crypto.js';
|
|
5
|
+
|
|
6
|
+
export interface NeonStorageOptions {
|
|
7
|
+
schema?: string;
|
|
8
|
+
table?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type NeonSql = {
|
|
12
|
+
query(queryWithPlaceholders: string, params?: unknown[]): Promise<any[]>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type NeonSessionRow = {
|
|
16
|
+
session_id: string;
|
|
17
|
+
server_id?: string | null;
|
|
18
|
+
server_name?: string | null;
|
|
19
|
+
server_url: string;
|
|
20
|
+
transport_type: 'sse' | 'streamable-http';
|
|
21
|
+
callback_url: string;
|
|
22
|
+
created_at: string | Date;
|
|
23
|
+
user_id: string;
|
|
24
|
+
headers?: unknown;
|
|
25
|
+
active?: boolean | null;
|
|
26
|
+
client_information?: unknown;
|
|
27
|
+
tokens?: unknown;
|
|
28
|
+
code_verifier?: string | null;
|
|
29
|
+
client_id?: string | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class NeonStorageBackend implements SessionStore {
|
|
33
|
+
private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
|
|
34
|
+
private readonly tableName: string;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly sql: NeonSql,
|
|
38
|
+
options: NeonStorageOptions = {}
|
|
39
|
+
) {
|
|
40
|
+
const schema = options.schema || 'public';
|
|
41
|
+
const table = options.table || 'mcp_sessions';
|
|
42
|
+
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async init(): Promise<void> {
|
|
46
|
+
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
47
|
+
'SELECT to_regclass($1) AS exists',
|
|
48
|
+
[this.tableName.replace(/"/g, '')]
|
|
49
|
+
) as Array<{ exists: string | null }>;
|
|
50
|
+
|
|
51
|
+
if (!exists) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'[NeonStorage] Table "mcp_sessions" not found in your database. ' +
|
|
54
|
+
'Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
generateSessionId(): string {
|
|
62
|
+
return generateSessionId();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private quoteIdentifier(identifier: string): string {
|
|
66
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
67
|
+
throw new Error(`Invalid Neon storage identifier: ${identifier}`);
|
|
68
|
+
}
|
|
69
|
+
return `"${identifier}"`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private mapRowToSessionData(row: NeonSessionRow): Session {
|
|
73
|
+
return {
|
|
74
|
+
sessionId: row.session_id,
|
|
75
|
+
serverId: row.server_id ?? undefined,
|
|
76
|
+
serverName: row.server_name ?? undefined,
|
|
77
|
+
serverUrl: row.server_url,
|
|
78
|
+
transportType: row.transport_type,
|
|
79
|
+
callbackUrl: row.callback_url,
|
|
80
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
81
|
+
userId: row.user_id,
|
|
82
|
+
headers: decryptObject(row.headers),
|
|
83
|
+
active: row.active ?? false,
|
|
84
|
+
clientInformation: row.client_information as Session['clientInformation'],
|
|
85
|
+
tokens: decryptObject(row.tokens),
|
|
86
|
+
codeVerifier: row.code_verifier ?? undefined,
|
|
87
|
+
clientId: row.client_id ?? undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async create(session: Session, ttl?: number): Promise<void> {
|
|
92
|
+
const { sessionId, userId } = session;
|
|
93
|
+
if (!sessionId || !userId) throw new Error('userId and sessionId required');
|
|
94
|
+
|
|
95
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
96
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await this.sql.query(
|
|
100
|
+
`INSERT INTO ${this.tableName} (
|
|
101
|
+
session_id,
|
|
102
|
+
user_id,
|
|
103
|
+
server_id,
|
|
104
|
+
server_name,
|
|
105
|
+
server_url,
|
|
106
|
+
transport_type,
|
|
107
|
+
callback_url,
|
|
108
|
+
created_at,
|
|
109
|
+
headers,
|
|
110
|
+
active,
|
|
111
|
+
client_information,
|
|
112
|
+
tokens,
|
|
113
|
+
code_verifier,
|
|
114
|
+
client_id,
|
|
115
|
+
expires_at
|
|
116
|
+
) VALUES (
|
|
117
|
+
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
118
|
+
$9, $10, $11, $12, $13, $14, $15
|
|
119
|
+
)`,
|
|
120
|
+
[
|
|
121
|
+
sessionId,
|
|
122
|
+
userId,
|
|
123
|
+
session.serverId,
|
|
124
|
+
session.serverName,
|
|
125
|
+
session.serverUrl,
|
|
126
|
+
session.transportType,
|
|
127
|
+
session.callbackUrl,
|
|
128
|
+
new Date(session.createdAt || Date.now()).toISOString(),
|
|
129
|
+
encryptObject(session.headers),
|
|
130
|
+
session.active ?? false,
|
|
131
|
+
session.clientInformation,
|
|
132
|
+
encryptObject(session.tokens),
|
|
133
|
+
session.codeVerifier,
|
|
134
|
+
session.clientId,
|
|
135
|
+
expiresAt,
|
|
136
|
+
]
|
|
137
|
+
);
|
|
138
|
+
} catch (error: any) {
|
|
139
|
+
if (error.code === '23505') {
|
|
140
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
|
|
147
|
+
const currentSession = await this.get(userId, sessionId);
|
|
148
|
+
if (!currentSession) {
|
|
149
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const updatedSession = { ...currentSession, ...data };
|
|
153
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
154
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
155
|
+
|
|
156
|
+
const updatedRows = await this.sql.query(
|
|
157
|
+
`UPDATE ${this.tableName}
|
|
158
|
+
SET
|
|
159
|
+
server_id = $1,
|
|
160
|
+
server_name = $2,
|
|
161
|
+
server_url = $3,
|
|
162
|
+
transport_type = $4,
|
|
163
|
+
callback_url = $5,
|
|
164
|
+
active = $6,
|
|
165
|
+
headers = $7,
|
|
166
|
+
client_information = $8,
|
|
167
|
+
tokens = $9,
|
|
168
|
+
code_verifier = $10,
|
|
169
|
+
client_id = $11,
|
|
170
|
+
expires_at = $12,
|
|
171
|
+
updated_at = now()
|
|
172
|
+
WHERE user_id = $13 AND session_id = $14
|
|
173
|
+
RETURNING id`,
|
|
174
|
+
[
|
|
175
|
+
updatedSession.serverId,
|
|
176
|
+
updatedSession.serverName,
|
|
177
|
+
updatedSession.serverUrl,
|
|
178
|
+
updatedSession.transportType,
|
|
179
|
+
updatedSession.callbackUrl,
|
|
180
|
+
updatedSession.active ?? false,
|
|
181
|
+
encryptObject(updatedSession.headers),
|
|
182
|
+
updatedSession.clientInformation,
|
|
183
|
+
encryptObject(updatedSession.tokens),
|
|
184
|
+
updatedSession.codeVerifier,
|
|
185
|
+
updatedSession.clientId,
|
|
186
|
+
expiresAt,
|
|
187
|
+
userId,
|
|
188
|
+
sessionId,
|
|
189
|
+
]
|
|
190
|
+
) as Array<{ id: string }>;
|
|
191
|
+
|
|
192
|
+
if (updatedRows.length === 0) {
|
|
193
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async get(userId: string, sessionId: string): Promise<Session | null> {
|
|
198
|
+
try {
|
|
199
|
+
const rows = await this.sql.query(
|
|
200
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
201
|
+
[userId, sessionId]
|
|
202
|
+
) as NeonSessionRow[];
|
|
203
|
+
return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('[NeonStorage] Failed to get session:', error);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async list(userId: string): Promise<Session[]> {
|
|
211
|
+
try {
|
|
212
|
+
const rows = await this.sql.query(
|
|
213
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1`,
|
|
214
|
+
[userId]
|
|
215
|
+
) as NeonSessionRow[];
|
|
216
|
+
return rows.map((row) => this.mapRowToSessionData(row));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error(`[NeonStorage] Failed to get session data for ${userId}:`, error);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async delete(userId: string, sessionId: string): Promise<void> {
|
|
224
|
+
try {
|
|
225
|
+
await this.sql.query(
|
|
226
|
+
`DELETE FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
227
|
+
[userId, sessionId]
|
|
228
|
+
);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('[NeonStorage] Failed to remove session:', error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async listIds(userId: string): Promise<string[]> {
|
|
235
|
+
try {
|
|
236
|
+
const rows = await this.sql.query(
|
|
237
|
+
`SELECT session_id FROM ${this.tableName} WHERE user_id = $1`,
|
|
238
|
+
[userId]
|
|
239
|
+
) as Array<{ session_id: string }>;
|
|
240
|
+
return rows.map((row) => row.session_id);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`[NeonStorage] Failed to get sessions for ${userId}:`, error);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async listAllIds(): Promise<string[]> {
|
|
248
|
+
try {
|
|
249
|
+
const rows = await this.sql.query(
|
|
250
|
+
`SELECT session_id FROM ${this.tableName}`
|
|
251
|
+
) as Array<{ session_id: string }>;
|
|
252
|
+
return rows.map((row) => row.session_id);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('[NeonStorage] Failed to get all sessions:', error);
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async clearAll(): Promise<void> {
|
|
260
|
+
try {
|
|
261
|
+
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('[NeonStorage] Failed to clear sessions:', error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async cleanupExpired(): Promise<void> {
|
|
268
|
+
try {
|
|
269
|
+
await this.sql.query(
|
|
270
|
+
`DELETE FROM ${this.tableName} WHERE expires_at < $1`,
|
|
271
|
+
[new Date().toISOString()]
|
|
272
|
+
);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('[NeonStorage] Failed to cleanup expired sessions:', error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async disconnect(): Promise<void> {
|
|
279
|
+
// Neon HTTP queries do not hold a persistent connection.
|
|
280
|
+
}
|
|
281
|
+
}
|