honertia 0.1.0 → 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 +472 -124
- package/dist/effect/bridge.d.ts +27 -3
- package/dist/effect/bridge.d.ts.map +1 -1
- package/dist/effect/bridge.js +5 -0
- package/dist/effect/routing.d.ts +12 -12
- package/dist/effect/routing.d.ts.map +1 -1
- package/dist/helpers.d.ts +17 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +30 -4
- package/dist/setup.d.ts +3 -3
- package/dist/setup.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# Honertia
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/honertia)
|
|
4
|
+
[](https://bundlephobia.com/package/honertia)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
[](https://hono.dev/)
|
|
9
|
+
[](https://workers.cloudflare.com/)
|
|
10
|
+
[](https://effect.website/)
|
|
7
11
|
|
|
8
|
-
##
|
|
12
|
+
## Overview
|
|
9
13
|
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
- `effect` >= 3.12.0
|
|
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
|
+
|
|
16
|
+
## Raison d'être
|
|
17
|
+
|
|
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.
|
|
16
19
|
|
|
17
20
|
## Installation
|
|
18
21
|
|
|
@@ -25,157 +28,400 @@ bun add honertia
|
|
|
25
28
|
```typescript
|
|
26
29
|
// src/index.ts
|
|
27
30
|
import { Hono } from 'hono'
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Database & Auth setup (your own middleware)
|
|
31
|
+
import { logger } from 'hono/logger'
|
|
32
|
+
import { setupHonertia, createTemplate, createVersion, registerErrorHandlers, vite } from 'honertia'
|
|
33
|
+
import { Context, Layer } from 'effect'
|
|
34
|
+
import manifest from '../dist/manifest.json'
|
|
35
|
+
|
|
36
|
+
import { createDb } from './db'
|
|
37
|
+
import type { Env } from './types'
|
|
38
|
+
import { createAuth } from './lib/auth'
|
|
39
|
+
import { registerRoutes } from './routes'
|
|
40
|
+
|
|
41
|
+
const app = new Hono<Env>()
|
|
42
|
+
const assetVersion = createVersion(manifest)
|
|
43
|
+
const entry = manifest['src/main.tsx']
|
|
44
|
+
const assetPath = (path: string) => `/${path}`
|
|
45
|
+
|
|
46
|
+
class BindingsService extends Context.Tag('app/Bindings')<
|
|
47
|
+
BindingsService,
|
|
48
|
+
{ KV: KVNamespace }
|
|
49
|
+
>() {}
|
|
50
|
+
|
|
51
|
+
// Request-scoped setup: put db/auth on c.var so Honertia/Effect can read them.
|
|
51
52
|
app.use('*', async (c, next) => {
|
|
52
53
|
c.set('db', createDb(c.env.DATABASE_URL))
|
|
53
|
-
c.set('auth', createAuth({
|
|
54
|
+
c.set('auth', createAuth({
|
|
55
|
+
db: c.var.db,
|
|
56
|
+
secret: c.env.BETTER_AUTH_SECRET,
|
|
57
|
+
baseURL: new URL(c.req.url).origin,
|
|
58
|
+
}))
|
|
54
59
|
await next()
|
|
55
60
|
})
|
|
56
61
|
|
|
57
|
-
//
|
|
58
|
-
app.use('*',
|
|
59
|
-
|
|
62
|
+
// Honertia bundles the core middleware + auth loading + Effect runtime setup.
|
|
63
|
+
app.use('*', setupHonertia<Env, BindingsService>({
|
|
64
|
+
honertia: {
|
|
65
|
+
// Use your asset manifest hash so Inertia reloads on deploy.
|
|
66
|
+
version: assetVersion,
|
|
67
|
+
render: createTemplate((ctx) => {
|
|
68
|
+
const isProd = ctx.env.ENVIRONMENT === 'production'
|
|
69
|
+
return {
|
|
70
|
+
title: 'My Web App',
|
|
71
|
+
scripts: isProd ? [assetPath(entry.file)] : [vite.script()],
|
|
72
|
+
styles: isProd ? (entry.css ?? []).map(assetPath) : [],
|
|
73
|
+
head: isProd ? '' : vite.hmrHead(),
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
effect: {
|
|
78
|
+
// Expose Cloudflare bindings to Effect handlers via a service layer.
|
|
79
|
+
services: (c) => Layer.succeed(BindingsService, {
|
|
80
|
+
KV: c.env.MY_KV,
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
// Optional: extra Hono middleware in the same chain.
|
|
84
|
+
middleware: [
|
|
85
|
+
logger(),
|
|
86
|
+
// register additional middleware here...
|
|
87
|
+
],
|
|
88
|
+
}))
|
|
60
89
|
|
|
61
|
-
|
|
62
|
-
app
|
|
90
|
+
registerRoutes(app)
|
|
91
|
+
registerErrorHandlers(app)
|
|
63
92
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
loginComponent: 'Auth/Login',
|
|
67
|
-
registerComponent: 'Auth/Register',
|
|
68
|
-
})
|
|
93
|
+
export default app
|
|
94
|
+
```
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
```typescript
|
|
97
|
+
// src/routes.ts
|
|
98
|
+
import type { Hono } from 'hono'
|
|
99
|
+
import type { Env } from './types'
|
|
100
|
+
import { effectRoutes } from 'honertia/effect'
|
|
101
|
+
import { effectAuthRoutes, RequireAuthLayer } from 'honertia/auth'
|
|
102
|
+
import { showDashboard, listProjects, createProject, showProject, deleteProject } from './actions'
|
|
103
|
+
|
|
104
|
+
export function registerRoutes(app: Hono<Env>) {
|
|
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).
|
|
107
|
+
effectAuthRoutes(app, {
|
|
108
|
+
loginComponent: 'Auth/Login',
|
|
109
|
+
registerComponent: 'Auth/Register',
|
|
76
110
|
})
|
|
77
111
|
|
|
78
|
-
|
|
112
|
+
// Effect routes give you typed, DI-friendly handlers (no direct Hono ctx).
|
|
113
|
+
effectRoutes(app)
|
|
114
|
+
.provide(RequireAuthLayer)
|
|
115
|
+
.group((route) => {
|
|
116
|
+
// Grouped routes share layers and path prefixes.
|
|
117
|
+
route.get('/', showDashboard) // GET example.com
|
|
118
|
+
|
|
119
|
+
route.prefix('/projects').group((route) => {
|
|
120
|
+
route.get('/', listProjects) // GET example.com/projects
|
|
121
|
+
route.post('/', createProject) // POST example.com/projects
|
|
122
|
+
route.get('/:id', showProject) // GET example.com/projects/2
|
|
123
|
+
route.delete('/:id', deleteProject) // DELETE example.com/projects/2
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
79
127
|
```
|
|
80
128
|
|
|
81
|
-
###
|
|
129
|
+
### Environment Variables
|
|
82
130
|
|
|
83
|
-
|
|
131
|
+
Honertia reads these from `c.env` (Cloudflare Workers bindings):
|
|
84
132
|
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
133
|
+
```toml
|
|
134
|
+
# wrangler.toml
|
|
135
|
+
ENVIRONMENT = "production"
|
|
136
|
+
```
|
|
88
137
|
|
|
89
|
-
|
|
138
|
+
If you prefer `wrangler.jsonc`, the same binding looks like:
|
|
90
139
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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,
|
|
98
184
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
185
|
+
resolve: {
|
|
186
|
+
alias: {
|
|
187
|
+
'~': path.resolve(__dirname, 'src'),
|
|
188
|
+
},
|
|
102
189
|
},
|
|
103
|
-
|
|
104
|
-
|
|
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()
|
|
105
227
|
},
|
|
106
|
-
})
|
|
228
|
+
setup({ el, App, props }) {
|
|
229
|
+
createRoot(el).render(<App {...props} />)
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
```
|
|
107
233
|
|
|
108
|
-
|
|
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
|
|
109
251
|
```
|
|
110
252
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- `shareAuthMiddleware()` - Shares auth state with pages
|
|
115
|
-
- `effectBridge()` - Sets up Effect runtime for each request
|
|
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`.
|
|
116
256
|
|
|
117
|
-
|
|
257
|
+
### Recommended File Structure
|
|
118
258
|
|
|
119
|
-
|
|
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
|
+
|
|
286
|
+
### Example Action
|
|
287
|
+
|
|
288
|
+
Here's the `listProjects` action referenced above:
|
|
120
289
|
|
|
121
290
|
```typescript
|
|
122
|
-
|
|
123
|
-
import {
|
|
291
|
+
// src/actions/projects/list.ts
|
|
292
|
+
import { Effect } from 'effect'
|
|
293
|
+
import { eq } from 'drizzle-orm'
|
|
294
|
+
import { DatabaseService, AuthUserService, render, type AuthUser } from 'honertia/effect'
|
|
295
|
+
import { schema, type Database, type Project } from '../../db'
|
|
124
296
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
297
|
+
interface ProjectsIndexProps {
|
|
298
|
+
projects: Project[]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const fetchProjects = (
|
|
302
|
+
db: Database,
|
|
303
|
+
user: AuthUser
|
|
304
|
+
): Effect.Effect<ProjectsIndexProps, Error, never> =>
|
|
305
|
+
Effect.tryPromise({
|
|
306
|
+
try: async () => {
|
|
307
|
+
const projects = await db.query.projects.findMany({
|
|
308
|
+
where: eq(schema.projects.userId, user.user.id),
|
|
309
|
+
orderBy: (projects, { desc }) => [desc(projects.createdAt)],
|
|
310
|
+
})
|
|
311
|
+
return { projects }
|
|
312
|
+
},
|
|
313
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error)),
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
export const listProjects = Effect.gen(function* () {
|
|
317
|
+
const db = yield* DatabaseService
|
|
318
|
+
const user = yield* AuthUserService
|
|
319
|
+
const props = yield* fetchProjects(db as Database, user)
|
|
320
|
+
return yield* render('Projects/Index', props)
|
|
321
|
+
})
|
|
322
|
+
```
|
|
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
|
|
136
329
|
```
|
|
137
330
|
|
|
138
|
-
|
|
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
|
+
```
|
|
139
405
|
|
|
140
|
-
|
|
406
|
+
### Vite Helpers
|
|
141
407
|
|
|
142
|
-
`
|
|
408
|
+
The `vite` helper provides dev/prod asset management:
|
|
143
409
|
|
|
144
410
|
```typescript
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
window.$RefreshReg$ = () => {}
|
|
150
|
-
window.$RefreshSig$ = () => (type) => type
|
|
151
|
-
window.__vite_plugin_react_preamble_installed__ = true
|
|
152
|
-
</script>
|
|
153
|
-
<script type="module" src="http://localhost:5173/@vite/client"></script>
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
app.use('*', setupHonertia({
|
|
157
|
-
honertia: {
|
|
158
|
-
version: '1.0.0',
|
|
159
|
-
render: createTemplate((ctx) => {
|
|
160
|
-
const isProd = ctx.env.ENVIRONMENT === 'production'
|
|
161
|
-
return {
|
|
162
|
-
title: 'My App',
|
|
163
|
-
scripts: isProd
|
|
164
|
-
? ['/assets/main.js']
|
|
165
|
-
: ['http://localhost:5173/src/main.tsx'],
|
|
166
|
-
head: isProd
|
|
167
|
-
? '<link rel="icon" href="/favicon.svg" />'
|
|
168
|
-
: `${viteHmrHead}<link rel="icon" href="/favicon.svg" />`,
|
|
169
|
-
}
|
|
170
|
-
}),
|
|
171
|
-
},
|
|
172
|
-
}))
|
|
411
|
+
import { vite } from 'honertia'
|
|
412
|
+
|
|
413
|
+
vite.script() // 'http://localhost:5173/src/main.tsx'
|
|
414
|
+
vite.hmrHead() // HMR preamble script tags for React Fast Refresh
|
|
173
415
|
```
|
|
174
416
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
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
|
|
179
425
|
|
|
180
426
|
## Core Concepts
|
|
181
427
|
|
|
@@ -237,6 +483,91 @@ Honertia provides these services via Effect's dependency injection:
|
|
|
237
483
|
| `RequestService` | Request context (params, query, body) |
|
|
238
484
|
| `ResponseFactoryService` | Response builders |
|
|
239
485
|
|
|
486
|
+
#### Custom Services
|
|
487
|
+
|
|
488
|
+
You can inject Cloudflare Worker bindings (KV, D1, Queues, Analytics Engine) as services using the `services` option in `setupHonertia`, `effectBridge`, or `effectRoutes`.
|
|
489
|
+
|
|
490
|
+
Choose the option that matches your setup:
|
|
491
|
+
|
|
492
|
+
- `setupHonertia`: recommended for most apps; keeps config in one place and applies services to every Effect handler.
|
|
493
|
+
- `effectBridge`: use when wiring middleware manually or when you need precise middleware ordering; applies services to all Effect handlers.
|
|
494
|
+
- `effectRoutes`: use when you want services scoped to a route group or different services per group.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
import { Effect, Layer, Context } from 'effect'
|
|
498
|
+
import { setupHonertia, effectBridge, effectRoutes } from 'honertia'
|
|
499
|
+
|
|
500
|
+
// Define your custom service
|
|
501
|
+
export class BindingsService extends Context.Tag('app/Bindings')<
|
|
502
|
+
BindingsService,
|
|
503
|
+
{
|
|
504
|
+
KV: KVNamespace
|
|
505
|
+
ANALYTICS: AnalyticsEngineDataset
|
|
506
|
+
DB: D1Database
|
|
507
|
+
}
|
|
508
|
+
>() {}
|
|
509
|
+
|
|
510
|
+
// Option 1: setupHonertia (global services via the one-liner setup)
|
|
511
|
+
app.use('*', setupHonertia<Env, BindingsService>({
|
|
512
|
+
honertia: {
|
|
513
|
+
version: '1.0.0',
|
|
514
|
+
render: (page) => JSON.stringify(page),
|
|
515
|
+
},
|
|
516
|
+
effect: {
|
|
517
|
+
services: (c) => Layer.succeed(BindingsService, {
|
|
518
|
+
KV: c.env.MY_KV,
|
|
519
|
+
ANALYTICS: c.env.ANALYTICS,
|
|
520
|
+
DB: c.env.DB,
|
|
521
|
+
}),
|
|
522
|
+
},
|
|
523
|
+
}))
|
|
524
|
+
|
|
525
|
+
// Option 2: effectBridge (manual middleware wiring)
|
|
526
|
+
app.use('*', effectBridge<Env, BindingsService>({
|
|
527
|
+
database: (c) => createDb(c.env.DATABASE_URL),
|
|
528
|
+
services: (c) => Layer.succeed(BindingsService, {
|
|
529
|
+
KV: c.env.MY_KV,
|
|
530
|
+
ANALYTICS: c.env.ANALYTICS,
|
|
531
|
+
DB: c.env.DB,
|
|
532
|
+
}),
|
|
533
|
+
}))
|
|
534
|
+
|
|
535
|
+
// Option 3: effectRoutes (scoped to a route group)
|
|
536
|
+
effectRoutes<Env, BindingsService>(app, {
|
|
537
|
+
services: (c) => Layer.succeed(BindingsService, {
|
|
538
|
+
KV: c.env.MY_KV,
|
|
539
|
+
ANALYTICS: c.env.ANALYTICS,
|
|
540
|
+
DB: c.env.DB,
|
|
541
|
+
}),
|
|
542
|
+
}).group((route) => {
|
|
543
|
+
route.get('/data', getDataFromKV)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
// Use the custom service in your actions
|
|
547
|
+
const getDataFromKV = Effect.gen(function* () {
|
|
548
|
+
const bindings = yield* BindingsService
|
|
549
|
+
const value = yield* Effect.tryPromise(() =>
|
|
550
|
+
bindings.KV.get('my-key')
|
|
551
|
+
)
|
|
552
|
+
return yield* json({ value })
|
|
553
|
+
})
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
You can provide multiple bindings in any option using `Layer.mergeAll` (for example, a `QueueService` tag for a queue binding):
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
app.use('*', effectBridge<Env, BindingsService | QueueService>({
|
|
560
|
+
services: (c) => Layer.mergeAll(
|
|
561
|
+
Layer.succeed(BindingsService, {
|
|
562
|
+
KV: c.env.MY_KV,
|
|
563
|
+
ANALYTICS: c.env.ANALYTICS,
|
|
564
|
+
DB: c.env.DB,
|
|
565
|
+
}),
|
|
566
|
+
Layer.succeed(QueueService, c.env.MY_QUEUE),
|
|
567
|
+
),
|
|
568
|
+
}))
|
|
569
|
+
```
|
|
570
|
+
|
|
240
571
|
### Routing
|
|
241
572
|
|
|
242
573
|
Use `effectRoutes` for Laravel-style route definitions:
|
|
@@ -487,6 +818,23 @@ effectAuthRoutes(app, {
|
|
|
487
818
|
})
|
|
488
819
|
```
|
|
489
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
|
+
|
|
490
838
|
## Action Factories
|
|
491
839
|
|
|
492
840
|
For common patterns, use action factories:
|
package/dist/effect/bridge.d.ts
CHANGED
|
@@ -8,9 +8,33 @@ import type { Context as HonoContext, MiddlewareHandler, Env } from 'hono';
|
|
|
8
8
|
import { DatabaseService, AuthService, AuthUserService, HonertiaService, RequestService, ResponseFactoryService } from './services.js';
|
|
9
9
|
/**
|
|
10
10
|
* Configuration for the Effect bridge.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam E - Hono environment type
|
|
13
|
+
* @typeParam CustomServices - Custom services provided via the `services` option
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Provide Cloudflare Worker bindings as a service
|
|
17
|
+
* effectBridge<Env, BindingsService>({
|
|
18
|
+
* database: (c) => createDb(c.env.DATABASE_URL),
|
|
19
|
+
* services: (c) => Layer.succeed(BindingsService, c.env),
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Provide multiple custom services
|
|
24
|
+
* effectBridge<Env, BindingsService | LoggerService>({
|
|
25
|
+
* services: (c) => Layer.mergeAll(
|
|
26
|
+
* Layer.succeed(BindingsService, c.env),
|
|
27
|
+
* Layer.succeed(LoggerService, createLogger(c)),
|
|
28
|
+
* ),
|
|
29
|
+
* })
|
|
11
30
|
*/
|
|
12
|
-
export interface EffectBridgeConfig<E extends Env> {
|
|
31
|
+
export interface EffectBridgeConfig<E extends Env, CustomServices = never> {
|
|
13
32
|
database?: (c: HonoContext<E>) => unknown;
|
|
33
|
+
/**
|
|
34
|
+
* Custom services to provide to all Effect handlers.
|
|
35
|
+
* Return a Layer that provides your custom services.
|
|
36
|
+
*/
|
|
37
|
+
services?: (c: HonoContext<E>) => Layer.Layer<CustomServices, never, never>;
|
|
14
38
|
}
|
|
15
39
|
/**
|
|
16
40
|
* Symbol for storing Effect runtime in Hono context.
|
|
@@ -27,7 +51,7 @@ declare module 'hono' {
|
|
|
27
51
|
/**
|
|
28
52
|
* Build the Effect layer from Hono context.
|
|
29
53
|
*/
|
|
30
|
-
export declare function buildContextLayer<E extends Env>(c: HonoContext<E>, config?: EffectBridgeConfig<E>): Layer.Layer<RequestService | ResponseFactoryService | HonertiaService | DatabaseService | AuthService | AuthUserService, never, never>;
|
|
54
|
+
export declare function buildContextLayer<E extends Env, CustomServices = never>(c: HonoContext<E>, config?: EffectBridgeConfig<E, CustomServices>): Layer.Layer<RequestService | ResponseFactoryService | HonertiaService | DatabaseService | AuthService | AuthUserService | CustomServices, never, never>;
|
|
31
55
|
/**
|
|
32
56
|
* Get the Effect runtime from Hono context.
|
|
33
57
|
*/
|
|
@@ -35,6 +59,6 @@ export declare function getEffectRuntime<E extends Env>(c: HonoContext<E>): Mana
|
|
|
35
59
|
/**
|
|
36
60
|
* Middleware that sets up the Effect runtime for each request.
|
|
37
61
|
*/
|
|
38
|
-
export declare function effectBridge<E extends Env>(config?: EffectBridgeConfig<E>): MiddlewareHandler<E>;
|
|
62
|
+
export declare function effectBridge<E extends Env, CustomServices = never>(config?: EffectBridgeConfig<E, CustomServices>): MiddlewareHandler<E>;
|
|
39
63
|
export {};
|
|
40
64
|
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/effect/bridge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAU,KAAK,EAAE,cAAc,EAAW,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC1E,OAAO,EACL,eAAe,EACf,WAAW,EACX,eAAe,EACf,eAAe,EACf,cAAc,EACd,sBAAsB,EAKvB,MAAM,eAAe,CAAA;AAEtB
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/effect/bridge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAU,KAAK,EAAE,cAAc,EAAW,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC1E,OAAO,EACL,eAAe,EACf,WAAW,EACX,eAAe,EACf,eAAe,EACf,cAAc,EACd,sBAAsB,EAKvB,MAAM,eAAe,CAAA;AAEtB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,GAAG,EAAE,cAAc,GAAG,KAAK;IACvE,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAA;IACzC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;CAC5E;AAED;;GAEG;AACH,QAAA,MAAM,cAAc,eAA0B,CAAA;AAE9C;;GAEG;AACH,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,CAAC,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC,cAAc,CAC5C,eAAe,GACf,WAAW,GACX,eAAe,GACf,eAAe,GACf,cAAc,GACd,sBAAsB,EACxB,KAAK,CACN,CAAA;KACF;CACF;AAqDD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAAE,cAAc,GAAG,KAAK,EACrE,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACjB,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,cAAc,CAAC,GAC7C,KAAK,CAAC,KAAK,CACV,cAAc,GACd,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,WAAW,GACX,eAAe,GACf,cAAc,EAChB,KAAK,EACL,KAAK,CACN,CA0CA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAChB,cAAc,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,SAAS,CAEvD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,GAAG,EAAE,cAAc,GAAG,KAAK,EAChE,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,cAAc,CAAC,GAC7C,iBAAiB,CAAC,CAAC,CAAC,CAetB"}
|
package/dist/effect/bridge.js
CHANGED
|
@@ -75,6 +75,11 @@ export function buildContextLayer(c, config) {
|
|
|
75
75
|
if (c.var?.authUser) {
|
|
76
76
|
baseLayer = Layer.merge(baseLayer, Layer.succeed(AuthUserService, c.var.authUser));
|
|
77
77
|
}
|
|
78
|
+
// Merge custom services if provided
|
|
79
|
+
if (config?.services) {
|
|
80
|
+
const customServicesLayer = config.services(c);
|
|
81
|
+
baseLayer = Layer.merge(baseLayer, customServicesLayer);
|
|
82
|
+
}
|
|
78
83
|
return baseLayer;
|
|
79
84
|
}
|
|
80
85
|
/**
|
package/dist/effect/routing.d.ts
CHANGED
|
@@ -21,25 +21,25 @@ export type BaseServices = RequestService | ResponseFactoryService | HonertiaSer
|
|
|
21
21
|
/**
|
|
22
22
|
* Effect Route Builder with layer composition.
|
|
23
23
|
*/
|
|
24
|
-
export declare class EffectRouteBuilder<E extends Env, ProvidedServices = never> {
|
|
24
|
+
export declare class EffectRouteBuilder<E extends Env, ProvidedServices = never, CustomServices = never> {
|
|
25
25
|
private readonly app;
|
|
26
26
|
private readonly layers;
|
|
27
27
|
private readonly pathPrefix;
|
|
28
28
|
private readonly bridgeConfig?;
|
|
29
|
-
constructor(app: Hono<E>, layers?: Layer.Layer<any, never, never>[], pathPrefix?: string, bridgeConfig?: EffectBridgeConfig<E> | undefined);
|
|
29
|
+
constructor(app: Hono<E>, layers?: Layer.Layer<any, never, never>[], pathPrefix?: string, bridgeConfig?: EffectBridgeConfig<E, CustomServices> | undefined);
|
|
30
30
|
/**
|
|
31
31
|
* Add a layer to provide services to all routes in this builder.
|
|
32
32
|
* The layer's error type must be handled by the effect bridge (AppError or subtype).
|
|
33
33
|
*/
|
|
34
|
-
provide<S, LayerErr extends AppError>(layer: Layer.Layer<S, LayerErr, never>): EffectRouteBuilder<E, ProvidedServices | S>;
|
|
34
|
+
provide<S, LayerErr extends AppError>(layer: Layer.Layer<S, LayerErr, never>): EffectRouteBuilder<E, ProvidedServices | S, CustomServices>;
|
|
35
35
|
/**
|
|
36
36
|
* Set path prefix for all routes in this builder.
|
|
37
37
|
*/
|
|
38
|
-
prefix(path: string): EffectRouteBuilder<E, ProvidedServices>;
|
|
38
|
+
prefix(path: string): EffectRouteBuilder<E, ProvidedServices, CustomServices>;
|
|
39
39
|
/**
|
|
40
40
|
* Create a nested group with the same configuration.
|
|
41
41
|
*/
|
|
42
|
-
group(callback: (route: EffectRouteBuilder<E, ProvidedServices>) => void): void;
|
|
42
|
+
group(callback: (route: EffectRouteBuilder<E, ProvidedServices, CustomServices>) => void): void;
|
|
43
43
|
/**
|
|
44
44
|
* Resolve the full path.
|
|
45
45
|
*/
|
|
@@ -51,27 +51,27 @@ export declare class EffectRouteBuilder<E extends Env, ProvidedServices = never>
|
|
|
51
51
|
/**
|
|
52
52
|
* Register a GET route.
|
|
53
53
|
*/
|
|
54
|
-
get<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
54
|
+
get<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
55
55
|
/**
|
|
56
56
|
* Register a POST route.
|
|
57
57
|
*/
|
|
58
|
-
post<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
58
|
+
post<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
59
59
|
/**
|
|
60
60
|
* Register a PUT route.
|
|
61
61
|
*/
|
|
62
|
-
put<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
62
|
+
put<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
63
63
|
/**
|
|
64
64
|
* Register a PATCH route.
|
|
65
65
|
*/
|
|
66
|
-
patch<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
66
|
+
patch<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
67
67
|
/**
|
|
68
68
|
* Register a DELETE route.
|
|
69
69
|
*/
|
|
70
|
-
delete<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
70
|
+
delete<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
71
71
|
/**
|
|
72
72
|
* Register a route for all HTTP methods.
|
|
73
73
|
*/
|
|
74
|
-
all<R extends BaseServices | ProvidedServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
74
|
+
all<R extends BaseServices | ProvidedServices | CustomServices>(path: string, effect: EffectHandler<R, AppError | Error>): void;
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Create an Effect route builder for an app.
|
|
@@ -86,5 +86,5 @@ export declare class EffectRouteBuilder<E extends Env, ProvidedServices = never>
|
|
|
86
86
|
* route.post('/projects', createProject)
|
|
87
87
|
* })
|
|
88
88
|
*/
|
|
89
|
-
export declare function effectRoutes<E extends Env>(app: Hono<E>, config?: EffectBridgeConfig<E>): EffectRouteBuilder<E, never>;
|
|
89
|
+
export declare function effectRoutes<E extends Env, CustomServices = never>(app: Hono<E>, config?: EffectBridgeConfig<E, CustomServices>): EffectRouteBuilder<E, never, CustomServices>;
|
|
90
90
|
//# sourceMappingURL=routing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/effect/routing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,KAAK,EAAE,IAAI,EAAqB,GAAG,EAAE,MAAM,MAAM,CAAA;AAExD,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EACL,eAAe,EACf,WAAW,EAEX,eAAe,EACf,cAAc,EACd,sBAAsB,EACvB,MAAM,eAAe,CAAA;AAEtB;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,SAAS,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,CACjG,QAAQ,GAAG,QAAQ,EACnB,CAAC,EACD,CAAC,CACF,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,cAAc,GACd,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,WAAW,CAAA;AAEf;;GAEG;AACH,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,GAAG,EACb,gBAAgB,GAAG,KAAK;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/effect/routing.ts"],"names":[],"mappings":"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"}
|
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
|
-
* ? [
|
|
33
|
-
* : [
|
|
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:
|
|
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 ? ['/
|
|
73
|
+
* scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
|
|
61
74
|
* ```
|
|
62
75
|
*/
|
|
63
76
|
script(entry?: string, port?: number): string;
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
-
* ? [
|
|
21
|
-
* : [
|
|
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, ''');
|
|
67
74
|
}
|
|
68
75
|
export function createVersion(manifest) {
|
|
69
|
-
const
|
|
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 ? ['/
|
|
136
|
+
* scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
|
|
111
137
|
* ```
|
|
112
138
|
*/
|
|
113
139
|
script(entry = '/src/main.tsx', port = 5173) {
|
package/dist/setup.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { type EffectBridgeConfig } from './effect/bridge.js';
|
|
|
10
10
|
/**
|
|
11
11
|
* Configuration for Honertia setup.
|
|
12
12
|
*/
|
|
13
|
-
export interface HonertiaSetupConfig<E extends Env = Env> {
|
|
13
|
+
export interface HonertiaSetupConfig<E extends Env = Env, CustomServices = never> {
|
|
14
14
|
/**
|
|
15
15
|
* Honertia core configuration.
|
|
16
16
|
*/
|
|
@@ -18,7 +18,7 @@ export interface HonertiaSetupConfig<E extends Env = Env> {
|
|
|
18
18
|
/**
|
|
19
19
|
* Effect bridge configuration (optional).
|
|
20
20
|
*/
|
|
21
|
-
effect?: EffectBridgeConfig<E>;
|
|
21
|
+
effect?: EffectBridgeConfig<E, CustomServices>;
|
|
22
22
|
/**
|
|
23
23
|
* Auth configuration (optional).
|
|
24
24
|
*/
|
|
@@ -53,7 +53,7 @@ export interface HonertiaSetupConfig<E extends Env = Env> {
|
|
|
53
53
|
* }))
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
|
-
export declare function setupHonertia<E extends Env>(config: HonertiaSetupConfig<E>): MiddlewareHandler<E>;
|
|
56
|
+
export declare function setupHonertia<E extends Env, CustomServices = never>(config: HonertiaSetupConfig<E, CustomServices>): MiddlewareHandler<E>;
|
|
57
57
|
/**
|
|
58
58
|
* Error handler configuration.
|
|
59
59
|
*/
|
package/dist/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE1E;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE1E;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG,EAAE,cAAc,GAAG,KAAK;IAC9E;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAA;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;IAE9C;;OAEG;IACH,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IAED;;;OAGG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;CACpC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,GAAG,EAAE,cAAc,GAAG,KAAK,EACjE,MAAM,EAAE,mBAAmB,CAAC,CAAC,EAAE,cAAc,CAAC,GAC7C,iBAAiB,CAAC,CAAC,CAAC,CAmBtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,GAAG,EAAE,MAAM,GAAE,kBAAuB;kBAQ3D,OAAO,CAAC,CAAC,CAAC;mBAOT,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC;EAU3C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,GAAG,EACjD,GAAG,EAAE;IAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAA;CAAE,EAC1E,MAAM,GAAE,kBAAuB,GAC9B,IAAI,CAIN"}
|