@concavejs/cli 0.0.1-alpha.7 → 0.0.1-alpha.8

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/cli.js CHANGED
@@ -4757,7 +4757,7 @@ async function loadAppDefinition(configPath, parentComponentPath, entries, visit
4757
4757
  for (const child of childComponents) {
4758
4758
  const componentPath = joinComponentPath(parentComponentPath, child.name);
4759
4759
  const definitionPath = resolveChildDefinitionPath(configPath, child.path);
4760
- const modulesDir = path.join(path.dirname(definitionPath), "convex");
4760
+ const modulesDir = await resolveComponentModulesDir(definitionPath);
4761
4761
  entries.push({
4762
4762
  componentPath,
4763
4763
  modulesDir,
@@ -4780,7 +4780,7 @@ async function loadComponentDefinition(configPath, componentPath, entries, visit
4780
4780
  for (const child of childComponents) {
4781
4781
  const nestedComponentPath = joinComponentPath(componentPath, child.name);
4782
4782
  const definitionPath = resolveChildDefinitionPath(configPath, child.path);
4783
- const modulesDir = path.join(path.dirname(definitionPath), "convex");
4783
+ const modulesDir = await resolveComponentModulesDir(definitionPath);
4784
4784
  entries.push({
4785
4785
  componentPath: nestedComponentPath,
4786
4786
  modulesDir,
@@ -4806,6 +4806,17 @@ function resolveChildDefinitionPath(parentConfigPath, childPath) {
4806
4806
  const parentDir = path.dirname(parentConfigPath);
4807
4807
  return path.resolve(parentDir, childPath);
4808
4808
  }
4809
+ async function resolveComponentModulesDir(definitionPath) {
4810
+ const definitionDir = path.dirname(definitionPath);
4811
+ const nestedConvexDir = path.join(definitionDir, "convex");
4812
+ try {
4813
+ const stats = await fs.stat(nestedConvexDir);
4814
+ if (stats.isDirectory()) {
4815
+ return nestedConvexDir;
4816
+ }
4817
+ } catch {}
4818
+ return definitionDir;
4819
+ }
4809
4820
  function joinComponentPath(parent, child) {
4810
4821
  if (!parent) {
4811
4822
  return child;
@@ -5146,6 +5157,7 @@ async function generateUserEntry(projectRoot, outDir) {
5146
5157
  const componentManifest = await scanComponents(projectRoot);
5147
5158
  await writeComponentManifest(targetDir, componentManifest.entries);
5148
5159
  await copyComponentModules(targetDir, componentManifest.entries);
5160
+ const componentLoaderBlocks = await buildComponentLoaderBlocks(targetDir, componentManifest.entries);
5149
5161
  const fallbackEntries = files.map((filePath) => {
5150
5162
  const importPath = formatImportPath(targetDir, filePath);
5151
5163
  return ` '${importPath}': () => import('${importPath}'),`;
@@ -5154,10 +5166,20 @@ async function generateUserEntry(projectRoot, outDir) {
5154
5166
  const content = `// Generated by concave CLI
5155
5167
  // Do not edit this file manually
5156
5168
 
5169
+ import { createModuleLoaderFromGlob, setConcaveModuleLoader } from "@concavejs/core/udf";
5170
+
5157
5171
  const modules = import.meta?.glob?.(["../convex/**/*.*s", "!../**/*.*.*s"]) ?? {
5158
5172
  ${fallbackEntries}
5159
5173
  };
5160
5174
 
5175
+ const componentLoaders = [];
5176
+ ${componentLoaderBlocks.join(`
5177
+
5178
+ `)}
5179
+ for (const { componentPath, loader } of componentLoaders) {
5180
+ setConcaveModuleLoader(loader, { componentPath });
5181
+ }
5182
+
5161
5183
  export { modules };
5162
5184
  `;
5163
5185
  const contentWithFunctionsPath = content.replace('const modules = import.meta?.glob?.(["../convex/**/*.*s", "!../**/*.*.*s"]) ?? {', `const modules = import.meta?.glob?.([${JSON.stringify(functionsGlobPattern)}, "!../**/*.*.*s"]) ?? {`);
@@ -7387,6 +7409,8 @@ import {
7387
7409
  } from "convex/server";
7388
7410
  import type { DataModel } from "./dataModel.js";
7389
7411
 
7412
+ export { defineSchema, defineTable } from "convex/server";
7413
+
7390
7414
  /**
7391
7415
  * Define a query in this Convex app's public API.
7392
7416
  *
@@ -7528,6 +7552,8 @@ import {
7528
7552
  internalActionGeneric,
7529
7553
  internalMutationGeneric,
7530
7554
  internalQueryGeneric,
7555
+ defineSchema,
7556
+ defineTable,
7531
7557
  } from "convex/server";
7532
7558
 
7533
7559
  /**
@@ -7599,6 +7625,11 @@ export const internalAction = internalActionGeneric;
7599
7625
  * @returns The wrapped endpoint function. Route a URL path to this function in \`convex/http.js\`.
7600
7626
  */
7601
7627
  export const httpAction = httpActionGeneric;
7628
+
7629
+ /**
7630
+ * Define a table in this Convex app's schema.
7631
+ */
7632
+ export { defineTable, defineSchema };
7602
7633
  `;
7603
7634
  await fs9.writeFile(path12.join(generatedDir, "server.js"), content, "utf8");
7604
7635
  }
@@ -7735,8 +7766,59 @@ async function generateApiJs(generatedDir) {
7735
7766
  * @module
7736
7767
  */
7737
7768
 
7738
- import { anyApi } from "convex/server";
7739
- import { componentsGeneric } from "convex/server";
7769
+ const functionName = Symbol.for("functionName");
7770
+ const toReferencePath = Symbol.for("toReferencePath");
7771
+
7772
+ function createApi(pathParts = []) {
7773
+ return new Proxy(
7774
+ {},
7775
+ {
7776
+ get(_target, prop) {
7777
+ if (typeof prop === "string") {
7778
+ return createApi([...pathParts, prop]);
7779
+ }
7780
+ if (prop === functionName) {
7781
+ if (pathParts.length < 2) {
7782
+ const found = ["api", ...pathParts].join(".");
7783
+ throw new Error(
7784
+ "API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`" + found + "\`",
7785
+ );
7786
+ }
7787
+ const modulePath = pathParts.slice(0, -1).join("/");
7788
+ const exportName = pathParts[pathParts.length - 1];
7789
+ return exportName === "default" ? modulePath : modulePath + ":" + exportName;
7790
+ }
7791
+ if (prop === Symbol.toStringTag) {
7792
+ return "FunctionReference";
7793
+ }
7794
+ return undefined;
7795
+ },
7796
+ },
7797
+ );
7798
+ }
7799
+
7800
+ function createComponents(pathParts = []) {
7801
+ return new Proxy(
7802
+ {},
7803
+ {
7804
+ get(_target, prop) {
7805
+ if (typeof prop === "string") {
7806
+ return createComponents([...pathParts, prop]);
7807
+ }
7808
+ if (prop === toReferencePath) {
7809
+ if (pathParts.length < 1) {
7810
+ const found = ["components", ...pathParts].join(".");
7811
+ throw new Error(
7812
+ "API path is expected to be of the form \`components.childComponent.functionName\`. Found: \`" + found + "\`",
7813
+ );
7814
+ }
7815
+ return "_reference/childComponent/" + pathParts.join("/");
7816
+ }
7817
+ return undefined;
7818
+ },
7819
+ },
7820
+ );
7821
+ }
7740
7822
 
7741
7823
  /**
7742
7824
  * A utility for referencing Convex functions in your app's API.
@@ -7746,9 +7828,9 @@ import { componentsGeneric } from "convex/server";
7746
7828
  * const myFunctionReference = api.myModule.myFunction;
7747
7829
  * \`\`\`
7748
7830
  */
7749
- export const api = anyApi;
7750
- export const internal = anyApi;
7751
- export const components = componentsGeneric();
7831
+ export const api = createApi();
7832
+ export const internal = createApi();
7833
+ export const components = createComponents();
7752
7834
  `;
7753
7835
  await fs9.writeFile(path12.join(generatedDir, "api.js"), content, "utf8");
7754
7836
  }
@@ -8297,6 +8379,8 @@ import {
8297
8379
  } from "convex/server";
8298
8380
  import type { DataModel } from "./dataModel.js";
8299
8381
 
8382
+ export { defineSchema, defineTable } from "convex/server";
8383
+
8300
8384
  export declare const query: QueryBuilder<DataModel, "public">;
8301
8385
  export declare const internalQuery: QueryBuilder<DataModel, "internal">;
8302
8386
  export declare const mutation: MutationBuilder<DataModel, "public">;
@@ -8332,15 +8416,84 @@ import {
8332
8416
  internalActionGeneric,
8333
8417
  internalMutationGeneric,
8334
8418
  internalQueryGeneric,
8419
+ defineSchema,
8420
+ defineTable,
8335
8421
  } from "convex/server";
8336
8422
 
8423
+ /**
8424
+ * Define a query in this Convex app's public API.
8425
+ *
8426
+ * This function will be allowed to read your Convex database and will be accessible from the client.
8427
+ *
8428
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
8429
+ * @returns The wrapped query. Include this as an \`export\` to name it and make it accessible.
8430
+ */
8337
8431
  export const query = queryGeneric;
8432
+
8433
+ /**
8434
+ * Define a query that is only accessible from other Convex functions (but not from the client).
8435
+ *
8436
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
8437
+ *
8438
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
8439
+ * @returns The wrapped query. Include this as an \`export\` to name it and make it accessible.
8440
+ */
8338
8441
  export const internalQuery = internalQueryGeneric;
8442
+
8443
+ /**
8444
+ * Define a mutation in this Convex app's public API.
8445
+ *
8446
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
8447
+ *
8448
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
8449
+ * @returns The wrapped mutation. Include this as an \`export\` to name it and make it accessible.
8450
+ */
8339
8451
  export const mutation = mutationGeneric;
8452
+
8453
+ /**
8454
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
8455
+ *
8456
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
8457
+ *
8458
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
8459
+ * @returns The wrapped mutation. Include this as an \`export\` to name it and make it accessible.
8460
+ */
8340
8461
  export const internalMutation = internalMutationGeneric;
8462
+
8463
+ /**
8464
+ * Define an action in this Convex app's public API.
8465
+ *
8466
+ * An action is a function which can execute any JavaScript code, including non-deterministic
8467
+ * code and code with side-effects, like calling third-party services.
8468
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
8469
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
8470
+ *
8471
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
8472
+ * @returns The wrapped action. Include this as an \`export\` to name it and make it accessible.
8473
+ */
8341
8474
  export const action = actionGeneric;
8475
+
8476
+ /**
8477
+ * Define an action that is only accessible from other Convex functions (but not from the client).
8478
+ *
8479
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
8480
+ * @returns The wrapped function. Include this as an \`export\` to name it and make it accessible.
8481
+ */
8342
8482
  export const internalAction = internalActionGeneric;
8483
+
8484
+ /**
8485
+ * Define a Convex HTTP action.
8486
+ *
8487
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a \`Request\` object
8488
+ * as its second.
8489
+ * @returns The wrapped endpoint function. Route a URL path to this function in \`convex/http.js\`.
8490
+ */
8343
8491
  export const httpAction = httpActionGeneric;
8492
+
8493
+ /**
8494
+ * Define a table in this Convex app's schema.
8495
+ */
8496
+ export { defineTable, defineSchema };
8344
8497
  `;
8345
8498
  await fs10.writeFile(path13.join(generatedDir, "server.js"), content, "utf8");
8346
8499
  }
@@ -8383,7 +8536,59 @@ async function generateApiJs2(generatedDir) {
8383
8536
  * @module
8384
8537
  */
8385
8538
 
8386
- import { anyApi, componentsGeneric } from "convex/server";
8539
+ const functionName = Symbol.for("functionName");
8540
+ const toReferencePath = Symbol.for("toReferencePath");
8541
+
8542
+ function createApi(pathParts = []) {
8543
+ return new Proxy(
8544
+ {},
8545
+ {
8546
+ get(_target, prop) {
8547
+ if (typeof prop === "string") {
8548
+ return createApi([...pathParts, prop]);
8549
+ }
8550
+ if (prop === functionName) {
8551
+ if (pathParts.length < 2) {
8552
+ const found = ["api", ...pathParts].join(".");
8553
+ throw new Error(
8554
+ "API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`" + found + "\`",
8555
+ );
8556
+ }
8557
+ const modulePath = pathParts.slice(0, -1).join("/");
8558
+ const exportName = pathParts[pathParts.length - 1];
8559
+ return exportName === "default" ? modulePath : modulePath + ":" + exportName;
8560
+ }
8561
+ if (prop === Symbol.toStringTag) {
8562
+ return "FunctionReference";
8563
+ }
8564
+ return undefined;
8565
+ },
8566
+ },
8567
+ );
8568
+ }
8569
+
8570
+ function createComponents(pathParts = []) {
8571
+ return new Proxy(
8572
+ {},
8573
+ {
8574
+ get(_target, prop) {
8575
+ if (typeof prop === "string") {
8576
+ return createComponents([...pathParts, prop]);
8577
+ }
8578
+ if (prop === toReferencePath) {
8579
+ if (pathParts.length < 1) {
8580
+ const found = ["components", ...pathParts].join(".");
8581
+ throw new Error(
8582
+ "API path is expected to be of the form \`components.childComponent.functionName\`. Found: \`" + found + "\`",
8583
+ );
8584
+ }
8585
+ return "_reference/childComponent/" + pathParts.join("/");
8586
+ }
8587
+ return undefined;
8588
+ },
8589
+ },
8590
+ );
8591
+ }
8387
8592
 
8388
8593
  /**
8389
8594
  * A utility for referencing Convex functions in your app's API.
@@ -8393,9 +8598,9 @@ import { anyApi, componentsGeneric } from "convex/server";
8393
8598
  * const myFunctionReference = api.myModule.myFunction;
8394
8599
  * \`\`\`
8395
8600
  */
8396
- export const api = anyApi;
8397
- export const internal = anyApi;
8398
- export const components = componentsGeneric();
8601
+ export const api = createApi();
8602
+ export const internal = createApi();
8603
+ export const components = createComponents();
8399
8604
  `;
8400
8605
  await fs10.writeFile(path13.join(generatedDir, "api.js"), content, "utf8");
8401
8606
  }
@@ -10098,7 +10303,7 @@ function setupAuthEnvironment(keys, siteUrl, options = {}) {
10098
10303
  setEnv("AUTH_SKIP_VERIFICATION", "true");
10099
10304
  }
10100
10305
  // package.json
10101
- var version = "0.0.1-alpha.7";
10306
+ var version = "0.0.1-alpha.8";
10102
10307
 
10103
10308
  // src/cli/cli.ts
10104
10309
  var WATCH_IGNORE_PATTERNS = [
@@ -10284,7 +10489,7 @@ async function ensureConvexDependencyReady(projectRoot, commandName) {
10284
10489
  }
10285
10490
  if (!hasConvexDependency(packageJson)) {
10286
10491
  console.error(import_picocolors9.default.red("✗ Missing dependency: convex"));
10287
- console.error(import_picocolors9.default.dim(" Concave function files import `convex/values` and generated files import `convex/server`."));
10492
+ console.error(import_picocolors9.default.dim(" Concave function files import `convex/values` and generated types depend on `convex/server`."));
10288
10493
  console.error(import_picocolors9.default.dim(` Run: ${addCommand.display}`));
10289
10494
  process.exit(1);
10290
10495
  }
@@ -10947,17 +11152,15 @@ async function runBunDevServer(cwd, opts, artifacts, layout) {
10947
11152
  throw error;
10948
11153
  }
10949
11154
  };
10950
- const restartServer = async (reason) => {
11155
+ const reloadServer = async (reason) => {
10951
11156
  if (isRestarting)
10952
11157
  return;
10953
11158
  isRestarting = true;
10954
11159
  try {
10955
- console.log(import_picocolors9.default.yellow(`
10956
- ⟳ ${reason}`));
10957
- if (server) {
10958
- await server.close();
10959
- }
10960
- await startServer();
11160
+ console.log(import_picocolors9.default.dim(`
11161
+ ⟳ ${reason}`));
11162
+ await server.reload();
11163
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
10961
11164
  } catch (error) {
10962
11165
  console.error(import_picocolors9.default.red("✗ Failed to reload server:"), error.message);
10963
11166
  } finally {
@@ -10981,7 +11184,7 @@ async function runBunDevServer(cwd, opts, artifacts, layout) {
10981
11184
  try {
10982
11185
  await generateTypes(cwd, { silent: true });
10983
11186
  } catch {}
10984
- restartServer(`File changed: ${relPath}`);
11187
+ reloadServer(`File changed: ${relPath}`);
10985
11188
  }, 300);
10986
11189
  };
10987
11190
  watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
@@ -11088,20 +11291,136 @@ process.on("SIGTERM", async () => {
11088
11291
  `;
11089
11292
  await fs12.writeFile(tempScriptPath, scriptContent, "utf8");
11090
11293
  const bunArgs = [tempScriptPath];
11091
- const bunProcess = spawn2("bun", bunArgs, {
11092
- cwd,
11093
- stdio: "inherit"
11094
- });
11095
- bunProcess.on("exit", (code) => {
11096
- fs12.unlink(tempScriptPath).catch(() => {});
11097
- process.exit(code ?? 0);
11098
- });
11099
- process.on("SIGINT", () => {
11100
- bunProcess.kill("SIGTERM");
11101
- });
11102
- process.on("SIGTERM", () => {
11103
- bunProcess.kill("SIGTERM");
11294
+ let watcher;
11295
+ let bunProcess;
11296
+ let reloadTimer;
11297
+ let isRestarting = false;
11298
+ let isShuttingDown = false;
11299
+ const stopBunProcess = async () => {
11300
+ const activeProcess = bunProcess;
11301
+ if (!activeProcess || activeProcess.exitCode !== null) {
11302
+ return;
11303
+ }
11304
+ await new Promise((resolve3) => {
11305
+ let settled = false;
11306
+ let forceKillTimer;
11307
+ const finish = () => {
11308
+ if (settled) {
11309
+ return;
11310
+ }
11311
+ settled = true;
11312
+ if (forceKillTimer) {
11313
+ clearTimeout(forceKillTimer);
11314
+ forceKillTimer = undefined;
11315
+ }
11316
+ activeProcess.off("exit", onExit);
11317
+ resolve3();
11318
+ };
11319
+ const onExit = () => finish();
11320
+ activeProcess.once("exit", onExit);
11321
+ try {
11322
+ activeProcess.kill("SIGTERM");
11323
+ } catch {
11324
+ finish();
11325
+ return;
11326
+ }
11327
+ forceKillTimer = setTimeout(() => {
11328
+ if (settled) {
11329
+ return;
11330
+ }
11331
+ try {
11332
+ activeProcess.kill("SIGKILL");
11333
+ } catch {
11334
+ finish();
11335
+ }
11336
+ }, 2000);
11337
+ });
11338
+ };
11339
+ const cleanup = async () => {
11340
+ if (reloadTimer) {
11341
+ clearTimeout(reloadTimer);
11342
+ reloadTimer = undefined;
11343
+ }
11344
+ if (watcher) {
11345
+ await watcher.close();
11346
+ watcher = undefined;
11347
+ }
11348
+ await stopBunProcess();
11349
+ await fs12.unlink(tempScriptPath).catch(() => {});
11350
+ };
11351
+ const spawnBunProcess = () => {
11352
+ const child = spawn2("bun", bunArgs, {
11353
+ cwd,
11354
+ stdio: "inherit"
11355
+ });
11356
+ child.on("exit", (code) => {
11357
+ if (isRestarting || isShuttingDown) {
11358
+ return;
11359
+ }
11360
+ cleanup().catch(() => {}).finally(() => {
11361
+ process.exit(code ?? 0);
11362
+ });
11363
+ });
11364
+ return child;
11365
+ };
11366
+ const reloadServerViaHttp = async (reason) => {
11367
+ if (isRestarting || isShuttingDown) {
11368
+ return;
11369
+ }
11370
+ isRestarting = true;
11371
+ try {
11372
+ console.log(import_picocolors9.default.dim(`
11373
+ ⟳ ${reason}`));
11374
+ const response = await fetch(`http://127.0.0.1:${port}/_dev/reload`, {
11375
+ method: "POST",
11376
+ signal: AbortSignal.timeout(5000)
11377
+ });
11378
+ if (response.ok) {
11379
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11380
+ } else {
11381
+ throw new Error(`HTTP ${response.status}`);
11382
+ }
11383
+ } catch (error) {
11384
+ console.warn(import_picocolors9.default.dim(` HTTP reload failed (${error.message}), restarting process...`));
11385
+ await stopBunProcess();
11386
+ bunProcess = spawnBunProcess();
11387
+ } finally {
11388
+ isRestarting = false;
11389
+ }
11390
+ };
11391
+ bunProcess = spawnBunProcess();
11392
+ watcher = esm_default.watch(convexDir, {
11393
+ ignoreInitial: true,
11394
+ ignored: WATCH_IGNORE_PATTERNS,
11395
+ persistent: true
11104
11396
  });
11397
+ const scheduleReload = (_event, filePath) => {
11398
+ if (reloadTimer) {
11399
+ clearTimeout(reloadTimer);
11400
+ }
11401
+ reloadTimer = setTimeout(async () => {
11402
+ reloadTimer = undefined;
11403
+ const relPath = path15.relative(cwd, filePath);
11404
+ try {
11405
+ await generateTypes(cwd, { silent: true });
11406
+ } catch {}
11407
+ await reloadServerViaHttp(`File changed: ${relPath}`);
11408
+ }, 300);
11409
+ };
11410
+ watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
11411
+ const shutdown = async () => {
11412
+ if (isShuttingDown) {
11413
+ return;
11414
+ }
11415
+ isShuttingDown = true;
11416
+ console.log(import_picocolors9.default.cyan(`
11417
+
11418
+ \uD83C\uDF0A Shutting down Concave dev server...`));
11419
+ await cleanup();
11420
+ process.exit(0);
11421
+ };
11422
+ process.on("SIGINT", shutdown);
11423
+ process.on("SIGTERM", shutdown);
11105
11424
  }
11106
11425
  async function ensureNodeSqliteReady() {
11107
11426
  const hasFlag = process.execArgv.includes("--experimental-sqlite");
@@ -11254,17 +11573,15 @@ async function runNodeDevServer(cwd, opts, artifacts, layout) {
11254
11573
  throw error;
11255
11574
  }
11256
11575
  };
11257
- const restartServer = async (reason) => {
11576
+ const reloadServer = async (reason) => {
11258
11577
  if (isRestarting)
11259
11578
  return;
11260
11579
  isRestarting = true;
11261
11580
  try {
11262
- console.log(import_picocolors9.default.yellow(`
11263
- ⟳ ${reason}`));
11264
- if (server) {
11265
- await server.close();
11266
- }
11267
- await startServer();
11581
+ console.log(import_picocolors9.default.dim(`
11582
+ ⟳ ${reason}`));
11583
+ await server.reload();
11584
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11268
11585
  } catch (error) {
11269
11586
  console.error(import_picocolors9.default.red("✗ Failed to reload server:"), error.message);
11270
11587
  } finally {
@@ -11288,7 +11605,7 @@ async function runNodeDevServer(cwd, opts, artifacts, layout) {
11288
11605
  try {
11289
11606
  await generateTypes(cwd, { silent: true });
11290
11607
  } catch {}
11291
- restartServer(`File changed: ${relPath}`);
11608
+ reloadServer(`File changed: ${relPath}`);
11292
11609
  }, 300);
11293
11610
  };
11294
11611
  watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
@@ -11404,20 +11721,136 @@ process.on("SIGTERM", async () => {
11404
11721
  await fs12.writeFile(tempScriptPath, scriptContent, "utf8");
11405
11722
  const nodeArgs = ["--experimental-sqlite", "--import", tsxLoaderPath, tempScriptPath];
11406
11723
  const executable = detectHostRuntime() === "node" ? process.execPath : "node";
11407
- const nodeProcess = spawn2(executable, nodeArgs, {
11408
- cwd,
11409
- stdio: "inherit"
11410
- });
11411
- nodeProcess.on("exit", (code) => {
11412
- fs12.unlink(tempScriptPath).catch(() => {});
11413
- process.exit(code ?? 0);
11414
- });
11415
- process.on("SIGINT", () => {
11416
- nodeProcess.kill("SIGTERM");
11417
- });
11418
- process.on("SIGTERM", () => {
11419
- nodeProcess.kill("SIGTERM");
11724
+ let watcher;
11725
+ let nodeProcess;
11726
+ let reloadTimer;
11727
+ let isRestarting = false;
11728
+ let isShuttingDown = false;
11729
+ const stopNodeProcess = async () => {
11730
+ const activeProcess = nodeProcess;
11731
+ if (!activeProcess || activeProcess.exitCode !== null) {
11732
+ return;
11733
+ }
11734
+ await new Promise((resolve3) => {
11735
+ let settled = false;
11736
+ let forceKillTimer;
11737
+ const finish = () => {
11738
+ if (settled) {
11739
+ return;
11740
+ }
11741
+ settled = true;
11742
+ if (forceKillTimer) {
11743
+ clearTimeout(forceKillTimer);
11744
+ forceKillTimer = undefined;
11745
+ }
11746
+ activeProcess.off("exit", onExit);
11747
+ resolve3();
11748
+ };
11749
+ const onExit = () => finish();
11750
+ activeProcess.once("exit", onExit);
11751
+ try {
11752
+ activeProcess.kill("SIGTERM");
11753
+ } catch {
11754
+ finish();
11755
+ return;
11756
+ }
11757
+ forceKillTimer = setTimeout(() => {
11758
+ if (settled) {
11759
+ return;
11760
+ }
11761
+ try {
11762
+ activeProcess.kill("SIGKILL");
11763
+ } catch {
11764
+ finish();
11765
+ }
11766
+ }, 2000);
11767
+ });
11768
+ };
11769
+ const cleanup = async () => {
11770
+ if (reloadTimer) {
11771
+ clearTimeout(reloadTimer);
11772
+ reloadTimer = undefined;
11773
+ }
11774
+ if (watcher) {
11775
+ await watcher.close();
11776
+ watcher = undefined;
11777
+ }
11778
+ await stopNodeProcess();
11779
+ await fs12.unlink(tempScriptPath).catch(() => {});
11780
+ };
11781
+ const spawnNodeProcess = () => {
11782
+ const child = spawn2(executable, nodeArgs, {
11783
+ cwd,
11784
+ stdio: "inherit"
11785
+ });
11786
+ child.on("exit", (code) => {
11787
+ if (isRestarting || isShuttingDown) {
11788
+ return;
11789
+ }
11790
+ cleanup().catch(() => {}).finally(() => {
11791
+ process.exit(code ?? 0);
11792
+ });
11793
+ });
11794
+ return child;
11795
+ };
11796
+ const reloadServerViaHttp = async (reason) => {
11797
+ if (isRestarting || isShuttingDown) {
11798
+ return;
11799
+ }
11800
+ isRestarting = true;
11801
+ try {
11802
+ console.log(import_picocolors9.default.dim(`
11803
+ ⟳ ${reason}`));
11804
+ const response = await fetch(`http://127.0.0.1:${port}/_dev/reload`, {
11805
+ method: "POST",
11806
+ signal: AbortSignal.timeout(5000)
11807
+ });
11808
+ if (response.ok) {
11809
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11810
+ } else {
11811
+ throw new Error(`HTTP ${response.status}`);
11812
+ }
11813
+ } catch (error) {
11814
+ console.warn(import_picocolors9.default.dim(` HTTP reload failed (${error.message}), restarting process...`));
11815
+ await stopNodeProcess();
11816
+ nodeProcess = spawnNodeProcess();
11817
+ } finally {
11818
+ isRestarting = false;
11819
+ }
11820
+ };
11821
+ nodeProcess = spawnNodeProcess();
11822
+ watcher = esm_default.watch(convexDir, {
11823
+ ignoreInitial: true,
11824
+ ignored: WATCH_IGNORE_PATTERNS,
11825
+ persistent: true
11420
11826
  });
11827
+ const scheduleReload = (_event, filePath) => {
11828
+ if (reloadTimer) {
11829
+ clearTimeout(reloadTimer);
11830
+ }
11831
+ reloadTimer = setTimeout(async () => {
11832
+ reloadTimer = undefined;
11833
+ const relPath = path15.relative(cwd, filePath);
11834
+ try {
11835
+ await generateTypes(cwd, { silent: true });
11836
+ } catch {}
11837
+ await reloadServerViaHttp(`File changed: ${relPath}`);
11838
+ }, 300);
11839
+ };
11840
+ watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
11841
+ const shutdown = async () => {
11842
+ if (isShuttingDown) {
11843
+ return;
11844
+ }
11845
+ isShuttingDown = true;
11846
+ console.log(import_picocolors9.default.cyan(`
11847
+
11848
+ \uD83C\uDF0A Shutting down Concave dev server...`));
11849
+ await cleanup();
11850
+ process.exit(0);
11851
+ };
11852
+ process.on("SIGINT", shutdown);
11853
+ process.on("SIGTERM", shutdown);
11421
11854
  }
11422
11855
  async function ensureWranglerConfig(cwd) {
11423
11856
  const userConfigs = [