planche 0.5.1 → 0.5.3

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 CHANGED
@@ -40,6 +40,7 @@ npx planche [options]
40
40
  -p, --port <n> Port to listen on (default: 8888)
41
41
  --addr <host> Address to bind (default: 127.0.0.1)
42
42
  --token <str> Auth token (default: random on each start)
43
+ --print-token Print the token (hidden by default; the pairing link is enough)
43
44
  --no-open Print the pairing link without opening a browser
44
45
  --verbose Log every request
45
46
  -h, --help Show help
package/dist/index.js CHANGED
@@ -526,6 +526,7 @@ var MySqlDriver = class {
526
526
  var mysqlDriverFactory = {
527
527
  async open(config) {
528
528
  const connection = await mysql.createConnection(toConnectionOptions(config));
529
+ connection.on("error", (err) => console.error("[mysql] Connection error:", err.message));
529
530
  return new MySqlDriver(connection, config);
530
531
  },
531
532
  async test(config) {
@@ -719,6 +720,7 @@ var postgresDriverFactory = {
719
720
  async open(config) {
720
721
  const client = new pg.Client(toClientConfig(config));
721
722
  await client.connect();
723
+ client.on("error", (err) => console.error("[postgres] Connection error:", err.message));
722
724
  const driver = new PostgresDriver(client, config);
723
725
  await driver.loadTypeNames();
724
726
  return driver;
@@ -1520,6 +1522,69 @@ function testDirect(config) {
1520
1522
  }
1521
1523
  }
1522
1524
 
1525
+ // src/drivers/connection-lost.ts
1526
+ var ConnectionLostError = class extends Error {
1527
+ status = 404;
1528
+ constructor(message) {
1529
+ super(message);
1530
+ this.name = "ConnectionLostError";
1531
+ }
1532
+ };
1533
+ var LOST_CODES = /* @__PURE__ */ new Set([
1534
+ // mysql2
1535
+ "PROTOCOL_CONNECTION_LOST",
1536
+ "PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR",
1537
+ "PROTOCOL_ENQUEUE_AFTER_QUIT",
1538
+ "PROTOCOL_ENQUEUE_AFTER_DESTROY",
1539
+ // node sockets (pg, mssql/tedious surface these directly)
1540
+ "ECONNRESET",
1541
+ "EPIPE",
1542
+ "ETIMEDOUT",
1543
+ "ECONNABORTED",
1544
+ "ENETUNREACH",
1545
+ "EHOSTUNREACH",
1546
+ "ENETDOWN",
1547
+ // mssql
1548
+ "ESOCKET",
1549
+ "ECLOSE",
1550
+ "ENOTOPEN",
1551
+ // pg: admin_shutdown / crash_shutdown / cannot_connect_now
1552
+ "57P01",
1553
+ "57P02",
1554
+ "57P03"
1555
+ ]);
1556
+ var LOST_MESSAGES = [
1557
+ /connection is in closed state/i,
1558
+ // mysql2 "Can't add new command when connection is in closed state"
1559
+ /connection lost/i,
1560
+ /connection terminated/i,
1561
+ // pg
1562
+ /client has encountered a connection error/i,
1563
+ // pg
1564
+ /client was closed and is not queryable/i,
1565
+ // pg
1566
+ /connection is closed/i,
1567
+ // mssql
1568
+ /socket hang up/i,
1569
+ /ORA-0311[34]\b/,
1570
+ /ORA-01012\b/,
1571
+ /ORA-12541\b/,
1572
+ /ORA-03135\b/,
1573
+ /NJS-500\b/,
1574
+ /NJS-521\b/
1575
+ // oracle
1576
+ ];
1577
+ function isConnectionLost(err) {
1578
+ if (!err || typeof err !== "object") return false;
1579
+ const e = err;
1580
+ if (typeof e.code === "string" && LOST_CODES.has(e.code)) return true;
1581
+ if (e.fatal === true) return true;
1582
+ if (typeof e.errorNum === "number" && [3113, 3114, 1012, 12541, 3135].includes(e.errorNum)) return true;
1583
+ const message = typeof e.message === "string" ? e.message : "";
1584
+ if (LOST_MESSAGES.some((re) => re.test(message))) return true;
1585
+ return e.originalError !== void 0 && e.originalError !== err && isConnectionLost(e.originalError);
1586
+ }
1587
+
1523
1588
  // src/tunneling.ts
1524
1589
  var IDLE_TIMEOUT_MS = Number(process.env.PLANCHE_IDLE_TIMEOUT_MS) || 30 * 60 * 1e3;
1525
1590
  var MAX_CONNECTIONS = Number(process.env.PLANCHE_MAX_CONNECTIONS) || 64;
@@ -1567,7 +1632,16 @@ var ConnectionManager = class {
1567
1632
  throw new Error(`Connection not found: ${connectionId}`);
1568
1633
  }
1569
1634
  this.lastUsed.set(connectionId, Date.now());
1570
- return driver.query(sql2, database);
1635
+ try {
1636
+ return await driver.query(sql2, database);
1637
+ } catch (err) {
1638
+ if (!isConnectionLost(err)) throw err;
1639
+ console.log(`[ConnectionManager] Connection lost, evicting: ${connectionId}`);
1640
+ this.connections.delete(connectionId);
1641
+ this.lastUsed.delete(connectionId);
1642
+ await driver.close().catch(() => void 0);
1643
+ throw new ConnectionLostError(`Connection lost: ${err instanceof Error ? err.message : String(err)}`);
1644
+ }
1571
1645
  }
1572
1646
  /**
1573
1647
  * Close and remove a connection.
@@ -1818,6 +1892,10 @@ function createAgent(options) {
1818
1892
  } catch (err) {
1819
1893
  const message = errorMessage(err);
1820
1894
  console.error("[query] Error:", message);
1895
+ if (err instanceof ConnectionLostError) {
1896
+ fail(res, 404, message);
1897
+ return;
1898
+ }
1821
1899
  fail(res, 422, message);
1822
1900
  }
1823
1901
  });
@@ -1879,6 +1957,7 @@ Options:
1879
1957
  -p, --port <n> Port to listen on (default: 8888, env PLANCHE_PORT)
1880
1958
  --addr <host> Address to bind (default: 127.0.0.1, env PLANCHE_ADDR)
1881
1959
  --token <str> Auth token (default: random, env PLANCHE_AUTH_TOKEN)
1960
+ --print-token Print the token (for manual setup); hidden by default
1882
1961
  --app <url> Web app to open (default: ${DEFAULT_APP_URL}, env PLANCHE_APP_URL)
1883
1962
  --origin <url> Extra browser origin allowed to use the agent (repeatable,
1884
1963
  env PLANCHE_ALLOWED_ORIGINS as a comma-separated list)
@@ -1931,6 +2010,7 @@ function main() {
1931
2010
  open: { type: "boolean" },
1932
2011
  verbose: { type: "boolean" },
1933
2012
  "allow-remote": { type: "boolean" },
2013
+ "print-token": { type: "boolean" },
1934
2014
  version: { type: "boolean", short: "v" },
1935
2015
  help: { type: "boolean", short: "h" }
1936
2016
  },
@@ -2011,7 +2091,8 @@ function main() {
2011
2091
 
2012
2092
  ${pairUrl}
2013
2093
  `);
2014
- console.log(tokenFromEnv ? "Auth token: (from PLANCHE_AUTH_TOKEN)" : `Auth token (for manual setup): ${authToken}`);
2094
+ if (flags["print-token"]) console.log(`Auth token: ${authToken}`);
2095
+ else console.log(tokenFromEnv ? "Auth token: (from PLANCHE_AUTH_TOKEN)" : "Auth token: hidden \u2014 run with --print-token to show it for manual setup");
2015
2096
  console.log(`Allowed origins: ${allowedOrigins.join(", ")}`);
2016
2097
  if (!loopback) {
2017
2098
  console.error(`WARNING: bound to ${addr} without TLS \u2014 anyone who can reach this port and knows the token can use your databases, and anyone on the path can read the traffic.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "planche",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Local agent for Planche — lets the browser-based Planche SQL client reach MySQL/MariaDB/PostgreSQL/SQLite/SQL Server/Oracle/Neon/Supabase/Cloudflare D1 databases from your machine.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://planche.dev",