ts-procedures 7.1.2 → 7.3.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.
Files changed (41) hide show
  1. package/README.md +8 -0
  2. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +35 -0
  3. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +40 -0
  4. package/agent_config/claude-code/skills/ts-procedures/patterns.md +14 -0
  5. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +1 -0
  6. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/astro-catchall.md +23 -0
  7. package/agent_config/copilot/copilot-instructions.md +6 -0
  8. package/agent_config/cursor/cursorrules +6 -0
  9. package/build/implementations/http/astro/astro-context.d.ts +19 -0
  10. package/build/implementations/http/astro/astro-context.js +28 -0
  11. package/build/implementations/http/astro/astro-context.js.map +1 -0
  12. package/build/implementations/http/astro/create-handler.d.ts +26 -0
  13. package/build/implementations/http/astro/create-handler.js +28 -0
  14. package/build/implementations/http/astro/create-handler.js.map +1 -0
  15. package/build/implementations/http/astro/index.d.ts +3 -0
  16. package/build/implementations/http/astro/index.js +6 -0
  17. package/build/implementations/http/astro/index.js.map +1 -0
  18. package/build/implementations/http/astro/index.test.d.ts +1 -0
  19. package/build/implementations/http/astro/index.test.js +295 -0
  20. package/build/implementations/http/astro/index.test.js.map +1 -0
  21. package/build/implementations/http/astro/rewrite-request.d.ts +13 -0
  22. package/build/implementations/http/astro/rewrite-request.js +32 -0
  23. package/build/implementations/http/astro/rewrite-request.js.map +1 -0
  24. package/build/index.d.ts +10 -0
  25. package/build/index.js +12 -13
  26. package/build/index.js.map +1 -1
  27. package/build/index.test.js +107 -0
  28. package/build/index.test.js.map +1 -1
  29. package/docs/astro-adapter.md +227 -0
  30. package/docs/core.md +19 -0
  31. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +1396 -0
  32. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +254 -0
  33. package/package.json +8 -2
  34. package/src/implementations/http/astro/README.md +89 -0
  35. package/src/implementations/http/astro/astro-context.ts +34 -0
  36. package/src/implementations/http/astro/create-handler.ts +59 -0
  37. package/src/implementations/http/astro/index.test.ts +350 -0
  38. package/src/implementations/http/astro/index.ts +6 -0
  39. package/src/implementations/http/astro/rewrite-request.ts +31 -0
  40. package/src/index.test.ts +171 -0
  41. package/src/index.ts +27 -15
