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,181 +1,181 @@
1
1
  'use client'
2
- import { useMemo, useState, useEffect, useRef, useCallback } from 'react'
3
- import { Live } from '@/core/client'
4
- import { LivePingPong } from '@server/live/LivePingPong'
5
- import type { PingRoom } from '@server/live/rooms/PingRoom'
6
- import { FaGaugeHigh, FaPlay, FaSignal, FaStopwatch } from 'react-icons/fa6'
7
-
8
- interface PingEntry {
9
- seq: number
10
- sentAt: number
11
- rtt: number | null
12
- }
13
-
14
- function StatCard({ label, value, tone = 'white' }: { label: string; value: string; tone?: string }) {
15
- return (
16
- <div className="rounded-lg border border-white/10 bg-white/[0.025] p-4">
17
- <div className={`text-2xl font-semibold tabular-nums ${tone}`}>{value}</div>
18
- <div className="mt-1 text-xs font-medium uppercase tracking-[0.16em] text-gray-600">{label}</div>
19
- </div>
20
- )
21
- }
22
-
23
- export function PingPongDemo() {
24
- const username = useMemo(() => {
25
- const prefix = ['Edge', 'Core', 'Node', 'Wire', 'Frame'][Math.floor(Math.random() * 5)]
26
- const suffix = Math.floor(Math.random() * 100)
27
- return `${prefix}-${suffix}`
28
- }, [])
29
-
30
- const live = Live.use(LivePingPong, {
31
- initialState: { ...LivePingPong.defaultState, username },
32
- })
33
-
34
- const [pings, setPings] = useState<PingEntry[]>([])
35
- const [avgRtt, setAvgRtt] = useState<number | null>(null)
36
- const [minRtt, setMinRtt] = useState<number | null>(null)
37
- const [maxRtt, setMaxRtt] = useState<number | null>(null)
38
- const [autoPing, setAutoPing] = useState(false)
39
- const seqRef = useRef(0)
40
- const pendingRef = useRef<Map<number, number>>(new Map())
41
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
42
-
43
- useEffect(() => {
44
- const unsub = live.$room<PingRoom>('ping:global').on('pong', (data) => {
45
- const sentAt = pendingRef.current.get(data.seq)
46
- if (sentAt == null) return
47
- pendingRef.current.delete(data.seq)
48
-
49
- const rtt = Date.now() - sentAt
50
- setPings(prev => {
51
- const updated = [{ seq: data.seq, sentAt, rtt }, ...prev].slice(0, 18)
52
- const rtts = updated.filter(p => p.rtt != null).map(p => p.rtt!)
53
- if (rtts.length > 0) {
54
- setAvgRtt(Math.round(rtts.reduce((a, b) => a + b, 0) / rtts.length))
55
- setMinRtt(Math.min(...rtts))
56
- setMaxRtt(Math.max(...rtts))
57
- }
58
- return updated
59
- })
60
- })
61
- return unsub
62
- }, [live])
63
-
64
- const sendPing = useCallback(() => {
65
- const seq = ++seqRef.current
66
- pendingRef.current.set(seq, Date.now())
67
- live.ping({ seq })
68
- }, [live])
69
-
70
- useEffect(() => {
71
- if (autoPing && live.$connected) {
72
- intervalRef.current = setInterval(sendPing, 500)
73
- }
74
- return () => {
75
- if (intervalRef.current != null) {
76
- clearInterval(intervalRef.current)
77
- intervalRef.current = null
78
- }
79
- }
80
- }, [autoPing, live.$connected, sendPing])
81
-
82
- const rttColor = (rtt: number) => {
83
- if (rtt < 10) return 'text-emerald-300'
84
- if (rtt < 50) return 'text-amber-300'
85
- return 'text-red-300'
86
- }
87
-
88
- return (
89
- <div className="grid w-full max-w-5xl gap-4 lg:grid-cols-[380px_1fr]">
90
- <section className="rounded-lg border border-white/10 bg-[#07070b]/85 p-5 shadow-2xl shadow-black/20">
91
- <div className="mb-6 flex items-start justify-between gap-4">
92
- <div>
93
- <h2 className="text-2xl font-semibold tracking-tight text-white">Latency probe</h2>
94
- <p className="mt-2 text-sm leading-6 text-gray-500">
95
- Send binary msgpack events and measure round-trip time through the Live room.
96
- </p>
97
- </div>
98
- <span className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs ${
99
- live.$connected
100
- ? 'border-emerald-400/25 bg-emerald-400/10 text-emerald-200'
101
- : 'border-red-400/25 bg-red-400/10 text-red-200'
102
- }`}>
103
- <span className={`h-1.5 w-1.5 rounded-full ${live.$connected ? 'bg-emerald-300' : 'bg-red-300'}`} />
104
- {live.$connected ? 'Connected' : 'Offline'}
105
- </span>
106
- </div>
107
-
108
- <div className="grid gap-3">
109
- <StatCard label="Average" value={avgRtt != null ? `${avgRtt}ms` : '--'} />
110
- <div className="grid grid-cols-2 gap-3">
111
- <StatCard label="Min" value={minRtt != null ? `${minRtt}ms` : '--'} tone="text-emerald-300" />
112
- <StatCard label="Max" value={maxRtt != null ? `${maxRtt}ms` : '--'} tone="text-red-300" />
113
- </div>
114
- </div>
115
-
116
- <div className="mt-6 grid grid-cols-2 gap-3">
117
- <button
118
- onClick={sendPing}
119
- disabled={!live.$connected || live.$loading}
120
- className="inline-flex h-12 items-center justify-center gap-2 rounded-lg bg-white px-4 text-sm font-semibold text-black transition hover:bg-gray-200 disabled:opacity-50"
121
- >
122
- <FaPlay className="h-3.5 w-3.5" />
123
- Ping
124
- </button>
125
- <button
126
- onClick={() => setAutoPing(!autoPing)}
127
- disabled={!live.$connected}
128
- className={`inline-flex h-12 items-center justify-center gap-2 rounded-lg border px-4 text-sm font-semibold transition disabled:opacity-50 ${
129
- autoPing
130
- ? 'border-amber-400/25 bg-amber-400/10 text-amber-200'
131
- : 'border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.06]'
132
- }`}
133
- >
134
- <FaStopwatch className="h-3.5 w-3.5" />
135
- {autoPing ? 'Auto on' : 'Auto off'}
136
- </button>
137
- </div>
138
-
139
- <div className="mt-6 rounded-lg border border-white/10 bg-white/[0.025] p-4 text-sm text-gray-400">
140
- <div className="mb-2 flex items-center gap-2 text-white">
141
- <FaSignal className="text-theme" />
142
- Session
143
- </div>
144
- <div className="grid gap-2 text-xs">
145
- <div className="flex justify-between"><span>Client</span><span className="font-mono text-gray-200">{username}</span></div>
146
- <div className="flex justify-between"><span>Online</span><span className="font-mono text-gray-200">{live.$state.onlineCount}</span></div>
147
- <div className="flex justify-between"><span>Total pings</span><span className="font-mono text-gray-200">{live.$state.totalPings}</span></div>
148
- </div>
149
- </div>
150
- </section>
151
-
152
- <section className="rounded-lg border border-white/10 bg-black/30">
153
- <div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
154
- <div className="inline-flex items-center gap-2 text-sm font-semibold text-white">
155
- <FaGaugeHigh className="text-theme" />
156
- Event log
157
- </div>
158
- <span className="text-xs text-gray-500">wire: msgpack binary frames</span>
159
- </div>
160
-
161
- <div className="max-h-[560px] overflow-auto">
162
- {pings.length === 0 ? (
163
- <div className="px-4 py-14 text-center text-sm text-gray-600">
164
- Send a ping to populate the event log.
165
- </div>
166
- ) : (
167
- pings.map((p) => (
168
- <div key={p.seq} className="grid grid-cols-[90px_1fr_auto] items-center gap-3 border-b border-white/5 px-4 py-3 text-sm">
169
- <span className="font-mono text-gray-500">#{p.seq}</span>
170
- <span className="text-gray-500">{new Date(p.sentAt).toLocaleTimeString()}</span>
171
- <span className={`font-mono font-semibold ${p.rtt != null ? rttColor(p.rtt) : 'text-gray-600'}`}>
172
- {p.rtt != null ? `${p.rtt}ms` : 'pending'}
173
- </span>
174
- </div>
175
- ))
176
- )}
177
- </div>
178
- </section>
179
- </div>
180
- )
181
- }
2
+ import { useMemo, useState, useEffect, useRef, useCallback } from 'react'
3
+ import { Live } from '@/core/client'
4
+ import { LivePingPong } from '@server/live/LivePingPong'
5
+ import type { PingRoom } from '@server/live/rooms/PingRoom'
6
+ import { FaGaugeHigh, FaPlay, FaSignal, FaStopwatch } from 'react-icons/fa6'
7
+
8
+ interface PingEntry {
9
+ seq: number
10
+ sentAt: number
11
+ rtt: number | null
12
+ }
13
+
14
+ function StatCard({ label, value, tone = 'white' }: { label: string; value: string; tone?: string }) {
15
+ return (
16
+ <div className="rounded-lg border border-white/10 bg-white/[0.025] p-4">
17
+ <div className={`text-2xl font-semibold tabular-nums ${tone}`}>{value}</div>
18
+ <div className="mt-1 text-xs font-medium uppercase tracking-[0.16em] text-gray-600">{label}</div>
19
+ </div>
20
+ )
21
+ }
22
+
23
+ export function PingPongDemo() {
24
+ const username = useMemo(() => {
25
+ const prefix = ['Edge', 'Core', 'Node', 'Wire', 'Frame'][Math.floor(Math.random() * 5)]
26
+ const suffix = Math.floor(Math.random() * 100)
27
+ return `${prefix}-${suffix}`
28
+ }, [])
29
+
30
+ const live = Live.use(LivePingPong, {
31
+ initialState: { ...LivePingPong.defaultState, username },
32
+ })
33
+
34
+ const [pings, setPings] = useState<PingEntry[]>([])
35
+ const [avgRtt, setAvgRtt] = useState<number | null>(null)
36
+ const [minRtt, setMinRtt] = useState<number | null>(null)
37
+ const [maxRtt, setMaxRtt] = useState<number | null>(null)
38
+ const [autoPing, setAutoPing] = useState(false)
39
+ const seqRef = useRef(0)
40
+ const pendingRef = useRef<Map<number, number>>(new Map())
41
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
42
+
43
+ useEffect(() => {
44
+ const unsub = live.$room<PingRoom>('ping:global').on('pong', (data) => {
45
+ const sentAt = pendingRef.current.get(data.seq)
46
+ if (sentAt == null) return
47
+ pendingRef.current.delete(data.seq)
48
+
49
+ const rtt = Date.now() - sentAt
50
+ setPings(prev => {
51
+ const updated = [{ seq: data.seq, sentAt, rtt }, ...prev].slice(0, 18)
52
+ const rtts = updated.filter(p => p.rtt != null).map(p => p.rtt!)
53
+ if (rtts.length > 0) {
54
+ setAvgRtt(Math.round(rtts.reduce((a, b) => a + b, 0) / rtts.length))
55
+ setMinRtt(Math.min(...rtts))
56
+ setMaxRtt(Math.max(...rtts))
57
+ }
58
+ return updated
59
+ })
60
+ })
61
+ return unsub
62
+ }, [live])
63
+
64
+ const sendPing = useCallback(() => {
65
+ const seq = ++seqRef.current
66
+ pendingRef.current.set(seq, Date.now())
67
+ live.ping({ seq })
68
+ }, [live])
69
+
70
+ useEffect(() => {
71
+ if (autoPing && live.$connected) {
72
+ intervalRef.current = setInterval(sendPing, 500)
73
+ }
74
+ return () => {
75
+ if (intervalRef.current != null) {
76
+ clearInterval(intervalRef.current)
77
+ intervalRef.current = null
78
+ }
79
+ }
80
+ }, [autoPing, live.$connected, sendPing])
81
+
82
+ const rttColor = (rtt: number) => {
83
+ if (rtt < 10) return 'text-emerald-300'
84
+ if (rtt < 50) return 'text-amber-300'
85
+ return 'text-red-300'
86
+ }
87
+
88
+ return (
89
+ <div className="grid w-full max-w-5xl gap-4 lg:grid-cols-[380px_1fr]">
90
+ <section className="rounded-lg border border-white/10 bg-[#07070b]/85 p-5 shadow-2xl shadow-black/20">
91
+ <div className="mb-6 flex items-start justify-between gap-4">
92
+ <div>
93
+ <h2 className="text-2xl font-semibold tracking-tight text-white">Latency probe</h2>
94
+ <p className="mt-2 text-sm leading-6 text-gray-500">
95
+ Send binary msgpack events and measure round-trip time through the Live room.
96
+ </p>
97
+ </div>
98
+ <span className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs ${
99
+ live.$connected
100
+ ? 'border-emerald-400/25 bg-emerald-400/10 text-emerald-200'
101
+ : 'border-red-400/25 bg-red-400/10 text-red-200'
102
+ }`}>
103
+ <span className={`h-1.5 w-1.5 rounded-full ${live.$connected ? 'bg-emerald-300' : 'bg-red-300'}`} />
104
+ {live.$connected ? 'Connected' : 'Offline'}
105
+ </span>
106
+ </div>
107
+
108
+ <div className="grid gap-3">
109
+ <StatCard label="Average" value={avgRtt != null ? `${avgRtt}ms` : '--'} />
110
+ <div className="grid grid-cols-2 gap-3">
111
+ <StatCard label="Min" value={minRtt != null ? `${minRtt}ms` : '--'} tone="text-emerald-300" />
112
+ <StatCard label="Max" value={maxRtt != null ? `${maxRtt}ms` : '--'} tone="text-red-300" />
113
+ </div>
114
+ </div>
115
+
116
+ <div className="mt-6 grid grid-cols-2 gap-3">
117
+ <button
118
+ onClick={sendPing}
119
+ disabled={!live.$connected || live.$loading}
120
+ className="inline-flex h-12 items-center justify-center gap-2 rounded-lg bg-white px-4 text-sm font-semibold text-black transition hover:bg-gray-200 disabled:opacity-50"
121
+ >
122
+ <FaPlay className="h-3.5 w-3.5" />
123
+ Ping
124
+ </button>
125
+ <button
126
+ onClick={() => setAutoPing(!autoPing)}
127
+ disabled={!live.$connected}
128
+ className={`inline-flex h-12 items-center justify-center gap-2 rounded-lg border px-4 text-sm font-semibold transition disabled:opacity-50 ${
129
+ autoPing
130
+ ? 'border-amber-400/25 bg-amber-400/10 text-amber-200'
131
+ : 'border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.06]'
132
+ }`}
133
+ >
134
+ <FaStopwatch className="h-3.5 w-3.5" />
135
+ {autoPing ? 'Auto on' : 'Auto off'}
136
+ </button>
137
+ </div>
138
+
139
+ <div className="mt-6 rounded-lg border border-white/10 bg-white/[0.025] p-4 text-sm text-gray-400">
140
+ <div className="mb-2 flex items-center gap-2 text-white">
141
+ <FaSignal className="text-theme" />
142
+ Session
143
+ </div>
144
+ <div className="grid gap-2 text-xs">
145
+ <div className="flex justify-between"><span>Client</span><span className="font-mono text-gray-200">{username}</span></div>
146
+ <div className="flex justify-between"><span>Online</span><span className="font-mono text-gray-200">{live.$state.onlineCount}</span></div>
147
+ <div className="flex justify-between"><span>Total pings</span><span className="font-mono text-gray-200">{live.$state.totalPings}</span></div>
148
+ </div>
149
+ </div>
150
+ </section>
151
+
152
+ <section className="rounded-lg border border-white/10 bg-black/30">
153
+ <div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
154
+ <div className="inline-flex items-center gap-2 text-sm font-semibold text-white">
155
+ <FaGaugeHigh className="text-theme" />
156
+ Event log
157
+ </div>
158
+ <span className="text-xs text-gray-500">wire: msgpack binary frames</span>
159
+ </div>
160
+
161
+ <div className="max-h-[560px] overflow-auto">
162
+ {pings.length === 0 ? (
163
+ <div className="px-4 py-14 text-center text-sm text-gray-600">
164
+ Send a ping to populate the event log.
165
+ </div>
166
+ ) : (
167
+ pings.map((p) => (
168
+ <div key={p.seq} className="grid grid-cols-[90px_1fr_auto] items-center gap-3 border-b border-white/5 px-4 py-3 text-sm">
169
+ <span className="font-mono text-gray-500">#{p.seq}</span>
170
+ <span className="text-gray-500">{new Date(p.sentAt).toLocaleTimeString()}</span>
171
+ <span className={`font-mono font-semibold ${p.rtt != null ? rttColor(p.rtt) : 'text-gray-600'}`}>
172
+ {p.rtt != null ? `${p.rtt}ms` : 'pending'}
173
+ </span>
174
+ </div>
175
+ ))
176
+ )}
177
+ </div>
178
+ </section>
179
+ </div>
180
+ )
181
+ }