@meetploy/cli 1.17.1 → 1.18.0
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-Ch3tPHX5.js → main-BFhQn9QT.js} +34 -34
- package/dist/dashboard-dist/index.html +1 -1
- package/dist/dev.js +162 -103
- package/dist/index.js +344 -282
- package/package.json +1 -3
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
|
|
|
13
13
|
import { URL, fileURLToPath } from 'url';
|
|
14
14
|
import { serve } from '@hono/node-server';
|
|
15
15
|
import { Hono } from 'hono';
|
|
16
|
-
import
|
|
16
|
+
import { DatabaseSync, backup } from 'node:sqlite';
|
|
17
17
|
import { spawn, exec } from 'child_process';
|
|
18
18
|
import createClient from 'openapi-fetch';
|
|
19
19
|
import { createServer } from 'http';
|
|
@@ -219,7 +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.
|
|
222
|
+
validateBindings(config.timer, "timer", configFile);
|
|
223
223
|
if (config.ai !== void 0 && typeof config.ai !== "boolean") {
|
|
224
224
|
throw new Error(`'ai' in ${configFile} must be a boolean`);
|
|
225
225
|
}
|
|
@@ -295,7 +295,7 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
|
|
|
295
295
|
return validatePloyConfig(config, configFile, validationOptions);
|
|
296
296
|
}
|
|
297
297
|
function hasBindings(config) {
|
|
298
|
-
return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.fs ?? config.workflow ?? config.cron ?? config.
|
|
298
|
+
return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.fs ?? config.workflow ?? config.cron ?? config.timer ?? config.ai ?? config.auth);
|
|
299
299
|
}
|
|
300
300
|
function parseDotEnv(content) {
|
|
301
301
|
const result = {};
|
|
@@ -398,72 +398,6 @@ var init_cli = __esm({
|
|
|
398
398
|
}
|
|
399
399
|
});
|
|
400
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
|
-
|
|
467
401
|
// ../emulator/dist/runtime/cache-runtime.js
|
|
468
402
|
var CACHE_RUNTIME_CODE;
|
|
469
403
|
var init_cache_runtime = __esm({
|
|
@@ -1087,6 +1021,72 @@ export function initializeState(stateName: string, serviceUrl: string): StateBin
|
|
|
1087
1021
|
}
|
|
1088
1022
|
});
|
|
1089
1023
|
|
|
1024
|
+
// ../emulator/dist/runtime/timer-runtime.js
|
|
1025
|
+
var TIMER_RUNTIME_CODE;
|
|
1026
|
+
var init_timer_runtime = __esm({
|
|
1027
|
+
"../emulator/dist/runtime/timer-runtime.js"() {
|
|
1028
|
+
TIMER_RUNTIME_CODE = `
|
|
1029
|
+
export function initializeTimer(timerName, serviceUrl) {
|
|
1030
|
+
function parseOptions(options) {
|
|
1031
|
+
if (options === undefined || options === null) {
|
|
1032
|
+
return { payload: undefined, intervalMs: undefined };
|
|
1033
|
+
}
|
|
1034
|
+
if (typeof options === "object" && options !== null && ("payload" in options || "intervalMs" in options)) {
|
|
1035
|
+
return { payload: options.payload, intervalMs: options.intervalMs };
|
|
1036
|
+
}
|
|
1037
|
+
return { payload: options, intervalMs: undefined };
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
async set(id, scheduledTime, options) {
|
|
1042
|
+
const ts = typeof scheduledTime === "number" ? scheduledTime : scheduledTime.getTime();
|
|
1043
|
+
const { payload, intervalMs } = parseOptions(options);
|
|
1044
|
+
const response = await fetch(serviceUrl + "/timer/set", {
|
|
1045
|
+
method: "POST",
|
|
1046
|
+
headers: { "Content-Type": "application/json" },
|
|
1047
|
+
body: JSON.stringify({ timerName, id, scheduledTime: ts, payload, intervalMs }),
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
if (!response.ok) {
|
|
1051
|
+
const errorText = await response.text();
|
|
1052
|
+
throw new Error("Timer set failed: " + errorText);
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
async get(id) {
|
|
1057
|
+
const response = await fetch(serviceUrl + "/timer/get", {
|
|
1058
|
+
method: "POST",
|
|
1059
|
+
headers: { "Content-Type": "application/json" },
|
|
1060
|
+
body: JSON.stringify({ timerName, id }),
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
if (!response.ok) {
|
|
1064
|
+
const errorText = await response.text();
|
|
1065
|
+
throw new Error("Timer get failed: " + errorText);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const result = await response.json();
|
|
1069
|
+
return result.timer ?? null;
|
|
1070
|
+
},
|
|
1071
|
+
|
|
1072
|
+
async delete(id) {
|
|
1073
|
+
const response = await fetch(serviceUrl + "/timer/delete", {
|
|
1074
|
+
method: "POST",
|
|
1075
|
+
headers: { "Content-Type": "application/json" },
|
|
1076
|
+
body: JSON.stringify({ timerName, id }),
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
if (!response.ok) {
|
|
1080
|
+
const errorText = await response.text();
|
|
1081
|
+
throw new Error("Timer delete failed: " + errorText);
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
`;
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
1090
|
// ../emulator/dist/runtime/workflow-runtime.js
|
|
1091
1091
|
var WORKFLOW_RUNTIME_CODE;
|
|
1092
1092
|
var init_workflow_runtime = __esm({
|
|
@@ -1337,10 +1337,10 @@ function generateWrapperCode(config, mockServiceUrl, envVars) {
|
|
|
1337
1337
|
bindings.push(` ${bindingName}: initializeWorkflow("${workflowName}", "${mockServiceUrl}"),`);
|
|
1338
1338
|
}
|
|
1339
1339
|
}
|
|
1340
|
-
if (config.
|
|
1341
|
-
imports.push('import {
|
|
1342
|
-
for (const [bindingName,
|
|
1343
|
-
bindings.push(` ${bindingName}:
|
|
1340
|
+
if (config.timer) {
|
|
1341
|
+
imports.push('import { initializeTimer } from "__ploy_timer_runtime__";');
|
|
1342
|
+
for (const [bindingName, timerName] of Object.entries(config.timer)) {
|
|
1343
|
+
bindings.push(` ${bindingName}: initializeTimer("${timerName}", "${mockServiceUrl}"),`);
|
|
1344
1344
|
}
|
|
1345
1345
|
}
|
|
1346
1346
|
imports.push('import userWorker from "__ploy_user_worker__";');
|
|
@@ -1402,18 +1402,18 @@ function generateWrapperCode(config, mockServiceUrl, envVars) {
|
|
|
1402
1402
|
headers: { "Content-Type": "application/json" }
|
|
1403
1403
|
});
|
|
1404
1404
|
}` : "";
|
|
1405
|
-
const
|
|
1406
|
-
// Handle
|
|
1407
|
-
if (request.headers.get("X-Ploy-
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1411
|
-
if (
|
|
1405
|
+
const timerHandlerCode = config.timer ? `
|
|
1406
|
+
// Handle timer fire delivery
|
|
1407
|
+
if (request.headers.get("X-Ploy-Timer-Fire") === "true") {
|
|
1408
|
+
const timerId = request.headers.get("X-Ploy-Timer-Id");
|
|
1409
|
+
const timerName = request.headers.get("X-Ploy-Timer-Name");
|
|
1410
|
+
const timerTime = request.headers.get("X-Ploy-Timer-Time");
|
|
1411
|
+
if (timerId && userWorker.timer) {
|
|
1412
1412
|
const body = await request.json().catch(() => ({}));
|
|
1413
1413
|
try {
|
|
1414
|
-
await userWorker.
|
|
1415
|
-
id:
|
|
1416
|
-
scheduledTime: parseInt(
|
|
1414
|
+
await userWorker.timer({
|
|
1415
|
+
id: timerId,
|
|
1416
|
+
scheduledTime: parseInt(timerTime || "0", 10),
|
|
1417
1417
|
payload: body.payload,
|
|
1418
1418
|
intervalMs: body.intervalMs
|
|
1419
1419
|
}, injectedEnv, ctx);
|
|
@@ -1472,7 +1472,7 @@ export default {
|
|
|
1472
1472
|
async fetch(request, env, ctx) {
|
|
1473
1473
|
const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
|
|
1474
1474
|
${cronHandlerCode}
|
|
1475
|
-
${
|
|
1475
|
+
${timerHandlerCode}
|
|
1476
1476
|
${workflowHandlerCode}
|
|
1477
1477
|
${queueHandlerCode}
|
|
1478
1478
|
|
|
@@ -1520,8 +1520,8 @@ function createRuntimePlugin(_config) {
|
|
|
1520
1520
|
path: "__ploy_workflow_runtime__",
|
|
1521
1521
|
namespace: "ploy-runtime"
|
|
1522
1522
|
}));
|
|
1523
|
-
build2.onResolve({ filter: /^
|
|
1524
|
-
path: "
|
|
1523
|
+
build2.onResolve({ filter: /^__ploy_timer_runtime__$/ }, () => ({
|
|
1524
|
+
path: "__ploy_timer_runtime__",
|
|
1525
1525
|
namespace: "ploy-runtime"
|
|
1526
1526
|
}));
|
|
1527
1527
|
build2.onLoad({ filter: /^__ploy_db_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
@@ -1548,8 +1548,8 @@ function createRuntimePlugin(_config) {
|
|
|
1548
1548
|
contents: WORKFLOW_RUNTIME_CODE,
|
|
1549
1549
|
loader: "ts"
|
|
1550
1550
|
}));
|
|
1551
|
-
build2.onLoad({ filter: /^
|
|
1552
|
-
contents:
|
|
1551
|
+
build2.onLoad({ filter: /^__ploy_timer_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
1552
|
+
contents: TIMER_RUNTIME_CODE,
|
|
1553
1553
|
loader: "ts"
|
|
1554
1554
|
}));
|
|
1555
1555
|
}
|
|
@@ -1588,12 +1588,12 @@ async function bundleWorker(options) {
|
|
|
1588
1588
|
var NODE_BUILTINS;
|
|
1589
1589
|
var init_bundler = __esm({
|
|
1590
1590
|
"../emulator/dist/bundler/bundler.js"() {
|
|
1591
|
-
init_alarm_runtime();
|
|
1592
1591
|
init_cache_runtime();
|
|
1593
1592
|
init_db_runtime();
|
|
1594
1593
|
init_fs_runtime();
|
|
1595
1594
|
init_queue_runtime();
|
|
1596
1595
|
init_state_runtime();
|
|
1596
|
+
init_timer_runtime();
|
|
1597
1597
|
init_workflow_runtime();
|
|
1598
1598
|
NODE_BUILTINS = [
|
|
1599
1599
|
"assert",
|
|
@@ -1852,135 +1852,6 @@ var init_workerd_config = __esm({
|
|
|
1852
1852
|
"../emulator/dist/config/workerd-config.js"() {
|
|
1853
1853
|
}
|
|
1854
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
|
-
});
|
|
1984
1855
|
function parseCronField(field, min, max) {
|
|
1985
1856
|
const values = /* @__PURE__ */ new Set();
|
|
1986
1857
|
for (const part of field.split(",")) {
|
|
@@ -2495,7 +2366,7 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
2495
2366
|
fs: config.fs,
|
|
2496
2367
|
workflow: config.workflow,
|
|
2497
2368
|
cron: config.cron,
|
|
2498
|
-
|
|
2369
|
+
timer: config.timer,
|
|
2499
2370
|
auth: config.auth
|
|
2500
2371
|
});
|
|
2501
2372
|
});
|
|
@@ -3050,22 +2921,22 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
3050
2921
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
3051
2922
|
}
|
|
3052
2923
|
});
|
|
3053
|
-
app.get("/api/
|
|
2924
|
+
app.get("/api/timer/:binding/entries", (c) => {
|
|
3054
2925
|
const binding = c.req.param("binding");
|
|
3055
|
-
const
|
|
2926
|
+
const timerName = config.timer?.[binding];
|
|
3056
2927
|
const limit = parseInt(c.req.query("limit") ?? "20", 10);
|
|
3057
2928
|
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
3058
|
-
if (!
|
|
3059
|
-
return c.json({ error: "
|
|
2929
|
+
if (!timerName) {
|
|
2930
|
+
return c.json({ error: "Timer binding not found" }, 404);
|
|
3060
2931
|
}
|
|
3061
2932
|
try {
|
|
3062
2933
|
const db = dbManager2.emulatorDb;
|
|
3063
|
-
const total = db.prepare(`SELECT COUNT(*) as count FROM
|
|
2934
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM timer_entries WHERE timer_name = ?`).get(timerName).count;
|
|
3064
2935
|
const entries = db.prepare(`SELECT id, scheduled_time, payload, interval_ms, status, created_at
|
|
3065
|
-
FROM
|
|
3066
|
-
WHERE
|
|
2936
|
+
FROM timer_entries
|
|
2937
|
+
WHERE timer_name = ?
|
|
3067
2938
|
ORDER BY scheduled_time DESC
|
|
3068
|
-
LIMIT ? OFFSET ?`).all(
|
|
2939
|
+
LIMIT ? OFFSET ?`).all(timerName, limit, offset);
|
|
3069
2940
|
return c.json({
|
|
3070
2941
|
entries: entries.map((e) => ({
|
|
3071
2942
|
id: e.id,
|
|
@@ -3083,19 +2954,19 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
3083
2954
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
3084
2955
|
}
|
|
3085
2956
|
});
|
|
3086
|
-
app.get("/api/
|
|
2957
|
+
app.get("/api/timer/:binding/metrics", (c) => {
|
|
3087
2958
|
const binding = c.req.param("binding");
|
|
3088
|
-
const
|
|
3089
|
-
if (!
|
|
3090
|
-
return c.json({ error: "
|
|
2959
|
+
const timerName = config.timer?.[binding];
|
|
2960
|
+
if (!timerName) {
|
|
2961
|
+
return c.json({ error: "Timer binding not found" }, 404);
|
|
3091
2962
|
}
|
|
3092
2963
|
try {
|
|
3093
2964
|
const db = dbManager2.emulatorDb;
|
|
3094
2965
|
const metrics = { pending: 0, fired: 0 };
|
|
3095
2966
|
const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
|
|
3096
|
-
FROM
|
|
3097
|
-
WHERE
|
|
3098
|
-
GROUP BY status`).all(
|
|
2967
|
+
FROM timer_entries
|
|
2968
|
+
WHERE timer_name = ?
|
|
2969
|
+
GROUP BY status`).all(timerName);
|
|
3099
2970
|
for (const row of statusCounts) {
|
|
3100
2971
|
if (row.status === "pending") {
|
|
3101
2972
|
metrics.pending = row.count;
|
|
@@ -3108,24 +2979,24 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
3108
2979
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
3109
2980
|
}
|
|
3110
2981
|
});
|
|
3111
|
-
app.get("/api/
|
|
2982
|
+
app.get("/api/timer/:binding/executions", (c) => {
|
|
3112
2983
|
const binding = c.req.param("binding");
|
|
3113
|
-
const
|
|
2984
|
+
const timerName = config.timer?.[binding];
|
|
3114
2985
|
const limit = parseInt(c.req.query("limit") ?? "20", 10);
|
|
3115
|
-
if (!
|
|
3116
|
-
return c.json({ error: "
|
|
2986
|
+
if (!timerName) {
|
|
2987
|
+
return c.json({ error: "Timer binding not found" }, 404);
|
|
3117
2988
|
}
|
|
3118
2989
|
try {
|
|
3119
2990
|
const db = dbManager2.emulatorDb;
|
|
3120
|
-
const executions = db.prepare(`SELECT id,
|
|
3121
|
-
FROM
|
|
3122
|
-
WHERE
|
|
2991
|
+
const executions = db.prepare(`SELECT id, timer_id, scheduled_time, status, error, duration_ms, created_at
|
|
2992
|
+
FROM timer_executions
|
|
2993
|
+
WHERE timer_name = ?
|
|
3123
2994
|
ORDER BY created_at DESC
|
|
3124
|
-
LIMIT ?`).all(
|
|
2995
|
+
LIMIT ?`).all(timerName, limit);
|
|
3125
2996
|
return c.json({
|
|
3126
2997
|
executions: executions.map((e) => ({
|
|
3127
2998
|
id: e.id,
|
|
3128
|
-
|
|
2999
|
+
timerId: e.timer_id,
|
|
3129
3000
|
scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
|
|
3130
3001
|
status: e.status.toUpperCase(),
|
|
3131
3002
|
error: e.error,
|
|
@@ -3269,7 +3140,7 @@ function createDbHandler(getDatabase) {
|
|
|
3269
3140
|
return c.json(results);
|
|
3270
3141
|
}
|
|
3271
3142
|
if (method === "dump") {
|
|
3272
|
-
const buffer = db.
|
|
3143
|
+
const buffer = await db.dumpToBuffer();
|
|
3273
3144
|
return new Response(new Uint8Array(buffer), {
|
|
3274
3145
|
headers: {
|
|
3275
3146
|
"Content-Type": "application/octet-stream"
|
|
@@ -3625,6 +3496,135 @@ var init_state_service = __esm({
|
|
|
3625
3496
|
"../emulator/dist/services/state-service.js"() {
|
|
3626
3497
|
}
|
|
3627
3498
|
});
|
|
3499
|
+
|
|
3500
|
+
// ../emulator/dist/services/timer-service.js
|
|
3501
|
+
function createTimerHandlers(db) {
|
|
3502
|
+
const setHandler = async (c) => {
|
|
3503
|
+
try {
|
|
3504
|
+
const body = await c.req.json();
|
|
3505
|
+
const { timerName, id, scheduledTime, payload, intervalMs } = body;
|
|
3506
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3507
|
+
const scheduledTimeSec = Math.floor(scheduledTime / 1e3);
|
|
3508
|
+
db.prepare(`INSERT OR REPLACE INTO timer_entries (id, timer_name, scheduled_time, payload, interval_ms, status, created_at)
|
|
3509
|
+
VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(id, timerName, scheduledTimeSec, payload !== void 0 ? JSON.stringify(payload) : null, intervalMs ?? null, now);
|
|
3510
|
+
return c.json({ success: true });
|
|
3511
|
+
} catch (err) {
|
|
3512
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3513
|
+
return c.json({ success: false, error: message }, 500);
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
const getHandler = async (c) => {
|
|
3517
|
+
try {
|
|
3518
|
+
const body = await c.req.json();
|
|
3519
|
+
const { timerName, id } = body;
|
|
3520
|
+
const row = db.prepare(`SELECT id, scheduled_time, payload, interval_ms FROM timer_entries
|
|
3521
|
+
WHERE timer_name = ? AND id = ? AND status = 'pending'`).get(timerName, id);
|
|
3522
|
+
if (!row) {
|
|
3523
|
+
return c.json({ timer: null });
|
|
3524
|
+
}
|
|
3525
|
+
return c.json({
|
|
3526
|
+
timer: {
|
|
3527
|
+
id: row.id,
|
|
3528
|
+
scheduledTime: row.scheduled_time * 1e3,
|
|
3529
|
+
payload: row.payload ? JSON.parse(row.payload) : void 0,
|
|
3530
|
+
intervalMs: row.interval_ms ?? void 0
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
} catch (err) {
|
|
3534
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3535
|
+
return c.json({ success: false, error: message }, 500);
|
|
3536
|
+
}
|
|
3537
|
+
};
|
|
3538
|
+
const deleteHandler = async (c) => {
|
|
3539
|
+
try {
|
|
3540
|
+
const body = await c.req.json();
|
|
3541
|
+
const { timerName, id } = body;
|
|
3542
|
+
db.prepare(`DELETE FROM timer_entries WHERE timer_name = ? AND id = ?`).run(timerName, id);
|
|
3543
|
+
return c.json({ success: true });
|
|
3544
|
+
} catch (err) {
|
|
3545
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3546
|
+
return c.json({ success: false, error: message }, 500);
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
return {
|
|
3550
|
+
setHandler,
|
|
3551
|
+
getHandler,
|
|
3552
|
+
deleteHandler
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
function createTimerProcessor(db, workerUrl) {
|
|
3556
|
+
let interval = null;
|
|
3557
|
+
async function processTimers() {
|
|
3558
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3559
|
+
const rows = db.prepare(`SELECT id, timer_name, scheduled_time, payload, interval_ms
|
|
3560
|
+
FROM timer_entries
|
|
3561
|
+
WHERE status = 'pending' AND scheduled_time <= ?
|
|
3562
|
+
ORDER BY scheduled_time ASC
|
|
3563
|
+
LIMIT 50`).all(now);
|
|
3564
|
+
for (const row of rows) {
|
|
3565
|
+
const startTime = Date.now();
|
|
3566
|
+
try {
|
|
3567
|
+
const response = await fetch(workerUrl, {
|
|
3568
|
+
method: "POST",
|
|
3569
|
+
headers: {
|
|
3570
|
+
"Content-Type": "application/json",
|
|
3571
|
+
"X-Ploy-Timer-Fire": "true",
|
|
3572
|
+
"X-Ploy-Timer-Id": row.id,
|
|
3573
|
+
"X-Ploy-Timer-Name": row.timer_name,
|
|
3574
|
+
"X-Ploy-Timer-Time": String(row.scheduled_time * 1e3)
|
|
3575
|
+
},
|
|
3576
|
+
body: JSON.stringify({
|
|
3577
|
+
scheduledTime: row.scheduled_time * 1e3,
|
|
3578
|
+
payload: row.payload ? JSON.parse(row.payload) : void 0,
|
|
3579
|
+
intervalMs: row.interval_ms ?? void 0
|
|
3580
|
+
})
|
|
3581
|
+
});
|
|
3582
|
+
const durationMs = Date.now() - startTime;
|
|
3583
|
+
if (response.ok) {
|
|
3584
|
+
if (row.interval_ms) {
|
|
3585
|
+
const nextTimeSec = row.scheduled_time + Math.floor(row.interval_ms / 1e3);
|
|
3586
|
+
db.prepare(`UPDATE timer_entries SET scheduled_time = ? WHERE id = ? AND timer_name = ?`).run(nextTimeSec, row.id, row.timer_name);
|
|
3587
|
+
} else {
|
|
3588
|
+
db.prepare(`UPDATE timer_entries SET status = 'fired' WHERE id = ? AND timer_name = ?`).run(row.id, row.timer_name);
|
|
3589
|
+
}
|
|
3590
|
+
db.prepare(`INSERT INTO timer_executions (timer_id, timer_name, scheduled_time, status, duration_ms, created_at)
|
|
3591
|
+
VALUES (?, ?, ?, 'completed', ?, ?)`).run(row.id, row.timer_name, row.scheduled_time, durationMs, now);
|
|
3592
|
+
} else {
|
|
3593
|
+
db.prepare(`INSERT INTO timer_executions (timer_id, timer_name, scheduled_time, status, error, duration_ms, created_at)
|
|
3594
|
+
VALUES (?, ?, ?, 'failed', ?, ?, ?)`).run(row.id, row.timer_name, row.scheduled_time, `HTTP ${String(response.status)}`, durationMs, now);
|
|
3595
|
+
db.prepare(`UPDATE timer_entries SET scheduled_time = ? WHERE id = ? AND timer_name = ?`).run(now + 5, row.id, row.timer_name);
|
|
3596
|
+
}
|
|
3597
|
+
} catch (err) {
|
|
3598
|
+
const durationMs = Date.now() - startTime;
|
|
3599
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3600
|
+
db.prepare(`INSERT INTO timer_executions (timer_id, timer_name, scheduled_time, status, error, duration_ms, created_at)
|
|
3601
|
+
VALUES (?, ?, ?, 'failed', ?, ?, ?)`).run(row.id, row.timer_name, row.scheduled_time, errorMessage, durationMs, now);
|
|
3602
|
+
db.prepare(`UPDATE timer_entries SET scheduled_time = ? WHERE id = ? AND timer_name = ?`).run(now + 5, row.id, row.timer_name);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return {
|
|
3607
|
+
start() {
|
|
3608
|
+
if (interval) {
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
interval = setInterval(() => {
|
|
3612
|
+
processTimers().catch(() => {
|
|
3613
|
+
});
|
|
3614
|
+
}, 1e3);
|
|
3615
|
+
},
|
|
3616
|
+
stop() {
|
|
3617
|
+
if (interval) {
|
|
3618
|
+
clearInterval(interval);
|
|
3619
|
+
interval = null;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
var init_timer_service = __esm({
|
|
3625
|
+
"../emulator/dist/services/timer-service.js"() {
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
3628
|
function createWorkflowHandlers(db, workerUrl) {
|
|
3629
3629
|
const triggerHandler = async (c) => {
|
|
3630
3630
|
try {
|
|
@@ -3847,11 +3847,11 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
3847
3847
|
app.post("/fs/delete", fsHandlers.deleteHandler);
|
|
3848
3848
|
app.post("/fs/list", fsHandlers.listHandler);
|
|
3849
3849
|
}
|
|
3850
|
-
if (config.
|
|
3851
|
-
const
|
|
3852
|
-
app.post("/
|
|
3853
|
-
app.post("/
|
|
3854
|
-
app.post("/
|
|
3850
|
+
if (config.timer) {
|
|
3851
|
+
const timerHandlers = createTimerHandlers(dbManager2.emulatorDb);
|
|
3852
|
+
app.post("/timer/set", timerHandlers.setHandler);
|
|
3853
|
+
app.post("/timer/get", timerHandlers.getHandler);
|
|
3854
|
+
app.post("/timer/delete", timerHandlers.deleteHandler);
|
|
3855
3855
|
}
|
|
3856
3856
|
if (config.auth) {
|
|
3857
3857
|
const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
|
|
@@ -3885,7 +3885,6 @@ var DEFAULT_MOCK_SERVER_PORT;
|
|
|
3885
3885
|
var init_mock_server = __esm({
|
|
3886
3886
|
"../emulator/dist/services/mock-server.js"() {
|
|
3887
3887
|
init_paths();
|
|
3888
|
-
init_alarm_service();
|
|
3889
3888
|
init_auth_service();
|
|
3890
3889
|
init_cache_service();
|
|
3891
3890
|
init_dashboard_routes();
|
|
@@ -3893,20 +3892,82 @@ var init_mock_server = __esm({
|
|
|
3893
3892
|
init_fs_service();
|
|
3894
3893
|
init_queue_service();
|
|
3895
3894
|
init_state_service();
|
|
3895
|
+
init_timer_service();
|
|
3896
3896
|
init_workflow_service();
|
|
3897
3897
|
DEFAULT_MOCK_SERVER_PORT = 4003;
|
|
3898
3898
|
}
|
|
3899
3899
|
});
|
|
3900
|
+
function openDatabase(filepath) {
|
|
3901
|
+
const db = new DatabaseSync(filepath);
|
|
3902
|
+
return {
|
|
3903
|
+
prepare(sql) {
|
|
3904
|
+
const stmt = db.prepare(sql);
|
|
3905
|
+
return {
|
|
3906
|
+
run(...params) {
|
|
3907
|
+
const result = stmt.run(...params);
|
|
3908
|
+
return {
|
|
3909
|
+
changes: Number(result.changes),
|
|
3910
|
+
lastInsertRowid: result.lastInsertRowid
|
|
3911
|
+
};
|
|
3912
|
+
},
|
|
3913
|
+
get(...params) {
|
|
3914
|
+
return stmt.get(...params);
|
|
3915
|
+
},
|
|
3916
|
+
all(...params) {
|
|
3917
|
+
return stmt.all(...params);
|
|
3918
|
+
}
|
|
3919
|
+
};
|
|
3920
|
+
},
|
|
3921
|
+
exec(sql) {
|
|
3922
|
+
db.exec(sql);
|
|
3923
|
+
},
|
|
3924
|
+
close() {
|
|
3925
|
+
db.close();
|
|
3926
|
+
},
|
|
3927
|
+
pragma(pragma) {
|
|
3928
|
+
db.exec(`PRAGMA ${pragma}`);
|
|
3929
|
+
},
|
|
3930
|
+
transaction(fn) {
|
|
3931
|
+
return () => {
|
|
3932
|
+
db.exec("BEGIN");
|
|
3933
|
+
try {
|
|
3934
|
+
const result = fn();
|
|
3935
|
+
db.exec("COMMIT");
|
|
3936
|
+
return result;
|
|
3937
|
+
} catch (e) {
|
|
3938
|
+
db.exec("ROLLBACK");
|
|
3939
|
+
throw e;
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
},
|
|
3943
|
+
async dumpToBuffer() {
|
|
3944
|
+
const tempPath = join(tmpdir(), `ploy-dump-${randomUUID()}.db`);
|
|
3945
|
+
try {
|
|
3946
|
+
await backup(db, tempPath);
|
|
3947
|
+
return readFileSync(tempPath);
|
|
3948
|
+
} finally {
|
|
3949
|
+
try {
|
|
3950
|
+
unlinkSync(tempPath);
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
var init_sqlite_db = __esm({
|
|
3958
|
+
"../emulator/dist/utils/sqlite-db.js"() {
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3900
3961
|
function initializeDatabases(projectDir) {
|
|
3901
3962
|
const dataDir = ensureDataDir(projectDir);
|
|
3902
3963
|
const d1Databases = /* @__PURE__ */ new Map();
|
|
3903
|
-
const emulatorDb =
|
|
3964
|
+
const emulatorDb = openDatabase(join(dataDir, "emulator.db"));
|
|
3904
3965
|
emulatorDb.pragma("journal_mode = WAL");
|
|
3905
3966
|
emulatorDb.exec(EMULATOR_SCHEMA);
|
|
3906
3967
|
function getD1Database(bindingName) {
|
|
3907
3968
|
let db = d1Databases.get(bindingName);
|
|
3908
3969
|
if (!db) {
|
|
3909
|
-
db =
|
|
3970
|
+
db = openDatabase(join(dataDir, "db", `${bindingName}.db`));
|
|
3910
3971
|
db.pragma("journal_mode = WAL");
|
|
3911
3972
|
d1Databases.set(bindingName, db);
|
|
3912
3973
|
}
|
|
@@ -3929,6 +3990,7 @@ var EMULATOR_SCHEMA;
|
|
|
3929
3990
|
var init_sqlite = __esm({
|
|
3930
3991
|
"../emulator/dist/utils/sqlite.js"() {
|
|
3931
3992
|
init_paths();
|
|
3993
|
+
init_sqlite_db();
|
|
3932
3994
|
EMULATOR_SCHEMA = `
|
|
3933
3995
|
-- Queue messages table
|
|
3934
3996
|
CREATE TABLE IF NOT EXISTS queue_messages (
|
|
@@ -4065,26 +4127,26 @@ CREATE TABLE IF NOT EXISTS cron_executions (
|
|
|
4065
4127
|
CREATE INDEX IF NOT EXISTS idx_cron_executions_name
|
|
4066
4128
|
ON cron_executions(cron_name, triggered_at);
|
|
4067
4129
|
|
|
4068
|
-
--
|
|
4069
|
-
CREATE TABLE IF NOT EXISTS
|
|
4130
|
+
-- Timer entries table (durable timers, optionally recurring)
|
|
4131
|
+
CREATE TABLE IF NOT EXISTS timer_entries (
|
|
4070
4132
|
id TEXT NOT NULL,
|
|
4071
|
-
|
|
4133
|
+
timer_name TEXT NOT NULL,
|
|
4072
4134
|
scheduled_time INTEGER NOT NULL,
|
|
4073
4135
|
payload TEXT,
|
|
4074
4136
|
interval_ms INTEGER,
|
|
4075
4137
|
status TEXT DEFAULT 'pending',
|
|
4076
4138
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
4077
|
-
PRIMARY KEY (
|
|
4139
|
+
PRIMARY KEY (timer_name, id)
|
|
4078
4140
|
);
|
|
4079
4141
|
|
|
4080
|
-
CREATE INDEX IF NOT EXISTS
|
|
4081
|
-
ON
|
|
4142
|
+
CREATE INDEX IF NOT EXISTS idx_timer_entries_status_time
|
|
4143
|
+
ON timer_entries(status, scheduled_time);
|
|
4082
4144
|
|
|
4083
|
-
--
|
|
4084
|
-
CREATE TABLE IF NOT EXISTS
|
|
4145
|
+
-- Timer execution log
|
|
4146
|
+
CREATE TABLE IF NOT EXISTS timer_executions (
|
|
4085
4147
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4086
|
-
|
|
4087
|
-
|
|
4148
|
+
timer_id TEXT NOT NULL,
|
|
4149
|
+
timer_name TEXT NOT NULL,
|
|
4088
4150
|
scheduled_time INTEGER NOT NULL,
|
|
4089
4151
|
status TEXT DEFAULT 'pending',
|
|
4090
4152
|
error TEXT,
|
|
@@ -4092,8 +4154,8 @@ CREATE TABLE IF NOT EXISTS alarm_executions (
|
|
|
4092
4154
|
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
4093
4155
|
);
|
|
4094
4156
|
|
|
4095
|
-
CREATE INDEX IF NOT EXISTS
|
|
4096
|
-
ON
|
|
4157
|
+
CREATE INDEX IF NOT EXISTS idx_timer_executions_name
|
|
4158
|
+
ON timer_executions(timer_name, created_at);
|
|
4097
4159
|
`;
|
|
4098
4160
|
}
|
|
4099
4161
|
});
|
|
@@ -4110,10 +4172,10 @@ var init_emulator = __esm({
|
|
|
4110
4172
|
init_env();
|
|
4111
4173
|
init_ploy_config2();
|
|
4112
4174
|
init_workerd_config();
|
|
4113
|
-
init_alarm_service();
|
|
4114
4175
|
init_cron_service();
|
|
4115
4176
|
init_mock_server();
|
|
4116
4177
|
init_queue_service();
|
|
4178
|
+
init_timer_service();
|
|
4117
4179
|
init_logger();
|
|
4118
4180
|
init_paths();
|
|
4119
4181
|
init_sqlite();
|
|
@@ -4128,7 +4190,7 @@ var init_emulator = __esm({
|
|
|
4128
4190
|
fileWatcher = null;
|
|
4129
4191
|
queueProcessor = null;
|
|
4130
4192
|
cronScheduler = null;
|
|
4131
|
-
|
|
4193
|
+
timerProcessor = null;
|
|
4132
4194
|
resolvedEnvVars = {};
|
|
4133
4195
|
constructor(options = {}) {
|
|
4134
4196
|
const port = options.port ?? 8787;
|
|
@@ -4202,11 +4264,11 @@ var init_emulator = __esm({
|
|
|
4202
4264
|
this.cronScheduler.start();
|
|
4203
4265
|
debug("Cron scheduler started", this.options.verbose);
|
|
4204
4266
|
}
|
|
4205
|
-
if (this.config.
|
|
4267
|
+
if (this.config.timer) {
|
|
4206
4268
|
const workerUrl2 = `http://${this.options.host}:${String(this.options.port)}`;
|
|
4207
|
-
this.
|
|
4208
|
-
this.
|
|
4209
|
-
debug("
|
|
4269
|
+
this.timerProcessor = createTimerProcessor(this.dbManager.emulatorDb, workerUrl2);
|
|
4270
|
+
this.timerProcessor.start();
|
|
4271
|
+
debug("Timer processor started", this.options.verbose);
|
|
4210
4272
|
}
|
|
4211
4273
|
success(`Emulator running at http://${this.options.host}:${String(this.options.port)}`);
|
|
4212
4274
|
log(` Dashboard: http://${this.options.host}:${String(this.mockServer.port)}`);
|
|
@@ -4233,8 +4295,8 @@ var init_emulator = __esm({
|
|
|
4233
4295
|
log(` Cron: ${name} = ${expr}`);
|
|
4234
4296
|
}
|
|
4235
4297
|
}
|
|
4236
|
-
if (this.config.
|
|
4237
|
-
log(`
|
|
4298
|
+
if (this.config.timer) {
|
|
4299
|
+
log(` Timer bindings: ${Object.keys(this.config.timer).join(", ")}`);
|
|
4238
4300
|
}
|
|
4239
4301
|
this.setupSignalHandlers();
|
|
4240
4302
|
} catch (err) {
|
|
@@ -4364,9 +4426,9 @@ var init_emulator = __esm({
|
|
|
4364
4426
|
this.cronScheduler.stop();
|
|
4365
4427
|
this.cronScheduler = null;
|
|
4366
4428
|
}
|
|
4367
|
-
if (this.
|
|
4368
|
-
this.
|
|
4369
|
-
this.
|
|
4429
|
+
if (this.timerProcessor) {
|
|
4430
|
+
this.timerProcessor.stop();
|
|
4431
|
+
this.timerProcessor = null;
|
|
4370
4432
|
}
|
|
4371
4433
|
if (this.queueProcessor) {
|
|
4372
4434
|
this.queueProcessor.stop();
|
|
@@ -6272,12 +6334,12 @@ ${varProps}
|
|
|
6272
6334
|
}
|
|
6273
6335
|
}
|
|
6274
6336
|
}
|
|
6275
|
-
if (config.
|
|
6276
|
-
const
|
|
6277
|
-
if (
|
|
6278
|
-
imports.push("
|
|
6279
|
-
for (const bindingName of
|
|
6280
|
-
properties.push(` ${bindingName}:
|
|
6337
|
+
if (config.timer) {
|
|
6338
|
+
const timerKeys = Object.keys(config.timer);
|
|
6339
|
+
if (timerKeys.length > 0) {
|
|
6340
|
+
imports.push("TimerBinding");
|
|
6341
|
+
for (const bindingName of timerKeys) {
|
|
6342
|
+
properties.push(` ${bindingName}: TimerBinding;`);
|
|
6281
6343
|
}
|
|
6282
6344
|
}
|
|
6283
6345
|
}
|
|
@@ -6322,7 +6384,7 @@ async function typesCommand(options = {}) {
|
|
|
6322
6384
|
console.error("Error: ploy.yaml not found in current directory");
|
|
6323
6385
|
process.exit(1);
|
|
6324
6386
|
}
|
|
6325
|
-
const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow || config.fs || config.
|
|
6387
|
+
const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow || config.fs || config.timer || config.cron;
|
|
6326
6388
|
if (!hasBindings2) {
|
|
6327
6389
|
console.log("No bindings found in ploy.yaml. Generating empty Env.");
|
|
6328
6390
|
}
|