@constela/start 1.4.3 → 1.5.1

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.
@@ -1,6 +1,13 @@
1
1
  // src/runtime/entry-server.ts
2
2
  import { renderToString } from "@constela/server";
3
3
  async function renderPage(program, ctx) {
4
+ const stateOverrides = {};
5
+ if (ctx.cookies?.["theme"] && program.state?.["theme"]) {
6
+ const themeFromCookie = ctx.cookies["theme"];
7
+ if (themeFromCookie) {
8
+ stateOverrides["theme"] = themeFromCookie;
9
+ }
10
+ }
4
11
  const options = {
5
12
  route: {
6
13
  params: ctx.params,
@@ -8,6 +15,12 @@ async function renderPage(program, ctx) {
8
15
  path: ctx.url
9
16
  }
10
17
  };
18
+ if (ctx.cookies) {
19
+ options.cookies = ctx.cookies;
20
+ }
21
+ if (Object.keys(stateOverrides).length > 0) {
22
+ options.stateOverrides = stateOverrides;
23
+ }
11
24
  if (program.importData) {
12
25
  options.imports = program.importData;
13
26
  }
@@ -3,7 +3,7 @@ import {
3
3
  generateMetaTags,
4
4
  renderPage,
5
5
  wrapHtml
6
- } from "./chunk-XAC4ETQU.js";
6
+ } from "./chunk-LCCIX5MB.js";
7
7
 
8
8
  // src/router/file-router.ts
9
9
  import fg from "fast-glob";
@@ -1221,6 +1221,207 @@ var DataLoader = class {
1221
1221
  }
1222
1222
  };
1223
1223
 
