@meetploy/cli 1.17.0 → 1.17.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/index.js CHANGED
@@ -219,6 +219,7 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
219
219
  validateBindings(config.fs, "fs", configFile);
220
220
  validateBindings(config.workflow, "workflow", configFile);
221
221
  validateCronBindings(config.cron, configFile);
222
+ validateBindings(config.alarm, "alarm", configFile);
222
223
  if (config.ai !== void 0 && typeof config.ai !== "boolean") {
223
224
  throw new Error(`'ai' in ${configFile} must be a boolean`);
224
225
  }
@@ -294,7 +295,7 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
294
295
  return validatePloyConfig(config, configFile, validationOptions);
295
296
  }
296
297
  function hasBindings(config) {
297
- return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.fs ?? config.workflow ?? config.cron ?? config.ai ?? config.auth);
298
+ return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.fs ?? config.workflow ?? config.cron ?? config.alarm ?? config.ai ?? config.auth);
298
299
  }
299
300
  function parseDotEnv(content) {
300
301
  const result = {};
@@ -397,6 +398,72 @@ var init_cli = __esm({
397
398
  }
398
399
  });
399
400
 
401
+ // ../emulator/dist/runtime/alarm-runtime.js
402
+ var ALARM_RUNTIME_CODE;
403
+ var init_alarm_runtime = __esm({
404
+ "../emulator/dist/runtime/alarm-runtime.js"() {
405
+ ALARM_RUNTIME_CODE = `
406
+ export function initializeAlarm(alarmName, serviceUrl) {
407
+ function parseOptions(options) {
408
+ if (options === undefined || options === null) {
409
+ return { payload: undefined, intervalMs: undefined };
410
+ }
411
+ if (typeof options === "object" && options !== null && ("payload" in options || "intervalMs" in options)) {
412
+ return { payload: options.payload, intervalMs: options.intervalMs };
413
+ }
414
+ return { payload: options, intervalMs: undefined };
415
+ }
416
+
417
+ return {
418
+ async set(id, scheduledTime, options) {
419
+ const ts = typeof scheduledTime === "number" ? scheduledTime : scheduledTime.getTime();
420
+ const { payload, intervalMs } = parseOptions(options);
421
+ const response = await fetch(serviceUrl + "/alarm/set", {
422
+ method: "POST",
423
+ headers: { "Content-Type": "application/json" },
424
+ body: JSON.stringify({ alarmName, id, scheduledTime: ts, payload, intervalMs }),
425
+ });
426
+
427
+ if (!response.ok) {
428
+ const errorText = await response.text();
429
+ throw new Error("Alarm set failed: " + errorText);
430
+ }
431
+ },
432
+
433
+ async get(id) {
434
+ const response = await fetch(serviceUrl + "/alarm/get", {
435
+ method: "POST",
436
+ headers: { "Content-Type": "application/json" },
437
+ body: JSON.stringify({ alarmName, id }),
438
+ });
439
+
440
+ if (!response.ok) {
441
+ const errorText = await response.text();
442
+ throw new Error("Alarm get failed: " + errorText);
443
+ }
444
+
445
+ const result = await response.json();
446
+ return result.alarm ?? null;
447
+ },
448
+
449
+ async delete(id) {
450
+ const response = await fetch(serviceUrl + "/alarm/delete", {
451
+ method: "POST",
452
+ headers: { "Content-Type": "application/json" },
453
+ body: JSON.stringify({ alarmName, id }),
454
+ });
455
+
456
+ if (!response.ok) {
457
+ const errorText = await response.text();
458
+ throw new Error("Alarm delete failed: " + errorText);
459
+ }
460
+ },
461
+ };
462
+ }
463
+ `;
464
+ }
465
+ });
466
+
400
467
  // ../emulator/dist/runtime/cache-runtime.js
401
468
  var CACHE_RUNTIME_CODE;
