@tenphi/tasty 0.0.0-snapshot.cfcf770 → 0.0.0-snapshot.d2dcdeb

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 (101) hide show
  1. package/README.md +25 -19
  2. package/dist/compute-styles.js +6 -28
  3. package/dist/compute-styles.js.map +1 -1
  4. package/dist/config.d.ts +41 -1
  5. package/dist/config.js +92 -7
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/index.d.ts +2 -2
  8. package/dist/core/index.js +1 -1
  9. package/dist/debug.js +4 -4
  10. package/dist/debug.js.map +1 -1
  11. package/dist/hooks/useCounterStyle.d.ts +3 -17
  12. package/dist/hooks/useCounterStyle.js +55 -35
  13. package/dist/hooks/useCounterStyle.js.map +1 -1
  14. package/dist/hooks/useFontFace.d.ts +3 -1
  15. package/dist/hooks/useFontFace.js +21 -24
  16. package/dist/hooks/useFontFace.js.map +1 -1
  17. package/dist/hooks/useGlobalStyles.d.ts +18 -2
  18. package/dist/hooks/useGlobalStyles.js +51 -40
  19. package/dist/hooks/useGlobalStyles.js.map +1 -1
  20. package/dist/hooks/useKeyframes.d.ts +4 -2
  21. package/dist/hooks/useKeyframes.js +42 -50
  22. package/dist/hooks/useKeyframes.js.map +1 -1
  23. package/dist/hooks/useProperty.d.ts +4 -2
  24. package/dist/hooks/useProperty.js +29 -41
  25. package/dist/hooks/useProperty.js.map +1 -1
  26. package/dist/hooks/useRawCSS.d.ts +13 -44
  27. package/dist/hooks/useRawCSS.js +90 -21
  28. package/dist/hooks/useRawCSS.js.map +1 -1
  29. package/dist/hooks/useStyles.d.ts +4 -4
  30. package/dist/hooks/useStyles.js +7 -5
  31. package/dist/hooks/useStyles.js.map +1 -1
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +1 -1
  34. package/dist/injector/index.js +1 -1
  35. package/dist/injector/index.js.map +1 -1
  36. package/dist/injector/injector.d.ts +9 -7
  37. package/dist/injector/injector.js +126 -38
  38. package/dist/injector/injector.js.map +1 -1
  39. package/dist/injector/sheet-manager.js +4 -2
  40. package/dist/injector/sheet-manager.js.map +1 -1
  41. package/dist/injector/types.d.ts +11 -2
  42. package/dist/pipeline/parseStateKey.js +4 -4
  43. package/dist/pipeline/parseStateKey.js.map +1 -1
  44. package/dist/plugins/types.d.ts +12 -1
  45. package/dist/rsc-cache.js +79 -0
  46. package/dist/rsc-cache.js.map +1 -0
  47. package/dist/ssr/astro-client.d.ts +1 -0
  48. package/dist/ssr/astro-client.js +19 -0
  49. package/dist/ssr/astro-client.js.map +1 -0
  50. package/dist/ssr/astro-middleware.d.ts +15 -0
  51. package/dist/ssr/astro-middleware.js +19 -0
  52. package/dist/ssr/astro-middleware.js.map +1 -0
  53. package/dist/ssr/astro.d.ts +89 -10
  54. package/dist/ssr/astro.js +112 -27
  55. package/dist/ssr/astro.js.map +1 -1
  56. package/dist/ssr/async-storage.js +14 -4
  57. package/dist/ssr/async-storage.js.map +1 -1
  58. package/dist/ssr/collect-auto-properties.js +28 -9
  59. package/dist/ssr/collect-auto-properties.js.map +1 -1
  60. package/dist/ssr/collector.d.ts +5 -13
  61. package/dist/ssr/collector.js +27 -15
  62. package/dist/ssr/collector.js.map +1 -1
  63. package/dist/ssr/context.js +16 -0
  64. package/dist/ssr/context.js.map +1 -0
  65. package/dist/ssr/hydrate.d.ts +20 -13
  66. package/dist/ssr/hydrate.js +24 -28
  67. package/dist/ssr/hydrate.js.map +1 -1
  68. package/dist/ssr/index.d.ts +3 -3
  69. package/dist/ssr/index.js +4 -4
  70. package/dist/ssr/index.js.map +1 -1
  71. package/dist/ssr/next.d.ts +7 -4
  72. package/dist/ssr/next.js +7 -6
  73. package/dist/ssr/next.js.map +1 -1
  74. package/dist/ssr/ssr-collector-ref.js +19 -2
  75. package/dist/ssr/ssr-collector-ref.js.map +1 -1
  76. package/dist/tasty.d.ts +1 -1
  77. package/dist/tasty.js +9 -4
  78. package/dist/tasty.js.map +1 -1
  79. package/dist/utils/deps-equal.js +15 -0
  80. package/dist/utils/deps-equal.js.map +1 -0
  81. package/dist/utils/hash.js +14 -0
  82. package/dist/utils/hash.js.map +1 -0
  83. package/dist/utils/typography.d.ts +21 -10
  84. package/dist/utils/typography.js +1 -1
  85. package/dist/utils/typography.js.map +1 -1
  86. package/dist/zero/babel.d.ts +7 -108
  87. package/dist/zero/babel.js +36 -12
  88. package/dist/zero/babel.js.map +1 -1
  89. package/docs/README.md +2 -2
  90. package/docs/adoption.md +5 -3
  91. package/docs/comparison.md +24 -25
  92. package/docs/configuration.md +69 -1
  93. package/docs/design-system.md +22 -10
  94. package/docs/dsl.md +3 -3
  95. package/docs/getting-started.md +10 -10
  96. package/docs/injector.md +9 -7
  97. package/docs/methodology.md +2 -2
  98. package/docs/{runtime.md → react-api.md} +17 -32
  99. package/docs/ssr.md +125 -39
  100. package/docs/tasty-static.md +14 -2
  101. package/package.json +9 -3
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,19 @@
1
+ import { hydrateTastyClasses } from "./hydrate.js";
2
+ //#region src/ssr/astro-client.ts
3
+ /**
4
+ * Client-side cache hydration for Astro islands.
5
+ *
6
+ * Reads the class name list from `window.__TASTY__` (populated by
7
+ * inline scripts emitted during SSR) and pre-populates the injector
8
+ * so island hydration skips the style pipeline entirely.
9
+ *
10
+ * This module is browser-safe — it does NOT import node:async_hooks.
11
+ *
12
+ * Usage:
13
+ * - Automatically injected by tastyIntegration() via injectScript('before-hydration')
14
+ * - Can be imported manually: `import '@tenphi/tasty/ssr/astro-client'`
15
+ */
16
+ if (typeof window !== "undefined") hydrateTastyClasses();
17
+ //#endregion
18
+
19
+ //# sourceMappingURL=astro-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astro-client.js","names":[],"sources":["../../src/ssr/astro-client.ts"],"sourcesContent":["/**\n * Client-side cache hydration for Astro islands.\n *\n * Reads the class name list from `window.__TASTY__` (populated by\n * inline scripts emitted during SSR) and pre-populates the injector\n * so island hydration skips the style pipeline entirely.\n *\n * This module is browser-safe — it does NOT import node:async_hooks.\n *\n * Usage:\n * - Automatically injected by tastyIntegration() via injectScript('before-hydration')\n * - Can be imported manually: `import '@tenphi/tasty/ssr/astro-client'`\n */\n\nimport { hydrateTastyClasses } from './hydrate';\n\nif (typeof window !== 'undefined') {\n hydrateTastyClasses();\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,IAAI,OAAO,WAAW,YACpB,sBAAqB"}
@@ -0,0 +1,15 @@
1
+ //#region src/ssr/astro-middleware.d.ts
2
+ /**
3
+ * Astro middleware entrypoint for tastyIntegration().
4
+ *
5
+ * Referenced by the integration via addMiddleware(). Not intended
6
+ * as a public package export — use tastyMiddleware() directly if
7
+ * you need manual middleware composition.
8
+ *
9
+ * The transferCache setting is controlled by setMiddlewareTransferCache(),
10
+ * called by tastyIntegration() before middleware is loaded.
11
+ */
12
+ declare const onRequest: (_context: unknown, next: () => Promise<Response>) => Promise<Response>;
13
+ //#endregion
14
+ export { onRequest };
15
+ //# sourceMappingURL=astro-middleware.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { getMiddlewareTransferCache, tastyMiddleware } from "./astro.js";
2
+ //#region src/ssr/astro-middleware.ts
3
+ /**
4
+ * Astro middleware entrypoint for tastyIntegration().
5
+ *
6
+ * Referenced by the integration via addMiddleware(). Not intended
7
+ * as a public package export — use tastyMiddleware() directly if
8
+ * you need manual middleware composition.
9
+ *
10
+ * The transferCache setting is controlled by setMiddlewareTransferCache(),
11
+ * called by tastyIntegration() before middleware is loaded.
12
+ */
13
+ const onRequest = tastyMiddleware({ get transferCache() {
14
+ return getMiddlewareTransferCache();
15
+ } });
16
+ //#endregion
17
+ export { onRequest };
18
+
19
+ //# sourceMappingURL=astro-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astro-middleware.js","names":[],"sources":["../../src/ssr/astro-middleware.ts"],"sourcesContent":["/**\n * Astro middleware entrypoint for tastyIntegration().\n *\n * Referenced by the integration via addMiddleware(). Not intended\n * as a public package export — use tastyMiddleware() directly if\n * you need manual middleware composition.\n *\n * The transferCache setting is controlled by setMiddlewareTransferCache(),\n * called by tastyIntegration() before middleware is loaded.\n */\n\nimport { getMiddlewareTransferCache, tastyMiddleware } from './astro';\n\nexport const onRequest = tastyMiddleware({\n get transferCache() {\n return getMiddlewareTransferCache();\n },\n});\n"],"mappings":";;;;;;;;;;;;AAaA,MAAa,YAAY,gBAAgB,EACvC,IAAI,gBAAgB;AAClB,QAAO,4BAA4B;GAEtC,CAAC"}
@@ -1,29 +1,108 @@
1
- import { hydrateTastyCache } from "./hydrate.js";
2
-
3
1
  //#region src/ssr/astro.d.ts
