@hyperspan/framework 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  > [!NOTE] > **Hyperspan is still in the early stages of development, your feedback is appreciated.**
4
4
 
5
- No JSX. No Virtual DOM. No hydration time. No nonsense. Just blazing fast HTML strings with reactive templates.
5
+ Hyperspan is a full-stack framework built with [Bun](https://bun.sh) that is focused on simplicity and ease of use.
6
6
 
7
- Hyperspan is a full-stack framework built with [Bun](https://bun.sh) that is focused on simplicity and perforamnce.
7
+ No JSX. No Virtual DOM. No hydration time. No nonsense. Just blazing fast HTML strings with reactive templates.
8
8
 
9
9
  ## Who Is Hyperspan For?
10
10
 
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
@@ -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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
2
  var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
6
  var __toESM = (mod, isNodeMode, target) => {
@@ -18,7 +18,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
18
18
 
19
19
  // node_modules/escape-html/index.js
20
20
  var require_escape_html = __commonJS((exports, module) => {
21
- var escapeHtml = function(string) {
21
+ function escapeHtml(string) {
22
22
  var str = "" + string;
23
23
  var match = matchHtmlRegExp.exec(str);
24
24
  if (!match) {
@@ -55,7 +55,7 @@ var require_escape_html = __commonJS((exports, module) => {
55
55
  html += escape;
56
56
  }
57
57
  return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
58
- };
58
+ }
59
59
  /*!
60
60
  * escape-html
61
61
  * Copyright(c) 2012-2013 TJ Holowaychuk
@@ -71,7 +71,7 @@ var require_escape_html = __commonJS((exports, module) => {
71
71
  var import_escape_html = __toESM(require_escape_html(), 1);
72
72
 
73
73
  // src/clientjs/md5.js
74
- var md5cycle = function(x, k) {
74
+ function md5cycle(x, k) {
75
75
  var a = x[0], b = x[1], c = x[2], d = x[3];
76
76
  a = ff(a, b, c, d, k[0], 7, -680876936);
77
77
  d = ff(d, a, b, c, k[1], 12, -389564586);
@@ -141,24 +141,24 @@ var md5cycle = function(x, k) {
141
141
  x[1] = add32(b, x[1]);
142
142
  x[2] = add32(c, x[2]);
143
143
  x[3] = add32(d, x[3]);
144
- };
145
- var cmn = function(q, a, b, x, s, t) {
144
+ }
145
+ function cmn(q, a, b, x, s, t) {
146
146
  a = add32(add32(a, q), add32(x, t));
147
147
  return add32(a << s | a >>> 32 - s, b);
148
- };
149
- var ff = function(a, b, c, d, x, s, t) {
148
+ }
149
+ function ff(a, b, c, d, x, s, t) {
150
150
  return cmn(b & c | ~b & d, a, b, x, s, t);
151
- };
152
- var gg = function(a, b, c, d, x, s, t) {
151
+ }
152
+ function gg(a, b, c, d, x, s, t) {
153
153
  return cmn(b & d | c & ~d, a, b, x, s, t);
154
- };
155
- var hh = function(a, b, c, d, x, s, t) {
154
+ }
155
+ function hh(a, b, c, d, x, s, t) {
156
156
  return cmn(b ^ c ^ d, a, b, x, s, t);
157
- };
158
- var ii = function(a, b, c, d, x, s, t) {
157
+ }
158
+ function ii(a, b, c, d, x, s, t) {
159
159
  return cmn(c ^ (b | ~d), a, b, x, s, t);
160
- };
161
- var md51 = function(s) {
160
+ }
161
+ function md51(s) {
162
162
  var txt = "";
163
163
  var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;
164
164
  for (i = 64;i <= s.length; i += 64) {
@@ -177,28 +177,28 @@ var md51 = function(s) {
177
177
  tail[14] = n * 8;
178
178
  md5cycle(state, tail);
179
179
  return state;
180
- };
181
- var md5blk = function(s) {
180
+ }
181
+ function md5blk(s) {
182
182
  var md5blks = [], i;
183
183
  for (i = 0;i < 64; i += 4) {
184
184
  md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
185
185
  }
186
186
  return md5blks;
187
- };
188
- var rhex = function(n) {
187
+ }
188
+ function rhex(n) {
189
189
  var s = "", j = 0;
190
190
  for (;j < 4; j++)
191
191
  s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
192
192
  return s;
193
- };
194
- var hex = function(x) {
193
+ }
194
+ function hex(x) {
195
195
  for (var i = 0;i < x.length; i++)
196
196
  x[i] = rhex(x[i]);
197
197
  return x.join("");
198
- };
199
- var add32 = function(a, b) {
198
+ }
199
+ function add32(a, b) {
200
200
  return a + b & 4294967295;
201
- };
201
+ }
202
202
  function md5(s) {
203
203
  return hex(md51(s));
204
204
  }
@@ -232,7 +232,7 @@ async function* _render(obj, promises = [], { js }) {
232
232
  kind = _typeOf(obj);
233
233
  value = obj;
234
234
  }
235
- if (value instanceof HSTemplate) {
235
+ if (value instanceof HSTemplate || value.__hsTemplate) {
236
236
  yield* renderToStream(value);
237
237
  } else if (typeof value.render !== "undefined") {
238
238
  value.id = id;
@@ -269,6 +269,9 @@ async function* _render(obj, promises = [], { js }) {
269
269
  case "json":
270
270
  yield "";
271
271
  break;
272
+ case "number":
273
+ yield String(value);
274
+ break;
272
275
  case "object":
273
276
  if (typeof value.render === "function") {
274
277
  yield value.render();
@@ -322,9 +325,9 @@ async function renderToString(template) {
322
325
  function compressHTMLString(str) {
323
326
  return str.replace(/(<(pre|script|style|textarea)[^]+?<\/\2)|(^|>)\s+|\s+(?=<|$)/g, "$1$3");
324
327
  }
325
- var randomId = function() {
328
+ function randomId() {
326
329
  return Math.random().toString(36).substring(2, 9);
327
- };
330
+ }
328
331
  function _typeOf(obj) {
329
332
  if (obj instanceof Promise)
330
333
  return "promise";
@@ -352,12 +355,12 @@ function _typeOf(obj) {
352
355
  return "json";
353
356
  return typeof obj;
354
357
  }
355
- var isGenerator = function(obj) {
358
+ function isGenerator(obj) {
356
359
  return obj && typeof obj.next == "function" && typeof obj.throw == "function";
357
- };
358
- var isPlainObject = function(val) {
360
+ }
361
+ function isPlainObject(val) {
359
362
  return Object == val.constructor;
360
- };
363
+ }
361
364
  function clientComponent(id, wc) {
362
365
  const comp = {
363
366
  ...wc,
@@ -414,7 +417,7 @@ function renderFunctionToString(fn) {
414
417
  }
415
418
  return fns;
416
419
  }
417
- var renderObjectToLiteralString = function(obj) {
420
+ function renderObjectToLiteralString(obj) {
418
421
  const lines = [];
419
422
  let str = 'hyperspan.wc.set("' + obj.id + '", {\n';
420
423
  for (const prop in obj) {
@@ -444,7 +447,7 @@ var renderObjectToLiteralString = function(obj) {
444
447
  str += lines.map((line) => line.join("") + ",").join("\n");
445
448
  str += "\n})";
446
449
  return str;
447
- };
450
+ }
448
451
  var IS_CLIENT = typeof window !== "undefined";
449
452
 
450
453
  class HSTemplate {
@@ -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,7 +1,6 @@
1
- // @bun
2
1
  var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
2
  var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
6
  var __toESM = (mod, isNodeMode, target) => {
@@ -19,7 +18,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
19
18
 
20
19
  // node_modules/escape-html/index.js
21
20
  var require_escape_html = __commonJS((exports, module) => {
22
- var escapeHtml = function(string) {
21
+ function escapeHtml(string) {
23
22
  var str = "" + string;
24
23
  var match = matchHtmlRegExp.exec(str);
25
24
  if (!match) {
@@ -56,7 +55,7 @@ var require_escape_html = __commonJS((exports, module) => {
56
55
  html += escape;
57
56
  }
58
57
  return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
59
- };
58
+ }
60
59
  /*!
61
60
  * escape-html
62
61
  * Copyright(c) 2012-2013 TJ Holowaychuk
@@ -300,7 +299,7 @@ var require_trek_middleware = __commonJS((exports, module) => {
300
299
 
301
300
  // node_modules/@fastify/deepmerge/index.js
302
301
  var require_deepmerge = __commonJS((exports, module) => {
303
- var deepmergeConstructor = function(options) {
302
+ function deepmergeConstructor(options) {
304
303
  function isNotPrototypeKey(value) {
305
304
  return value !== "constructor" && value !== "prototype" && value !== "__proto__";
306
305
  }
@@ -404,7 +403,7 @@ var require_deepmerge = __commonJS((exports, module) => {
404
403
  return result;
405
404
  }
406
405
  return options && options.all ? _deepmergeAll : _deepmerge;
407
- };
406
+ }
408
407
  var JSON_PROTO = Object.getPrototypeOf({});
409
408
  module.exports = deepmergeConstructor;
410
409
  module.exports.default = deepmergeConstructor;
@@ -412,14 +411,14 @@ 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);
420
419
 
421
420
  // src/clientjs/md5.js
422
- var md5cycle = function(x, k) {
421
+ function md5cycle(x, k) {
423
422
  var a = x[0], b = x[1], c = x[2], d = x[3];
424
423
  a = ff(a, b, c, d, k[0], 7, -680876936);
425
424
  d = ff(d, a, b, c, k[1], 12, -389564586);
@@ -489,24 +488,24 @@ var md5cycle = function(x, k) {
489
488
  x[1] = add32(b, x[1]);
490
489
  x[2] = add32(c, x[2]);
491
490
  x[3] = add32(d, x[3]);
492
- };
493
- var cmn = function(q, a, b, x, s, t) {
491
+ }
492
+ function cmn(q, a, b, x, s, t) {
494
493
  a = add32(add32(a, q), add32(x, t));
495
494
  return add32(a << s | a >>> 32 - s, b);
496
- };
497
- var ff = function(a, b, c, d, x, s, t) {
495
+ }
496
+ function ff(a, b, c, d, x, s, t) {
498
497
  return cmn(b & c | ~b & d, a, b, x, s, t);
499
- };
500
- var gg = function(a, b, c, d, x, s, t) {
498
+ }
499
+ function gg(a, b, c, d, x, s, t) {
501
500
  return cmn(b & d | c & ~d, a, b, x, s, t);
502
- };
503
- var hh = function(a, b, c, d, x, s, t) {
501
+ }
502
+ function hh(a, b, c, d, x, s, t) {
504
503
  return cmn(b ^ c ^ d, a, b, x, s, t);
505
- };
506
- var ii = function(a, b, c, d, x, s, t) {
504
+ }
505
+ function ii(a, b, c, d, x, s, t) {
507
506
  return cmn(c ^ (b | ~d), a, b, x, s, t);
508
- };
509
- var md51 = function(s) {
507
+ }
508
+ function md51(s) {
510
509
  var txt = "";
511
510
  var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;
512
511
  for (i = 64;i <= s.length; i += 64) {
@@ -525,28 +524,28 @@ var md51 = function(s) {
525
524
  tail[14] = n * 8;
526
525
  md5cycle(state, tail);
527
526
  return state;
528
- };
529
- var md5blk = function(s) {
527
+ }
528
+ function md5blk(s) {
530
529
  var md5blks = [], i;
531
530
  for (i = 0;i < 64; i += 4) {
532
531
  md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
533
532
  }
534
533
  return md5blks;
535
- };
536
- var rhex = function(n) {
534
+ }
535
+ function rhex(n) {
537
536
  var s = "", j = 0;
538
537
  for (;j < 4; j++)
539
538
  s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
540
539
  return s;
541
- };
542
- var hex = function(x) {
540
+ }
541
+ function hex(x) {
543
542
  for (var i = 0;i < x.length; i++)
544
543
  x[i] = rhex(x[i]);
545
544
  return x.join("");
546
- };
547
- var add32 = function(a, b) {
545
+ }
546
+ function add32(a, b) {
548
547
  return a + b & 4294967295;
549
- };
548
+ }
550
549
  function md5(s) {
551
550
  return hex(md51(s));
552
551
  }
@@ -580,7 +579,7 @@ async function* _render(obj, promises = [], { js }) {
580
579
  kind = _typeOf(obj);
581
580
  value = obj;
582
581
  }
583
- if (value instanceof HSTemplate) {
582
+ if (value instanceof HSTemplate || value.__hsTemplate) {
584
583
  yield* renderToStream(value);
585
584
  } else if (typeof value.render !== "undefined") {
586
585
  value.id = id;
@@ -617,6 +616,9 @@ async function* _render(obj, promises = [], { js }) {
617
616
  case "json":
618
617
  yield "";
619
618
  break;
619
+ case "number":
620
+ yield String(value);
621
+ break;
620
622
  case "object":
621
623
  if (typeof value.render === "function") {
622
624
  yield value.render();
@@ -667,9 +669,9 @@ async function renderToString(template) {
667
669
  }
668
670
  return result;
669
671
  }
670
- var randomId = function() {
672
+ function randomId() {
671
673
  return Math.random().toString(36).substring(2, 9);
672
- };
674
+ }
673
675
  function _typeOf(obj) {
674
676
  if (obj instanceof Promise)
675
677
  return "promise";
@@ -697,12 +699,12 @@ function _typeOf(obj) {
697
699
  return "json";
698
700
  return typeof obj;
699
701
  }
700
- var isGenerator = function(obj) {
702
+ function isGenerator(obj) {
701
703
  return obj && typeof obj.next == "function" && typeof obj.throw == "function";
702
- };
703
- var isPlainObject = function(val) {
704
+ }
705
+ function isPlainObject(val) {
704
706
  return Object == val.constructor;
705
- };
707
+ }
706
708
  function renderFunctionToString(fn) {
707
709
  let fns = fn.toString();
708
710
  const firstLine = fns.split("\n")[0];
@@ -735,7 +737,7 @@ html.raw = (value) => {
735
737
  };
736
738
 
737
739
  // node_modules/isbot/index.mjs
738
- var getPattern = function() {
740
+ function getPattern() {
739
741
  if (pattern instanceof RegExp) {
740
742
  return pattern;
741
743
  }
@@ -745,10 +747,10 @@ var getPattern = function() {
745
747
  pattern = naivePattern;
746
748
  }
747
749
  return pattern;
748
- };
749
- var isbot = function(userAgent) {
750
+ }
751
+ function isbot(userAgent) {
750
752
  return Boolean(userAgent) && getPattern().test(userAgent);
751
- };
753
+ }
752
754
  var fullPattern = " daum[ /]| deusu/| yadirectfetcher|(?:^|[^g])news(?!sapphire)|(?<! (?:channel/|google/))google(?!(app|/google| pixel))|(?<! cu)bots?(?:\\b|_)|(?<!(?:lib))http|(?<![hg]m)score|@[a-z][\\w-]+\\.|\\(\\)|\\.com\\b|\\btime/|^<|^[\\w \\.\\-\\(?:\\):]+(?:/v?\\d+(?:\\.\\d+)?(?:\\.\\d{1,10})*?)?(?:,|$)|^[^ ]{50,}$|^\\d+\\b|^\\w*search\\b|^\\w+/[\\w\\(\\)]*$|^active|^ad muncher|^amaya|^avsdevicesdk/|^biglotron|^bot|^bw/|^clamav[ /]|^client/|^cobweb/|^custom|^ddg[_-]android|^discourse|^dispatch/\\d|^downcast/|^duckduckgo|^facebook|^getright/|^gozilla/|^hobbit|^hotzonu|^hwcdn/|^jeode/|^jetty/|^jigsaw|^microsoft bits|^movabletype|^mozilla/5\\.0\\s[a-z\\.-]+$|^mozilla/\\d\\.\\d \\(compatible;?\\)$|^mozilla/\\d\\.\\d \\w*$|^navermailapp|^netsurf|^offline|^owler|^php|^postman|^python|^rank|^read|^reed|^rest|^rss|^snapchat|^space bison|^svn|^swcd |^taringa|^thumbor/|^track|^valid|^w3c|^webbandit/|^webcopier|^wget|^whatsapp|^wordpress|^xenu link sleuth|^yahoo|^yandex|^zdm/\\d|^zoom marketplace/|^{{.*}}$|adscanner/|analyzer|archive|ask jeeves/teoma|bit\\.ly/|bluecoat drtr|browsex|burpcollaborator|capture|catch|check\\b|checker|chrome-lighthouse|chromeframe|classifier|cloudflare|convertify|crawl|cypress/|dareboost|datanyze|dejaclick|detect|dmbrowser|download|evc-batch/|exaleadcloudview|feed|firephp|functionize|gomezagent|headless|httrack|hubspot marketing grader|hydra|ibisbrowser|images|infrawatch|insight|inspect|iplabel|ips-agent|java(?!;)|jsjcw_scanner|library|linkcheck|mail\\.ru/|manager|measure|neustar wpm|node|nutch|offbyone|optimize|pageburst|pagespeed|parser|perl|phantomjs|pingdom|powermarks|preview|proxy|ptst[ /]\\d|reputation|resolver|retriever|rexx;|rigor|rss\\b|scanner\\.|scrape|server|sogou|sparkler/|speedcurve|spider|splash|statuscake|supercleaner|synapse|synthetic|tools|torrent|trace|transcoder|url|virtuoso|wappalyzer|webglance|webkit2png|whatcms/|zgrab";
753
755
  var naivePattern = /bot|crawl|http|lighthouse|scan|search|spider/i;
754
756
  var pattern;
@@ -756,7 +758,7 @@ var pattern;
756
758
  // src/app.ts
757
759
  var import_trek_router = __toESM(require_trek_router(), 1);
758
760
  var import_trek_middleware = __toESM(require_trek_middleware(), 1);
759
- var deepmerge = __toESM(require_deepmerge(), 1);
761
+ var import_deepmerge = __toESM(require_deepmerge(), 1);
760
762
 
761
763
  // node_modules/@mjackson/headers/dist/lib/param-values.js
762
764
  function parseParams(input, delimiter = ";") {
@@ -1023,7 +1025,7 @@ class CacheControl {
1023
1025
  }
1024
1026
  }
1025
1027
  // node_modules/@mjackson/headers/dist/lib/content-disposition.js
1026
- var decodeFilenameSplat = function(value) {
1028
+ function decodeFilenameSplat(value) {
1027
1029
  let match = value.match(/^([\w-]+)'([^']*)'(.+)$/);
1028
1030
  if (!match)
1029
1031
  return null;
@@ -1037,12 +1039,12 @@ var decodeFilenameSplat = function(value) {
1037
1039
  console.warn(`Failed to decode filename from charset ${charset}:`, error);
1038
1040
  return decodedFilename;
1039
1041
  }
1040
- };
1041
- var percentDecode = function(value) {
1042
+ }
1043
+ function percentDecode(value) {
1042
1044
  return value.replace(/\+/g, " ").replace(/%([0-9A-Fa-f]{2})/g, (_, hex2) => {
1043
1045
  return String.fromCharCode(parseInt(hex2, 16));
1044
1046
  });
1045
- };
1047
+ }
1046
1048
 
1047
1049
  class ContentDisposition {
1048
1050
  filename;
@@ -1602,7 +1604,7 @@ class SuperHeaders extends Headers {
1602
1604
  function normalizePath(urlPath) {
1603
1605
  return (urlPath.endsWith("/") ? urlPath.substring(0, urlPath.length - 1) : urlPath).toLowerCase() || "/";
1604
1606
  }
1605
- var mergeAll = deepmerge.default({ all: true });
1607
+ var mergeAll = import_deepmerge.default({ all: true });
1606
1608
 
1607
1609
  class HSRequestContext {
1608
1610
  req;
@@ -1707,10 +1709,10 @@ class HSApp {
1707
1709
  }
1708
1710
 
1709
1711
  // src/server.ts
1710
- var requestIsBot = function(req) {
1712
+ function requestIsBot(req) {
1711
1713
  const ua = req.headers.get("User-Agent");
1712
1714
  return ua ? isbot(ua) : false;
1713
- };
1715
+ }
1714
1716
  async function runFileRoute(routeFile, context) {
1715
1717
  const req = context.req;
1716
1718
  const url = new URL(req.url);
@@ -1808,7 +1810,7 @@ async function buildRoutes(config) {
1808
1810
  });
1809
1811
  }
1810
1812
  routes.push({
1811
- file,
1813
+ file: join("./", routesDir, file),
1812
1814
  route: route || "/",
1813
1815
  params
1814
1816
  });
@@ -1817,46 +1819,52 @@ async function buildRoutes(config) {
1817
1819
  }
1818
1820
  async function createServer(config) {
1819
1821
  await Promise.all([buildClientJS(), buildClientCSS()]);
1820
- const app2 = new HSApp;
1821
- app2.defaultRoute(() => {
1822
+ const app = new HSApp;
1823
+ app.defaultRoute(() => {
1822
1824
  return new Response("Not... found?", { status: 404 });
1823
1825
  });
1824
- config.beforeRoutesAdded && config.beforeRoutesAdded(app2);
1826
+ config.beforeRoutesAdded && config.beforeRoutesAdded(app);
1825
1827
  const fileRoutes = await buildRoutes(config);
1828
+ const routeMap = [];
1826
1829
  for (let i = 0;i < fileRoutes.length; i++) {
1827
1830
  let route = fileRoutes[i];
1828
- const fullRouteFile = join("../../", config.appDir, "routes", route.file);
1831
+ const fullRouteFile = join(CWD, route.file);
1829
1832
  const routePattern = normalizePath(route.route);
1830
- console.log("[Hyperspan] Added route: ", routePattern);
1831
- app2.all(routePattern, async (context) => {
1833
+ routeMap.push({ route: routePattern, file: route.file });
1834
+ app.all(routePattern, async (context) => {
1832
1835
  const matchedRoute = await runFileRoute(fullRouteFile, context);
1833
1836
  if (matchedRoute) {
1834
1837
  return matchedRoute;
1835
1838
  }
1836
- return app2._defaultRoute(context);
1839
+ return app._defaultRoute(context);
1837
1840
  });
1838
1841
  }
1839
- config.afterRoutesAdded && config.afterRoutesAdded(app2);
1840
- app2.all("*", (context) => {
1842
+ if (!IS_PROD) {
1843
+ console.log("[Hyperspan] File system routes (in app/routes):");
1844
+ console.table(routeMap);
1845
+ }
1846
+ config.afterRoutesAdded && config.afterRoutesAdded(app);
1847
+ app.all("*", (context) => {
1841
1848
  const req = context.req;
1842
1849
  if (STATIC_FILE_MATCHER.test(req.url)) {
1843
1850
  const filePath = config.staticFileRoot + new URL(req.url).pathname;
1844
1851
  const file = Bun.file(filePath);
1845
- let headers2 = {};
1852
+ let headers = {};
1846
1853
  if (IS_PROD) {
1847
- headers2 = {
1854
+ headers = {
1848
1855
  "cache-control": "public, max-age=31557600"
1849
1856
  };
1850
1857
  }
1851
- return new Response(file, { headers: headers2 });
1858
+ return new Response(file, { headers });
1852
1859
  }
1853
- return app2._defaultRoute(context);
1860
+ return app._defaultRoute(context);
1854
1861
  });
1855
- return app2;
1862
+ return app;
1856
1863
  }
1857
1864
  async function buildClientJS() {
1865
+ const sourceFile = resolve(PWD, "../", "./src/clientjs/hyperspan-client.ts");
1858
1866
  const output = await Bun.build({
1859
- entrypoints: ["./src/hyperspan/clientjs/hyperspan-client.ts"],
1867
+ entrypoints: [sourceFile],
1860
1868
  outdir: `./public/_hs/js`,
1861
1869
  naming: IS_PROD ? "[dir]/[name]-[hash].[ext]" : undefined,
1862
1870
  minify: IS_PROD
@@ -1900,6 +1908,8 @@ function formRoute(handlerFn) {
1900
1908
  };
1901
1909
  }
1902
1910
  var IS_PROD = false;
1911
+ var PWD = import.meta.dir;
1912
+ var CWD = process.cwd();
1903
1913
  var STATIC_FILE_MATCHER = /[^/\\&\?]+\.([a-zA-Z]+)$/;
1904
1914
  var _routeCache = {};
1905
1915
  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.1",
3
+ "version": "0.0.3",
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 run clean && bun run build:client && bun run build:server",
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": ["framework", "node", "bun", "web", "framework", "javascript", "typescript"],
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.11",
46
+ "@types/escape-html": "^1.0.4",
47
+ "@types/node": "^22.7.5",
48
+ "bun-plugin-dts": "^0.3.0",
49
+ "typescript": "^5.6.3"
37
50
  }
38
51
  }
package/src/html.ts CHANGED
@@ -68,7 +68,7 @@ async function* _render(
68
68
  value = obj;
69
69
  }
70
70
 
71
- if (value instanceof HSTemplate) {
71
+ if (value instanceof HSTemplate || value.__hsTemplate) {
72
72
  yield* renderToStream(value);
73
73
  } else if (typeof value.render !== 'undefined') {
74
74
  value.id = id;
@@ -108,6 +108,9 @@ async function* _render(
108
108
  case 'json':
109
109
  yield ''; //JSON.stringify(value);
110
110
  break;
111
+ case 'number':
112
+ yield String(value);
113
+ break;
111
114
  case 'object':
112
115
  if (typeof value.render === 'function') {
113
116
  yield value.render();
package/src/server.ts CHANGED
@@ -1,11 +1,13 @@
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 PWD = import.meta.dir;
10
+ const CWD = process.cwd();
9
11
  const STATIC_FILE_MATCHER = /[^/\\&\?]+\.([a-zA-Z]+)$/;
10
12
 
11
13
  // Cached route components
@@ -188,7 +190,7 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
188
190
  }
189
191
 
190
192
  routes.push({
191
- file,
193
+ file: join('./', routesDir, file),
192
194
  route: route || '/',
193
195
  params,
194
196
  });
@@ -215,14 +217,14 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
215
217
 
216
218
  // Scan routes folder and add all file routes to the router
217
219
  const fileRoutes = await buildRoutes(config);
220
+ const routeMap = [];
218
221
 
219
222
  for (let i = 0; i < fileRoutes.length; i++) {
220
223
  let route = fileRoutes[i];
221
- const fullRouteFile = join('../../', config.appDir, 'routes', route.file);
224
+ const fullRouteFile = join(CWD, route.file);
222
225
  const routePattern = normalizePath(route.route);
223
226
 
224
- // @ts-ignore
225
- console.log('[Hyperspan] Added route: ', routePattern);
227
+ routeMap.push({ route: routePattern, file: route.file });
226
228
 
227
229
  app.all(routePattern, async (context) => {
228
230
  const matchedRoute = await runFileRoute(fullRouteFile, context);
@@ -234,6 +236,12 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
234
236
  });
235
237
  }
236
238
 
239
+ // Display routing table for dev env
240
+ if (!IS_PROD) {
241
+ console.log('[Hyperspan] File system routes (in app/routes):');
242
+ console.table(routeMap);
243
+ }
244
+
237
245
  // [Customization] After routes added...
238
246
  config.afterRoutesAdded && config.afterRoutesAdded(app);
239
247
 
@@ -267,8 +275,9 @@ export async function createServer(config: THSServerConfig): Promise<HSApp> {
267
275
  */
268
276
  export let clientJSFile: string;
269
277
  export async function buildClientJS() {
278
+ const sourceFile = resolve(PWD, '../', './src/clientjs/hyperspan-client.ts');
270
279
  const output = await Bun.build({
271
- entrypoints: ['./src/hyperspan/clientjs/hyperspan-client.ts'],
280
+ entrypoints: [sourceFile],
272
281
  outdir: `./public/_hs/js`,
273
282
  naming: IS_PROD ? '[dir]/[name]-[hash].[ext]' : undefined,
274
283
  minify: IS_PROD,
package/tsconfig.json CHANGED
@@ -19,8 +19,7 @@
19
19
  "skipLibCheck": true,
20
20
  "allowSyntheticDefaultImports": true,
21
21
  "forceConsistentCasingInFileNames": true,
22
- "allowJs": true,
23
- "types": ["bun-types"]
22
+ "allowJs": true
24
23
  },
25
24
  "exclude": ["node_modules", "__tests__", "*.test.ts"]
26
25
  }