402
469
  var init_cache_runtime = __esm({
@@ -1270,6 +1337,12 @@ function generateWrapperCode(config, mockServiceUrl, envVars) {
1270
1337
  bindings.push(` ${bindingName}: initializeWorkflow("${workflowName}", "${mockServiceUrl}"),`);
1271
1338
  }
1272
1339
  }
1340
+ if (config.alarm) {
1341
+ imports.push('import { initializeAlarm } from "__ploy_alarm_runtime__";');
1342
+ for (const [bindingName, alarmName] of Object.entries(config.alarm)) {
1343
+ bindings.push(` ${bindingName}: initializeAlarm("${alarmName}", "${mockServiceUrl}"),`);
1344
+ }
1345
+ }
1273
1346
  imports.push('import userWorker from "__ploy_user_worker__";');
1274
1347
  const workflowHandlerCode = config.workflow ? `
1275
1348
  // Handle workflow execution requests
@@ -1329,6 +1402,32 @@ function generateWrapperCode(config, mockServiceUrl, envVars) {
1329
1402
  headers: { "Content-Type": "application/json" }
1330
1403
  });
1331
1404
  }` : "";
1405
+ const alarmHandlerCode = config.alarm ? `
1406
+ // Handle alarm fire delivery
1407
+ if (request.headers.get("X-Ploy-Alarm-Fire") === "true") {
1408
+ const alarmId = request.headers.get("X-Ploy-Alarm-Id");
1409
+ const alarmName = request.headers.get("X-Ploy-Alarm-Name");
1410
+ const alarmTime = request.headers.get("X-Ploy-Alarm-Time");
1411
+ if (alarmId && userWorker.alarm) {
1412
+ const body = await request.json().catch(() => ({}));
1413
+ try {
1414
+ await userWorker.alarm({
1415
+ id: alarmId,
1416
+ scheduledTime: parseInt(alarmTime || "0", 10),
1417
+ payload: body.payload,
1418
+ intervalMs: body.intervalMs
1419
+ }, injectedEnv, ctx);
1420
+ return new Response(JSON.stringify({ success: true }), {
1421
+ headers: { "Content-Type": "application/json" }
1422
+ });
1423
+ } catch (error) {
1424
+ return new Response(JSON.stringify({ success: false, error: String(error) }), {
1425
+ status: 500,
1426
+ headers: { "Content-Type": "application/json" }
1427
+ });
1428
+ }
1429
+ }
1430
+ }` : "";
1332
1431
  const queueHandlerCode = config.queue ? `
1333
1432
  // Handle queue message delivery
1334
1433
  if (request.headers.get("X-Ploy-Queue-Delivery") === "true") {
@@ -1373,6 +1472,7 @@ export default {
1373
1472
  async fetch(request, env, ctx) {
1374
1473
  const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
1375
1474
  ${cronHandlerCode}
1475
+ ${alarmHandlerCode}
1376
1476
  ${workflowHandlerCode}
1377
1477
  ${queueHandlerCode}
1378
1478
 
@@ -1420,6 +1520,10 @@ function createRuntimePlugin(_config) {
1420
1520
  path: "__ploy_workflow_runtime__",
1421
1521
  namespace: "ploy-runtime"
1422
1522
  }));
1523
+ build2.onResolve({ filter: /^__ploy_alarm_runtime__$/ }, () => ({
1524
+ path: "__ploy_alarm_runtime__",
1525
+ namespace: "ploy-runtime"
1526
+ }));
1423
1527
  build2.onLoad({ filter: /^__ploy_db_runtime__$/, namespace: "ploy-runtime" }, () => ({
1424
1528
  contents: DB_RUNTIME_CODE,
1425
1529
  loader: "ts"
@@ -1444,6 +1548,10 @@ function createRuntimePlugin(_config) {
1444
1548
  contents: WORKFLOW_RUNTIME_CODE,
1445
1549
  loader: "ts"
1446
1550
  }));
1551
+ build2.onLoad({ filter: /^__ploy_alarm_runtime__$/, namespace: "ploy-runtime" }, () => ({
1552
+ contents: ALARM_RUNTIME_CODE,
1553
+ loader: "ts"
1554
+ }));
1447
1555
  }
1448
1556
  };
1449
1557
  }
@@ -1480,6 +1588,7 @@ async function bundleWorker(options) {
1480
1588
  var NODE_BUILTINS;
1481
1589
  var init_bundler = __esm({
1482
1590
  "../emulator/dist/bundler/bundler.js"() {
1591
+ init_alarm_runtime();
1483
1592
  init_cache_runtime();
1484
1593
  init_db_runtime();
1485
1594
  init_fs_runtime();
@@ -1743,6 +1852,135 @@ var init_workerd_config = __esm({
1743
1852
  "../emulator/dist/config/workerd-config.js"() {
1744
1853
  }
1745
1854
  });
1855
+
1856
+ // ../emulator/dist/services/alarm-service.js
1857
+ function createAlarmHandlers(db) {
1858
+ const setHandler = async (c) => {
1859
+ try {
1860
+ const body = await c.req.json();
1861
+ const { alarmName, id, scheduledTime, payload, intervalMs } = body;
1862
+ const now = Math.floor(Date.now() / 1e3);
1863
+ const scheduledTimeSec = Math.floor(scheduledTime / 1e3);
1864
+ db.prepare(`INSERT OR REPLACE INTO alarm_entries (id, alarm_name, scheduled_time, payload, interval_ms, status, created_at)
1865
+ VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(id, alarmName, scheduledTimeSec, payload !== void 0 ? JSON.stringify(payload) : null, intervalMs ?? null, now);
1866
+ return c.json({ success: true });
1867
+ } catch (err) {
1868
+ const message = err instanceof Error ? err.message : String(err);
1869
+ return c.json({ success: false, error: message }, 500);
1870
+ }
1871
+ };
1872
+ const getHandler = async (c) => {
1873
+ try {
1874
+ const body = await c.req.json();
1875
+ const { alarmName, id } = body;
1876
+ const row = db.prepare(`SELECT id, scheduled_time, payload, interval_ms FROM alarm_entries
1877
+ WHERE alarm_name = ? AND id = ? AND status = 'pending'`).get(alarmName, id);
1878
+ if (!row) {
1879
+ return c.json({ alarm: null });
1880
+ }
1881
+ return c.json({
1882
+ alarm: {
1883
+ id: row.id,
1884
+ scheduledTime: row.scheduled_time * 1e3,
1885
+ payload: row.payload ? JSON.parse(row.payload) : void 0,
1886
+ intervalMs: row.interval_ms ?? void 0
1887
+ }
1888
+ });
1889
+ } catch (err) {
1890
+ const message = err instanceof Error ? err.message : String(err);
1891
+ return c.json({ success: false, error: message }, 500);
1892
+ }
1893
+ };
1894
+ const deleteHandler = async (c) => {
1895
+ try {
1896
+ const body = await c.req.json();
1897
+ const { alarmName, id } = body;
1898
+ db.prepare(`DELETE FROM alarm_entries WHERE alarm_name = ? AND id = ?`).run(alarmName, id);
1899
+ return c.json({ success: true });
1900
+ } catch (err) {
1901
+ const message = err instanceof Error ? err.message : String(err);
1902
+ return c.json({ success: false, error: message }, 500);
1903
+ }
1904
+ };
1905
+ return {
1906
+ setHandler,
1907
+ getHandler,
1908
+ deleteHandler
1909
+ };
1910
+ }
1911
+ function createAlarmProcessor(db, workerUrl) {
1912
+ let interval = null;
1913
+ async function processAlarms() {
1914
+ const now = Math.floor(Date.now() / 1e3);
1915
+ const rows = db.prepare(`SELECT id, alarm_name, scheduled_time, payload, interval_ms
1916
+ FROM alarm_entries
1917
+ WHERE status = 'pending' AND scheduled_time <= ?
1918
+ ORDER BY scheduled_time ASC
1919
+ LIMIT 50`).all(now);
1920
+ for (const row of rows) {
1921
+ const startTime = Date.now();
1922
+ try {
1923
+ const response = await fetch(workerUrl, {
1924
+ method: "POST",
1925
+ headers: {
1926
+ "Content-Type": "application/json",
1927
+ "X-Ploy-Alarm-Fire": "true",
1928
+ "X-Ploy-Alarm-Id": row.id,
1929
+ "X-Ploy-Alarm-Name": row.alarm_name,
1930
+ "X-Ploy-Alarm-Time": String(row.scheduled_time * 1e3)
1931
+ },
1932
+ body: JSON.stringify({
1933
+ scheduledTime: row.scheduled_time * 1e3,
1934
+ payload: row.payload ? JSON.parse(row.payload) : void 0,
1935
+ intervalMs: row.interval_ms ?? void 0
1936
+ })
1937
+ });
1938
+ const durationMs = Date.now() - startTime;
1939
+ if (response.ok) {
1940
+ if (row.interval_ms) {
1941
+ const nextTimeSec = row.scheduled_time + Math.floor(row.interval_ms / 1e3);
1942
+ db.prepare(`UPDATE alarm_entries SET scheduled_time = ? WHERE id = ? AND alarm_name = ?`).run(nextTimeSec, row.id, row.alarm_name);
1943
+ } else {
1944
+ db.prepare(`UPDATE alarm_entries SET status = 'fired' WHERE id = ? AND alarm_name = ?`).run(row.id, row.alarm_name);
1945
+ }
1946
+ db.prepare(`INSERT INTO alarm_executions (alarm_id, alarm_name, scheduled_time, status, duration_ms, created_at)
1947
+ VALUES (?, ?, ?, 'completed', ?, ?)`).run(row.id, row.alarm_name, row.scheduled_time, durationMs, now);
1948
+ } else {
1949
+ db.prepare(`INSERT INTO alarm_executions (alarm_id, alarm_name, scheduled_time, status, error, duration_ms, created_at)
1950
+ VALUES (?, ?, ?, 'failed', ?, ?, ?)`).run(row.id, row.alarm_name, row.scheduled_time, `HTTP ${String(response.status)}`, durationMs, now);
1951
+ db.prepare(`UPDATE alarm_entries SET scheduled_time = ? WHERE id = ? AND alarm_name = ?`).run(now + 5, row.id, row.alarm_name);
1952
+ }
1953
+ } catch (err) {
1954
+ const durationMs = Date.now() - startTime;
1955
+ const errorMessage = err instanceof Error ? err.message : String(err);
1956
+ db.prepare(`INSERT INTO alarm_executions (alarm_id, alarm_name, scheduled_time, status, error, duration_ms, created_at)
1957
+ VALUES (?, ?, ?, 'failed', ?, ?, ?)`).run(row.id, row.alarm_name, row.scheduled_time, errorMessage, durationMs, now);
1958
+ db.prepare(`UPDATE alarm_entries SET scheduled_time = ? WHERE id = ? AND alarm_name = ?`).run(now + 5, row.id, row.alarm_name);
1959
+ }
1960
+ }
1961
+ }
1962
+ return {
1963
+ start() {
1964
+ if (interval) {
1965
+ return;
1966
+ }
1967
+ interval = setInterval(() => {
1968
+ processAlarms().catch(() => {
1969
+ });
1970
+ }, 1e3);
1971
+ },
1972
+ stop() {
1973
+ if (interval) {
1974
+ clearInterval(interval);
1975
+ interval = null;
1976
+ }
1977
+ }
1978
+ };
1979
+ }
1980
+ var init_alarm_service = __esm({
1981
+ "../emulator/dist/services/alarm-service.js"() {
1982
+ }
1983
+ });
1746
1984
  function parseCronField(field, min, max) {
1747
1985
  const values = /* @__PURE__ */ new Set();
1748
1986
  for (const part of field.split(",")) {
@@ -2257,6 +2495,7 @@ function createDashboardRoutes(app, dbManager2, config) {
2257
2495
  fs: config.fs,
2258
2496
  workflow: config.workflow,
2259
2497
  cron: config.cron,
2498
+ alarm: config.alarm,
2260
2499
  auth: config.auth
2261
2500
  });
2262
2501
  });