2
+ /**
3
+ * Astro integration for Tasty SSR.
4
+ *
5
+ * Provides:
6
+ * - tastyIntegration() — Astro Integration API (recommended)
7
+ * - tastyMiddleware() — manual middleware for advanced composition
8
+ *
9
+ * Import from '@tenphi/tasty/ssr/astro'.
10
+ */
4
11
  interface TastyMiddlewareOptions {
5
12
  /**
6
- * Whether to embed the cache state script for client hydration.
7
- * Set to false to skip cache transfer. Default: true.
13
+ * Whether to embed the class-list script for client hydration.
14
+ * Set to false to skip class transfer (e.g. for CSP restrictions).
15
+ * Without it, client components may re-inject CSS that already exists
16
+ * in server-rendered `<style>` tags. Default: true.
8
17
  */
9
18
  transferCache?: boolean;
10
19
  }
11
20
  /**
12
21
  * Create an Astro middleware that collects Tasty styles during SSR.
13
22
  *
14
- * All React components rendered during the request (both static
15
- * and islands) will have their useStyles() calls captured by the
16
- * collector via AsyncLocalStorage. After rendering, the middleware
17
- * injects the collected CSS into </head>.
23
+ * All React components rendered during the request will have their
24
+ * computeStyles() calls captured by the collector via AsyncLocalStorage.
25
+ * After rendering, the middleware injects the collected CSS into </head>.
18
26
  *
19
- * @example
27
+ * @example Manual middleware setup
20
28
  * ```ts
21
29
  * // src/middleware.ts
22
30
  * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
23
31
  * export const onRequest = tastyMiddleware();
24
32
  * ```
33
+ *
34
+ * @example Composing with other middleware
35
+ * ```ts
36
+ * // src/middleware.ts
37
+ * import { sequence } from 'astro:middleware';
38
+ * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
39
+ *
40
+ * export const onRequest = sequence(
41
+ * tastyMiddleware(),
42
+ * myOtherMiddleware,
43
+ * );
44
+ * ```
25
45
  */
26
46
  declare function tastyMiddleware(options?: TastyMiddlewareOptions): (_context: unknown, next: () => Promise<Response>) => Promise<Response>;
47
+ /** @internal */
48
+ declare function setMiddlewareTransferCache(value: boolean): void;
49
+ /** @internal */
50
+ declare function getMiddlewareTransferCache(): boolean;
51
+ interface TastyIntegrationOptions {
52
+ /**
53
+ * Enable island hydration support.
54
+ *
55
+ * When `true` (default): injects a client hydration script via
56
+ * `injectScript('before-hydration')` and sets `transferCache: true`
57
+ * on the middleware. Islands skip the style pipeline during hydration.
58
+ *
59
+ * When `false`: no client JS is shipped and `transferCache` is set
60
+ * to `false`. Use this for fully static sites without `client:*`
61
+ * directives.
62
+ */
63
+ islands?: boolean;
64
+ }
65
+ /**
66
+ * Astro integration that automatically sets up Tasty SSR.
67
+ *
68
+ * Registers middleware for cross-component CSS deduplication and
69
+ * optionally injects a client hydration script for island support.
70
+ *
71
+ * @example Basic setup (with islands)
72
+ * ```ts
73
+ * // astro.config.mjs
74
+ * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
75
+ *
76
+ * export default defineConfig({
77
+ * integrations: [tastyIntegration()],
78
+ * });
79
+ * ```
80
+ *
81
+ * @example Static-only (no client JS)
82
+ * ```ts
83
+ * // astro.config.mjs
84
+ * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
85
+ *
86
+ * export default defineConfig({
87
+ * integrations: [tastyIntegration({ islands: false })],
88
+ * });
89
+ * ```
90
+ */
91
+ declare function tastyIntegration(options?: TastyIntegrationOptions): {
92
+ name: string;
93
+ hooks: {
94
+ 'astro:config:setup': ({
95
+ addMiddleware,
96
+ injectScript
97
+ }: {
98
+ addMiddleware: (middleware: {
99
+ entrypoint: string | URL;
100
+ order: "pre" | "post";
101
+ }) => void;
102
+ injectScript: (stage: string, content: string) => void;
103
+ }) => void;
104
+ };
105
+ };
27
106
  //#endregion
