@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/dashboard-dist/assets/{main-B-euJxpr.css → main-5Kt9I_hM.css} +1 -1
- package/dist/dashboard-dist/assets/main-Ch3tPHX5.js +354 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +180 -0
- package/dist/index.js +398 -2
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-duAiLjPq.js +0 -339
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
|
}
|