@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.
- package/dist/agent/schemas.d.ts +48 -0
- package/dist/agent/schemas.d.ts.map +1 -1
- package/dist/events/index.cjs +4 -1
- package/dist/events/index.d.ts +20 -4
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -1
- package/dist/llm/executor/provider-options.cjs +87 -0
- package/dist/llm/executor/provider-options.d.ts +49 -0
- package/dist/llm/executor/provider-options.d.ts.map +1 -0
- package/dist/llm/executor/provider-options.js +63 -0
- package/dist/llm/executor/stream-processor.cjs +11 -8
- package/dist/llm/executor/stream-processor.d.ts.map +1 -1
- package/dist/llm/executor/stream-processor.js +11 -8
- package/dist/llm/executor/turn-executor.cjs +10 -0
- package/dist/llm/executor/turn-executor.d.ts +1 -0
- package/dist/llm/executor/turn-executor.d.ts.map +1 -1
- package/dist/llm/executor/turn-executor.js +10 -0
- package/dist/llm/formatters/vercel.cjs +9 -1
- package/dist/llm/formatters/vercel.d.ts.map +1 -1
- package/dist/llm/formatters/vercel.js +9 -1
- package/dist/llm/registry.cjs +69 -0
- package/dist/llm/registry.d.ts +9 -0
- package/dist/llm/registry.d.ts.map +1 -1
- package/dist/llm/registry.js +68 -0
- package/dist/llm/schemas.cjs +17 -1
- package/dist/llm/schemas.d.ts +23 -0
- package/dist/llm/schemas.d.ts.map +1 -1
- package/dist/llm/schemas.js +17 -1
- package/dist/llm/services/vercel.cjs +3 -1
- package/dist/llm/services/vercel.d.ts.map +1 -1
- package/dist/llm/services/vercel.js +3 -1
- package/dist/logger/logger.cjs +7 -3
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +7 -3
- package/dist/memory/schemas.d.ts +2 -2
- package/dist/providers/discovery.cjs +14 -0
- package/dist/providers/discovery.d.ts +4 -2
- package/dist/providers/discovery.d.ts.map +1 -1
- package/dist/providers/discovery.js +14 -0
- package/dist/session/history/database.cjs +49 -15
- package/dist/session/history/database.d.ts.map +1 -1
- package/dist/session/history/database.js +49 -15
- package/dist/session/session-manager.cjs +2 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +2 -1
- package/dist/storage/database/postgres-store.cjs +174 -78
- package/dist/storage/database/postgres-store.d.ts +19 -0
- package/dist/storage/database/postgres-store.d.ts.map +1 -1
- package/dist/storage/database/postgres-store.js +174 -78
- package/dist/storage/database/schemas.cjs +4 -1
- package/dist/storage/database/schemas.d.ts +8 -0
- package/dist/storage/database/schemas.d.ts.map +1 -1
- package/dist/storage/database/schemas.js +4 -1
- package/dist/storage/schemas.d.ts +7 -0
- package/dist/storage/schemas.d.ts.map +1 -1
- package/dist/tools/custom-tool-registry.d.ts +9 -3
- package/dist/tools/custom-tool-registry.d.ts.map +1 -1
- package/dist/tools/internal-tools/provider.cjs +5 -2
- package/dist/tools/internal-tools/provider.d.ts.map +1 -1
- package/dist/tools/internal-tools/provider.js +5 -2
- 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
|
-
|
|
24
|
-
this.
|
|
25
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AAErD,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAc;IAcnB,OAAO,CAAC,QAAQ;IAbpB,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,qBAAqB,CAAiB;IAE9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA2C;IAE5E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IACpE,OAAO,CAAC,MAAM,CAAe;gBAGjB,QAAQ,EAAE;QACd,YAAY,EAAE,iBAAiB,CAAC;QAChC,mBAAmB,EAAE,mBAAmB,CAAC;QACzC,WAAW,EAAE,WAAW,CAAC;QACzB,aAAa,EAAE,aAAa,CAAC;QAC7B,cAAc,EAAE,cAAc,CAAC;QAC/B,eAAe,EAAE,OAAO,uBAAuB,EAAE,eAAe,CAAC;QACjE,aAAa,EAAE,aAAa,CAAC;QAC7B,UAAU,EAAE,OAAO,mBAAmB,EAAE,UAAU,CAAC;KACtD,EACD,MAAM,EAAE,oBAAoB,YAAK,EACjC,MAAM,EAAE,YAAY;IAOxB;;;OAGG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBlC;;;OAGG;YACW,0BAA0B;IAmCxC;;OAEG;YACW,iBAAiB;IAS/B;;;;;;OAMG;IACU,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA6BpE;;;OAGG;YACW,qBAAqB;IA4EnC;;;;;;OAMG;IACU,UAAU,CACnB,SAAS,EAAE,MAAM,EACjB,kBAAkB,GAAE,OAAc,GACnC,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAmCnC;;;;;OAKG;IACU,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBzD;;;;;OAKG;IACU,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
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
|
|
51
|
+
connectionString,
|
|
39
52
|
max: this.config.maxConnections || 20,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
103
|
-
const client = await this.pool.connect();
|
|
104
|
-
try {
|
|
168
|
+
await this.withRetry("delete", async (client) => {
|
|
105
169
|
await client.query("BEGIN");
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
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
|
-
}
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
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
|
-
}
|
|
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.
|
|
200
|
-
const client = await this.pool.connect();
|
|
201
|
-
try {
|
|
305
|
+
return await this.withRetry("transaction", async (client) => {
|
|
202
306
|
await client.query("BEGIN");
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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.
|
|
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
|
-
}
|
|
229
|
-
client.release();
|
|
230
|
-
}
|
|
330
|
+
});
|
|
231
331
|
}
|
|
232
332
|
// Maintenance operations
|
|
233
333
|
async vacuum() {
|
|
234
|
-
this.
|
|
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
|
-
}
|
|
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;
|
|
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"}
|