1224
+ // src/api/handler.ts
1225
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
1226
+ function getAllowedMethods(module) {
1227
+ const methods = [];
1228
+ for (const method of HTTP_METHODS) {
1229
+ if (module[method]) {
1230
+ methods.push(method);
1231
+ }
1232
+ }
1233
+ if (module.GET) {
1234
+ methods.push("HEAD");
1235
+ }
1236
+ methods.push("OPTIONS");
1237
+ return methods;
1238
+ }
1239
+ function createMethodNotAllowedResponse(allowedMethods) {
1240
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
1241
+ status: 405,
1242
+ headers: {
1243
+ "Content-Type": "application/json",
1244
+ Allow: allowedMethods.join(", ")
1245
+ }
1246
+ });
1247
+ }
1248
+ function createInternalErrorResponse(error) {
1249
+ const isDev = process.env["NODE_ENV"] !== "production";
1250
+ const message = isDev && error instanceof Error ? error.message : "Internal Server Error";
1251
+ return new Response(JSON.stringify({ error: message }), {
1252
+ status: 500,
1253
+ headers: {
1254
+ "Content-Type": "application/json"
1255
+ }
1256
+ });
1257
+ }
1258
+ function createAPIHandler(module) {
1259
+ return async (ctx) => {
1260
+ const method = ctx.request.method.toUpperCase();
1261
+ const allowedMethods = getAllowedMethods(module);
1262
+ if (method === "OPTIONS") {
1263
+ return new Response(null, {
1264
+ status: 204,
1265
+ headers: {
1266
+ Allow: allowedMethods.join(", ")
1267
+ }
1268
+ });
1269
+ }
1270
+ if (method === "HEAD") {
1271
+ const getHandler = module.GET;
1272
+ if (getHandler) {
1273
+ return new Response(null, { status: 200 });
1274
+ }
1275
+ return createMethodNotAllowedResponse(allowedMethods);
1276
+ }
1277
+ const handler = module[method];
1278
+ if (!handler) {
1279
+ return createMethodNotAllowedResponse(allowedMethods);
1280
+ }
1281
+ try {
1282
+ const response = await handler(ctx);
1283
+ return response;
1284
+ } catch (error) {
1285
+ return createInternalErrorResponse(error);
1286
+ }
1287
+ };
1288
+ }
1289
+
1290
+ // src/utils/resolve-page.ts
1291
+ function isPageExportFunction(exported) {
1292
+ return typeof exported === "function";
1293
+ }
1294
+ async function resolvePageExport(pageDefault, params, expectedParams) {
1295
+ if (expectedParams) {
1296
+ for (const key of expectedParams) {
1297
+ if (!(key in params)) {
1298
+ throw new Error(`Missing required route param: ${key}`);
1299
+ }
1300
+ }
1301
+ }
1302
+ if (isPageExportFunction(pageDefault)) {
1303
+ return await pageDefault(params);
1304
+ }
1305
+ return pageDefault;
1306
+ }
1307
+
1308
+ // src/edge/adapter.ts
1309
+ import { matchRoute } from "@constela/router";
1310
+ import { isCookieInitialExpr } from "@constela/core";
1311
+ async function defaultLoadModule(file) {
1312
+ return import(file);
1313
+ }
1314
+ function isStaticAssetRequest(pathname) {
1315
+ return pathname.startsWith("/_assets/") || pathname.startsWith("/_static/") || pathname.endsWith(".css") || pathname.endsWith(".js") || pathname.endsWith(".map");
1316
+ }
1317
+ function createNotFoundResponse() {
1318
+ return new Response(JSON.stringify({ error: "Not Found" }), {
1319
+ status: 404,
1320
+ headers: { "Content-Type": "application/json" }
1321
+ });
1322
+ }
1323
+ function createErrorResponse(error) {
1324
+ const message = error instanceof Error ? error.message : "Internal Server Error";
1325
+ return new Response(JSON.stringify({ error: message }), {
1326
+ status: 500,
1327
+ headers: { "Content-Type": "application/json" }
1328
+ });
1329
+ }
1330
+ function parseCookies(cookieHeader) {
1331
+ if (!cookieHeader) return {};
1332
+ const cookies = {};
1333
+ for (const part of cookieHeader.split(";")) {
1334
+ const [name, ...valueParts] = part.trim().split("=");
1335
+ if (name) {
1336
+ const value = valueParts.join("=").trim();
1337
+ try {
1338
+ cookies[name.trim()] = decodeURIComponent(value);
1339
+ } catch {
1340
+ cookies[name.trim()] = value;
1341
+ }
1342
+ }
1343
+ }
1344
+ return cookies;
1345
+ }
1346
+ function createAdapter(options) {
1347
+ const { routes, loadModule = defaultLoadModule } = options;
1348
+ async function fetch2(request) {
1349
+ try {
1350
+ const url = new URL(request.url);
1351
+ let pathname = url.pathname;
1352
+ if (pathname !== "/" && pathname.endsWith("/")) {
1353
+ pathname = pathname.slice(0, -1);
1354
+ }
1355
+ if (isStaticAssetRequest(pathname)) {
1356
+ return createNotFoundResponse();
1357
+ }
1358
+ let matchedRoute = null;
1359
+ let matchedParams = {};
1360
+ for (const route of routes) {
1361
+ const match = matchRoute(route.pattern, pathname);
1362
+ if (match) {
1363
+ matchedRoute = route;
1364
+ matchedParams = match.params;
1365
+ break;
1366
+ }
1367
+ }
1368
+ if (!matchedRoute) {
1369
+ return createNotFoundResponse();
1370
+ }
1371
+ const module = await loadModule(matchedRoute.file);
1372
+ if (matchedRoute.type === "api") {
1373
+ const apiModule = module;
1374
+ const ctx = {
1375
+ params: matchedParams,
1376
+ query: url.searchParams,
1377
+ request
1378
+ };
1379
+ const handler = createAPIHandler(apiModule);
1380
+ return await handler(ctx);
1381
+ } else {
1382
+ const pageModule = module;
1383
+ const program = await resolvePageExport(pageModule.default, matchedParams, matchedRoute.params);
1384
+ const cookieHeader = request.headers.get("Cookie");
1385
+ const cookies = parseCookies(cookieHeader);
1386
+ const content = await renderPage(program, {
1387
+ url: request.url,
1388
+ params: matchedParams,
1389
+ query: url.searchParams,
1390
+ cookies
1391
+ });
1392
+ const routeContext = {
1393
+ params: matchedParams,
1394
+ query: Object.fromEntries(url.searchParams.entries()),
1395
+ path: url.pathname
1396
+ };
1397
+ const hydrationScript = generateHydrationScript(program, void 0, routeContext);
1398
+ const themeState = program.state?.["theme"];
1399
+ let initialTheme;
1400
+ if (themeState) {
1401
+ if (isCookieInitialExpr(themeState.initial)) {
1402
+ const cookieValue = cookies[themeState.initial.key];
1403
+ initialTheme = cookieValue ?? themeState.initial.default;
1404
+ } else if (typeof themeState.initial === "string") {
1405
+ initialTheme = themeState.initial;
1406
+ }
1407
+ }
1408
+ const html = wrapHtml(content, hydrationScript, void 0, initialTheme ? {
1409
+ theme: initialTheme,
1410
+ defaultTheme: initialTheme,
1411
+ themeStorageKey: "theme"
1412
+ } : void 0);
1413
+ return new Response(html, {
1414
+ status: 200,
1415
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1416
+ });
1417
+ }
1418
+ } catch (error) {
1419
+ return createErrorResponse(error);
1420
+ }
1421
+ }
1422
+ return { fetch: fetch2 };
1423
+ }
1424
+
1224
1425
  // src/layout/resolver.ts
