evolit 0.1.0-alpha.16 → 0.1.0-alpha.18

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/README.md CHANGED
@@ -104,12 +104,18 @@ export const routeConfig = {
104
104
  - `static`: prerender in `build` and serve from the response cache in `start`
105
105
  - `revalidate`: cache the HTML response for `N` seconds and regenerate on expiry
106
106
 
107
+ Pages without `routeConfig.cache` default to `{ revalidate: 60 }`. This caches a normal SSR
108
+ render by pathname and query string while keeping content fresh without requiring a cache
109
+ declaration on every catalog or CMS page. Declare `cache: "static"` for fully static pages or
110
+ `cache: "dynamic"` when a page must always render per request.
111
+
107
112
  The same semantics work in local development and in production runtimes. Only the backing cache
108
113
  store changes.
109
114
 
110
- The default cache key includes the pathname and query string. Reading the `request` prop, request
111
- headers, cookies, or `requestUrl()` makes the completed render dynamic; it is never stored in the
112
- HTML response cache. `routeConfig.cache` is the sole authority for HTML caching; setting a
115
+ The default cache key includes the pathname and query string, so `params` and `searchParams` are
116
+ cacheable by URL. Reading the `request` prop, request headers, cookies, or `requestUrl()` makes
117
+ the completed render dynamic; it is never stored in the HTML response cache. `routeConfig.cache`
118
+ is the sole authority for HTML caching; setting a
113
119
  `Cache-Control` response header does not alter that policy.
114
120
 
115
121
  ## Request APIs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
package/src/build.js CHANGED
@@ -255,17 +255,35 @@ export async function buildProject(projectRoot) {
255
255
  ...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
256
256
  ].join("\n");
257
257
  },
258
- resolveBootstrap({ result }) {
258
+ resolveBootstrap({ routeResult, result }) {
259
+ const routeClientImports = resolveRouteClientImports(
260
+ routeResult,
261
+ projectRoot,
262
+ clientAssets,
263
+ );
259
264
  return createHydrationBootstrap({
260
- hydrationData: normalizeHydrationDataForClient(result.hydrationData, projectRoot),
265
+ hydrationData: normalizeHydrationDataForClient(
266
+ result.hydrationData,
267
+ projectRoot,
268
+ routeClientImports,
269
+ ),
261
270
  assetResolver,
262
271
  hydrationModuleUrl,
263
272
  });
264
273
  },
265
- transformDocument({ result, document }) {
274
+ transformDocument({ routeResult, result, document }) {
275
+ const routeClientImports = resolveRouteClientImports(
276
+ routeResult,
277
+ projectRoot,
278
+ clientAssets,
279
+ );
266
280
  return rewriteHydrationDataScript(
267
281
  document,
268
- normalizeHydrationDataForClient(result.hydrationData, projectRoot),
282
+ normalizeHydrationDataForClient(
283
+ result.hydrationData,
284
+ projectRoot,
285
+ routeClientImports,
286
+ ),
269
287
  );
270
288
  },
271
289
  });
package/src/cli.js CHANGED
@@ -3,7 +3,9 @@
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { scaffoldSite } from "./scaffold.js";
6
- import { terminal } from "./terminal.js";
6
+ import { createDevelopmentEventReporter } from "./development-events.js";
7
+
8
+ const reportEvent = createDevelopmentEventReporter();
7
9
 