28
- export { TastyMiddlewareOptions, hydrateTastyCache, tastyMiddleware };
107
+ export { TastyIntegrationOptions, TastyMiddlewareOptions, getMiddlewareTransferCache, setMiddlewareTransferCache, tastyIntegration, tastyMiddleware };
29
108
  //# sourceMappingURL=astro.d.ts.map
package/dist/ssr/astro.js CHANGED
@@ -1,64 +1,149 @@
1
1
  import { getConfig } from "../config.js";
2
- import { registerSSRCollectorGetter } from "./ssr-collector-ref.js";
2
+ import { registerSSRCollectorGetterGlobal } from "./ssr-collector-ref.js";
3
3
  import { ServerStyleCollector } from "./collector.js";
4
4
  import { getSSRCollector, runWithCollector } from "./async-storage.js";
5
- import { hydrateTastyCache } from "./hydrate.js";
6
5
  //#region src/ssr/astro.ts
7
6
  /**
8
7
  * Astro integration for Tasty SSR.
9
8
  *
10
- * Provides tastyMiddleware() for Astro's middleware system.
11
- * The middleware wraps request handling in a ServerStyleCollector
12
- * via AsyncLocalStorage, then injects collected CSS into </head>.
9
+ * Provides:
10
+ * - tastyIntegration() Astro Integration API (recommended)
11
+ * - tastyMiddleware() — manual middleware for advanced composition
13
12
  *
14
13
  * Import from '@tenphi/tasty/ssr/astro'.
15
14
  */
16
- registerSSRCollectorGetter(getSSRCollector);
15
+ registerSSRCollectorGetterGlobal(getSSRCollector);
17
16
  /**
18
17
  * Create an Astro middleware that collects Tasty styles during SSR.
19
18
  *
20
- * All React components rendered during the request (both static
21
- * and islands) will have their useStyles() calls captured by the
22
- * collector via AsyncLocalStorage. After rendering, the middleware
23
- * injects the collected CSS into </head>.
19
+ * All React components rendered during the request will have their
20
+ * computeStyles() calls captured by the collector via AsyncLocalStorage.
21
+ * After rendering, the middleware injects the collected CSS into </head>.
24
22
  *
25
- * @example
23
+ * @example Manual middleware setup
26
24
  * ```ts
27
25
  * // src/middleware.ts
28
26
  * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
29
27
  * export const onRequest = tastyMiddleware();
30
28
  * ```
29
+ *
30
+ * @example Composing with other middleware
31
+ * ```ts
32
+ * // src/middleware.ts
33
+ * import { sequence } from 'astro:middleware';
34
+ * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
35
+ *
36
+ * export const onRequest = sequence(
37
+ * tastyMiddleware(),
38
+ * myOtherMiddleware,
39
+ * );
40
+ * ```
31
41
  */
