@hyperspan/framework 0.4.5 → 0.5.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/dist/assets.js CHANGED
@@ -22,12 +22,12 @@ async function buildClientJS() {
22
22
  const jsFile = output.outputs[0].path.split("/").reverse()[0];
23
23
  clientJSFiles.set("_hs", { src: `${CLIENTJS_PUBLIC_PATH}/${jsFile}` });
24
24
  }
25
- function renderClientJS(module, onLoad) {
25
+ function renderClientJS(module, loadScript) {
26
26
  if (!module.__CLIENT_JS) {
27
27
  throw new Error(`[Hyperspan] Client JS was not loaded by Hyperspan! Ensure the filename ends with .client.ts to use this render method.`);
28
28
  }
29
29
  return html.raw(module.__CLIENT_JS.renderScriptTag({
30
- onLoad: onLoad ? typeof onLoad === "string" ? onLoad : functionToString(onLoad) : undefined
30
+ loadScript: loadScript ? typeof loadScript === "string" ? loadScript : functionToString(loadScript) : undefined
31
31
  }));
32
32
  }
33
33
  function functionToString(fn) {
package/dist/server.js CHANGED
@@ -54,8 +54,8 @@ export const __CLIENT_JS = {
54
54
  esmName: "${esmName}",
55
55
  sourceFile: "${args.path}",
56
56
  outputFile: "${result.outputs[0].path}",
57
- renderScriptTag: ({ onLoad }) => {
58
- const fn = onLoad ? (typeof onLoad === 'string' ? onLoad : \`const fn = \${functionToString(onLoad)}; fn(${fnArgs});\`) : '';
57
+ renderScriptTag: ({ loadScript }) => {
58
+ const fn = loadScript ? (typeof loadScript === 'string' ? loadScript : \`const fn = \${functionToString(loadScript)}; fn(${fnArgs});\`) : '';
59
59
  return \`<script type="module" data-source-id="${jsId}">import ${exports} from "${esmName}";
60
60
  \${fn}</script>\`;
61
61
  },
@@ -2117,7 +2117,7 @@ async function buildRoutes(config) {
2117
2117
  const files = await readdir(routesDir, { recursive: true });
2118
2118
  const routes = [];
2119
2119
  for (const file of files) {
2120
- if (!file.includes(".") || basename(file).startsWith(".")) {
2120
+ if (!file.includes(".") || basename(file).startsWith(".") || file.includes(".test.") || file.includes(".spec.")) {
2121
2121
  continue;
2122
2122
  }
2123
2123
  let route = "/" + file.replace(extname(file), "");
@@ -2155,7 +2155,7 @@ async function buildActions(config) {
2155
2155
  const files = await readdir(routesDir, { recursive: true });
2156
2156
  const routes = [];
2157
2157
  for (const file of files) {
2158
- if (!file.includes(".") || basename(file).startsWith(".")) {
2158
+ if (!file.includes(".") || basename(file).startsWith(".") || file.includes(".test.") || file.includes(".spec.")) {
2159
2159
  continue;
2160
2160
  }
2161
2161
  let route = assetHash("/" + file.replace(extname(file), ""));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.4.5",
3
+ "version": "0.5.1",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.ts",
6
6
  "types": "src/server.ts",
@@ -1,11 +1,11 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
  import { unstable__createAction } from './actions';
3
3
  import { describe, it, expect } from 'bun:test';
4
4
  import { html, render, type HSHtml } from '@hyperspan/html';
5
- import type { Context } from 'hono';
5
+ import type { THSContext } from './server';
6
6
 
7
7
  describe('createAction', () => {
8
- const formWithNameOnly = ({ data }: { data?: { name: string } }) => {
8
+ const formWithNameOnly = (c: THSContext, { data }: { data?: { name: string } }) => {
9
9
  return html`
10
10
  <form>
11
11
  <p>
@@ -23,8 +23,18 @@ describe('createAction', () => {
23
23
  name: z.string(),
24
24
  });
25
25
  const action = unstable__createAction(schema, formWithNameOnly);
26
+ const mockContext = {
27
+ req: {
28
+ method: 'POST',
29
+ formData: async () => {
30
+ const formData = new FormData();
31
+ formData.append('name', 'John');
32
+ return formData;
33
+ },
34
+ },
35
+ } as THSContext;
26
36
 
27
- const formResponse = render(action.render({ data: { name: 'John' } }) as HSHtml);
37
+ const formResponse = render(action.render(mockContext, { data: { name: 'John' } }) as HSHtml);
28
38
  expect(formResponse).toContain('value="John"');
29
39
  });
30
40
  });
@@ -52,7 +62,7 @@ describe('createAction', () => {
52
62
  return formData;
53
63
  },
54
64
  },
55
- } as Context;
65
+ } as THSContext;
56
66
 
