@hyperspan/framework 0.1.0 → 0.1.2
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/build.ts +27 -0
- package/dist/assets.d.ts +46 -0
- package/dist/assets.js +393 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +2477 -0
- package/dist/server.d.ts +132 -0
- package/dist/server.js +2477 -0
- package/package.json +21 -8
- package/src/assets.ts +12 -12
- package/src/clientjs/hyperspan-client.ts +4 -4
- package/src/index.ts +1 -0
- package/src/server.ts +19 -80
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
|
-
"
|
|
6
|
-
"module": "src/server.ts",
|
|
5
|
+
"main": "dist/index.js",
|
|
7
6
|
"public": true,
|
|
8
7
|
"publishConfig": {
|
|
9
8
|
"access": "public"
|
|
10
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./assets": {
|
|
16
|
+
"types": "./dist/assets.d.ts",
|
|
17
|
+
"default": "./dist/assets.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
11
20
|
"author": "Vance Lucas <vance@vancelucas.com>",
|
|
12
21
|
"license": "BSD-3-Clause",
|
|
13
22
|
"keywords": [
|
|
@@ -23,18 +32,22 @@
|
|
|
23
32
|
"homepage": "https://www.hyperspan.dev",
|
|
24
33
|
"repository": {
|
|
25
34
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/vlucas/hyperspan
|
|
35
|
+
"url": "git+https://github.com/vlucas/hyperspan.git"
|
|
27
36
|
},
|
|
28
37
|
"bugs": {
|
|
29
|
-
"url": "https://github.com/vlucas/hyperspan
|
|
38
|
+
"url": "https://github.com/vlucas/hyperspan/issues"
|
|
30
39
|
},
|
|
31
40
|
"scripts": {
|
|
32
|
-
"
|
|
41
|
+
"build": "bun ./build.ts",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"test": "bun test",
|
|
44
|
+
"prepack": "npm run clean && npm run build"
|
|
33
45
|
},
|
|
34
46
|
"devDependencies": {
|
|
35
47
|
"@types/bun": "^1.1.9",
|
|
36
48
|
"@types/node": "^22.5.5",
|
|
37
49
|
"@types/react": "^19.1.0",
|
|
50
|
+
"bun-plugin-dts": "^0.3.0",
|
|
38
51
|
"bun-types": "latest",
|
|
39
52
|
"prettier": "^3.2.5"
|
|
40
53
|
},
|
|
@@ -42,10 +55,10 @@
|
|
|
42
55
|
"typescript": "^5.0.0"
|
|
43
56
|
},
|
|
44
57
|
"dependencies": {
|
|
45
|
-
"@hyperspan/html": "^0.1.
|
|
58
|
+
"@hyperspan/html": "^0.1.1",
|
|
46
59
|
"@preact/compat": "^18.3.1",
|
|
47
60
|
"hono": "^4.7.4",
|
|
48
61
|
"isbot": "^5.1.25",
|
|
49
|
-
"
|
|
62
|
+
"zod": "^4.0.0-beta.20250415T232143"
|
|
50
63
|
}
|
|
51
64
|
}
|
package/src/assets.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import {html} from '@hyperspan/html';
|
|
2
|
+
import {md5} from './clientjs/md5';
|
|
3
|
+
import {readdir} from 'node:fs/promises';
|
|
4
|
+
import {resolve} from 'node:path';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const IS_PROD = process.env.NODE_ENV === 'production';
|
|
7
7
|
const PWD = import.meta.dir;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Build client JS for end users (minimal JS for Hyperspan to work)
|
|
11
11
|
*/
|
|
12
|
-
export const clientJSFiles = new Map<string, {
|
|
12
|
+
export const clientJSFiles = new Map<string, {src: string; type?: string}>();
|
|
13
13
|
export async function buildClientJS() {
|
|
14
|
-
const sourceFile = resolve(PWD, '../', './
|
|
14
|
+
const sourceFile = resolve(PWD, '../', './src/clientjs/hyperspan-client.ts');
|
|
15
15
|
const output = await Bun.build({
|
|
16
16
|
entrypoints: [sourceFile],
|
|
17
17
|
outdir: `./public/_hs/js`,
|
|
@@ -20,7 +20,7 @@ export async function buildClientJS() {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
const jsFile = output.outputs[0].path.split('/').reverse()[0];
|
|
23
|
-
clientJSFiles.set('_hs', {
|
|
23
|
+
clientJSFiles.set('_hs', {src: '/_hs/js/' + jsFile});
|
|
24
24
|
return jsFile;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -61,7 +61,7 @@ export async function buildClientCSS() {
|
|
|
61
61
|
export function hyperspanStyleTags() {
|
|
62
62
|
const cssFiles = Array.from(clientCSSFiles.entries());
|
|
63
63
|
return html`${cssFiles.map(
|
|
64
|
-
([
|
|
64
|
+
([_, file]) => html`<link rel="stylesheet" href="/_hs/css/${file}" />`
|
|
65
65
|
)}`;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -84,13 +84,13 @@ export function hyperspanScriptTags() {
|
|
|
84
84
|
}
|
|
85
85
|
</script>
|
|
86
86
|
${jsFiles.map(
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
([key, file]) =>
|
|
88
|
+
html`<script
|
|
89
89
|
id="js-${key}"
|
|
90
90
|
type="${file.type || 'text/javascript'}"
|
|
91
91
|
src="${file.src}"
|
|
92
92
|
></script>`
|
|
93
|
-
|
|
93
|
+
)}
|
|
94
94
|
`;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {html} from '@hyperspan/html';
|
|
2
|
+
import {Idiomorph} from './idiomorph.esm';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Used for streaming content from the server to the client.
|
|
@@ -20,7 +20,7 @@ function htmlAsyncContentObserver() {
|
|
|
20
20
|
asyncContent.forEach((el: any) => {
|
|
21
21
|
try {
|
|
22
22
|
// Also observe child nodes for nested async content
|
|
23
|
-
asyncContentObserver.observe(el.content, {
|
|
23
|
+
asyncContentObserver.observe(el.content, {childList: true, subtree: true});
|
|
24
24
|
|
|
25
25
|
const slotId = el.id.replace('_content', '');
|
|
26
26
|
const slotEl = document.getElementById(slotId);
|
|
@@ -41,7 +41,7 @@ function htmlAsyncContentObserver() {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
|
-
asyncContentObserver.observe(document.body, {
|
|
44
|
+
asyncContentObserver.observe(document.body, {childList: true, subtree: true});
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
htmlAsyncContentObserver();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './server';
|
package/src/server.ts
CHANGED
|
@@ -5,82 +5,12 @@ import { isbot } from 'isbot';
|
|
|
5
5
|
import { buildClientJS, buildClientCSS } from './assets';
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
7
|
import { serveStatic } from 'hono/bun';
|
|
8
|
+
import * as z from 'zod';
|
|
8
9
|
import type { Context, Handler } from 'hono';
|
|
9
10
|
|
|
10
|
-
import * as v from 'valibot';
|
|
11
|
-
import type {
|
|
12
|
-
AnySchema,
|
|
13
|
-
ArraySchema,
|
|
14
|
-
BigintSchema,
|
|
15
|
-
BooleanSchema,
|
|
16
|
-
DateSchema,
|
|
17
|
-
EnumSchema,
|
|
18
|
-
GenericIssue,
|
|
19
|
-
IntersectSchema,
|
|
20
|
-
LazySchema,
|
|
21
|
-
LiteralSchema,
|
|
22
|
-
NullSchema,
|
|
23
|
-
NullableSchema,
|
|
24
|
-
NullishSchema,
|
|
25
|
-
NumberSchema,
|
|
26
|
-
ObjectSchema,
|
|
27
|
-
ObjectWithRestSchema,
|
|
28
|
-
OptionalSchema,
|
|
29
|
-
PicklistSchema,
|
|
30
|
-
PipeItem,
|
|
31
|
-
RecordSchema,
|
|
32
|
-
SchemaWithPipe,
|
|
33
|
-
StrictObjectSchema,
|
|
34
|
-
StrictTupleSchema,
|
|
35
|
-
StringSchema,
|
|
36
|
-
TupleSchema,
|
|
37
|
-
TupleWithRestSchema,
|
|
38
|
-
UndefinedSchema,
|
|
39
|
-
UnionSchema,
|
|
40
|
-
VariantSchema,
|
|
41
|
-
} from 'valibot';
|
|
42
|
-
|
|
43
11
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
44
|
-
const PWD = import.meta.dir;
|
|
45
12
|
const CWD = process.cwd();
|
|
46
13
|
|
|
47
|
-
type NonPipeSchemas =
|
|
48
|
-
| AnySchema
|
|
49
|
-
| LiteralSchema<any, any>
|
|
50
|
-
| NullSchema<any>
|
|
51
|
-
| NumberSchema<any>
|
|
52
|
-
| BigintSchema<any>
|
|
53
|
-
| StringSchema<any>
|
|
54
|
-
| BooleanSchema<any>
|
|
55
|
-
| NullableSchema<any, any>
|
|
56
|
-
| StrictObjectSchema<any, any>
|
|
57
|
-
| ObjectSchema<any, any>
|
|
58
|
-
| ObjectWithRestSchema<any, any, any>
|
|
59
|
-
| RecordSchema<any, any, any>
|
|
60
|
-
| ArraySchema<any, any>
|
|
61
|
-
| TupleSchema<any, any>
|
|
62
|
-
| StrictTupleSchema<any, any>
|
|
63
|
-
| TupleWithRestSchema<readonly any[], any, any>
|
|
64
|
-
| IntersectSchema<any, any>
|
|
65
|
-
| UnionSchema<any, any>
|
|
66
|
-
| VariantSchema<any, any, any>
|
|
67
|
-
| PicklistSchema<any, any>
|
|
68
|
-
| EnumSchema<any, any>
|
|
69
|
-
| LazySchema<any>
|
|
70
|
-
| DateSchema<any>
|
|
71
|
-
| NullishSchema<any, any>
|
|
72
|
-
| OptionalSchema<any, any>
|
|
73
|
-
| UndefinedSchema<any>;
|
|
74
|
-
|
|
75
|
-
type PipeSchema = SchemaWithPipe<[NonPipeSchemas, ...PipeItem<any, any, GenericIssue<any>>[]]>;
|
|
76
|
-
// Type inference for valibot taken from:
|
|
77
|
-
// @link https://github.com/gcornut/valibot-json-schema/blob/main/src/toJSONSchema/schemas.ts
|
|
78
|
-
export type TSupportedSchema = NonPipeSchemas | PipeSchema;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* ===========================================================================
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
14
|
/**
|
|
85
15
|
* Route
|
|
86
16
|
* Define a route that can handle a direct HTTP request
|
|
@@ -114,7 +44,7 @@ export function createComponent(render: () => THSComponentReturn | Promise<THSCo
|
|
|
114
44
|
*/
|
|
115
45
|
export function createForm(
|
|
116
46
|
renderForm: (data?: any) => THSResponseTypes,
|
|
117
|
-
schema?:
|
|
47
|
+
schema?: z.ZodSchema | null
|
|
118
48
|
): HSFormRoute {
|
|
119
49
|
return new HSFormRoute(renderForm, schema);
|
|
120
50
|
}
|
|
@@ -165,12 +95,12 @@ export class HSFormRoute {
|
|
|
165
95
|
_handlers: Record<string, Handler> = {};
|
|
166
96
|
_form: THSFormRenderer;
|
|
167
97
|
_methods: null | string[] = null;
|
|
168
|
-
_schema: null |
|
|
98
|
+
_schema: null | z.ZodSchema = null;
|
|
169
99
|
|
|
170
|
-
constructor(renderForm: THSFormRenderer, schema:
|
|
100
|
+
constructor(renderForm: THSFormRenderer, schema: z.ZodSchema | null = null) {
|
|
171
101
|
// Haz schema?
|
|
172
102
|
if (schema) {
|
|
173
|
-
type TSchema =
|
|
103
|
+
type TSchema = z.infer<typeof schema>;
|
|
174
104
|
this._form = renderForm as (data: TSchema) => THSResponseTypes;
|
|
175
105
|
this._schema = schema;
|
|
176
106
|
} else {
|
|
@@ -178,7 +108,7 @@ export class HSFormRoute {
|
|
|
178
108
|
}
|
|
179
109
|
|
|
180
110
|
// GET request is render form by default
|
|
181
|
-
this._handlers.GET = (
|
|
111
|
+
this._handlers.GET = () => renderForm(this.getDefaultData());
|
|
182
112
|
}
|
|
183
113
|
|
|
184
114
|
// Form data
|
|
@@ -187,8 +117,8 @@ export class HSFormRoute {
|
|
|
187
117
|
return {};
|
|
188
118
|
}
|
|
189
119
|
|
|
190
|
-
type TSchema =
|
|
191
|
-
const data =
|
|
120
|
+
type TSchema = z.infer<typeof this._schema>;
|
|
121
|
+
const data = z.parse(this._schema, {});
|
|
192
122
|
return data as TSchema;
|
|
193
123
|
}
|
|
194
124
|
|
|
@@ -276,8 +206,15 @@ export async function runFileRoute(RouteModule: any, context: Context): Promise<
|
|
|
276
206
|
return routeContent;
|
|
277
207
|
}
|
|
278
208
|
|
|
209
|
+
let routeKind = typeof routeContent;
|
|
210
|
+
|
|
279
211
|
// Render TmplHtml if returned from route handler
|
|
280
|
-
if (
|
|
212
|
+
if (
|
|
213
|
+
routeKind === 'object' &&
|
|
214
|
+
(routeContent instanceof TmplHtml ||
|
|
215
|
+
routeContent.constructor.name === 'TmplHtml' ||
|
|
216
|
+
routeContent?._kind === 'TmplHtml')
|
|
217
|
+
) {
|
|
281
218
|
if (streamingEnabled) {
|
|
282
219
|
return new StreamResponse(renderStream(routeContent)) as Response;
|
|
283
220
|
} else {
|
|
@@ -286,6 +223,8 @@ export async function runFileRoute(RouteModule: any, context: Context): Promise<
|
|
|
286
223
|
}
|
|
287
224
|
}
|
|
288
225
|
|
|
226
|
+
console.log('Returning unknown type... ', routeContent);
|
|
227
|
+
|
|
289
228
|
return routeContent;
|
|
290
229
|
} catch (e) {
|
|
291
230
|
console.error(e);
|
|
@@ -379,7 +318,7 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
|
|
|
379
318
|
|
|
380
319
|
if (dynamicPaths) {
|
|
381
320
|
params = [];
|
|
382
|
-
route = route.replace(ROUTE_SEGMENT, (match: string
|
|
321
|
+
route = route.replace(ROUTE_SEGMENT, (match: string) => {
|
|
383
322
|
const paramName = match.replace(/[^a-zA-Z_\.]+/g, '');
|
|
384
323
|
|
|
385
324
|
if (match.includes('...')) {
|