@meetploy/cli 1.13.0 → 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
@@ -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)`);
@@ -197,6 +194,7 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
197
194
  validateBindings(config.db, "db", configFile);
198
195
  validateBindings(config.queue, "queue", configFile);
199
196
  validateBindings(config.cache, "cache", configFile);
197
+ validateBindings(config.state, "state", configFile);
200
198
  validateBindings(config.workflow, "workflow", configFile);
201
199
  if (config.ai !== void 0 && typeof config.ai !== "boolean") {
202
200
  throw new Error(`'ai' in ${configFile} must be a boolean`);
@@ -223,9 +221,6 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
223
221
  }
224
222
  }
225
223
  if (config.auth !== void 0) {
226
- if (typeof config.auth !== "object" || config.auth === null) {
227
- throw new Error(`'auth' in ${configFile} must be an object`);
228
- }
229
224
  if (!config.auth.binding) {
230
225
  throw new Error(`'auth.binding' is required in ${configFile} when auth is configured`);
231
226
  }
@@ -239,7 +234,7 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
239
234
  return validatedConfig;
240
235
  }
241
236
  async function readPloyConfig(projectDir, configPath) {
242
- const configFile = configPath || "ploy.yaml";
237
+ const configFile = configPath ?? "ploy.yaml";
243
238
  const fullPath = join(projectDir, configFile);
244
239
  try {
245
240
  const content = await readFileAsync(fullPath, "utf-8");
@@ -254,7 +249,7 @@ async function readPloyConfig(projectDir, configPath) {
254
249
  }
255
250
  }
256
251
  function readPloyConfigSync(projectDir, configPath) {
257
- const configFile = configPath || "ploy.yaml";
252
+ const configFile = configPath ?? "ploy.yaml";
258
253
  const fullPath = join(projectDir, configFile);
259
254
  if (!existsSync(fullPath)) {
260
255
  throw new Error(`Config file not found: ${fullPath}`);
@@ -263,7 +258,7 @@ function readPloyConfigSync(projectDir, configPath) {
263
258
  return parse(content);
264
259
  }
265
260
  async function readAndValidatePloyConfig(projectDir, configPath, validationOptions) {
266
- const configFile = configPath || "ploy.yaml";
261
+ const configFile = configPath ?? "ploy.yaml";
267
262
  const config = await readPloyConfig(projectDir, configPath);
268
263
  if (!config) {
269
264
  return null;
@@ -271,12 +266,12 @@ async function readAndValidatePloyConfig(projectDir, configPath, validationOptio
271
266
  return validatePloyConfig(config, configFile, validationOptions);
272
267
  }
273
268
  function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions) {
274
- const configFile = configPath || "ploy.yaml";
269
+ const configFile = configPath ?? "ploy.yaml";
275
270
  const config = readPloyConfigSync(projectDir, configPath);
276
271
  return validatePloyConfig(config, configFile, validationOptions);
277
272
  }
278
273
  function hasBindings(config) {
279
- return !!(config.db || config.queue || config.cache || config.workflow || config.ai || config.auth);
274
+ return !!(config.db || config.queue || config.cache || config.state || config.workflow || config.ai || config.auth);
280
275
  }
281
276
  function getWorkerEntryPoint(projectDir, config) {
282
277
  if (config.main) {
@@ -777,6 +772,66 @@ export function initializeQueue<T = unknown>(queueName: string, serviceUrl: stri
777
772
  }
778
773
  });
779
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
+
780
835
  // ../emulator/dist/runtime/workflow-runtime.js
781
836
  var WORKFLOW_RUNTIME_CODE;
782
837
  var init_workflow_runtime = __esm({
@@ -1009,6 +1064,12 @@ function generateWrapperCode(config, mockServiceUrl) {
1009
1064
  bindings.push(` ${bindingName}: initializeCache("${cacheName}", "${mockServiceUrl}"),`);
1010
1065
  }
1011
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
+ }
1012
1073
  if (config.workflow) {
1013
1074
  imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
1014
1075
  for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
@@ -1116,6 +1177,10 @@ function createRuntimePlugin(_config) {
1116
1177
  path: "__ploy_cache_runtime__",
1117
1178
  namespace: "ploy-runtime"
1118
1179
  }));
1180
+ build2.onResolve({ filter: /^__ploy_state_runtime__$/ }, () => ({
1181
+ path: "__ploy_state_runtime__",
1182
+ namespace: "ploy-runtime"
1183
+ }));
1119
1184
  build2.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
1120
1185
  path: "__ploy_workflow_runtime__",
1121
1186
  namespace: "ploy-runtime"
@@ -1132,6 +1197,10 @@ function createRuntimePlugin(_config) {
1132
1197
  contents: CACHE_RUNTIME_CODE,
1133
1198
  loader: "ts"
1134
1199
  }));
1200
+ build2.onLoad({ filter: /^__ploy_state_runtime__$/, namespace: "ploy-runtime" }, () => ({
1201
+ contents: STATE_RUNTIME_CODE,
1202
+ loader: "ts"
1203
+ }));
1135
1204
  build2.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
1136
1205
  contents: WORKFLOW_RUNTIME_CODE,
1137
1206
  loader: "ts"
@@ -1175,6 +1244,7 @@ var init_bundler = __esm({
1175
1244
  init_cache_runtime();
1176
1245
  init_db_runtime();
1177
1246
  init_queue_runtime();
1247
+ init_state_runtime();
1178
1248
  init_workflow_runtime();
1179
1249
  NODE_BUILTINS = [
1180
1250
  "assert",
@@ -1731,6 +1801,7 @@ function createDashboardRoutes(app, dbManager2, config) {
1731
1801
  db: config.db,
1732
1802
  queue: config.queue,
1733
1803
  cache: config.cache,
1804
+ state: config.state,
1734
1805
  workflow: config.workflow,
1735
1806
  auth: config.auth
1736
1807
  });
@@ -2104,6 +2175,38 @@ function createDashboardRoutes(app, dbManager2, config) {
2104
2175
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
2105
2176
  }
2106
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
+ });
2107
2210
  app.get("/api/workflow/:binding/executions", (c) => {
2108
2211
  const binding = c.req.param("binding");
2109
2212
  const workflowConfig = config.workflow?.[binding];
@@ -2531,6 +2634,37 @@ var init_queue_service = __esm({
2531
2634
  "../emulator/dist/services/queue-service.js"() {
2532
2635
  }
2533
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
+ });
2534
2668
  function createWorkflowHandlers(db, workerUrl) {
2535
2669
  const triggerHandler = async (c) => {
2536
2670
  try {
@@ -2739,6 +2873,12 @@ async function startMockServer(dbManager2, config, options = {}) {
2739
2873
  app.post("/cache/set", cacheHandlers.setHandler);
2740
2874
  app.post("/cache/delete", cacheHandlers.deleteHandler);
2741
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
+ }
2742
2882
  if (config.auth) {
2743
2883
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
2744
2884
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -2759,7 +2899,9 @@ async function startMockServer(dbManager2, config, options = {}) {
2759
2899
  resolve({
2760
2900
  port: info.port,
2761
2901
  close: () => new Promise((res) => {
2762
- server.close(() => res());
2902
+ server.close(() => {
2903
+ res();
2904
+ });
2763
2905
  })
2764
2906
  });
2765
2907
  });
@@ -2773,6 +2915,7 @@ var init_mock_server = __esm({
2773
2915
  init_dashboard_routes();
2774
2916
  init_db_service();
2775
2917
  init_queue_service();
2918
+ init_state_service();
2776
2919
  init_workflow_service();
2777
2920
  DEFAULT_MOCK_SERVER_PORT = 4003;
2778
2921
  }
@@ -2938,6 +3081,14 @@ CREATE TABLE IF NOT EXISTS cache_entries (
2938
3081
  expires_at INTEGER NOT NULL,
2939
3082
  PRIMARY KEY (cache_name, key)
2940
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
+ );
2941
3092
  `;
