@withone/cli 1.56.1 → 1.57.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/README.md +14 -1
- package/dist/{chunk-KAF5SVJS.js → chunk-GKAIRI4Y.js} +1 -1
- package/dist/{chunk-VH46MAE4.js → chunk-IMU2X3ZV.js} +335 -102
- package/dist/{chunk-XWTV7FA3.js → chunk-YTM6HJKA.js} +1 -1
- package/dist/index.js +548 -19
- package/dist/{migrate-IJI3BTWL.js → migrate-TACDLGPD.js} +2 -2
- package/dist/{runtime-65IPTPVD.js → runtime-SX36HGBA.js} +1 -1
- package/dist/{schema-OO4WK5ND.js → schema-RJC2WQQ6.js} +1 -1
- package/dist/{sql-QIVDWTL3.js → sql-SYCWUPNP.js} +2 -2
- package/package.json +1 -1
- package/skills/one/SKILL.md +22 -1
package/README.md
CHANGED
|
@@ -280,6 +280,18 @@ one actions knowledge shopify 67890abcdef
|
|
|
280
280
|
|
|
281
281
|
Always read the knowledge before executing. It tells you exactly what parameters are required, what format they need, and any platform-specific quirks.
|
|
282
282
|
|
|
283
|
+
In `--agent` mode the response is a **digest**: request-building sections in full (method/URL, headers, description, enforcement rules, required and optional parameters, sample request, gotchas, error handling) plus a table of contents for the rest. Response shapes, response-field tables, and worked examples — the bulk of every large doc — are loaded on demand:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
one --agent actions knowledge github <actionId> # digest + sections[] table of contents
|
|
287
|
+
one --agent actions knowledge github <actionId> --section "Response Fields" # one section, by heading or id
|
|
288
|
+
one --agent actions knowledge github <actionId> --section response,examples # several, by alias
|
|
289
|
+
one --agent actions knowledge github <actionId> --full # whole document
|
|
290
|
+
one --agent actions knowledge github <actionId> --toc # every section id, no document
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The JSON carries `truncated`, `sections[]` (the omitted sections: id, heading, chars), and `more` (the commands to load the rest); the markdown ends with the same notice so it is never mistaken for the complete doc. Small documents are returned whole. Section requests are served from the local cache. Human (non-agent) output always prints the full document; `--section` works there too.
|
|
294
|
+
|
|
283
295
|
### `one actions execute <platform> <actionId> <connectionKey>`
|
|
284
296
|
|
|
285
297
|
Execute an API action on a connected platform.
|
|
@@ -354,6 +366,7 @@ Knowledge and search commands also support cache flags:
|
|
|
354
366
|
```bash
|
|
355
367
|
one actions knowledge gmail <actionId> --no-cache # Skip cache, fetch fresh
|
|
356
368
|
one actions knowledge gmail <actionId> --cache-status # Check cache status
|
|
369
|
+
one actions knowledge gmail <actionId> --section response # Served from the cached doc, no fetch
|
|
357
370
|
one actions search gmail "send email" --no-cache # Skip cache for search
|
|
358
371
|
one actions execute gmail <actionId> <key> --no-cache # Fresh action-details lookup
|
|
359
372
|
```
|
|
@@ -364,7 +377,7 @@ Note: execution responses are never cached — the action always runs live. Only
|
|
|
364
377
|
|
|
365
378
|
### `one mem` — unified memory store
|
|
366
379
|
|
|
367
|
-
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin for remote/self-hosted) that backs both user-authored notes and synced platform data. **Zero-config** — the first `one mem` call on a fresh machine auto-initializes the cluster at `~/.one/pg/cluster/` and writes a daemon PID file so subsequent CLI invocations reuse it.
|
|
380
|
+
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin for remote/self-hosted) that backs both user-authored notes and synced platform data. **Zero-config** — the first `one mem` call on a fresh machine auto-initializes the cluster at `~/.one/pg/cluster/` and writes a daemon PID file so subsequent CLI invocations reuse it. The daemon listens on port 5434, moving to the next free port if another program already holds it; pin a port with `one mem config set embedded-postgres.port <n>`.
|
|
368
381
|
|
|
369
382
|
```bash
|
|
370
383
|
# User memories — works immediately on a new install
|
|
@@ -1456,12 +1456,333 @@ var postgresPlugin = {
|
|
|
1456
1456
|
};
|
|
1457
1457
|
|
|
1458
1458
|
// src/lib/memory/plugins/embedded-postgres/index.ts
|
|
1459
|
+
import path2 from "path";
|
|
1460
|
+
|
|
1461
|
+
// src/lib/memory/plugins/embedded-postgres/daemon.ts
|
|
1459
1462
|
import fs from "fs";
|
|
1460
1463
|
import net from "net";
|
|
1461
1464
|
import path from "path";
|
|
1462
1465
|
import { createRequire } from "module";
|
|
1463
|
-
import { spawn } from "child_process";
|
|
1466
|
+
import { spawn, spawnSync } from "child_process";
|
|
1464
1467
|
var requireFromHere = createRequire(import.meta.url);
|
|
1468
|
+
var PG_PORT_OFFSET = 1e3;
|
|
1469
|
+
var PORT_SEARCH_SPAN = 20;
|
|
1470
|
+
var LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
1471
|
+
var LOG_KEEP_BYTES = 1024 * 1024;
|
|
1472
|
+
var DEFAULT_STOP_TIMEOUT_MS = 15e3;
|
|
1473
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1474
|
+
function pidFilePath(dataDir) {
|
|
1475
|
+
return path.join(dataDir, ".pgserve.json");
|
|
1476
|
+
}
|
|
1477
|
+
function readPidFile(dataDir) {
|
|
1478
|
+
try {
|
|
1479
|
+
return JSON.parse(fs.readFileSync(pidFilePath(dataDir), "utf8"));
|
|
1480
|
+
} catch {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function writePidFile(dataDir, rec) {
|
|
1485
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
1486
|
+
fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
|
|
1487
|
+
}
|
|
1488
|
+
function isPidAlive(pid) {
|
|
1489
|
+
try {
|
|
1490
|
+
process.kill(pid, 0);
|
|
1491
|
+
return true;
|
|
1492
|
+
} catch {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async function waitForExit(pid, timeoutMs) {
|
|
1497
|
+
const deadline = Date.now() + timeoutMs;
|
|
1498
|
+
while (isPidAlive(pid)) {
|
|
1499
|
+
if (Date.now() >= deadline) return false;
|
|
1500
|
+
await sleep(50);
|
|
1501
|
+
}
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
function signal(pid, sig) {
|
|
1505
|
+
try {
|
|
1506
|
+
process.kill(pid, sig);
|
|
1507
|
+
} catch {
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function canBind(host, port) {
|
|
1511
|
+
return new Promise((resolve) => {
|
|
1512
|
+
const server = net.createServer();
|
|
1513
|
+
server.once("error", () => resolve(false));
|
|
1514
|
+
server.listen({ port, host, exclusive: true }, () => server.close(() => resolve(true)));
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
async function portPairFree(host, port) {
|
|
1518
|
+
return port + PG_PORT_OFFSET <= 65535 && await canBind(host, port) && await canBind("127.0.0.1", port + PG_PORT_OFFSET);
|
|
1519
|
+
}
|
|
1520
|
+
async function choosePort(host, preferred, explicit) {
|
|
1521
|
+
if (await portPairFree(host, preferred)) return preferred;
|
|
1522
|
+
const hint = 'set a different "memory.embedded-postgres.port" in ~/.one/config.json';
|
|
1523
|
+
if (explicit) {
|
|
1524
|
+
throw new Error(
|
|
1525
|
+
`Port ${preferred} (or ${preferred + PG_PORT_OFFSET}, its internal Postgres port) on ${host} is already in use by another program, so the embedded Postgres can't start there. Free it, or ${hint}.`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
for (let port = preferred + 1; port <= preferred + PORT_SEARCH_SPAN; port++) {
|
|
1529
|
+
if (await portPairFree(host, port)) return port;
|
|
1530
|
+
}
|
|
1531
|
+
throw new Error(
|
|
1532
|
+
`Ports ${preferred}-${preferred + PORT_SEARCH_SPAN} on ${host} are all already in use, so the embedded Postgres can't start. Free one, or ${hint}.`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
function readRange(file, start, length) {
|
|
1536
|
+
const fd = fs.openSync(file, "r");
|
|
1537
|
+
try {
|
|
1538
|
+
const buf = Buffer.alloc(length);
|
|
1539
|
+
const n = fs.readSync(fd, buf, 0, length, start);
|
|
1540
|
+
return buf.subarray(0, n);
|
|
1541
|
+
} finally {
|
|
1542
|
+
fs.closeSync(fd);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function fileSize(file) {
|
|
1546
|
+
try {
|
|
1547
|
+
return fs.statSync(file).size;
|
|
1548
|
+
} catch {
|
|
1549
|
+
return 0;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function trimLog(logPath, maxBytes = LOG_MAX_BYTES, keepBytes = LOG_KEEP_BYTES) {
|
|
1553
|
+
const size = fileSize(logPath);
|
|
1554
|
+
if (size <= maxBytes) return;
|
|
1555
|
+
const tail = readRange(logPath, size - keepBytes, keepBytes);
|
|
1556
|
+
const nl = tail.indexOf(10);
|
|
1557
|
+
const kept = nl === -1 ? tail : tail.subarray(nl + 1);
|
|
1558
|
+
const notice = `[one] pgserve.log passed ${maxBytes} bytes; kept the last ${kept.length} (${(/* @__PURE__ */ new Date()).toISOString()})
|
|
1559
|
+
`;
|
|
1560
|
+
fs.writeFileSync(logPath, Buffer.concat([Buffer.from(notice), kept]));
|
|
1561
|
+
}
|
|
1562
|
+
function logSince(logPath, offset, maxBytes = 2e3) {
|
|
1563
|
+
const size = fileSize(logPath);
|
|
1564
|
+
const start = Math.max(offset, size - maxBytes);
|
|
1565
|
+
if (size <= start) return "";
|
|
1566
|
+
return readRange(logPath, start, size - start).toString("utf8").replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
1567
|
+
}
|
|
1568
|
+
function lockIsStale(lockPath, ttlMs) {
|
|
1569
|
+
try {
|
|
1570
|
+
if (Date.now() - fs.statSync(lockPath).mtimeMs > ttlMs) return true;
|
|
1571
|
+
const { pid } = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
1572
|
+
return typeof pid === "number" && !isPidAlive(pid);
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async function withStartupLock(dataDir, waitMs, fn) {
|
|
1578
|
+
const lockPath = path.join(dataDir, ".pgserve.lock");
|
|
1579
|
+
const deadline = Date.now() + waitMs;
|
|
1580
|
+
for (; ; ) {
|
|
1581
|
+
try {
|
|
1582
|
+
fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), { flag: "wx" });
|
|
1583
|
+
break;
|
|
1584
|
+
} catch (err) {
|
|
1585
|
+
if (err.code !== "EEXIST") throw err;
|
|
1586
|
+
}
|
|
1587
|
+
if (lockIsStale(lockPath, waitMs)) {
|
|
1588
|
+
fs.rmSync(lockPath, { force: true });
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (Date.now() >= deadline) {
|
|
1592
|
+
throw new Error(`Timed out waiting for another \`one\` process to start the embedded Postgres (lock: ${lockPath}).`);
|
|
1593
|
+
}
|
|
1594
|
+
await sleep(100);
|
|
1595
|
+
}
|
|
1596
|
+
try {
|
|
1597
|
+
return await fn();
|
|
1598
|
+
} finally {
|
|
1599
|
+
fs.rmSync(lockPath, { force: true });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function readPostmasterPid(clusterDir) {
|
|
1603
|
+
try {
|
|
1604
|
+
const lines = fs.readFileSync(path.join(clusterDir, "postmaster.pid"), "utf8").split("\n");
|
|
1605
|
+
const pid = Number.parseInt(lines[0] ?? "", 10);
|
|
1606
|
+
const port = Number.parseInt(lines[3] ?? "", 10);
|
|
1607
|
+
return pid > 0 && port > 0 ? { pid, port } : null;
|
|
1608
|
+
} catch {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function pgserveOwner(postmasterPid, deps) {
|
|
1613
|
+
const ppid = deps.processInfo(postmasterPid)?.ppid;
|
|
1614
|
+
if (!ppid || ppid <= 1) return null;
|
|
1615
|
+
const command = deps.processInfo(ppid)?.command ?? "";
|
|
1616
|
+
return /pgserve[\\/]bin[\\/]postgres-server\.js/.test(command) ? ppid : null;
|
|
1617
|
+
}
|
|
1618
|
+
async function stopStaleCluster(clusterDir, deps, timeoutMs) {
|
|
1619
|
+
const holder = readPostmasterPid(clusterDir);
|
|
1620
|
+
if (!holder || !isPidAlive(holder.pid)) return;
|
|
1621
|
+
if (await deps.probe(holder.port, "postmaster") !== "ours") return;
|
|
1622
|
+
const owner = pgserveOwner(holder.pid, deps);
|
|
1623
|
+
if (owner !== null) {
|
|
1624
|
+
signal(owner, "SIGTERM");
|
|
1625
|
+
if (!await waitForExit(owner, timeoutMs)) signal(owner, "SIGKILL");
|
|
1626
|
+
}
|
|
1627
|
+
if (!isPidAlive(holder.pid)) return;
|
|
1628
|
+
signal(holder.pid, "SIGINT");
|
|
1629
|
+
if (!await waitForExit(holder.pid, timeoutMs)) {
|
|
1630
|
+
throw new Error(
|
|
1631
|
+
`The embedded Postgres (PID ${holder.pid}) is holding ${clusterDir} and did not stop. Stop it (kill ${holder.pid}) and retry.`
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
async function findHealthyDaemon(dataDir, deps) {
|
|
1636
|
+
const rec = readPidFile(dataDir);
|
|
1637
|
+
if (!rec || !isPidAlive(rec.pid)) return null;
|
|
1638
|
+
return await deps.probe(rec.port, "router") === "ours" ? rec.port : null;
|
|
1639
|
+
}
|
|
1640
|
+
function pgserveArgs(cfg, clusterDir, port) {
|
|
1641
|
+
const args = [
|
|
1642
|
+
"--data",
|
|
1643
|
+
clusterDir,
|
|
1644
|
+
"--port",
|
|
1645
|
+
String(port),
|
|
1646
|
+
"--host",
|
|
1647
|
+
cfg.host,
|
|
1648
|
+
"--log",
|
|
1649
|
+
cfg.logLevel,
|
|
1650
|
+
"--no-stats",
|
|
1651
|
+
// Single process. In cluster mode a worker that can't bind the port is
|
|
1652
|
+
// re-forked immediately, forever, each crash logging a full stack trace.
|
|
1653
|
+
"--no-cluster"
|
|
1654
|
+
];
|
|
1655
|
+
if (cfg.pgvector) args.push("--pgvector");
|
|
1656
|
+
return args;
|
|
1657
|
+
}
|
|
1658
|
+
async function waitUntilServing(child, cfg, port, deps, log) {
|
|
1659
|
+
const deadline = Date.now() + cfg.startupTimeoutMs;
|
|
1660
|
+
const details = () => {
|
|
1661
|
+
const tail = logSince(log.path, log.offset);
|
|
1662
|
+
return `
|
|
1663
|
+
Log: ${log.path}${tail ? `
|
|
1664
|
+
${tail}` : ""}`;
|
|
1665
|
+
};
|
|
1666
|
+
for (; ; ) {
|
|
1667
|
+
const exit = child.exitStatus();
|
|
1668
|
+
if (exit) throw new Error(`pgserve exited (${exit}) before it was ready.${details()}`);
|
|
1669
|
+
if (await deps.probe(port, "router") === "ours") return;
|
|
1670
|
+
if (Date.now() >= deadline) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
`pgserve did not start serving on ${cfg.host}:${port} within ${Math.round(cfg.startupTimeoutMs / 1e3)}s.${details()}`
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
await sleep(250);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async function ensureRunning(cfg, deps) {
|
|
1679
|
+
const reused = await findHealthyDaemon(cfg.dataDir, deps);
|
|
1680
|
+
if (reused !== null) return reused;
|
|
1681
|
+
fs.mkdirSync(cfg.dataDir, { recursive: true });
|
|
1682
|
+
const stopTimeoutMs = deps.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
|
|
1683
|
+
return withStartupLock(cfg.dataDir, cfg.startupTimeoutMs + 3 * stopTimeoutMs, async () => {
|
|
1684
|
+
const raced = await findHealthyDaemon(cfg.dataDir, deps);
|
|
1685
|
+
if (raced !== null) return raced;
|
|
1686
|
+
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1687
|
+
fs.mkdirSync(clusterDir, { recursive: true });
|
|
1688
|
+
await stopStaleCluster(clusterDir, deps, stopTimeoutMs);
|
|
1689
|
+
const port = await choosePort(cfg.host, cfg.port, cfg.portExplicit);
|
|
1690
|
+
const logPath = path.join(cfg.dataDir, "pgserve.log");
|
|
1691
|
+
trimLog(logPath);
|
|
1692
|
+
const log = { path: logPath, offset: fileSize(logPath) };
|
|
1693
|
+
const fd = fs.openSync(logPath, "a");
|
|
1694
|
+
let child;
|
|
1695
|
+
try {
|
|
1696
|
+
child = deps.spawnPgserve(pgserveArgs(cfg, clusterDir, port), fd);
|
|
1697
|
+
} finally {
|
|
1698
|
+
fs.closeSync(fd);
|
|
1699
|
+
}
|
|
1700
|
+
await waitUntilServing(child, cfg, port, deps, log);
|
|
1701
|
+
writePidFile(cfg.dataDir, {
|
|
1702
|
+
pid: child.pid,
|
|
1703
|
+
port,
|
|
1704
|
+
dataDir: cfg.dataDir,
|
|
1705
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1706
|
+
});
|
|
1707
|
+
return port;
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
function sameDir(a, b) {
|
|
1711
|
+
const norm = (p) => {
|
|
1712
|
+
let r = path.resolve(p);
|
|
1713
|
+
try {
|
|
1714
|
+
r = fs.realpathSync.native(r);
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
1718
|
+
};
|
|
1719
|
+
return a !== "" && norm(a) === norm(b);
|
|
1720
|
+
}
|
|
1721
|
+
async function probeDataDirectory(Client, target, clusterDir) {
|
|
1722
|
+
const client = new Client({ ...target, connectionTimeoutMillis: 3e3, query_timeout: 3e3 });
|
|
1723
|
+
client.on("error", () => {
|
|
1724
|
+
});
|
|
1725
|
+
try {
|
|
1726
|
+
await client.connect();
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
return err.code === "ECONNREFUSED" ? "down" : "other";
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
const { rows } = await client.query("SHOW data_directory");
|
|
1732
|
+
return sameDir(String(rows[0]?.data_directory ?? ""), clusterDir) ? "ours" : "other";
|
|
1733
|
+
} catch {
|
|
1734
|
+
return "other";
|
|
1735
|
+
} finally {
|
|
1736
|
+
await client.end().catch(() => {
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
function resolvePgserveBin() {
|
|
1741
|
+
const pkg = requireFromHere.resolve("pgserve/package.json");
|
|
1742
|
+
return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
|
|
1743
|
+
}
|
|
1744
|
+
function spawnPgserve(args, logFd, cwd) {
|
|
1745
|
+
const child = spawn(process.execPath, [resolvePgserveBin(), ...args], {
|
|
1746
|
+
detached: true,
|
|
1747
|
+
stdio: ["ignore", logFd, logFd],
|
|
1748
|
+
// pgserve resolves the bundled bun binary relative to its own package
|
|
1749
|
+
// location, not cwd, so cwd doesn't matter — but pin it for clarity.
|
|
1750
|
+
cwd
|
|
1751
|
+
});
|
|
1752
|
+
let status = null;
|
|
1753
|
+
child.once("error", (err) => {
|
|
1754
|
+
status = `failed to start: ${err.message}`;
|
|
1755
|
+
});
|
|
1756
|
+
child.once("exit", (code, sig) => {
|
|
1757
|
+
status = sig ? `signal ${sig}` : `code ${code}`;
|
|
1758
|
+
});
|
|
1759
|
+
child.unref();
|
|
1760
|
+
if (typeof child.pid !== "number") {
|
|
1761
|
+
throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
|
|
1762
|
+
}
|
|
1763
|
+
return { pid: child.pid, exitStatus: () => status };
|
|
1764
|
+
}
|
|
1765
|
+
function processInfo(pid) {
|
|
1766
|
+
if (process.platform === "win32") return null;
|
|
1767
|
+
const res = spawnSync("ps", ["-o", "ppid=", "-o", "command=", "-p", String(pid)], { encoding: "utf8" });
|
|
1768
|
+
const match = /^(\d+)\s+(.*)$/.exec((res.stdout ?? "").trim());
|
|
1769
|
+
return match ? { ppid: Number(match[1]), command: match[2] } : null;
|
|
1770
|
+
}
|
|
1771
|
+
function nodeDaemonDeps(cfg, Client, router) {
|
|
1772
|
+
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1773
|
+
const direct = { user: "postgres", password: "postgres", database: "postgres" };
|
|
1774
|
+
return {
|
|
1775
|
+
probe: (port, via) => probeDataDirectory(
|
|
1776
|
+
Client,
|
|
1777
|
+
via === "router" ? { host: cfg.host, port, ...router } : { host: "127.0.0.1", port, ...direct },
|
|
1778
|
+
clusterDir
|
|
1779
|
+
),
|
|
1780
|
+
spawnPgserve: (args, logFd) => spawnPgserve(args, logFd, cfg.dataDir),
|
|
1781
|
+
processInfo
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/lib/memory/plugins/embedded-postgres/index.ts
|
|
1465
1786
|
var BASE_CAPABILITIES = {
|
|
1466
1787
|
vectorSearch: true,
|
|
1467
1788
|
fullTextSearch: true,
|
|
@@ -1477,13 +1798,14 @@ var DEFAULTS = {
|
|
|
1477
1798
|
// `path.join(homeDir(), ...)` here would capture the home directory before
|
|
1478
1799
|
// ONE_HOME could take effect. See lib/home.ts.
|
|
1479
1800
|
get dataDir() {
|
|
1480
|
-
return
|
|
1801
|
+
return path2.join(homeDir(), ".one", "pg");
|
|
1481
1802
|
},
|
|
1482
1803
|
database: "one_mem",
|
|
1483
1804
|
schema: "public",
|
|
1484
1805
|
pgvector: true,
|
|
1485
1806
|
host: "127.0.0.1",
|
|
1486
1807
|
port: 5434,
|
|
1808
|
+
portExplicit: false,
|
|
1487
1809
|
// pgserve auto-provisions databases; the bundled superuser is `postgres`
|
|
1488
1810
|
// with no password by default. We don't expose the password to the user
|
|
1489
1811
|
// since the daemon only listens on 127.0.0.1.
|
|
@@ -1501,6 +1823,7 @@ function parseConfig2(raw) {
|
|
|
1501
1823
|
pgvector: r.pgvector ?? DEFAULTS.pgvector,
|
|
1502
1824
|
host: r.host ?? DEFAULTS.host,
|
|
1503
1825
|
port: r.port ?? DEFAULTS.port,
|
|
1826
|
+
portExplicit: r.port !== void 0,
|
|
1504
1827
|
user: r.user ?? DEFAULTS.user,
|
|
1505
1828
|
password: r.password ?? DEFAULTS.password,
|
|
1506
1829
|
logLevel: r.logLevel ?? DEFAULTS.logLevel,
|
|
@@ -1547,104 +1870,6 @@ function wrapClient2(pool) {
|
|
|
1547
1870
|
}
|
|
1548
1871
|
};
|
|
1549
1872
|
}
|
|
1550
|
-
function pidFilePath(dataDir) {
|
|
1551
|
-
return path.join(dataDir, ".pgserve.json");
|
|
1552
|
-
}
|
|
1553
|
-
function readPidFile(dataDir) {
|
|
1554
|
-
try {
|
|
1555
|
-
const raw = fs.readFileSync(pidFilePath(dataDir), "utf8");
|
|
1556
|
-
return JSON.parse(raw);
|
|
1557
|
-
} catch {
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
function writePidFile(dataDir, rec) {
|
|
1562
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
1563
|
-
fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
|
|
1564
|
-
}
|
|
1565
|
-
function isPidAlive(pid) {
|
|
1566
|
-
try {
|
|
1567
|
-
process.kill(pid, 0);
|
|
1568
|
-
return true;
|
|
1569
|
-
} catch {
|
|
1570
|
-
return false;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
async function isPortListening(host, port, timeoutMs = 1e3) {
|
|
1574
|
-
return new Promise((resolve) => {
|
|
1575
|
-
const socket = new net.Socket();
|
|
1576
|
-
let resolved = false;
|
|
1577
|
-
const finish = (ok) => {
|
|
1578
|
-
if (resolved) return;
|
|
1579
|
-
resolved = true;
|
|
1580
|
-
socket.destroy();
|
|
1581
|
-
resolve(ok);
|
|
1582
|
-
};
|
|
1583
|
-
socket.setTimeout(timeoutMs);
|
|
1584
|
-
socket.once("connect", () => finish(true));
|
|
1585
|
-
socket.once("timeout", () => finish(false));
|
|
1586
|
-
socket.once("error", () => finish(false));
|
|
1587
|
-
socket.connect(port, host);
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
async function waitForPort(host, port, deadlineAt) {
|
|
1591
|
-
while (Date.now() < deadlineAt) {
|
|
1592
|
-
if (await isPortListening(host, port)) return;
|
|
1593
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
1594
|
-
}
|
|
1595
|
-
throw new Error(
|
|
1596
|
-
`pgserve did not start listening on ${host}:${port} within ${Math.round((deadlineAt - Date.now() + 3e4) / 1e3)}s. Check the data dir for the postgres log, or remove ${path.basename(pidFilePath("<dataDir>"))} and retry.`
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
function resolvePgserveBin() {
|
|
1600
|
-
const pkg = requireFromHere.resolve("pgserve/package.json");
|
|
1601
|
-
return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
|
|
1602
|
-
}
|
|
1603
|
-
async function ensureRunning(cfg) {
|
|
1604
|
-
const existing = readPidFile(cfg.dataDir);
|
|
1605
|
-
if (existing && isPidAlive(existing.pid)) {
|
|
1606
|
-
if (await isPortListening(cfg.host, existing.port)) {
|
|
1607
|
-
cfg.port = existing.port;
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
fs.mkdirSync(cfg.dataDir, { recursive: true });
|
|
1612
|
-
const clusterDir = path.join(cfg.dataDir, "cluster");
|
|
1613
|
-
fs.mkdirSync(clusterDir, { recursive: true });
|
|
1614
|
-
const args = [
|
|
1615
|
-
"--data",
|
|
1616
|
-
clusterDir,
|
|
1617
|
-
"--port",
|
|
1618
|
-
String(cfg.port),
|
|
1619
|
-
"--host",
|
|
1620
|
-
cfg.host,
|
|
1621
|
-
"--log",
|
|
1622
|
-
cfg.logLevel,
|
|
1623
|
-
"--no-stats"
|
|
1624
|
-
];
|
|
1625
|
-
if (cfg.pgvector) args.push("--pgvector");
|
|
1626
|
-
const bin = resolvePgserveBin();
|
|
1627
|
-
const out = fs.openSync(path.join(cfg.dataDir, "pgserve.log"), "a");
|
|
1628
|
-
const child = spawn(process.execPath, [bin, ...args], {
|
|
1629
|
-
detached: true,
|
|
1630
|
-
stdio: ["ignore", out, out],
|
|
1631
|
-
// pgserve resolves the bundled bun binary relative to its own package
|
|
1632
|
-
// location, not cwd, so cwd doesn't matter — but pin it for clarity.
|
|
1633
|
-
cwd: cfg.dataDir
|
|
1634
|
-
});
|
|
1635
|
-
child.unref();
|
|
1636
|
-
if (typeof child.pid !== "number") {
|
|
1637
|
-
throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
|
|
1638
|
-
}
|
|
1639
|
-
const deadlineAt = Date.now() + cfg.startupTimeoutMs;
|
|
1640
|
-
await waitForPort(cfg.host, cfg.port, deadlineAt);
|
|
1641
|
-
writePidFile(cfg.dataDir, {
|
|
1642
|
-
pid: child.pid,
|
|
1643
|
-
port: cfg.port,
|
|
1644
|
-
dataDir: cfg.dataDir,
|
|
1645
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1646
|
-
});
|
|
1647
|
-
}
|
|
1648
1873
|
async function probePgvector(client) {
|
|
1649
1874
|
try {
|
|
1650
1875
|
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
@@ -1662,12 +1887,20 @@ var LazyEmbeddedPostgresBackend = class {
|
|
|
1662
1887
|
}
|
|
1663
1888
|
async ensure() {
|
|
1664
1889
|
if (this.backend) return this.backend;
|
|
1665
|
-
await ensureRunning(this.config);
|
|
1666
1890
|
const pgMod = await import("pg").catch((err) => {
|
|
1667
1891
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1668
1892
|
throw new Error(`pg is not installed. Run \`npm i pg\` or pick a different backend. (${msg})`);
|
|
1669
1893
|
});
|
|
1670
|
-
const
|
|
1894
|
+
const pgExports = pgMod.Pool ? pgMod : pgMod.default;
|
|
1895
|
+
const PoolCtor = pgExports.Pool;
|
|
1896
|
+
this.config.port = await ensureRunning(
|
|
1897
|
+
this.config,
|
|
1898
|
+
nodeDaemonDeps(this.config, pgExports.Client, {
|
|
1899
|
+
user: this.config.user,
|
|
1900
|
+
password: this.config.password,
|
|
1901
|
+
database: this.config.database
|
|
1902
|
+
})
|
|
1903
|
+
);
|
|
1671
1904
|
const pool = new PoolCtor({
|
|
1672
1905
|
host: this.config.host,
|
|
1673
1906
|
port: this.config.port,
|