@hyperspan/framework 0.0.1 → 0.0.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 +29 -0
- package/bun.lockb +0 -0
- package/dist/index.d.ts +50 -0
- package/dist/server.d.ts +109 -0
- package/dist/server.js +11 -5
- package/package.json +18 -5
- package/src/server.ts +12 -4
- package/tsconfig.json +1 -2
package/build.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import dts from 'bun-plugin-dts';
|
|
2
|
+
|
|
3
|
+
await Promise.all([
|
|
4
|
+
// Build JS
|
|
5
|
+
Bun.build({
|
|
6
|
+
entrypoints: ['./src/index.ts'],
|
|
7
|
+
outdir: './dist',
|
|
8
|
+
target: 'browser',
|
|
9
|
+
}),
|
|
10
|
+
Bun.build({
|
|
11
|
+
entrypoints: ['./src/server.ts'],
|
|
12
|
+
outdir: './dist',
|
|
13
|
+
target: 'node',
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
// Build type files for TypeScript
|
|
17
|
+
Bun.build({
|
|
18
|
+
entrypoints: ['./src/index.ts'],
|
|
19
|
+
outdir: './dist',
|
|
20
|
+
target: 'browser',
|
|
21
|
+
plugins: [dts()],
|
|
22
|
+
}),
|
|
23
|
+
Bun.build({
|
|
24
|
+
entrypoints: ['./src/server.ts'],
|
|
25
|
+
outdir: './dist',
|
|
26
|
+
target: 'node',
|
|
27
|
+
plugins: [dts()],
|
|
28
|
+
}),
|
|
29
|
+
]);
|
package/bun.lockb
CHANGED
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Template object - used so it will be possible to (eventually) pass down context
|
|
5
|
+
*/
|
|
6
|
+
export declare class HSTemplate {
|
|
7
|
+
__hsTemplate: boolean;
|
|
8
|
+
content: any[];
|
|
9
|
+
constructor(content: any[]);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* HTML template
|
|
13
|
+
*/
|
|
14
|
+
export declare function html(strings: TemplateStringsArray, ...values: any[]): HSTemplate;
|
|
15
|
+
export declare namespace html {
|
|
16
|
+
var raw: (value: string) => HSTemplate;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render HSTemplate to async generator that streams output to a string
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderToStream(template: HSTemplate | string): AsyncGenerator<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Render HSTemplate to string (awaits/buffers entire response)
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderToString(template: HSTemplate | string): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Strip extra spacing between HTML tags (used for tests)
|
|
28
|
+
*/
|
|
29
|
+
export declare function compressHTMLString(str: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* LOL JavaScript...
|
|
32
|
+
*/
|
|
33
|
+
export declare function _typeOf(obj: any): string;
|
|
34
|
+
/**
|
|
35
|
+
* Client component
|
|
36
|
+
*/
|
|
37
|
+
export type THSWCState = Record<string, any>;
|
|
38
|
+
export type THSWCSetStateArg = THSWCState | ((state: THSWCState) => THSWCState);
|
|
39
|
+
export type THSWC = {
|
|
40
|
+
this: THSWC;
|
|
41
|
+
state: THSWCState | undefined;
|
|
42
|
+
id: string;
|
|
43
|
+
setState: (fn: THSWCSetStateArg) => THSWCState;
|
|
44
|
+
mergeState: (newState: THSWCState) => THSWCState;
|
|
45
|
+
render: () => any;
|
|
46
|
+
};
|
|
47
|
+
export type THSWCUser = Pick<THSWC, "render"> & Record<string, any>;
|
|
48
|
+
export declare function clientComponent(id: string, wc: THSWCUser): (attrs?: Record<string, string>, state?: Record<string, any>) => HSTemplate;
|
|
49
|
+
|
|
50
|
+
export {};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
+
|
|
3
|
+
import Headers$1 from '@mjackson/headers';
|
|
4
|
+
|
|
5
|
+
declare class HSTemplate {
|
|
6
|
+
__hsTemplate: boolean;
|
|
7
|
+
content: any[];
|
|
8
|
+
constructor(content: any[]);
|
|
9
|
+
}
|
|
10
|
+
export type THTTPMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
|
+
declare class HSRequestContext {
|
|
12
|
+
req: Request;
|
|
13
|
+
locals: Record<string, any>;
|
|
14
|
+
headers: Headers$1;
|
|
15
|
+
route: {
|
|
16
|
+
params: Record<string, string>;
|
|
17
|
+
query: URLSearchParams;
|
|
18
|
+
};
|
|
19
|
+
constructor(req: Request, params?: Record<string, string>);
|
|
20
|
+
/**
|
|
21
|
+
* Response helper
|
|
22
|
+
* Merges a Response object while preserving all headers added in context/middleware
|
|
23
|
+
*/
|
|
24
|
+
resMerge(res: Response): Response;
|
|
25
|
+
/**
|
|
26
|
+
* HTML response helper
|
|
27
|
+
* Preserves all headers added in context/middleware
|
|
28
|
+
*/
|
|
29
|
+
html(content: string, options?: ResponseInit): Response;
|
|
30
|
+
/**
|
|
31
|
+
* JSON response helper
|
|
32
|
+
* Preserves all headers added in context/middleware
|
|
33
|
+
*/
|
|
34
|
+
json(content: any, options?: ResponseInit): Response;
|
|
35
|
+
notFound(msg?: string): Response;
|
|
36
|
+
}
|
|
37
|
+
export type THSRouteHandler = (context: HSRequestContext) => (Response | null | void) | Promise<Response | null | void>;
|
|
38
|
+
declare class HSApp {
|
|
39
|
+
private _router;
|
|
40
|
+
private _mw;
|
|
41
|
+
_defaultRoute: THSRouteHandler;
|
|
42
|
+
constructor();
|
|
43
|
+
get(path: string, handler: THSRouteHandler): this;
|
|
44
|
+
post(path: string, handler: THSRouteHandler): this;
|
|
45
|
+
put(path: string, handler: THSRouteHandler): this;
|
|
46
|
+
delete(path: string, handler: THSRouteHandler): this;
|
|
47
|
+
all(path: string, handler: THSRouteHandler): this;
|
|
48
|
+
addRoute(methods: THTTPMethod[], path: string, handler: THSRouteHandler): this;
|
|
49
|
+
defaultRoute(handler: THSRouteHandler): void;
|
|
50
|
+
private _route;
|
|
51
|
+
run(req: Request): Promise<Response>;
|
|
52
|
+
}
|
|
53
|
+
export declare const IS_PROD: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Run route from file
|
|
56
|
+
*/
|
|
57
|
+
export declare function runFileRoute(routeFile: string, context: HSRequestContext): Promise<any>;
|
|
58
|
+
export type THSServerConfig = {
|
|
59
|
+
appDir: string;
|
|
60
|
+
staticFileRoot: string;
|
|
61
|
+
beforeRoutesAdded?: (app: HSApp) => void;
|
|
62
|
+
afterRoutesAdded?: (app: HSApp) => void;
|
|
63
|
+
};
|
|
64
|
+
export type THSRouteMap = {
|
|
65
|
+
file: string;
|
|
66
|
+
route: string;
|
|
67
|
+
params: string[];
|
|
68
|
+
};
|
|
69
|
+
export declare function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Create and start Bun HTTP server
|
|
72
|
+
*/
|
|
73
|
+
export declare function createServer(config: THSServerConfig): Promise<HSApp>;
|
|
74
|
+
/**
|
|
75
|
+
* Build client JS for end users (minimal JS for Hyperspan to work)
|
|
76
|
+
*/
|
|
77
|
+
export declare let clientJSFile: string;
|
|
78
|
+
export declare function buildClientJS(): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Find client CSS file built for end users
|
|
81
|
+
* @TODO: Build this in code here vs. relying on tailwindcss CLI tool from package scripts
|
|
82
|
+
*/
|
|
83
|
+
export declare let clientCSSFile: string;
|
|
84
|
+
export declare function buildClientCSS(): Promise<string | undefined>;
|
|
85
|
+
/**
|
|
86
|
+
* Streaming HTML Response
|
|
87
|
+
*/
|
|
88
|
+
export declare class StreamResponse {
|
|
89
|
+
constructor(iterator: AsyncIterator<unknown>, options?: {});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Does what it says on the tin...
|
|
93
|
+
*/
|
|
94
|
+
export declare function createReadableStreamFromAsyncGenerator(output: AsyncGenerator): ReadableStream<any>;
|
|
95
|
+
/**
|
|
96
|
+
* Form route
|
|
97
|
+
* Automatically handles and parses form data
|
|
98
|
+
*
|
|
99
|
+
* 1. Renders component as initial form markup
|
|
100
|
+
* 2. Bind form onSubmit function to custom client JS handling
|
|
101
|
+
* 3. Submits form with JavaScript fetch()
|
|
102
|
+
* 4. Replaces form content with content from server
|
|
103
|
+
* 5. All validation and save logic is on the server
|
|
104
|
+
* 6. Handles any Exception thrown on server as error displayed in client
|
|
105
|
+
*/
|
|
106
|
+
export type TFormRouteFn = (context: HSRequestContext) => HSTemplate | Response;
|
|
107
|
+
export declare function formRoute(handlerFn: TFormRouteFn): (context: HSRequestContext) => void;
|
|
108
|
+
|
|
109
|
+
export {};
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @bun
|
|
2
1
|
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -412,8 +411,8 @@ var require_deepmerge = __commonJS((exports, module) => {
|
|
|
412
411
|
});
|
|
413
412
|
|
|
414
413
|
// src/server.ts
|
|
415
|
-
import {readdir} from "fs/promises";
|
|
416
|
-
import {basename, extname, join} from "path";
|
|
414
|
+
import {readdir} from "node:fs/promises";
|
|
415
|
+
import {basename, extname, join, resolve} from "node:path";
|
|
417
416
|
|
|
418
417
|
// src/html.ts
|
|
419
418
|
var import_escape_html = __toESM(require_escape_html(), 1);
|
|
@@ -1823,11 +1822,12 @@ async function createServer(config) {
|
|
|
1823
1822
|
});
|
|
1824
1823
|
config.beforeRoutesAdded && config.beforeRoutesAdded(app2);
|
|
1825
1824
|
const fileRoutes = await buildRoutes(config);
|
|
1825
|
+
const routeMap = [];
|
|
1826
1826
|
for (let i = 0;i < fileRoutes.length; i++) {
|
|
1827
1827
|
let route = fileRoutes[i];
|
|
1828
1828
|
const fullRouteFile = join("../../", config.appDir, "routes", route.file);
|
|
1829
1829
|
const routePattern = normalizePath(route.route);
|
|
1830
|
-
|
|
1830
|
+
routeMap.push({ route: routePattern, file: config.appDir + "/routes/" + route.file });
|
|
1831
1831
|
app2.all(routePattern, async (context) => {
|
|
1832
1832
|
const matchedRoute = await runFileRoute(fullRouteFile, context);
|
|
1833
1833
|
if (matchedRoute) {
|
|
@@ -1836,6 +1836,10 @@ async function createServer(config) {
|
|
|
1836
1836
|
return app2._defaultRoute(context);
|
|
1837
1837
|
});
|
|
1838
1838
|
}
|
|
1839
|
+
if (!IS_PROD) {
|
|
1840
|
+
console.log("[Hyperspan] File system routes (in app/routes):");
|
|
1841
|
+
console.table(routeMap);
|
|
1842
|
+
}
|
|
1839
1843
|
config.afterRoutesAdded && config.afterRoutesAdded(app2);
|
|
1840
1844
|
app2.all("*", (context) => {
|
|
1841
1845
|
const req = context.req;
|
|
@@ -1855,8 +1859,9 @@ async function createServer(config) {
|
|
|
1855
1859
|
return app2;
|
|
1856
1860
|
}
|
|
1857
1861
|
async function buildClientJS() {
|
|
1862
|
+
const sourceFile = resolve(CWD, "../", "./src/clientjs/hyperspan-client.ts");
|
|
1858
1863
|
const output = await Bun.build({
|
|
1859
|
-
entrypoints: [
|
|
1864
|
+
entrypoints: [sourceFile],
|
|
1860
1865
|
outdir: `./public/_hs/js`,
|
|
1861
1866
|
naming: IS_PROD ? "[dir]/[name]-[hash].[ext]" : undefined,
|
|
1862
1867
|
minify: IS_PROD
|
|
@@ -1900,6 +1905,7 @@ function formRoute(handlerFn) {
|
|
|
1900
1905
|
};
|
|
1901
1906
|
}
|
|
1902
1907
|
var IS_PROD = false;
|
|
1908
|
+
var CWD = import.meta.dir;
|
|
1903
1909
|
var STATIC_FILE_MATCHER = /[^/\\&\?]+\.([a-zA-Z]+)$/;
|
|
1904
1910
|
var _routeCache = {};
|
|
1905
1911
|
var ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
"access": "public"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "bun
|
|
13
|
-
"build:client": "bun build ./src/index.ts --outdir ./dist --target browser",
|
|
14
|
-
"build:server": "bun build ./src/server.ts --outdir ./dist --target bun",
|
|
12
|
+
"build": "bun ./build.ts",
|
|
15
13
|
"clean": "rm -rf dist",
|
|
16
14
|
"test": "bun test",
|
|
17
15
|
"prepack": "npm run clean && npm run build"
|
|
@@ -20,7 +18,15 @@
|
|
|
20
18
|
"type": "git",
|
|
21
19
|
"url": "https://github.com/vlucas/hyperspan-framework"
|
|
22
20
|
},
|
|
23
|
-
"keywords": [
|
|
21
|
+
"keywords": [
|
|
22
|
+
"framework",
|
|
23
|
+
"node",
|
|
24
|
+
"bun",
|
|
25
|
+
"web",
|
|
26
|
+
"framework",
|
|
27
|
+
"javascript",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
24
30
|
"author": "Vance Lucas <vance@vancelucas.com>",
|
|
25
31
|
"license": "BSD-3-Clause",
|
|
26
32
|
"bugs": {
|
|
@@ -34,5 +40,12 @@
|
|
|
34
40
|
"isbot": "^5.1.17",
|
|
35
41
|
"trek-middleware": "^1.2.0",
|
|
36
42
|
"trek-router": "^1.2.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/bun": "^1.1.9",
|
|
46
|
+
"@types/escape-html": "^1.0.4",
|
|
47
|
+
"@types/node": "^22.5.5",
|
|
48
|
+
"bun-plugin-dts": "^0.2.3",
|
|
49
|
+
"typescript": "^5.6.2"
|
|
37
50
|
}
|
|
38
51
|
}
|
package/src/server.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { readdir } from 'node:fs/promises';
|
|
2
|
-
import { basename, extname, join } from 'node:path';
|
|
2
|
+
import { basename, extname, join, resolve } from 'node:path';
|
|
3
3
|
import { html, renderToStream, renderToString } from './html';
|
|
4
4
|
import { isbot } from 'isbot';
|
|
5
5
|
import { HSTemplate } from './html';
|
|
6
6
|
import { HSApp, HSRequestContext, normalizePath } from './app';
|
|
7
7
|
|
|
8
8
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
9
|
+
const CWD = import.meta.dir;
|
|
9
10
|
const STATIC_FILE_MATCHER = /[^/\\&\?]+\.([a-zA-Z]+)$/;
|
|
10
11
|
|
|
11
12
|
// Cached route components
|
|
@@ -215,14 +216,14 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
|
|
|
215
216
|
|
|
216
217
|
// Scan routes folder and add all file routes to the router
|
|
217
218
|
const fileRoutes = await buildRoutes(config);
|
|
219
|
+
const routeMap = [];
|
|
218
220
|
|
|
219
221
|
for (let i = 0; i < fileRoutes.length; i++) {
|
|
220
222
|
let route = fileRoutes[i];
|
|
221
223
|
const fullRouteFile = join('../../', config.appDir, 'routes', route.file);
|
|
222
224
|
const routePattern = normalizePath(route.route);
|
|
223
225
|
|
|
224
|
-
|
|
225
|
-
console.log('[Hyperspan] Added route: ', routePattern);
|
|
226
|
+
routeMap.push({ route: routePattern, file: config.appDir + '/routes/' + route.file });
|
|
226
227
|
|
|
227
228
|
app.all(routePattern, async (context) => {
|
|
228
229
|
const matchedRoute = await runFileRoute(fullRouteFile, context);
|
|
@@ -234,6 +235,12 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
|
|
|
234
235
|
});
|
|
235
236
|
}
|
|
236
237
|
|
|
238
|
+
// Display routing table for dev env
|
|
239
|
+
if (!IS_PROD) {
|
|
240
|
+
console.log('[Hyperspan] File system routes (in app/routes):');
|
|
241
|
+
console.table(routeMap);
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
// [Customization] After routes added...
|
|
238
245
|
config.afterRoutesAdded && config.afterRoutesAdded(app);
|
|
239
246
|
|
|
@@ -267,8 +274,9 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
|
|
|
267
274
|
*/
|
|
268
275
|
export let clientJSFile: string;
|
|
269
276
|
export async function buildClientJS() {
|
|
277
|
+
const sourceFile = resolve(CWD, '../', './src/clientjs/hyperspan-client.ts');
|
|
270
278
|
const output = await Bun.build({
|
|
271
|
-
entrypoints: [
|
|
279
|
+
entrypoints: [sourceFile],
|
|
272
280
|
outdir: `./public/_hs/js`,
|
|
273
281
|
naming: IS_PROD ? '[dir]/[name]-[hash].[ext]' : undefined,
|
|
274
282
|
minify: IS_PROD,
|
package/tsconfig.json
CHANGED