@gnosticdev/hono-actions 2.0.6 → 2.0.8

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
@@ -239,6 +239,95 @@ const handleSubmit = async (formData: FormData) => {
239
239
  }
240
240
  ```
241
241
 
242
+ ## Advanced Usage
243
+
244
+ ### Using AsyncLocalStorage with Custom Hono Instances
245
+
246
+ For advanced use cases involving request-scoped data and custom Hono instances, see [docs/async_hooks.md](./docs/async_hooks.md).
247
+
248
+ ## Augmenting Type Interfaces
249
+
250
+ When using this library, you may need to augment the type interfaces to add custom types for your environment bindings, Hono context variables, or Astro locals. This is especially important when using the Cloudflare adapter.
251
+
252
+ ### Cloudflare Runtime Types
253
+
254
+ When using the `@astrojs/cloudflare` adapter, the library automatically exports Cloudflare runtime types. However, it assumes that Cloudflare types have been generated by Wrangler via the `wrangler types` command, which creates an `Env` interface that is automatically added to `HonoEnv['Bindings']`.
255
+
256
+ To generate Cloudflare types:
257
+
258
+ ```bash
259
+ bunx --bun wrangler types
260
+ ```
261
+
262
+ This creates a `worker-configuration.d.ts` file (or similar) containing the `Env` interface based on your `wrangler.jsonc` configuration.
263
+
264
+ ### Extending HonoEnv Types
265
+
266
+ To add additional types to your Hono environment, create a type declaration file (e.g., `src/env.d.ts`) and augment the module:
267
+
268
+ ```typescript
269
+ // src/env.d.ts
270
+ import '@gnosticdev/hono-actions/actions'
271
+
272
+ // Augmenting the actions types for use with cloudflare adapter
273
+ declare module '@gnosticdev/hono-actions/actions' {
274
+ // 1) Extend existing Bindings, with Env from worker-configuration.d.ts
275
+ interface Bindings extends Env {
276
+ anotherVar: string
277
+ }
278
+
279
+ // 2) Add Variables available on `ctx.var.db` or `ctx.var.db.get('randomKey')` in hono actions
280
+ interface HonoEnv {
281
+ Variables: {
282
+ db: Map<string, any>
283
+ }
284
+ Bindings: Bindings
285
+ }
286
+ }
287
+
288
+ // Extend Astro Locals if you want to use in middleware
289
+ // need to add this to global scope bc we have an import in the file
290
+ declare global {
291
+ type Runtime = import('@astrojs/cloudflare').Runtime<Env>
292
+ declare namespace App {
293
+ interface Locals extends Runtime {
294
+ db: Map<string, any> // this will now be available on both `ctx.var.db` and `Astro.locals.db`
295
+ }
296
+ }
297
+ }
298
+
299
+
300
+ ```
301
+
302
+ Now in your actions, you'll have full type safety:
303
+
304
+ ```typescript
305
+ // src/hono.ts
306
+ import { defineHonoAction, type HonoEnv } from '@gnosticdev/hono-actions/actions'
307
+
308
+ export const myAction = defineHonoAction({
309
+ handler: async (input, ctx) => {
310
+ // ctx.env has type: Bindings (includes Env + your custom bindings)
311
+ const kv = ctx.env.CUSTOM_KV
312
+
313
+ // ctx.var has type: HonoEnv['Variables']
314
+ const user = kv.get('user')
315
+
316
+ return { success: true }
317
+ }
318
+ })
319
+ ```
320
+
321
+ And in your Astro pages:
322
+
323
+ ```typescript
324
+ // src/pages/index.astro
325
+ ---
326
+ // Astro.locals has type: App.Locals
327
+ const user = Astro.locals.user
328
+ ---
329
+ ```
330
+
242
331
  ## Package Structure
243
332
 
244
333
  This package provides these entry points:
package/dist/actions.d.ts CHANGED
@@ -20,6 +20,10 @@ declare class HonoActionError<TMessage extends string, TCode extends ActionError
20
20
 
21
21
  interface Bindings {
22
22
  }
23
+ interface Variables {
24
+ /** Variables */
25
+ [key: string]: unknown;
26
+ }
23
27
  /**
24
28
  * HonoEnv is passed to the Hono context to provide types on `ctx.env`.
25
29
  *
@@ -36,7 +40,7 @@ interface Bindings {
36
40
  */
37
41
  interface HonoEnv {
38
42
  Bindings: Bindings;
39
- Variables: Record<string, unknown>;
43
+ Variables: Variables;
40
44
  }
41
45
  type HonoActionSchema = z.ZodTypeAny;
42
46
  /**
@@ -76,14 +80,14 @@ type HonoActionParams<TSchema extends HonoActionSchema, TReturn, TEnv extends Ho
76
80
  declare function defineHonoAction<TEnv extends HonoEnv, TSchema extends HonoActionSchema, TReturn, TContext extends Context<TEnv, any, any>>({ schema, handler }: HonoActionParams<TSchema, TReturn, TEnv, TContext>): hono_hono_base.HonoBase<TEnv, {
77
81
  "/": {
78
82
  $post: {
79
- input: unknown extends ((undefined extends z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> ? true : false) extends true ? {
80
- json?: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | undefined;
83
+ input: unknown extends ((undefined extends z.input<TSchema> ? true : false) extends true ? {
84
+ json?: z.input<TSchema> | undefined;
81
85
  } : {
82
- json: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
83
- }) ? {} : (undefined extends z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> ? true : false) extends true ? {
84
- json?: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | undefined;
86
+ json: z.input<TSchema>;
87
+ }) ? {} : (undefined extends z.input<TSchema> ? true : false) extends true ? {
88
+ json?: z.input<TSchema> | undefined;
85
89
  } : {
86
- json: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
90
+ json: z.input<TSchema>;
87
91
  };
88
92
  output: {
89
93
  data: null;
@@ -95,14 +99,14 @@ declare function defineHonoAction<TEnv extends HonoEnv, TSchema extends HonoActi
95
99
  outputFormat: "json";
96
100
  status: 500;
97
101
  } | {
98
- input: unknown extends ((undefined extends z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> ? true : false) extends true ? {
99
- json?: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | undefined;
102
+ input: unknown extends ((undefined extends z.input<TSchema> ? true : false) extends true ? {
103
+ json?: z.input<TSchema> | undefined;
100
104
  } : {
101
- json: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
102
- }) ? {} : (undefined extends z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> ? true : false) extends true ? {
103
- json?: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | undefined;
105
+ json: z.input<TSchema>;
106
+ }) ? {} : (undefined extends z.input<TSchema> ? true : false) extends true ? {
107
+ json?: z.input<TSchema> | undefined;
104
108
  } : {
105
- json: z.input<TSchema | z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
109
+ json: z.input<TSchema>;
106
110
  };
107
111
  output: unknown extends (Awaited<TReturn> | null extends bigint | readonly bigint[] ? never : { [K in keyof {
108
112
  data: Awaited<TReturn>;
@@ -151,6 +155,6 @@ declare function defineHonoAction<TEnv extends HonoEnv, TSchema extends HonoActi
151
155
  status: 200;
152
156
  };
153
157
  };
154
- }, "/">;
158
+ }, "/", "/">;
155
159
 
156
- export { type Bindings, HonoActionError, type HonoEnv, type MergeActionKeyIntoPath, defineHonoAction };
160
+ export { type Bindings, HonoActionError, type HonoEnv, type MergeActionKeyIntoPath, type Variables, defineHonoAction };
package/dist/actions.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { zValidator } from '@hono/zod-validator';
2
+ import { z } from 'astro/zod';
3
+ import { Hono } from 'hono';
4
+
1
5
  // src/actions.ts
2
- import { zValidator } from "@hono/zod-validator";
3
- import { z } from "astro/zod";
4
- import { Hono } from "hono";
5
6
 
6
7
  // src/error.ts
7
8
  var HonoActionError = class extends Error {
@@ -24,23 +25,27 @@ function defineHonoAction({ schema, handler }) {
24
25
  const app = new Hono();
25
26
  const route = app.post(
26
27
  "/",
27
- zValidator("json", schema ?? z.object({}), async (result, c) => {
28
- if (!result.success) {
29
- console.error(result.error.issues);
30
- const firstIssue = result.error.issues[0];
31
- return c.json(
32
- {
33
- data: null,
34
- error: new HonoActionError({
35
- message: firstIssue?.message || "Validation error",
36
- code: "INPUT_VALIDATION_ERROR",
37
- issue: firstIssue
38
- })
39
- },
40
- 400
41
- );
28
+ zValidator(
29
+ "json",
30
+ schema ?? z.any(),
31
+ async (result, c) => {
32
+ if (!result.success) {
33
+ console.error(result.error.issues);
34
+ const firstIssue = result.error.issues[0];
35
+ return c.json(
36
+ {
37
+ data: null,
38
+ error: new HonoActionError({
39
+ message: firstIssue?.message || "Validation error",
40
+ code: "INPUT_VALIDATION_ERROR",
41
+ issue: firstIssue
42
+ })
43
+ },
44
+ 400
45
+ );
46
+ }
42
47
  }
43
- }),
48
+ ),
44
49
  async (c) => {
45
50
  try {
46
51
  const json = c.req.valid("json");
@@ -78,7 +83,5 @@ function defineHonoAction({ schema, handler }) {
78
83
  );
79
84
  return route;
80
85
  }
81
- export {
82
- HonoActionError,
83
- defineHonoAction
84
- };
86
+
87
+ export { HonoActionError, defineHonoAction };
package/dist/index.d.ts CHANGED
@@ -33,8 +33,11 @@ type IntegrationOptions = z.output<typeof optionsSchema>;
33
33
  * generates type-safe client code, and sets up API routes.
34
34
  *
35
35
  * Supprted Adapters:
36
- * - Cloudflare
37
- * - (more to come)
36
+ * - @astrojs/cloudflare
37
+ * - @astrojs/node
38
+ * - @astrojs/vercel
39
+ * - @astrojs/netlify
40
+ *
38
41
  *
39
42
  * @param options - Configuration options for the integration
40
43
  * @param options.basePath - Base path for API routes (default: '/api')
package/dist/index.js CHANGED
@@ -1,20 +1,17 @@
1
+ import { defineIntegration, createResolver, addVirtualImports } from 'astro-integration-kit';
2
+ import { z } from 'astro/zod';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { glob } from 'tinyglobby';
6
+
1
7
  // src/integration.ts
2
- import {
3
- addVirtualImports,
4
- createResolver,
5
- defineIntegration
6
- } from "astro-integration-kit";
7
- import { z } from "astro/zod";
8
- import fs from "node:fs/promises";
9
- import path from "node:path";
10
- import { glob } from "tinyglobby";
11
8
 
12
9
  // src/integration-files.ts
13
10
  function generateRouter(opts) {
14
11
  const { basePath, relativeActionsPath, adapter } = opts;
15
- let exportedApp = `export default app`;
12
+ let exportedApp = "export default app";
16
13
  if (adapter === "@astrojs/netlify") {
17
- exportedApp = `export default handle(app)`;
14
+ exportedApp = "export default handle(app)";
18
15
  }
19
16
  return `import type { HonoEnv, MergeActionKeyIntoPath } from '@gnosticdev/hono-actions/actions'
