create-fluxstack 1.8.3 → 1.10.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.
- package/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/README.md +653 -275
- package/app/client/src/App.tsx +39 -43
- package/app/client/src/lib/eden-api.ts +2 -7
- package/app/client/src/live/FileUploadExample.tsx +359 -0
- package/app/client/src/live/MinimalLiveClock.tsx +47 -0
- package/app/client/src/live/QuickUploadTest.tsx +193 -0
- package/app/client/src/main.tsx +10 -10
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +45 -44
- package/app/client/tsconfig.node.json +25 -25
- package/app/server/index.ts +30 -103
- package/app/server/live/LiveFileUploadComponent.ts +77 -0
- package/app/server/live/register-components.ts +19 -19
- package/core/build/bundler.ts +202 -55
- package/core/build/index.ts +126 -2
- package/core/build/live-components-generator.ts +68 -1
- package/core/cli/generators/plugin.ts +6 -6
- package/core/cli/index.ts +232 -4
- package/core/client/LiveComponentsProvider.tsx +3 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/useChunkedUpload.ts +112 -61
- package/core/client/hooks/useHybridLiveComponent.ts +80 -26
- package/core/client/hooks/useTypedLiveComponent.ts +133 -0
- package/core/client/hooks/useWebSocket.ts +4 -16
- package/core/client/index.ts +20 -2
- package/core/framework/server.ts +181 -8
- package/core/live/ComponentRegistry.ts +5 -1
- package/core/plugins/built-in/index.ts +8 -5
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
- package/core/plugins/built-in/vite/index.ts +75 -187
- package/core/plugins/built-in/vite/vite-dev.ts +88 -0
- package/core/plugins/registry.ts +54 -2
- package/core/plugins/types.ts +86 -2
- package/core/server/index.ts +1 -2
- package/core/server/live/ComponentRegistry.ts +14 -5
- package/core/server/live/FileUploadManager.ts +22 -25
- package/core/server/live/auto-generated-components.ts +29 -26
- package/core/server/live/websocket-plugin.ts +19 -5
- package/core/server/plugins/static-files-plugin.ts +49 -240
- package/core/server/plugins/swagger.ts +33 -33
- package/core/types/build.ts +22 -0
- package/core/types/plugin.ts +9 -1
- package/core/types/types.ts +137 -0
- package/core/utils/logger/startup-banner.ts +20 -4
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +7 -7
- package/eslint.config.js +23 -23
- package/package.json +3 -2
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +52 -51
- package/workspace.json +5 -5
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// 🚀 Quick Upload Test - Compact upload tester for home page
|
|
2
|
+
import { useState, useRef } from 'react'
|
|
3
|
+
import { useTypedLiveComponent, useChunkedUpload, useLiveComponents } from '@/core/client'
|
|
4
|
+
|
|
5
|
+
// Import component type DIRECTLY from backend - full type inference!
|
|
6
|
+
import type { LiveFileUploadComponent } from '@/server/live/LiveFileUploadComponent'
|
|
7
|
+
|
|
8
|
+
export function QuickUploadTest() {
|
|
9
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
10
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
11
|
+
|
|
12
|
+
// Get sendMessageAndWait from LiveComponents context
|
|
13
|
+
const { sendMessageAndWait } = useLiveComponents()
|
|
14
|
+
|
|
15
|
+
// Setup Live Component with full type inference
|
|
16
|
+
const {
|
|
17
|
+
state,
|
|
18
|
+
call,
|
|
19
|
+
componentId,
|
|
20
|
+
connected
|
|
21
|
+
} = useTypedLiveComponent<LiveFileUploadComponent>('LiveFileUpload', {
|
|
22
|
+
uploadedFiles: [],
|
|
23
|
+
maxFiles: 10
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Setup Chunked Upload with Adaptive Chunking
|
|
27
|
+
const {
|
|
28
|
+
uploading,
|
|
29
|
+
progress,
|
|
30
|
+
error: uploadError,
|
|
31
|
+
uploadFile,
|
|
32
|
+
cancelUpload,
|
|
33
|
+
reset: resetUpload,
|
|
34
|
+
bytesUploaded,
|
|
35
|
+
totalBytes
|
|
36
|
+
} = useChunkedUpload(componentId || '', {
|
|
37
|
+
chunkSize: 64 * 1024,
|
|
38
|
+
maxFileSize: 500 * 1024 * 1024,
|
|
39
|
+
allowedTypes: [], // Aceita todos os tipos
|
|
40
|
+
sendMessageAndWait,
|
|
41
|
+
|
|
42
|
+
// Enable Adaptive Chunking
|
|
43
|
+
adaptiveChunking: true,
|
|
44
|
+
adaptiveConfig: {
|
|
45
|
+
minChunkSize: 16 * 1024,
|
|
46
|
+
maxChunkSize: 512 * 1024,
|
|
47
|
+
initialChunkSize: 64 * 1024,
|
|
48
|
+
targetLatency: 200,
|
|
49
|
+
adjustmentFactor: 1.5,
|
|
50
|
+
measurementWindow: 3
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
onComplete: async (response) => {
|
|
54
|
+
if (selectedFile && response.fileUrl) {
|
|
55
|
+
await call('onFileUploaded', {
|
|
56
|
+
filename: selectedFile.name,
|
|
57
|
+
fileUrl: response.fileUrl
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
setSelectedFile(null)
|
|
61
|
+
resetUpload()
|
|
62
|
+
if (fileInputRef.current) {
|
|
63
|
+
fileInputRef.current.value = ''
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
onError: (error) => {
|
|
68
|
+
console.error('Upload error:', error)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
73
|
+
const file = e.target.files?.[0]
|
|
74
|
+
if (file) {
|
|
75
|
+
setSelectedFile(file)
|
|
76
|
+
resetUpload()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const handleUpload = async () => {
|
|
81
|
+
if (!selectedFile) return
|
|
82
|
+
await uploadFile(selectedFile)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const formatBytes = (bytes: number): string => {
|
|
86
|
+
if (bytes === 0) return '0 B'
|
|
87
|
+
const k = 1024
|
|
88
|
+
const sizes = ['B', 'KB', 'MB']
|
|
89
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
90
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!connected) {
|
|
94
|
+
return (
|
|
95
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-6">
|
|
96
|
+
<div className="text-yellow-400 text-sm">🔌 Connecting...</div>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-6 hover:bg-white/10 transition-all">
|
|
103
|
+
<div className="flex items-center gap-3 mb-4">
|
|
104
|
+
<div className="text-3xl">📤</div>
|
|
105
|
+
<div>
|
|
106
|
+
<h3 className="text-lg font-semibold text-white">Adaptive Upload Test</h3>
|
|
107
|
+
<p className="text-xs text-gray-400">Dynamic chunk sizing enabled</p>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* File Input */}
|
|
112
|
+
<div className="space-y-3">
|
|
113
|
+
<input
|
|
114
|
+
ref={fileInputRef}
|
|
115
|
+
type="file"
|
|
116
|
+
onChange={handleFileSelect}
|
|
117
|
+
disabled={uploading}
|
|
118
|
+
className="block w-full text-sm text-gray-300 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-purple-600 file:text-white hover:file:bg-purple-700 disabled:opacity-50 cursor-pointer"
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
{/* Selected File */}
|
|
122
|
+
{selectedFile && !uploading && (
|
|
123
|
+
<div className="text-sm text-gray-300">
|
|
124
|
+
📁 {selectedFile.name} ({formatBytes(selectedFile.size)})
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Upload Progress */}
|
|
129
|
+
{uploading && (
|
|
130
|
+
<div className="space-y-2">
|
|
131
|
+
<div className="flex justify-between text-xs text-gray-300">
|
|
132
|
+
<span>Uploading...</span>
|
|
133
|
+
<span>{progress.toFixed(1)}%</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="w-full bg-gray-700 rounded-full h-2">
|
|
136
|
+
<div
|
|
137
|
+
className="bg-gradient-to-r from-blue-500 to-purple-600 h-2 rounded-full transition-all duration-300"
|
|
138
|
+
style={{ width: `${progress}%` }}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="text-xs text-gray-400">
|
|
142
|
+
{formatBytes(bytesUploaded)} / {formatBytes(totalBytes)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{/* Error */}
|
|
148
|
+
{uploadError && (
|
|
149
|
+
<div className="text-xs text-red-400">❌ {uploadError}</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{/* Buttons */}
|
|
153
|
+
<div className="flex gap-2">
|
|
154
|
+
<button
|
|
155
|
+
onClick={handleUpload}
|
|
156
|
+
disabled={!selectedFile || uploading}
|
|
157
|
+
className="flex-1 px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg hover:shadow-lg hover:shadow-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed font-medium text-sm transition-all"
|
|
158
|
+
>
|
|
159
|
+
{uploading ? '⏳ Uploading...' : '🚀 Upload'}
|
|
160
|
+
</button>
|
|
161
|
+
|
|
162
|
+
{uploading && (
|
|
163
|
+
<button
|
|
164
|
+
onClick={cancelUpload}
|
|
165
|
+
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 font-medium text-sm"
|
|
166
|
+
>
|
|
167
|
+
❌
|
|
168
|
+
</button>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Last Upload Info */}
|
|
173
|
+
{state.uploadedFiles.length > 0 && !uploading && (
|
|
174
|
+
<div className="pt-3 border-t border-white/10">
|
|
175
|
+
<div className="text-xs text-green-400">
|
|
176
|
+
✅ Last: {state.uploadedFiles[0].filename}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{/* Quick Stats */}
|
|
182
|
+
<div className="pt-3 border-t border-white/10">
|
|
183
|
+
<div className="text-xs text-gray-400 space-y-1">
|
|
184
|
+
<div>🚀 Adaptive: 16KB - 512KB</div>
|
|
185
|
+
<div>📊 Target: 200ms/chunk</div>
|
|
186
|
+
<div>💾 Max: 500MB</div>
|
|
187
|
+
<div>📁 All file types</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
)
|
|
193
|
+
}
|
package/app/client/src/main.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { StrictMode } from 'react'
|
|
2
|
-
import { createRoot } from 'react-dom/client'
|
|
3
|
-
import './index.css'
|
|
4
|
-
import App from './App.tsx'
|
|
5
|
-
|
|
6
|
-
createRoot(document.getElementById('root')!).render(
|
|
7
|
-
<StrictMode>
|
|
8
|
-
<App />
|
|
9
|
-
</StrictMode>,
|
|
10
|
-
)
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import './index.css'
|
|
4
|
+
import App from './App.tsx'
|
|
5
|
+
|
|
6
|
+
createRoot(document.getElementById('root')!).render(
|
|
7
|
+
<StrictMode>
|
|
8
|
+
<App />
|
|
9
|
+
</StrictMode>,
|
|
10
|
+
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -1,44 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"useDefineForClassFields": true,
|
|
6
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
-
"module": "ESNext",
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
|
|
10
|
-
/* Bundler mode */
|
|
11
|
-
"moduleResolution": "bundler",
|
|
12
|
-
"allowImportingTsExtensions": true,
|
|
13
|
-
"verbatimModuleSyntax": true,
|
|
14
|
-
"moduleDetection": "force",
|
|
15
|
-
"noEmit": true,
|
|
16
|
-
"jsx": "react-jsx",
|
|
17
|
-
|
|
18
|
-
/* Path mapping (alias support) */
|
|
19
|
-
"baseUrl": ".",
|
|
20
|
-
"paths": {
|
|
21
|
-
"@/*": ["./src/*"],
|
|
22
|
-
"@/components/*": ["./src/components/*"],
|
|
23
|
-
"@/utils/*": ["./src/utils/*"],
|
|
24
|
-
"@/hooks/*": ["./src/hooks/*"],
|
|
25
|
-
"@/assets/*": ["./src/assets/*"],
|
|
26
|
-
"@/lib/*": ["./src/lib/*"],
|
|
27
|
-
"@/types/*": ["./src/types/*"],
|
|
28
|
-
"@/shared/*": ["../shared/*"],
|
|
29
|
-
"@/
|
|
30
|
-
"@/
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
|
|
18
|
+
/* Path mapping (alias support) */
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["./src/*"],
|
|
22
|
+
"@/components/*": ["./src/components/*"],
|
|
23
|
+
"@/utils/*": ["./src/utils/*"],
|
|
24
|
+
"@/hooks/*": ["./src/hooks/*"],
|
|
25
|
+
"@/assets/*": ["./src/assets/*"],
|
|
26
|
+
"@/lib/*": ["./src/lib/*"],
|
|
27
|
+
"@/types/*": ["./src/types/*"],
|
|
28
|
+
"@/shared/*": ["../shared/*"],
|
|
29
|
+
"@/server/*": ["../server/*"],
|
|
30
|
+
"@/core/*": ["../../core/*"],
|
|
31
|
+
"@/config/*": ["../../config/*"],
|
|
32
|
+
"fluxstack": ["../../core/client/fluxstack"],
|
|
33
|
+
"elysia": ["../../node_modules/elysia"]
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/* Linting */
|
|
37
|
+
"strict": true,
|
|
38
|
+
"noUnusedLocals": true,
|
|
39
|
+
"noUnusedParameters": true,
|
|
40
|
+
"erasableSyntaxOnly": true,
|
|
41
|
+
"noFallthroughCasesInSwitch": true,
|
|
42
|
+
"noUncheckedSideEffectImports": true
|
|
43
|
+
},
|
|
44
|
+
"include": ["src"]
|
|
45
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
-
"target": "ES2023",
|
|
5
|
-
"lib": ["ES2023"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
|
|
9
|
-
/* Bundler mode */
|
|
10
|
-
"moduleResolution": "bundler",
|
|
11
|
-
"allowImportingTsExtensions": true,
|
|
12
|
-
"verbatimModuleSyntax": true,
|
|
13
|
-
"moduleDetection": "force",
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
|
|
16
|
-
/* Linting */
|
|
17
|
-
"strict": true,
|
|
18
|
-
"noUnusedLocals": true,
|
|
19
|
-
"noUnusedParameters": true,
|
|
20
|
-
"erasableSyntaxOnly": true,
|
|
21
|
-
"noFallthroughCasesInSwitch": true,
|
|
22
|
-
"noUncheckedSideEffectImports": true
|
|
23
|
-
},
|
|
24
|
-
"include": ["vite.config.ts"]
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"erasableSyntaxOnly": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedSideEffectImports": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["vite.config.ts"]
|
|
25
|
+
}
|
package/app/server/index.ts
CHANGED
|
@@ -1,115 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Main server configuration and initialization
|
|
2
|
+
* FluxStack Application Server Entry Point
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
//
|
|
5
|
+
// Core
|
|
7
6
|
import { FluxStackFramework } from "@/core/server"
|
|
8
|
-
import { isDevelopment } from "@/core/utils/helpers"
|
|
9
|
-
import { DEBUG } from "@/core/utils/logger"
|
|
10
7
|
import { helpers } from "@/core/utils/env"
|
|
11
|
-
|
|
12
|
-
// ===== Configuration =====
|
|
13
8
|
import { appConfig } from "@/config/app.config"
|
|
14
9
|
import { serverConfig } from "@/config/server.config"
|
|
15
10
|
|
|
16
|
-
//
|
|
17
|
-
import {
|
|
18
|
-
vitePlugin,
|
|
19
|
-
swaggerPlugin,
|
|
20
|
-
staticPlugin,
|
|
21
|
-
liveComponentsPlugin,
|
|
22
|
-
staticFilesPlugin
|
|
23
|
-
} from "@/core/server"
|
|
11
|
+
// Plugins
|
|
12
|
+
import { vitePlugin, swaggerPlugin, liveComponentsPlugin, staticFilesPlugin } from "@/core/server"
|
|
24
13
|
import cryptoAuthPlugin from "@/plugins/crypto-auth"
|
|
25
|
-
|
|
26
|
-
// ===== Application Routes =====
|
|
14
|
+
// Routes & Components
|
|
27
15
|
import { appInstance } from "./app"
|
|
28
16
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
function createFrameworkConfig() {
|
|
46
|
-
return {
|
|
47
|
-
server: {
|
|
48
|
-
port: serverConfig.server.port,
|
|
49
|
-
host: serverConfig.server.host,
|
|
50
|
-
apiPrefix: serverConfig.server.apiPrefix,
|
|
51
|
-
cors: {
|
|
52
|
-
origins: serverConfig.cors.origins,
|
|
53
|
-
methods: serverConfig.cors.methods,
|
|
54
|
-
headers: serverConfig.cors.headers,
|
|
55
|
-
credentials: serverConfig.cors.credentials
|
|
56
|
-
},
|
|
57
|
-
middleware: []
|
|
58
|
-
},
|
|
59
|
-
app: {
|
|
60
|
-
name: appConfig.name,
|
|
61
|
-
version: appConfig.version
|
|
62
|
-
},
|
|
63
|
-
client: {
|
|
64
|
-
port: serverConfig.server.backendPort,
|
|
65
|
-
proxy: {
|
|
66
|
-
target: helpers.getServerUrl()
|
|
67
|
-
},
|
|
68
|
-
build: {
|
|
69
|
-
sourceMaps: false,
|
|
70
|
-
minify: false,
|
|
71
|
-
target: 'es2020' as any,
|
|
72
|
-
outDir: 'dist'
|
|
73
|
-
}
|
|
74
|
-
}
|
|
17
|
+
// Server
|
|
18
|
+
const app = new FluxStackFramework({
|
|
19
|
+
server: {
|
|
20
|
+
...serverConfig.server,
|
|
21
|
+
cors: serverConfig.cors,
|
|
22
|
+
middleware: []
|
|
23
|
+
},
|
|
24
|
+
app: {
|
|
25
|
+
name: appConfig.name,
|
|
26
|
+
version: appConfig.version
|
|
27
|
+
},
|
|
28
|
+
client: {
|
|
29
|
+
port: serverConfig.server.backendPort,
|
|
30
|
+
proxy: { target: helpers.getServerUrl() },
|
|
31
|
+
build: { sourceMaps: false, minify: false, target: 'es2020', outDir: 'dist' }
|
|
75
32
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
app
|
|
86
|
-
|
|
87
|
-
// 2. Development/Production plugins (conditional)
|
|
88
|
-
if (isDevelopment()) {
|
|
89
|
-
app.use(vitePlugin) // Development: Vite dev server
|
|
90
|
-
} else {
|
|
91
|
-
app.use(staticPlugin) // Production: Static file serving
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 3. Static files (after Vite, before Live Components to avoid conflicts)
|
|
95
|
-
app.use(staticFilesPlugin)
|
|
96
|
-
|
|
97
|
-
// 4. Live Components (WebSocket support)
|
|
98
|
-
app.use(liveComponentsPlugin)
|
|
99
|
-
|
|
100
|
-
// ===== Register Routes =====
|
|
101
|
-
// Note: Routes are now registered in app.ts (including envTestRoute)
|
|
102
|
-
app.routes(appInstance)
|
|
103
|
-
|
|
104
|
-
// ===== Final Setup =====
|
|
105
|
-
|
|
106
|
-
// Swagger documentation (must be last to discover all routes)
|
|
107
|
-
app.use(swaggerPlugin)
|
|
108
|
-
|
|
109
|
-
// ===== Start Server =====
|
|
110
|
-
// Banner will be displayed automatically by the framework
|
|
111
|
-
app.listen()
|
|
112
|
-
|
|
113
|
-
// ===== Eden Treaty Type Export =====
|
|
114
|
-
// Export application type for type-safe client communication
|
|
115
|
-
export type App = typeof app
|
|
33
|
+
})
|
|
34
|
+
.use(cryptoAuthPlugin)
|
|
35
|
+
.use(vitePlugin)
|
|
36
|
+
.use(staticFilesPlugin)
|
|
37
|
+
.use(liveComponentsPlugin)
|
|
38
|
+
.routes(appInstance)
|
|
39
|
+
.use(swaggerPlugin)
|
|
40
|
+
.listen()
|
|
41
|
+
|
|
42
|
+
export type App = typeof app
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// 📤 Live File Upload Component - Universal file upload with chunking
|
|
2
|
+
import { LiveComponent } from '@/core/types/types'
|
|
3
|
+
|
|
4
|
+
interface FileUploadState {
|
|
5
|
+
uploadedFiles: Array<{
|
|
6
|
+
id: string
|
|
7
|
+
filename: string
|
|
8
|
+
url: string
|
|
9
|
+
uploadedAt: number
|
|
10
|
+
}>
|
|
11
|
+
maxFiles: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class LiveFileUploadComponent extends LiveComponent<FileUploadState> {
|
|
15
|
+
constructor(initialState: FileUploadState, ws: any, options?: { room?: string; userId?: string }) {
|
|
16
|
+
super({
|
|
17
|
+
uploadedFiles: [],
|
|
18
|
+
maxFiles: 10,
|
|
19
|
+
...initialState
|
|
20
|
+
}, ws, options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handle successful file upload
|
|
25
|
+
* This is called from the client after useChunkedUpload completes
|
|
26
|
+
*/
|
|
27
|
+
async onFileUploaded(payload: { filename: string; fileUrl: string }): Promise<void> {
|
|
28
|
+
const { filename, fileUrl } = payload
|
|
29
|
+
|
|
30
|
+
// Add new file to the list
|
|
31
|
+
const newFile = {
|
|
32
|
+
id: `file-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
33
|
+
filename,
|
|
34
|
+
url: fileUrl,
|
|
35
|
+
uploadedAt: Date.now()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Limit to maxFiles
|
|
39
|
+
const updatedFiles = [newFile, ...this.state.uploadedFiles].slice(0, this.state.maxFiles)
|
|
40
|
+
|
|
41
|
+
// Update state and broadcast to all clients
|
|
42
|
+
this.setState({
|
|
43
|
+
uploadedFiles: updatedFiles
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Remove an uploaded file
|
|
49
|
+
*/
|
|
50
|
+
async removeFile(payload: { fileId: string }): Promise<void> {
|
|
51
|
+
this.setState({
|
|
52
|
+
uploadedFiles: this.state.uploadedFiles.filter(file => file.id !== payload.fileId)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clear all uploaded files
|
|
58
|
+
*/
|
|
59
|
+
async clearAll(): Promise<void> {
|
|
60
|
+
this.setState({
|
|
61
|
+
uploadedFiles: []
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get upload statistics
|
|
67
|
+
*/
|
|
68
|
+
async getStats(): Promise<{
|
|
69
|
+
totalFiles: number
|
|
70
|
+
remainingSlots: number
|
|
71
|
+
}> {
|
|
72
|
+
return {
|
|
73
|
+
totalFiles: this.state.uploadedFiles.length,
|
|
74
|
+
remainingSlots: this.state.maxFiles - this.state.uploadedFiles.length
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// ⚠️ DEPRECATION NOTICE
|
|
2
|
-
// This file has been moved to: core/server/live/auto-generated-components.ts
|
|
3
|
-
//
|
|
4
|
-
// The auto-generated component registration is now located in the core/ directory
|
|
5
|
-
// to prevent accidental user modifications and keep framework code separate from
|
|
6
|
-
// application code.
|
|
7
|
-
//
|
|
8
|
-
// If you're looking for component registration logic:
|
|
9
|
-
// - Generated file: core/server/live/auto-generated-components.ts (auto-generated during build)
|
|
10
|
-
// - Generator: core/build/live-components-generator.ts
|
|
11
|
-
// - Import location: app/server/index.ts
|
|
12
|
-
//
|
|
13
|
-
// To add new Live Components:
|
|
14
|
-
// 1. Create your component class in this directory (app/server/live/)
|
|
15
|
-
// 2. Extend LiveComponent class
|
|
16
|
-
// 3. Run 'bun run build' to regenerate the registration file
|
|
17
|
-
|
|
18
|
-
// This file intentionally left empty - do not import
|
|
19
|
-
export {}
|
|
1
|
+
// ⚠️ DEPRECATION NOTICE
|
|
2
|
+
// This file has been moved to: core/server/live/auto-generated-components.ts
|
|
3
|
+
//
|
|
4
|
+
// The auto-generated component registration is now located in the core/ directory
|
|
5
|
+
// to prevent accidental user modifications and keep framework code separate from
|
|
6
|
+
// application code.
|
|
7
|
+
//
|
|
8
|
+
// If you're looking for component registration logic:
|
|
9
|
+
// - Generated file: core/server/live/auto-generated-components.ts (auto-generated during build)
|
|
10
|
+
// - Generator: core/build/live-components-generator.ts
|
|
11
|
+
// - Import location: app/server/index.ts
|
|
12
|
+
//
|
|
13
|
+
// To add new Live Components:
|
|
14
|
+
// 1. Create your component class in this directory (app/server/live/)
|
|
15
|
+
// 2. Extend LiveComponent class
|
|
16
|
+
// 3. Run 'bun run build' to regenerate the registration file
|
|
17
|
+
|
|
18
|
+
// This file intentionally left empty - do not import
|
|
19
|
+
export {}
|