@stacksjs/stx 0.2.176 → 0.2.178

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 (44) hide show
  1. package/dist/builtins/attrs.d.ts +19 -0
  2. package/dist/builtins/attrs.js +18 -0
  3. package/dist/builtins/safe-image.js +2 -1
  4. package/dist/builtins/stx-image.js +2 -1
  5. package/dist/builtins/stx-link.js +2 -1
  6. package/dist/cli-flags.d.ts +34 -0
  7. package/dist/cli-flags.js +20 -0
  8. package/dist/cli.js +899 -778
  9. package/dist/client-script.js +2 -1
  10. package/dist/component-registry.d.ts +9 -0
  11. package/dist/component-renderer.js +20 -1
  12. package/dist/composables/use-fetch.d.ts +6 -1
  13. package/dist/composables/use-fetch.js +40 -18
  14. package/dist/composables/use-query.d.ts +7 -2
  15. package/dist/cookie-serialize.d.ts +40 -0
  16. package/dist/cookie-serialize.js +18 -0
  17. package/dist/craft.js +500 -381
  18. package/dist/crosswind-config.d.ts +13 -0
  19. package/dist/crosswind-config.js +6 -2
  20. package/dist/dev-server/crosswind.js +2 -1
  21. package/dist/edge-runtime.d.ts +2 -9
  22. package/dist/edge-runtime.js +2 -16
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/page-action.d.ts +10 -20
  26. package/dist/page-action.js +33 -9
  27. package/dist/production-server.js +8 -7
  28. package/dist/pwa.js +452 -333
  29. package/dist/reactive.js +2 -3
  30. package/dist/signal-processing.js +37 -6
  31. package/dist/signals.js +150 -32
  32. package/dist/ssg.js +465 -346
  33. package/dist/ssr.js +2 -16
  34. package/dist/stx-virtual-ts.d.ts +45 -1
  35. package/dist/stx-virtual-ts.js +75 -5
  36. package/dist/stx.d.ts +66 -7
  37. package/dist/template-hydrator.d.ts +1 -1
  38. package/dist/template-hydrator.js +6 -6
  39. package/dist/theme-tokens.d.ts +88 -0
  40. package/dist/theme-tokens.js +71 -0
  41. package/dist/typecheck.d.ts +6 -1
  42. package/dist/typecheck.js +7 -6
  43. package/package.json +5 -5
  44. package/stx.d.ts +66 -7
@@ -46,6 +46,19 @@ export interface MergedCrosswindConfig {
46
46
  includePreflight: boolean;
47
47
  /** Honours the user's `minify` key (default false). */
48
48
  minify: boolean;
49
+ /**
50
+ * The `:root` / `.dark` declarations backing the role tokens (#1930).
51
+ *
52
+ * Returned rather than folded into `preflights` because a project can set
53
+ * `preflight: false`, and the tokens are not preflight — they are the values
54
+ * the generated utilities resolve against. Callers prepend this to the CSS
55
+ * they emit.
56
+ *
57
+ * Missing it degrades rather than breaks: every token carries its light value
58
+ * as the `var()` fallback, so light mode stays correct and dark mode reverts
59
+ * to the light colour.
60
+ */
61
+ tokenCSS: string;
49
62
  }