20
17
  import { Hono } from 'hono'
@@ -106,27 +103,94 @@ export { handler as ALL }
106
103
  var generateHonoClient = (port) => `
107
104
  // Generated by Hono Actions Integration
108
105
  import type { HonoRouter } from './router.js'
109
- import { hc, parseResponse } from 'hono/client'
110
- import type { DetailedError } from 'hono/client'
106
+ import { parseResponse, hc } from 'hono/client'
107
+ import type { DetailedError, ClientRequestOptions } from 'hono/client'
111
108
 
112
109
  function getBaseUrl() {
113
- // client side can just use the base path
110
+ // client side can just use the origin
114
111
  if (typeof window !== 'undefined') {
115
- return '/'
112
+ return window.location.origin
116
113
  }
117
114
 
118
115
  // dev server (dev server) needs to know the port
119
116
  if (import.meta.env.DEV) {
120
- return \`http://localhost:\${${port}}\`
117
+ return 'http://localhost:${port}'
121
118
  }
122
119
 
123
120
  // server side (production) needs full url
124
- return import.meta.env.SITE ?? ''
121
+ if (import.meta.env.SITE) {
122
+ return import.meta.env.SITE
123
+ }
124
+
125
+ return '/'
126
+ }
127
+
128
+ export { parseResponse }
129
+ export type { DetailedError, ClientRequestOptions, HonoRouter }
130
+ export const honoClient = createHonoClient<HonoRouter>(getBaseUrl())
131
+ export function createHonoClient<T extends HonoRouter = HonoRouter>(basePath: string, fetchOptions?: ClientRequestOptions) {
132
+ return hc<T>(basePath, fetchOptions)
133
+ }
134
+ `;
135
+ var generateIntegrationTypes = (adapter) => {
136
+ let actionTypes = `
137
+ // Generated by Hono Actions Integration
138
+ declare module '@gnosticdev/hono-actions/actions' {
139
+ interface Bindings { [key: string]: unknown }
140
+ interface Variables { [key: string]: unknown }
141
+ interface HonoEnv { Bindings: Bindings, Variables: Variables }
142
+ }
143
+ export {}
144
+ `;
145
+ let clientTypes = `
146
+ // Generated by Hono Actions Integration
147
+ declare module '@gnosticdev/hono-actions/client' {
148
+ /**
149
+ * Default hono client using the base url from the environment
150
+ * **Note** if running in production, the \`siteUrl\` option from the astro config will be used.
151
+ * If customization is needed, use the \`createHonoClient\` function instead.
152
+ */
153
+ export const honoClient: typeof import('./client').honoClient
154
+ /**
155
+ * Helper function to parse the response from the Hono client
156
+ */
157
+ export const parseResponse: typeof import('./client').parseResponse
158
+ /**
159
+ * Create a new hono client with custom base url + fetch options
160
+ */
161
+ export const createHonoClient: typeof import('./client').createHonoClient
162
+ export type DetailedError = import('./client').DetailedError
163
+ export type ClientRequestOptions = import('./client').ClientRequestOptions
164
+ /**
165
+ * The hono actions routes. for use in the hono client
166
+ */
167
+ export type HonoRouter = import('./client').HonoRouter
125
168
  }
126
- export { parseResponse, hc }
127
- export type { DetailedError }
128
- export const honoClient = hc<HonoRouter>(getBaseUrl())
129
169
  `;
170
+ switch (adapter) {
171
+ // cloudflare uses Bindings and Variables passed in from the route handler
172
+ case "@astrojs/cloudflare":
173
+ actionTypes = `
174
+ // Generated by Hono Actions Integration
175
+ // keeping separate from the main types.d.ts to avoid clobbering package exports
176
+ declare module '@gnosticdev/hono-actions/actions' {
177
+ interface Bindings extends Env { ASTRO_LOCALS: App.Locals }
178
+ interface Variables { [key: string]: unknown }
179
+ interface HonoEnv { Bindings: Bindings, Variables: Variables }
180
+ }
181
+ export {}
182
+ `;
183
+ clientTypes += `
184
+ type Runtime = import('@astrojs/cloudflare').Runtime<Env>
185
+
186
+ declare namespace App {
187
+ interface Locals extends Runtime {}
188
+ }
189
+ `;
190
+ break;
191
+ }
192
+ return { actionTypes, clientTypes };
193
+ };
130
194
 