57
67
  const response = await action.run(mockContext);
58
68
 
@@ -85,7 +95,7 @@ describe('createAction', () => {
85
95
  return formData;
86
96
  },
87
97
  },
88
- } as Context;
98
+ } as THSContext;
89
99
 
90
100
  const response = await action.run(mockContext);
91
101
 
package/src/actions.ts CHANGED
@@ -2,8 +2,8 @@ import { html, HSHtml } from '@hyperspan/html';
2
2
  import * as z from 'zod/v4';
3
3
  import { HTTPException } from 'hono/http-exception';
4
4
  import { assetHash } from './assets';
5
- import { IS_PROD, returnHTMLResponse, type THSResponseTypes } from './server';
6
- import type { Context, MiddlewareHandler } from 'hono';
5
+ import { IS_PROD, returnHTMLResponse, type THSContext, type THSResponseTypes } from './server';
6
+ import type { MiddlewareHandler } from 'hono';
7
7
  import type { HandlerResponse, Next, TypedResponse } from 'hono/types';
8
8
 
9
9
  /**
@@ -29,39 +29,34 @@ export interface HSAction<T extends z.ZodTypeAny> {
29
29
  _form: Parameters<HSAction<T>['form']>[0];
30
30
  form(
31
31
  renderForm: (
32
- c: Context<any, any, {}>,
32
+ c: THSContext,
33
33
  { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
34
34
  ) => HSHtml | void | null | Promise<HSHtml | void | null>
35
35
  ): HSAction<T>;
36
36
  post(
37
37
  handler: (
38
- c: Context<any, any, {}>,
38
+ c: THSContext,
39
39
  { data }: { data?: z.infer<T> }
40
40
  ) => TActionResponse | Promise<TActionResponse>
41
41
  ): HSAction<T>;
42
42
  error(
43
43
  handler: (
44
- c: Context<any, any, {}>,
44
+ c: THSContext,
45
45
  { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
46
46
  ) => TActionResponse
47
47
  ): HSAction<T>;
48
- render(
49
- c: Context<any, any, {}>,
50
- props?: { data?: z.infer<T>; error?: z.ZodError | Error }
51
- ): TActionResponse;
52
- run(c: Context<any, any, {}>): TActionResponse | Promise<TActionResponse>;
48
+ render(c: THSContext, props?: { data?: z.infer<T>; error?: z.ZodError | Error }): TActionResponse;
49
+ run(c: THSContext): TActionResponse | Promise<TActionResponse>;
53
50
  middleware: (
54
51
  middleware: Array<
55
52
  | MiddlewareHandler
56
- | ((
57
- context: Context<any, string, {}>
58
- ) => TActionResponse | Promise<TActionResponse> | void | Promise<void>)
53
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse> | void | Promise<void>)
59
54
  >
60
55
  ) => HSAction<T>;
61
56
  _getRouteHandlers: () => Array<
62
57
  | MiddlewareHandler
63
- | ((context: Context, next: Next) => TActionResponse | Promise<TActionResponse>)
64
- | ((context: Context) => TActionResponse | Promise<TActionResponse>)
58
+ | ((context: THSContext, next: Next) => TActionResponse | Promise<TActionResponse>)
59
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse>)
65
60
  >;
66
61
  }
67
62
 
@@ -74,8 +69,8 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
74
69
  _errorHandler: Parameters<HSAction<T>['error']>[0] | null = null,
75
70
  _middleware: Array<
76
71
  | MiddlewareHandler
77
- | ((context: Context, next: Next) => TActionResponse | Promise<TActionResponse>)
78
- | ((context: Context) => TActionResponse | Promise<TActionResponse>)
72
+ | ((context: THSContext, next: Next) => TActionResponse | Promise<TActionResponse>)
73
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse>)
79
74
  > = [];
80
75
 
81
76
  const api: HSAction<T> = {
@@ -113,10 +108,7 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
113
108
  /**
114
109
  * Get form renderer method
115
110
  */
