@meetploy/cli 1.12.2 → 1.14.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { mkdir, writeFile, readFile, chmod, access } from 'fs/promises';
4
- import { join, dirname, relative } from 'path';
4
+ import { join, dirname, relative, basename } from 'path';
5
5
  import { existsSync, readFileSync, writeFileSync, unlinkSync, readFile as readFile$1, mkdirSync } from 'fs';
6
6
  import { promisify } from 'util';
7
7
  import { parse } from 'yaml';
@@ -137,15 +137,12 @@ function getCompatibilityFlags(config) {
137
137
  return [...new Set(allFlags)];
138
138
  }
139
139
  function getCompatibilityDate(config) {
140
- return config.compatibility_date || DEFAULT_COMPATIBILITY_DATE;
140
+ return config.compatibility_date ?? DEFAULT_COMPATIBILITY_DATE;
141
141
  }
142
142
  function validateBindings(bindings, bindingType, configFile) {
143
143
  if (bindings === void 0) {
144
144
  return;
145
145
  }
146
- if (typeof bindings !== "object" || bindings === null) {
147
- throw new Error(`'${bindingType}' in ${configFile} must be an object`);
148
- }
149
146
  for (const [bindingName, resourceName] of Object.entries(bindings)) {
150
147
  if (!BINDING_NAME_REGEX.test(bindingName)) {
151
148
  throw new Error(`Invalid ${bindingType} binding name '${bindingName}' in ${configFile}. Binding names must be uppercase with underscores (e.g., DB, USERS_DB)`);
@@ -196,6 +193,8 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
196
193
  validatedConfig.main = validateRelativePath(config.main, "main", configFile);
197
194
  validateBindings(config.db, "db", configFile);
198
195
  validateBindings(config.queue, "queue", configFile);
196
+ validateBindings(config.cache, "cache", configFile);
197
+ validateBindings(config.state, "state", configFile);
199
198
  validateBindings(config.workflow, "workflow", configFile);
200
199
  if (config.ai !== void 0 && typeof config.ai !== "boolean") {
201
200
  throw new Error(`'ai' in ${configFile} must be a boolean`);
@@ -222,9 +221,6 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
222
221
  }
223
222
  }
224
223
  if (config.auth !== void 0) {
225
- if (typeof config.auth !== "object" || config.auth === null) {
226
- throw new Error(`'auth' in ${configFile} must be an object`);
227
- }
228
224
  if (!config.auth.binding) {
229
225
  throw new Error(`'auth.binding' is required in ${configFile} when auth is configured`);
230
226
  }
@@ -238,7 +234,7 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
238
234
  return validatedConfig;
239
235
  }
240
236
  async function readPloyConfig(projectDir, configPath) {
241
- const configFile = configPath || "ploy.yaml";
237
+ const configFile = configPath ?? "ploy.yaml";
242
238
  const fullPath = join(projectDir, configFile);
243
239
  try {
244
240
  const content = await readFileAsync(fullPath, "utf-8");
@@ -253,7 +249,7 @@ async function readPloyConfig(projectDir, configPath) {
253
249
  }
254
250
  }
255
251
  function readPloyConfigSync(projectDir, configPath) {
256
- const configFile = configPath || "ploy.yaml";
252
+ const configFile = configPath ?? "ploy.yaml";
257
253
  const fullPath = join(projectDir, configFile);
258
254
  if (!existsSync(fullPath)) {
259
255
  throw new Error(`Config file not found: ${fullPath}`);
@@ -262,7 +258,7 @@ function readPloyConfigSync(projectDir, configPath) {
262
258
  return parse(content);
263
259
  }
264
260
  async function readAndValidatePloyConfig(projectDir, configPath, validationOptions) {
265
- const configFile = configPath || "ploy.yaml";
261
+ const configFile = configPath ?? "ploy.yaml";
266
262
  const config = await readPloyConfig(projectDir, configPath);
267
263
  if (!config) {
268
264
  return null;
@@ -270,12 +266,12 @@ async function readAndValidatePloyConfig(projectDir, configPath, validationOptio
270
266
  return validatePloyConfig(config, configFile, validationOptions);
271
267
  }
272
268
  function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions) {
273
- const configFile = configPath || "ploy.yaml";
269
+ const configFile = configPath ?? "ploy.yaml";
274
270
  const config = readPloyConfigSync(projectDir, configPath);
275
271
  return validatePloyConfig(config, configFile, validationOptions);
276
272
  }
277
273
  function hasBindings(config) {
278
- return !!(config.db || config.queue || config.workflow || config.ai || config.auth);
274
+ return !!(config.db || config.queue || config.cache || config.state || config.workflow || config.ai || config.auth);
279
275
  }
280
276
  function getWorkerEntryPoint(projectDir, config) {
281
277
  if (config.main) {
@@ -331,6 +327,66 @@ var init_cli = __esm({
331
327
  }
332
328
  });
333
329
 
330
+ // ../emulator/dist/runtime/cache-runtime.js
331
+ var CACHE_RUNTIME_CODE;
332
+ var init_cache_runtime = __esm({
333
+ "../emulator/dist/runtime/cache-runtime.js"() {
334
+ CACHE_RUNTIME_CODE = `
335
+ interface CacheBinding {
336
+ get: (key: string) => Promise<string | null>;
337
+ set: (key: string, value: string, opts: { ttl: number }) => Promise<void>;
338
+ delete: (key: string) => Promise<void>;
339
+ }
340
+
341
+ export function initializeCache(cacheName: string, serviceUrl: string): CacheBinding {
342
+ return {
343
+ async get(key: string): Promise<string | null> {
344
+ const response = await fetch(serviceUrl + "/cache/get", {
345
+ method: "POST",
346
+ headers: { "Content-Type": "application/json" },
347
+ body: JSON.stringify({ cacheName, key }),
348
+ });
349
+
350
+ if (!response.ok) {
351
+ const errorText = await response.text();
352
+ throw new Error("Cache get failed: " + errorText);
353
+ }
354
+
355
+ const result = await response.json();
356
+ return result.value ?? null;
357
+ },
358
+
359
+ async set(key: string, value: string, opts: { ttl: number }): Promise<void> {
360
+ const response = await fetch(serviceUrl + "/cache/set", {
361
+ method: "POST",
362
+ headers: { "Content-Type": "application/json" },
363
+ body: JSON.stringify({ cacheName, key, value, ttl: opts.ttl }),
364
+ });
365
+
366
+ if (!response.ok) {
367
+ const errorText = await response.text();
368
+ throw new Error("Cache set failed: " + errorText);
369
+ }
370
+ },
371
+
372
+ async delete(key: string): Promise<void> {
373
+ const response = await fetch(serviceUrl + "/cache/delete", {
374
+ method: "POST",
375
+ headers: { "Content-Type": "application/json" },
376
+ body: JSON.stringify({ cacheName, key }),
377
+ });
378
+
379
+ if (!response.ok) {
380
+ const errorText = await response.text();
381
+ throw new Error("Cache delete failed: " + errorText);
382
+ }
383
+ },
384
+ };
385
+ }
386
+ `;
387
+ }
388
+ });
389
+
334
390
  // ../shared/dist/d1-runtime.js
335
391
  var DB_RUNTIME_CODE, DB_RUNTIME_CODE_PRODUCTION;
336
392
  var init_d1_runtime = __esm({
@@ -716,6 +772,66 @@ export function initializeQueue<T = unknown>(queueName: string, serviceUrl: stri
716
772
  }
717
773
  });
718
774
 
775
+ // ../emulator/dist/runtime/state-runtime.js
776
+ var STATE_RUNTIME_CODE;
777
+ var init_state_runtime = __esm({
778
+ "../emulator/dist/runtime/state-runtime.js"() {
779
+ STATE_RUNTIME_CODE = `
780
+ interface StateBinding {
781
+ get: (key: string) => Promise<string | null>;
782
+ set: (key: string, value: string) => Promise<void>;
783
+ delete: (key: string) => Promise<void>;
784
+ }
785
+
786
+ export function initializeState(stateName: string, serviceUrl: string): StateBinding {
787
+ return {
788
+ async get(key: string): Promise<string | null> {
789
+ const response = await fetch(serviceUrl + "/state/get", {
790
+ method: "POST",
791
+ headers: { "Content-Type": "application/json" },
792
+ body: JSON.stringify({ stateName, key }),
793
+ });
794
+
795
+ if (!response.ok) {
796
+ const errorText = await response.text();
797
+ throw new Error("State get failed: " + errorText);
798
+ }
799
+
800
+ const result = await response.json();
801
+ return result.value ?? null;
802
+ },
803
+
804
+ async set(key: string, value: string): Promise<void> {
805
+ const response = await fetch(serviceUrl + "/state/set", {
806
+ method: "POST",
807
+ headers: { "Content-Type": "application/json" },
808
+ body: JSON.stringify({ stateName, key, value }),
809
+ });
810
+
811
+ if (!response.ok) {
812
+ const errorText = await response.text();
813
+ throw new Error("State set failed: " + errorText);
814
+ }
815
+ },
816
+
817
+ async delete(key: string): Promise<void> {
818
+ const response = await fetch(serviceUrl + "/state/delete", {
819
+ method: "POST",
820
+ headers: { "Content-Type": "application/json" },
821
+ body: JSON.stringify({ stateName, key }),
822
+ });
823
+
824
+ if (!response.ok) {
825
+ const errorText = await response.text();
826
+ throw new Error("State delete failed: " + errorText);
827
+ }
828
+ },
829
+ };
830
+ }
831
+ `;
832
+ }
833
+ });
834
+
719
835
  // ../emulator/dist/runtime/workflow-runtime.js
720
836
  var WORKFLOW_RUNTIME_CODE;
721
837
  var init_workflow_runtime = __esm({
@@ -942,6 +1058,18 @@ function generateWrapperCode(config, mockServiceUrl) {
942
1058
  bindings.push(` ${bindingName}: initializeQueue("${queueName}", "${mockServiceUrl}"),`);
943
1059
  }
944
1060
  }
1061
+ if (config.cache) {
1062
+ imports.push('import { initializeCache } from "__ploy_cache_runtime__";');
1063
+ for (const [bindingName, cacheName] of Object.entries(config.cache)) {
1064
+ bindings.push(` ${bindingName}: initializeCache("${cacheName}", "${mockServiceUrl}"),`);
1065
+ }
1066
+ }
1067
+ if (config.state) {
1068
+ imports.push('import { initializeState } from "__ploy_state_runtime__";');
1069
+ for (const [bindingName, stateName] of Object.entries(config.state)) {
1070
+ bindings.push(` ${bindingName}: initializeState("${stateName}", "${mockServiceUrl}"),`);
1071
+ }
1072
+ }
945
1073
  if (config.workflow) {
946
1074
  imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
947
1075
  for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
@@ -1045,6 +1173,14 @@ function createRuntimePlugin(_config) {
1045
1173
  path: "__ploy_queue_runtime__",
1046
1174
  namespace: "ploy-runtime"
1047
1175
  }));
1176
+ build2.onResolve({ filter: /^__ploy_cache_runtime__$/ }, () => ({
1177
+ path: "__ploy_cache_runtime__",
1178
+ namespace: "ploy-runtime"
1179
+ }));
1180
+ build2.onResolve({ filter: /^__ploy_state_runtime__$/ }, () => ({
1181
+ path: "__ploy_state_runtime__",
1182
+ namespace: "ploy-runtime"
1183
+ }));
1048
1184
  build2.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
1049
1185
  path: "__ploy_workflow_runtime__",
1050
1186
  namespace: "ploy-runtime"
@@ -1057,6 +1193,14 @@ function createRuntimePlugin(_config) {
1057
1193
  contents: QUEUE_RUNTIME_CODE,
1058
1194
  loader: "ts"
1059
1195
  }));
1196
+ build2.onLoad({ filter: /^__ploy_cache_runtime__$/, namespace: "ploy-runtime" }, () => ({
1197
+ contents: CACHE_RUNTIME_CODE,
1198
+ loader: "ts"
1199
+ }));
1200
+ build2.onLoad({ filter: /^__ploy_state_runtime__$/, namespace: "ploy-runtime" }, () => ({
1201
+ contents: STATE_RUNTIME_CODE,
1202
+ loader: "ts"
1203
+ }));
1060
1204
  build2.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
1061
1205
  contents: WORKFLOW_RUNTIME_CODE,
1062
1206
  loader: "ts"
@@ -1097,8 +1241,10 @@ async function bundleWorker(options) {
1097
1241
  var NODE_BUILTINS;
1098
1242
  var init_bundler = __esm({
1099
1243
  "../emulator/dist/bundler/bundler.js"() {
1244
+ init_cache_runtime();
1100
1245
  init_db_runtime();
1101
1246
  init_queue_runtime();
1247
+ init_state_runtime();
1102
1248
  init_workflow_runtime();
1103
1249
  NODE_BUILTINS = [
1104
1250
  "assert",
@@ -1156,9 +1302,6 @@ function log(message) {
1156
1302
  function success(message) {
1157
1303
  console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.green}[ploy]${COLORS.reset} ${message}`);
1158
1304
  }
1159
- function warn(message) {
1160
- console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}[ploy]${COLORS.reset} ${message}`);
1161
- }
1162
1305
  function error(message) {
1163
1306
  console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
1164
1307
  }
@@ -1181,6 +1324,22 @@ var init_logger = __esm({
1181
1324
  };
1182
1325
  }
1183
1326
  });
1327
+ function isIgnored(filePath) {
1328
+ const name = basename(filePath);
1329
+ if (name.startsWith(".")) {
1330
+ return true;
1331
+ }
1332
+ if (IGNORED_DIRS.has(name)) {
1333
+ return true;
1334
+ }
1335
+ if (IGNORED_FILES.has(name)) {
1336
+ return true;
1337
+ }
1338
+ if (name.endsWith(".log")) {
1339
+ return true;
1340
+ }
1341
+ return false;
1342
+ }
1184
1343
  function createFileWatcher(srcDir, onRebuild) {
1185
1344
  let watcher = null;
1186
1345
  let debounceTimer = null;
@@ -1213,25 +1372,11 @@ function createFileWatcher(srcDir, onRebuild) {
1213
1372
  watcher = watch(srcDir, {
1214
1373
  persistent: true,
1215
1374
  ignoreInitial: true,
1216
- ignored: [
1217
- "**/node_modules/**",
1218
- "**/dist/**",
1219
- "**/.*",
1220
- "**/.*/**",
1221
- "**/coverage/**",
1222
- "**/build/**",
1223
- "**/*.log",
1224
- "**/pnpm-lock.yaml",
1225
- "**/package-lock.json",
1226
- "**/yarn.lock"
1227
- ]
1375
+ ignored: isIgnored
1228
1376
  });
1229
1377
  watcher.on("error", (err) => {
1230
1378
  const error2 = err;
1231
- if (error2.code === "EMFILE") {
1232
- warn("Warning: Too many open files. Some file changes may not be detected.");
1233
- warn("Consider increasing your system's file descriptor limit (ulimit -n).");
1234
- } else {
1379
+ if (error2.code !== "EMFILE") {
1235
1380
  error(`File watcher error: ${error2.message || String(err)}`);
1236
1381
  }
1237
1382
  });
@@ -1263,9 +1408,16 @@ function createFileWatcher(srcDir, onRebuild) {
1263
1408
  }
1264
1409
  };
1265
1410
  }
1411
+ var IGNORED_DIRS, IGNORED_FILES;
1266
1412
  var init_watcher = __esm({
1267
1413
  "../emulator/dist/bundler/watcher.js"() {
1268
1414
  init_logger();
1415
+ IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", "build"]);
1416
+ IGNORED_FILES = /* @__PURE__ */ new Set([
1417
+ "pnpm-lock.yaml",
1418
+ "package-lock.json",
1419
+ "yarn.lock"
1420
+ ]);
1269
1421
  }
1270
1422
  });
1271
1423
 
@@ -1587,6 +1739,40 @@ var init_auth_service = __esm({
1587
1739
  SESSION_TOKEN_EXPIRY = 7 * 24 * 60 * 60;
1588
1740
  }
1589
1741
  });
1742
+
1743
+ // ../emulator/dist/services/cache-service.js
1744
+ function createCacheHandlers(db) {
1745
+ const getHandler = async (c) => {
1746
+ const body = await c.req.json();
1747
+ const { cacheName, key } = body;
1748
+ const now = Math.floor(Date.now() / 1e3);
1749
+ const row = db.prepare(`SELECT value FROM cache_entries WHERE cache_name = ? AND key = ? AND expires_at > ?`).get(cacheName, key, now);
1750
+ return c.json({ value: row?.value ?? null });
1751
+ };
1752
+ const setHandler = async (c) => {
1753
+ const body = await c.req.json();
1754
+ const { cacheName, key, value, ttl } = body;
1755
+ const now = Math.floor(Date.now() / 1e3);
1756
+ const expiresAt = now + ttl;
1757
+ db.prepare(`INSERT OR REPLACE INTO cache_entries (cache_name, key, value, expires_at) VALUES (?, ?, ?, ?)`).run(cacheName, key, value, expiresAt);
1758
+ return c.json({ success: true });
1759
+ };
1760
+ const deleteHandler = async (c) => {
1761
+ const body = await c.req.json();
1762
+ const { cacheName, key } = body;
1763
+ db.prepare(`DELETE FROM cache_entries WHERE cache_name = ? AND key = ?`).run(cacheName, key);
1764
+ return c.json({ success: true });
1765
+ };
1766
+ return {
1767
+ getHandler,
1768
+ setHandler,
1769
+ deleteHandler
1770
+ };
1771
+ }
1772
+ var init_cache_service = __esm({
1773
+ "../emulator/dist/services/cache-service.js"() {
1774
+ }
1775
+ });
1590
1776
  function findDashboardDistPath() {
1591
1777
  const possiblePaths = [
1592
1778
  join(__dirname, "dashboard-dist"),
@@ -1614,6 +1800,8 @@ function createDashboardRoutes(app, dbManager2, config) {
1614
1800
  return c.json({
1615
1801
  db: config.db,
1616
1802
  queue: config.queue,
1803
+ cache: config.cache,
1804
+ state: config.state,
1617
1805
  workflow: config.workflow,
1618
1806
  auth: config.auth
1619
1807
  });
@@ -1954,6 +2142,71 @@ function createDashboardRoutes(app, dbManager2, config) {
1954
2142
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1955
2143
  }
1956
2144
  });
2145
+ app.get("/api/cache/:binding/entries", (c) => {
2146
+ const binding = c.req.param("binding");
2147
+ const cacheName = config.cache?.[binding];
2148
+ const limit = parseInt(c.req.query("limit") || "20", 10);
2149
+ const offset = parseInt(c.req.query("offset") || "0", 10);
2150
+ const now = Math.floor(Date.now() / 1e3);
2151
+ if (!cacheName) {
2152
+ return c.json({ error: "Cache binding not found" }, 404);
2153
+ }
2154
+ try {
2155
+ const db = dbManager2.emulatorDb;
2156
+ const total = db.prepare(`SELECT COUNT(*) as count FROM cache_entries
2157
+ WHERE cache_name = ? AND expires_at > ?`).get(cacheName, now).count;
2158
+ const entries = db.prepare(`SELECT key, value, expires_at
2159
+ FROM cache_entries
2160
+ WHERE cache_name = ? AND expires_at > ?
2161
+ ORDER BY key ASC
2162
+ LIMIT ? OFFSET ?`).all(cacheName, now, limit, offset);
2163
+ return c.json({
2164
+ entries: entries.map((e) => ({
2165
+ key: e.key,
2166
+ value: e.value,
2167
+ ttlSeconds: Math.max(0, e.expires_at - now),
2168
+ expiresAt: new Date(e.expires_at * 1e3).toISOString()
2169
+ })),
2170
+ total,
2171
+ limit,
2172
+ offset
2173
+ });
2174
+ } catch (err) {
2175
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2176
+ }
2177
+ });
2178
+ app.get("/api/state/:binding/entries", (c) => {
2179
+ const binding = c.req.param("binding");
2180
+ const stateName = config.state?.[binding];
2181
+ const limit = parseInt(c.req.query("limit") || "20", 10);
2182
+ const offset = parseInt(c.req.query("offset") || "0", 10);
2183
+ const search = c.req.query("search") || "";
2184
+ if (!stateName) {
2185
+ return c.json({ error: "State binding not found" }, 404);
2186
+ }
2187
+ try {
2188
+ const db = dbManager2.emulatorDb;
2189
+ const fetchLimit = limit + 1;
2190
+ const entries = search ? db.prepare(`SELECT key, value
2191
+ FROM state_entries
2192
+ WHERE state_name = ? AND key LIKE ?
2193
+ ORDER BY key ASC
2194
+ LIMIT ? OFFSET ?`).all(stateName, `%${search}%`, fetchLimit, offset) : db.prepare(`SELECT key, value
2195
+ FROM state_entries
2196
+ WHERE state_name = ?
2197
+ ORDER BY key ASC
2198
+ LIMIT ? OFFSET ?`).all(stateName, fetchLimit, offset);
2199
+ const hasMore = entries.length > limit;
2200
+ return c.json({
2201
+ entries: entries.slice(0, limit),
2202
+ hasMore,
2203
+ limit,
2204
+ offset
2205
+ });
2206
+ } catch (err) {
2207
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2208
+ }
2209
+ });
1957
2210
  app.get("/api/workflow/:binding/executions", (c) => {
1958
2211
  const binding = c.req.param("binding");
1959
2212
  const workflowConfig = config.workflow?.[binding];
@@ -2381,6 +2634,37 @@ var init_queue_service = __esm({
2381
2634
  "../emulator/dist/services/queue-service.js"() {
2382
2635
  }
2383
2636
  });
2637
+
2638
+ // ../emulator/dist/services/state-service.js
2639
+ function createStateHandlers(db) {
2640
+ const getHandler = async (c) => {
2641
+ const body = await c.req.json();
2642
+ const { stateName, key } = body;
2643
+ const row = db.prepare(`SELECT value FROM state_entries WHERE state_name = ? AND key = ?`).get(stateName, key);
2644
+ return c.json({ value: row?.value ?? null });
2645
+ };
2646
+ const setHandler = async (c) => {
2647
+ const body = await c.req.json();
2648
+ const { stateName, key, value } = body;
2649
+ db.prepare(`INSERT OR REPLACE INTO state_entries (state_name, key, value) VALUES (?, ?, ?)`).run(stateName, key, value);
2650
+ return c.json({ success: true });
2651
+ };
2652
+ const deleteHandler = async (c) => {
2653
+ const body = await c.req.json();
2654
+ const { stateName, key } = body;
2655
+ db.prepare(`DELETE FROM state_entries WHERE state_name = ? AND key = ?`).run(stateName, key);
2656
+ return c.json({ success: true });
2657
+ };
2658
+ return {
2659
+ getHandler,
2660
+ setHandler,
2661
+ deleteHandler
2662
+ };
2663
+ }
2664
+ var init_state_service = __esm({
2665
+ "../emulator/dist/services/state-service.js"() {
2666
+ }
2667
+ });
2384
2668
  function createWorkflowHandlers(db, workerUrl) {
2385
2669
  const triggerHandler = async (c) => {
2386
2670
  try {
@@ -2583,6 +2867,18 @@ async function startMockServer(dbManager2, config, options = {}) {
2583
2867
  app.post("/workflow/complete", workflowHandlers.completeHandler);
2584
2868
  app.post("/workflow/fail", workflowHandlers.failHandler);
2585
2869
  }
2870
+ if (config.cache) {
2871
+ const cacheHandlers = createCacheHandlers(dbManager2.emulatorDb);
2872
+ app.post("/cache/get", cacheHandlers.getHandler);
2873
+ app.post("/cache/set", cacheHandlers.setHandler);
2874
+ app.post("/cache/delete", cacheHandlers.deleteHandler);
2875
+ }
2876
+ if (config.state) {
2877
+ const stateHandlers = createStateHandlers(dbManager2.emulatorDb);
2878
+ app.post("/state/get", stateHandlers.getHandler);
2879
+ app.post("/state/set", stateHandlers.setHandler);
2880
+ app.post("/state/delete", stateHandlers.deleteHandler);
2881
+ }
2586
2882
  if (config.auth) {
2587
2883
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
2588
2884
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -2603,7 +2899,9 @@ async function startMockServer(dbManager2, config, options = {}) {
2603
2899
  resolve({
2604
2900
  port: info.port,
2605
2901
  close: () => new Promise((res) => {
2606
- server.close(() => res());
2902
+ server.close(() => {
2903
+ res();
2904
+ });
2607
2905
  })
2608
2906
  });
2609
2907
  });
@@ -2613,9 +2911,11 @@ var DEFAULT_MOCK_SERVER_PORT;
2613
2911
  var init_mock_server = __esm({
2614
2912
  "../emulator/dist/services/mock-server.js"() {
2615
2913
  init_auth_service();
2914
+ init_cache_service();
2616
2915
  init_dashboard_routes();
2617
2916
  init_db_service();
2618
2917
  init_queue_service();
2918
+ init_state_service();
2619
2919
  init_workflow_service();
2620
2920
  DEFAULT_MOCK_SERVER_PORT = 4003;
2621
2921
  }
@@ -2772,6 +3072,23 @@ CREATE TABLE IF NOT EXISTS auth_settings (
2772
3072
 
2773
3073
  -- Insert default settings if not exists
2774
3074
  INSERT OR IGNORE INTO auth_settings (id) VALUES (1);
3075
+
3076
+ -- Cache entries table
3077
+ CREATE TABLE IF NOT EXISTS cache_entries (
3078
+ cache_name TEXT NOT NULL,
3079
+ key TEXT NOT NULL,
3080
+ value TEXT NOT NULL,
3081
+ expires_at INTEGER NOT NULL,
3082
+ PRIMARY KEY (cache_name, key)
3083
+ );
3084
+
3085
+ -- State entries table (durable key-value store, no expiry)
3086
+ CREATE TABLE IF NOT EXISTS state_entries (
3087
+ state_name TEXT NOT NULL,
3088
+ key TEXT NOT NULL,
3089
+ value TEXT NOT NULL,
3090
+ PRIMARY KEY (state_name, key)
3091
+ );
2775
3092
  `;
2776
3093
  }
2777
3094
  });
@@ -2872,6 +3189,9 @@ var init_emulator = __esm({
2872
3189
  if (this.config.queue) {
2873
3190
  log(` Queue bindings: ${Object.keys(this.config.queue).join(", ")}`);
2874
3191
  }
3192
+ if (this.config.cache) {
3193
+ log(` Cache bindings: ${Object.keys(this.config.cache).join(", ")}`);
3194
+ }
2875
3195
  if (this.config.workflow) {
2876
3196
  log(` Workflow bindings: ${Object.keys(this.config.workflow).join(", ")}`);
2877
3197
  }
@@ -2902,7 +3222,7 @@ var init_emulator = __esm({
2902
3222
  const workerdBin = this.findWorkerdBinary();
2903
3223
  log(`[ploy] Using workerd binary: ${workerdBin}`);
2904
3224
  debug(`Starting workerd: ${workerdBin} ${args2.join(" ")}`, this.options.verbose);
2905
- return await new Promise((resolve, reject) => {
3225
+ await new Promise((resolve, reject) => {
2906
3226
  const workerdBinDir = dirname(workerdBin);
2907
3227
  this.workerdProcess = spawn(workerdBin, args2, {
2908
3228
  cwd: this.tempDir,
@@ -2922,7 +3242,7 @@ var init_emulator = __esm({
2922
3242
  }
2923
3243
  if (!started && (output.includes("Listening") || output.includes("running"))) {
2924
3244
  started = true;
2925
- resolve();
3245
+ resolve(void 0);
2926
3246
  }
2927
3247
  });
2928
3248
  this.workerdProcess.stderr?.on("data", (data) => {
@@ -2936,7 +3256,7 @@ var init_emulator = __esm({
2936
3256
  }
2937
3257
  if (!started && output.includes("Listening")) {
2938
3258
  started = true;
2939
- resolve();
3259
+ resolve(void 0);
2940
3260
  }
2941
3261
  });
2942
3262
  this.workerdProcess.on("error", (err) => {
@@ -2959,7 +3279,7 @@ var init_emulator = __esm({
2959
3279
  setTimeout(() => {
2960
3280
  if (!started) {
2961
3281
  started = true;
2962
- resolve();
3282
+ resolve(void 0);
2963
3283
  }
2964
3284
  }, 2e3);
2965
3285
  });
@@ -4516,7 +4836,7 @@ function createCallbackServer(expectedState, apiUrl) {
4516
4836
  </html>
4517
4837
  `);
4518
4838
  server?.close();
4519
- reject(err);
4839
+ reject(err instanceof Error ? err : new Error(String(err)));
4520
4840
  }
4521
4841
  });
4522
4842
  server.on("error", (err) => {
@@ -4856,6 +5176,18 @@ function generateEnvType(config) {
4856
5176
  properties.push(` ${bindingName}: QueueBinding;`);
4857
5177
  }
4858
5178
  }
5179
+ if (config.cache) {
5180
+ imports.push("CacheBinding");
5181
+ for (const bindingName of Object.keys(config.cache)) {
5182
+ properties.push(` ${bindingName}: CacheBinding;`);
5183
+ }
5184
+ }
5185
+ if (config.state) {
5186
+ imports.push("StateBinding");
5187
+ for (const bindingName of Object.keys(config.state)) {
5188
+ properties.push(` ${bindingName}: StateBinding;`);
5189
+ }
5190
+ }
4859
5191
  if (config.workflow) {
4860
5192
  imports.push("WorkflowBinding");
4861
5193
  for (const bindingName of Object.keys(config.workflow)) {
@@ -4903,7 +5235,7 @@ async function typesCommand(options = {}) {
4903
5235
  console.error("Error: ploy.yaml not found in current directory");
4904
5236
  process.exit(1);
4905
5237
  }
4906
- const hasBindings2 = config.ai || config.db || config.queue || config.workflow;
5238
+ const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
4907
5239
  if (!hasBindings2) {
4908
5240
  console.log("No bindings found in ploy.yaml. Generating empty Env.");
4909
5241
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetploy/cli",
3
- "version": "1.12.2",
3
+ "version": "1.14.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",