50
63
  /**
51
64
  * Merge a project's crosswind config over the package defaults.
@@ -1,3 +1,4 @@
1
+ import { semanticColors, semanticTokenCSS } from "./theme-tokens.js";
1
2
  function isPlainObject(value) {
2
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
3
4
  }
@@ -8,7 +9,9 @@ export function deepMergeThemes(target, source) {
8
9
  return out;
9
10
  }
10
11
  export function mergeCrosswindConfig(base = {}, user = {}) {
11
- const baseTheme = base.theme || {}, userTheme = user.theme || {}, { extend: userExtend, ...userThemeKeys } = userTheme, { extend: baseExtend, ...baseThemeKeys } = baseTheme, theme = deepMergeThemes(baseThemeKeys, userThemeKeys), extend = deepMergeThemes(baseExtend || {}, userExtend || {});
12
+ const baseTheme = base.theme || {}, userTheme = user.theme || {}, { extend: userExtend, ...userThemeKeys } = userTheme, { extend: baseExtend, ...baseThemeKeys } = baseTheme, basePalette = baseThemeKeys.colors || {};
13
+ baseThemeKeys.colors = { ...basePalette, ...semanticColors(basePalette) };
14
+ const theme = deepMergeThemes(baseThemeKeys, userThemeKeys), extend = deepMergeThemes(baseExtend || {}, userExtend || {});
12
15
  if (Object.keys(extend).length > 0)
13
16
  theme.extend = extend;
14
17
  const safelist = [
@@ -35,6 +38,7 @@ export function mergeCrosswindConfig(base = {}, user = {}) {
35
38
  safelist,
36
39
  shortcuts,
37
40
  includePreflight: user.preflight !== !1,
38
- minify: user.minify === !0
41
+ minify: user.minify === !0,
42
+ tokenCSS: semanticTokenCSS(basePalette)
39
43
  };
40
44
  }
@@ -324,7 +324,7 @@ export async function generateCrosswindCSS(htmlContent, appDir) {
324
324
  configFingerprint = "unhashable";
325
325
  }
326
326
  }
327
- const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCrosswindConfig(baseConfig, userConfig), { safelist, includePreflight, minify } = merged, crosswindConfig = merged.config, generator = new hw.CSSGenerator(crosswindConfig);
327
+ const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCrosswindConfig(baseConfig, userConfig), { safelist, includePreflight, minify, tokenCSS } = merged, crosswindConfig = merged.config, generator = new hw.CSSGenerator(crosswindConfig);
328
328
  for (const cls of safelist)
329
329
  generator.generate(cls);
330
330
  for (const className of classes)
@@ -374,6 +374,7 @@ export async function generateCrosswindCSS(htmlContent, appDir) {
374
374
  css += `
375
375
  @media (prefers-color-scheme: dark) { .dark .${name} { ${darkDecls.join(" ")} } }`;
376
376
  }
377
+ css = tokenCSS + css;
377
378
  setLruCache(cacheKey, css);
378
379
  writeDiskCache(cacheKey, css);
379
380
  return css;
@@ -1,3 +1,4 @@
1
+ import { type SetCookieOptions } from './cookie-serialize';
1
2
  /**
2
3
  * Detect the current edge runtime platform.
3
4
  */
@@ -63,15 +64,7 @@ export declare function parseCookies(request: Request): Record<string, string>;
63
64
  /**
64
65
  * Create a Set-Cookie header value.
65
66
  */
66
- export declare function createCookie(name: string, value: string, options?: {
67
- maxAge?: number
68
- expires?: Date
69
- path?: string
70
- domain?: string
71
- secure?: boolean
72
- httpOnly?: boolean
73
- sameSite?: 'Strict' | 'Lax' | 'None'
74
- }): string;
67
+ export declare function createCookie(name: string, value: string, options?: SetCookieOptions): string;
75
68
  /**
76
69
  * JSON response helper.
77
70
  */
@@ -1,3 +1,4 @@
1
+ import { serializeSetCookie } from "./cookie-serialize.js";
1
2
  import nodeProcess from "node:process";
