@meetploy/cli 1.17.0 → 1.17.2

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Ploy Dev Dashboard</title>
7
- <script type="module" crossorigin src="/assets/main-duAiLjPq.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/main-B-euJxpr.css">
7
+ <script type="module" crossorigin src="/assets/main-BFhQn9QT.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/main-5Kt9I_hM.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/dev.js CHANGED
@@ -10,9 +10,9 @@ import { parse } from 'yaml';
10
10
  import { randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, randomBytes } from 'crypto';
11
11
  import { serve } from '@hono/node-server';
12
12
  import { Hono } from 'hono';
13
- import 'os';
13
+ import { tmpdir } from 'os';
14
14
  import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
15
- import Database from 'better-sqlite3';
15
+ import { DatabaseSync, backup } from 'node:sqlite';
16
16
 
17
17
  createRequire(import.meta.url);
18
18
 
@@ -381,6 +381,7 @@ function createDashboardRoutes(app, dbManager2, config) {
381
381
  fs: config.fs,
382
382
  workflow: config.workflow,
383
383
  cron: config.cron,
384
+ timer: config.timer,
384
385
  auth: config.auth
385
386
  });
386
387
  });
@@ -935,6 +936,93 @@ function createDashboardRoutes(app, dbManager2, config) {
935
936
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
936
937
  }
937
938
  });
