honertia 0.1.1 → 0.1.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/README.md CHANGED
@@ -9,20 +9,13 @@
9
9
  [![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?logo=cloudflare&logoColor=fff)](https://workers.cloudflare.com/)
10
10
  [![Effect](https://img.shields.io/badge/Effect-TS-black)](https://effect.website/)
11
11
 
12
- ## Raison d'être
12
+ ## Overview
13
13
 
14
- I've found myself wanting to use Cloudflare Workers for everything, but having come from a Laravel background nothing quite matched the DX and simplicity of Laravel x Inertia.js. When building Laravel projects I would always use Loris Leiva's laravel-actions package among other opinionated architecture decisions such as Vite, Tailwind, Bun, React etc., all of which have or will be incorporated into this project. With Cloudflare Workers the obvious choice is Hono and so we've adapted the Inertia.js protocol to run on workers+hono to mimic a Laravel-style app. Ever since learning of Effect.ts I've known that I wanted to use it for something bigger, and so we've utilised it here. Ultimately this is a workers+hono+vite+bun+laravel+inertia+effect+betterauth+planetscale mashup that delivers clean, readable, and powerful web app scaffolding.
14
+ An Inertia.js-style adapter for Hono with Effect.js integration. Inertia keeps a server-driven app but behaves like an SPA: link clicks and form posts are intercepted, a fetch/XHR request returns a JSON page object (component + props), and the client swaps the page without a full reload. Honertia layers Laravel-style route patterns and Effect actions on top of that so handlers stay clean, readable, and composable.
15
15
 
16
- An Inertia.js-style adapter for Hono with Effect.js integration. Build full-stack applications with type-safe server actions, Laravel-inspired validation, and seamless React rendering.
16
+ ## Raison d'être
17
17
 
18
- ## Requirements
19
-
20
- - **Runtime**: Node.js 18+ or Bun 1.0+
21
- - **Peer Dependencies**:
22
- - `hono` >= 4.0.0
23
- - `better-auth` >= 1.0.0
24
- - **Dependencies**:
25
- - `effect` >= 3.12.0
18
+ I've found myself wanting to use Cloudflare Workers for everything, but having come from a Laravel background nothing quite matched the DX and simplicity of Laravel x Inertia.js. When building Laravel projects I would always use Loris Leiva's laravel-actions package among other opinionated architecture decisions such as Vite, Tailwind, Bun, React etc., all of which have or will be incorporated into this project. With Cloudflare Workers the obvious choice is Hono and so we've adapted the Inertia.js protocol to run on workers+hono to mimic a Laravel-style app. Ever since learning of Effect.ts I've known that I wanted to use it for something bigger, and so we've utilised it here. Ultimately this is a workers+hono+vite+bun+laravel+inertia+effect+betterauth+planetscale mashup.
26
19
 
27
20
  ## Installation
28
21
 
@@ -36,8 +29,9 @@ bun add honertia
36
29
  // src/index.ts
37
30
  import { Hono } from 'hono'
38
31
  import { logger } from 'hono/logger'
39
- import { setupHonertia, createTemplate, registerErrorHandlers, vite } from 'honertia'
32
+ import { setupHonertia, createTemplate, createVersion, registerErrorHandlers, vite } from 'honertia'
40
33
  import { Context, Layer } from 'effect'
34
+ import manifest from '../dist/manifest.json'
41
35
 
42
36
  import { createDb } from './db'
43
37
  import type { Env } from './types'
@@ -45,13 +39,16 @@ import { createAuth } from './lib/auth'
45
39
  import { registerRoutes } from './routes'
46
40
 
47
41
  const app = new Hono<Env>()
42
+ const assetVersion = createVersion(manifest)
43
+ const entry = manifest['src/main.tsx']
44
+ const assetPath = (path: string) => `/${path}`
48
45
 
49
46
  class BindingsService extends Context.Tag('app/Bindings')<
50
47
  BindingsService,
51
48
  { KV: KVNamespace }
52
49
  >() {}
53
50
 
54
- // Database & Auth
51
+ // Request-scoped setup: put db/auth on c.var so Honertia/Effect can read them.
55
52
  app.use('*', async (c, next) => {
56
53
  c.set('db', createDb(c.env.DATABASE_URL))
57
54
  c.set('auth', createAuth({
@@ -62,24 +59,28 @@ app.use('*', async (c, next) => {
62
59
  await next()
63
60
  })
64
61
 
65
- // Honertia (bundles core middleware, auth loading, and Effect bridge)
62
+ // Honertia bundles the core middleware + auth loading + Effect runtime setup.
66
63
  app.use('*', setupHonertia<Env, BindingsService>({
67
64
  honertia: {
68
- version: '1.0.0',
65
+ // Use your asset manifest hash so Inertia reloads on deploy.
66
+ version: assetVersion,
69
67
  render: createTemplate((ctx) => {
70
68
  const isProd = ctx.env.ENVIRONMENT === 'production'
71
69
  return {
72
- title: 'Dashboard',
73
- scripts: isProd ? ['/assets/main.js'] : [vite.script()],
70
+ title: 'My Web App',
71
+ scripts: isProd ? [assetPath(entry.file)] : [vite.script()],
72
+ styles: isProd ? (entry.css ?? []).map(assetPath) : [],
74
73
  head: isProd ? '' : vite.hmrHead(),
75
74
  }
76
75
  }),
77
76
  },
78
77
  effect: {
78
+ // Expose Cloudflare bindings to Effect handlers via a service layer.
79
79
  services: (c) => Layer.succeed(BindingsService, {
80
80
  KV: c.env.MY_KV,
81
81
  }),
82
82
  },
83
+ // Optional: extra Hono middleware in the same chain.
83
84
  middleware: [
84
85
  logger(),
85
86
  // register additional middleware here...
@@ -101,16 +102,18 @@ import { effectAuthRoutes, RequireAuthLayer } from 'honertia/auth'
101
102
  import { showDashboard, listProjects, createProject, showProject, deleteProject } from './actions'
102
103
 
103
104
  export function registerRoutes(app: Hono<Env>) {
104
- // Auth routes (login, register, logout, API handler)
105
+ // Auth routes (login, register, logout, API handler) wired to better-auth.
106
+ // CORS for /api/auth/* can be enabled via the `cors` option (see below).
105
107
  effectAuthRoutes(app, {
106
108
  loginComponent: 'Auth/Login',
107
109
  registerComponent: 'Auth/Register',
108
110
  })
109
111
 
110
- // Routes that require the user to be authenticated
112
+ // Effect routes give you typed, DI-friendly handlers (no direct Hono ctx).
111
113
  effectRoutes(app)
112
114
  .provide(RequireAuthLayer)
113
115
  .group((route) => {
116
+ // Grouped routes share layers and path prefixes.
114
117
  route.get('/', showDashboard) // GET example.com
115
118
 
116
119
  route.prefix('/projects').group((route) => {
@@ -123,6 +126,163 @@ export function registerRoutes(app: Hono<Env>) {
123
126
  }
124
127
  ```
125
128
 
129
+ ### Environment Variables
130
+
131
+ Honertia reads these from `c.env` (Cloudflare Workers bindings):
132
+
133
+ ```toml
134
+ # wrangler.toml
135
+ ENVIRONMENT = "production"
136
+ ```
137
+
138
+ If you prefer `wrangler.jsonc`, the same binding looks like:
139
+
140
+ ```jsonc
141
+ {
142
+ "vars": {
143
+ "ENVIRONMENT": "production"
144
+ }
145
+ }
146
+ ```
147
+
148
+ Set secrets like `DATABASE_URL` and `BETTER_AUTH_SECRET` via Wrangler (not in source control):
149
+
150
+ ```bash
151
+ wrangler secret put DATABASE_URL
152
+ wrangler secret put BETTER_AUTH_SECRET
153
+ ```
154
+
155
+ ### Client Setup (React + Inertia)
156
+
157
+ Honertia uses the standard Inertia React client. You'll need a client entry
158
+ point and a Vite build that emits a manifest (for `createVersion`).
159
+
160
+ Install client dependencies:
161
+
162
+ ```bash
163
+ bun add react react-dom @inertiajs/react
164
+ bun add -d @vitejs/plugin-react tailwindcss @tailwindcss/vite
165
+ ```
166
+
167
+ Create a Vite config that enables Tailwind v4, sets up an alias used in the
168
+ examples, and emits `dist/manifest.json`:
169
+
170
+ ```typescript
171
+ // vite.config.ts
172
+ import { defineConfig } from 'vite'
173
+ import react from '@vitejs/plugin-react'
174
+ import tailwindcss from '@tailwindcss/vite'
175
+ import path from 'path'
176
+
177
+ export default defineConfig({
178
+ plugins: [tailwindcss(), react()],
179
+ build: {
180
+ outDir: 'dist',
181
+ // Use an explicit filename so imports match build output.
182
+ manifest: 'manifest.json',
183
+ emptyOutDir: true,
184
+ },
185
+ resolve: {
186
+ alias: {
187
+ '~': path.resolve(__dirname, 'src'),
188
+ },
189
+ },
190
+ })
191
+ ```
192
+
193
+ Create a Tailwind CSS entry file:
194
+
195
+ ```css
196
+ /* src/styles.css */
197
+ @import "tailwindcss";
198
+
199
+ @layer base {
200
+ body {
201
+ margin: 0;
202
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
203
+ background-color: #f8fafc;
204
+ color: #0f172a;
205
+ }
206
+ }
207
+ ```
208
+
209
+ Set up the Inertia client entry point (default path matches `vite.script()`):
210
+
211
+ ```tsx
212
+ // src/main.tsx
213
+ import './styles.css'
214
+
215
+ import { createInertiaApp } from '@inertiajs/react'
216
+ import { createRoot } from 'react-dom/client'
217
+
218
+ const pages = import.meta.glob('./pages/**/*.tsx')
219
+
220
+ createInertiaApp({
221
+ resolve: (name) => {
222
+ const page = pages[`./pages/${name}.tsx`]
223
+ if (!page) {
224
+ throw new Error(`Page not found: ${name}`)
225
+ }
226
+ return page()
227
+ },
228
+ setup({ el, App, props }) {
229
+ createRoot(el).render(<App {...props} />)
230
+ },
231
+ })
232
+ ```
233
+
234
+ The `resolve` function maps `render('Projects/Index')` to
235
+ `src/pages/Projects/Index.tsx`.
236
+
237
+ Optional: add a `tailwind.config.ts` only if you need theme extensions or
238
+ custom content globs.
239
+
240
+ ### Build & Deploy Notes
241
+
242
+ The server imports `dist/manifest.json`, so it must exist at build time. In
243
+ production, read scripts and styles from the manifest (Tailwind's CSS is listed
244
+ under your entry's `css` array). When deploying with Wrangler, build the client
245
+ assets first:
246
+
247
+ ```bash
248
+ # build client assets before deploying the worker
249
+ bun run build:client
250
+ wrangler deploy
251
+ ```
252
+
253
+ Optional dev convenience: if you want to run the worker without building the
254
+ client, you can keep a stub `dist/manifest.json` (ignored by git) and replace it
255
+ once you run `vite build`.
256
+
257
+ ### Recommended File Structure
258
+
259
+ Here's a minimal project layout that matches the Quick Start:
260
+
261
+ ```
262
+ .
263
+ ├── src/
264
+ │ ├── index.ts # Hono app setup (setupHonertia)
265
+ │ ├── routes.ts # effectRoutes / effectAuthRoutes
266
+ │ ├── main.tsx # Inertia + React client entry
267
+ │ ├── styles.css # Tailwind CSS entry
268
+ │ ├── actions/
269
+ │ │ └── projects/
270
+ │ │ └── list.ts # listProjects action
271
+ │ ├── pages/
272
+ │ │ └── Projects/
273
+ │ │ └── Index.tsx # render('Projects/Index')
274
+ │ ├── db.ts
275
+ │ ├── lib/
276
+ │ │ └── auth.ts
277
+ │ └── types.ts
278
+ ├── dist/
279
+ │ └── manifest.json # generated by Vite build
280
+ ├── vite.config.ts
281
+ ├── wrangler.toml # or wrangler.jsonc
282
+ ├── package.json
283
+ └── tsconfig.json
284
+ ```
285
+
126
286
  ### Example Action
127
287
 
128
288
  Here's the `listProjects` action referenced above:
@@ -157,10 +317,92 @@ export const listProjects = Effect.gen(function* () {
157
317
  const db = yield* DatabaseService
158
318
  const user = yield* AuthUserService
159
319
  const props = yield* fetchProjects(db as Database, user)
160
- return yield* render('Dashboard/Projects/Index', props)
320
+ return yield* render('Projects/Index', props)
161
321
  })
162
322
  ```
163
323
 
324
+ The component name `Projects/Index` maps to a file on disk. A common
325
+ Vite + React layout is:
326
+
327
+ ```
328
+ src/pages/Projects/Index.tsx
329
+ ```
330
+
331
+ That means the folders mirror the component path, and `Index.tsx` is the file
332
+ that exports the page component. In the example below, `Link` comes from
333
+ `@inertiajs/react` because it performs Inertia client-side visits (preserving
334
+ page state and avoiding full reloads), whereas a plain `<a>` would do a full
335
+ navigation.
336
+
337
+ ```tsx
338
+ // src/pages/Projects/Index.tsx
339
+ /**
340
+ * Projects Index Page
341
+ */
342
+
343
+ import { Link } from '@inertiajs/react'
344
+ import Layout from '~/components/Layout'
345
+ import type { PageProps, Project } from '~/types'
346
+
347
+ interface Props {
348
+ projects: Project[]
349
+ }
350
+
351
+ export default function ProjectsIndex({ projects }: PageProps<Props>) {
352
+ return (
353
+ <Layout>
354
+ <div className="flex justify-between items-center mb-6">
355
+ <h1 className="text-2xl font-bold text-gray-900">Projects</h1>
356
+ <Link
357
+ href="/projects/create"
358
+ className="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"
359
+ >
360
+ New Project
361
+ </Link>
362
+ </div>
363
+
364
+ <div className="bg-white rounded-lg shadow">
365
+ {projects.length === 0 ? (
366
+ <div className="p-6 text-center text-gray-500">
367
+ No projects yet.{' '}
368
+ <Link href="/projects/create" className="text-indigo-600 hover:underline">
369
+ Create your first project
370
+ </Link>
371
+ </div>
372
+ ) : (
373
+ <ul className="divide-y divide-gray-200">
374
+ {projects.map((project) => (
375
+ <li key={project.id}>
376
+ <Link
377
+ href={`/projects/${project.id}`}
378
+ className="block px-6 py-4 hover:bg-gray-50"
379
+ >
380
+ <div className="flex justify-between items-start">
381
+ <div>
382
+ <h3 className="text-sm font-medium text-gray-900">
383
+ {project.name}
384
+ </h3>
385
+ {project.description && (
386
+ <p className="text-sm text-gray-500 mt-1">
387
+ {project.description}
388
+ </p>
389
+ )}
390
+ </div>
391
+ <span className="text-sm text-gray-400">
392
+ {new Date(project.createdAt).toLocaleDateString()}
393
+ </span>
394
+ </div>
395
+ </Link>
396
+ </li>
397
+ ))}
398
+ </ul>
399
+ )}
400
+ </div>
401
+ </Layout>
402
+ )
403
+ }
404
+ ```
405
+
164
406
  ### Vite Helpers
165
407
 
166
408
  The `vite` helper provides dev/prod asset management:
@@ -172,6 +414,15 @@ vite.script() // 'http://localhost:5173/src/main.tsx'
172
414
  vite.hmrHead() // HMR preamble script tags for React Fast Refresh
173
415
  ```
174
416
 
417
+ ## Requirements
418
+
419
+ - **Runtime**: Node.js 18+ or Bun 1.0+
420
+ - **Peer Dependencies**:
421
+ - `hono` >= 4.0.0
422
+ - `better-auth` >= 1.0.0
423
+ - **Dependencies**:
424
+ - `effect` >= 3.12.0
425
+
175
426
  ## Core Concepts
176
427
 
177
428
  ### Effect-Based Handlers
@@ -567,6 +818,23 @@ effectAuthRoutes(app, {
567
818
  })
568
819
  ```
569
820
 
821
+ To enable CORS for the auth API handler (`/api/auth/*`), pass a `cors` config.
822
+ By default, no CORS headers are added (recommended when your UI and API share the same origin).
823
+ Use this when your frontend is on a different origin (local dev, separate domain, mobile app, etc.).
824
+
825
+ ```typescript
826
+ effectAuthRoutes(app, {
827
+ apiPath: '/api/auth',
828
+ cors: {
829
+ origin: ['http://localhost:5173', 'http://localhost:3000'],
830
+ credentials: true,
831
+ },
832
+ })
833
+ ```
834
+
835
+ This sets the appropriate `Access-Control-*` headers and handles `OPTIONS` preflight for the auth API routes.
836
+ Always keep the `origin` list tight; avoid `'*'` for auth endpoints, especially with `credentials: true`.
837
+
570
838
  ## Action Factories
571
839
 
572
840
  For common patterns, use action factories:
package/dist/helpers.d.ts CHANGED
@@ -6,6 +6,12 @@ import type { PageObject } from './types.js';
6
6
  export interface PageProps {
7
7
  errors?: Record<string, string>;
8
8
  }
9
+ export interface AssetManifestEntry {
10
+ file?: string;
11
+ css?: string[];
12
+ assets?: string[];
13
+ }
14
+ export type AssetManifest = Record<string, string | AssetManifestEntry>;
9
15
  export interface TemplateOptions {
10
16
  title?: string;
11
17
  scripts?: string[];
@@ -26,16 +32,23 @@ export interface TemplateOptions {
26
32
  *
27
33
  * @example Dynamic config based on environment
28
34
  * ```ts
35
+ * const entry = manifest['src/main.tsx']
36
+ * const assetPath = (path: string) => `/${path}`
37
+ *
29
38
  * createTemplate((ctx) => ({
30
39
  * title: 'App',
31
40
  * scripts: ctx.env.ENVIRONMENT === 'production'
32
- * ? ['/assets/main.js']
33
- * : ['http://localhost:5173/src/main.tsx'],
41
+ * ? [assetPath(entry.file)]
42
+ * : [vite.script()],
43
+ * styles: ctx.env.ENVIRONMENT === 'production'
44
+ * ? (entry.css ?? []).map(assetPath)
45
+ * : [],
46
+ * head: ctx.env.ENVIRONMENT === 'production' ? '' : vite.hmrHead(),
34
47
  * }))
35
48
  * ```
36
49
  */
37
50
  export declare function createTemplate(options: TemplateOptions | ((ctx: Context) => TemplateOptions)): (page: PageObject, ctx?: Context) => string;
38
- export declare function createVersion(manifest: Record<string, string>): string;
51
+ export declare function createVersion(manifest: AssetManifest): string;
39
52
  /**
40
53
  * Vite development configuration helpers.
41
54
  */
@@ -57,7 +70,7 @@ export declare const vite: {
57
70
  * @param port - Vite dev server port (default: 5173)
58
71
  * @example
59
72
  * ```ts
60
- * scripts: isProd ? ['/assets/main.js'] : [vite.script()]
73
+ * scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
61
74
  * ```
62
75
  */
63
76
  script(entry?: string, port?: number): string;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC,GAC7D,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,MAAM,CA6C7C;AAWD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAStE;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;IACf;;;;;;;;OAQG;4BACmB,MAAM;IAa5B;;;;;;;;;OASG;2CAC2C,MAAM;CAGrD,CAAA"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAAC,CAAA;AAEvE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC,GAC7D,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,MAAM,CA6C7C;AAWD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CA8B7D;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;IACf;;;;;;;;OAQG;4BACmB,MAAM;IAa5B;;;;;;;;;OASG;2CAC2C,MAAM;CAGrD,CAAA"}
package/dist/helpers.js CHANGED
@@ -14,11 +14,18 @@
14
14
  *
15
15
  * @example Dynamic config based on environment
16
16
  * ```ts
17
+ * const entry = manifest['src/main.tsx']
18
+ * const assetPath = (path: string) => `/${path}`
19
+ *
17
20
  * createTemplate((ctx) => ({
18
21
  * title: 'App',
19
22
  * scripts: ctx.env.ENVIRONMENT === 'production'
20
- * ? ['/assets/main.js']
21
- * : ['http://localhost:5173/src/main.tsx'],
23
+ * ? [assetPath(entry.file)]
24
+ * : [vite.script()],
25
+ * styles: ctx.env.ENVIRONMENT === 'production'
26
+ * ? (entry.css ?? []).map(assetPath)
27
+ * : [],
28
+ * head: ctx.env.ENVIRONMENT === 'production' ? '' : vite.hmrHead(),
22
29
  * }))
23
30
  * ```
24
31
  */
@@ -66,7 +73,26 @@ function escapeHtml(str) {
66
73
  .replace(/'/g, '&#39;');
67
74
  }
68
75
  export function createVersion(manifest) {
69
- const combined = Object.values(manifest).sort().join('');
76
+ const assetFiles = [];
77
+ for (const value of Object.values(manifest)) {
78
+ if (typeof value === 'string') {
79
+ assetFiles.push(value);
80
+ continue;
81
+ }
82
+ if (!value || typeof value !== 'object') {
83
+ continue;
84
+ }
85
+ if (typeof value.file === 'string') {
86
+ assetFiles.push(value.file);
87
+ }
88
+ if (Array.isArray(value.css)) {
89
+ assetFiles.push(...value.css);
90
+ }
91
+ if (Array.isArray(value.assets)) {
92
+ assetFiles.push(...value.assets);
93
+ }
94
+ }
95
+ const combined = assetFiles.sort().join('');
70
96
  let hash = 0;
71
97
  for (let i = 0; i < combined.length; i++) {
72
98
  const char = combined.charCodeAt(i);
@@ -107,7 +133,7 @@ export const vite = {
107
133
  * @param port - Vite dev server port (default: 5173)
108
134
  * @example
109
135
  * ```ts
110
- * scripts: isProd ? ['/assets/main.js'] : [vite.script()]
136
+ * scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
111
137
  * ```
112
138
  */
113
139
  script(entry = '/src/main.tsx', port = 5173) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honertia",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Inertia.js-style server-driven SPA adapter for Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",