@vesk/vesk-cli 0.2.38 → 0.2.40

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.
Files changed (2) hide show
  1. package/dist/cli.js +492 -140
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -379,6 +379,14 @@ var init_ir = __esm({
379
379
  ComponentIR = class {
380
380
  name;
381
381
  paramNames;
382
+ /**
383
+ * The declared first parameter when it is a plain identifier
384
+ * (`component Card(p)`), i.e. the whole props object bound to another name.
385
+ * `null` for a destructuring pattern (`component Card({ title })`) or when
386
+ * there is no parameter. `paramNames` alone cannot express this: `p` and
387
+ * `{ p }` both flatten to `['p']` but need opposite bindings.
388
+ */
389
+ propsAlias;
382
390
  propsType;
383
391
  isClient;
384
392
  isAsync;
@@ -391,6 +399,7 @@ var init_ir = __esm({
391
399
  constructor(name, paramNames, body, opts = {}) {
392
400
  this.name = name;
393
401
  this.paramNames = paramNames;
402
+ this.propsAlias = opts.propsAlias ?? null;
394
403
  this.body = body;
395
404
  this.isClient = opts.isClient ?? false;
396
405
  this.isAsync = opts.isAsync ?? false;
@@ -1540,6 +1549,26 @@ function setActiveComponent(value) {
1540
1549
  function createContext(value) {
1541
1550
  return new Context(value);
1542
1551
  }
1552
+ function createLocals() {
1553
+ const store = {};
1554
+ return {
1555
+ set(key, value) {
1556
+ store[key] = value;
1557
+ },
1558
+ get(key) {
1559
+ return store[key];
1560
+ },
1561
+ has(key) {
1562
+ return Object.prototype.hasOwnProperty.call(store, key);
1563
+ },
1564
+ delete(key) {
1565
+ delete store[key];
1566
+ },
1567
+ all() {
1568
+ return { ...store };
1569
+ }
1570
+ };
1571
+ }
1543
1572
  var ctx, Context;
1544
1573
  var init_context = __esm({
1545
1574
  "../runtime/src/context.ts"() {
@@ -1547,9 +1576,17 @@ var init_context = __esm({
1547
1576
  ctx = { current: null };
1548
1577
  Context = class {
1549
1578
  _v;
1579
+ /** Identity of this context instance — a Map key in the provider chain. */
1580
+ id;
1550
1581
  constructor(value) {
1551
1582
  this._v = value;
1583
+ this.id = Symbol("vesk-context");
1552
1584
  }
1585
+ /**
1586
+ * Reads the nearest value set by an ancestor component, else the default.
1587
+ * Typed as `T`, so a context created with a type carries that type all the
1588
+ * way to every reader.
1589
+ */
1553
1590
  get() {
1554
1591
  let current2 = getActiveComponent();
1555
1592
  while (current2) {
@@ -1558,6 +1595,7 @@ var init_context = __esm({
1558
1595
  }
1559
1596
  return this._v;
1560
1597
  }
1598
+ /** Sets the value for this component and its descendants. Typed as `T`. */
1561
1599
  set(value) {
1562
1600
  const component = getActiveComponent();
1563
1601
  if (component === null) throw new Error("No active component found, cannot set context");
@@ -1999,6 +2037,32 @@ function revalidate(handle2) {
1999
2037
  handle2.controller?.abort();
2000
2038
  startRequest(handle2, true);
2001
2039
  }
2040
+ function mutate(key, data2) {
2041
+ if (isServer()) return;
2042
+ const registry = getRegistry().get(key);
2043
+ const prev = getInflight().get(key);
2044
+ if (data2 !== void 0) {
2045
+ getClientCache().set(key, { data: data2, fetchedAt: Date.now() });
2046
+ if (prev) {
2047
+ getInflight().delete(key);
2048
+ if (registry) registry.forEach((h) => h.controller?.abort());
2049
+ }
2050
+ if (registry) {
2051
+ registry.forEach((h) => {
2052
+ if (h.into) setInto(h.into, data2);
2053
+ set(h.state, { loading: false, error: null, data: data2 });
2054
+ });
2055
+ }
2056
+ return;
2057
+ }
2058
+ if (registry && registry.size > 0) {
2059
+ if (prev) {
2060
+ getInflight().delete(key);
2061
+ registry.forEach((h) => h.controller?.abort());
2062
+ }
2063
+ registry.forEach((h) => startRequest(h, true));
2064
+ }
2065
+ }
2002
2066
  function createResourceAccessor(handle2) {
2003
2067
  const state = handle2.state;
2004
2068
  const accessor = {};
@@ -5342,11 +5406,18 @@ function createRouter(routes, options = {}) {
5342
5406
  if (decision === false) return;
5343
5407
  if (typeof decision === "string" && decision !== url.pathname && (self._guardDepth ?? 0) < 5) {
5344
5408
  self._guardDepth = (self._guardDepth ?? 0) + 1;
5345
- try {
5346
- self.navigate(decision, { replace: true });
5347
- } finally {
5348
- self._guardDepth = 0;
5409
+ const hop = self.navigate(decision, { replace: true });
5410
+ if (hop && typeof hop.then === "function") {
5411
+ return hop.then(
5412
+ () => {
5413
+ self._guardDepth = 0;
5414
+ },
5415
+ () => {
5416
+ self._guardDepth = 0;
5417
+ }
5418
+ );
5349
5419
  }
5420
+ self._guardDepth = 0;
5350
5421
  return;
5351
5422
  }
5352
5423
  proceedNav();
@@ -5767,11 +5838,18 @@ function createFileRouter(routeTree, options = {}) {
5767
5838
  if (decision === false) return;
5768
5839
  if (typeof decision === "string" && decision !== url.pathname && (router._guardDepth || 0) < 5) {
5769
5840
  router._guardDepth = (router._guardDepth || 0) + 1;
5770
- try {
5771
- router.navigate(decision, { replace: true });
5772
- } finally {
5773
- router._guardDepth = 0;
5841
+ const hop = router.navigate(decision, { replace: true });
5842
+ if (hop && typeof hop.then === "function") {
5843
+ return hop.then(
5844
+ () => {
5845
+ router._guardDepth = 0;
5846
+ },
5847
+ () => {
5848
+ router._guardDepth = 0;
5849
+ }
5850
+ );
5774
5851
  }
5852
+ router._guardDepth = 0;
5775
5853
  return;
5776
5854
  }
5777
5855
  afterGuards();
@@ -17629,9 +17707,24 @@ function blankComments(text) {
17629
17707
  continue;
17630
17708
  }
17631
17709
  if (ch === "/" && text[i + 1] === "*") {
17632
- let j = i;
17633
- while (j < n && !(text[j] === "*" && text[j + 1] === "/")) j++;
17634
- const end = Math.min(j + 2, n);
17710
+ if (!lineStart) {
17711
+ i += 2;
17712
+ continue;
17713
+ }
17714
+ let j = i + 2;
17715
+ let end = -1;
17716
+ while (j < n) {
17717
+ if (text[j] === "*" && text[j + 1] === "/") {
17718
+ end = j + 2;
17719
+ break;
17720
+ }
17721
+ j++;
17722
+ }
17723
+ if (end === -1) {
17724
+ lineStart = false;
17725
+ i += 2;
17726
+ continue;
17727
+ }
17635
17728
  for (let k = i; k < end; k++) if (text[k] !== "\n") chars[k] = " ";
17636
17729
  lineStart = false;
17637
17730
  i = end;
@@ -19833,7 +19926,7 @@ function containsJSX(node) {
19833
19926
  return false;
19834
19927
  }
19835
19928
  function slotReadPropName(expr) {
19836
- if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === "props" && expr.property.type === "Identifier") {
19929
+ if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === (__propsParam || "props") && expr.property.type === "Identifier") {
19837
19930
  return expr.property.name;
19838
19931
  }
19839
19932
  return null;
@@ -20037,7 +20130,7 @@ function processJSXChildren(source, children) {
20037
20130
  i++;
20038
20131
  continue;
20039
20132
  }
20040
- if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === "props" && expr.property.type === "Identifier" && expr.property.name === "children" || expr.type === "Identifier" && expr.name === "children") {
20133
+ if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === (__propsParam || "props") && expr.property.type === "Identifier" && expr.property.name === "children" || expr.type === "Identifier" && expr.name === "children") {
20041
20134
  result.push(new SlotNode());
20042
20135
  i++;
20043
20136
  continue;
@@ -20107,7 +20200,7 @@ function exprToIR(source, expr) {
20107
20200
  const consequent = exprToIR(source, expr.right);
20108
20201
  return [new OpaqueDynamicRegion(condExpr, consequent)];
20109
20202
  }
20110
- if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === "props" && expr.property.type === "Identifier" && expr.property.name === "children" || expr.type === "Identifier" && expr.name === "children") {
20203
+ if (expr.type === "MemberExpression" && !expr.computed && expr.object.type === "Identifier" && expr.object.name === (__propsParam || "props") && expr.property.type === "Identifier" && expr.property.name === "children" || expr.type === "Identifier" && expr.name === "children") {
20111
20204
  return [new SlotNode()];
20112
20205
  }
20113
20206
  const slotName = slotReadPropName(expr);
@@ -20628,16 +20721,19 @@ function generateIR(ast, source, filename) {
20628
20721
  }
20629
20722
  const name = inner.id.name;
20630
20723
  const paramNames = getParamNames(inner.params, source);
20724
+ const firstParam = inner.params?.[0];
20725
+ const propsAlias = firstParam && firstParam.type === "Identifier" ? firstParam.name : null;
20631
20726
  const propsType = getPropsType(inner.params, source);
20632
20727
  const bodyStmts = inner.body.body;
20633
20728
  const isClientComp = !!inner.client;
20634
20729
  const prevSlotProps = __slotProps;
20635
20730
  __slotProps = slotPropNamesFromType(propsType);
20731
+ __propsParam = propsAlias || null;
20636
20732
  if (isStatementMode(bodyStmts)) {
20637
20733
  const raw2 = processStatementModeBody(source, bodyStmts, file);
20638
20734
  const { body, css } = extractStyle(raw2);
20639
20735
  validateBlocks(name, isClientComp, body, file, source);
20640
- const comp = new ComponentIR(name, paramNames, body, { mode: "statement", exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
20736
+ const comp = new ComponentIR(name, paramNames, body, { mode: "statement", exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType, propsAlias });
20641
20737
  comp.style = css;
20642
20738
  components.push(comp);
20643
20739
  __slotProps = prevSlotProps;
@@ -20676,7 +20772,7 @@ function generateIR(ast, source, filename) {
20676
20772
  const guardBody = buildGuardChain(source, guardClauses, mainReturn);
20677
20773
  const { body, css } = extractStyle([...preamble, ...guardBody]);
20678
20774
  validateBlocks(name, isClientComp, body, file, source);
20679
- const comp = new ComponentIR(name, paramNames, body, { exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
20775
+ const comp = new ComponentIR(name, paramNames, body, { exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType, propsAlias });
20680
20776
  comp.style = css;
20681
20777
  components.push(comp);
20682
20778
  __slotProps = prevSlotProps;
@@ -20770,7 +20866,7 @@ function generateIR(ast, source, filename) {
20770
20866
  }
20771
20867
  return new IRRoot(components, imports, importedNames, staticProps, loadFn, topLevelCode);
20772
20868
  }
20773
- var __vskAnnotations, __slotProps;
20869
+ var __vskAnnotations, __slotProps, __propsParam;
20774
20870
  var init_ir_generator = __esm({
20775
20871
  "../compiler/src/ir-generator.ts"() {
20776
20872
  "use strict";
@@ -20784,6 +20880,7 @@ var init_ir_generator = __esm({
20784
20880
  init_tokens();
20785
20881
  __vskAnnotations = [];
20786
20882
  __slotProps = null;
20883
+ __propsParam = null;
20787
20884
  }
20788
20885
  });
20789
20886
 
@@ -21634,11 +21731,14 @@ function tryParseImport(code) {
21634
21731
  return null;
21635
21732
  }
21636
21733
  }
21637
- function buildParamInit(paramNames) {
21734
+ function buildParamInit(paramNames, propsAlias = null) {
21735
+ if (propsAlias) {
21736
+ return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
21737
+ }
21738
+ if (paramNames.length === 0) return "";
21638
21739
  if (paramNames.length === 1 && paramNames[0] === "props") {
21639
21740
  return "";
21640
21741
  }
21641
- if (paramNames.length === 0) return "";
21642
21742
  return `const { ${paramNames.join(", ")} } = props;`;
21643
21743
  }
21644
21744
  function resolveComponentName(source) {
@@ -22056,6 +22156,12 @@ function webhook(options) {
22056
22156
  });
22057
22157
  }
22058
22158
  async function webhookHandler(request) {
22159
+ if (request.method !== "POST") {
22160
+ return new Response(JSON.stringify({ error: "Method not allowed" }), {
22161
+ status: 405,
22162
+ headers: { "Content-Type": "application/json", Allow: "POST" }
22163
+ });
22164
+ }
22059
22165
  const signature = request.headers.get(headerName);
22060
22166
  if (!signature) {
22061
22167
  return new Response(JSON.stringify({ error: "Missing signature header" }), {
@@ -22224,7 +22330,7 @@ async function readSignedCookie2(name, cookieString, host) {
22224
22330
  if (impl.readSignedCookie) return impl.readSignedCookie(name, cookieString, host);
22225
22331
  throw new Error("readSignedCookie: @vesk/compiler not available");
22226
22332
  }
22227
- var ServerRequest, ServerResponse, VeskRequest, _VeskResponse, VeskResponse, _signImpl;
22333
+ var ServerRequest, ServerResponse, DEFAULT_MAX_BODY_BYTES2, PayloadTooLargeError, VeskRequest, _VeskResponse, VeskResponse, _signImpl;
22228
22334
  var init_request = __esm({
22229
22335
  "../runtime/src/request.ts"() {
22230
22336
  "use strict";
@@ -22292,6 +22398,14 @@ var init_request = __esm({
22292
22398
  });
22293
22399
  }
22294
22400
  };
22401
+ DEFAULT_MAX_BODY_BYTES2 = 1024 * 1024;
22402
+ PayloadTooLargeError = class extends Error {
22403
+ status = 413;
22404
+ constructor(message = `Request body exceeds limit (${DEFAULT_MAX_BODY_BYTES2} bytes)`) {
22405
+ super(message);
22406
+ this.name = "PayloadTooLargeError";
22407
+ }
22408
+ };
22295
22409
  VeskRequest = class _VeskRequest extends ServerRequest {
22296
22410
  _query;
22297
22411
  _ip;
@@ -22500,14 +22614,35 @@ var init_request = __esm({
22500
22614
  this._flushSecurityHeaders();
22501
22615
  return this;
22502
22616
  }
22617
+ /**
22618
+ * Rejects a body that declares itself over the cap before any bytes are
22619
+ * read. The dev server additionally caps the Node stream itself; this is
22620
+ * the portable guard for platform/serverless builds, so a route that does
22621
+ * `await req.json()` on an oversized payload fails with 413 instead of an
22622
+ * opaque 500.
22623
+ */
22624
+ _assertBodyWithinLimit() {
22625
+ const limit = DEFAULT_MAX_BODY_BYTES2;
22626
+ const declared = Number(this.headers?.get("content-length") || "0");
22627
+ if (Number.isFinite(declared) && declared > limit) {
22628
+ throw new PayloadTooLargeError(`Request body exceeds limit (${limit} bytes)`);
22629
+ }
22630
+ }
22503
22631
  async text() {
22504
22632
  this._flushSecurityHeaders();
22633
+ this._assertBodyWithinLimit();
22505
22634
  return super.text();
22506
22635
  }
22507
22636
  async json() {
22508
22637
  this._flushSecurityHeaders();
22638
+ this._assertBodyWithinLimit();
22509
22639
  return super.json();
22510
22640
  }
22641
+ async formData() {
22642
+ this._flushSecurityHeaders();
22643
+ this._assertBodyWithinLimit();
22644
+ return super.formData();
22645
+ }
22511
22646
  setCookie(name, value, opts = {}) {
22512
22647
  const parts = [`${name}=${encodeURIComponent(value)}`];
22513
22648
  if (opts.httpOnly !== false) parts.push("HttpOnly");
@@ -25697,11 +25832,13 @@ __export(index_server_exports, {
25697
25832
  ArticleSchema: () => ArticleSchema,
25698
25833
  BreadcrumbListSchema: () => BreadcrumbListSchema,
25699
25834
  Context: () => Context,
25835
+ DEFAULT_MAX_BODY_BYTES: () => DEFAULT_MAX_BODY_BYTES2,
25700
25836
  Experiment: () => Experiment,
25701
25837
  FAQPageSchema: () => FAQPageSchema,
25702
25838
  Field: () => Field,
25703
25839
  For: () => For,
25704
25840
  Form: () => Form,
25841
+ HttpError: () => HttpError,
25705
25842
  Image: () => Image,
25706
25843
  JsonLd: () => JsonLd,
25707
25844
  Link: () => Link,
@@ -25713,6 +25850,7 @@ __export(index_server_exports, {
25713
25850
  NotFoundError: () => NotFoundError,
25714
25851
  OrganizationSchema: () => OrganizationSchema,
25715
25852
  Outlet: () => Outlet,
25853
+ PayloadTooLargeError: () => PayloadTooLargeError,
25716
25854
  Portal: () => Portal,
25717
25855
  ProductSchema: () => ProductSchema,
25718
25856
  Redirect: () => Redirect,
@@ -25720,6 +25858,7 @@ __export(index_server_exports, {
25720
25858
  ServerResponse: () => ServerResponse,
25721
25859
  Show: () => Show,
25722
25860
  Switch: () => Switch,
25861
+ TimeoutError: () => TimeoutError,
25723
25862
  VeskRequest: () => VeskRequest,
25724
25863
  VeskResponse: () => VeskResponse,
25725
25864
  VideoSchema: () => VideoSchema,
@@ -25740,6 +25879,7 @@ __export(index_server_exports, {
25740
25879
  cors: () => cors,
25741
25880
  createContext: () => createContext,
25742
25881
  createFileRouter: () => createFileRouter,
25882
+ createLocals: () => createLocals,
25743
25883
  createResource: () => createResource,
25744
25884
  createRouter: () => createRouter,
25745
25885
  create_component_ctx: () => create_component_ctx,
@@ -25771,6 +25911,7 @@ __export(index_server_exports, {
25771
25911
  locals: () => locals,
25772
25912
  maxLength: () => maxLength,
25773
25913
  minLength: () => minLength,
25914
+ mutate: () => mutate,
25774
25915
  notFound: () => notFound,
25775
25916
  on_destroy: () => on_destroy,
25776
25917
  pageIsr: () => pageIsr,
@@ -29896,7 +30037,7 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
29896
30037
  ctx2.push(indent2(`const $root = document.createDocumentFragment();`));
29897
30038
  }
29898
30039
  ctx2.push(indent2(`let __pendingChild = null;`));
29899
- const paramInit = buildParamInit2(comp.paramNames);
30040
+ const paramInit = buildParamInit2(comp.paramNames, comp.propsAlias);
29900
30041
  if (paramInit) ctx2.push(indent2(paramInit));
29901
30042
  for (const node of comp.body) {
29902
30043
  if (node instanceof TrackDecl) {
@@ -29945,7 +30086,8 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
29945
30086
  ctx2.push(`}`);
29946
30087
  return ctx2.getCode();
29947
30088
  }
29948
- function buildParamInit2(paramNames) {
30089
+ function buildParamInit2(paramNames, propsAlias = null) {
30090
+ if (propsAlias) return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
29949
30091
  if (paramNames.length === 1 && paramNames[0] === "props") return "";
29950
30092
  if (paramNames.length === 0) return "";
29951
30093
  return `const { ${paramNames.join(", ")} } = props;`;
@@ -30830,7 +30972,7 @@ function buildComponentEntries(irRoot) {
30830
30972
  const entries = [];
30831
30973
  for (const comp of irRoot.components) {
30832
30974
  const bodyCode = generateFunctionBody(comp, importedNames);
30833
- const paramInit = buildParamInit(comp.paramNames);
30975
+ const paramInit = buildParamInit(comp.paramNames, comp.propsAlias);
30834
30976
  const diag = process.env.VESK_SSR_LOG ? `console.error('[SSR-CALL]', ${JSON.stringify(comp.name)}, props ? JSON.stringify(props) : String(props));
30835
30977
  ` : "";
30836
30978
  const code = `${scopeDecl}${paramInit}${diag}
@@ -36894,6 +37036,54 @@ function isTrackDeclStatement(stmt) {
36894
37036
  (d) => d && d.id && d.id.type === "ArrayPattern" && d.id.lazy === true && d.init
36895
37037
  );
36896
37038
  }
37039
+ function stripCallTypeArgs(text) {
37040
+ let i = 0;
37041
+ while (i < text.length && /\s/.test(text[i])) i++;
37042
+ const nameStart = i;
37043
+ while (i < text.length && /[A-Za-z0-9_$]/.test(text[i])) i++;
37044
+ if (i === nameStart) return text;
37045
+ while (i < text.length && (text[i] === "." || text[i] === "?" || text[i] === "[")) {
37046
+ const ch = text[i];
37047
+ if (ch === "[") {
37048
+ const close = text.indexOf("]", i);
37049
+ if (close === -1) return text;
37050
+ i = close + 1;
37051
+ } else {
37052
+ i++;
37053
+ while (i < text.length && /[A-Za-z0-9_$]/.test(text[i])) i++;
37054
+ }
37055
+ }
37056
+ if (text[i] !== "<") return text;
37057
+ let depth = 0;
37058
+ let quote = "";
37059
+ for (let j = i; j < text.length; j++) {
37060
+ const c = text[j];
37061
+ if (quote) {
37062
+ if (c === "\\") {
37063
+ j++;
37064
+ continue;
37065
+ }
37066
+ if (c === quote) quote = "";
37067
+ continue;
37068
+ }
37069
+ if (c === '"' || c === "'" || c === "`") {
37070
+ quote = c;
37071
+ continue;
37072
+ }
37073
+ if (c === "<") depth++;
37074
+ else if (c === ">") {
37075
+ depth--;
37076
+ if (depth === 0) {
37077
+ const after = text.slice(j + 1);
37078
+ if (/^\s*\(/.test(after)) return text.slice(0, i) + after;
37079
+ return text;
37080
+ }
37081
+ } else if (c === ";" || c === "\n") {
37082
+ return text;
37083
+ }
37084
+ }
37085
+ return text;
37086
+ }
36897
37087
  function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36898
37088
  const fullText = stripTrailingSemicolons(source.slice(stmt.start, stmt.end).trimEnd());
36899
37089
  const text = fullText + (!fullText.endsWith("}") && !isLast ? ";" : "");
@@ -36931,8 +37121,15 @@ function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36931
37121
  if (opts.typedCells) {
36932
37122
  const cellName = g2.cellCount === 0 ? "__cell" : `__cell${g2.cellCount}`;
36933
37123
  g2.cellCount++;
36934
- g2.add(`const ${cellName} = `);
36935
- g2.add(initText, decl.init.start, decl.init.end);
37124
+ if (annotation) {
37125
+ g2.add(`const ${cellName}: Tracked<`);
37126
+ g2.add(annotation, annStart, annEnd);
37127
+ g2.add("> = ");
37128
+ } else {
37129
+ g2.add(`const ${cellName} = `);
37130
+ }
37131
+ const cellInit = annotation ? stripCallTypeArgs(initText) : initText;
37132
+ g2.add(cellInit, decl.init.start, decl.init.end);
36936
37133
  g2.add(";");
36937
37134
  g2.add(" let ");
36938
37135
  g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
@@ -36954,16 +37151,18 @@ function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36954
37151
  g2.add(": ");
36955
37152
  g2.add(annotation, annStart, annEnd);
36956
37153
  g2.add(" = (");
36957
- g2.add(initText, decl.init.start, decl.init.end);
37154
+ g2.add(stripCallTypeArgs(initText), decl.init.start, decl.init.end);
36958
37155
  g2.add(" as unknown as ");
36959
37156
  g2.add(annotation, annStart, annEnd);
36960
37157
  g2.add(");");
36961
37158
  } else {
36962
- g2.add("let ");
36963
- g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
36964
- g2.add(": any = ");
37159
+ const cellName = g2.cellCount === 0 ? "__cell" : `__cell${g2.cellCount}`;
37160
+ g2.cellCount++;
37161
+ g2.add(`const ${cellName} = `);
36965
37162
  g2.add(initText, decl.init.start, decl.init.end);
36966
- g2.add(";");
37163
+ g2.add("; let ");
37164
+ g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
37165
+ g2.add(` = ${cellName}.get();`);
36967
37166
  }
36968
37167
  for (let n = 1; n < names.length; n++) {
36969
37168
  g2.add(" let ");
@@ -37225,10 +37424,18 @@ function emitIf(g2, source, stmt, indent4, opts) {
37225
37424
  emitBlock(g2, source, stmt.alternate, indent4, opts);
37226
37425
  }
37227
37426
  }
37427
+ function emitLoopVariable(g2, source, left) {
37428
+ if (left && left.type === "Identifier") {
37429
+ g2.addRaw("const ");
37430
+ g2.add(source.slice(left.start, left.end), left.start, left.end);
37431
+ return;
37432
+ }
37433
+ g2.add(source.slice(left.start, left.end), left.start, left.end);
37434
+ }
37228
37435
  function emitForOf(g2, source, stmt, indent4, opts) {
37229
37436
  g2.add(indent4);
37230
37437
  g2.addRaw("for (");
37231
- g2.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
37438
+ emitLoopVariable(g2, source, stmt.left);
37232
37439
  g2.addRaw(" of ");
37233
37440
  g2.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
37234
37441
  g2.addRaw(") ");
@@ -37237,7 +37444,7 @@ function emitForOf(g2, source, stmt, indent4, opts) {
37237
37444
  function emitForIn(g2, source, stmt, indent4, opts) {
37238
37445
  g2.add(indent4);
37239
37446
  g2.addRaw("for (");
37240
- g2.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
37447
+ emitLoopVariable(g2, source, stmt.left);
37241
37448
  g2.addRaw(" in ");
37242
37449
  g2.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
37243
37450
  g2.addRaw(") ");
@@ -37705,6 +37912,9 @@ function compileVskCodegen(source, opts = {}) {
37705
37912
  g2.prepend("declare const Head: (props: { children?: Component }) => Component;\n");
37706
37913
  code = g2.code;
37707
37914
  }
37915
+ if (!isModuleAst(ast)) {
37916
+ code += "\nexport {};\n";
37917
+ }
37708
37918
  return { code, mappings: g2.mappings, styleRegions: g2.styleRegions, errors: [] };
37709
37919
  }
37710
37920
  function vskToTsx(source) {
@@ -37972,7 +38182,8 @@ function createTypecheckHost(projectRoot, appDir, vskFiles) {
37972
38182
  resolveModuleNameLiterals(moduleLiterals, containingFile, redirectedReference, options) {
37973
38183
  return moduleLiterals.map(({ text }) => {
37974
38184
  if (text.endsWith(".vsk")) {
37975
- const abs = normalize(join25(dirname27(containingFile), text));
38185
+ const aliased = text.startsWith(".") ? null : resolveAliasModule(text, dirname27(containingFile));
38186
+ const abs = normalize(aliased ?? join25(dirname27(containingFile), text));
37976
38187
  return {
37977
38188
  resolvedModule: {
37978
38189
  resolvedFileName: abs + ".d.ts",
@@ -38055,6 +38266,8 @@ function typecheckProject(projectRoot, opts = {}) {
38055
38266
  esModuleInterop: true,
38056
38267
  allowSyntheticDefaultImports: true,
38057
38268
  allowImportingTsExtensions: true,
38269
+ // Required so the virtual `.vsk` files are actually pulled into the
38270
+ // program (without it tsc silently excludes them and reports nothing).
38058
38271
  allowNonTsExtensions: true,
38059
38272
  resolveJsonModule: true,
38060
38273
  lib: ["lib.es2022.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
@@ -38102,6 +38315,7 @@ var init_typecheck = __esm({
38102
38315
  "use strict";
38103
38316
  init_parser();
38104
38317
  init_vsk_tsx();
38318
+ init_module_imports();
38105
38319
  SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".vesk", ".git", "dist", "tarballs", ".next"]);
38106
38320
  SKIP_FILES = /* @__PURE__ */ new Set(["__vesk_ambient.d.ts", "__vesk_runtime_override.d.ts"]);
38107
38321
  AMBIENT = `
@@ -38476,6 +38690,16 @@ declare namespace JSX {
38476
38690
  }
38477
38691
  declare type Component = string | number | boolean | null | undefined | Component[];
38478
38692
  declare const Head: (props: { children?: Component }) => Component;
38693
+ // Server-side .vsk code reads process.env (NODE_ENV, feature flags, secrets).
38694
+ // @types/node is not installed for browser targets, so declare the slice the
38695
+ // framework documents rather than failing every project that branches on env.
38696
+ declare const process: {
38697
+ env: Record<string, string | undefined>;
38698
+ argv: string[];
38699
+ platform: string;
38700
+ cwd(): string;
38701
+ exit(code?: number): never;
38702
+ };
38479
38703
  interface Tracked<T> {
38480
38704
  get(): T;
38481
38705
  set(value: T): void;
@@ -38493,7 +38717,26 @@ declare function peek<T>(fn: () => T): T;
38493
38717
  declare function tick(): Promise<void>;
38494
38718
  declare function flushSync(fn: () => void): void;
38495
38719
  declare function on_destroy(fn: () => void): void;
38496
- declare function createContext<T>(defaultValue?: T): { id: symbol; defaultValue: T | undefined };
38720
+ /**
38721
+ * A context created by createContext. The value type flows from the default
38722
+ * (or an explicit type argument) through set() and every get().
38723
+ */
38724
+ interface VeskContext<T> {
38725
+ readonly id: symbol;
38726
+ get(): T;
38727
+ set(value: T): void;
38728
+ }
38729
+ declare function createContext<const T>(defaultValue: T): VeskContext<T>;
38730
+ // Typed request-scoped store (middleware locals, event payloads). Declare the
38731
+ // shape once and every set/get is checked against it.
38732
+ interface VeskLocals<T extends object> {
38733
+ set<K extends keyof T>(key: K, value: T[K]): void;
38734
+ get<K extends keyof T>(key: K): T[K];
38735
+ has<K extends keyof T>(key: K): boolean;
38736
+ delete<K extends keyof T>(key: K): void;
38737
+ all(): T;
38738
+ }
38739
+ declare function createLocals<T extends object = Record<string, unknown>>(): VeskLocals<T>;
38497
38740
  // useFetch returns a THENABLE resource, not the data itself. In async
38498
38741
  // components you must \`await\` it before reading/iterating the payload;
38499
38742
  // passing \`into: <tracked cell>\` writes the payload into the cell and makes
@@ -38612,6 +38855,8 @@ declare module '@vesk/runtime' {
38612
38855
  export function track<T>(initialValue: T): Tracked<T>;
38613
38856
  export function track<T>(fn: () => T): Derived<T>;
38614
38857
  export function derived<T>(fn: () => T): Derived<T>;
38858
+ export function createContext<const T>(defaultValue: T): VeskContext<T>;
38859
+ export function createLocals<T extends object = Record<string, unknown>>(): VeskLocals<T>;
38615
38860
  }
38616
38861
  `;
38617
38862
  }
@@ -38672,10 +38917,14 @@ SECURITY_PRESETS["default"] = { ...SECURITY_PRESETS["strict"] };
38672
38917
  function preset(name, overrides = {}) {
38673
38918
  const presets = {
38674
38919
  production: { ...SECURITY_PRESETS["strict"] },
38920
+ // `strict` is the real name of the production preset; both resolve to it.
38921
+ strict: { ...SECURITY_PRESETS["strict"] },
38675
38922
  development: {
38676
38923
  ...SECURITY_PRESETS["strict"],
38677
38924
  contentSecurityPolicy: false
38678
- }
38925
+ },
38926
+ // autoEscape only, SAMEORIGIN framing, no CSRF/HSTS/CSP.
38927
+ minimal: { ...SECURITY_PRESETS["minimal"] }
38679
38928
  };
38680
38929
  if (!presets[name]) {
38681
38930
  throw VeskError.configError(`Unknown security preset: "${name}".`, Object.keys(presets));
@@ -38693,8 +38942,13 @@ function definePlugin(plugin) {
38693
38942
  }
38694
38943
  function defineConfig(config) {
38695
38944
  if (config.security === false || config.security === "off") {
38696
- config.security = {};
38697
- } else if (typeof config.security === "string") {
38945
+ config.security = { autoEscape: true };
38946
+ if (config.routeDataCache === void 0) config.routeDataCache = 0;
38947
+ const mdOff = normalizeMdConfig(config.md);
38948
+ if (mdOff !== void 0) config.md = mdOff;
38949
+ return config;
38950
+ }
38951
+ if (typeof config.security === "string") {
38698
38952
  const p = SECURITY_PRESETS[config.security];
38699
38953
  if (!p) throw VeskError.configError(`Unknown security preset string: "${config.security}".`, Object.keys(SECURITY_PRESETS));
38700
38954
  config.security = { ...p };
@@ -38813,7 +39067,7 @@ init_parser();
38813
39067
  init_ir_generator();
38814
39068
  init_scan();
38815
39069
  init_tokens();
38816
- var DEFAULT_MAX_BODY_BYTES2 = 1024 * 1024;
39070
+ var DEFAULT_MAX_BODY_BYTES3 = 1024 * 1024;
38817
39071
  function isAlnum2(code) {
38818
39072
  return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
38819
39073
  }
@@ -39298,6 +39552,8 @@ async function bundleRuntime(appDir, outDir) {
39298
39552
  "",
39299
39553
  "// Re-export runtime classes used by API routes",
39300
39554
  "export const VeskRequest = __veskRuntime.VeskRequest;",
39555
+ "export const PayloadTooLargeError = __veskRuntime.PayloadTooLargeError;",
39556
+ "export const DEFAULT_MAX_BODY_BYTES = __veskRuntime.DEFAULT_MAX_BODY_BYTES;",
39301
39557
  "export const VeskResponse = __veskRuntime.VeskResponse;",
39302
39558
  "",
39303
39559
  "// Server actions",
@@ -39307,6 +39563,39 @@ async function bundleRuntime(appDir, outDir) {
39307
39563
  "export const validateActionInput = __veskRuntime.validateActionInput;",
39308
39564
  "export const issuesToFieldMap = __veskRuntime.issuesToFieldMap;",
39309
39565
  "",
39566
+ "// API-route surface. `@vesk/runtime/server` imports in app/api/**/route.ts",
39567
+ "// are rewritten to this bundle, so anything a route is documented to",
39568
+ "// import must be re-exported here \u2014 otherwise it resolves in dev (which",
39569
+ "// imports the real barrel) and fails only in a production build.",
39570
+ "export const redirect = __veskRuntime.redirect;",
39571
+ "export const permanentRedirect = __veskRuntime.permanentRedirect;",
39572
+ "export const notFound = __veskRuntime.notFound;",
39573
+ "export const NotFoundError = __veskRuntime.NotFoundError;",
39574
+ "export const useParams = __veskRuntime.useParams;",
39575
+ "export const useBody = __veskRuntime.useBody;",
39576
+ "export const useRequest = __veskRuntime.useRequest;",
39577
+ "export const withValidation = __veskRuntime.withValidation;",
39578
+ "export const cors = __veskRuntime.cors;",
39579
+ "export const webhook = __veskRuntime.webhook;",
39580
+ "export const signCookie = __veskRuntime.signCookie;",
39581
+ "export const unsignCookie = __veskRuntime.unsignCookie;",
39582
+ "export const setSignedCookie = __veskRuntime.setSignedCookie;",
39583
+ "export const readSignedCookie = __veskRuntime.readSignedCookie;",
39584
+ "export const HttpError = __veskRuntime.HttpError;",
39585
+ "export const TimeoutError = __veskRuntime.TimeoutError;",
39586
+ "export const mutate = __veskRuntime.mutate;",
39587
+ "export const runHooks = __veskRuntime.runHooks;",
39588
+ "",
39589
+ "// ISR (server-only; revalidate*/clear* are no-ops outside a request).",
39590
+ "export const isr = __veskRuntime.isr;",
39591
+ "export const pageIsr = __veskRuntime.pageIsr;",
39592
+ "export const componentIsr = __veskRuntime.componentIsr;",
39593
+ "export const revalidatePath = __veskRuntime.revalidatePath;",
39594
+ "export const revalidateTag = __veskRuntime.revalidateTag;",
39595
+ "export const revalidateComponent = __veskRuntime.revalidateComponent;",
39596
+ "export const clearIsrCache = __veskRuntime.clearIsrCache;",
39597
+ "export const isrConfigToRevalidate = __veskRuntime.isrConfigToRevalidate;",
39598
+ "",
39310
39599
  "// Server-wide context store (server events)",
39311
39600
  "export const serverLocals = __veskRuntime.serverLocals;",
39312
39601
  "export const getServerContext = __veskRuntime.getServerContext;",
@@ -39976,19 +40265,122 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
39976
40265
  }
39977
40266
  const paramsCode = extracts.length > 0 ? ` const params = { ${extracts.join(", ")} };
39978
40267
  ` : " const params = {};\n";
39979
- const handlerMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].filter((m) => routeSrc.includes(`async function ${m}(`) || routeSrc.includes(`async function ${m} (`) || routeSrc.includes(`export async function ${m}`)).map((m) => `${m}`).join(", ");
40268
+ const sharedBody = [
40269
+ " const url = new URL(request.url);",
40270
+ " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
40271
+ " const method = request.method || 'GET';",
40272
+ `${paramsCode}`,
40273
+ " Object.defineProperty(request, 'query', {",
40274
+ " get: () => Object.fromEntries(url.searchParams.entries()),",
40275
+ " enumerable: true,",
40276
+ " });",
40277
+ " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};"
40278
+ ];
40279
+ const dispatchBody = [
40280
+ " try {",
40281
+ " const __mod = {",
40282
+ ...["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].map(
40283
+ (m) => ` ${m}: typeof ${m} === 'function' ? ${m} : undefined,`
40284
+ ),
40285
+ " };",
40286
+ " const __allowed = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'].filter((m) => __mod[m]);",
40287
+ " const handler = __mod[method];",
40288
+ " if (!handler) {",
40289
+ " if (method === 'OPTIONS') {",
40290
+ " return new Response(null, { status: 204, headers: { Allow: __allowed.join(', ') } });",
40291
+ " }",
40292
+ " return new Response(JSON.stringify({ error: `Method ${method} not allowed` }), {",
40293
+ " status: 405,",
40294
+ " headers: { 'Content-Type': 'application/json', Allow: __allowed.join(', ') },",
40295
+ " });",
40296
+ " }",
40297
+ " const __routeConfig = (typeof config !== 'undefined' && config) || {};",
40298
+ " // Default CSRF defense for mutating API calls: same-origin check unless",
40299
+ " // the route opts out via `config.csrf = false`.",
40300
+ " if (__routeConfig.csrf !== false && method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {",
40301
+ " try {",
40302
+ " assertSameOrigin({",
40303
+ " method,",
40304
+ " headers: Object.fromEntries([...request.headers.entries()].map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])),",
40305
+ " });",
40306
+ " } catch {",
40307
+ " return new Response(JSON.stringify({ error: 'Cross-origin request blocked' }), {",
40308
+ " status: 403, headers: { 'Content-Type': 'application/json' },",
40309
+ " });",
40310
+ " }",
40311
+ " }",
40312
+ " // Body cap. The handler receives the raw platform Request, so a route that",
40313
+ " // calls `req.json()` directly would bypass a VeskRequest-level guard \u2014",
40314
+ " // enforce it here, before any handler runs, on every target (dev, node,",
40315
+ " // serverless, edge).",
40316
+ " const __declared = Number(request.headers.get('content-length') || '0');",
40317
+ " if (Number.isFinite(__declared) && __declared > DEFAULT_MAX_BODY_BYTES) {",
40318
+ " return new Response(JSON.stringify({ error: `Request body exceeds limit (${DEFAULT_MAX_BODY_BYTES} bytes)` }), {",
40319
+ " status: 413, headers: { 'Content-Type': 'application/json' },",
40320
+ " });",
40321
+ " }",
40322
+ " let __signal = request.signal;",
40323
+ " let __timeout;",
40324
+ " if (__routeConfig.maxDuration) {",
40325
+ " const __controller = new AbortController();",
40326
+ " __timeout = setTimeout(",
40327
+ " () => __controller.abort(new Error(`Request timed out after ${__routeConfig.maxDuration}s`)),",
40328
+ " Number(__routeConfig.maxDuration) * 1000",
40329
+ " );",
40330
+ " __signal = __controller.signal;",
40331
+ " Object.defineProperty(request, 'signal', { value: __signal, writable: false });",
40332
+ " }",
40333
+ " // Route hooks may be a single function or an array of them.",
40334
+ " const __before = typeof beforeRequest === 'undefined' ? [] : (Array.isArray(beforeRequest) ? beforeRequest : [beforeRequest]);",
40335
+ " for (const __hook of __before) {",
40336
+ " const __hookResult = await __hook(request, { params: Promise.resolve(params), locals: ctx.locals });",
40337
+ " if (__hookResult instanceof Response) return __hookResult;",
40338
+ " }",
40339
+ " let __global = await runHooks('beforeRequest', request, { params, locals: ctx.locals });",
40340
+ " if (__global instanceof Response) return __global;",
40341
+ " let __response;",
40342
+ " try {",
40343
+ " __response = await handler(request, { params: Promise.resolve(params) });",
40344
+ " } catch (__e) {",
40345
+ " const __handled = await runHooks('onError', __e, request);",
40346
+ " if (__handled instanceof Response) return __handled;",
40347
+ " throw __e;",
40348
+ " } finally {",
40349
+ " if (__timeout) clearTimeout(__timeout);",
40350
+ " }",
40351
+ " const __after = typeof afterRequest === 'undefined' ? [] : (Array.isArray(afterRequest) ? afterRequest : [afterRequest]);",
40352
+ " for (const __hook of __after) {",
40353
+ " const __hookResult = await __hook(request, __response);",
40354
+ " if (__hookResult instanceof Response) __response = __hookResult;",
40355
+ " }",
40356
+ " __global = await runHooks('afterRequest', request, __response);",
40357
+ " if (__global instanceof Response) __response = __global;",
40358
+ " if (__response && typeof __response.build === 'function') __response.build();",
40359
+ " if (__response instanceof Response) return __response;",
40360
+ " return new Response(JSON.stringify(__response), {",
40361
+ " status: 200,",
40362
+ " headers: { 'Content-Type': 'application/json' },",
40363
+ " });",
40364
+ " } catch (e) {",
40365
+ " const err = /** @type {Error & Record<string, unknown>} */(e);",
40366
+ " if (err.name === 'Redirect') {",
40367
+ " return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || '/') } });",
40368
+ " }",
40369
+ " if (err.name === 'NotFoundError') {",
40370
+ " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40371
+ " }",
40372
+ " if (err.name === 'PayloadTooLargeError') {",
40373
+ " return new Response(JSON.stringify({ error: 'Request body too large' }), { status: 413, headers: { 'Content-Type': 'application/json' } });",
40374
+ " }",
40375
+ " return new Response(JSON.stringify({ error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40376
+ " } finally {",
40377
+ " globalThis.__vesk_request = prev;",
40378
+ " }"
40379
+ ];
39980
40380
  let handleBody;
39981
40381
  if (middlewareCode) {
39982
40382
  handleBody = [
39983
- " const url = new URL(request.url);",
39984
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
39985
- " const method = request.method || 'GET';",
39986
- `${paramsCode}`,
39987
- " Object.defineProperty(request, 'query', {",
39988
- " get: () => Object.fromEntries(url.searchParams.entries()),",
39989
- " enumerable: true,",
39990
- " });",
39991
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
40383
+ ...sharedBody,
39992
40384
  " // \u2500\u2500 Middleware context \u2500\u2500",
39993
40385
  " const __ctx = {",
39994
40386
  " request,",
@@ -40015,47 +40407,11 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40015
40407
  " });",
40016
40408
  " const prev = globalThis.__vesk_request;",
40017
40409
  " globalThis.__vesk_request = ctx;",
40018
- " try {",
40019
- ` const handler = { ${handlerMethods} }[method];`,
40020
- " if (!handler) {",
40021
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
40022
- " status: 405,",
40023
- " headers: { 'Content-Type': 'application/json' },",
40024
- " });",
40025
- " }",
40026
- " const response = await handler(request, { params: Promise.resolve(params) });",
40027
- " if (response instanceof Response) {",
40028
- " if (typeof response.build === 'function') response.build();",
40029
- " return response;",
40030
- " }",
40031
- " return new Response(JSON.stringify(response), {",
40032
- " status: 200,",
40033
- " headers: { 'Content-Type': 'application/json' },",
40034
- " });",
40035
- " } catch (e) {",
40036
- " const err = /** @type {Error & Record<string, unknown>} */(e);",
40037
- " if (err.name === 'Redirect') {",
40038
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
40039
- " }",
40040
- " if (err.name === 'NotFoundError') {",
40041
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40042
- " }",
40043
- " return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40044
- " } finally {",
40045
- " globalThis.__vesk_request = prev;",
40046
- " }"
40410
+ ...dispatchBody
40047
40411
  ].join("\n");
40048
40412
  } else {
40049
40413
  handleBody = [
40050
- " const url = new URL(request.url);",
40051
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
40052
- " const method = request.method || 'GET';",
40053
- `${paramsCode}`,
40054
- " Object.defineProperty(request, 'query', {",
40055
- " get: () => Object.fromEntries(url.searchParams.entries()),",
40056
- " enumerable: true,",
40057
- " });",
40058
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
40414
+ ...sharedBody,
40059
40415
  " const ctx = {",
40060
40416
  " headers: Object.fromEntries(request.headers.entries()),",
40061
40417
  " url: request.url,",
@@ -40069,35 +40425,7 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40069
40425
  " });",
40070
40426
  " const prev = globalThis.__vesk_request;",
40071
40427
  " globalThis.__vesk_request = ctx;",
40072
- " try {",
40073
- ` const handler = { ${handlerMethods} }[method];`,
40074
- " if (!handler) {",
40075
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
40076
- " status: 405,",
40077
- " headers: { 'Content-Type': 'application/json' },",
40078
- " });",
40079
- " }",
40080
- " const response = await handler(request, { params: Promise.resolve(params) });",
40081
- " if (response instanceof Response) {",
40082
- " if (typeof response.build === 'function') response.build();",
40083
- " return response;",
40084
- " }",
40085
- " return new Response(JSON.stringify(response), {",
40086
- " status: 200,",
40087
- " headers: { 'Content-Type': 'application/json' },",
40088
- " });",
40089
- " } catch (e) {",
40090
- " const err = /** @type {Error & Record<string, unknown>} */(e);",
40091
- " if (err.name === 'Redirect') {",
40092
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
40093
- " }",
40094
- " if (err.name === 'NotFoundError') {",
40095
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40096
- " }",
40097
- " return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40098
- " } finally {",
40099
- " globalThis.__vesk_request = prev;",
40100
- " }"
40428
+ ...dispatchBody
40101
40429
  ].join("\n");
40102
40430
  }
40103
40431
  const funcCode = [
@@ -40106,7 +40434,7 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40106
40434
  routeSrc.trim(),
40107
40435
  "",
40108
40436
  "// \u2500\u2500 Request handler wrapper \u2500\u2500",
40109
- "import { parseCookies } from '../runtime.js';",
40437
+ "import { parseCookies, assertSameOrigin, runHooks, DEFAULT_MAX_BODY_BYTES } from '../runtime.js';",
40110
40438
  middlewareCode ? "import { parseCookies as __parseCookies } from '../runtime.js';" : "",
40111
40439
  "",
40112
40440
  middlewareCode || "",
@@ -44296,7 +44624,7 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
44296
44624
  ctx2.push(indent3(`const $root = document.createDocumentFragment();`));
44297
44625
  }
44298
44626
  ctx2.push(indent3(`let __pendingChild = null;`));
44299
- const paramInit = buildParamInit3(comp.paramNames);
44627
+ const paramInit = buildParamInit3(comp.paramNames, comp.propsAlias);
44300
44628
  if (paramInit)
44301
44629
  ctx2.push(indent3(paramInit));
44302
44630
  for (const node of comp.body) {
@@ -44345,7 +44673,9 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
44345
44673
  ctx2.push(`}`);
44346
44674
  return ctx2.getCode();
44347
44675
  }
44348
- function buildParamInit3(paramNames) {
44676
+ function buildParamInit3(paramNames, propsAlias = null) {
44677
+ if (propsAlias)
44678
+ return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
44349
44679
  if (paramNames.length === 1 && paramNames[0] === "props")
44350
44680
  return "";
44351
44681
  if (paramNames.length === 0)
@@ -45434,8 +45764,12 @@ async function executeApiRoute(filePath, method, request, params = {}, locals2 =
45434
45764
  signal = controller.signal;
45435
45765
  Object.defineProperty(request, "signal", { value: signal, writable: false });
45436
45766
  }
45437
- const beforeHooks = mod.beforeRequest || [];
45438
- for (const hook of beforeHooks) {
45767
+ const toHookList = (v) => {
45768
+ if (v == null)
45769
+ return [];
45770
+ return Array.isArray(v) ? v : [v];
45771
+ };
45772
+ for (const hook of toHookList(mod.beforeRequest)) {
45439
45773
  const hookResult = await hook(request, { params, locals: locals2 });
45440
45774
  if (hookResult instanceof Response)
45441
45775
  return hookResult;
@@ -45453,8 +45787,7 @@ async function executeApiRoute(filePath, method, request, params = {}, locals2 =
45453
45787
  return errorResult;
45454
45788
  throw e;
45455
45789
  }
45456
- const afterHooks = mod.afterRequest || [];
45457
- for (const hook of afterHooks) {
45790
+ for (const hook of toHookList(mod.afterRequest)) {
45458
45791
  const hookResult = await hook(request, response);
45459
45792
  if (hookResult instanceof Response)
45460
45793
  response = hookResult;
@@ -52850,7 +53183,7 @@ async function handleActionRequest(req, res, ctx2) {
52850
53183
  let tooLarge = false;
52851
53184
  for await (const chunk of req) {
52852
53185
  bodyTotal += chunk.byteLength;
52853
- if (bodyTotal > (ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES2)) {
53186
+ if (bodyTotal > (ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES3)) {
52854
53187
  tooLarge = true;
52855
53188
  break;
52856
53189
  }
@@ -52858,7 +53191,7 @@ async function handleActionRequest(req, res, ctx2) {
52858
53191
  }
52859
53192
  if (tooLarge) {
52860
53193
  res.writeHead(413, { "Content-Type": "application/json" });
52861
- res.end(JSON.stringify({ ok: false, error: `Request body exceeds limit (${ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES2} bytes)` }));
53194
+ res.end(JSON.stringify({ ok: false, error: `Request body exceeds limit (${ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES3} bytes)` }));
52862
53195
  return true;
52863
53196
  }
52864
53197
  const bodyBuffer = Buffer.concat(chunks);
@@ -53324,8 +53657,8 @@ async function startDevServer(port, projectDir, config, host) {
53324
53657
  if (!process.env.NODE_ENV) process.env.NODE_ENV = "development";
53325
53658
  const secCfg = config.security || {};
53326
53659
  const maxBodyBytes = (typeof secCfg.maxBodyBytes === "number" ? secCfg.maxBodyBytes : void 0) || DEFAULT_MAX_BODY_BYTES;
53327
- const appDirPath = join24(projectDir, "app");
53328
- const publicDir = join24(projectDir, "public");
53660
+ const appDirPath = resolve38(projectDir, typeof config.appDir === "string" ? config.appDir : "./app");
53661
+ const publicDir = resolve38(projectDir, typeof config.publicDir === "string" ? config.publicDir : "./public");
53329
53662
  installMdReadHook2([publicDir]);
53330
53663
  try {
53331
53664
  ensurePackagesBuilt();
@@ -54706,11 +55039,13 @@ __export(index_server_exports2, {
54706
55039
  ArticleSchema: () => ArticleSchema,
54707
55040
  BreadcrumbListSchema: () => BreadcrumbListSchema,
54708
55041
  Context: () => Context,
55042
+ DEFAULT_MAX_BODY_BYTES: () => DEFAULT_MAX_BODY_BYTES2,
54709
55043
  Experiment: () => Experiment,
54710
55044
  FAQPageSchema: () => FAQPageSchema,
54711
55045
  Field: () => Field,
54712
55046
  For: () => For,
54713
55047
  Form: () => Form,
55048
+ HttpError: () => HttpError,
54714
55049
  Image: () => Image,
54715
55050
  JsonLd: () => JsonLd,
54716
55051
  Link: () => Link,
@@ -54722,6 +55057,7 @@ __export(index_server_exports2, {
54722
55057
  NotFoundError: () => NotFoundError,
54723
55058
  OrganizationSchema: () => OrganizationSchema,
54724
55059
  Outlet: () => Outlet,
55060
+ PayloadTooLargeError: () => PayloadTooLargeError,
54725
55061
  Portal: () => Portal,
54726
55062
  ProductSchema: () => ProductSchema,
54727
55063
  Redirect: () => Redirect,
@@ -54729,6 +55065,7 @@ __export(index_server_exports2, {
54729
55065
  ServerResponse: () => ServerResponse,
54730
55066
  Show: () => Show,
54731
55067
  Switch: () => Switch,
55068
+ TimeoutError: () => TimeoutError,
54732
55069
  VeskRequest: () => VeskRequest,
54733
55070
  VeskResponse: () => VeskResponse,
54734
55071
  VideoSchema: () => VideoSchema,
@@ -54749,6 +55086,7 @@ __export(index_server_exports2, {
54749
55086
  cors: () => cors,
54750
55087
  createContext: () => createContext,
54751
55088
  createFileRouter: () => createFileRouter,
55089
+ createLocals: () => createLocals,
54752
55090
  createResource: () => createResource,
54753
55091
  createRouter: () => createRouter,
54754
55092
  create_component_ctx: () => create_component_ctx,
@@ -54780,6 +55118,7 @@ __export(index_server_exports2, {
54780
55118
  locals: () => locals,
54781
55119
  maxLength: () => maxLength,
54782
55120
  minLength: () => minLength,
55121
+ mutate: () => mutate,
54783
55122
  notFound: () => notFound,
54784
55123
  on_destroy: () => on_destroy,
54785
55124
  pageIsr: () => pageIsr,
@@ -54936,12 +55275,20 @@ async function loadConfig(projectDir) {
54936
55275
  }
54937
55276
  return config;
54938
55277
  }
55278
+ function resolveProjectDirs(projectDir, config) {
55279
+ const cfg = config || {};
55280
+ return {
55281
+ appDir: resolve40(projectDir, typeof cfg.appDir === "string" ? cfg.appDir : "./app"),
55282
+ publicDir: resolve40(projectDir, typeof cfg.publicDir === "string" ? cfg.publicDir : "./public"),
55283
+ outDir: resolve40(projectDir, typeof cfg.outDir === "string" ? cfg.outDir : ".vesk")
55284
+ };
55285
+ }
54939
55286
  if (cmd === "build") {
54940
55287
  const projectDir = process.cwd();
54941
- const appDirPath = join26(projectDir, "app");
54942
- const publicDir = join26(projectDir, "public");
55288
+ const rawConfig = await loadConfig(projectDir) || {};
55289
+ const { appDir: appDirPath, publicDir, outDir } = resolveProjectDirs(projectDir, rawConfig);
54943
55290
  if (!existsSync43(appDirPath)) {
54944
- console.error(`vesk build: no app/ directory found in ${projectDir}`);
55291
+ console.error(`vesk build: no app directory found in ${projectDir} (config appDir: ${rawConfig.appDir ?? "./app"})`);
54945
55292
  process.exit(1);
54946
55293
  }
54947
55294
  const restArgs = process.argv.slice(3);
@@ -54951,13 +55298,14 @@ if (cmd === "build") {
54951
55298
  const platform = platformIdx !== -1 ? restArgs[platformIdx + 1] : void 0;
54952
55299
  const targetIdx = restArgs.indexOf("--target");
54953
55300
  const target = targetIdx !== -1 && restArgs[targetIdx + 1] === "edge" ? "edge" : "node";
54954
- const config = await loadConfig(projectDir);
54955
- const plugins = config?.plugins || [];
54956
- const mdCfg = config?.md;
54957
- const opts = { publicDir, plugins, seo, strictSeo: strict, codeSplit: !restArgs.includes("--skip-split"), target };
55301
+ const config = rawConfig;
55302
+ const plugins = config.plugins || [];
55303
+ const mdCfg = config.md;
55304
+ const opts = { outDir, publicDir, plugins, seo, strictSeo: strict, codeSplit: !restArgs.includes("--skip-split"), target };
54958
55305
  if (mdCfg) opts.md = mdCfg;
54959
55306
  if (config.routeDataCache !== void 0) opts.routeDataCache = config.routeDataCache;
54960
55307
  if (platform) opts.platform = platform;
55308
+ if (config.ssg !== void 0 && config.ssg !== false) opts.ssg = true;
54961
55309
  try {
54962
55310
  await build2(appDirPath, opts);
54963
55311
  const { drainMdHtmlWarnings: drainMdHtmlWarnings3 } = await Promise.resolve().then(() => (init_md2(), md_exports2));
@@ -55010,9 +55358,9 @@ ${code}`;
55010
55358
  }
55011
55359
  if (cmd === "seo") {
55012
55360
  const projectDir = process.cwd();
55013
- const appDirPath = join26(projectDir, "app");
55361
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55014
55362
  if (!existsSync43(appDirPath)) {
55015
- console.error(`vesk seo: no app/ directory found in ${projectDir}`);
55363
+ console.error(`vesk seo: no app directory found in ${projectDir}`);
55016
55364
  process.exit(1);
55017
55365
  }
55018
55366
  const strict = args.includes("--strict");
@@ -55025,9 +55373,9 @@ if (cmd === "seo") {
55025
55373
  }
55026
55374
  if (cmd === "typecheck") {
55027
55375
  const projectDir = process.cwd();
55028
- const appDirPath = join26(projectDir, "app");
55376
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55029
55377
  if (!existsSync43(appDirPath)) {
55030
- console.error(`vesk typecheck: no app/ directory found in ${projectDir}`);
55378
+ console.error(`vesk typecheck: no app directory found in ${projectDir}`);
55031
55379
  process.exit(1);
55032
55380
  }
55033
55381
  const { typecheckProject: typecheckProject2, formatTypecheckErrors: formatTypecheckErrors2, formatTypecheckWarnings: formatTypecheckWarnings2 } = await Promise.resolve().then(() => (init_typecheck(), typecheck_exports));
@@ -55050,16 +55398,20 @@ if (cmd === "typecheck") {
55050
55398
  }
55051
55399
  if (cmd === "start") {
55052
55400
  const projectDir = process.cwd();
55053
- const outDir = join26(projectDir, ".vesk");
55401
+ const { outDir } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55054
55402
  const port = parsePortArg(args);
55055
55403
  const host = parseHostArg(args);
55404
+ if (!existsSync43(outDir)) {
55405
+ console.error(`vesk start: no build found at ${outDir} \u2014 run "vesk build" first.`);
55406
+ process.exit(1);
55407
+ }
55056
55408
  startProdServer(outDir, { port, host });
55057
55409
  await new Promise(() => {
55058
55410
  });
55059
55411
  }
55060
55412
  if (cmd === "init") {
55061
55413
  const projectDir = process.cwd();
55062
- const appDirPath = join26(projectDir, "app");
55414
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55063
55415
  const target = join26(appDirPath, "global.css");
55064
55416
  if (existsSync43(target)) {
55065
55417
  console.error(`vesk init: ${target} already exists \u2014 skipping`);
@@ -55079,7 +55431,7 @@ if (cmd === "init") {
55079
55431
  }
55080
55432
  if (cmd === "dev") {
55081
55433
  const projectDir = process.cwd();
55082
- const appDirPath = join26(projectDir, "app");
55434
+ const { appDir: appDirPath, publicDir } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55083
55435
  const port = parsePortArg(args);
55084
55436
  const host = parseHostArg(args);
55085
55437
  if (!existsSync43(appDirPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.38",
12
- "@vesk/compiler": "^0.2.38",
13
- "@vesk/plugin-tailwind": "^0.2.38",
14
- "@vesk/runtime": "^0.2.38",
11
+ "@vesk/adapter": "^0.2.40",
12
+ "@vesk/compiler": "^0.2.40",
13
+ "@vesk/plugin-tailwind": "^0.2.40",
14
+ "@vesk/runtime": "^0.2.40",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",