116
- render(
117
- c: Context<any, any, {}>,
118
- formState?: { data?: z.infer<T>; error?: z.ZodError | Error }
119
- ) {
111
+ render(c: THSContext, formState?: { data?: z.infer<T>; error?: z.ZodError | Error }) {
120
112
  const form = _form ? _form(c, formState || {}) : null;
121
113
  return form ? html`<hs-action url="${this._route}">${form}</hs-action>` : null;
122
114
  },
@@ -124,7 +116,7 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
124
116
  _getRouteHandlers() {
125
117
  return [
126
118
  ..._middleware,
127
- async (c: Context) => {
119
+ async (c: THSContext) => {
128
120
  const response = await returnHTMLResponse(c, () => api.run(c));
129
121
 
130
122
  // Replace redirects with special header because fetch() automatically follows redirects
package/src/assets.ts CHANGED
@@ -36,7 +36,7 @@ export async function buildClientJS() {
36
36
  /**
37
37
  * Render a client JS module as a script tag
38
38
  */
39
- export function renderClientJS<T>(module: T, onLoad?: (module: T) => void | string) {
39
+ export function renderClientJS<T>(module: T, loadScript?: ((module: T) => void) | string) {
40
40
  // @ts-ignore
41
41
  if (!module.__CLIENT_JS) {
42
42
  throw new Error(
@@ -47,7 +47,11 @@ export function renderClientJS<T>(module: T, onLoad?: (module: T) => void | stri
47
47
  return html.raw(
48
48
  // @ts-ignore
49
49
  module.__CLIENT_JS.renderScriptTag({
50
- onLoad: onLoad ? (typeof onLoad === 'string' ? onLoad : functionToString(onLoad)) : undefined,
50
+ loadScript: loadScript
51
+ ? typeof loadScript === 'string'
52
+ ? loadScript
53
+ : functionToString(loadScript)
54
+ : undefined,
51
55
  })
52
56
  );
53
57
  }
package/src/plugins.ts CHANGED
@@ -71,8 +71,8 @@ export const __CLIENT_JS = {
71
71
  esmName: "${esmName}",
72
72
  sourceFile: "${args.path}",
73
73
  outputFile: "${result.outputs[0].path}",
74
- renderScriptTag: ({ onLoad }) => {
75
- const fn = onLoad ? (typeof onLoad === 'string' ? onLoad : \`const fn = \${functionToString(onLoad)}; fn(${fnArgs});\`) : '';
74
+ renderScriptTag: ({ loadScript }) => {
75
+ const fn = loadScript ? (typeof loadScript === 'string' ? loadScript : \`const fn = \${functionToString(loadScript)}; fn(${fnArgs});\`) : '';
76
76
  return \`<script type="module" data-source-id="${jsId}">import ${exports} from "${esmName}";\n\${fn}</script>\`;
77
77
  },
78
78
  }
package/src/server.ts CHANGED
@@ -18,16 +18,19 @@ const CWD = process.cwd();
18
18
  /**
19
19
  * Types
20
20
  */
21
+ export type THSContext = Context<any, any, {}>;
21
22
  export type THSResponseTypes = HSHtml | Response | string | null;
22
- export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
23
- export type THSAPIRouteHandler = (context: Context) => Promise<any> | any;
23
+ export type THSRouteHandler = (context: THSContext) => THSResponseTypes | Promise<THSResponseTypes>;
24
+ export type THSAPIRouteHandler = (context: THSContext) => Promise<any> | any;
24
25
 
25
26
  export type THSRoute = {
26
27
  _kind: 'hsRoute';
27
28
  get: (handler: THSRouteHandler) => THSRoute;
28
29
  post: (handler: THSRouteHandler) => THSRoute;
29
30
  middleware: (middleware: Array<MiddlewareHandler>) => THSRoute;
30
- _getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
31
+ _getRouteHandlers: () => Array<
32
+ MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)
33
+ >;
31
34
  };
32
35
  export type THSAPIRoute = {
33
36
  _kind: 'hsAPIRoute';
@@ -37,7 +40,9 @@ export type THSAPIRoute = {
37
40
  delete: (handler: THSAPIRouteHandler) => THSAPIRoute;
38
41
  patch: (handler: THSAPIRouteHandler) => THSAPIRoute;
39
42
  middleware: (middleware: Array<MiddlewareHandler>) => THSAPIRoute;
40
- _getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
43
+ _getRouteHandlers: () => Array<
44
+ MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)
45
+ >;
41
46
  };
42
47
 
43
48
  export function createConfig(config: THSServerConfig): THSServerConfig {
@@ -82,7 +87,7 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
82
87
  _getRouteHandlers() {
83
88
  return [
84
89
  ..._middleware,
85
- async (context: Context) => {
90
+ async (context: THSContext) => {
86
91
  const method = context.req.method.toUpperCase();
87
92
 
88
93
  // Handle CORS preflight requests
@@ -166,7 +171,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
166
171
  _getRouteHandlers() {
167
172
  return [
168
173
  ..._middleware,
169
- async (context: Context) => {
174
+ async (context: THSContext) => {
170
175
  const method = context.req.method.toUpperCase();
171
176
 
172
177
  // Handle CORS preflight requests
@@ -246,7 +251,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
246
251
  * Return HTML response from userland route handler
247
252
  */
248
253
  export async function returnHTMLResponse(
249
- context: Context,
254
+ context: THSContext,
250
255
  handlerFn: () => unknown,
251
256
  responseOptions?: { status?: ContentfulStatusCode; headers?: Headers | Record<string, string> }
252
257
  ): Promise<Response> {
@@ -333,7 +338,7 @@ export function isRunnableRoute(route: unknown): boolean {
333
338
  * @TODO: Should check for and load user-customizeable template with special name (app/__error.ts ?)
334
339
  */
335
340
  async function showErrorReponse(
336
- context: Context,
341
+ context: THSContext,
337
342
  err: Error,
338
343
  responseOptions?: { status?: ContentfulStatusCode; headers?: Headers | Record<string, string> }
339
344
  ) {
@@ -408,8 +413,13 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
408
413
  const routes: THSRouteMap[] = [];
409
414
 
410
415
  for (const file of files) {
411
- // No directories
412
- if (!file.includes('.') || basename(file).startsWith('.')) {
416
+ // No directories or test files
417
+ if (
418
+ !file.includes('.') ||
419
+ basename(file).startsWith('.') ||
420
+ file.includes('.test.') ||
421
+ file.includes('.spec.')
422
+ ) {
413
423
  continue;
414
424
  }
415
425
 
@@ -466,8 +476,13 @@ export async function buildActions(config: THSServerConfig): Promise<THSRouteMap
466
476
  const routes: THSRouteMap[] = [];
467
477
 
468
478
  for (const file of files) {
469
- // No directories
470
- if (!file.includes('.') || basename(file).startsWith('.')) {
479
+ // No directories or test files
480
+ if (
481
+ !file.includes('.') ||
482
+ basename(file).startsWith('.') ||
483
+ file.includes('.test.') ||
484
+ file.includes('.spec.')
485
+ ) {
471
486
  continue;
472
487
  }
473
488
 
@@ -496,7 +511,7 @@ export async function buildActions(config: THSServerConfig): Promise<THSRouteMap
496
511
  */
497
512
  export function createRouteFromModule(
498
513
  RouteModule: any
499
- ): Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)> {
514
+ ): Array<MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)> {
500
515
  const route = getRunnableRoute(RouteModule);
501
516
  return route._getRouteHandlers();
502
517
  }