2
3
  export function detectRuntime() {
3
4
  if (typeof globalThis < "u" && "caches" in globalThis && "CF" in globalThis)
@@ -500,22 +501,7 @@ export function parseCookies(request) {
500
501
  return cookies;
501
502
  }
502
503
  export function createCookie(name, value, options) {
503
- let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
504
- if (options?.maxAge !== void 0)
505
- cookie += `; Max-Age=${options.maxAge}`;
506
- if (options?.expires)
507
- cookie += `; Expires=${options.expires.toUTCString()}`;
508
- if (options?.path)
509
- cookie += `; Path=${options.path}`;
510
- if (options?.domain)
511
- cookie += `; Domain=${options.domain}`;
512
- if (options?.secure)
513
- cookie += "; Secure";
514
- if (options?.httpOnly)
515
- cookie += "; HttpOnly";
516
- if (options?.sameSite)
517
- cookie += `; SameSite=${options.sameSite}`;
518
- return cookie;
504
+ return serializeSetCookie(name, value, options);
519
505
  }
520
506
  export function jsonResponse(data, init) {
521
507
  const headers = new Headers(init?.headers);
package/dist/index.d.ts CHANGED
@@ -205,6 +205,8 @@ export * from './client-helpers';
205
205
  export * from './client-script';
206
206
  export * from './strip-literals';
207
207
  export * from './resolve-stx-targets';
208
+ export * from './cookie-serialize';
209
+ export * from './cli-flags';
208
210
  export * from './component-hmr';
209
211
  export * from './hydration';
210
212
  // ssg moved to stx/ssg
package/dist/index.js CHANGED
@@ -114,6 +114,8 @@ export * from "./client-helpers.js";
114
114
  export * from "./client-script.js";
115
115
  export * from "./strip-literals.js";
116
116
  export * from "./resolve-stx-targets.js";
117
+ export * from "./cookie-serialize.js";
118
+ export * from "./cli-flags.js";
117
119
  export * from "./component-hmr.js";
118
120
  export * from "./hydration.js";
119
121
  export * from "./ssr.js";
@@ -1,3 +1,4 @@
1
+ import { type SetCookieOptions } from './cookie-serialize';
1
2
  /**
2
3
  * Parse a form submission body into a plain object.
3
4
  *
@@ -36,26 +37,7 @@ export declare function runPageAction(context: Record<string, any>, options?: Ru
36
37
  * 303 rather than 302 deliberately: it is the status that makes a browser
37
38
  * follow up with a GET, so a reload or a Back does not resubmit the form.
38
39
  */
39
- export declare function actionRedirectResponse(to: string): Response;
40
- /**
41
- * A page handling its own form submission (stacksjs/stx#1847).
42
- *
43
- * A page that declares `action` in its `<script server>` block receives its own
44
- * non-GET request. Whatever the action returns is merged into the render
45
- * context, so `errors` and `values` repopulate the very same template that drew
46
- * the form — one handler for the no-JS submit and the enhanced one, which is
47
- * the half of the Laravel/Remix/SvelteKit contract stx was missing.
48
- *
49
- * ## Why this is a module rather than two implementations
50
- *
51
- * It shipped in `bun-plugin/src/serve.ts` only, so a form worked under
52
- * `buddy dev` and silently did nothing in production — the request fell through
53
- * to the ordinary render and answered 200 with the pre-submit markup, which is
54
- * indistinguishable from the action having run and found nothing wrong. That is
55
- * the same dev-only-correctness shape as `stx typecheck` being stricter than
56
- * the editor, and this repo has been bitten repeatedly by two hand-maintained
57
- * copies of one rule drifting apart. One module, both servers.
58
- */
40
+ export declare function actionRedirectResponse(to: string, cookies?: readonly string[]): Response;
59
41
  /**
60
42
  * Everything an action is given about the request.
61
43
  *
@@ -72,6 +54,7 @@ export declare interface PageActionContext {
72
54
  /** What running an action produced. */
73
55
  export declare interface PageActionResult {
74
56
  redirect?: string
57
+ cookies?: string[]
75
58
  ran: boolean
76
59
  }
77
60
  /** Request data a caller has already parsed, if it has. */
@@ -81,3 +64,10 @@ export declare interface RunPageActionOptions {
81
64
  params?: Record<string, string>
82
65
  cookies?: Record<string, string>
83
66
  }
67
+ /**
68
+ * A cookie an action wants set on the response.
69
+ *
70
+ * A bare string is the whole cookie for the common case; the object form adds
71
+ * the attributes a session cookie needs.
72
+ */
73
+ export type PageActionCookie = string | (SetCookieOptions & { value: string });
@@ -1,3 +1,4 @@
1
+ import { serializeSetCookie } from "./cookie-serialize.js";
1
2
  export async function parseFormBody(request) {
2
3
  const type = request.headers.get("content-type") || "", form = {}, put = (key, value) => {
3
4
  const existing = form[key];
@@ -46,19 +47,42 @@ export async function runPageAction(context, options = {}) {
46
47
  cookies: options.cookies ?? {}
47
48
  });
48
49
  if (result && typeof result === "object") {
49
- const to = result.redirect;
50
+ const returned = result, cookies = serializeActionCookies(returned.cookies), to = returned.redirect;
50
51
  if (typeof to === "string" && to)
51
- return { redirect: to, ran: !0 };
52
- Object.assign(context, result);
52
+ return { redirect: to, ran: !0, ...cookies.length > 0 ? { cookies } : {} };
53
+ const { cookies: _cookies, redirect: _redirect, ...data } = returned;
54
+ Object.assign(context, data);
55
+ if (cookies.length > 0)
56
+ return { ran: !0, cookies };
53
57
  }
54
58
  return { ran: !0 };
55
59
  }
56
- export function actionRedirectResponse(to) {
57
- return new Response(null, {
58
- status: 303,
59
- headers: {
60
- Location: to,
61
- "Cache-Control": "no-store"
60
+ function serializeActionCookies(value) {
61
+ if (!value || typeof value !== "object" || Array.isArray(value))
62
+ return [];
63
+ const headers = [];
64
+ for (const [name, entry] of Object.entries(value)) {
65
+ if (!name)
66
+ continue;
67
+ if (typeof entry === "string") {
68
+ headers.push(serializeSetCookie(name, entry));
69
+ continue;
62
70
  }
71
+ if (entry && typeof entry === "object") {
72
+ const options = entry;
73
+ if (typeof options.value !== "string")
74
+ continue;
75
+ headers.push(serializeSetCookie(name, options.value, options));
76
+ }
77
+ }
78
+ return headers;
79
+ }
80
+ export function actionRedirectResponse(to, cookies = []) {
81
+ const headers = new Headers({
82
+ Location: to,
83
+ "Cache-Control": "no-store"
63
84
  });
85
+ for (const cookie of cookies)
86
+ headers.append("Set-Cookie", cookie);
87
+ return new Response(null, { status: 303, headers });
64
88
  }
@@ -165,25 +165,26 @@ export async function startProductionServer(options = {}) {
165
165
  }
166
166
  });
167
167
  try {
168
- const { html, boundaries, redirect } = await hydrateTemplateStream(compiled, {
168
+ const { html, boundaries, redirect, cookies } = await hydrateTemplateStream(compiled, {
169
169
  params,
170
170
  request,
171
171
  method: request.method
172
172
  });
173
173
  if (redirect)
174
- return actionRedirectResponse(redirect);
174
+ return actionRedirectResponse(redirect, cookies);
175
175
  if (boundaries && boundaries.length > 0) {
176
176
  const { renderStreamingPage, streamToResponse } = await import("./streaming.js");
177
177
  return streamToResponse(renderStreamingPage(html, boundaries, { timeoutMs: 30000 }), {
178
178
  headers: { "Cache-Control": "no-cache" }
179
179
  });
180
180
  }
181
- return new Response(html, {
182
- headers: {
183
- "Content-Type": "text/html",
184
- "Cache-Control": "no-cache"
185
- }
181
+ const headers = new Headers({
182
+ "Content-Type": "text/html",
183
+ "Cache-Control": "no-cache"
186
184
  });
185
+ for (const cookie of cookies ?? [])
186
+ headers.append("Set-Cookie", cookie);
187
+ return new Response(html, { headers });
187
188
  } catch (error) {
188
189
  console.error(`[stx] Hydration error for ${pathname}:`, error);
189
190
  return new Response(compiled.html, {