@@ -0,0 +1,254 @@
1
+ # Astro adapter for ts-procedures
2
+
3
+ **Date:** 2026-05-07
4
+ **Status:** Design — pending implementation
5
+ **Subpath export:** `ts-procedures/astro`
6
+
7
+ ## Problem
8
+
9
+ Downstream developers using Astro want to host ts-procedures handlers inside Astro server endpoints (`src/pages/**/*.ts`). Today they must hand-roll the bridge: take an Astro `APIRoute`, convert its `APIContext` into a `Request`, find a way to dispatch through their procedure factories, and translate the result back. Astro's filesystem-as-router model also resists the existing `register(...).build()` pattern, which produces one app for many routes.
10
+
11
+ The goal is a "drop in the entry point" adapter: a single catch-all Astro file delegates every procedure call to one or more already-built ts-procedures apps, with full access to Astro's per-request data (`locals`, `cookies`, `redirect`) inside procedure handlers.
12
+
13
+ ## Non-goals
14
+
15
+ - Express RPC support. Express uses Node `req`/`res`, not Web `Request`/`Response`. A bridge is non-trivial (body streams, hijacked sockets, `res.flushHeaders`); defer until requested.
16
+ - Native Astro builders. Mirroring `HonoAPIAppBuilder` as `AstroAPIAppBuilder` would duplicate path-matching, schema validation, error taxonomy, and DocRegistry plumbing. The catch-all pattern covers the request without that cost.
17
+ - Per-procedure file scaffolding (one Astro file per procedure). Out of scope for v1.
18
+ - DocRegistry / codegen integration. Users keep wiring `DocRegistry` against the same builders they pass to the adapter; the adapter returns no `.docs`.
19
+ - `dispatch: 'merge'` strategy. YAGNI — users who want one Hono app can pre-merge with `app.route(...)` themselves.
20
+
21
+ ## Approach
22
+
23
+ The adapter is a thin Web-fetch delegator. It accepts already-built Hono apps (from any of `HonoAPIAppBuilder`, `HonoRPCAppBuilder`, `HonoStreamAppBuilder`) and exposes Astro `APIRoute` exports the developer re-exports from a catch-all file.
24
+
25
+ ```
26
+ Browser → /api/users/123
27
+
28
+ Astro invokes ALL({ request, locals, cookies, params, redirect, url, ... })
29
+
30
+ Adapter:
31
+ 1. Strip pathPrefix from request URL (if configured) → `rewritten`
32
+ (when no prefix configured, `rewritten` IS `request`)
33
+ 2. astroContextMap.set(rewritten, apiContext) — WeakMap stash
34
+ 3. For each app in order: response = await app.fetch(rewritten)
35
+ — first response with status !== 404 is returned (a 500 from
36
+ app A stops dispatch; it is treated as a real answer, not a
37
+ miss). Only a literal 404 falls through to the next app.
38
+ 4. All apps 404 → new Response(null, { status: 404 })
39
+
40
+ Inside any factory-context closure:
41
+ getAstroContext(c) → reads c.req.raw → WeakMap lookup → APIContext
42
+ ```
43
+
44
+ The WeakMap is keyed by the `Request` object that Hono ends up seeing — i.e., the post-rewrite Request when `pathPrefix` is configured, and the original Request when it isn't. Hono exposes that same Request on `c.req.raw`, which is what `getAstroContext` reads. Stashing happens AFTER the rewrite so the key matches what Hono observes. Entries clear when the Request is GC'd — no manual cleanup, no leak.
45
+
46
+ ## Public API
47
+
48
+ Two exports:
49
+
50
+ ```ts
51
+ import type { Hono, Context as HonoContext } from 'hono'
52
+ import type { APIContext, APIRoute } from 'astro'
53
+
54
+ export type AstroAdapterConfig = {
55
+ /** One or more built Hono apps. Order matters in 'first-match' dispatch. */
56
+ apps: Hono | Hono[]
57
+
58
+ /**
59
+ * Path prefix matching the Astro catch-all mount point.
60
+ * For src/pages/api/[...rest].ts use '/api'. The adapter strips this
61
+ * before delegating, so Hono routes do NOT need to repeat the prefix.
62
+ *
63
+ * Normalization: leading and trailing slashes are optional; '/api',
64
+ * 'api', and '/api/' are equivalent.
65
+ */
66
+ pathPrefix?: string
67
+ }
68
+
69
+ export type AstroHandlers = {
70
+ ALL: APIRoute
71
+ GET: APIRoute
72
+ POST: APIRoute
73
+ PUT: APIRoute
74
+ PATCH: APIRoute
75
+ DELETE: APIRoute
76
+ HEAD: APIRoute
77
+ OPTIONS: APIRoute
78
+ }
79
+
80
+ export function createAstroHandler(config: AstroAdapterConfig): AstroHandlers
81
+
82
+ /**
83
+ * Read Astro's APIContext from inside a Hono factory-context closure.
84
+ * Throws if called outside an Astro request scope (e.g., if the Hono app
85
+ * is also being served outside the adapter and the closure ran there).
86
+ */
87
+ export function getAstroContext(c: HonoContext): APIContext
88
+ ```
89
+
90
+ `createAstroHandler` returns every method handler so the developer can destructure whichever they want. Most consumers use `ALL`. Per-method exports support cases where Astro's behavior diverges by method (e.g., `HEAD` auto-derived from `GET`).
91
+
92
+ ## Developer-facing usage
93
+
94
+ ```ts
95
+ // src/server/procedures/users.ts
96
+ import { Procedures } from 'ts-procedures'
97
+ import { Type } from 'typebox'
98
+
99
+ export const usersAPI = Procedures<{ db: Db; user: User | null }, APIConfig>()
100
+
101
+ usersAPI.Create('GetUser', {
102
+ path: '/users/:id',
103
+ method: 'get',
104
+ schema: {
105
+ input: { pathParams: Type.Object({ id: Type.String() }) },
106
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
107
+ },
108
+ }, async (ctx, { pathParams }) => {
109
+ return ctx.db.users.findOne(pathParams.id)
110
+ })
111
+ ```
112
+
113
+ ```ts
114
+ // src/server/api.ts
115
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
116
+ import { getAstroContext } from 'ts-procedures/astro'
117
+ import { usersAPI } from './procedures/users'
118
+ import { db } from './db'
119
+
120
+ export const apiApp = new HonoAPIAppBuilder()
121
+ .register(usersAPI, (c) => {
122
+ const astro = getAstroContext(c)
123
+ return { db, user: astro.locals.user ?? null }
124
+ })
125
+ .build()
126
+ ```
127
+
128
+ ```ts
129
+ // src/pages/api/[...rest].ts
130
+ import { createAstroHandler } from 'ts-procedures/astro'
131
+ import { apiApp } from '../../server/api'
132
+
133
+ export const { ALL } = createAstroHandler({
134
+ apps: apiApp,
135
+ pathPrefix: '/api',
136
+ })
137
+ ```
138
+
139
+ Multi-app variant:
140
+
141
+ ```ts
142
+ export const { ALL } = createAstroHandler({
143
+ apps: [apiApp, rpcApp, streamsApp],
144
+ pathPrefix: '/api',
145
+ })
146
+ ```
147
+
148
+ ## Path-prefix rewrite
149
+
150
+ ```ts
151
+ function stripPrefix(request: Request, prefix: string | undefined): Request | null {
152
+ if (!prefix) return request
153
+ const url = new URL(request.url)
154
+ const norm = '/' + prefix.replace(/^\/|\/$/g, '') // → '/api'
155
+ if (norm === '/') return request
156
+ if (!url.pathname.startsWith(norm)) return null // 404 fast-path
157
+ const rest = url.pathname.slice(norm.length)
158
+ url.pathname = rest === '' ? '/' : rest
159
+ return new Request(url, request) // copies method/headers/body/signal/duplex
160
+ }
161
+ ```
162
+
163
+ `new Request(url, request)` per the Web spec init pattern preserves method, headers, body stream, abort signal, and `duplex` mode. Body streams pass through; client disconnect aborts continue to fire on `c.req.raw.signal` inside Hono.
164
+
165
+ Edge cases:
166
+ - `pathPrefix` undefined → no rewrite
167
+ - `pathPrefix` normalizes to `'/'` → no rewrite (root mount)
168
+ - Request path doesn't start with the normalized prefix → adapter returns 404 without invoking any app
169
+ - Exact prefix match (`/api` with request `/api`) → rewrites to `/`
170
+
171
+ ## Streams & abort signals
172
+
173
+ Hono stream builders return `Response` with a `ReadableStream` body. Astro SSR forwards that body verbatim — no special handling.
174
+
175
+ `request.signal` flow on client disconnect:
176
+ ```
177
+ Browser closes EventSource
178
+ → Astro aborts request.signal
179
+ → rewriteRequest preserves signal via new Request(url, request)
180
+ → Hono's c.req.raw.signal === request.signal
181
+ → Stream builder's ctx.signal fires (reason 'client-disconnect')
182
+ ```
183
+
184
+ Same path the existing Hono stream tests cover; one new integration test wires Astro→Hono-stream end-to-end.
185
+
186
+ ## Error handling
187
+
188
+ The adapter performs no error handling of its own. Errors flow through whatever the underlying Hono builder is configured with — taxonomy, `onError`, `onRequestError`. The adapter returns whatever Response Hono produced.
189
+
190
+ The only Response the adapter constructs itself: a stock `new Response(null, { status: 404 })` returned when (a) the path-prefix rewrite fails (request path outside the mount), or (b) every registered app returned 404.
191
+
192
+ If `getAstroContext(c)` is called outside an Astro request (the Hono app is also being served via a non-Astro path and the closure ran there), it throws with a clear message. This propagates into Hono's normal error path and through the configured taxonomy.
193
+
194
+ ## File layout
195
+
196
+ ```
197
+ src/implementations/http/astro/
198
+ ├── README.md
199
+ ├── index.ts # public exports
200
+ ├── create-handler.ts # createAstroHandler, multi-app dispatch
201
+ ├── astro-context.ts # WeakMap + getAstroContext
202
+ ├── rewrite-request.ts # stripPrefix
203
+ └── index.test.ts # integration tests vs real Hono builders
204
+ ```
205
+
206
+ ## Tests
207
+
208
+ Integration-style, real builders, simulated `Request`:
209
+
210
+ 1. Single Hono API app — GET and POST roundtrip, request/response bodies intact, headers preserved
211
+ 2. Path prefix normalization: `/api`, `api`, `/api/`, exact-match → root, missing prefix → 404 short-circuit, no prefix configured, query string preserved through the rewrite (`/api/users?limit=10` → inner app sees `/users?limit=10`)
212
+ 3. `getAstroContext(c)` returns the same `APIContext` object passed to `ALL`
213
+ 4. Multi-app first-match: app A returns 404 → app B handles → app B's response returned
214
+ 5. Multi-app non-404 short-circuit: app A returns 500 → app B is NOT invoked; app A's 500 is returned
215
+ 6. All-404 fallback returns adapter's 404 (status 404, empty body), not Hono's
216
+ 7. Stream pass-through: `HonoStreamAppBuilder` + generator yielding 3 events; verify all 3 reach the consumer through the adapter
217
+ 8. Abort signal flow: simulate client disconnect by aborting the incoming `Request` mid-stream; assert `ctx.signal.aborted === true` inside the handler with `signal.reason !== 'stream-completed'`
218
+ 9. `getAstroContext` outside Astro scope throws a clear error
219
+
220
+ ## Package wiring
221
+
222
+ ```json
223
+ // package.json (additions)
224
+ "exports": {
225
+ "./astro": {
226
+ "types": "./build/implementations/http/astro/index.d.ts",
227
+ "import": "./build/implementations/http/astro/index.js"
228
+ }
229
+ },
230
+ "optionalDependencies": {
231
+ "ajsc": "...",
232
+ "express": "...",
233
+ "hono": "...",
234
+ "astro": "^5.0.0"
235
+ },
236
+ "devDependencies": {
237
+ "...": "...",
238
+ "astro": "^5.0.0"
239
+ }
240
+ ```
241
+
242
+ Matching the existing pattern for `hono` and `express`: listed as `optionalDependencies` so it isn't pulled into non-Astro deployments, and as `devDependencies` so types resolve at build time. Astro is imported via `import type` only — zero runtime dependency on the published package.
243
+
244
+ ## Agent config / docs
245
+
246
+ Mirror the pattern used when other builders shipped:
247
+ - New `docs/astro-adapter.md` end-to-end walkthrough
248
+ - Update `agent_config/claude-code/skills/ts-procedures/api-reference.md` and `patterns.md` to teach downstream Claude/Cursor/Copilot rules the Astro path
249
+ - New `agent_config/claude-code/skills/ts-procedures-scaffold/templates/astro-catchall.ts` template
250
+ - `src/implementations/http/astro/README.md` with usage and the prefix-semantics table
251
+
252
+ ## Open questions
253
+
254
+ None at design time. Ready for implementation planning.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-procedures",
3
- "version": "7.1.2",
3
+ "version": "7.3.0",
4
4
  "description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