939
+ app.get("/api/timer/:binding/entries", (c) => {
940
+ const binding = c.req.param("binding");
941
+ const timerName = config.timer?.[binding];
942
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
943
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
944
+ if (!timerName) {
945
+ return c.json({ error: "Timer binding not found" }, 404);
946
+ }
947
+ try {
948
+ const db = dbManager2.emulatorDb;
949
+ const total = db.prepare(`SELECT COUNT(*) as count FROM timer_entries WHERE timer_name = ?`).get(timerName).count;
950
+ const entries = db.prepare(`SELECT id, scheduled_time, payload, interval_ms, status, created_at
951
+ FROM timer_entries
952
+ WHERE timer_name = ?
953
+ ORDER BY scheduled_time DESC
954
+ LIMIT ? OFFSET ?`).all(timerName, limit, offset);
955
+ return c.json({
956
+ entries: entries.map((e) => ({
957
+ id: e.id,
958
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
959
+ payload: e.payload ? JSON.parse(e.payload) : null,
960
+ intervalMs: e.interval_ms,
961
+ status: e.status.toUpperCase(),
962
+ createdAt: new Date(e.created_at * 1e3).toISOString()
963
+ })),
964
+ total,
965
+ limit,
966
+ offset
967
+ });
968
+ } catch (err) {
969
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
970
+ }
971
+ });
972
+ app.get("/api/timer/:binding/metrics", (c) => {
973
+ const binding = c.req.param("binding");
974
+ const timerName = config.timer?.[binding];
975
+ if (!timerName) {
976
+ return c.json({ error: "Timer binding not found" }, 404);
977
+ }
978
+ try {
979
+ const db = dbManager2.emulatorDb;
980
+ const metrics = { pending: 0, fired: 0 };
981
+ const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
982
+ FROM timer_entries
983
+ WHERE timer_name = ?
984
+ GROUP BY status`).all(timerName);
985
+ for (const row of statusCounts) {
986
+ if (row.status === "pending") {
987
+ metrics.pending = row.count;
988
+ } else if (row.status === "fired") {
989
+ metrics.fired = row.count;
990
+ }
991
+ }
992
+ return c.json({ metrics });
993
+ } catch (err) {
994
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
995
+ }
996
+ });
997
+ app.get("/api/timer/:binding/executions", (c) => {
998
+ const binding = c.req.param("binding");
999
+ const timerName = config.timer?.[binding];
1000
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
1001
+ if (!timerName) {
1002
+ return c.json({ error: "Timer binding not found" }, 404);
1003
+ }
1004
+ try {
1005
+ const db = dbManager2.emulatorDb;
1006
+ const executions = db.prepare(`SELECT id, timer_id, scheduled_time, status, error, duration_ms, created_at
1007
+ FROM timer_executions
1008
+ WHERE timer_name = ?
1009
+ ORDER BY created_at DESC
1010
+ LIMIT ?`).all(timerName, limit);
1011
+ return c.json({
1012
+ executions: executions.map((e) => ({
1013
+ id: e.id,
1014
+ timerId: e.timer_id,
1015
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
1016
+ status: e.status.toUpperCase(),
1017
+ error: e.error,
1018
+ durationMs: e.duration_ms,
1019
+ createdAt: new Date(e.created_at * 1e3).toISOString()
1020
+ }))
1021
+ });
1022
+ } catch (err) {
1023
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1024
+ }
1025
+ });
938
1026
  if (hasDashboard) {
939
1027
  app.get("/assets/*", (c) => {
940
1028
  const path = c.req.path;
@@ -1048,7 +1136,7 @@ function createDbHandler(getDatabase) {
1048
1136
  return c.json(results);
1049
1137
  }
1050
1138
  if (method === "dump") {
1051
- const buffer = db.serialize();
1139
+ const buffer = await db.dumpToBuffer();
1052
1140
  return new Response(new Uint8Array(buffer), {
1053
1141
  headers: {
1054
1142
  "Content-Type": "application/octet-stream"
@@ -1317,6 +1405,62 @@ function createStateHandlers(db) {
1317
1405
  deleteHandler
1318
1406
  };
1319
1407
  }
1408
+
1409
+ // ../emulator/dist/services/timer-service.js
1410
+ function createTimerHandlers(db) {
1411
+ const setHandler = async (c) => {
1412
+ try {
1413
+ const body = await c.req.json();
1414
+ const { timerName, id, scheduledTime, payload, intervalMs } = body;
1415
+ const now = Math.floor(Date.now() / 1e3);
1416
+ const scheduledTimeSec = Math.floor(scheduledTime / 1e3);
1417
+ db.prepare(`INSERT OR REPLACE INTO timer_entries (id, timer_name, scheduled_time, payload, interval_ms, status, created_at)
1418
+ VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(id, timerName, scheduledTimeSec, payload !== void 0 ? JSON.stringify(payload) : null, intervalMs ?? null, now);
1419
+ return c.json({ success: true });
1420
+ } catch (err) {
1421
+ const message = err instanceof Error ? err.message : String(err);
1422
+ return c.json({ success: false, error: message }, 500);
1423
+ }
1424
+ };
1425
+ const getHandler = async (c) => {
1426
+ try {
1427
+ const body = await c.req.json();
1428
+ const { timerName, id } = body;
1429
+ const row = db.prepare(`SELECT id, scheduled_time, payload, interval_ms FROM timer_entries
1430
+ WHERE timer_name = ? AND id = ? AND status = 'pending'`).get(timerName, id);
1431
+ if (!row) {
1432
+ return c.json({ timer: null });
1433
+ }
1434
+ return c.json({
1435
+ timer: {
1436
+ id: row.id,
1437
+ scheduledTime: row.scheduled_time * 1e3,
1438
+ payload: row.payload ? JSON.parse(row.payload) : void 0,
1439
+ intervalMs: row.interval_ms ?? void 0
1440
+ }
1441
+ });
1442
+ } catch (err) {
1443
+ const message = err instanceof Error ? err.message : String(err);
1444
+ return c.json({ success: false, error: message }, 500);
1445
+ }
1446
+ };
1447
+ const deleteHandler = async (c) => {
1448
+ try {
1449
+ const body = await c.req.json();
1450
+ const { timerName, id } = body;
1451
+ db.prepare(`DELETE FROM timer_entries WHERE timer_name = ? AND id = ?`).run(timerName, id);
1452
+ return c.json({ success: true });
1453
+ } catch (err) {
1454
+ const message = err instanceof Error ? err.message : String(err);
1455
+ return c.json({ success: false, error: message }, 500);
1456
+ }
1457
+ };
1458
+ return {
1459
+ setHandler,
1460
+ getHandler,
1461
+ deleteHandler
1462
+ };
1463
+ }
1320
1464
  function createWorkflowHandlers(db, workerUrl) {
1321
1465
  const triggerHandler = async (c) => {
1322
1466
  try {
@@ -1538,6 +1682,12 @@ async function startMockServer(dbManager2, config, options = {}) {
1538
1682
  app.post("/fs/delete", fsHandlers.deleteHandler);
1539
1683
  app.post("/fs/list", fsHandlers.listHandler);
1540
1684
  }
1685
+ if (config.timer) {
1686
+ const timerHandlers = createTimerHandlers(dbManager2.emulatorDb);
1687
+ app.post("/timer/set", timerHandlers.setHandler);
1688
+ app.post("/timer/get", timerHandlers.getHandler);
1689
+ app.post("/timer/delete", timerHandlers.deleteHandler);
1690
+ }
1541
1691
  if (config.auth) {
1542
1692
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
1543
1693
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -1566,6 +1716,65 @@ async function startMockServer(dbManager2, config, options = {}) {
1566
1716
  });
1567
1717
  });
1568
1718
  }
