@hyperspan/framework 0.1.0 → 0.1.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/build.ts +27 -0
- package/dist/assets.d.ts +34 -0
- package/dist/assets.js +394 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +2484 -0
- package/dist/server.d.ts +121 -0
- package/dist/server.js +2484 -0
- package/package.json +21 -8
- package/src/assets.ts +1 -1
- package/src/index.ts +1 -0
- package/src/server.ts +26 -96
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
@@ -3,7 +3,7 @@ import { md5 } from './clientjs/md5';
|
|
|
3
3
|
import { readdir } from 'node:fs/promises';
|
|
4
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
|
/**
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './server';
|
package/src/server.ts
CHANGED
|
@@ -1,86 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
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';
|
|
1
|
+
import {readdir} from 'node:fs/promises';
|
|
2
|
+
import {basename, extname, join} from 'node:path';
|
|
3
|
+
import {TmplHtml, html, renderStream, renderAsync, render} from '@hyperspan/html';
|
|
4
|
+
import {isbot} from 'isbot';
|
|
5
|
+
import {buildClientJS, buildClientCSS} from './assets';
|
|
6
|
+
import {Hono} from 'hono';
|
|
7
|
+
import {serveStatic} from 'hono/bun';
|
|
8
|
+
import * as z from 'zod';
|
|
9
|
+
import type {Context, Handler} from 'hono';
|
|
42
10
|
|
|
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
|
|
|
@@ -263,7 +193,7 @@ export async function runFileRoute(RouteModule: any, context: Context): Promise<
|
|
|
263
193
|
if (!routeMethodHandler) {
|
|
264
194
|
return new Response('Method Not Allowed', {
|
|
265
195
|
status: 405,
|
|
266
|
-
headers: {
|
|
196
|
+
headers: {'content-type': 'text/plain'},
|
|
267
197
|
});
|
|
268
198
|
}
|
|
269
199
|
|
|
@@ -305,13 +235,13 @@ async function runAPIRoute(routeFn: any, context: Context, middlewareResult?: an
|
|
|
305
235
|
|
|
306
236
|
return context.json(
|
|
307
237
|
{
|
|
308
|
-
meta: {
|
|
238
|
+
meta: {success: false},
|
|
309
239
|
data: {
|
|
310
240
|
message: e.message,
|
|
311
241
|
stack: IS_PROD ? undefined : e.stack?.split('\n'),
|
|
312
242
|
},
|
|
313
243
|
},
|
|
314
|
-
{
|
|
244
|
+
{status: 500}
|
|
315
245
|
);
|
|
316
246
|
}
|
|
317
247
|
}
|
|
@@ -337,7 +267,7 @@ async function showErrorReponse(context: Context, err: Error) {
|
|
|
337
267
|
export type THSServerConfig = {
|
|
338
268
|
appDir: string;
|
|
339
269
|
staticFileRoot: string;
|
|
340
|
-
rewrites?: Array<{
|
|
270
|
+
rewrites?: Array<{source: string; destination: string}>;
|
|
341
271
|
// For customizing the routes and adding your own...
|
|
342
272
|
beforeRoutesAdded?: (app: Hono) => void;
|
|
343
273
|
afterRoutesAdded?: (app: Hono) => void;
|
|
@@ -357,7 +287,7 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
|
|
|
357
287
|
// Walk all pages and add them as routes
|
|
358
288
|
const routesDir = join(config.appDir, 'routes');
|
|
359
289
|
console.log(routesDir);
|
|
360
|
-
const files = await readdir(routesDir, {
|
|
290
|
+
const files = await readdir(routesDir, {recursive: true});
|
|
361
291
|
const routes: THSRouteMap[] = [];
|
|
362
292
|
|
|
363
293
|
for (const file of files) {
|
|
@@ -379,7 +309,7 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
|
|
|
379
309
|
|
|
380
310
|
if (dynamicPaths) {
|
|
381
311
|
params = [];
|
|
382
|
-
route = route.replace(ROUTE_SEGMENT, (match: string
|
|
312
|
+
route = route.replace(ROUTE_SEGMENT, (match: string) => {
|
|
383
313
|
const paramName = match.replace(/[^a-zA-Z_\.]+/g, '');
|
|
384
314
|
|
|
385
315
|
if (match.includes('...')) {
|
|
@@ -423,7 +353,7 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
423
353
|
const fullRouteFile = join(CWD, route.file);
|
|
424
354
|
const routePattern = normalizePath(route.route);
|
|
425
355
|
|
|
426
|
-
routeMap.push({
|
|
356
|
+
routeMap.push({route: routePattern, file: route.file});
|
|
427
357
|
|
|
428
358
|
// Import route
|
|
429
359
|
const routeModule = await import(fullRouteFile);
|
|
@@ -443,7 +373,7 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
443
373
|
app.get('/', (context) => {
|
|
444
374
|
return context.text(
|
|
445
375
|
'No routes found. Add routes to app/routes. Example: `app/routes/index.ts`',
|
|
446
|
-
{
|
|
376
|
+
{status: 404}
|
|
447
377
|
);
|
|
448
378
|
});
|
|
449
379
|
}
|
|
@@ -466,7 +396,7 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
466
396
|
);
|
|
467
397
|
|
|
468
398
|
app.notFound((context) => {
|
|
469
|
-
return context.text('Not... found?', {
|
|
399
|
+
return context.text('Not... found?', {status: 404});
|
|
470
400
|
});
|
|
471
401
|
|
|
472
402
|
return app;
|
|
@@ -501,7 +431,7 @@ export function createReadableStreamFromAsyncGenerator(output: AsyncGenerator) {
|
|
|
501
431
|
return new ReadableStream({
|
|
502
432
|
async start(controller) {
|
|
503
433
|
while (true) {
|
|
504
|
-
const {
|
|
434
|
+
const {done, value} = await output.next();
|
|
505
435
|
|
|
506
436
|
if (done) {
|
|
507
437
|
controller.close();
|