@@ -2811,6 +3050,93 @@ function createDashboardRoutes(app, dbManager2, config) {
2811
3050
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2812
3051
  }
2813
3052
  });
3053
+ app.get("/api/alarm/:binding/entries", (c) => {
3054
+ const binding = c.req.param("binding");
3055
+ const alarmName = config.alarm?.[binding];
3056
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
3057
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
3058
+ if (!alarmName) {
3059
+ return c.json({ error: "Alarm binding not found" }, 404);
3060
+ }
3061
+ try {
3062
+ const db = dbManager2.emulatorDb;
3063
+ const total = db.prepare(`SELECT COUNT(*) as count FROM alarm_entries WHERE alarm_name = ?`).get(alarmName).count;
3064
+ const entries = db.prepare(`SELECT id, scheduled_time, payload, interval_ms, status, created_at
3065
+ FROM alarm_entries
3066
+ WHERE alarm_name = ?
3067
+ ORDER BY scheduled_time DESC
3068
+ LIMIT ? OFFSET ?`).all(alarmName, limit, offset);
3069
+ return c.json({
3070
+ entries: entries.map((e) => ({
3071
+ id: e.id,
3072
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
3073
+ payload: e.payload ? JSON.parse(e.payload) : null,
3074
+ intervalMs: e.interval_ms,
3075
+ status: e.status.toUpperCase(),
3076
+ createdAt: new Date(e.created_at * 1e3).toISOString()
3077
+ })),
3078
+ total,
3079
+ limit,
3080
+ offset
3081
+ });
3082
+ } catch (err) {
3083
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
3084
+ }
3085
+ });
3086
+ app.get("/api/alarm/:binding/metrics", (c) => {
3087
+ const binding = c.req.param("binding");
3088
+ const alarmName = config.alarm?.[binding];
3089
+ if (!alarmName) {
3090
+ return c.json({ error: "Alarm binding not found" }, 404);
3091
+ }
3092
+ try {
3093
+ const db = dbManager2.emulatorDb;
3094
+ const metrics = { pending: 0, fired: 0 };
3095
+ const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
3096
+ FROM alarm_entries
3097
+ WHERE alarm_name = ?
3098
+ GROUP BY status`).all(alarmName);
3099
+ for (const row of statusCounts) {
3100
+ if (row.status === "pending") {
3101
+ metrics.pending = row.count;
3102
+ } else if (row.status === "fired") {
3103
+ metrics.fired = row.count;
3104
+ }
3105
+ }
3106
+ return c.json({ metrics });
3107
+ } catch (err) {
3108
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
3109
+ }
3110
+ });
3111
+ app.get("/api/alarm/:binding/executions", (c) => {
3112
+ const binding = c.req.param("binding");
3113
+ const alarmName = config.alarm?.[binding];
3114
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
3115
+ if (!alarmName) {
3116
+ return c.json({ error: "Alarm binding not found" }, 404);
3117
+ }
3118
+ try {
3119
+ const db = dbManager2.emulatorDb;
3120
+ const executions = db.prepare(`SELECT id, alarm_id, scheduled_time, status, error, duration_ms, created_at
3121
+ FROM alarm_executions
3122
+ WHERE alarm_name = ?
3123
+ ORDER BY created_at DESC
3124
+ LIMIT ?`).all(alarmName, limit);
3125
+ return c.json({
3126
+ executions: executions.map((e) => ({
3127
+ id: e.id,
3128
+ alarmId: e.alarm_id,
3129
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
3130
+ status: e.status.toUpperCase(),
3131
+ error: e.error,
3132
+ durationMs: e.duration_ms,
3133
+ createdAt: new Date(e.created_at * 1e3).toISOString()
3134
+ }))
3135
+ });
3136
+ } catch (err) {
3137
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
3138
+ }
3139
+ });
2814
3140
  if (hasDashboard) {
2815
3141
  app.get("/assets/*", (c) => {
2816
3142
  const path = c.req.path;
@@ -3521,6 +3847,12 @@ async function startMockServer(dbManager2, config, options = {}) {
3521
3847
  app.post("/fs/delete", fsHandlers.deleteHandler);
3522
3848
  app.post("/fs/list", fsHandlers.listHandler);
3523
3849
  }
3850
+ if (config.alarm) {
3851
+ const alarmHandlers = createAlarmHandlers(dbManager2.emulatorDb);
3852
+ app.post("/alarm/set", alarmHandlers.setHandler);
3853
+ app.post("/alarm/get", alarmHandlers.getHandler);
3854
+ app.post("/alarm/delete", alarmHandlers.deleteHandler);
3855
+ }
3524
3856
  if (config.auth) {
3525
3857
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
3526
3858
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -3553,6 +3885,7 @@ var DEFAULT_MOCK_SERVER_PORT;
3553
3885
  var init_mock_server = __esm({
3554
3886
  "../emulator/dist/services/mock-server.js"() {
3555
3887
  init_paths();
3888
+ init_alarm_service();
3556
3889
  init_auth_service();
3557
3890
  init_cache_service();
3558
3891
  init_dashboard_routes();
@@ -3731,6 +4064,36 @@ CREATE TABLE IF NOT EXISTS cron_executions (
3731
4064
 
3732
4065
  CREATE INDEX IF NOT EXISTS idx_cron_executions_name
3733
4066
  ON cron_executions(cron_name, triggered_at);
4067
+
4068
+ -- Alarm entries table (durable timers, optionally recurring)
4069
+ CREATE TABLE IF NOT EXISTS alarm_entries (
4070
+ id TEXT NOT NULL,
4071
+ alarm_name TEXT NOT NULL,
4072
+ scheduled_time INTEGER NOT NULL,
4073
+ payload TEXT,
4074
+ interval_ms INTEGER,
4075
+ status TEXT DEFAULT 'pending',
4076
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
4077
+ PRIMARY KEY (alarm_name, id)
4078
+ );
4079
+
4080
+ CREATE INDEX IF NOT EXISTS idx_alarm_entries_status_time
4081
+ ON alarm_entries(status, scheduled_time);
4082
+
4083
+ -- Alarm execution log
4084
+ CREATE TABLE IF NOT EXISTS alarm_executions (
4085
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4086
+ alarm_id TEXT NOT NULL,
4087
+ alarm_name TEXT NOT NULL,
4088
+ scheduled_time INTEGER NOT NULL,
4089
+ status TEXT DEFAULT 'pending',
4090
+ error TEXT,
4091
+ duration_ms INTEGER,
4092
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
4093
+ );
4094
+
4095
+ CREATE INDEX IF NOT EXISTS idx_alarm_executions_name
4096
+ ON alarm_executions(alarm_name, created_at);
3734
4097
  `;
3735
4098
  }
3736
4099
  });