131
195
  // src/lib/utils.ts
132
196
  var reservedRoutes = ["_astro", "_actions", "_server_islands"];
@@ -237,6 +301,9 @@ ${ACTION_PATTERNS.map((p) => ` - ${p}`).join("\n")}`
237
301
  codeGenDir.pathname,
238
302
  "client.ts"
239
303
  );
304
+ if (!config.site) {
305
+ logger.warn("No site url found in astro config, add one if you want to use the hono client with SSR");
306
+ }
240
307
  const clientContent = generateHonoClient(config.server.port);
241
308
  await fs.writeFile(clientPathAbs, clientContent, "utf-8");
242
309
  addVirtualImports(params, {
@@ -261,26 +328,6 @@ ${ACTION_PATTERNS.map((p) => ` - ${p}`).join("\n")}`
261
328
  config,
262
329
  logger
263
330
  }) => {
264
- injectTypes({
265
- filename: "actions.d.ts",
266
- content: `
267
- // Generated by Hono Actions Integration
268
- // keeping separate from the main types.d.ts to avoid clobbering package exports
269
- declare module '@gnosticdev/hono-actions/actions' {
270
- interface Bindings extends Env { ASTRO_LOCALS: App.Locals }
271
- interface HonoEnv { Bindings: Bindings }
272
- }
273
- export {}
274
- `
275
- });
276
- let clientTypes = `
277
- // Generated by Hono Actions Integration
278
- declare module '@gnosticdev/hono-actions/client' {
279
- export const honoClient: typeof import('./client').honoClient
280
- export const parseResponse: typeof import('./client').parseResponse
281
- export type DetailedError = import('./client').DetailedError
282
- }
283
- `;
284
331
  const adapter = config.adapter?.name;
285
332
  if (!adapter) {
286
333
  logger.warn("No adapter found...");
@@ -292,14 +339,11 @@ declare module '@gnosticdev/hono-actions/client' {
292
339
  );
293
340
  return;
294
341
  }
295
- if (adapter === "@astrojs/cloudflare") {
296
- clientTypes += `
297
- type Runtime = import('@astrojs/cloudflare').Runtime<Env>
298
- declare namespace App {
299
- interface Locals extends Runtime {}
300
- }
301
- `;
302
- }
342
+ const { actionTypes, clientTypes } = generateIntegrationTypes(adapter);
343
+ injectTypes({
344
+ filename: "actions.d.ts",
345
+ content: actionTypes
346
+ });
303
347
  injectTypes({
304
348
  filename: "types.d.ts",
305
349
  content: clientTypes
@@ -312,6 +356,5 @@ declare module '@gnosticdev/hono-actions/client' {
312
356
 
313
357
  // src/index.ts
314
358
  var src_default = integration_default;
315
- export {
316
- src_default as default
317
- };
359
+
360
+ export { src_default as default };
package/package.json CHANGED
@@ -7,19 +7,18 @@
7
7
  "@hono/standard-validator": "^0.2.0",
8
8
  "@hono/zod-validator": "^0.2.2",
9
9
  "astro-integration-kit": "^0.19.0",
10
- "hono": "^4.10.6",
11
10
  "tinyglobby": "^0.2.15"
12
11
  },
13
12
  "description": "Define server actions with built-in validation, error handling, and a pre-built hono client for calling the routes.",
14
13
  "devDependencies": {
15
- "tsup": "^8.5.0",
16
- "typescript": "catalog:",
17
- "vitest": "catalog:",
18
14
  "@astrojs/cloudflare": "catalog:",
19
15
  "@astrojs/netlify": "catalog:",
20
16
  "@astrojs/node": "catalog:",
21
17
  "@astrojs/vercel": "catalog:",
22
- "astro": "catalog:"
18
+ "@biomejs/biome": "catalog:",
19
+ "astro": "catalog:",
20
+ "tsup": "^8.5.0",
21
+ "typescript": "catalog:"
23
22
  },
24
23
  "exports": {
25
24
  ".": {
@@ -49,7 +48,8 @@
49
48
  "main": "./dist/index.js",
50
49
  "name": "@gnosticdev/hono-actions",
51
50
  "peerDependencies": {
52
- "astro": "^5.13.0"
51
+ "astro": "^5.13.0",
52
+ "hono": "^4.10.6"
53
53
  },
54
54
  "publishConfig": {
55
55
  "access": "public"
@@ -61,5 +61,5 @@
61
61
  },
62
62
  "type": "module",
63
63
  "types": "./dist/index.d.ts",
64
- "version": "2.0.6"
64
+ "version": "2.0.8"
65
65
  }