32
42
  function tastyMiddleware(options) {
33
- const { transferCache = true } = options ?? {};
34
43
  return async (_context, next) => {
44
+ const transferCache = options?.transferCache ?? true;
35
45
  const collector = new ServerStyleCollector();
36
- const response = await runWithCollector(collector, () => next());
46
+ const rendered = await runWithCollector(collector, async () => {
47
+ const response = await next();
48
+ const body = response.body;
49
+ if (!body) return {
50
+ html: null,
51
+ status: response.status,
52
+ headers: response.headers
53
+ };
54
+ const reader = body.pipeThrough(new TextDecoderStream()).getReader();
55
+ const parts = [];
56
+ for (;;) {
57
+ const { done, value } = await reader.read();
58
+ if (done) break;
59
+ parts.push(value);
60
+ }
61
+ return {
62
+ html: parts.join(""),
63
+ status: response.status,
64
+ headers: response.headers
65
+ };
66
+ });
67
+ if (!rendered.html) return new Response(null, {
68
+ status: rendered.status,
69
+ headers: rendered.headers
70
+ });
71
+ let { html } = rendered;
37
72
  const css = collector.getCSS();
38
- if (!css) return response;
73
+ if (!css) return new Response(html, {
74
+ status: rendered.status,
75
+ headers: rendered.headers
76
+ });
39
77
  const nonce = getConfig().nonce;
40
78
  const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
41
- const html = await response.text();
42
79
  const styleTag = `<style data-tasty-ssr${nonceAttr}>${css}</style>`;
43
80
  let cacheTag = "";
44
81
  if (transferCache) {
45
- const cacheState = collector.getCacheState();
46
- if (Object.keys(cacheState.entries).length > 0) cacheTag = `<script data-tasty-cache type="application/json"${nonceAttr}>${JSON.stringify(cacheState)}<\/script>`;
82
+ const classNames = collector.getRenderedClassNames();
83
+ if (classNames.length > 0) cacheTag = `<script${nonceAttr}>(window.__TASTY__=window.__TASTY__||[]).push(${classNames.map((n) => `"${n}"`).join(",")})<\/script>`;
47
84
  }
48
- const modifiedHtml = html.replace("</head>", `${styleTag}${cacheTag}</head>`);
49
- return new Response(modifiedHtml, {
50
- status: response.status,
51
- headers: response.headers
85
+ const injection = styleTag + cacheTag;
86
+ const idx = html.indexOf("</head>");
87
+ if (idx !== -1) html = html.slice(0, idx) + injection + html.slice(idx);
88
+ else html = injection + html;
89
+ const headers = new Headers(rendered.headers);
90
+ headers.delete("content-length");
91
+ return new Response(html, {
92
+ status: rendered.status,
93
+ headers
52
94
  });
53
95
  };
54
96
  }
55
- if (typeof window !== "undefined") {
56
- const script = document.querySelector("script[data-tasty-cache]");
57
- if (script) try {
58
- hydrateTastyCache(JSON.parse(script.textContent));
59
- } catch {}
97
+ let _middlewareTransferCache = true;
98
+ /** @internal */
99
+ function setMiddlewareTransferCache(value) {
100
+ _middlewareTransferCache = value;
101
+ }
102
+ /** @internal */
103
+ function getMiddlewareTransferCache() {
104
+ return _middlewareTransferCache;
105
+ }
106
+ /**
107
+ * Astro integration that automatically sets up Tasty SSR.
108
+ *
109
+ * Registers middleware for cross-component CSS deduplication and
110
+ * optionally injects a client hydration script for island support.
111
+ *
112
+ * @example Basic setup (with islands)
113
+ * ```ts
114
+ * // astro.config.mjs
115
+ * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
116
+ *
117
+ * export default defineConfig({
118
+ * integrations: [tastyIntegration()],
119
+ * });
120
+ * ```
121
+ *
122
+ * @example Static-only (no client JS)
123
+ * ```ts
124
+ * // astro.config.mjs
125
+ * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
126
+ *
127
+ * export default defineConfig({
128
+ * integrations: [tastyIntegration({ islands: false })],
129
+ * });
130
+ * ```
131
+ */
132
+ function tastyIntegration(options) {
133
+ const { islands = true } = options ?? {};
134
+ setMiddlewareTransferCache(islands);
135
+ return {
136
+ name: "@tenphi/tasty",
137
+ hooks: { "astro:config:setup": ({ addMiddleware, injectScript }) => {
138
+ addMiddleware({
139
+ entrypoint: new URL("./astro-middleware.js", import.meta.url),
140
+ order: "pre"
141
+ });
142
+ if (islands) injectScript("before-hydration", `import "@tenphi/tasty/ssr/astro-client";`);
143
+ } }
144
+ };
60
145
  }
61
146
  //#endregion
62
- export { hydrateTastyCache, tastyMiddleware };
147
+ export { getMiddlewareTransferCache, setMiddlewareTransferCache, tastyIntegration, tastyMiddleware };
63
148
 
64
149
  //# sourceMappingURL=astro.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"astro.js","names":[],"sources":["../../src/ssr/astro.ts"],"sourcesContent":["/**\n * Astro integration for Tasty SSR.\n *\n * Provides tastyMiddleware() for Astro's middleware system.\n * The middleware wraps request handling in a ServerStyleCollector\n * via AsyncLocalStorage, then injects collected CSS into </head>.\n *\n * Import from '@tenphi/tasty/ssr/astro'.\n */\n\nimport { getConfig } from '../config';\nimport { getSSRCollector, runWithCollector } from './async-storage';\nimport { ServerStyleCollector } from './collector';\nimport { hydrateTastyCache } from './hydrate';\nimport { registerSSRCollectorGetter } from './ssr-collector-ref';\n\n// Wire up ALS-based collector discovery so useStyles can find\n// the collector set by tastyMiddleware's runWithCollector().\nregisterSSRCollectorGetter(getSSRCollector);\n\n// Re-export for convenience\nexport { hydrateTastyCache };\n\nexport interface TastyMiddlewareOptions {\n /**\n * Whether to embed the cache state script for client hydration.\n * Set to false to skip cache transfer. Default: true.\n */\n transferCache?: boolean;\n}\n\n/**\n * Create an Astro middleware that collects Tasty styles during SSR.\n *\n * All React components rendered during the request (both static\n * and islands) will have their useStyles() calls captured by the\n * collector via AsyncLocalStorage. After rendering, the middleware\n * injects the collected CSS into </head>.\n *\n * @example\n * ```ts\n * // src/middleware.ts\n * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';\n * export const onRequest = tastyMiddleware();\n * ```\n */\nexport function tastyMiddleware(options?: TastyMiddlewareOptions) {\n const { transferCache = true } = options ?? {};\n\n return async (\n _context: unknown,\n next: () => Promise<Response>,\n ): Promise<Response> => {\n const collector = new ServerStyleCollector();\n\n const response = await runWithCollector(collector, () => next());\n\n const css = collector.getCSS();\n if (!css) return response;\n\n const nonce = getConfig().nonce;\n const nonceAttr = nonce ? ` nonce=\"${nonce}\"` : '';\n const html = await response.text();\n const styleTag = `<style data-tasty-ssr${nonceAttr}>${css}</style>`;\n\n let cacheTag = '';\n if (transferCache) {\n const cacheState = collector.getCacheState();\n const hasHydratableStyles = Object.keys(cacheState.entries).length > 0;\n if (hasHydratableStyles) {\n cacheTag = `<script data-tasty-cache type=\"application/json\"${nonceAttr}>${JSON.stringify(cacheState)}</script>`;\n }\n }\n\n const modifiedHtml = html.replace(\n '</head>',\n `${styleTag}${cacheTag}</head>`,\n );\n\n return new Response(modifiedHtml, {\n status: response.status,\n headers: response.headers,\n });\n };\n}\n\n// Client-side auto-hydration.\n// When imported in the browser, reads the cache state from the DOM\n// and pre-populates the injector before any island hydrates.\nif (typeof window !== 'undefined') {\n const script = document.querySelector('script[data-tasty-cache]');\n if (script) {\n try {\n const state = JSON.parse(script.textContent!);\n hydrateTastyCache(state);\n } catch {\n // Ignore malformed cache state\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,2BAA2B,gBAAgB;;;;;;;;;;;;;;;;AA4B3C,SAAgB,gBAAgB,SAAkC;CAChE,MAAM,EAAE,gBAAgB,SAAS,WAAW,EAAE;AAE9C,QAAO,OACL,UACA,SACsB;EACtB,MAAM,YAAY,IAAI,sBAAsB;EAE5C,MAAM,WAAW,MAAM,iBAAiB,iBAAiB,MAAM,CAAC;EAEhE,MAAM,MAAM,UAAU,QAAQ;AAC9B,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,QAAQ,WAAW,CAAC;EAC1B,MAAM,YAAY,QAAQ,WAAW,MAAM,KAAK;EAChD,MAAM,OAAO,MAAM,SAAS,MAAM;EAClC,MAAM,WAAW,wBAAwB,UAAU,GAAG,IAAI;EAE1D,IAAI,WAAW;AACf,MAAI,eAAe;GACjB,MAAM,aAAa,UAAU,eAAe;AAE5C,OAD4B,OAAO,KAAK,WAAW,QAAQ,CAAC,SAAS,EAEnE,YAAW,mDAAmD,UAAU,GAAG,KAAK,UAAU,WAAW,CAAC;;EAI1G,MAAM,eAAe,KAAK,QACxB,WACA,GAAG,WAAW,SAAS,SACxB;AAED,SAAO,IAAI,SAAS,cAAc;GAChC,QAAQ,SAAS;GACjB,SAAS,SAAS;GACnB,CAAC;;;AAON,IAAI,OAAO,WAAW,aAAa;CACjC,MAAM,SAAS,SAAS,cAAc,2BAA2B;AACjE,KAAI,OACF,KAAI;AAEF,oBADc,KAAK,MAAM,OAAO,YAAa,CACrB;SAClB"}
1
+ {"version":3,"file":"astro.js","names":[],"sources":["../../src/ssr/astro.ts"],"sourcesContent":["/**\n * Astro integration for Tasty SSR.\n *\n * Provides:\n * - tastyIntegration() Astro Integration API (recommended)\n * - tastyMiddleware() — manual middleware for advanced composition\n *\n * Import from '@tenphi/tasty/ssr/astro'.\n */\n\nimport { getConfig } from '../config';\nimport { getSSRCollector, runWithCollector } from './async-storage';\nimport { ServerStyleCollector } from './collector';\nimport { registerSSRCollectorGetterGlobal } from './ssr-collector-ref';\n\n// Wire up ALS-based collector discovery so computeStyles() can find\n// the collector set by tastyMiddleware's runWithCollector().\n// Uses globalThis so the getter is visible across Astro's separate\n// module graphs (middleware vs page components).\nregisterSSRCollectorGetterGlobal(getSSRCollector);\n\nexport interface TastyMiddlewareOptions {\n /**\n * Whether to embed the class-list script for client hydration.\n * Set to false to skip class transfer (e.g. for CSP restrictions).\n * Without it, client components may re-inject CSS that already exists\n * in server-rendered `<style>` tags. Default: true.\n */\n transferCache?: boolean;\n}\n\n/**\n * Create an Astro middleware that collects Tasty styles during SSR.\n *\n * All React components rendered during the request will have their\n * computeStyles() calls captured by the collector via AsyncLocalStorage.\n * After rendering, the middleware injects the collected CSS into </head>.\n *\n * @example Manual middleware setup\n * ```ts\n * // src/middleware.ts\n * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';\n * export const onRequest = tastyMiddleware();\n * ```\n *\n * @example Composing with other middleware\n * ```ts\n * // src/middleware.ts\n * import { sequence } from 'astro:middleware';\n * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';\n *\n * export const onRequest = sequence(\n * tastyMiddleware(),\n * myOtherMiddleware,\n * );\n * ```\n */\nexport function tastyMiddleware(options?: TastyMiddlewareOptions) {\n return async (\n _context: unknown,\n next: () => Promise<Response>,\n ): Promise<Response> => {\n const transferCache = options?.transferCache ?? true;\n const collector = new ServerStyleCollector();\n\n // Run the entire request — including body stream consumption — inside\n // the ALS context so that components rendering lazily during stream\n // reads can still find the collector via getSSRCollector().\n const rendered = await runWithCollector(collector, async () => {\n const response = await next();\n const body = response.body;\n if (!body) {\n return {\n html: null as string | null,\n status: response.status,\n headers: response.headers,\n };\n }\n\n const reader = body.pipeThrough(new TextDecoderStream()).getReader();\n const parts: string[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n parts.push(value);\n }\n return {\n html: parts.join(''),\n status: response.status,\n headers: response.headers,\n };\n });\n\n if (!rendered.html) {\n return new Response(null, {\n status: rendered.status,\n headers: rendered.headers,\n });\n }\n\n let { html } = rendered;\n\n const css = collector.getCSS();\n if (!css) {\n return new Response(html, {\n status: rendered.status,\n headers: rendered.headers,\n });\n }\n\n const nonce = getConfig().nonce;\n const nonceAttr = nonce ? ` nonce=\"${nonce}\"` : '';\n const styleTag = `<style data-tasty-ssr${nonceAttr}>${css}</style>`;\n\n let cacheTag = '';\n if (transferCache) {\n const classNames = collector.getRenderedClassNames();\n if (classNames.length > 0) {\n const classListJSON = classNames.map((n) => `\"${n}\"`).join(',');\n cacheTag = `<script${nonceAttr}>(window.__TASTY__=window.__TASTY__||[]).push(${classListJSON})</script>`;\n }\n }\n\n const injection = styleTag + cacheTag;\n const idx = html.indexOf('</head>');\n if (idx !== -1) {\n html = html.slice(0, idx) + injection + html.slice(idx);\n } else {\n html = injection + html;\n }\n\n const headers = new Headers(rendered.headers);\n headers.delete('content-length');\n\n return new Response(html, {\n status: rendered.status,\n headers,\n });\n };\n}\n\n// ============================================================================\n// Module-level middleware config (set by tastyIntegration, read by\n// astro-middleware.ts via getter property)\n// ============================================================================\n\nlet _middlewareTransferCache = true;\n\n/** @internal */\nexport function setMiddlewareTransferCache(value: boolean): void {\n _middlewareTransferCache = value;\n}\n\n/** @internal */\nexport function getMiddlewareTransferCache(): boolean {\n return _middlewareTransferCache;\n}\n\n// ============================================================================\n// Astro Integration API\n// ============================================================================\n\nexport interface TastyIntegrationOptions {\n /**\n * Enable island hydration support.\n *\n * When `true` (default): injects a client hydration script via\n * `injectScript('before-hydration')` and sets `transferCache: true`\n * on the middleware. Islands skip the style pipeline during hydration.\n *\n * When `false`: no client JS is shipped and `transferCache` is set\n * to `false`. Use this for fully static sites without `client:*`\n * directives.\n */\n islands?: boolean;\n}\n\n/**\n * Astro integration that automatically sets up Tasty SSR.\n *\n * Registers middleware for cross-component CSS deduplication and\n * optionally injects a client hydration script for island support.\n *\n * @example Basic setup (with islands)\n * ```ts\n * // astro.config.mjs\n * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';\n *\n * export default defineConfig({\n * integrations: [tastyIntegration()],\n * });\n * ```\n *\n * @example Static-only (no client JS)\n * ```ts\n * // astro.config.mjs\n * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';\n *\n * export default defineConfig({\n * integrations: [tastyIntegration({ islands: false })],\n * });\n * ```\n */\nexport function tastyIntegration(options?: TastyIntegrationOptions) {\n const { islands = true } = options ?? {};\n\n setMiddlewareTransferCache(islands);\n\n return {\n name: '@tenphi/tasty',\n hooks: {\n 'astro:config:setup': ({\n addMiddleware,\n injectScript,\n }: {\n addMiddleware: (middleware: {\n entrypoint: string | URL;\n order: 'pre' | 'post';\n }) => void;\n injectScript: (stage: string, content: string) => void;\n }) => {\n addMiddleware({\n entrypoint: new URL('./astro-middleware.js', import.meta.url),\n order: 'pre',\n });\n\n if (islands) {\n injectScript(\n 'before-hydration',\n `import \"@tenphi/tasty/ssr/astro-client\";`,\n );\n }\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,iCAAiC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCjD,SAAgB,gBAAgB,SAAkC;AAChE,QAAO,OACL,UACA,SACsB;EACtB,MAAM,gBAAgB,SAAS,iBAAiB;EAChD,MAAM,YAAY,IAAI,sBAAsB;EAK5C,MAAM,WAAW,MAAM,iBAAiB,WAAW,YAAY;GAC7D,MAAM,WAAW,MAAM,MAAM;GAC7B,MAAM,OAAO,SAAS;AACtB,OAAI,CAAC,KACH,QAAO;IACL,MAAM;IACN,QAAQ,SAAS;IACjB,SAAS,SAAS;IACnB;GAGH,MAAM,SAAS,KAAK,YAAY,IAAI,mBAAmB,CAAC,CAAC,WAAW;GACpE,MAAM,QAAkB,EAAE;AAC1B,YAAS;IACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,QAAI,KAAM;AACV,UAAM,KAAK,MAAM;;AAEnB,UAAO;IACL,MAAM,MAAM,KAAK,GAAG;IACpB,QAAQ,SAAS;IACjB,SAAS,SAAS;IACnB;IACD;AAEF,MAAI,CAAC,SAAS,KACZ,QAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB,SAAS,SAAS;GACnB,CAAC;EAGJ,IAAI,EAAE,SAAS;EAEf,MAAM,MAAM,UAAU,QAAQ;AAC9B,MAAI,CAAC,IACH,QAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB,SAAS,SAAS;GACnB,CAAC;EAGJ,MAAM,QAAQ,WAAW,CAAC;EAC1B,MAAM,YAAY,QAAQ,WAAW,MAAM,KAAK;EAChD,MAAM,WAAW,wBAAwB,UAAU,GAAG,IAAI;EAE1D,IAAI,WAAW;AACf,MAAI,eAAe;GACjB,MAAM,aAAa,UAAU,uBAAuB;AACpD,OAAI,WAAW,SAAS,EAEtB,YAAW,UAAU,UAAU,gDADT,WAAW,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAC8B;;EAIjG,MAAM,YAAY,WAAW;EAC7B,MAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,MAAI,QAAQ,GACV,QAAO,KAAK,MAAM,GAAG,IAAI,GAAG,YAAY,KAAK,MAAM,IAAI;MAEvD,QAAO,YAAY;EAGrB,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAC7C,UAAQ,OAAO,iBAAiB;AAEhC,SAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB;GACD,CAAC;;;AASN,IAAI,2BAA2B;;AAG/B,SAAgB,2BAA2B,OAAsB;AAC/D,4BAA2B;;;AAI7B,SAAgB,6BAAsC;AACpD,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDT,SAAgB,iBAAiB,SAAmC;CAClE,MAAM,EAAE,UAAU,SAAS,WAAW,EAAE;AAExC,4BAA2B,QAAQ;AAEnC,QAAO;EACL,MAAM;EACN,OAAO,EACL,uBAAuB,EACrB,eACA,mBAOI;AACJ,iBAAc;IACZ,YAAY,IAAI,IAAI,yBAAyB,OAAO,KAAK,IAAI;IAC7D,OAAO;IACR,CAAC;AAEF,OAAI,QACF,cACE,oBACA,2CACD;KAGN;EACF"}
@@ -8,25 +8,35 @@ import { AsyncLocalStorage } from "node:async_hooks";
8
8
  * The middleware calls runWithCollector() around the render, and
9
9
  * useStyles() calls getSSRCollector() to find it.
10
10
  *
11
+ * Uses globalThis to ensure the AsyncLocalStorage instance is shared
12
+ * across module instances — frameworks like Astro may load middleware
13
+ * and page components from separate module graphs.
14
+ *
11
15
  * This module imports from 'node:async_hooks' — it must be excluded
12
16
  * from client bundles via the build configuration.
13
17
  */
14
- const tastySSRStorage = new AsyncLocalStorage();
18
+ const ALS_KEY = "__tasty_ssr_als__";
19
+ function getSharedStorage() {
20
+ const g = globalThis;
21
+ if (!g[ALS_KEY]) g[ALS_KEY] = new AsyncLocalStorage();
22
+ return g[ALS_KEY];
23
+ }
15
24
  /**
16
25
  * Run a function with a ServerStyleCollector bound to the current
17
26
  * async context. All useStyles() calls within `fn` (and any async
18
27
  * continuations) will find this collector via getSSRCollector().
19
28
  */
20
29
  function runWithCollector(collector, fn) {
21
- return tastySSRStorage.run(collector, fn);
30
+ return getSharedStorage().run(collector, fn);
22
31
  }
23
32
  /**
24
33
  * Retrieve the ServerStyleCollector bound to the current async context.
25
34
  * Returns null when called outside of runWithCollector() or on the client.
26
35
  */
27
36
  function getSSRCollector() {
28
- if (typeof tastySSRStorage?.getStore !== "function") return null;
29
- return tastySSRStorage.getStore() ?? null;
37
+ const storage = getSharedStorage();
38
+ if (typeof storage?.getStore !== "function") return null;
39
+ return storage.getStore() ?? null;
30
40
  }
31
41
  //#endregion
32
42
  export { getSSRCollector, runWithCollector };
@@ -1 +1 @@
1
- {"version":3,"file":"async-storage.js","names":[],"sources":["../../src/ssr/async-storage.ts"],"sourcesContent":["/**\n * AsyncLocalStorage integration for SSR collector discovery.\n *\n * Used by Astro middleware and generic framework integrations where\n * the library cannot wrap the React tree with a context provider.\n * The middleware calls runWithCollector() around the render, and\n * useStyles() calls getSSRCollector() to find it.\n *\n * This module imports from 'node:async_hooks' — it must be excluded\n * from client bundles via the build configuration.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { ServerStyleCollector } from './collector';\n\nconst tastySSRStorage = new AsyncLocalStorage<ServerStyleCollector>();\n\n/**\n * Run a function with a ServerStyleCollector bound to the current\n * async context. All useStyles() calls within `fn` (and any async\n * continuations) will find this collector via getSSRCollector().\n */\nexport function runWithCollector<T>(\n collector: ServerStyleCollector,\n fn: () => T,\n): T {\n return tastySSRStorage.run(collector, fn);\n}\n\n/**\n * Retrieve the ServerStyleCollector bound to the current async context.\n * Returns null when called outside of runWithCollector() or on the client.\n */\nexport function getSSRCollector(): ServerStyleCollector | null {\n if (typeof tastySSRStorage?.getStore !== 'function') return null;\n return tastySSRStorage.getStore() ?? null;\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAM,kBAAkB,IAAI,mBAAyC;;;;;;AAOrE,SAAgB,iBACd,WACA,IACG;AACH,QAAO,gBAAgB,IAAI,WAAW,GAAG;;;;;;AAO3C,SAAgB,kBAA+C;AAC7D,KAAI,OAAO,iBAAiB,aAAa,WAAY,QAAO;AAC5D,QAAO,gBAAgB,UAAU,IAAI"}
1
+ {"version":3,"file":"async-storage.js","names":[],"sources":["../../src/ssr/async-storage.ts"],"sourcesContent":["/**\n * AsyncLocalStorage integration for SSR collector discovery.\n *\n * Used by Astro middleware and generic framework integrations where\n * the library cannot wrap the React tree with a context provider.\n * The middleware calls runWithCollector() around the render, and\n * useStyles() calls getSSRCollector() to find it.\n *\n * Uses globalThis to ensure the AsyncLocalStorage instance is shared\n * across module instances — frameworks like Astro may load middleware\n * and page components from separate module graphs.\n *\n * This module imports from 'node:async_hooks' — it must be excluded\n * from client bundles via the build configuration.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { ServerStyleCollector } from './collector';\n\nconst ALS_KEY = '__tasty_ssr_als__';\n\nfunction getSharedStorage(): AsyncLocalStorage<ServerStyleCollector> {\n const g = globalThis as Record<string, unknown>;\n if (!g[ALS_KEY]) {\n g[ALS_KEY] = new AsyncLocalStorage<ServerStyleCollector>();\n }\n return g[ALS_KEY] as AsyncLocalStorage<ServerStyleCollector>;\n}\n\n/**\n * Run a function with a ServerStyleCollector bound to the current\n * async context. All useStyles() calls within `fn` (and any async\n * continuations) will find this collector via getSSRCollector().\n */\nexport function runWithCollector<T>(\n collector: ServerStyleCollector,\n fn: () => T,\n): T {\n return getSharedStorage().run(collector, fn);\n}\n\n/**\n * Retrieve the ServerStyleCollector bound to the current async context.\n * Returns null when called outside of runWithCollector() or on the client.\n */\nexport function getSSRCollector(): ServerStyleCollector | null {\n const storage = getSharedStorage();\n if (typeof storage?.getStore !== 'function') return null;\n return storage.getStore() ?? null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAM,UAAU;AAEhB,SAAS,mBAA4D;CACnE,MAAM,IAAI;AACV,KAAI,CAAC,EAAE,SACL,GAAE,WAAW,IAAI,mBAAyC;AAE5D,QAAO,EAAE;;;;;;;AAQX,SAAgB,iBACd,WACA,IACG;AACH,QAAO,kBAAkB,CAAC,IAAI,WAAW,GAAG;;;;;;AAO9C,SAAgB,kBAA+C;CAC7D,MAAM,UAAU,kBAAkB;AAClC,KAAI,OAAO,SAAS,aAAa,WAAY,QAAO;AACpD,QAAO,QAAQ,UAAU,IAAI"}
@@ -1,16 +1,13 @@
1
1
  import { parsePropertyToken } from "../properties/index.js";
2
2
  import { PropertyTypeResolver } from "../properties/property-type-resolver.js";
3
+ import { pushRSCCSS } from "../rsc-cache.js";
3
4
  import { formatPropertyCSS } from "./format-property.js";
4
5
  //#region src/ssr/collect-auto-properties.ts
5
6
  /**
6
- * Scan rendered rules for custom property declarations and collect
7
- * auto-inferred @property rules via the SSR collector.
8
- *
9
- * @param rules - Rendered style rules containing CSS declarations
10
- * @param collector - SSR collector to emit @property CSS into
11
- * @param styles - Original styles object (used to skip explicit @properties)
7
+ * Scan rendered rules for auto-inferable custom properties and emit
8
+ * @property CSS via the provided callback.
12
9
  */
13
- function collectAutoInferredProperties(rules, collector, styles) {
10
+ function scanAndEmitAutoProperties(rules, styles, emit) {
14
11
  const registered = /* @__PURE__ */ new Set();
15
12
  if (styles) {
16
13
  const localProps = styles["@properties"];
@@ -29,11 +26,33 @@ function collectAutoInferredProperties(rules, collector, styles) {
29
26
  inherits: true,
30
27
  initialValue
31
28
  });
32
- if (css) collector.collectProperty(`__auto:${name}`, css);
29
+ if (css) emit(name, css);
33
30
  });
34
31
  }
35
32
  }
33
+ /**
34
+ * Scan rendered rules for custom property declarations and collect
35
+ * auto-inferred @property rules via the SSR collector.
36
+ *
37
+ * @param rules - Rendered style rules containing CSS declarations
38
+ * @param collector - SSR collector to emit @property CSS into
39
+ * @param styles - Original styles object (used to skip explicit @properties)
40
+ */
41
+ function collectAutoInferredProperties(rules, collector, styles) {
42
+ scanAndEmitAutoProperties(rules, styles, (name, css) => {
43
+ collector.collectProperty(`__auto:${name}`, css);
44
+ });
45
+ }
46
+ /**
47
+ * RSC variant: scan rendered rules and push auto-inferred @property CSS
48
+ * into the RSC pending buffer.
49
+ */
50
+ function collectAutoInferredPropertiesRSC(rules, rscCache, styles) {
51
+ scanAndEmitAutoProperties(rules, styles, (name, css) => {
52
+ pushRSCCSS(rscCache, `__auto:${name}`, css);
53
+ });
54
+ }
36
55
  //#endregion
37
- export { collectAutoInferredProperties };
56
+ export { collectAutoInferredProperties, collectAutoInferredPropertiesRSC };
38
57
 
39
58
  //# sourceMappingURL=collect-auto-properties.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collect-auto-properties.js","names":[],"sources":["../../src/ssr/collect-auto-properties.ts"],"sourcesContent":["/**\n * SSR auto-property inference.\n *\n * Scans rendered CSS declarations for custom properties whose types\n * can be inferred from their values (e.g. `--angle: 30deg` → `<angle>`).\n * Mirrors the client-side auto-inference in StyleInjector.inject().\n */\n\nimport type { StyleResult } from '../pipeline';\nimport { parsePropertyToken } from '../properties';\nimport { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport type { Styles } from '../styles/types';\n\nimport type { ServerStyleCollector } from './collector';\nimport { formatPropertyCSS } from './format-property';\n\n/**\n * Scan rendered rules for custom property declarations and collect\n * auto-inferred @property rules via the SSR collector.\n *\n * @param rules - Rendered style rules containing CSS declarations\n * @param collector - SSR collector to emit @property CSS into\n * @param styles - Original styles object (used to skip explicit @properties)\n */\nexport function collectAutoInferredProperties(\n rules: StyleResult[],\n collector: ServerStyleCollector,\n styles?: Styles,\n): void {\n const registered = new Set<string>();\n\n if (styles) {\n const localProps = styles['@properties'];\n if (localProps && typeof localProps === 'object') {\n for (const token of Object.keys(localProps as Record<string, unknown>)) {\n const parsed = parsePropertyToken(token);\n if (parsed.isValid) {\n registered.add(parsed.cssName);\n }\n }\n }\n }\n\n const resolver = new PropertyTypeResolver();\n\n for (const rule of rules) {\n if (!rule.declarations) continue;\n resolver.scanDeclarations(\n rule.declarations,\n (name) => registered.has(name),\n (name, syntax, initialValue) => {\n registered.add(name);\n const css = formatPropertyCSS(name, {\n syntax,\n inherits: true,\n initialValue,\n });\n if (css) {\n collector.collectProperty(`__auto:${name}`, css);\n }\n },\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,SAAgB,8BACd,OACA,WACA,QACM;CACN,MAAM,6BAAa,IAAI,KAAa;AAEpC,KAAI,QAAQ;EACV,MAAM,aAAa,OAAO;AAC1B,MAAI,cAAc,OAAO,eAAe,SACtC,MAAK,MAAM,SAAS,OAAO,KAAK,WAAsC,EAAE;GACtE,MAAM,SAAS,mBAAmB,MAAM;AACxC,OAAI,OAAO,QACT,YAAW,IAAI,OAAO,QAAQ;;;CAMtC,MAAM,WAAW,IAAI,sBAAsB;AAE3C,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,aAAc;AACxB,WAAS,iBACP,KAAK,eACJ,SAAS,WAAW,IAAI,KAAK,GAC7B,MAAM,QAAQ,iBAAiB;AAC9B,cAAW,IAAI,KAAK;GACpB,MAAM,MAAM,kBAAkB,MAAM;IAClC;IACA,UAAU;IACV;IACD,CAAC;AACF,OAAI,IACF,WAAU,gBAAgB,UAAU,QAAQ,IAAI;IAGrD"}
1
+ {"version":3,"file":"collect-auto-properties.js","names":[],"sources":["../../src/ssr/collect-auto-properties.ts"],"sourcesContent":["/**\n * SSR / RSC auto-property inference.\n *\n * Scans rendered CSS declarations for custom properties whose types\n * can be inferred from their values (e.g. `--angle: 30deg` → `<angle>`).\n * Mirrors the client-side auto-inference in StyleInjector.inject().\n */\n\nimport type { StyleResult } from '../pipeline';\nimport { parsePropertyToken } from '../properties';\nimport { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport type { RSCStyleCache } from '../rsc-cache';\nimport { pushRSCCSS } from '../rsc-cache';\nimport type { Styles } from '../styles/types';\n\nimport type { ServerStyleCollector } from './collector';\nimport { formatPropertyCSS } from './format-property';\n\n/**\n * Scan rendered rules for auto-inferable custom properties and emit\n * @property CSS via the provided callback.\n */\nfunction scanAndEmitAutoProperties(\n rules: StyleResult[],\n styles: Styles | undefined,\n emit: (name: string, css: string) => void,\n): void {\n const registered = new Set<string>();\n\n if (styles) {\n const localProps = styles['@properties'];\n if (localProps && typeof localProps === 'object') {\n for (const token of Object.keys(localProps as Record<string, unknown>)) {\n const parsed = parsePropertyToken(token);\n if (parsed.isValid) {\n registered.add(parsed.cssName);\n }\n }\n }\n }\n\n const resolver = new PropertyTypeResolver();\n\n for (const rule of rules) {\n if (!rule.declarations) continue;\n resolver.scanDeclarations(\n rule.declarations,\n (name) => registered.has(name),\n (name, syntax, initialValue) => {\n registered.add(name);\n const css = formatPropertyCSS(name, {\n syntax,\n inherits: true,\n initialValue,\n });\n if (css) {\n emit(name, css);\n }\n },\n );\n }\n}\n\n/**\n * Scan rendered rules for custom property declarations and collect\n * auto-inferred @property rules via the SSR collector.\n *\n * @param rules - Rendered style rules containing CSS declarations\n * @param collector - SSR collector to emit @property CSS into\n * @param styles - Original styles object (used to skip explicit @properties)\n */\nexport function collectAutoInferredProperties(\n rules: StyleResult[],\n collector: ServerStyleCollector,\n styles?: Styles,\n): void {\n scanAndEmitAutoProperties(rules, styles, (name, css) => {\n collector.collectProperty(`__auto:${name}`, css);\n });\n}\n\n/**\n * RSC variant: scan rendered rules and push auto-inferred @property CSS\n * into the RSC pending buffer.\n */\nexport function collectAutoInferredPropertiesRSC(\n rules: StyleResult[],\n rscCache: RSCStyleCache,\n styles?: Styles,\n): void {\n scanAndEmitAutoProperties(rules, styles, (name, css) => {\n pushRSCCSS(rscCache, `__auto:${name}`, css);\n });\n}\n"],"mappings":";;;;;;;;;AAsBA,SAAS,0BACP,OACA,QACA,MACM;CACN,MAAM,6BAAa,IAAI,KAAa;AAEpC,KAAI,QAAQ;EACV,MAAM,aAAa,OAAO;AAC1B,MAAI,cAAc,OAAO,eAAe,SACtC,MAAK,MAAM,SAAS,OAAO,KAAK,WAAsC,EAAE;GACtE,MAAM,SAAS,mBAAmB,MAAM;AACxC,OAAI,OAAO,QACT,YAAW,IAAI,OAAO,QAAQ;;;CAMtC,MAAM,WAAW,IAAI,sBAAsB;AAE3C,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,aAAc;AACxB,WAAS,iBACP,KAAK,eACJ,SAAS,WAAW,IAAI,KAAK,GAC7B,MAAM,QAAQ,iBAAiB;AAC9B,cAAW,IAAI,KAAK;GACpB,MAAM,MAAM,kBAAkB,MAAM;IAClC;IACA,UAAU;IACV;IACD,CAAC;AACF,OAAI,IACF,MAAK,MAAM,IAAI;IAGpB;;;;;;;;;;;AAYL,SAAgB,8BACd,OACA,WACA,QACM;AACN,2BAA0B,OAAO,SAAS,MAAM,QAAQ;AACtD,YAAU,gBAAgB,UAAU,QAAQ,IAAI;GAChD;;;;;;AAOJ,SAAgB,iCACd,OACA,UACA,QACM;AACN,2BAA0B,OAAO,SAAS,MAAM,QAAQ;AACtD,aAAW,UAAU,UAAU,QAAQ,IAAI;GAC3C"}
@@ -1,19 +1,9 @@
1
1
  import { StyleResult } from "../pipeline/index.js";
2
2
 
3
3
  //#region src/ssr/collector.d.ts
4
- /**
5
- * Cache state serialized to the client for hydration.
6
- */
7
- interface SSRCacheState {
8
- /** cacheKey → className map, to pre-populate the client registry */
9
- entries: Record<string, string>;
10
- /** Counter value so client allocations don't collide with server ones */
11
- classCounter: number;
12
- }
13
4
  declare class ServerStyleCollector {
14
5
  private chunks;
15
6
  private cacheKeyToClassName;
16
- private classCounter;
17
7
  private flushedKeys;
18
8
  private propertyRules;
19
9
  private flushedPropertyKeys;
@@ -92,11 +82,13 @@ declare class ServerStyleCollector {
92
82
  * Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).
93
83
  */
94
84
  flushCSS(): string;
85
+ private flushedClassNames;
95
86
  /**
96
- * Serialize the cache state for client hydration.
87
+ * Return class names rendered since the last call (for streaming).
88
+ * Used to emit lightweight class-list scripts for client hydration.
97
89
  */
98
- getCacheState(): SSRCacheState;
90
+ getRenderedClassNames(): string[];
99
91
  }
100
92
  //#endregion
101
- export { SSRCacheState, ServerStyleCollector };
93
+ export { ServerStyleCollector };
102
94
  //# sourceMappingURL=collector.d.ts.map