1719
+ function openDatabase(filepath) {
1720
+ const db = new DatabaseSync(filepath);
1721
+ return {
1722
+ prepare(sql) {
1723
+ const stmt = db.prepare(sql);
1724
+ return {
1725
+ run(...params) {
1726
+ const result = stmt.run(...params);
1727
+ return {
1728
+ changes: Number(result.changes),
1729
+ lastInsertRowid: result.lastInsertRowid
1730
+ };
1731
+ },
1732
+ get(...params) {
1733
+ return stmt.get(...params);
1734
+ },
1735
+ all(...params) {
1736
+ return stmt.all(...params);
1737
+ }
1738
+ };
1739
+ },
1740
+ exec(sql) {
1741
+ db.exec(sql);
1742
+ },
1743
+ close() {
1744
+ db.close();
1745
+ },
1746
+ pragma(pragma) {
1747
+ db.exec(`PRAGMA ${pragma}`);
1748
+ },
1749
+ transaction(fn) {
1750
+ return () => {
1751
+ db.exec("BEGIN");
1752
+ try {
1753
+ const result = fn();
1754
+ db.exec("COMMIT");
1755
+ return result;
1756
+ } catch (e) {
1757
+ db.exec("ROLLBACK");
1758
+ throw e;
1759
+ }
1760
+ };
1761
+ },
1762
+ async dumpToBuffer() {
1763
+ const tempPath = join(tmpdir(), `ploy-dump-${randomUUID()}.db`);
1764
+ try {
1765
+ await backup(db, tempPath);
1766
+ return readFileSync(tempPath);
1767
+ } finally {
1768
+ try {
1769
+ unlinkSync(tempPath);
1770
+ } catch {
1771
+ }
1772
+ }
1773
+ }
1774
+ };
1775
+ }
1776
+
1777
+ // ../emulator/dist/utils/sqlite.js
1569
1778
  var EMULATOR_SCHEMA = `
1570
1779
  -- Queue messages table
1571
1780
  CREATE TABLE IF NOT EXISTS queue_messages (
@@ -1701,17 +1910,47 @@ CREATE TABLE IF NOT EXISTS cron_executions (
1701
1910
 
1702
1911
  CREATE INDEX IF NOT EXISTS idx_cron_executions_name
1703
1912
  ON cron_executions(cron_name, triggered_at);
1913
+
1914
+ -- Timer entries table (durable timers, optionally recurring)
1915
+ CREATE TABLE IF NOT EXISTS timer_entries (
1916
+ id TEXT NOT NULL,
1917
+ timer_name TEXT NOT NULL,
1918
+ scheduled_time INTEGER NOT NULL,
1919
+ payload TEXT,
1920
+ interval_ms INTEGER,
1921
+ status TEXT DEFAULT 'pending',
1922
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
1923
+ PRIMARY KEY (timer_name, id)
1924
+ );
1925
+
1926
+ CREATE INDEX IF NOT EXISTS idx_timer_entries_status_time
1927
+ ON timer_entries(status, scheduled_time);
1928
+
1929
+ -- Timer execution log
1930
+ CREATE TABLE IF NOT EXISTS timer_executions (
1931
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1932
+ timer_id TEXT NOT NULL,
1933
+ timer_name TEXT NOT NULL,
1934
+ scheduled_time INTEGER NOT NULL,
1935
+ status TEXT DEFAULT 'pending',
1936
+ error TEXT,
1937
+ duration_ms INTEGER,
1938
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
1939
+ );
1940
+
1941
+ CREATE INDEX IF NOT EXISTS idx_timer_executions_name
1942
+ ON timer_executions(timer_name, created_at);
1704
1943
  `;
1705
1944
  function initializeDatabases(projectDir) {
1706
1945
  const dataDir = ensureDataDir(projectDir);
1707
1946
  const d1Databases = /* @__PURE__ */ new Map();
1708
- const emulatorDb = new Database(join(dataDir, "emulator.db"));
1947
+ const emulatorDb = openDatabase(join(dataDir, "emulator.db"));
1709
1948
  emulatorDb.pragma("journal_mode = WAL");
1710
1949
  emulatorDb.exec(EMULATOR_SCHEMA);
1711
1950
  function getD1Database(bindingName) {
1712
1951
  let db = d1Databases.get(bindingName);
1713
1952
  if (!db) {
1714
- db = new Database(join(dataDir, "db", `${bindingName}.db`));
1953
+ db = openDatabase(join(dataDir, "db", `${bindingName}.db`));
1715
1954
  db.pragma("journal_mode = WAL");
1716
1955
  d1Databases.set(bindingName, db);
1717
1956
  }