1225
1426
  import { existsSync as existsSync5, statSync as statSync3, readFileSync as readFileSync3 } from "fs";
1226
1427
  import { join as join5, basename as basename2, dirname } from "path";
@@ -1417,6 +1618,7 @@ import { createServer } from "http";
1417
1618
  import { createReadStream } from "fs";
1418
1619
  import { join as join7, isAbsolute } from "path";
1419
1620
  import { createServer as createViteServer } from "vite";
1621
+ import { isCookieInitialExpr as isCookieInitialExpr2 } from "@constela/core";
1420
1622
 
1421
1623
  // src/json-page-loader.ts
1422
1624
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
@@ -1977,7 +2179,7 @@ var DEFAULT_PORT = 3e3;
1977
2179
  var DEFAULT_HOST = "localhost";
1978
2180
  var DEFAULT_PUBLIC_DIR = "public";
1979
2181
  var DEFAULT_ROUTES_DIR = "src/routes";
1980
- function matchRoute(url, routes) {
2182
+ function matchRoute2(url, routes) {
1981
2183
  const normalizedUrl = url === "/" ? "/" : url.replace(/\/$/, "");
1982
2184
  const urlSegments = normalizedUrl.split("/").filter(Boolean);
1983
2185
  for (const route of routes) {
@@ -2129,7 +2331,7 @@ async function createDevServer(options = {}) {
2129
2331
  });
2130
2332
  return;
2131
2333
  }
2132
- const match = matchRoute(pathname, routes);
2334
+ const match = matchRoute2(pathname, routes);
2133
2335
  if (match) {
2134
2336
  try {
2135
2337
  const projectRoot = process.cwd();
@@ -2163,10 +2365,12 @@ async function createDevServer(options = {}) {
2163
2365
  }
2164
2366
  }
2165
2367
  }
2368
+ const cookies = parseCookies(req.headers.cookie ?? null);
2166
2369
  const ssrContext = {
2167
2370
  url: pathname,
2168
2371
  params: match.params,
2169
- query: url.searchParams
2372
+ query: url.searchParams,
2373
+ cookies
2170
2374
  };
2171
2375
  const content = await renderPage(composedProgram, ssrContext);
2172
2376
  const routeContext = {
@@ -2183,7 +2387,15 @@ async function createDevServer(options = {}) {
2183
2387
  const cssHead = css ? (Array.isArray(css) ? css : [css]).map((p) => `<link rel="stylesheet" href="/${p}">`).join("\n") : "";
2184
2388
  const head = [metaTags, cssHead].filter(Boolean).join("\n");
2185
2389
  const themeState = composedProgram.state?.["theme"];
2186
- const initialTheme = themeState?.initial;
2390
+ let initialTheme;
2391
+ if (themeState) {
2392
+ if (isCookieInitialExpr2(themeState.initial)) {
2393
+ const cookieValue = cookies[themeState.initial.key];
2394
+ initialTheme = cookieValue ?? themeState.initial.default;
2395
+ } else if (typeof themeState.initial === "string") {
2396
+ initialTheme = themeState.initial;
2397
+ }
2398
+ }
2187
2399
  const importMap = {
2188
2400
  "@constela/runtime": "/node_modules/@constela/runtime/dist/index.js",
2189
2401
  "@constela/core": "/node_modules/@constela/core/dist/index.js",
@@ -3142,6 +3354,10 @@ export {
3142
3354
  loadApi,
3143
3355
  generateStaticPaths,
3144
3356
  DataLoader,
3357
+ createAPIHandler,
3358
+ isPageExportFunction,
3359
+ resolvePageExport,
3360
+ createAdapter,
3145
3361
  scanLayouts,
3146
3362
  resolveLayout,
3147
3363
  loadLayout,
package/dist/cli/index.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  hyperlink,
5
5
  loadConfig,
6
6
  resolveConfig
7
- } from "../chunk-72BPUYTI.js";
8
- import "../chunk-XAC4ETQU.js";
7
+ } from "../chunk-W6SJTQAP.js";
8
+ import "../chunk-LCCIX5MB.js";
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -2,11 +2,14 @@ import {
2
2
  DataLoader,
3
3
  LayoutResolver,
4
4
  build,
5
+ createAPIHandler,
6
+ createAdapter,
5
7
  createDevServer,
6
8
  filePathToPattern,
7
9
  generateStaticPaths,
8
10
  getMimeType,
9
11
  hyperlink,
12
+ isPageExportFunction,
10
13
  isPathSafe,
11
14
  loadApi,
12
15
  loadComponentDefinitions,
@@ -18,44 +21,25 @@ import {
18
21
  mdxToConstela,
19
22
  resolveConfig,
20
23
  resolveLayout,
24
+ resolvePageExport,
21
25
  resolveStaticFile,
22
26
  scanLayouts,
23
27
  scanRoutes,
24
28
  transformCsv,
25
29
  transformMdx,
26
30
  transformYaml
27
- } from "./chunk-72BPUYTI.js";
31
+ } from "./chunk-W6SJTQAP.js";
28
32
  import {
29
33
  evaluateMetaExpression,
30
34
  generateHydrationScript,
31
35
  generateMetaTags,
32
36
  renderPage,
33
37
  wrapHtml
34
- } from "./chunk-XAC4ETQU.js";
38
+ } from "./chunk-LCCIX5MB.js";
35
39
 
36
40
  // src/build/ssg.ts
37
41
  import { mkdir, writeFile } from "fs/promises";
38
42
  import { join, dirname } from "path";
39
-
40
- // src/utils/resolve-page.ts
41
- function isPageExportFunction(exported) {
42
- return typeof exported === "function";
43
- }
44
- async function resolvePageExport(pageDefault, params, expectedParams) {
45
- if (expectedParams) {
46
- for (const key of expectedParams) {
47
- if (!(key in params)) {
48
- throw new Error(`Missing required route param: ${key}`);
49
- }
50
- }
51
- }
52
- if (isPageExportFunction(pageDefault)) {
53
- return await pageDefault(params);
54
- }
55
- return pageDefault;
56
- }
57
-
58
- // src/build/ssg.ts
59
43
  var defaultProgram = {
60
44
  version: "1.0",
61
45
  state: {},
@@ -174,72 +158,6 @@ async function generateStaticPages(routes, outDir, options = {}) {
174
158
  return generatedPaths;
175
159
  }
176
160
 
177
- // src/api/handler.ts
178
- var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
179
- function getAllowedMethods(module) {
180
- const methods = [];
181
- for (const method of HTTP_METHODS) {
182
- if (module[method]) {
183
- methods.push(method);
184
- }
185
- }
186
- if (module.GET) {
187
- methods.push("HEAD");
188
- }
189
- methods.push("OPTIONS");
190
- return methods;
191
- }
192
- function createMethodNotAllowedResponse(allowedMethods) {
193
- return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
194
- status: 405,
195
- headers: {
196
- "Content-Type": "application/json",
197
- Allow: allowedMethods.join(", ")
198
- }
199
- });
200
- }
201
- function createInternalErrorResponse(error) {
202
- const isDev = process.env["NODE_ENV"] !== "production";
203
- const message = isDev && error instanceof Error ? error.message : "Internal Server Error";
204
- return new Response(JSON.stringify({ error: message }), {
205
- status: 500,
206
- headers: {
207
- "Content-Type": "application/json"
208
- }
209
- });
210
- }
211
- function createAPIHandler(module) {
212
- return async (ctx) => {
213
- const method = ctx.request.method.toUpperCase();
214
- const allowedMethods = getAllowedMethods(module);
215
- if (method === "OPTIONS") {
216
- return new Response(null, {
217
- status: 204,
218
- headers: {
219
- Allow: allowedMethods.join(", ")
220
- }
221
- });
222
- }
223
- if (method === "HEAD") {
224
- const getHandler = module.GET;
225
- if (getHandler) {
226
- return new Response(null, { status: 200 });
227
- }
228
- return createMethodNotAllowedResponse(allowedMethods);
229
- }
230
- const handler = module[method];
231
- if (!handler) {
232
- return createMethodNotAllowedResponse(allowedMethods);
233
- }
234
- try {
235
- const response = await handler(ctx);
236
- return response;
237
- } catch (error) {
238
- return createInternalErrorResponse(error);
239
- }
240
- };
241
- }
242
-
243
161
  // src/middleware/index.ts
244
162
  function createMiddlewareChain(middlewares) {
245
163
  return async (ctx, finalNext) => {
@@ -254,95 +172,6 @@ function createMiddlewareChain(middlewares) {
254
172
  return dispatch(0);
255
173
  };
256
174
  }
257
-
258
- // src/edge/adapter.ts
259
- import { matchRoute } from "@constela/router";
260
- async function defaultLoadModule(file) {
261
- return import(file);
262
- }
263
- function isStaticAssetRequest(pathname) {
264
- return pathname.startsWith("/_assets/") || pathname.startsWith("/_static/") || pathname.endsWith(".css") || pathname.endsWith(".js") || pathname.endsWith(".map");
265
- }
266
- function createNotFoundResponse() {
267
- return new Response(JSON.stringify({ error: "Not Found" }), {
268
- status: 404,
269
- headers: { "Content-Type": "application/json" }
270
- });
271
- }
272
- function createErrorResponse(error) {
273
- const message = error instanceof Error ? error.message : "Internal Server Error";
274
- return new Response(JSON.stringify({ error: message }), {
275
- status: 500,
276
- headers: { "Content-Type": "application/json" }
277
- });
278
- }
279
- function createAdapter(options) {
280
- const { routes, loadModule = defaultLoadModule } = options;
281
- async function fetch(request) {
282
- try {
283
- const url = new URL(request.url);
284
- let pathname = url.pathname;
285
- if (pathname !== "/" && pathname.endsWith("/")) {
286
- pathname = pathname.slice(0, -1);
287
- }
288
- if (isStaticAssetRequest(pathname)) {
289
- return createNotFoundResponse();
290
- }
291
- let matchedRoute = null;
292
- let matchedParams = {};
293
- for (const route of routes) {
294
- const match = matchRoute(route.pattern, pathname);
295
- if (match) {
296
- matchedRoute = route;
297
- matchedParams = match.params;
298
- break;
299
- }
300
- }
301
- if (!matchedRoute) {
302
- return createNotFoundResponse();
303
- }
304
- const module = await loadModule(matchedRoute.file);
305
- if (matchedRoute.type === "api") {
306
- const apiModule = module;
307
- const ctx = {
308
- params: matchedParams,
309
- query: url.searchParams,
310
- request
311
- };
312
- const handler = createAPIHandler(apiModule);
313
- return await handler(ctx);
314
- } else {
315
- const pageModule = module;
316
- const program = await resolvePageExport(pageModule.default, matchedParams, matchedRoute.params);
317
- const content = await renderPage(program, {
318
- url: request.url,
319
- params: matchedParams,
320
- query: url.searchParams
321
- });
322
- const routeContext = {
323
- params: matchedParams,
324
- query: Object.fromEntries(url.searchParams.entries()),
325
- path: url.pathname
326
- };
327
- const hydrationScript = generateHydrationScript(program, void 0, routeContext);
328
- const themeState = program.state?.["theme"];
329
- const initialTheme = themeState?.initial;
330
- const html = wrapHtml(content, hydrationScript, void 0, initialTheme ? {
331
- theme: initialTheme,
332
- defaultTheme: initialTheme,
333
- themeStorageKey: "theme"
334
- } : void 0);
335
- return new Response(html, {
336
- status: 200,
337
- headers: { "Content-Type": "text/html; charset=utf-8" }
338
- });
339
- }
340
- } catch (error) {
341
- return createErrorResponse(error);
342
- }
343
- }
344
- return { fetch };
345
- }
346
175
  export {
347
176
  DataLoader,
348
177
  LayoutResolver,
@@ -9,6 +9,7 @@ interface SSRContext {
9
9
  url: string;
10
10
  params: Record<string, string>;
11
11
  query: URLSearchParams;
12
+ cookies?: Record<string, string>;
12
13
  }
13
14
  interface WrapHtmlOptions {
14
15
  theme?: 'dark' | 'light';
@@ -4,7 +4,7 @@ import {
4
4
  generateMetaTags,
5
5
  renderPage,
6
6
  wrapHtml
7
- } from "../chunk-XAC4ETQU.js";
7
+ } from "../chunk-LCCIX5MB.js";
8
8
  export {
9
9
  evaluateMetaExpression,
10
10
  generateHydrationScript,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.4.3",
3
+ "version": "1.5.1",
4
4
  "description": "Meta-framework for Constela applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,11 +43,11 @@
43
43
  "postcss": "^8.5.0",
44
44
  "@tailwindcss/postcss": "^4.0.0",
45
45
  "tailwindcss": "^4.0.0",
46
- "@constela/compiler": "0.11.0",
47
- "@constela/core": "0.11.0",
48
- "@constela/runtime": "0.14.0",
49
- "@constela/router": "12.0.0",
50
- "@constela/server": "7.0.0"
46
+ "@constela/compiler": "0.11.1",
47
+ "@constela/core": "0.12.0",
48
+ "@constela/runtime": "0.15.0",
49
+ "@constela/server": "8.0.0",
50
+ "@constela/router": "13.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/mdast": "^4.0.4",