5
5
  "main": "build/exports.js",
6
6
  "types": "build/exports.d.ts",
@@ -23,6 +23,10 @@
23
23
  "types": "./build/exports.d.ts",
24
24
  "import": "./build/exports.js"
25
25
  },
26
+ "./astro": {
27
+ "types": "./build/implementations/http/astro/index.d.ts",
28
+ "import": "./build/implementations/http/astro/index.js"
29
+ },
26
30
  "./http": {
27
31
  "types": "./build/implementations/types.d.ts"
28
32
  },
@@ -96,7 +100,8 @@
96
100
  "trpc"
97
101
  ],
98
102
  "optionalDependencies": {
99
- "ajsc": "7.2.0",
103
+ "ajsc": "^7.2.0",
104
+ "astro": "6.x.x || 7.x.x",
100
105
  "express": "^5.2.1",
101
106
  "hono": "^4.7.4"
102
107
  },
@@ -111,6 +116,7 @@
111
116
  "@eslint/js": "^9.17.0",
112
117
  "@types/express": "^5.0.6",
113
118
  "@types/supertest": "^7.2.0",
119
+ "astro": "^6.3.1",
114
120
  "eslint": "^9.17.0",
115
121
  "express": "^5.2.1",
116
122
  "hono": "^4.7.4",
