@dexto/core 1.5.0 → 1.5.1

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 (61) hide show
  1. package/dist/agent/schemas.d.ts +48 -0
  2. package/dist/agent/schemas.d.ts.map +1 -1
  3. package/dist/events/index.cjs +4 -1
  4. package/dist/events/index.d.ts +20 -4
  5. package/dist/events/index.d.ts.map +1 -1
  6. package/dist/events/index.js +3 -1
  7. package/dist/llm/executor/provider-options.cjs +87 -0
  8. package/dist/llm/executor/provider-options.d.ts +49 -0
  9. package/dist/llm/executor/provider-options.d.ts.map +1 -0
  10. package/dist/llm/executor/provider-options.js +63 -0
  11. package/dist/llm/executor/stream-processor.cjs +11 -8
  12. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  13. package/dist/llm/executor/stream-processor.js +11 -8
  14. package/dist/llm/executor/turn-executor.cjs +10 -0
  15. package/dist/llm/executor/turn-executor.d.ts +1 -0
  16. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  17. package/dist/llm/executor/turn-executor.js +10 -0
  18. package/dist/llm/formatters/vercel.cjs +9 -1
  19. package/dist/llm/formatters/vercel.d.ts.map +1 -1
  20. package/dist/llm/formatters/vercel.js +9 -1
  21. package/dist/llm/registry.cjs +69 -0
  22. package/dist/llm/registry.d.ts +9 -0
  23. package/dist/llm/registry.d.ts.map +1 -1
  24. package/dist/llm/registry.js +68 -0
  25. package/dist/llm/schemas.cjs +17 -1
  26. package/dist/llm/schemas.d.ts +23 -0
  27. package/dist/llm/schemas.d.ts.map +1 -1
  28. package/dist/llm/schemas.js +17 -1
  29. package/dist/llm/services/vercel.cjs +3 -1
  30. package/dist/llm/services/vercel.d.ts.map +1 -1
  31. package/dist/llm/services/vercel.js +3 -1
  32. package/dist/logger/logger.cjs +7 -3
  33. package/dist/logger/logger.d.ts.map +1 -1
  34. package/dist/logger/logger.js +7 -3
  35. package/dist/memory/schemas.d.ts +2 -2
  36. package/dist/providers/discovery.cjs +14 -0
  37. package/dist/providers/discovery.d.ts +4 -2
  38. package/dist/providers/discovery.d.ts.map +1 -1
  39. package/dist/providers/discovery.js +14 -0
  40. package/dist/session/history/database.cjs +49 -15
  41. package/dist/session/history/database.d.ts.map +1 -1
  42. package/dist/session/history/database.js +49 -15
  43. package/dist/session/session-manager.cjs +2 -1
  44. package/dist/session/session-manager.d.ts.map +1 -1
  45. package/dist/session/session-manager.js +2 -1
  46. package/dist/storage/database/postgres-store.cjs +174 -78
  47. package/dist/storage/database/postgres-store.d.ts +19 -0
  48. package/dist/storage/database/postgres-store.d.ts.map +1 -1
  49. package/dist/storage/database/postgres-store.js +174 -78
  50. package/dist/storage/database/schemas.cjs +4 -1
  51. package/dist/storage/database/schemas.d.ts +8 -0
  52. package/dist/storage/database/schemas.d.ts.map +1 -1
  53. package/dist/storage/database/schemas.js +4 -1
  54. package/dist/storage/schemas.d.ts +7 -0
  55. package/dist/storage/schemas.d.ts.map +1 -1
  56. package/dist/tools/custom-tool-registry.d.ts +9 -3
  57. package/dist/tools/custom-tool-registry.d.ts.map +1 -1
  58. package/dist/tools/internal-tools/provider.cjs +5 -2
  59. package/dist/tools/internal-tools/provider.d.ts.map +1 -1
  60. package/dist/tools/internal-tools/provider.js +5 -2
  61. package/package.json +1 -1
