create-fluxstack 1.22.0 → 1.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/app/client/src/components/AppLayout.tsx +290 -290
  2. package/app/client/src/components/BackButton.tsx +16 -16
  3. package/app/client/src/components/DemoPage.tsx +135 -135
  4. package/app/client/src/framework/ClientOnly.tsx +14 -14
  5. package/app/client/src/framework/LivePage.tsx +55 -55
  6. package/app/client/src/framework/RscHomePage.tsx +125 -125
  7. package/app/client/src/framework/RscLink.tsx +31 -31
  8. package/app/client/src/framework/RscNav.tsx +42 -42
  9. package/app/client/src/framework/RscRoot.tsx +54 -54
  10. package/app/client/src/framework/csrf.ts +22 -22
  11. package/app/client/src/framework/entry.browser.tsx +51 -51
  12. package/app/client/src/framework/entry.rsc.tsx +41 -41
  13. package/app/client/src/framework/entry.ssr.tsx +12 -12
  14. package/app/client/src/framework/navigation.ts +29 -29
  15. package/app/client/src/framework/params.tsx +22 -22
  16. package/app/client/src/framework/routes.ts +143 -143
  17. package/app/client/src/framework/vite-rsc.d.ts +21 -21
  18. package/app/client/src/live/AuthDemo.tsx +270 -270
  19. package/app/client/src/live/CounterDemo.tsx +152 -152
  20. package/app/client/src/live/FormDemo.tsx +140 -140
  21. package/app/client/src/live/PingPongDemo.tsx +180 -180
  22. package/app/client/src/live/RoomChatDemo.tsx +397 -397
  23. package/app/client/src/pages/auth.tsx +13 -13
  24. package/app/client/src/pages/blog/[slug].tsx +23 -23
  25. package/app/client/src/pages/counter.tsx +15 -15
  26. package/app/client/src/pages/form.tsx +13 -13
  27. package/app/client/src/pages/index.tsx +9 -9
  28. package/app/client/src/pages/ping-pong.tsx +13 -13
  29. package/app/client/src/pages/room-chat.tsx +13 -13
  30. package/app/client/src/pages/shared-counter.tsx +13 -13
  31. package/app/client/src/pages/sobre.tsx +19 -19
  32. package/app/server/live/rooms/index.ts +14 -14
  33. package/core/plugins/built-in/rsc/index.ts +156 -156
  34. package/core/plugins/built-in/ssr/bun-asset-loader.ts +46 -46
  35. package/core/plugins/built-in/ssr/index.ts +143 -143
  36. package/core/plugins/built-in/ssr/registry.ts +38 -38
  37. package/core/utils/version.ts +1 -1
  38. package/package.json +1 -1