2942
3093
  }
2943
3094
  });
@@ -3071,7 +3222,7 @@ var init_emulator = __esm({
3071
3222
  const workerdBin = this.findWorkerdBinary();
3072
3223
  log(`[ploy] Using workerd binary: ${workerdBin}`);
3073
3224
  debug(`Starting workerd: ${workerdBin} ${args2.join(" ")}`, this.options.verbose);
3074
- return await new Promise((resolve, reject) => {
3225
+ await new Promise((resolve, reject) => {
3075
3226
  const workerdBinDir = dirname(workerdBin);
3076
3227
  this.workerdProcess = spawn(workerdBin, args2, {
3077
3228
  cwd: this.tempDir,
@@ -3091,7 +3242,7 @@ var init_emulator = __esm({
3091
3242
  }
3092
3243
  if (!started && (output.includes("Listening") || output.includes("running"))) {
3093
3244
  started = true;
3094
- resolve();
3245
+ resolve(void 0);
3095
3246
  }
3096
3247
  });
3097
3248
  this.workerdProcess.stderr?.on("data", (data) => {
@@ -3105,7 +3256,7 @@ var init_emulator = __esm({
3105
3256
  }
3106
3257
  if (!started && output.includes("Listening")) {
3107
3258
  started = true;
3108
- resolve();
3259
+ resolve(void 0);
3109
3260
  }
3110
3261
  });
3111
3262
  this.workerdProcess.on("error", (err) => {
@@ -3128,7 +3279,7 @@ var init_emulator = __esm({
3128
3279
  setTimeout(() => {
3129
3280
  if (!started) {
3130
3281
  started = true;
3131
- resolve();
3282
+ resolve(void 0);
3132
3283
  }
3133
3284
  }, 2e3);
3134
3285
  });
@@ -4685,7 +4836,7 @@ function createCallbackServer(expectedState, apiUrl) {
4685
4836
  </html>
4686
4837
  `);
4687
4838
  server?.close();
4688
- reject(err);
4839
+ reject(err instanceof Error ? err : new Error(String(err)));
4689
4840
  }
4690
4841
  });
4691
4842
  server.on("error", (err) => {
@@ -5031,6 +5182,12 @@ function generateEnvType(config) {
5031
5182
  properties.push(` ${bindingName}: CacheBinding;`);
5032
5183
  }
5033
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
+ }
5034
5191
  if (config.workflow) {
5035
5192
  imports.push("WorkflowBinding");
5036
5193
  for (const bindingName of Object.keys(config.workflow)) {
@@ -5078,7 +5235,7 @@ async function typesCommand(options = {}) {
5078
5235
  console.error("Error: ploy.yaml not found in current directory");
5079
5236
  process.exit(1);
5080
5237
  }
5081
- const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.workflow;
5238
+ const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
5082
5239
  if (!hasBindings2) {
5083
5240
  console.log("No bindings found in ploy.yaml. Generating empty Env.");
5084
5241
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetploy/cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",