@@ -20,10 +20,37 @@ class DatabaseHistoryProvider {
20
20
  if (this.cache === null) {
21
21
  const key = this.getMessagesKey();
22
22
  try {
23
- this.cache = await this.database.getRange(key, 0, 1e4);
24
- this.logger.debug(
25
- `DatabaseHistoryProvider: Loaded ${this.cache.length} messages from DB for session ${this.sessionId}`
26
- );
23
+ const limit = 1e4;
24
+ const rawMessages = await this.database.getRange(key, 0, limit);
25
+ if (rawMessages.length === limit) {
26
+ this.logger.warn(
27
+ `DatabaseHistoryProvider: Session ${this.sessionId} hit message limit (${limit}), history may be truncated`
28
+ );
29
+ }
30
+ const seen = /* @__PURE__ */ new Set();
31
+ this.cache = [];
32
+ let duplicateCount = 0;
33
+ for (const msg of rawMessages) {
34
+ if (msg.id && seen.has(msg.id)) {
35
+ duplicateCount++;
36
+ continue;
37
+ }
38
+ if (msg.id) {
39
+ seen.add(msg.id);
40
+ }
41
+ this.cache.push(msg);
42
+ }
43
+ if (duplicateCount > 0) {
44
+ this.logger.warn(
45
+ `DatabaseHistoryProvider: Found ${duplicateCount} duplicate messages for session ${this.sessionId}, deduped to ${this.cache.length}`
46
+ );
47
+ this.dirty = true;
48
+ this.scheduleFlush();
49
+ } else {
50
+ this.logger.debug(
51
+ `DatabaseHistoryProvider: Loaded ${this.cache.length} messages for session ${this.sessionId}`
52
+ );
53
+ }
27
54
  } catch (error) {
28
55
  this.logger.error(
29
56
  `DatabaseHistoryProvider: Error loading messages for session ${this.sessionId}: ${error instanceof Error ? error.message : String(error)}`
@@ -42,15 +69,17 @@ class DatabaseHistoryProvider {
42
69
  if (this.cache === null) {
43
70
  await this.getHistory();
44
71
  }
72
+ if (message.id && this.cache.some((m) => m.id === message.id)) {
73
+ this.logger.debug(
74
+ `DatabaseHistoryProvider: Message ${message.id} already exists, skipping`
75
+ );
76
+ return;
77
+ }
45
78
  this.cache.push(message);
46
79
  try {
47
80
  await this.database.append(key, message);
48
81
  this.logger.debug(
49
- `DatabaseHistoryProvider: Saved message for session ${this.sessionId}`,
50
- {
51
- role: message.role,
52
- id: message.id
53
- }
82
+ `DatabaseHistoryProvider: Saved message ${message.id} (${message.role}) for session ${this.sessionId}`
54
83
  );
55
84
  } catch (error) {
56
85
  this.cache.pop();
@@ -137,18 +166,23 @@ class DatabaseHistoryProvider {
137
166
  return;
138
167
  }
139
168
  const key = this.getMessagesKey();
140
- const messageCount = this.cache.length;
169
+ const snapshot = [...this.cache];
170
+ const messageCount = snapshot.length;
171
+ this.logger.debug(
172
+ `DatabaseHistoryProvider: FLUSH START key=${key} snapshotSize=${messageCount} ids=[${snapshot.map((m) => m.id).join(",")}]`
173
+ );
141
174
  try {
142
175
  await this.database.delete(key);
143
- for (const msg of this.cache) {
176
+ this.logger.debug(`DatabaseHistoryProvider: FLUSH DELETED key=${key}`);
177
+ for (const msg of snapshot) {
144
178
  await this.database.append(key, msg);
145
179
  }
180
+ this.logger.debug(
181
+ `DatabaseHistoryProvider: FLUSH REAPPENDED key=${key} count=${messageCount}`
182
+ );
146
183
  if (!this.flushTimer) {
147
184
  this.dirty = false;
148
185
  }
149
- this.logger.debug(
150
- `DatabaseHistoryProvider: Flushed ${messageCount} messages to DB for session ${this.sessionId}`
151
- );
152
186
  } catch (error) {
153
187
  this.logger.error(
154
188
  `DatabaseHistoryProvider: Error flushing messages for session ${this.sessionId}: ${error instanceof Error ? error.message : String(error)}`
@@ -170,7 +204,7 @@ class DatabaseHistoryProvider {
170
204
  }
171
205
  this.flushTimer = setTimeout(() => {
172
206
  this.flushTimer = null;
173
- this.doFlush().catch(() => {
207
+ this.flush().catch(() => {
174
208
  });
175
209
  }, DatabaseHistoryProvider.FLUSH_DELAY_MS);
176
210
  }
@@ -245,13 +245,14 @@ class SessionManager {
245
245
  await this.ensureInitialized();
246
246
  const session = await this.getSession(sessionId);
247
247
  if (session) {
248
- await session.reset();
249
248
  await session.cleanup();
250
249
  this.sessions.delete(sessionId);
251
250
  }
252
251
  const sessionKey = `session:${sessionId}`;
253
252
  await this.services.storageManager.getDatabase().delete(sessionKey);
254
253
  await this.services.storageManager.getCache().delete(sessionKey);
254
+ const messagesKey = `messages:${sessionId}`;
255
+ await this.services.storageManager.getDatabase().delete(messagesKey);
255
256
  this.logger.debug(`Deleted session and conversation history: ${sessionId}`);
256
257
  }
257
258
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/session/session-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/session/session-manager.ts"],"names":[],"mappings":"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"}
@@ -223,13 +223,14 @@ class SessionManager {
223
223
  await this.ensureInitialized();
224
224
  const session = await this.getSession(sessionId);
225
225
  if (session) {
226
- await session.reset();
227
226
  await session.cleanup();
228
227
  this.sessions.delete(sessionId);
229
228
  }
230
229
  const sessionKey = `session:${sessionId}`;
231
230
  await this.services.storageManager.getDatabase().delete(sessionKey);
232
231
  await this.services.storageManager.getCache().delete(sessionKey);
232
+ const messagesKey = `messages:${sessionId}`;
233
+ await this.services.storageManager.getDatabase().delete(messagesKey);
233
234
  this.logger.debug(`Deleted session and conversation history: ${sessionId}`);
234
235
  }
235
236
  /**
@@ -34,20 +34,89 @@ class PostgresStore {
34
34
  logger;
35
35
  async connect() {
36
36
  if (this.connected) return;
37
+ const connectionString = this.config.connectionString || this.config.url;
38
+ if (connectionString?.startsWith("$")) {
39
+ throw import_errors.StorageError.connectionFailed(
40
+ `PostgreSQL: Connection string contains unexpanded environment variable: ${connectionString}. Ensure the environment variable is set in your .env file.`
41
+ );
42
+ }
43
+ if (!connectionString) {
44
+ throw import_errors.StorageError.connectionFailed(
45
+ "PostgreSQL: No connection string provided. Set url or connectionString in database config."
46
+ );
47
+ }
48
+ const { schema, ...pgOptions } = this.config.options || {};
49
+ this.logger.info("Connecting to PostgreSQL database...");
37
50
  this.pool = new import_pg.Pool({
38
- connectionString: this.config.connectionString || this.config.url,
51
+ connectionString,
39
52
  max: this.config.maxConnections || 20,
40
- idleTimeoutMillis: this.config.idleTimeoutMillis || 3e4,
41
- connectionTimeoutMillis: this.config.connectionTimeoutMillis || 2e3,
42
- ...this.config.options
53
+ // Shorter idle timeout for serverless DBs (Neon) - connections go stale quickly
54
+ idleTimeoutMillis: this.config.idleTimeoutMillis || 1e4,
55
+ connectionTimeoutMillis: this.config.connectionTimeoutMillis || 1e4,
56
+ // Enable TCP keepalive to detect dead connections
57
+ keepAlive: true,
58
+ keepAliveInitialDelayMillis: 1e4,
59
+ ...pgOptions
43
60
  });
44
- const client = await this.pool.connect();
61
+ this.pool.on("error", (err) => {
62
+ this.logger.warn(`PostgreSQL pool error (will retry on next query): ${err.message}`);
63
+ });
64
+ if (schema) {
65
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schema)) {
66
+ throw import_errors.StorageError.connectionFailed(
67
+ `PostgreSQL: Invalid schema name "${schema}". Schema names must start with a letter or underscore and contain only alphanumeric characters and underscores.`
68
+ );
69
+ }
70
+ this.pool.on("connect", async (client2) => {
71
+ try {
72
+ await client2.query(`SET search_path TO "${schema}", public`);
73
+ } catch (err) {
74
+ this.logger.error(`Failed to set search_path to "${schema}": ${err}`);
75
+ }
76
+ });
77
+ this.logger.info(`Using custom schema: "${schema}"`);
78
+ }
79
+ let client;
45
80
  try {
81
+ client = await this.pool.connect();
46
82
  await client.query("SELECT NOW()");
83
+ if (schema) {
84
+ await this.createSchema(client, schema);
85
+ }
47
86
  await this.createTables(client);
48
87
  this.connected = true;
88
+ this.logger.info("PostgreSQL database connected successfully");
89
+ } catch (error) {
90
+ const errorMessage = error instanceof Error ? error.message : String(error);
91
+ this.logger.error(`PostgreSQL connection failed: ${errorMessage}`);
92
+ if (this.pool) {
93
+ await this.pool.end().catch(() => {
94
+ });
95
+ this.pool = null;
96
+ }
97
+ throw import_errors.StorageError.connectionFailed(`PostgreSQL: ${errorMessage}`);
49
98
  } finally {
50
- client.release();
99
+ if (client) {
100
+ client.release();
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Creates a PostgreSQL schema if it doesn't exist.
106
+ */
107
+ async createSchema(client, schemaName) {
108
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schemaName)) {
109
+ throw import_errors.StorageError.connectionFailed(
110
+ `PostgreSQL: Invalid schema name "${schemaName}". Schema names must start with a letter or underscore and contain only alphanumeric characters and underscores.`
111
+ );
112
+ }
113
+ try {
114
+ await client.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
115
+ this.logger.debug(`Schema "${schemaName}" ready`);
116
+ } catch (error) {
117
+ this.logger.warn(
118
+ `Could not create schema "${schemaName}": ${error}. Assuming it exists.`
119
+ );
51
120
  }
52
121
  }
53
122
  async disconnect() {
@@ -63,61 +132,54 @@ class PostgresStore {
63
132
  getStoreType() {
64
133
  return "postgres";
65
134
  }
66
- // Core operations
135
+ // Core operations - all use withRetry for serverless DB resilience
67
136
  async get(key) {
68
- this.checkConnection();
69
- const client = await this.pool.connect();
70
137
  try {
71
- const result = await client.query("SELECT value FROM kv WHERE key = $1", [key]);
72
- return result.rows[0] ? result.rows[0].value : void 0;
138
+ return await this.withRetry("get", async (client) => {
139
+ const result = await client.query("SELECT value FROM kv WHERE key = $1", [key]);
140
+ return result.rows[0] ? result.rows[0].value : void 0;
141
+ });
73
142
  } catch (error) {
74
143
  throw import_errors.StorageError.readFailed(
75
144
  "get",
76
145
  error instanceof Error ? error.message : String(error),
77
146
  { key }
78
147
  );
79
- } finally {
80
- client.release();
81
148
  }
82
149
  }
83
150
  async set(key, value) {
84
- this.checkConnection();
85
- const client = await this.pool.connect();
86
151
  try {
87
- await client.query(
88
- "INSERT INTO kv (key, value, updated_at) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = $3",
89
- [key, value, /* @__PURE__ */ new Date()]
90
- );
152
+ await this.withRetry("set", async (client) => {
153
+ const jsonValue = JSON.stringify(value);
154
+ await client.query(
155
+ "INSERT INTO kv (key, value, updated_at) VALUES ($1, $2::jsonb, $3) ON CONFLICT (key) DO UPDATE SET value = $2::jsonb, updated_at = $3",
156
+ [key, jsonValue, /* @__PURE__ */ new Date()]
157
+ );
158
+ });
91
159
  } catch (error) {
92
160
  throw import_errors.StorageError.writeFailed(
93
161
  "set",
94
162
  error instanceof Error ? error.message : String(error),
95
163
  { key }
96
164
  );
97
- } finally {
98
- client.release();
99
165
  }
100
166
  }
101
167
  async delete(key) {
102
- this.checkConnection();
103
- const client = await this.pool.connect();
104
- try {
168
+ await this.withRetry("delete", async (client) => {
105
169
  await client.query("BEGIN");
106
- await client.query("DELETE FROM kv WHERE key = $1", [key]);
107
- await client.query("DELETE FROM lists WHERE key = $1", [key]);
108
- await client.query("COMMIT");
109
- } catch (error) {
110
- await client.query("ROLLBACK");
111
- throw error;
112
- } finally {
113
- client.release();
114
- }
170
+ try {
171
+ await client.query("DELETE FROM kv WHERE key = $1", [key]);
172
+ await client.query("DELETE FROM lists WHERE key = $1", [key]);
173
+ await client.query("COMMIT");
174
+ } catch (error) {
175
+ await client.query("ROLLBACK");
176
+ throw error;
177
+ }
178
+ });
115
179
  }
116
180
  // List operations
117
181
  async list(prefix) {
118
- this.checkConnection();
119
- const client = await this.pool.connect();
120
- try {
182
+ return await this.withRetry("list", async (client) => {
121
183
  const kvResult = await client.query("SELECT key FROM kv WHERE key LIKE $1", [
122
184
  `${prefix}%`
123
185
  ]);
@@ -130,41 +192,33 @@ class PostgresStore {
130
192
  ...listResult.rows.map((row) => row.key)
131
193
  ]);
132
194
  return Array.from(allKeys).sort();
133
- } finally {
134
- client.release();
135
- }
195
+ });
136
196
  }
137
197
  async append(key, item) {
138
- this.checkConnection();
139
- const client = await this.pool.connect();
140
198
  try {
141
- await client.query("INSERT INTO lists (key, item, created_at) VALUES ($1, $2, $3)", [
142
- key,
143
- item,
144
- /* @__PURE__ */ new Date()
145
- ]);
199
+ await this.withRetry("append", async (client) => {
200
+ const jsonItem = JSON.stringify(item);
201
+ await client.query(
202
+ "INSERT INTO lists (key, item, created_at) VALUES ($1, $2::jsonb, $3)",
203
+ [key, jsonItem, /* @__PURE__ */ new Date()]
204
+ );
205
+ });
146
206
  } catch (error) {
147
207
  throw import_errors.StorageError.writeFailed(
148
208
  "append",
149
209
  error instanceof Error ? error.message : String(error),
150
210
  { key }
151
211
  );
152
- } finally {
153
- client.release();
154
212
  }
155
213
  }
156
214
  async getRange(key, start, count) {
157
- this.checkConnection();
158
- const client = await this.pool.connect();
159
- try {
215
+ return await this.withRetry("getRange", async (client) => {
160
216
  const result = await client.query(
161
217
  "SELECT item FROM lists WHERE key = $1 ORDER BY created_at ASC LIMIT $2 OFFSET $3",
162
218
  [key, count, start]
163
219
  );
164
220
  return result.rows.map((row) => row.item);
165
- } finally {
166
- client.release();
167
- }
221
+ });
168
222
  }
169
223
  // Schema management
170
224
  async createTables(client) {
@@ -194,26 +248,74 @@ class PostgresStore {
194
248
  throw import_errors.StorageError.notConnected("PostgresStore");
195
249
  }
196
250
  }
251
+ /**
252
+ * Check if an error is a connection error that should trigger a retry.
253
+ * Common with serverless databases (Neon) where connections go stale.
254
+ */
255
+ isConnectionError(error) {
256
+ if (!(error instanceof Error)) return false;
257
+ const code = error.code;
258
+ return code === "ETIMEDOUT" || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "57P01" || // admin_shutdown
259
+ code === "57P02" || // crash_shutdown
260
+ code === "57P03" || // cannot_connect_now
261
+ error.message.includes("Connection terminated") || error.message.includes("connection lost");
262
+ }
263
+ /**
264
+ * Execute a database operation with automatic retry on connection errors.
265
+ * Handles serverless DB connection issues (Neon cold starts, stale connections).
266
+ */
267
+ async withRetry(operation, fn, maxRetries = 2) {
268
+ this.checkConnection();
269
+ let lastError;
270
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
271
+ let client;
272
+ try {
273
+ client = await this.pool.connect();
274
+ const result = await fn(client);
275
+ return result;
276
+ } catch (error) {
277
+ lastError = error instanceof Error ? error : new Error(String(error));
278
+ if (client) {
279
+ client.release(true);
280
+ client = void 0;
281
+ }
282
+ if (this.isConnectionError(error) && attempt < maxRetries) {
283
+ this.logger.warn(
284
+ `PostgreSQL ${operation} failed with connection error (attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message}. Retrying...`
285
+ );
286
+ await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
287
+ continue;
288
+ }
289
+ throw error;
290
+ } finally {
291
+ if (client) {
292
+ client.release();
293
+ }
294
+ }
295
+ }
296
+ throw lastError;
297
+ }
197
298
  // Advanced operations
299
+ /**
300
+ * Execute a callback within a database transaction.
301
+ * Note: On connection failure, the entire callback will be retried on a new connection.
302
+ * Ensure callback operations are idempotent or use this only for read operations.
303
+ */
198
304
  async transaction(callback) {
199
- this.checkConnection();
200
- const client = await this.pool.connect();
201
- try {
305
+ return await this.withRetry("transaction", async (client) => {
202
306
  await client.query("BEGIN");
203
- const result = await callback(client);
204
- await client.query("COMMIT");
205
- return result;
206
- } catch (error) {
207
- await client.query("ROLLBACK");
208
- throw error;
209
- } finally {
210
- client.release();
211
- }
307
+ try {
308
+ const result = await callback(client);
309
+ await client.query("COMMIT");
310
+ return result;
311
+ } catch (error) {
312
+ await client.query("ROLLBACK");
313
+ throw error;
314
+ }
315
+ });
212
316
  }
213
317
  async getStats() {
214
- this.checkConnection();
215
- const client = await this.pool.connect();
216
- try {
318
+ return await this.withRetry("getStats", async (client) => {
217
319
  const kvResult = await client.query("SELECT COUNT(*) as count FROM kv");
218
320
  const listResult = await client.query("SELECT COUNT(*) as count FROM lists");
219
321
  const sizeResult = await client.query(
@@ -225,19 +327,13 @@ class PostgresStore {
225
327
  listCount: parseInt(listResult.rows[0].count),
226
328
  totalSize: sizeResult.rows[0].size
227
329
  };
228
- } finally {
229
- client.release();
230
- }
330
+ });
231
331
  }
232
332
  // Maintenance operations
233
333
  async vacuum() {
234
- this.checkConnection();
235
- const client = await this.pool.connect();
236
- try {
334
+ await this.withRetry("vacuum", async (client) => {
237
335
  await client.query("VACUUM ANALYZE kv, lists");
238
- } finally {
239
- client.release();
240
- }
336
+ });
241
337
  }
242
338
  }
243
339
  // Annotate the CommonJS export names for ESM import in node:
@@ -14,6 +14,10 @@ export declare class PostgresStore implements Database {
14
14
  private logger;
15
15
  constructor(config: PostgresDatabaseConfig, logger: IDextoLogger);
16
16
  connect(): Promise<void>;
17
+ /**
18
+ * Creates a PostgreSQL schema if it doesn't exist.
19
+ */
20
+ private createSchema;
17
21
  disconnect(): Promise<void>;
18
22
  isConnected(): boolean;
19
23
  getStoreType(): string;
@@ -25,6 +29,21 @@ export declare class PostgresStore implements Database {
25
29
  getRange<T>(key: string, start: number, count: number): Promise<T[]>;
26
30
  private createTables;
27
31
  private checkConnection;
32
+ /**
33
+ * Check if an error is a connection error that should trigger a retry.
34
+ * Common with serverless databases (Neon) where connections go stale.
35
+ */
36
+ private isConnectionError;
37
+ /**
38
+ * Execute a database operation with automatic retry on connection errors.
39
+ * Handles serverless DB connection issues (Neon cold starts, stale connections).
40
+ */
41
+ private withRetry;
42
+ /**
43
+ * Execute a callback within a database transaction.
44
+ * Note: On connection failure, the entire callback will be retried on a new connection.
45
+ * Ensure callback operations are idempotent or use this only for read operations.
46
+ */
28
47
  transaction<T>(callback: (client: PoolClient) => Promise<T>): Promise<T>;
29
48
  getStats(): Promise<{
30
49
  kvCount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../../src/storage/database/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,IAAI,CAAC;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D;;;;GAIG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAMtC,OAAO,CAAC,MAAM;IALlB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAe;gBAGjB,MAAM,EAAE,sBAAsB,EACtC,MAAM,EAAE,YAAY;IAKlB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,MAAM;IAKhB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAiB3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBlC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBvC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9C,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;YAe5D,YAAY;IA4B1B,OAAO,CAAC,eAAe;IAOjB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgBxE,QAAQ,IAAI,OAAO,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;IAsBI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAShC"}
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../../src/storage/database/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,IAAI,CAAC;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D;;;;GAIG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAMtC,OAAO,CAAC,MAAM;IALlB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAe;gBAGjB,MAAM,EAAE,sBAAsB,EACtC,MAAM,EAAE,YAAY;IAKlB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+F9B;;OAEG;YACW,YAAY;IAmBpB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,MAAM;IAKhB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAe3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAelC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBvC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB9C,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;YAW5D,YAAY;IA4B1B,OAAO,CAAC,eAAe;IAMvB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;;OAGG;YACW,SAAS;IA8CvB;;;;OAIG;IACG,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAcxE,QAAQ,IAAI,OAAO,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;IAkBI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAKhC"}