@@ -1,46 +1,46 @@
1
- /**
2
- * Bun asset loader para SSR — alinha imports de asset com o Vite.
3
- *
4
- * Problema: no client, o Vite resolve `import logo from '...x.svg'` para a URL
5
- * `/src/assets/x.svg`. No server, o Bun resolve o MESMO import para o caminho
6
- * de arquivo absoluto (C:\...\x.svg). Strings diferentes no atributo src/href
7
- * → hydration mismatch (o React descarta o HTML do server e re-renderiza tudo).
8
- *
9
- * Solução: registrar um plugin no Bun que, ao carregar um asset, exporta a
10
- * MESMA URL que o Vite geraria — relativa ao root do client (app/client).
11
- * Em dev essa URL é servida pelo Vite; em prod, pelos assets buildados.
12
- *
13
- * Registrar via bunfig.toml [run].preload ANTES de qualquer import do app.
14
- */
15
-
16
- import { plugin } from 'bun'
17
- import { sep } from 'path'
18
-
19
- /** Root do client onde o Vite baseia as URLs (app/client) */
20
- const CLIENT_ROOT = 'app' + sep + 'client' + sep
21
-
22
- /** Extensões que o Vite trata como asset-URL (não código) */
23
- const ASSET_RE = /\.(svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|mp4|webm|mp3|wav)$/i
24
-
25
- plugin({
26
- name: 'ssr-asset-url',
27
- setup(build) {
28
- build.onLoad({ filter: ASSET_RE }, (args) => {
29
- // args.path é absoluto. Converter para a URL que o Vite usa:
30
- // .../app/client/src/assets/x.svg -> /src/assets/x.svg
31
- const idx = args.path.replace(/\//g, sep).indexOf(CLIENT_ROOT)
32
- let url: string
33
- if (idx !== -1) {
34
- const rel = args.path.slice(idx + CLIENT_ROOT.length).replaceAll(sep, '/')
35
- url = '/' + rel
36
- } else {
37
- // fallback: usa o basename
38
- url = '/' + args.path.split(/[\\/]/).pop()
39
- }
40
- return {
41
- exports: { default: url },
42
- loader: 'object',
43
- }
44
- })
45
- },
46
- })
1
+ /**
2
+ * Bun asset loader para SSR — alinha imports de asset com o Vite.
3
+ *
4
+ * Problema: no client, o Vite resolve `import logo from '...x.svg'` para a URL
5
+ * `/src/assets/x.svg`. No server, o Bun resolve o MESMO import para o caminho
6
+ * de arquivo absoluto (C:\...\x.svg). Strings diferentes no atributo src/href
7
+ * → hydration mismatch (o React descarta o HTML do server e re-renderiza tudo).
8
+ *
9
+ * Solução: registrar um plugin no Bun que, ao carregar um asset, exporta a
10
+ * MESMA URL que o Vite geraria — relativa ao root do client (app/client).
11
+ * Em dev essa URL é servida pelo Vite; em prod, pelos assets buildados.
12
+ *
13
+ * Registrar via bunfig.toml [run].preload ANTES de qualquer import do app.
14
+ */
15
+
16
+ import { plugin } from 'bun'
17
+ import { sep } from 'path'
18
+
19
+ /** Root do client onde o Vite baseia as URLs (app/client) */
20
+ const CLIENT_ROOT = 'app' + sep + 'client' + sep
21
+
22
+ /** Extensões que o Vite trata como asset-URL (não código) */
23
+ const ASSET_RE = /\.(svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|mp4|webm|mp3|wav)$/i
24
+
25
+ plugin({
26
+ name: 'ssr-asset-url',
27
+ setup(build) {
28
+ build.onLoad({ filter: ASSET_RE }, (args) => {
29
+ // args.path é absoluto. Converter para a URL que o Vite usa:
30
+ // .../app/client/src/assets/x.svg -> /src/assets/x.svg
31
+ const idx = args.path.replace(/\//g, sep).indexOf(CLIENT_ROOT)
32
+ let url: string
33
+ if (idx !== -1) {
34
+ const rel = args.path.slice(idx + CLIENT_ROOT.length).replaceAll(sep, '/')
35
+ url = '/' + rel
36
+ } else {
37
+ // fallback: usa o basename
38
+ url = '/' + args.path.split(/[\\/]/).pop()
39
+ }
40
+ return {
41
+ exports: { default: url },
42
+ loader: 'object',
43
+ }
44
+ })
45
+ },
46
+ })
@@ -1,143 +1,143 @@
1
- /**
2
- * FluxStack SSR Plugin
3
- *
4
- * Renderiza o HTML do app no servidor (renderToString) para rotas de PÁGINA,
5
- * substituindo o `<div id="root"></div>` vazio do SPA por markup pronto.
6
- * Os assets (JS/CSS/imagens, /@vite, /src/*) continuam servidos pelo Vite (dev)
7
- * ou pelo static fallback (prod) — o SSR só intercepta navegação de página.
8
- *
9
- * Ordem: prioridade 850 > vite (800), então o onBeforeRoute do SSR roda ANTES
10
- * do proxy do Vite. Se o SSR tratar a request (handled=true), o Vite não vê.
11
- *
12
- * O renderer do app é injetado via registry (registerSsrRenderer) — o core não
13
- * conhece o React do app. Ver ./registry.ts.
14
- *
15
- * LIMITAÇÃO ATUAL (PoC): pipeline de hydration montado para DEV (Vite :5173).
16
- * Produção (sem Vite dev) precisa apontar para os assets buildados — TODO abaixo.
17
- */
18
-
19
- import type { FluxStack, PluginContext, RequestContext } from '@core/plugins/types'
20
- import { FLUXSTACK_VERSION } from '@core/utils/version'
21
- import { isDevelopment } from '@core/utils/helpers'
22
- import { clientConfig, pluginsConfig } from '@config'
23
- import { getSsrRenderer } from './registry'
24
-
25
- type Plugin = FluxStack.Plugin
26
-
27
- const PLUGIN_PRIORITY = 850 // > vite (800): intercepta páginas antes do proxy Vite
28
- const IS_DEV = isDevelopment()
29
-
30
- /** Extensões de asset — nunca devem ser tratadas como página */
31
- const ASSET_EXT = /\.(js|mjs|ts|tsx|jsx|css|map|json|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|wasm|txt|xml|webmanifest)$/i
32
-
33
- /** Prefixos internos (Vite/API/dev) que NUNCA são páginas — passam direto */
34
- const PASSTHROUGH_PREFIXES = ['/@', '/src/', '/node_modules/', '/api', '/swagger', '/__plugins']
35
-
36
- /** Decide se um path é uma rota de PÁGINA (candidata a SSR) */
37
- function isPageRoute(path: string): boolean {
38
- if (ASSET_EXT.test(path)) return false
39
- return !PASSTHROUGH_PREFIXES.some((p) => path === p || path.startsWith(p))
40
- }
41
-
42
- /** Monta o documento HTML completo com o markup SSR + pipeline de hydration */
43
- function buildDocument(opts: {
44
- appHtml: string
45
- bootstrapData?: Record<string, unknown>
46
- headTags?: string
47
- }): string {
48
- const { appHtml, bootstrapData, headTags = '' } = opts
49
-
50
- const viteHost = (clientConfig.vite.host as string) || 'localhost'
51
- const vitePort = (clientConfig.vite.port as number) || 5173
52
- const viteBase = `http://${viteHost}:${vitePort}`
53
- const entry = (pluginsConfig as Record<string, unknown>).ssrClientEntry as string | undefined
54
- ?? '/src/entry-ssr.tsx'
55
-
56
- const dataScript = bootstrapData
57
- ? `<script>window.__SSR_DATA__ = ${JSON.stringify(bootstrapData).replace(/</g, '\\u003c')};</script>`
58
- : ''
59
-
60
- // DEV: pipeline do Vite (react-refresh preamble + client + entry TSX)
61
- const devScripts = IS_DEV
62
- ? `<script type="module">
63
- import RefreshRuntime from "${viteBase}/@react-refresh"
64
- RefreshRuntime.injectIntoGlobalHook(window)
65
- window.$RefreshReg$ = () => {}
66
- window.$RefreshSig$ = () => (type) => type
67
- window.__vite_plugin_react_preamble_installed__ = true
68
- </script>
69
- <script type="module" src="${viteBase}/@vite/client"></script>
70
- <script type="module" src="${viteBase}${entry}"></script>`
71
- // PROD: TODO — apontar para o entry buildado (ler do manifest do Vite).
72
- // Por ora, sem script em prod (HTML estático apenas, sem interatividade).
73
- : `<!-- TODO(SSR prod): injetar /assets/entry-ssr.[hash].js do build manifest -->`
74
-
75
- return `<!doctype html>
76
- <html lang="en">
77
- <head>
78
- <meta charset="UTF-8" />
79
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
80
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
81
- <title>FluxStack</title>
82
- ${headTags}
83
- ${IS_DEV ? devScripts : ''}
84
- </head>
85
- <body>
86
- <div id="root">${appHtml}</div>
87
- ${dataScript}
88
- ${IS_DEV ? '' : devScripts}
89
- </body>
90
- </html>`
91
- }
92
-
93
- export const ssrPlugin: Plugin = {
94
- name: 'ssr',
95
- version: FLUXSTACK_VERSION,
96
- description: 'Server-side rendering of the React app for page routes',
97
- author: 'FluxStack Team',
98
- priority: PLUGIN_PRIORITY,
99
- category: 'rendering',
100
- tags: ['ssr', 'react', 'hydration'],
101
- dependencies: [],
102
-
103
- setup: async (context: PluginContext) => {
104
- const enabled = (pluginsConfig as Record<string, unknown>).ssrEnabled
105
- if (!enabled) {
106
- context.logger.debug('SSR plugin disabled (set SSR_ENABLED=true)')
107
- return
108
- }
109
- context.logger.debug(`SSR plugin active (priority ${PLUGIN_PRIORITY})`)
110
- },
111
-
112
- onBeforeRoute: async (ctx: RequestContext) => {
113
- if (!(pluginsConfig as Record<string, unknown>).ssrEnabled) return
114
- if (ctx.method !== 'GET') return
115
- if (!isPageRoute(ctx.path)) return
116
-
117
- const renderer = getSsrRenderer()
118
- if (!renderer) return // app não registrou renderer → deixa o SPA fallback agir
119
-
120
- try {
121
- const result = await renderer({ url: ctx.path, headers: ctx.headers })
122
- const html = buildDocument({
123
- appHtml: result.html,
124
- bootstrapData: result.bootstrapData,
125
- headTags: result.headTags,
126
- })
127
- ctx.handled = true
128
- ctx.response = new Response(html, {
129
- status: result.status ?? 200,
130
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
131
- })
132
- } catch (err) {
133
- // SSR falhou → NÃO trata (handled fica false), deixa o Vite/SPA servir.
134
- // Falhar para o SPA é melhor que mostrar 500: o client ainda renderiza.
135
- const msg = err instanceof Error ? err.message : String(err)
136
- ;(ctx as unknown as { logger?: { error: (m: string) => void } }).logger?.error?.(
137
- `SSR render failed for ${ctx.path}, falling back to SPA: ${msg}`
138
- )
139
- }
140
- },
141
- }
142
-
143
- export default ssrPlugin
1
+ /**
2
+ * FluxStack SSR Plugin
3
+ *
4
+ * Renderiza o HTML do app no servidor (renderToString) para rotas de PÁGINA,
5
+ * substituindo o `<div id="root"></div>` vazio do SPA por markup pronto.
6
+ * Os assets (JS/CSS/imagens, /@vite, /src/*) continuam servidos pelo Vite (dev)
7
+ * ou pelo static fallback (prod) — o SSR só intercepta navegação de página.
8
+ *
9
+ * Ordem: prioridade 850 > vite (800), então o onBeforeRoute do SSR roda ANTES
10
+ * do proxy do Vite. Se o SSR tratar a request (handled=true), o Vite não vê.
11
+ *
12
+ * O renderer do app é injetado via registry (registerSsrRenderer) — o core não
13
+ * conhece o React do app. Ver ./registry.ts.
14
+ *
15
+ * LIMITAÇÃO ATUAL (PoC): pipeline de hydration montado para DEV (Vite :5173).
16
+ * Produção (sem Vite dev) precisa apontar para os assets buildados — TODO abaixo.
17
+ */
18
+
19
+ import type { FluxStack, PluginContext, RequestContext } from '@core/plugins/types'
20
+ import { FLUXSTACK_VERSION } from '@core/utils/version'
21
+ import { isDevelopment } from '@core/utils/helpers'
22
+ import { clientConfig, pluginsConfig } from '@config'
23
+ import { getSsrRenderer } from './registry'
24
+
25
+ type Plugin = FluxStack.Plugin
26
+
27
+ const PLUGIN_PRIORITY = 850 // > vite (800): intercepta páginas antes do proxy Vite
28
+ const IS_DEV = isDevelopment()
29
+
30
+ /** Extensões de asset — nunca devem ser tratadas como página */
31
+ const ASSET_EXT = /\.(js|mjs|ts|tsx|jsx|css|map|json|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|wasm|txt|xml|webmanifest)$/i
32
+
33
+ /** Prefixos internos (Vite/API/dev) que NUNCA são páginas — passam direto */
34
+ const PASSTHROUGH_PREFIXES = ['/@', '/src/', '/node_modules/', '/api', '/swagger', '/__plugins']
35
+
36
+ /** Decide se um path é uma rota de PÁGINA (candidata a SSR) */
37
+ function isPageRoute(path: string): boolean {
38
+ if (ASSET_EXT.test(path)) return false
39
+ return !PASSTHROUGH_PREFIXES.some((p) => path === p || path.startsWith(p))
40
+ }
41
+
42
+ /** Monta o documento HTML completo com o markup SSR + pipeline de hydration */
43
+ function buildDocument(opts: {
44
+ appHtml: string
45
+ bootstrapData?: Record<string, unknown>
46
+ headTags?: string
47
+ }): string {
48
+ const { appHtml, bootstrapData, headTags = '' } = opts
49
+
50
+ const viteHost = (clientConfig.vite.host as string) || 'localhost'
51
+ const vitePort = (clientConfig.vite.port as number) || 5173
52
+ const viteBase = `http://${viteHost}:${vitePort}`
53
+ const entry = (pluginsConfig as Record<string, unknown>).ssrClientEntry as string | undefined
54
+ ?? '/src/entry-ssr.tsx'
55
+
56
+ const dataScript = bootstrapData
57
+ ? `<script>window.__SSR_DATA__ = ${JSON.stringify(bootstrapData).replace(/</g, '\\u003c')};</script>`
58
+ : ''
59
+
60
+ // DEV: pipeline do Vite (react-refresh preamble + client + entry TSX)
61
+ const devScripts = IS_DEV
62
+ ? `<script type="module">
63
+ import RefreshRuntime from "${viteBase}/@react-refresh"
64
+ RefreshRuntime.injectIntoGlobalHook(window)
65
+ window.$RefreshReg$ = () => {}
66
+ window.$RefreshSig$ = () => (type) => type
67
+ window.__vite_plugin_react_preamble_installed__ = true
68
+ </script>
69
+ <script type="module" src="${viteBase}/@vite/client"></script>
70
+ <script type="module" src="${viteBase}${entry}"></script>`
71
+ // PROD: TODO — apontar para o entry buildado (ler do manifest do Vite).
72
+ // Por ora, sem script em prod (HTML estático apenas, sem interatividade).
73
+ : `<!-- TODO(SSR prod): injetar /assets/entry-ssr.[hash].js do build manifest -->`
74
+
75
+ return `<!doctype html>
76
+ <html lang="en">
77
+ <head>
78
+ <meta charset="UTF-8" />
79
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
80
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
81
+ <title>FluxStack</title>
82
+ ${headTags}
83
+ ${IS_DEV ? devScripts : ''}
84
+ </head>
85
+ <body>
86
+ <div id="root">${appHtml}</div>
87
+ ${dataScript}
88
+ ${IS_DEV ? '' : devScripts}
89
+ </body>
90
+ </html>`
91
+ }
92
+
93
+ export const ssrPlugin: Plugin = {
94
+ name: 'ssr',
95
+ version: FLUXSTACK_VERSION,
96
+ description: 'Server-side rendering of the React app for page routes',
97
+ author: 'FluxStack Team',
98
+ priority: PLUGIN_PRIORITY,
99
+ category: 'rendering',
100
+ tags: ['ssr', 'react', 'hydration'],
101
+ dependencies: [],
102
+
103
+ setup: async (context: PluginContext) => {
104
+ const enabled = (pluginsConfig as Record<string, unknown>).ssrEnabled
105
+ if (!enabled) {
106
+ context.logger.debug('SSR plugin disabled (set SSR_ENABLED=true)')
107
+ return
108
+ }
109
+ context.logger.debug(`SSR plugin active (priority ${PLUGIN_PRIORITY})`)
110
+ },
111
+
112
+ onBeforeRoute: async (ctx: RequestContext) => {
113
+ if (!(pluginsConfig as Record<string, unknown>).ssrEnabled) return
114
+ if (ctx.method !== 'GET') return
115
+ if (!isPageRoute(ctx.path)) return
116
+
117
+ const renderer = getSsrRenderer()
118
+ if (!renderer) return // app não registrou renderer → deixa o SPA fallback agir
119
+
120
+ try {
121
+ const result = await renderer({ url: ctx.path, headers: ctx.headers })
122
+ const html = buildDocument({
123
+ appHtml: result.html,
124
+ bootstrapData: result.bootstrapData,
125
+ headTags: result.headTags,
126
+ })
127
+ ctx.handled = true
128
+ ctx.response = new Response(html, {
129
+ status: result.status ?? 200,
130
+ headers: { 'Content-Type': 'text/html; charset=utf-8' },
131
+ })
132
+ } catch (err) {
133
+ // SSR falhou → NÃO trata (handled fica false), deixa o Vite/SPA servir.
134
+ // Falhar para o SPA é melhor que mostrar 500: o client ainda renderiza.
135
+ const msg = err instanceof Error ? err.message : String(err)
136
+ ;(ctx as unknown as { logger?: { error: (m: string) => void } }).logger?.error?.(
137
+ `SSR render failed for ${ctx.path}, falling back to SPA: ${msg}`
138
+ )
139
+ }
140
+ },
141
+ }
142
+
143
+ export default ssrPlugin
@@ -1,38 +1,38 @@
1
- /**
2
- * FluxStack SSR — Renderer Registry
3
- *
4
- * Inversão de dependência: o CORE (framework) não conhece o App do usuário.
5
- * O app registra sua função de render (que importa o AppShell client) aqui,
6
- * e o plugin SSR a consome. Assim o core fica agnóstico ao React do app.
7
- */
8
-
9
- export interface SsrRenderInput {
10
- /** Caminho da rota a renderizar (ex: '/', '/counter') */
11
- url: string
12
- /** Headers da request original (útil para cookies/auth no futuro) */
13
- headers: Record<string, string>
14
- }
15
-
16
- export interface SsrRenderOutput {
17
- /** HTML do corpo do app (vai dentro de <div id="root">) */
18
- html: string
19
- /** Status HTTP (default 200; use 404 para rota não encontrada) */
20
- status?: number
21
- /** Dados serializáveis injetados como window.__SSR_DATA__ para hydration */
22
- bootstrapData?: Record<string, unknown>
23
- /** <head> extra opcional (meta tags, title dinâmico) */
24
- headTags?: string
25
- }
26
-
27
- export type SsrRenderer = (input: SsrRenderInput) => SsrRenderOutput | Promise<SsrRenderOutput>
28
-
29
- let registeredRenderer: SsrRenderer | null = null
30
-
31
- /** O app chama isto no boot para registrar seu renderer (AppShell). */
32
- export function registerSsrRenderer(renderer: SsrRenderer): void {
33
- registeredRenderer = renderer
34
- }
35
-
36
- export function getSsrRenderer(): SsrRenderer | null {
37
- return registeredRenderer
38
- }
1
+ /**
2
+ * FluxStack SSR — Renderer Registry
3
+ *
4
+ * Inversão de dependência: o CORE (framework) não conhece o App do usuário.
5
+ * O app registra sua função de render (que importa o AppShell client) aqui,
6
+ * e o plugin SSR a consome. Assim o core fica agnóstico ao React do app.
7
+ */
8
+
9
+ export interface SsrRenderInput {
10
+ /** Caminho da rota a renderizar (ex: '/', '/counter') */
11
+ url: string
12
+ /** Headers da request original (útil para cookies/auth no futuro) */
13
+ headers: Record<string, string>
14
+ }
15
+
16
+ export interface SsrRenderOutput {
17
+ /** HTML do corpo do app (vai dentro de <div id="root">) */
18
+ html: string
19
+ /** Status HTTP (default 200; use 404 para rota não encontrada) */
20
+ status?: number
21
+ /** Dados serializáveis injetados como window.__SSR_DATA__ para hydration */
22
+ bootstrapData?: Record<string, unknown>
23
+ /** <head> extra opcional (meta tags, title dinâmico) */
24
+ headTags?: string
25
+ }
26
+
27
+ export type SsrRenderer = (input: SsrRenderInput) => SsrRenderOutput | Promise<SsrRenderOutput>
28
+
29
+ let registeredRenderer: SsrRenderer | null = null
30
+
31
+ /** O app chama isto no boot para registrar seu renderer (AppShell). */
32
+ export function registerSsrRenderer(renderer: SsrRenderer): void {
33
+ registeredRenderer = renderer
34
+ }
35
+
36
+ export function getSsrRenderer(): SsrRenderer | null {
37
+ return registeredRenderer
38
+ }
@@ -3,4 +3,4 @@
3
3
  * Single source of truth for version number
4
4
  * Auto-synced with package.json
5
5
  */
6
- export const FLUXSTACK_VERSION = '1.21.1'
6
+ export const FLUXSTACK_VERSION = '1.22.1'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.22.0",
3
+ "version": "1.22.1",
4
4
  "description": "⚡ Revolutionary full-stack TypeScript framework with Declarative Config System, Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",