@@ -0,0 +1,89 @@
1
+ # `ts-procedures/astro`
2
+
3
+ Drop one or more pre-built Hono apps (from `HonoAPIAppBuilder`, `HonoRPCAppBuilder`, or `HonoStreamAppBuilder`) into a single Astro catch-all endpoint. Procedure handlers can read Astro's per-request data (`locals`, `cookies`, `redirect`, `params`) inside their factory-context closures.
4
+
5
+ > **Requires SSR.** Your Astro project must use `output: 'server'` or `output: 'hybrid'` with `export const prerender = false` in the catch-all file. Static-prerender mode bypasses live endpoints.
6
+
7
+ ## Install
8
+
9
+ The adapter is part of `ts-procedures`. Astro is an optional peer; install it in your Astro app:
10
+
11
+ ```bash
12
+ npm install ts-procedures hono astro
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ // src/server/api.ts
19
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
20
+ import { getAstroContext } from 'ts-procedures/astro'
21
+ import { usersAPI } from './procedures/users'
22
+ import { db } from './db'
23
+
24
+ export const apiApp = new HonoAPIAppBuilder()
25
+ .register(usersAPI, (c) => {
26
+ const astro = getAstroContext(c)
27
+ return { db, user: astro.locals.user ?? null }
28
+ })
29
+ .build()
30
+ ```
31
+
32
+ ```ts
33
+ // src/pages/api/[...rest].ts
34
+ import { createAstroHandler } from 'ts-procedures/astro'
35
+ import { apiApp } from '../../server/api'
36
+
37
+ export const { ALL } = createAstroHandler({
38
+ apps: apiApp,
39
+ pathPrefix: '/api',
40
+ })
41
+ ```
42
+
43
+ ## Config reference
44
+
45
+ | Option | Type | Description |
46
+ | ------------ | --------------------- | -------------------------------------------------------------------------------------------- |
47
+ | `apps` | `Hono \| Hono[]` | One or more built Hono apps. Order matters in first-match dispatch. |
48
+ | `pathPrefix` | `string \| undefined` | Path prefix matching the Astro catch-all mount point. `/api`, `api`, `/api/` are equivalent. |
49
+
50
+ ## Path prefix semantics
51
+
52
+ The catch-all file is at `src/pages/api/[...rest].ts`, so Astro receives requests with paths like `/api/users/123`. The Hono builder routes you registered probably use just `/users/:id`. Configuring `pathPrefix: '/api'` strips the prefix before delegating — your Hono routes don't need to repeat the prefix.
53
+
54
+ | Request path | `pathPrefix` | Inner Hono sees |
55
+ | ---------------- | ------------ | --------------------- |
56
+ | `/api/users/123` | `'/api'` | `/users/123` |
57
+ | `/api` | `'/api'` | `/` |
58
+ | `/somewhere` | `'/api'` | (404, no app invoked) |
59
+ | `/users/123` | `undefined` | `/users/123` |
60
+
61
+ The prefix match is segment-aware: `/apikey/secret` does NOT match `pathPrefix: '/api'` and will return 404.
62
+
63
+ **Diagnostic:** if your routes 404 unexpectedly, the most common cause is a `pathPrefix` mismatch. The prefix MUST match the directory the catch-all file lives in. For `src/pages/api/[...rest].ts` use `pathPrefix: '/api'`; for `src/pages/v1/[...rest].ts` use `pathPrefix: '/v1'`.
64
+
65
+ ## Multi-app dispatch
66
+
67
+ Pass an array. The adapter tries each app in order:
68
+
69
+ ```ts
70
+ export const { ALL } = createAstroHandler({
71
+ apps: [apiApp, rpcApp, streamsApp],
72
+ pathPrefix: '/api',
73
+ })
74
+ ```
75
+
76
+ Dispatch rules:
77
+ - The first response with `status !== 404` is returned.
78
+ - A `500` from app A stops dispatch (it is treated as a real answer, not a miss).
79
+ - Only literal 404s fall through to the next app.
80
+ - All apps 404 → adapter returns its own `Response(null, { status: 404 })`.
81
+
82
+ ## Streams
83
+
84
+ `HonoStreamAppBuilder` returns a `Response` with a `ReadableStream` body. Astro SSR forwards that body verbatim — no additional configuration. Client disconnects abort `ctx.signal` inside the stream handler.
85
+
86
+ ## What's NOT included
87
+
88
+ - Express RPC support (Express uses Node `req`/`res`, not Web Fetch).
89
+ - DocRegistry coupling — wire `DocRegistry` against the same builders separately for client codegen.
@@ -0,0 +1,34 @@
1
+ import type { APIContext } from 'astro'
2
+ import type { Context as HonoContext } from 'hono'
3
+
4
+ const astroContextMap = new WeakMap<Request, APIContext>()
5
+
6
+ /**
7
+ * Internal — stash the Astro APIContext for the Request that Hono will receive.
8
+ *
9
+ * The key MUST be the post-rewrite Request (the one passed to `app.fetch`),
10
+ * because that is what Hono exposes via `c.req.raw` and what `getAstroContext`
11
+ * reads back. When no `pathPrefix` rewrite happens, this is the same Request
12
+ * that Astro originally invoked the adapter with.
13
+ */
14
+ export function setAstroContext(request: Request, apiContext: APIContext): void {
15
+ astroContextMap.set(request, apiContext)
16
+ }
17
+
18
+ /**
19
+ * Reads Astro's APIContext from inside a Hono factory-context closure.
20
+ *
21
+ * Throws when the request is not in scope — typically because the Hono app
22
+ * is being served outside the Astro adapter (e.g., bound to a Node listener
23
+ * or via a different adapter) and the closure was invoked there.
24
+ */
25
+ export function getAstroContext(c: HonoContext): APIContext {
26
+ const ctx = astroContextMap.get(c.req.raw)
27
+ if (!ctx) {
28
+ throw new Error(
29
+ 'getAstroContext was called outside an Astro request scope. ' +
30
+ 'Ensure this Hono app is being served via createAstroHandler when this closure runs.'
31
+ )
32
+ }
33
+ return ctx
34
+ }
@@ -0,0 +1,59 @@
1
+ import type { Hono } from 'hono'
2
+ import type { APIRoute } from 'astro'
3
+ import { setAstroContext } from './astro-context.js'
4
+ import { stripPrefix } from './rewrite-request.js'
5
+
6
+ export type AstroAdapterConfig = {
7
+ /** One or more built Hono apps. Order matters in first-match dispatch. */
8
+ apps: Hono | Hono[]
9
+ /**
10
+ * Path prefix matching the Astro catch-all mount point.
11
+ * For src/pages/api/[...rest].ts use '/api'. The adapter strips this
12
+ * before delegating, so Hono routes do NOT need to repeat the prefix.
13
+ *
14
+ * Normalization: leading and trailing slashes are optional; '/api',
15
+ * 'api', and '/api/' are equivalent. Undefined or '/' means no rewrite.
16
+ */
17
+ pathPrefix?: string
18
+ }
19
+
20
+ export type AstroHandlers = {
21
+ ALL: APIRoute
22
+ GET: APIRoute
23
+ POST: APIRoute
24
+ PUT: APIRoute
25
+ PATCH: APIRoute
26
+ DELETE: APIRoute
27
+ HEAD: APIRoute
28
+ OPTIONS: APIRoute
29
+ }
30
+
31
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const
32
+
33
+ export function createAstroHandler(config: AstroAdapterConfig): AstroHandlers {
34
+ const apps = Array.isArray(config.apps) ? config.apps : [config.apps]
35
+
36
+ if (apps.length === 0) {
37
+ throw new Error('createAstroHandler: `apps` must contain at least one Hono app.')
38
+ }
39
+
40
+ const ALL: APIRoute = async (apiContext) => {
41
+ const rewritten = stripPrefix(apiContext.request, config.pathPrefix)
42
+ if (rewritten === null) {
43
+ return new Response(null, { status: 404 })
44
+ }
45
+ setAstroContext(rewritten, apiContext)
46
+
47
+ for (const app of apps) {
48
+ const res = await app.fetch(rewritten)
49
+ if (res.status !== 404) return res
50
+ }
51
+ return new Response(null, { status: 404 })
52
+ }
53
+
54
+ const handlers = { ALL } as AstroHandlers
55
+ for (const method of HTTP_METHODS) {
56
+ handlers[method] = ALL
57
+ }
58
+ return handlers
59
+ }