@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "1.0.0-alpha.4",
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.2.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
- body: req.body,
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
- body: any;
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
  }