8
10
  function parseArguments(argv) {
9
11
  const [command, ...rest] = argv;
@@ -25,9 +27,7 @@ function parseArguments(argv) {
25
27
  }
26
28
 
27
29
  function printUsage() {
28
- console.log(`${terminal.bold("Usage:")} evolit <directory>`);
29
- console.log(` evolit ${terminal.cyan("init")} <directory>`);
30
- console.log(` evolit ${terminal.cyan("<dev|build|start>")} [--port 3000]`);
30
+ reportEvent({ type: "usage" });
31
31
  }
32
32
 
33
33
  async function createSite(projectRoot, targetDirectory) {
@@ -36,7 +36,7 @@ async function createSite(projectRoot, targetDirectory) {
36
36
  }
37
37
 
38
38
  const createdDirectory = await scaffoldSite(path.resolve(projectRoot, targetDirectory));
39
- console.log(`${terminal.green("✓")} Created evolit site: ${terminal.cyan(createdDirectory)}`);
39
+ reportEvent({ type: "site-created", directory: createdDirectory });
40
40
  }
41
41
 
42
42
  async function run() {
@@ -56,25 +56,29 @@ async function run() {
56
56
  if (command === "build") {
57
57
  const { buildProject } = await import("./build.js");
58
58
  const manifestPath = await buildProject(projectRoot);
59
- console.log(`${terminal.green("✓")} Built Evolit app: ${terminal.cyan(path.relative(projectRoot, manifestPath))}`);
59
+ reportEvent({
60
+ type: "build-ready",
61
+ manifestPath: path.relative(projectRoot, manifestPath),
62
+ });
60
63
  return;
61
64
  }
62
65
 
63
66
  if (command === "start") {
64
67
  const { createStartServer } = await import("./server.js");
65
- console.log(`${terminal.cyan("▲")} ${terminal.bold("Evolit")} ${terminal.cyan("start")}`);
68
+ reportEvent({ type: "server-starting", mode: "start" });
66
69
  const server = await createStartServer(projectRoot, options);
67
70
  await server.listen();
68
- console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${server.port}`)}`);
71
+ reportEvent({ type: "server-ready", port: server.port });
69
72
  return;
70
73
  }
71
74
 
72
75
  if (command === "dev") {
73
76
  const { createDevServer } = await import("./server.js");
74
- console.log(`${terminal.cyan("▲")} ${terminal.bold("Evolit")} ${terminal.cyan("dev")}`);
75
- const server = await createDevServer(projectRoot, options);
77
+ const server = await createDevServer(projectRoot, {
78
+ ...options,
79
+ onDevelopmentEvent: reportEvent,
80
+ });
76
81
  await server.listen();
77
- console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${server.port}`)}`);
78
82
  return;
79
83
  }
80
84
 
@@ -82,8 +86,6 @@ async function run() {
82
86
  }
83
87
 
84
88
  run().catch((error) => {
85
- console.error(
86
- `${terminal.red("[evolit]")} ${terminal.red("✖")} ${error instanceof Error ? error.stack ?? error.message : String(error)}`,
87
- );
89
+ reportEvent({ type: "fatal-error", error });
88
90
  process.exitCode = 1;
89
91
  });
@@ -934,6 +934,55 @@ function toCompiledClientModuleRelativePath(relativePath) {
934
934
  : `${relativePath}.mjs`;
935
935
  }
936
936
 
937
+ function toCatchAllClientModuleRelativePath(clientModule) {
938
+ return clientModule
939
+ .split(/[\\/]/)
940
+ .map((segment) => {
941
+ if (segment.startsWith("[[...") && segment.endsWith("]]")) {
942
+ return `**...${segment.slice(5, -2)}**`;
943
+ }
944
+
945
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
946
+ return `*...${segment.slice(4, -1)}*`;
947
+ }
948
+
949
+ return segment;
950
+ })
951
+ .join("/");
952
+ }
953
+
954
+ function toRollupSafeClientModuleRelativePath(clientModule) {
955
+ return clientModule.replaceAll(/[\[\]]/g, "_");
956
+ }
957
+
958
+ function getClientModuleCandidates(clientModule) {
959
+ const normalizedClientModule = clientModule.split(path.sep).join("/");
960
+ return [...new Set([
961
+ normalizedClientModule,
962
+ toCatchAllClientModuleRelativePath(normalizedClientModule),
963
+ toRollupSafeClientModuleRelativePath(normalizedClientModule),
964
+ ])];
965
+ }
966
+
967
+ function getClientModulePublicUrl(assetManifest, clientModule) {
968
+ for (const candidate of getClientModuleCandidates(clientModule)) {
969
+ const publicUrl = assetManifest.byClientModule[candidate];
970
+ if (typeof publicUrl === "string" && publicUrl.length > 0) {
971
+ return publicUrl;
972
+ }
973
+ }
974
+
975
+ const candidates = new Set(getClientModuleCandidates(clientModule));
976
+ const asset = assetManifest.assets.find((entry) =>
977
+ candidates.has(entry?.clientModule) && typeof entry?.publicUrl === "string",
978
+ );
979
+ if (asset) {
980
+ return asset.publicUrl;
981
+ }
982
+
983
+ return null;
984
+ }
985
+
937
986
  function toPublicHydrationModuleId(projectRoot, moduleId) {
938
987
  if (typeof moduleId !== "string" || moduleId.length === 0) {
939
988
  return null;
@@ -1001,10 +1050,7 @@ export function createAssetResolver(projectRoot, options = {}) {
1001
1050
  }
1002
1051
 
1003
1052
  if (assetManifest) {
1004
- return getAssetByClientModule(
1005
- assetManifest,
1006
- relativeClientModule,
1007
- )?.publicUrl ?? null;
1053
+ return getClientModulePublicUrl(assetManifest, relativeClientModule);
1008
1054
  }
1009
1055
 
1010
1056
  return null;
@@ -1029,8 +1075,8 @@ export function createHydrationBootstrap({
1029
1075
  hydrationModuleUrl = "@litsx/ssr/hydration",
1030
1076
  }) {
1031
1077
  const normalizedHydrationData = normalizeHydrationDataForClient(hydrationData);
1032
- const moduleUrls = [...new Set(
1033
- (normalizedHydrationData?.roots ?? [])
1078
+ const moduleUrls = [...new Set([
1079
+ ...(normalizedHydrationData?.roots ?? [])
1034
1080
  .map((root) => {
1035
1081
  if (typeof root?.moduleId !== "string" || root.moduleId.length === 0) {
1036
1082
  return null;
@@ -1039,7 +1085,10 @@ export function createHydrationBootstrap({
1039
1085
  return assetResolver?.(root.moduleId) ?? null;
1040
1086
  })
1041
1087
  .filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0),
1042
- )];
1088
+ ...(Array.isArray(normalizedHydrationData?.clientImports)
1089
+ ? normalizedHydrationData.clientImports
1090
+ : []),
1091
+ ].filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0))];
1043
1092
 
1044
1093
  if (moduleUrls.length === 0) {
1045
1094
  return "";
@@ -1063,7 +1112,11 @@ ${moduleLoaders}
1063
1112
  return escapeInlineScriptText(source);
1064
1113
  }
1065
1114
 
1066
- export function normalizeHydrationDataForClient(hydrationData, projectRoot = null) {
1115
+ export function normalizeHydrationDataForClient(
1116
+ hydrationData,
1117
+ projectRoot = null,
1118
+ additionalClientImports = [],
1119
+ ) {
1067
1120
  if (!hydrationData || typeof hydrationData !== "object") {
1068
1121
  return hydrationData ?? null;
1069
1122
  }
@@ -1096,7 +1149,10 @@ export function normalizeHydrationDataForClient(hydrationData, projectRoot = nul
1096
1149
  roots,
1097
1150
  };
1098
1151
  const payload = hydrationData.payload;
1099
- const clientImports = hydrationData.clientImports;
1152
+ const clientImports = [...new Set([
1153
+ ...(Array.isArray(hydrationData.clientImports) ? hydrationData.clientImports : []),
1154
+ ...(Array.isArray(additionalClientImports) ? additionalClientImports : []),
1155
+ ].filter((value) => typeof value === "string" && value.length > 0))];
1100
1156
 
1101
1157
  Object.defineProperties(normalizedHydrationData, {
1102
1158
  payload: {
@@ -1728,9 +1784,14 @@ async function bundleClientAssets(projectRoot, options = {}) {
1728
1784
 
1729
1785
  const outputPath = path.join(staticRoot, chunk.fileName);
1730
1786
  const publicUrl = toStaticPublicUrl(chunk.fileName);
1731
- const isNamedEntry = chunk.isEntry && clientModuleByEntryName.has(chunk.name);
1787
+ const entryClientModule = clientModuleByEntryName.get(chunk.name)
1788
+ ?? [...clientModuleByEntryName.entries()].find(([entryName]) =>
1789
+ toRollupSafeClientModuleRelativePath(entryName) === chunk.name,
1790
+ )?.[1]
1791
+ ?? null;
1792
+ const isNamedEntry = chunk.isEntry && entryClientModule != null;
1732
1793
  const clientModule = isNamedEntry
1733
- ? clientModuleByEntryName.get(chunk.name)
1794
+ ? entryClientModule
1734
1795
  : chunk.fileName;
1735
1796
  const transitiveMetadata = isNamedEntry
1736
1797
  ? await collectTransitiveClientModuleMetadata(projectRoot, mode, clientModule)
@@ -1939,7 +2000,7 @@ export function resolveRouteClientImports(routeResult, projectRoot, assetManifes
1939
2000
  const clientModule = extension
1940
2001
  ? `${relativePath.slice(0, -extension.length)}.mjs`
1941
2002
  : `${relativePath}.mjs`;
1942
- return normalizedManifest.byClientModule[clientModule] ?? null;
2003
+ return getClientModulePublicUrl(normalizedManifest, clientModule);
1943
2004
  }).filter((publicUrl) => typeof publicUrl === "string"))];
1944
2005
  }
1945
2006
 
package/src/compiler.js CHANGED
@@ -25,7 +25,7 @@ const developmentGraphCache = new Map();
25
25
  const developmentGraphDependencies = new Map();
26
26
  const developmentModuleNamespaceCache = new Map();
27
27
  const developmentGraphVersions = new Map();
28
- const developmentExternalImportWarnings = new Set();
28
+ const developmentUnmanagedImportWarnings = new Set();
29
29
 
30
30
  function isRelativeSpecifier(specifier) {
31
31
  return specifier.startsWith("./") || specifier.startsWith("../");
@@ -70,15 +70,15 @@ function isPathOutsideProject(projectRoot, filePath) {
70
70
  );
71
71
  }
72
72
 
73
- function reportDevelopmentExternalImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent) {
73
+ function reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent) {
74
74
  const warningKey = `${path.resolve(projectRoot)}::${sourcePath}::${resolvedImportPath}`;
75
- if (developmentExternalImportWarnings.has(warningKey)) {
75
+ if (developmentUnmanagedImportWarnings.has(warningKey)) {
76
76
  return;
77
77
  }
78
78
 
79
- developmentExternalImportWarnings.add(warningKey);
79
+ developmentUnmanagedImportWarnings.add(warningKey);
80
80
  onDevelopmentEvent?.({
81
- type: "external-import",
81
+ type: "unmanaged-import",
82
82
  sourcePath,
83
83
  resolvedImportPath,
84
84
  });
@@ -159,7 +159,7 @@ function toOutputRelativePath(projectRoot, sourcePath) {
159
159
  }
160
160
 
161
161
  return path.join(
162
- "__external__",
162
+ "__unmanaged__",
163
163
  ...relativePath.split(path.sep).map((segment) => segment === ".." ? "__up__" : segment),
164
164
  );
165
165
  }
@@ -211,7 +211,7 @@ async function rewriteRelativeSpecifiers({
211
211
  }
212
212
 
213
213
  if (mode === "development" && isPathOutsideProject(projectRoot, resolvedImportPath)) {
214
- reportDevelopmentExternalImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent);
214
+ reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent);
215
215
  }
216
216
 
217
217
  const importerOutputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
@@ -339,9 +339,9 @@ export function invalidateDevelopmentCompilationCache(projectRoot, changedPaths
339
339
  let invalidated = false;
340
340
 
341
341
  if (normalizedChangedPaths == null) {
342
- for (const warningKey of developmentExternalImportWarnings) {
342
+ for (const warningKey of developmentUnmanagedImportWarnings) {
343
343
  if (warningKey.startsWith(prefix)) {
344
- developmentExternalImportWarnings.delete(warningKey);
344
+ developmentUnmanagedImportWarnings.delete(warningKey);
345
345
  }
346
346
  }
347
347
  }
@@ -293,19 +293,37 @@ export async function createRequestRenderer({
293
293
  ...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
294
294
  ].join("\n");
295
295
  },
296
- resolveBootstrap({ result }) {
296
+ resolveBootstrap({ routeResult, result }) {
297
+ const routeClientImports = resolveRouteClientImports(
298
+ routeResult,
299
+ projectRoot,
300
+ currentAssetManifest,
301
+ );
297
302
  return createHydrationBootstrap({
298
- hydrationData: normalizeHydrationDataForClient(result.hydrationData, projectRoot),
303
+ hydrationData: normalizeHydrationDataForClient(
304
+ result.hydrationData,
305
+ projectRoot,
306
+ routeClientImports,
307
+ ),
299
308
  assetResolver(moduleId) {
300
309
  return currentAssetResolver(moduleId);
301
310
  },
302
311
  hydrationModuleUrl: currentHydrationModuleUrl,
303
312
  });
304
313
  },
305
- transformDocument({ result, document }) {
314
+ transformDocument({ routeResult, result, document }) {
315
+ const routeClientImports = resolveRouteClientImports(
316
+ routeResult,
317
+ projectRoot,
318
+ currentAssetManifest,
319
+ );
306
320
  return rewriteHydrationDataScript(
307
321
  document,
308
- normalizeHydrationDataForClient(result.hydrationData, projectRoot),
322
+ normalizeHydrationDataForClient(
323
+ result.hydrationData,
324
+ projectRoot,
325
+ routeClientImports,
326
+ ),
309
327
  );
310
328
  },
311
329
  });
@@ -0,0 +1,87 @@
1
+ import { terminal } from "./terminal.js";
2
+
3
+ /**
4
+ * Creates the default development-event sink used by the CLI and dev server.
5
+ * Keeping presentation here lets integrations replace it with onDevelopmentEvent
6
+ * without losing any lifecycle, build, or request diagnostics.
7
+ */
8
+ export function createDevelopmentEventReporter() {
9
+ return (event) => {
10
+ switch (event.type) {
11
+ case "server-starting":
12
+ console.log(`${terminal.cyan("▲")} ${terminal.bold("Evolit")} ${terminal.cyan(event.mode ?? "dev")}`);
13
+ break;
14
+ case "usage":
15
+ console.log(`${terminal.bold("Usage:")} evolit <directory>`);
16
+ console.log(` evolit ${terminal.cyan("init")} <directory>`);
17
+ console.log(` evolit ${terminal.cyan("<dev|build|start>")} [--port 3000]`);
18
+ break;
19
+ case "site-created":
20
+ console.log(`${terminal.green("✓")} Created evolit site: ${terminal.cyan(event.directory)}`);
21
+ break;
22
+ case "build-ready":
23
+ console.log(`${terminal.green("✓")} Built Evolit app: ${terminal.cyan(event.manifestPath)}`);
24
+ break;
25
+ case "initializing":
26
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("○")} Preparing development runtime…`);
27
+ break;
28
+ case "vendor-runtime-ready":
29
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Vendor runtime ready.`);
30
+ break;
31
+ case "client-assets-building":
32
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Compiling client assets (${event.entryCount} entries)…`);
33
+ break;
34
+ case "client-assets-ready":
35
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Client assets compiled in ${terminal.dim(`${event.durationMs}ms`)} (${event.entryCount} entries).`);
36
+ break;
37
+ case "client-assets-cache-hit":
38
+ console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Client assets served from cache.`);
39
+ break;
40
+ case "invalidated":
41
+ console.log(
42
+ `${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Source change detected${event.changedPathCount == null ? "" : ` (${event.changedPathCount} files)`}; invalidated ${event.affectedClientEntryCount} client entries.`,
43
+ );
44
+ break;
45
+ case "unmanaged-import":
46
+ console.warn(
47
+ `${terminal.yellow("[evolit]")} ${terminal.yellow("⚠")} Unmanaged import ${terminal.cyan(JSON.stringify(event.resolvedImportPath))} from ${terminal.cyan(JSON.stringify(event.sourcePath))} is outside the project root. Evolit does not watch it; changes may not trigger a rebuild, and it may be unavailable in another checkout or deployment. It will be emitted under ${terminal.cyan("/_evolit/static/__unmanaged__/")}.`,
48
+ );
49
+ break;
50
+ case "server-ready":
51
+ console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${event.port}`)}`);
52
+ break;
53
+ case "request": {
54
+ const statusColor = event.status >= 500
55
+ ? terminal.red
56
+ : event.status >= 400
57
+ ? terminal.yellow
58
+ : event.status >= 300
59
+ ? terminal.blue
60
+ : terminal.green;
61
+ console.log(
62
+ `${terminal.cyan("[evolit]")} ${terminal.dim(event.method)} ${event.pathname} ${statusColor(event.status)} in ${terminal.dim(`${event.durationMs}ms`)}${event.cacheState ? ` ${terminal.blue(`(${event.cacheState})`)}` : ""}`,
63
+ );
64
+ break;
65
+ }
66
+ case "request-error":
67
+ console.error(
68
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.method} ${event.pathname} failed`,
69
+ event.error,
70
+ );
71
+ break;
72
+ case "fatal-error":
73
+ console.error(
74
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.error instanceof Error ? event.error.stack ?? event.error.message : String(event.error)}`,
75
+ );
76
+ break;
77
+ case "route-error":
78
+ console.error(
79
+ `${terminal.red("[evolit]")} ${terminal.red("✖")} Route rendering failed ${terminal.dim(`(${event.digest})`)} at ${terminal.cyan(event.pathname)}`,
80
+ event.error,
81
+ );
82
+ break;
83
+ default:
84
+ break;
85
+ }
86
+ };
87
+ }
package/src/render.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  isEvolitHttpSignal,
13
13
  runWithRequestContext,
14
14
  } from "./request-context.js";
15
- import { terminal } from "./terminal.js";
15
+ import { createDevelopmentEventReporter } from "./development-events.js";
16
16
  import {
17
17
  LITSX_COMPONENT,
18
18
  LITSX_SERVER_COMPONENT,
@@ -90,10 +90,12 @@ function createBoundaryError(error, { mode, route, reportRouteError }) {
90
90
  if (typeof reportRouteError === "function") {
91
91
  reportRouteError(report);
92
92
  } else {
93
- console.error(
94
- `${terminal.red("[evolit]")} ${terminal.red("✖")} Route rendering failed ${terminal.dim(`(${digest})`)} at ${terminal.cyan(route.pathname)}`,
95
- originalError,
96
- );
93
+ createDevelopmentEventReporter()({
94
+ type: "route-error",
95
+ digest,
96
+ pathname: route.pathname,
97
+ error: originalError,
98
+ });
97
99
  }
98
100
 
99
101
  const boundaryError = new Error("An unexpected server error occurred.");
@@ -1,3 +1,5 @@
1
+ export const DEFAULT_REVALIDATE_SECONDS = 60;
2
+
1
3
  function normalizeRevalidatePolicy(cacheConfig) {
2
4
  if (!cacheConfig || typeof cacheConfig !== "object" || Array.isArray(cacheConfig)) {
3
5
  throw new Error("Expected routeConfig.cache to be an object when using revalidate mode.");
@@ -15,7 +17,7 @@ function normalizeRevalidatePolicy(cacheConfig) {
15
17
  }
16
18
 
17
19
  export function normalizeRouteCachePolicy(routeConfig = {}) {
18
- const cacheConfig = routeConfig?.cache ?? "dynamic";
20
+ const cacheConfig = routeConfig?.cache ?? { revalidate: DEFAULT_REVALIDATE_SECONDS };
19
21
 
20
22
  if (cacheConfig === "dynamic") {
21
23
  return { mode: "dynamic" };
package/src/server.js CHANGED
@@ -9,7 +9,7 @@ import { APP_DIRECTORY, BUILD_DIRECTORY, INTERNAL_DIRECTORY, MANIFEST_FILENAME }
9
9
  import { normalizeClientAssetManifest } from "./client-assets.js";
10
10
  import { pathExists, readJson } from "./fs-utils.js";
11
11
  import { createDefaultRouteCacheKey, resolveResponseCacheRuntime } from "./response-cache.js";
12
- import { terminal } from "./terminal.js";
12
+ import { createDevelopmentEventReporter } from "./development-events.js";
13
13
 
14
14
  function getPort(explicitPort) {
15
15
  const port = explicitPort ?? process.env.PORT ?? "3000";
@@ -141,57 +141,9 @@ async function createRecursiveDirectoryWatcher(rootDirectory, onChange) {
141
141
 
142
142
  async function createServer(projectRoot, mode, explicitPort, options = {}) {
143
143
  const reportDevelopmentEvent = mode === "development"
144
- ? options.onDevelopmentEvent ?? ((event) => {
145
- switch (event.type) {
146
- case "initializing":
147
- console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("○")} Preparing development runtime…`);
148
- break;
149
- case "vendor-runtime-ready":
150
- console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Vendor runtime ready.`);
151
- break;
152
- case "client-assets-building":
153
- console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Compiling client assets (${event.entryCount} entries)…`);
154
- break;
155
- case "client-assets-ready":
156
- console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Client assets compiled in ${terminal.dim(`${event.durationMs}ms`)} (${event.entryCount} entries).`);
157
- break;
158
- case "client-assets-cache-hit":
159
- console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Client assets served from cache.`);
160
- break;
161
- case "invalidated":
162
- console.log(
163
- `${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Source change detected${event.changedPathCount == null ? "" : ` (${event.changedPathCount} files)`}; invalidated ${event.affectedClientEntryCount} client entries.`,
164
- );
165
- break;
166
- case "external-import":
167
- console.warn(
168
- `${terminal.yellow("[evolit]")} ${terminal.yellow("⚠")} Development import ${terminal.cyan(JSON.stringify(event.resolvedImportPath))} from ${terminal.cyan(JSON.stringify(event.sourcePath))} is outside the project root. It will be emitted under ${terminal.cyan("/_evolit/static/__external__/")}.`,
169
- );
170
- break;
171
- case "request": {
172
- const statusColor = event.status >= 500
173
- ? terminal.red
174
- : event.status >= 400
175
- ? terminal.yellow
176
- : event.status >= 300
177
- ? terminal.blue
178
- : terminal.green;
179
- console.log(
180
- `${terminal.cyan("[evolit]")} ${terminal.dim(event.method)} ${event.pathname} ${statusColor(event.status)} in ${terminal.dim(`${event.durationMs}ms`)}${event.cacheState ? ` ${terminal.blue(`(${event.cacheState})`)}` : ""}`,
181
- );
182
- break;
183
- }
184
- case "request-error":
185
- console.error(
186
- `${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.method} ${event.pathname} failed`,
187
- event.error,
188
- );
189
- break;
190
- default:
191
- break;
192
- }
193
- })
144
+ ? options.onDevelopmentEvent ?? createDevelopmentEventReporter()
194
145
  : null;
146
+ reportDevelopmentEvent?.({ type: "server-starting" });
195
147
  const evolitConfig = options.evolitConfig ?? await loadEvolitConfig(projectRoot);
196
148
  const responseCacheRuntime = options.responseCacheStore
197
149
  ? {
@@ -331,6 +283,7 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
331
283
  await new Promise((resolve) => {
332
284
  server.listen(port, resolve);
333
285
  });
286
+ reportDevelopmentEvent?.({ type: "server-ready", port });
334
287
  return server;
335
288
  },
336
289
  async close() {