@hyperspan/framework 1.0.0-alpha.4 → 1.0.0-alpha.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/package.json +2 -2
- package/src/actions.ts +1 -70
- package/src/server.ts +4 -1
- package/src/types.ts +4 -1
- package/src/utils.ts +69 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.5",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"main": "src/server.ts",
|
|
6
6
|
"types": "src/server.ts",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@hyperspan/html": "0.
|
|
83
|
+
"@hyperspan/html": "^1.0.0-alpha",
|
|
84
84
|
"zod": "^4.1.12"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/actions.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { html, HSHtml } from '@hyperspan/html';
|
|
|
2
2
|
import { createRoute, returnHTMLResponse } from './server';
|
|
3
3
|
import * as z from 'zod/v4';
|
|
4
4
|
import type { Hyperspan as HS } from './types';
|
|
5
|
-
import { assetHash } from './utils';
|
|
5
|
+
import { assetHash, formDataToJSON } from './utils';
|
|
6
6
|
import * as actionsClient from './client/_hs/hyperspan-actions.client';
|
|
7
7
|
import { renderClientJS } from './client/js';
|
|
8
8
|
|
|
@@ -115,73 +115,4 @@ export function createAction<T extends z.ZodTypeAny>(params: { name: string; sch
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
return api;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Return JSON data structure for a given FormData object
|
|
122
|
-
* Accounts for array fields (e.g. name="options[]" or <select multiple>)
|
|
123
|
-
*
|
|
124
|
-
* @link https://stackoverflow.com/a/75406413
|
|
125
|
-
*/
|
|
126
|
-
export function formDataToJSON(formData: FormData): Record<string, string | string[]> {
|
|
127
|
-
let object = {};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Parses FormData key xxx`[x][x][x]` fields into array
|
|
131
|
-
*/
|
|
132
|
-
const parseKey = (key: string) => {
|
|
133
|
-
const subKeyIdx = key.indexOf('[');
|
|
134
|
-
|
|
135
|
-
if (subKeyIdx !== -1) {
|
|
136
|
-
const keys = [key.substring(0, subKeyIdx)];
|
|
137
|
-
key = key.substring(subKeyIdx);
|
|
138
|
-
|
|
139
|
-
for (const match of key.matchAll(/\[(?<key>.*?)]/gm)) {
|
|
140
|
-
if (match.groups) {
|
|
141
|
-
keys.push(match.groups.key);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return keys;
|
|
145
|
-
} else {
|
|
146
|
-
return [key];
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Recursively iterates over keys and assigns key/values to object
|
|
152
|
-
*/
|
|
153
|
-
const assign = (keys: string[], value: FormDataEntryValue, object: any): void => {
|
|
154
|
-
const key = keys.shift();
|
|
155
|
-
|
|
156
|
-
// When last key in the iterations
|
|
157
|
-
if (key === '' || key === undefined) {
|
|
158
|
-
return object.push(value);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (Reflect.has(object, key)) {
|
|
162
|
-
// If key has been found, but final pass - convert the value to array
|
|
163
|
-
if (keys.length === 0) {
|
|
164
|
-
if (!Array.isArray(object[key])) {
|
|
165
|
-
object[key] = [object[key], value];
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Recurse again with found object
|
|
170
|
-
return assign(keys, value, object[key]);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Create empty object for key, if next key is '' do array instead, otherwise set value
|
|
174
|
-
if (keys.length >= 1) {
|
|
175
|
-
object[key] = keys[0] === '' ? [] : {};
|
|
176
|
-
return assign(keys, value, object[key]);
|
|
177
|
-
} else {
|
|
178
|
-
object[key] = value;
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
for (const pair of formData.entries()) {
|
|
183
|
-
assign(parseKey(pair[0]), pair[1], object);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return object;
|
|
187
118
|
}
|
package/src/server.ts
CHANGED
|
@@ -49,7 +49,10 @@ export function createContext(req: Request, route?: HS.Route): HS.Context {
|
|
|
49
49
|
method,
|
|
50
50
|
headers,
|
|
51
51
|
query,
|
|
52
|
-
|
|
52
|
+
async text() { return req.text() },
|
|
53
|
+
async json<T = unknown>() { return await req.json() as T },
|
|
54
|
+
async formData<T = unknown>() { return await req.formData() as T },
|
|
55
|
+
async urlencoded() { return new URLSearchParams(await req.text()) },
|
|
53
56
|
},
|
|
54
57
|
res: {
|
|
55
58
|
headers: new Headers(),
|
package/src/types.ts
CHANGED
|
@@ -42,7 +42,10 @@ export namespace Hyperspan {
|
|
|
42
42
|
method: string; // Always uppercase
|
|
43
43
|
headers: Headers; // Case-insensitive
|
|
44
44
|
query: URLSearchParams;
|
|
45
|
-
|
|
45
|
+
text: () => Promise<string>;
|
|
46
|
+
json<T = unknown>(): Promise<T>;
|
|
47
|
+
formData<T = unknown>(): Promise<T>;
|
|
48
|
+
urlencoded(): Promise<URLSearchParams>;
|
|
46
49
|
};
|
|
47
50
|
res: {
|
|
48
51
|
headers: Headers; // Headers to merge with final outgoing response
|
package/src/utils.ts
CHANGED
|
@@ -6,4 +6,73 @@ export function assetHash(content: string): string {
|
|
|
6
6
|
|
|
7
7
|
export function randomHash(): string {
|
|
8
8
|
return createHash('md5').update(randomBytes(32).toString('hex')).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Return JSON data structure for a given FormData or URLSearchParams object
|
|
13
|
+
* Accounts for array fields (e.g. name="options[]" or <select multiple>)
|
|
14
|
+
*
|
|
15
|
+
* @link https://stackoverflow.com/a/75406413
|
|
16
|
+
*/
|
|
17
|
+
export function formDataToJSON(formData: FormData | URLSearchParams): Record<string, string | string[]> {
|
|
18
|
+
let object = {};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parses FormData key xxx`[x][x][x]` fields into array
|
|
22
|
+
*/
|
|
23
|
+
const parseKey = (key: string) => {
|
|
24
|
+
const subKeyIdx = key.indexOf('[');
|
|
25
|
+
|
|
26
|
+
if (subKeyIdx !== -1) {
|
|
27
|
+
const keys = [key.substring(0, subKeyIdx)];
|
|
28
|
+
key = key.substring(subKeyIdx);
|
|
29
|
+
|
|
30
|
+
for (const match of key.matchAll(/\[(?<key>.*?)]/gm)) {
|
|
31
|
+
if (match.groups) {
|
|
32
|
+
keys.push(match.groups.key);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return keys;
|
|
36
|
+
} else {
|
|
37
|
+
return [key];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Recursively iterates over keys and assigns key/values to object
|
|
43
|
+
*/
|
|
44
|
+
const assign = (keys: string[], value: FormDataEntryValue, object: any): void => {
|
|
45
|
+
const key = keys.shift();
|
|
46
|
+
|
|
47
|
+
// When last key in the iterations
|
|
48
|
+
if (key === '' || key === undefined) {
|
|
49
|
+
return object.push(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (Reflect.has(object, key)) {
|
|
53
|
+
// If key has been found, but final pass - convert the value to array
|
|
54
|
+
if (keys.length === 0) {
|
|
55
|
+
if (!Array.isArray(object[key])) {
|
|
56
|
+
object[key] = [object[key], value];
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Recurse again with found object
|
|
61
|
+
return assign(keys, value, object[key]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create empty object for key, if next key is '' do array instead, otherwise set value
|
|
65
|
+
if (keys.length >= 1) {
|
|
66
|
+
object[key] = keys[0] === '' ? [] : {};
|
|
67
|
+
return assign(keys, value, object[key]);
|
|
68
|
+
} else {
|
|
69
|
+
object[key] = value;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
for (const pair of formData.entries()) {
|
|
74
|
+
assign(parseKey(pair[0]), pair[1], object);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return object;
|
|
9
78
|
}
|