@gnosticdev/hono-actions 1.0.0
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 +89 -0
- package/dist/define-action.d.ts +119 -0
- package/dist/define-action.js +57 -0
- package/dist/error.d.ts +14 -0
- package/dist/error.js +10 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @gnosticdev/define-hono-action
|
|
2
|
+
|
|
3
|
+
Type-safe Hono action definitions with Valibot validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @gnosticdev/define-hono-action
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Peer Dependencies
|
|
12
|
+
|
|
13
|
+
This package requires the following peer dependencies:
|
|
14
|
+
|
|
15
|
+
- `@hono/valibot-validator`: ^0.2.0
|
|
16
|
+
- `hono`: ^4.0.0
|
|
17
|
+
- `valibot`: ^0.30.0
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { defineHonoAction, ActionError } from '@gnosticdev/define-hono-action'
|
|
23
|
+
import * as v from 'valibot'
|
|
24
|
+
|
|
25
|
+
// Define a simple action
|
|
26
|
+
export const simpleAction = defineHonoAction({
|
|
27
|
+
path: '/simple',
|
|
28
|
+
handler: async (input, ctx) => {
|
|
29
|
+
return { message: 'Hello World!' }
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Define an action with validation
|
|
34
|
+
export const validatedAction = defineHonoAction({
|
|
35
|
+
path: '/validated',
|
|
36
|
+
schema: v.object({
|
|
37
|
+
name: v.string(),
|
|
38
|
+
email: v.pipe(v.string(), v.email())
|
|
39
|
+
}),
|
|
40
|
+
handler: async (input, ctx) => {
|
|
41
|
+
// input is automatically typed based on schema
|
|
42
|
+
return {
|
|
43
|
+
message: `Hello ${input.name}!`,
|
|
44
|
+
email: input.email
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Use custom error handling
|
|
50
|
+
export const errorAction = defineHonoAction({
|
|
51
|
+
path: '/error',
|
|
52
|
+
handler: async (input, ctx) => {
|
|
53
|
+
if (someCondition) {
|
|
54
|
+
throw new ActionError('Custom error message', 'EXTERNAL_API_ERROR')
|
|
55
|
+
}
|
|
56
|
+
return { success: true }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Export all actions
|
|
61
|
+
export const honoActions = {
|
|
62
|
+
simpleAction,
|
|
63
|
+
validatedAction,
|
|
64
|
+
errorAction
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Types
|
|
69
|
+
|
|
70
|
+
The package exports the following types:
|
|
71
|
+
|
|
72
|
+
- `ActionErrorCode`: Union type of possible error codes
|
|
73
|
+
- `ActionError`: Custom error class for action errors
|
|
74
|
+
- `HonoActions`: Type for collections of actions
|
|
75
|
+
|
|
76
|
+
## Error Codes
|
|
77
|
+
|
|
78
|
+
Available error codes:
|
|
79
|
+
|
|
80
|
+
- `INPUT_VALIDATION_ERROR`: Validation failed for input data
|
|
81
|
+
- `EXTERNAL_API_ERROR`: Error calling external API
|
|
82
|
+
- `INTERNAL_SERVER_ERROR`: Internal server error
|
|
83
|
+
- `UNKNOWN_ERROR`: Unknown error occurred
|
|
84
|
+
- `LOCATION_NOT_FOUND`: Location not found
|
|
85
|
+
- `SESSION_NOT_FOUND`: Session not found
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
export interface Bindings {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* HonoEnv is passed to the Hono context to provide types on `ctx.env`.
|
|
7
|
+
*
|
|
8
|
+
* We are using `HonoEnv` to avoid confusion with the Cloudflare types on `Env` -> which cooresponds to `Bindings`
|
|
9
|
+
*/
|
|
10
|
+
export interface HonoEnv {
|
|
11
|
+
Bindings: Bindings;
|
|
12
|
+
Variables: Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
type HonoActionSchema = v.ObjectSchema<v.ObjectEntries, v.ErrorMessage<v.ObjectIssue> | undefined> | v.NeverSchema<undefined>;
|
|
15
|
+
interface HonoActionContext<TEnv extends HonoEnv, TPath extends string, TSchema extends HonoActionSchema> extends Context<TEnv, TPath, {
|
|
16
|
+
input: v.InferInput<TSchema>;
|
|
17
|
+
output: v.InferOutput<TSchema>;
|
|
18
|
+
outputFormat: 'json';
|
|
19
|
+
}> {
|
|
20
|
+
env: TEnv['Bindings'];
|
|
21
|
+
}
|
|
22
|
+
type HonoActionParams<TPath extends string, TSchema extends HonoActionSchema, TReturn, TEnv extends HonoEnv = HonoEnv> = {
|
|
23
|
+
path: TPath;
|
|
24
|
+
schema?: TSchema;
|
|
25
|
+
handler: (params: v.InferOutput<TSchema>, context: HonoActionContext<TEnv, TPath, TSchema>) => Promise<TReturn>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Defines a type-safe Hono action using Valibot for input validation.
|
|
29
|
+
*
|
|
30
|
+
* @param path - The path of the action.
|
|
31
|
+
* @param schema - The object schema for Valibot validation.
|
|
32
|
+
* @default never
|
|
33
|
+
* @param handler - The handler function for the action.
|
|
34
|
+
* @returns A Hono app instance with the defined route
|
|
35
|
+
*/
|
|
36
|
+
export declare function defineHonoAction<TPath extends string, TSchema extends HonoActionSchema, TReturn, TEnv extends HonoEnv = HonoEnv>({ path, schema, handler }: HonoActionParams<TPath, TSchema, TReturn, TEnv>): import("hono/hono-base").HonoBase<TEnv, { [K in import("hono/types").MergePath<"/", TPath>]: {
|
|
37
|
+
$post: {
|
|
38
|
+
input: import("hono/types").AddParam<unknown extends ((undefined extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? true : false) extends true ? {
|
|
39
|
+
json?: (v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_1 ? T_1 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_1 extends any ? T_1 : { [K2 in keyof T_1]?: any; } : never : never) | undefined;
|
|
40
|
+
} : {
|
|
41
|
+
json: v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_2 ? T_2 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_2 extends any ? T_2 : { [K2_1 in keyof T_2]: any; } : never : never;
|
|
42
|
+
}) ? {} : (undefined extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? true : false) extends true ? {
|
|
43
|
+
json?: (v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_3 ? T_3 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_3 extends any ? T_3 : { [K2_2 in keyof T_3]?: any; } : never : never) | undefined;
|
|
44
|
+
} : {
|
|
45
|
+
json: v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_4 ? T_4 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_4 extends any ? T_4 : { [K2_3 in keyof T_4]: any; } : never : never;
|
|
46
|
+
}, import("hono/types").MergePath<"/", TPath>>;
|
|
47
|
+
output: unknown extends ({
|
|
48
|
+
data: Awaited<TReturn>;
|
|
49
|
+
error: null;
|
|
50
|
+
} extends import("hono/utils/types").JSONValue ? { [K_2 in keyof {
|
|
51
|
+
data: Awaited<TReturn>;
|
|
52
|
+
error: null;
|
|
53
|
+
} as ({
|
|
54
|
+
data: Awaited<TReturn>;
|
|
55
|
+
error: null;
|
|
56
|
+
}[K_2] extends infer T_5 ? T_5 extends {
|
|
57
|
+
data: Awaited<TReturn>;
|
|
58
|
+
error: null;
|
|
59
|
+
}[K_2] ? T_5 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) extends true ? never : K_2]: boolean extends ({
|
|
60
|
+
data: Awaited<TReturn>;
|
|
61
|
+
error: null;
|
|
62
|
+
}[K_2] extends infer T_6 ? T_6 extends {
|
|
63
|
+
data: Awaited<TReturn>;
|
|
64
|
+
error: null;
|
|
65
|
+
}[K_2] ? T_6 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) ? import("hono/utils/types").JSONParsed<{
|
|
66
|
+
data: Awaited<TReturn>;
|
|
67
|
+
error: null;
|
|
68
|
+
}[K_2]> | undefined : import("hono/utils/types").JSONParsed<{
|
|
69
|
+
data: Awaited<TReturn>;
|
|
70
|
+
error: null;
|
|
71
|
+
}[K_2]>; } : never) ? {} : {
|
|
72
|
+
data: Awaited<TReturn>;
|
|
73
|
+
error: null;
|
|
74
|
+
} extends import("hono/utils/types").JSONValue ? { [K_2 in keyof {
|
|
75
|
+
data: Awaited<TReturn>;
|
|
76
|
+
error: null;
|
|
77
|
+
} as ({
|
|
78
|
+
data: Awaited<TReturn>;
|
|
79
|
+
error: null;
|
|
80
|
+
}[K_2] extends infer T_5 ? T_5 extends {
|
|
81
|
+
data: Awaited<TReturn>;
|
|
82
|
+
error: null;
|
|
83
|
+
}[K_2] ? T_5 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) extends true ? never : K_2]: boolean extends ({
|
|
84
|
+
data: Awaited<TReturn>;
|
|
85
|
+
error: null;
|
|
86
|
+
}[K_2] extends infer T_6 ? T_6 extends {
|
|
87
|
+
data: Awaited<TReturn>;
|
|
88
|
+
error: null;
|
|
89
|
+
}[K_2] ? T_6 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) ? import("hono/utils/types").JSONParsed<{
|
|
90
|
+
data: Awaited<TReturn>;
|
|
91
|
+
error: null;
|
|
92
|
+
}[K_2]> | undefined : import("hono/utils/types").JSONParsed<{
|
|
93
|
+
data: Awaited<TReturn>;
|
|
94
|
+
error: null;
|
|
95
|
+
}[K_2]>; } : never;
|
|
96
|
+
outputFormat: "json";
|
|
97
|
+
status: 200;
|
|
98
|
+
} | {
|
|
99
|
+
input: import("hono/types").AddParam<unknown extends ((undefined extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? true : false) extends true ? {
|
|
100
|
+
json?: (v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_5 ? T_5 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_5 extends any ? T_5 : { [K2_4 in keyof T_5]?: any; } : never : never) | undefined;
|
|
101
|
+
} : {
|
|
102
|
+
json: v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_6 ? T_6 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_6 extends any ? T_6 : { [K2_5 in keyof T_6]: any; } : never : never;
|
|
103
|
+
}) ? {} : (undefined extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? true : false) extends true ? {
|
|
104
|
+
json?: (v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_7 ? T_7 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_7 extends any ? T_7 : { [K2_6 in keyof T_7]?: any; } : never : never) | undefined;
|
|
105
|
+
} : {
|
|
106
|
+
json: v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> extends infer T_8 ? T_8 extends v.InferInput<TSchema | v.UnionSchema<[v.NeverSchema<undefined>, v.ObjectSchema<{}, undefined>], undefined>> ? T_8 extends any ? T_8 : { [K2_7 in keyof T_8]: any; } : never : never;
|
|
107
|
+
}, import("hono/types").MergePath<"/", TPath>>;
|
|
108
|
+
output: {
|
|
109
|
+
data: null;
|
|
110
|
+
error: {
|
|
111
|
+
message: string;
|
|
112
|
+
code: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
outputFormat: "json";
|
|
116
|
+
status: 500;
|
|
117
|
+
};
|
|
118
|
+
}; } extends infer T ? { [KeyType in keyof T]: T[KeyType]; } : never, "/">;
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { vValidator } from '@hono/valibot-validator';
|
|
2
|
+
import { createFactory } from 'hono/factory';
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
import { HonoActionError } from './error.js';
|
|
5
|
+
/**
|
|
6
|
+
* Defines a type-safe Hono action using Valibot for input validation.
|
|
7
|
+
*
|
|
8
|
+
* @param path - The path of the action.
|
|
9
|
+
* @param schema - The object schema for Valibot validation.
|
|
10
|
+
* @default never
|
|
11
|
+
* @param handler - The handler function for the action.
|
|
12
|
+
* @returns A Hono app instance with the defined route
|
|
13
|
+
*/
|
|
14
|
+
export function defineHonoAction({ path, schema, handler }) {
|
|
15
|
+
const factory = createFactory();
|
|
16
|
+
const app = factory.createApp();
|
|
17
|
+
const route = app.post(path, vValidator('json', schema ?? v.union([v.never(), v.object({})]), async (result, c) => {
|
|
18
|
+
if (!result.success) {
|
|
19
|
+
console.error(result.issues);
|
|
20
|
+
return c.json({
|
|
21
|
+
data: null,
|
|
22
|
+
error: new HonoActionError({
|
|
23
|
+
message: result.issues[0].message,
|
|
24
|
+
code: 'INPUT_VALIDATION_ERROR',
|
|
25
|
+
issue: result.issues[0]
|
|
26
|
+
})
|
|
27
|
+
}, 400);
|
|
28
|
+
}
|
|
29
|
+
}), async (c) => {
|
|
30
|
+
try {
|
|
31
|
+
const json = c.req.valid('json');
|
|
32
|
+
// context is validated after the middleware, but we only need the original definition to be passed back in to the handler here.
|
|
33
|
+
const result = await handler(json, c);
|
|
34
|
+
return c.json({
|
|
35
|
+
data: result,
|
|
36
|
+
error: null
|
|
37
|
+
}, 200);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
let errorMessage = 'Internal server error';
|
|
42
|
+
let errorCode = 'INTERNAL_SERVER_ERROR';
|
|
43
|
+
if (error instanceof HonoActionError) {
|
|
44
|
+
errorMessage = error.message;
|
|
45
|
+
errorCode = error.code;
|
|
46
|
+
}
|
|
47
|
+
return c.json({
|
|
48
|
+
data: null,
|
|
49
|
+
error: {
|
|
50
|
+
message: errorMessage,
|
|
51
|
+
code: errorCode
|
|
52
|
+
}
|
|
53
|
+
}, 500);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return route;
|
|
57
|
+
}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type * as v from 'valibot';
|
|
2
|
+
/**
|
|
3
|
+
* Standard error codes for actions
|
|
4
|
+
*/
|
|
5
|
+
export type ActionErrorCode = 'INPUT_VALIDATION_ERROR' | 'EXTERNAL_API_ERROR' | 'INTERNAL_SERVER_ERROR' | 'UNKNOWN_ERROR' | 'LOCATION_NOT_FOUND' | 'SESSION_NOT_FOUND';
|
|
6
|
+
export declare class HonoActionError<TSchema extends v.ObjectSchema<v.ObjectEntries, v.ErrorMessage<v.ObjectIssue> | undefined>, TMessage extends string, TCode extends ActionErrorCode, TIssue extends v.InferIssue<TSchema>> extends Error {
|
|
7
|
+
code: TCode;
|
|
8
|
+
issue?: TIssue;
|
|
9
|
+
constructor({ message, code, issue }: {
|
|
10
|
+
message: TMessage;
|
|
11
|
+
code: TCode;
|
|
12
|
+
issue?: TIssue;
|
|
13
|
+
});
|
|
14
|
+
}
|
package/dist/error.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": {
|
|
3
|
+
"name": "gnosticdev",
|
|
4
|
+
"url": "https://github.com/gnosticdev"
|
|
5
|
+
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@hono/valibot-validator": "0.5.3",
|
|
8
|
+
"astro-integration-kit": "^0.19.0",
|
|
9
|
+
"hono": "^4.9.4"
|
|
10
|
+
},
|
|
11
|
+
"description": "Type-safe Hono server actions with build-in validation",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/bun": "^1.2.20",
|
|
14
|
+
"typescript": "5.9.2"
|
|
15
|
+
},
|
|
16
|
+
"engineStrict": true,
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.0.0"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"default": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./define-action": {
|
|
26
|
+
"default": "./dist/define-action.js",
|
|
27
|
+
"types": "./dist/define-action.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./error": {
|
|
30
|
+
"default": "./dist/error.js",
|
|
31
|
+
"types": "./dist/error.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"keywords": [
|
|
38
|
+
"hono",
|
|
39
|
+
"actions",
|
|
40
|
+
"astro",
|
|
41
|
+
"validation",
|
|
42
|
+
"server actions"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"main": "dist/index.js",
|
|
46
|
+
"name": "@gnosticdev/hono-actions",
|
|
47
|
+
"packageManager": "bun@1.2.21",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"astro": "^5.13.3"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "rm -rf dist && tsc",
|
|
56
|
+
"dev": "rm -rf dist && tsc --watch",
|
|
57
|
+
"prepublishOnly": "bun run build"
|
|
58
|
+
},
|
|
59
|
+
"type": "module",
|
|
60
|
+
"types": "dist/index.d.ts",
|
|
61
|
+
"version": "1.0.0"
|
|
62
|
+
}
|