@@ -3747,6 +4110,7 @@ var init_emulator = __esm({
3747
4110
  init_env();
3748
4111
  init_ploy_config2();
3749
4112
  init_workerd_config();
4113
+ init_alarm_service();
3750
4114
  init_cron_service();
3751
4115
  init_mock_server();
3752
4116
  init_queue_service();
@@ -3764,6 +4128,7 @@ var init_emulator = __esm({
3764
4128
  fileWatcher = null;
3765
4129
  queueProcessor = null;
3766
4130
  cronScheduler = null;
4131
+ alarmProcessor = null;
3767
4132
  resolvedEnvVars = {};
3768
4133
  constructor(options = {}) {
3769
4134
  const port = options.port ?? 8787;
@@ -3837,6 +4202,12 @@ var init_emulator = __esm({
3837
4202
  this.cronScheduler.start();
3838
4203
  debug("Cron scheduler started", this.options.verbose);
3839
4204
  }
4205
+ if (this.config.alarm) {
4206
+ const workerUrl2 = `http://${this.options.host}:${String(this.options.port)}`;
4207
+ this.alarmProcessor = createAlarmProcessor(this.dbManager.emulatorDb, workerUrl2);
4208
+ this.alarmProcessor.start();
4209
+ debug("Alarm processor started", this.options.verbose);
4210
+ }
3840
4211
  success(`Emulator running at http://${this.options.host}:${String(this.options.port)}`);
3841
4212
  log(` Dashboard: http://${this.options.host}:${String(this.mockServer.port)}`);
3842
4213
  if (Object.keys(this.resolvedEnvVars).length > 0) {
@@ -3862,6 +4233,9 @@ var init_emulator = __esm({
3862
4233
  log(` Cron: ${name} = ${expr}`);
3863
4234
  }
3864
4235
  }
4236
+ if (this.config.alarm) {
4237
+ log(` Alarm bindings: ${Object.keys(this.config.alarm).join(", ")}`);
4238
+ }
3865
4239
  this.setupSignalHandlers();
3866
4240
  } catch (err) {
3867
4241
  error(`Failed to start emulator: ${err instanceof Error ? err.message : String(err)}`);
@@ -3990,6 +4364,10 @@ var init_emulator = __esm({
3990
4364
  this.cronScheduler.stop();
3991
4365
  this.cronScheduler = null;
3992
4366
  }
4367
+ if (this.alarmProcessor) {
4368
+ this.alarmProcessor.stop();
4369
+ this.alarmProcessor = null;
4370
+ }
3993
4371
  if (this.queueProcessor) {
3994
4372
  this.queueProcessor.stop();
3995
4373
  this.queueProcessor = null;
@@ -5885,6 +6263,24 @@ ${varProps}
5885
6263
  properties.push(` ${bindingName}: WorkflowBinding;`);
5886
6264
  }
5887
6265
  }
6266
+ if (config.fs) {
6267
+ const fsKeys = Object.keys(config.fs);
6268
+ if (fsKeys.length > 0) {
6269
+ imports.push("FileStorageBinding");
6270
+ for (const bindingName of fsKeys) {
6271
+ properties.push(` ${bindingName}: FileStorageBinding;`);
6272
+ }
6273
+ }
6274
+ }
6275
+ if (config.alarm) {
6276
+ const alarmKeys = Object.keys(config.alarm);
6277
+ if (alarmKeys.length > 0) {
6278
+ imports.push("AlarmBinding");
6279
+ for (const bindingName of alarmKeys) {
6280
+ properties.push(` ${bindingName}: AlarmBinding;`);
6281
+ }
6282
+ }
6283
+ }
5888
6284
  const lines = [
5889
6285
  "// This file is auto-generated by `ploy types`. Do not edit manually.",
5890
6286
  ""
@@ -5926,7 +6322,7 @@ async function typesCommand(options = {}) {
5926
6322
  console.error("Error: ploy.yaml not found in current directory");
5927
6323
  process.exit(1);
5928
6324
  }
5929
- const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow || config.cron;
6325
+ const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow || config.fs || config.alarm || config.cron;
5930
6326
  if (!hasBindings2) {
5931
6327
  console.log("No bindings found in ploy.yaml. Generating empty Env.");
5932
6328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetploy/cli",
3
- "version": "1.17.0",
3
+ "version": "1.17.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",