@hasna/mementos 0.14.47 → 0.14.49
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/Dockerfile.package +32 -0
- package/bun.lock +405 -0
- package/dist/cli/index.js +141 -70
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/health.d.ts +20 -0
- package/dist/generated/storage-kit/health.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +8 -0
- package/dist/generated/storage-kit/index.d.ts.map +1 -0
- package/dist/generated/storage-kit/migrations.d.ts +48 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -0
- package/dist/generated/storage-kit/mode.d.ts +48 -0
- package/dist/generated/storage-kit/mode.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +34 -0
- package/dist/generated/storage-kit/pool.d.ts.map +1 -0
- package/dist/generated/storage-kit/query.d.ts +36 -0
- package/dist/generated/storage-kit/query.d.ts.map +1 -0
- package/dist/generated/storage-kit/tls.d.ts +26 -0
- package/dist/generated/storage-kit/tls.d.ts.map +1 -0
- package/dist/index.js +96 -64
- package/dist/mcp/index.js +135 -64
- package/dist/pg-sync-worker.d.ts +2 -0
- package/dist/pg-sync-worker.d.ts.map +1 -0
- package/dist/pg-sync-worker.js +47 -0
- package/dist/sdk/index.d.ts +27 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +25 -6
- package/dist/server/auth.d.ts +11 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +362 -129
- package/dist/server/openapi.d.ts +2 -0
- package/dist/server/openapi.d.ts.map +1 -0
- package/dist/storage.d.ts +36 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +99 -64
- package/docker-entrypoint.sh +46 -0
- package/hasna.contract.json +16 -0
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -51,6 +51,8 @@ import { Database } from "bun:sqlite";
|
|
|
51
51
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
52
52
|
import { homedir } from "os";
|
|
53
53
|
import { join } from "path";
|
|
54
|
+
import { fileURLToPath } from "url";
|
|
55
|
+
import { Worker } from "worker_threads";
|
|
54
56
|
import pg from "pg";
|
|
55
57
|
function normalizeParams(params) {
|
|
56
58
|
const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
|
|
@@ -113,13 +115,15 @@ class SqliteAdapter {
|
|
|
113
115
|
function translateSql(sql) {
|
|
114
116
|
let parameterIndex = 0;
|
|
115
117
|
let translated = sql.replace(/\?/g, () => `$${++parameterIndex}`);
|
|
116
|
-
|
|
118
|
+
const ISO_FMT = `'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'`;
|
|
119
|
+
translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
|
|
117
120
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*,\s*'(-?\d+)\s+(minutes?|hours?|days?|seconds?)'\s*\)/gi, (_match, amount, unit) => {
|
|
118
121
|
const parsed = parseInt(String(amount), 10);
|
|
119
122
|
const absolute = Math.abs(parsed);
|
|
120
123
|
const normalizedUnit = String(unit).toLowerCase().replace(/s$/, "");
|
|
121
124
|
const pluralUnit = absolute === 1 ? normalizedUnit : `${normalizedUnit}s`;
|
|
122
|
-
|
|
125
|
+
const op = parsed < 0 ? "-" : "+";
|
|
126
|
+
return `to_char((now() ${op} INTERVAL '${absolute} ${pluralUnit}') AT TIME ZONE 'UTC', ${ISO_FMT})`;
|
|
123
127
|
});
|
|
124
128
|
translated = translated.replace(/lower\s*\(\s*hex\s*\(\s*randomblob\s*\(\s*\d+\s*\)\s*\)\s*\)/gi, "gen_random_uuid()::text");
|
|
125
129
|
translated = translated.replace(/\bIFNULL\s*\(/gi, "COALESCE(");
|
|
@@ -176,56 +180,24 @@ function makePool(connectionString) {
|
|
|
176
180
|
class PgAdapter {
|
|
177
181
|
pool;
|
|
178
182
|
constructor(input) {
|
|
179
|
-
this.pool = typeof input === "string" ?
|
|
180
|
-
}
|
|
181
|
-
runSync(fn) {
|
|
182
|
-
let result;
|
|
183
|
-
let error;
|
|
184
|
-
let done = false;
|
|
185
|
-
fn().then((value) => {
|
|
186
|
-
result = value;
|
|
187
|
-
done = true;
|
|
188
|
-
}).catch((caught) => {
|
|
189
|
-
error = caught;
|
|
190
|
-
done = true;
|
|
191
|
-
});
|
|
192
|
-
const deadline = Date.now() + 30000;
|
|
193
|
-
while (!done && Date.now() < deadline) {
|
|
194
|
-
Bun.sleepSync(1);
|
|
195
|
-
}
|
|
196
|
-
if (error) {
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
if (!done) {
|
|
200
|
-
throw new Error("PostgreSQL query timed out after 30s");
|
|
201
|
-
}
|
|
202
|
-
return result;
|
|
183
|
+
this.pool = typeof input === "string" ? new PgSyncPool(input) : input;
|
|
203
184
|
}
|
|
204
185
|
run(sql, ...params) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
};
|
|
211
|
-
});
|
|
186
|
+
const result = this.pool.query(translateSql(sql), normalizeParams(params));
|
|
187
|
+
return {
|
|
188
|
+
changes: result.rowCount ?? 0,
|
|
189
|
+
lastInsertRowid: result.rows?.[0]?.id ?? 0
|
|
190
|
+
};
|
|
212
191
|
}
|
|
213
192
|
get(sql, ...params) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return result.rows[0] ?? null;
|
|
217
|
-
});
|
|
193
|
+
const result = this.pool.query(translateSql(sql), normalizeParams(params));
|
|
194
|
+
return result.rows[0] ?? null;
|
|
218
195
|
}
|
|
219
196
|
all(sql, ...params) {
|
|
220
|
-
return this.
|
|
221
|
-
const result = await this.pool.query(translateSql(sql), normalizeParams(params));
|
|
222
|
-
return result.rows;
|
|
223
|
-
});
|
|
197
|
+
return this.pool.query(translateSql(sql), normalizeParams(params)).rows;
|
|
224
198
|
}
|
|
225
199
|
exec(sql) {
|
|
226
|
-
this.
|
|
227
|
-
await this.pool.query(sql);
|
|
228
|
-
});
|
|
200
|
+
this.pool.query(sql, []);
|
|
229
201
|
}
|
|
230
202
|
prepare(sql) {
|
|
231
203
|
return {
|
|
@@ -239,28 +211,20 @@ class PgAdapter {
|
|
|
239
211
|
return this.prepare(sql);
|
|
240
212
|
}
|
|
241
213
|
close() {
|
|
242
|
-
this.
|
|
243
|
-
await this.pool.end();
|
|
244
|
-
});
|
|
214
|
+
this.pool.end();
|
|
245
215
|
}
|
|
246
216
|
transaction(fn) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
217
|
+
this.pool.query("BEGIN", []);
|
|
218
|
+
try {
|
|
219
|
+
const value = fn();
|
|
220
|
+
this.pool.query("COMMIT", []);
|
|
221
|
+
return value;
|
|
222
|
+
} catch (error) {
|
|
250
223
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return value;
|
|
256
|
-
} catch (error) {
|
|
257
|
-
await client.query("ROLLBACK");
|
|
258
|
-
throw error;
|
|
259
|
-
} finally {
|
|
260
|
-
this.pool.query = originalQuery;
|
|
261
|
-
client.release();
|
|
262
|
-
}
|
|
263
|
-
});
|
|
224
|
+
this.pool.query("ROLLBACK", []);
|
|
225
|
+
} catch {}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
264
228
|
}
|
|
265
229
|
get raw() {
|
|
266
230
|
return this.pool;
|
|
@@ -610,7 +574,7 @@ function resetAllSyncMeta(db) {
|
|
|
610
574
|
ensureSyncMetaTable(db);
|
|
611
575
|
db.run("DELETE FROM _sync_meta");
|
|
612
576
|
}
|
|
613
|
-
var MEMENTOS_STORAGE_TABLES, STORAGE_TABLES, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes, getMementosStorageStatus, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
|
|
577
|
+
var PgSyncPool, MEMENTOS_STORAGE_TABLES, STORAGE_TABLES, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes, getMementosStorageStatus, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
|
|
614
578
|
CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
615
579
|
table_name TEXT PRIMARY KEY,
|
|
616
580
|
last_synced_at TEXT,
|
|
@@ -618,6 +582,74 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
618
582
|
direction TEXT DEFAULT 'push'
|
|
619
583
|
)`;
|
|
620
584
|
var init_storage = __esm(() => {
|
|
585
|
+
PgSyncPool = class PgSyncPool {
|
|
586
|
+
worker;
|
|
587
|
+
status;
|
|
588
|
+
data;
|
|
589
|
+
closed = false;
|
|
590
|
+
lastError = null;
|
|
591
|
+
static DATA_BYTES = 128 * 1024 * 1024;
|
|
592
|
+
static QUERY_TIMEOUT_MS = 60000;
|
|
593
|
+
static resolveWorkerPath() {
|
|
594
|
+
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
595
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
596
|
+
const candidates = [
|
|
597
|
+
join(here, `pg-sync-worker${ext}`),
|
|
598
|
+
join(here, "..", `pg-sync-worker${ext}`),
|
|
599
|
+
join(here, "..", "..", `pg-sync-worker${ext}`)
|
|
600
|
+
];
|
|
601
|
+
for (const candidate of candidates) {
|
|
602
|
+
if (existsSync(candidate))
|
|
603
|
+
return candidate;
|
|
604
|
+
}
|
|
605
|
+
return candidates[0];
|
|
606
|
+
}
|
|
607
|
+
constructor(connectionString) {
|
|
608
|
+
const control = new SharedArrayBuffer(8);
|
|
609
|
+
const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
|
|
610
|
+
this.status = new Int32Array(control);
|
|
611
|
+
this.data = new Uint8Array(dataSab);
|
|
612
|
+
this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
|
|
613
|
+
workerData: {
|
|
614
|
+
dsn: stripSslParams(connectionString),
|
|
615
|
+
ssl: sslConfigFor(connectionString),
|
|
616
|
+
control,
|
|
617
|
+
data: dataSab
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
this.worker.unref();
|
|
621
|
+
this.worker.on("error", (err) => {
|
|
622
|
+
this.lastError = err;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
query(sql, params) {
|
|
626
|
+
if (this.closed)
|
|
627
|
+
throw new Error("PgSyncPool is closed");
|
|
628
|
+
if (this.lastError)
|
|
629
|
+
throw this.lastError;
|
|
630
|
+
Atomics.store(this.status, 0, 0);
|
|
631
|
+
this.worker.postMessage({ sql, params });
|
|
632
|
+
const waitResult = Atomics.wait(this.status, 0, 0, PgSyncPool.QUERY_TIMEOUT_MS);
|
|
633
|
+
const code = Atomics.load(this.status, 0);
|
|
634
|
+
if (code === 0 || waitResult === "timed-out") {
|
|
635
|
+
if (this.lastError)
|
|
636
|
+
throw this.lastError;
|
|
637
|
+
throw new Error("PostgreSQL query timed out after 60s");
|
|
638
|
+
}
|
|
639
|
+
const len = Atomics.load(this.status, 1);
|
|
640
|
+
const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
|
|
641
|
+
if (code === 2) {
|
|
642
|
+
throw new Error(payload.message ?? "PostgreSQL error");
|
|
643
|
+
}
|
|
644
|
+
return payload;
|
|
645
|
+
}
|
|
646
|
+
end() {
|
|
647
|
+
if (this.closed)
|
|
648
|
+
return;
|
|
649
|
+
this.closed = true;
|
|
650
|
+
this.worker.terminate();
|
|
651
|
+
}
|
|
652
|
+
};
|
|
621
653
|
MEMENTOS_STORAGE_TABLES = [
|
|
622
654
|
"projects",
|
|
623
655
|
"agents",
|
package/dist/mcp/index.js
CHANGED
|
@@ -139,6 +139,8 @@ import { Database } from "bun:sqlite";
|
|
|
139
139
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
140
140
|
import { homedir } from "os";
|
|
141
141
|
import { join } from "path";
|
|
142
|
+
import { fileURLToPath } from "url";
|
|
143
|
+
import { Worker } from "worker_threads";
|
|
142
144
|
import pg from "pg";
|
|
143
145
|
function normalizeParams(params) {
|
|
144
146
|
const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
|
|
@@ -201,13 +203,15 @@ class SqliteAdapter {
|
|
|
201
203
|
function translateSql(sql) {
|
|
202
204
|
let parameterIndex = 0;
|
|
203
205
|
let translated = sql.replace(/\?/g, () => `$${++parameterIndex}`);
|
|
204
|
-
|
|
206
|
+
const ISO_FMT = `'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'`;
|
|
207
|
+
translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
|
|
205
208
|
translated = translated.replace(/datetime\s*\(\s*'now'\s*,\s*'(-?\d+)\s+(minutes?|hours?|days?|seconds?)'\s*\)/gi, (_match, amount, unit) => {
|
|
206
209
|
const parsed = parseInt(String(amount), 10);
|
|
207
210
|
const absolute = Math.abs(parsed);
|
|
208
211
|
const normalizedUnit = String(unit).toLowerCase().replace(/s$/, "");
|
|
209
212
|
const pluralUnit = absolute === 1 ? normalizedUnit : `${normalizedUnit}s`;
|
|
210
|
-
|
|
213
|
+
const op = parsed < 0 ? "-" : "+";
|
|
214
|
+
return `to_char((now() ${op} INTERVAL '${absolute} ${pluralUnit}') AT TIME ZONE 'UTC', ${ISO_FMT})`;
|
|
211
215
|
});
|
|
212
216
|
translated = translated.replace(/lower\s*\(\s*hex\s*\(\s*randomblob\s*\(\s*\d+\s*\)\s*\)\s*\)/gi, "gen_random_uuid()::text");
|
|
213
217
|
translated = translated.replace(/\bIFNULL\s*\(/gi, "COALESCE(");
|
|
@@ -264,56 +268,24 @@ function makePool(connectionString) {
|
|
|
264
268
|
class PgAdapter {
|
|
265
269
|
pool;
|
|
266
270
|
constructor(input) {
|
|
267
|
-
this.pool = typeof input === "string" ?
|
|
268
|
-
}
|
|
269
|
-
runSync(fn) {
|
|
270
|
-
let result;
|
|
271
|
-
let error;
|
|
272
|
-
let done = false;
|
|
273
|
-
fn().then((value) => {
|
|
274
|
-
result = value;
|
|
275
|
-
done = true;
|
|
276
|
-
}).catch((caught) => {
|
|
277
|
-
error = caught;
|
|
278
|
-
done = true;
|
|
279
|
-
});
|
|
280
|
-
const deadline = Date.now() + 30000;
|
|
281
|
-
while (!done && Date.now() < deadline) {
|
|
282
|
-
Bun.sleepSync(1);
|
|
283
|
-
}
|
|
284
|
-
if (error) {
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
287
|
-
if (!done) {
|
|
288
|
-
throw new Error("PostgreSQL query timed out after 30s");
|
|
289
|
-
}
|
|
290
|
-
return result;
|
|
271
|
+
this.pool = typeof input === "string" ? new PgSyncPool(input) : input;
|
|
291
272
|
}
|
|
292
273
|
run(sql, ...params) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
};
|
|
299
|
-
});
|
|
274
|
+
const result = this.pool.query(translateSql(sql), normalizeParams(params));
|
|
275
|
+
return {
|
|
276
|
+
changes: result.rowCount ?? 0,
|
|
277
|
+
lastInsertRowid: result.rows?.[0]?.id ?? 0
|
|
278
|
+
};
|
|
300
279
|
}
|
|
301
280
|
get(sql, ...params) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return result.rows[0] ?? null;
|
|
305
|
-
});
|
|
281
|
+
const result = this.pool.query(translateSql(sql), normalizeParams(params));
|
|
282
|
+
return result.rows[0] ?? null;
|
|
306
283
|
}
|
|
307
284
|
all(sql, ...params) {
|
|
308
|
-
return this.
|
|
309
|
-
const result = await this.pool.query(translateSql(sql), normalizeParams(params));
|
|
310
|
-
return result.rows;
|
|
311
|
-
});
|
|
285
|
+
return this.pool.query(translateSql(sql), normalizeParams(params)).rows;
|
|
312
286
|
}
|
|
313
287
|
exec(sql) {
|
|
314
|
-
this.
|
|
315
|
-
await this.pool.query(sql);
|
|
316
|
-
});
|
|
288
|
+
this.pool.query(sql, []);
|
|
317
289
|
}
|
|
318
290
|
prepare(sql) {
|
|
319
291
|
return {
|
|
@@ -327,28 +299,20 @@ class PgAdapter {
|
|
|
327
299
|
return this.prepare(sql);
|
|
328
300
|
}
|
|
329
301
|
close() {
|
|
330
|
-
this.
|
|
331
|
-
await this.pool.end();
|
|
332
|
-
});
|
|
302
|
+
this.pool.end();
|
|
333
303
|
}
|
|
334
304
|
transaction(fn) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
305
|
+
this.pool.query("BEGIN", []);
|
|
306
|
+
try {
|
|
307
|
+
const value = fn();
|
|
308
|
+
this.pool.query("COMMIT", []);
|
|
309
|
+
return value;
|
|
310
|
+
} catch (error) {
|
|
338
311
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return value;
|
|
344
|
-
} catch (error) {
|
|
345
|
-
await client.query("ROLLBACK");
|
|
346
|
-
throw error;
|
|
347
|
-
} finally {
|
|
348
|
-
this.pool.query = originalQuery;
|
|
349
|
-
client.release();
|
|
350
|
-
}
|
|
351
|
-
});
|
|
312
|
+
this.pool.query("ROLLBACK", []);
|
|
313
|
+
} catch {}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
352
316
|
}
|
|
353
317
|
get raw() {
|
|
354
318
|
return this.pool;
|
|
@@ -630,7 +594,7 @@ function getSyncMetaAll(db) {
|
|
|
630
594
|
ensureSyncMetaTable(db);
|
|
631
595
|
return db.all("SELECT table_name, last_synced_at, last_synced_row_count, direction FROM _sync_meta ORDER BY table_name");
|
|
632
596
|
}
|
|
633
|
-
var MEMENTOS_STORAGE_TABLES, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
|
|
597
|
+
var PgSyncPool, MEMENTOS_STORAGE_TABLES, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
|
|
634
598
|
CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
635
599
|
table_name TEXT PRIMARY KEY,
|
|
636
600
|
last_synced_at TEXT,
|
|
@@ -638,6 +602,74 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
638
602
|
direction TEXT DEFAULT 'push'
|
|
639
603
|
)`;
|
|
640
604
|
var init_storage = __esm(() => {
|
|
605
|
+
PgSyncPool = class PgSyncPool {
|
|
606
|
+
worker;
|
|
607
|
+
status;
|
|
608
|
+
data;
|
|
609
|
+
closed = false;
|
|
610
|
+
lastError = null;
|
|
611
|
+
static DATA_BYTES = 128 * 1024 * 1024;
|
|
612
|
+
static QUERY_TIMEOUT_MS = 60000;
|
|
613
|
+
static resolveWorkerPath() {
|
|
614
|
+
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
615
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
616
|
+
const candidates = [
|
|
617
|
+
join(here, `pg-sync-worker${ext}`),
|
|
618
|
+
join(here, "..", `pg-sync-worker${ext}`),
|
|
619
|
+
join(here, "..", "..", `pg-sync-worker${ext}`)
|
|
620
|
+
];
|
|
621
|
+
for (const candidate of candidates) {
|
|
622
|
+
if (existsSync(candidate))
|
|
623
|
+
return candidate;
|
|
624
|
+
}
|
|
625
|
+
return candidates[0];
|
|
626
|
+
}
|
|
627
|
+
constructor(connectionString) {
|
|
628
|
+
const control = new SharedArrayBuffer(8);
|
|
629
|
+
const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
|
|
630
|
+
this.status = new Int32Array(control);
|
|
631
|
+
this.data = new Uint8Array(dataSab);
|
|
632
|
+
this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
|
|
633
|
+
workerData: {
|
|
634
|
+
dsn: stripSslParams(connectionString),
|
|
635
|
+
ssl: sslConfigFor(connectionString),
|
|
636
|
+
control,
|
|
637
|
+
data: dataSab
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
this.worker.unref();
|
|
641
|
+
this.worker.on("error", (err) => {
|
|
642
|
+
this.lastError = err;
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
query(sql, params) {
|
|
646
|
+
if (this.closed)
|
|
647
|
+
throw new Error("PgSyncPool is closed");
|
|
648
|
+
if (this.lastError)
|
|
649
|
+
throw this.lastError;
|
|
650
|
+
Atomics.store(this.status, 0, 0);
|
|
651
|
+
this.worker.postMessage({ sql, params });
|
|
652
|
+
const waitResult = Atomics.wait(this.status, 0, 0, PgSyncPool.QUERY_TIMEOUT_MS);
|
|
653
|
+
const code = Atomics.load(this.status, 0);
|
|
654
|
+
if (code === 0 || waitResult === "timed-out") {
|
|
655
|
+
if (this.lastError)
|
|
656
|
+
throw this.lastError;
|
|
657
|
+
throw new Error("PostgreSQL query timed out after 60s");
|
|
658
|
+
}
|
|
659
|
+
const len = Atomics.load(this.status, 1);
|
|
660
|
+
const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
|
|
661
|
+
if (code === 2) {
|
|
662
|
+
throw new Error(payload.message ?? "PostgreSQL error");
|
|
663
|
+
}
|
|
664
|
+
return payload;
|
|
665
|
+
}
|
|
666
|
+
end() {
|
|
667
|
+
if (this.closed)
|
|
668
|
+
return;
|
|
669
|
+
this.closed = true;
|
|
670
|
+
this.worker.terminate();
|
|
671
|
+
}
|
|
672
|
+
};
|
|
641
673
|
MEMENTOS_STORAGE_TABLES = [
|
|
642
674
|
"projects",
|
|
643
675
|
"agents",
|
|
@@ -11798,6 +11830,45 @@ var init_pg_migrations = __esm(() => {
|
|
|
11798
11830
|
machine_id TEXT,
|
|
11799
11831
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
11800
11832
|
);
|
|
11833
|
+
`,
|
|
11834
|
+
`
|
|
11835
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
11836
|
+
id TEXT PRIMARY KEY,
|
|
11837
|
+
subject TEXT NOT NULL,
|
|
11838
|
+
description TEXT DEFAULT '',
|
|
11839
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','in_progress','completed','failed','cancelled')),
|
|
11840
|
+
priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('critical','high','medium','low')),
|
|
11841
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
11842
|
+
assigned_agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
11843
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
11844
|
+
session_id TEXT,
|
|
11845
|
+
parent_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
11846
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
11847
|
+
progress REAL NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 1),
|
|
11848
|
+
due_at TEXT,
|
|
11849
|
+
started_at TEXT,
|
|
11850
|
+
completed_at TEXT,
|
|
11851
|
+
failed_at TEXT,
|
|
11852
|
+
error TEXT,
|
|
11853
|
+
created_at TEXT NOT NULL DEFAULT to_char((now() AT TIME ZONE 'UTC'), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
11854
|
+
updated_at TEXT NOT NULL DEFAULT to_char((now() AT TIME ZONE 'UTC'), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
11855
|
+
);
|
|
11856
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
11857
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
|
11858
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(assigned_agent_id);
|
|
11859
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
|
|
11860
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
|
|
11861
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id);
|
|
11862
|
+
|
|
11863
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
11864
|
+
id TEXT PRIMARY KEY,
|
|
11865
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
11866
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
11867
|
+
body TEXT NOT NULL,
|
|
11868
|
+
created_at TEXT NOT NULL DEFAULT to_char((now() AT TIME ZONE 'UTC'), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
11869
|
+
);
|
|
11870
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
|
|
11871
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
|
|
11801
11872
|
`
|
|
11802
11873
|
];
|
|
11803
11874
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-sync-worker.d.ts","sourceRoot":"","sources":["../src/pg-sync-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/pg-sync-worker.ts
|
|
3
|
+
import { parentPort, workerData } from "worker_threads";
|
|
4
|
+
import pg from "pg";
|
|
5
|
+
var { dsn, ssl, control, data } = workerData;
|
|
6
|
+
var status = new Int32Array(control);
|
|
7
|
+
var dataView = new Uint8Array(data);
|
|
8
|
+
var encoder = new TextEncoder;
|
|
9
|
+
var client = new pg.Client({ connectionString: dsn, ssl });
|
|
10
|
+
var connected = false;
|
|
11
|
+
var connecting = null;
|
|
12
|
+
async function ensureConnected() {
|
|
13
|
+
if (connected)
|
|
14
|
+
return;
|
|
15
|
+
if (!connecting) {
|
|
16
|
+
connecting = client.connect().then(() => {
|
|
17
|
+
connected = true;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
await connecting;
|
|
21
|
+
}
|
|
22
|
+
function respond(statusCode, payload) {
|
|
23
|
+
const bytes = encoder.encode(JSON.stringify(payload));
|
|
24
|
+
if (bytes.length > dataView.length) {
|
|
25
|
+
const errBytes = encoder.encode(JSON.stringify({
|
|
26
|
+
message: `PgSyncWorker: response of ${bytes.length} bytes exceeds shared buffer (${dataView.length})`
|
|
27
|
+
}));
|
|
28
|
+
dataView.set(errBytes, 0);
|
|
29
|
+
Atomics.store(status, 1, errBytes.length);
|
|
30
|
+
Atomics.store(status, 0, 2);
|
|
31
|
+
Atomics.notify(status, 0);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
dataView.set(bytes, 0);
|
|
35
|
+
Atomics.store(status, 1, bytes.length);
|
|
36
|
+
Atomics.store(status, 0, statusCode);
|
|
37
|
+
Atomics.notify(status, 0);
|
|
38
|
+
}
|
|
39
|
+
parentPort?.on("message", async (msg) => {
|
|
40
|
+
try {
|
|
41
|
+
await ensureConnected();
|
|
42
|
+
const result = await client.query(msg.sql, msg.params);
|
|
43
|
+
respond(1, { rows: result.rows, rowCount: result.rowCount });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
respond(2, { message: error instanceof Error ? error.message : String(error) });
|
|
46
|
+
}
|
|
47
|
+
});
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -272,6 +272,18 @@ export interface ListTasksFilter {
|
|
|
272
272
|
export interface MementosClientConfig {
|
|
273
273
|
baseUrl?: string;
|
|
274
274
|
fetch?: typeof globalThis.fetch;
|
|
275
|
+
/**
|
|
276
|
+
* API key issued by `contracts issue-key --app mementos`. Sent as both
|
|
277
|
+
* `Authorization: Bearer <key>` and `x-api-key`. Required against a
|
|
278
|
+
* self_hosted deployment with API-key auth enabled.
|
|
279
|
+
*/
|
|
280
|
+
apiKey?: string;
|
|
281
|
+
/**
|
|
282
|
+
* Versioned route prefix. Defaults to the canonical `/v1`. The deployed
|
|
283
|
+
* service also serves the legacy `/api` prefix; set `prefix: "/api"` to target
|
|
284
|
+
* it explicitly.
|
|
285
|
+
*/
|
|
286
|
+
prefix?: string;
|
|
275
287
|
}
|
|
276
288
|
export declare class MementosError extends Error {
|
|
277
289
|
readonly status: number;
|
|
@@ -368,6 +380,8 @@ export interface SessionMemoryJob {
|
|
|
368
380
|
export declare class MementosClient {
|
|
369
381
|
private baseUrl;
|
|
370
382
|
private _fetch;
|
|
383
|
+
private apiKey?;
|
|
384
|
+
private prefix;
|
|
371
385
|
constructor(config?: MementosClientConfig);
|
|
372
386
|
static fromEnv(overrides?: Partial<MementosClientConfig>): MementosClient;
|
|
373
387
|
private request;
|
|
@@ -383,6 +397,7 @@ export declare class MementosClient {
|
|
|
383
397
|
getHealth(): Promise<{
|
|
384
398
|
status: "ok" | "warn";
|
|
385
399
|
version: string;
|
|
400
|
+
mode: "local" | "cloud";
|
|
386
401
|
profile: string;
|
|
387
402
|
db_path: string;
|
|
388
403
|
hostname: string;
|
|
@@ -394,6 +409,18 @@ export declare class MementosClient {
|
|
|
394
409
|
agents: number;
|
|
395
410
|
projects: number;
|
|
396
411
|
}>;
|
|
412
|
+
/** Liveness/readiness probe — verifies backing-store connectivity. */
|
|
413
|
+
getReady(): Promise<{
|
|
414
|
+
status: "ready" | "not_ready";
|
|
415
|
+
version: string;
|
|
416
|
+
mode: "local" | "cloud";
|
|
417
|
+
}>;
|
|
418
|
+
/** Service version + storage mode. */
|
|
419
|
+
getVersion(): Promise<{
|
|
420
|
+
status: "ok";
|
|
421
|
+
version: string;
|
|
422
|
+
mode: "local" | "cloud";
|
|
423
|
+
}>;
|
|
397
424
|
getReport(options?: {
|
|
398
425
|
days?: number;
|
|
399
426
|
project_id?: string;
|