what-server 0.5.4 → 0.5.5
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/README.md +1 -1
- package/package.json +4 -4
- package/src/actions.js +11 -4
- package/src/index.js +56 -5
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "What Framework - SSR, islands architecture, static generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
"server-rendering",
|
|
30
30
|
"what-framework"
|
|
31
31
|
],
|
|
32
|
-
"author": "",
|
|
32
|
+
"author": "ZVN DEV (https://zvndev.com)",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"what-core": "^0.5.3"
|
|
36
36
|
},
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/CelsianJs/whatfw"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/
|
|
42
|
+
"url": "https://github.com/CelsianJs/whatfw/issues"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://whatfw.com"
|
|
45
45
|
}
|
package/src/actions.js
CHANGED
|
@@ -17,7 +17,6 @@ import { signal, batch } from 'what-core';
|
|
|
17
17
|
|
|
18
18
|
// Registry of server actions
|
|
19
19
|
const actionRegistry = new Map();
|
|
20
|
-
let actionIdCounter = 0;
|
|
21
20
|
|
|
22
21
|
// --- CSRF Protection ---
|
|
23
22
|
// Server generates a token per session; client sends it with every action request.
|
|
@@ -170,11 +169,15 @@ export function formAction(actionFn, options = {}) {
|
|
|
170
169
|
formData = formDataOrEvent;
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
// Convert FormData to plain object
|
|
172
|
+
// Convert FormData to plain object, preserving File instances
|
|
174
173
|
const data = {};
|
|
174
|
+
let hasFiles = false;
|
|
175
175
|
for (const [key, value] of formData.entries()) {
|
|
176
|
+
if (typeof File !== 'undefined' && value instanceof File) {
|
|
177
|
+
hasFiles = true;
|
|
178
|
+
}
|
|
176
179
|
if (data[key]) {
|
|
177
|
-
// Handle multiple values (e.g., checkboxes)
|
|
180
|
+
// Handle multiple values (e.g., checkboxes, multi-file inputs)
|
|
178
181
|
if (Array.isArray(data[key])) {
|
|
179
182
|
data[key].push(value);
|
|
180
183
|
} else {
|
|
@@ -186,7 +189,11 @@ export function formAction(actionFn, options = {}) {
|
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
try {
|
|
189
|
-
|
|
192
|
+
// If form contains files, pass the raw FormData as second arg
|
|
193
|
+
// so the action handler can access files directly
|
|
194
|
+
const result = hasFiles
|
|
195
|
+
? await actionFn(data, formData)
|
|
196
|
+
: await actionFn(data);
|
|
190
197
|
if (onSuccess) onSuccess(result, form);
|
|
191
198
|
if (resetOnSuccess && form) form.reset();
|
|
192
199
|
return result;
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,23 @@ export function renderToString(vnode) {
|
|
|
15
15
|
return escapeHtml(String(vnode));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Signal — unwrap by calling it
|
|
19
|
+
if (typeof vnode === 'function' && vnode._signal) {
|
|
20
|
+
return renderToString(vnode());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Reactive function child — call to get value
|
|
24
|
+
if (typeof vnode === 'function') {
|
|
25
|
+
try {
|
|
26
|
+
return renderToString(vnode());
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
29
|
+
console.warn('[what-server] Error rendering reactive function in SSR:', e.message);
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
// Array
|
|
19
36
|
if (Array.isArray(vnode)) {
|
|
20
37
|
return vnode.map(renderToString).join('');
|
|
@@ -52,6 +69,24 @@ export async function* renderToStream(vnode) {
|
|
|
52
69
|
return;
|
|
53
70
|
}
|
|
54
71
|
|
|
72
|
+
// Signal — unwrap by calling it
|
|
73
|
+
if (typeof vnode === 'function' && vnode._signal) {
|
|
74
|
+
yield* renderToStream(vnode());
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Reactive function child — call to get value
|
|
79
|
+
if (typeof vnode === 'function') {
|
|
80
|
+
try {
|
|
81
|
+
yield* renderToStream(vnode());
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
84
|
+
console.warn('[what-server] Error rendering reactive function in stream SSR:', e.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
if (Array.isArray(vnode)) {
|
|
56
91
|
for (const child of vnode) {
|
|
57
92
|
yield* renderToStream(child);
|
|
@@ -60,10 +95,17 @@ export async function* renderToStream(vnode) {
|
|
|
60
95
|
}
|
|
61
96
|
|
|
62
97
|
if (typeof vnode.tag === 'function') {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
try {
|
|
99
|
+
const result = vnode.tag({ ...vnode.props, children: vnode.children });
|
|
100
|
+
// Support async components
|
|
101
|
+
const resolved = result instanceof Promise ? await result : result;
|
|
102
|
+
yield* renderToStream(resolved);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
105
|
+
console.warn('[what-server] Error rendering component in stream SSR:', e.message);
|
|
106
|
+
}
|
|
107
|
+
yield `<!-- SSR Error: ${escapeHtml(e.message || 'Component error')} -->`;
|
|
108
|
+
}
|
|
67
109
|
return;
|
|
68
110
|
}
|
|
69
111
|
|
|
@@ -181,7 +223,12 @@ function renderAttrs(props) {
|
|
|
181
223
|
.join(';');
|
|
182
224
|
out += ` style="${escapeHtml(css)}"`;
|
|
183
225
|
} else if (val === true) {
|
|
184
|
-
|
|
226
|
+
// ARIA attributes require explicit ="true", HTML boolean attrs can be bare
|
|
227
|
+
if (key.startsWith('aria-') || key === 'role') {
|
|
228
|
+
out += ` ${key}="true"`;
|
|
229
|
+
} else {
|
|
230
|
+
out += ` ${key}`;
|
|
231
|
+
}
|
|
185
232
|
} else {
|
|
186
233
|
out += ` ${key}="${escapeHtml(String(val))}"`;
|
|
187
234
|
}
|
|
@@ -199,6 +246,7 @@ function escapeHtml(str) {
|
|
|
199
246
|
}
|
|
200
247
|
|
|
201
248
|
function camelToKebab(str) {
|
|
249
|
+
if (str.startsWith('--')) return str; // CSS custom properties (variables) — leave unchanged
|
|
202
250
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
203
251
|
}
|
|
204
252
|
|
|
@@ -219,4 +267,7 @@ export {
|
|
|
219
267
|
invalidatePath,
|
|
220
268
|
handleActionRequest,
|
|
221
269
|
getRegisteredActions,
|
|
270
|
+
generateCsrfToken,
|
|
271
|
+
validateCsrfToken,
|
|
272
|
+
csrfMetaTag,
|
|
222
273
|
} from './actions.js';
|