ts-procedures 7.1.2 → 7.2.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 (32) hide show
  1. package/README.md +8 -0
  2. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +36 -0
  3. package/agent_config/claude-code/skills/ts-procedures/patterns.md +14 -0
  4. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +1 -0
  5. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/astro-catchall.md +23 -0
  6. package/agent_config/copilot/copilot-instructions.md +4 -0
  7. package/agent_config/cursor/cursorrules +4 -0
  8. package/build/implementations/http/astro/astro-context.d.ts +19 -0
  9. package/build/implementations/http/astro/astro-context.js +28 -0
  10. package/build/implementations/http/astro/astro-context.js.map +1 -0
  11. package/build/implementations/http/astro/create-handler.d.ts +26 -0
  12. package/build/implementations/http/astro/create-handler.js +28 -0
  13. package/build/implementations/http/astro/create-handler.js.map +1 -0
  14. package/build/implementations/http/astro/index.d.ts +3 -0
  15. package/build/implementations/http/astro/index.js +6 -0
  16. package/build/implementations/http/astro/index.js.map +1 -0
  17. package/build/implementations/http/astro/index.test.d.ts +1 -0
  18. package/build/implementations/http/astro/index.test.js +295 -0
  19. package/build/implementations/http/astro/index.test.js.map +1 -0
  20. package/build/implementations/http/astro/rewrite-request.d.ts +13 -0
  21. package/build/implementations/http/astro/rewrite-request.js +32 -0
  22. package/build/implementations/http/astro/rewrite-request.js.map +1 -0
  23. package/docs/astro-adapter.md +227 -0
  24. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +1396 -0
  25. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +254 -0
  26. package/package.json +8 -2
  27. package/src/implementations/http/astro/README.md +89 -0
  28. package/src/implementations/http/astro/astro-context.ts +34 -0
  29. package/src/implementations/http/astro/create-handler.ts +59 -0
  30. package/src/implementations/http/astro/index.test.ts +350 -0
  31. package/src/implementations/http/astro/index.ts +6 -0
  32. package/src/implementations/http/astro/rewrite-request.ts +31 -0
package/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  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 procedure documentation/configuration.
4
4
 
5
+ ## Goals of this Project
6
+
7
+ 1. Full type safety
8
+ 2. Auto generated documentation and client api spec
9
+ 3. Fast and performant
10
+ 4. Excellent DX (and agent documentation)
11
+
12
+
5
13
  ## Installation
6
14
 
7
15
  ```bash
@@ -1392,6 +1392,42 @@ type DefinitionInfo = {
1392
1392
 
1393
1393
  ---
1394
1394
 
1395
+ ## Astro adapter — `ts-procedures/astro`
1396
+
1397
+ For Astro apps, mount one or more pre-built Hono apps inside a single catch-all endpoint:
1398
+
1399
+ ```ts
1400
+ // src/pages/api/[...rest].ts
1401
+ import { createAstroHandler } from 'ts-procedures/astro'
1402
+ import { apiApp } from '../../server/api'
1403
+
1404
+ export const { ALL } = createAstroHandler({
1405
+ apps: apiApp,
1406
+ pathPrefix: '/api',
1407
+ })
1408
+ ```
1409
+
1410
+ Inside factory-context closures, read Astro's APIContext via `getAstroContext`:
1411
+
1412
+ ```ts
1413
+ import { getAstroContext } from 'ts-procedures/astro'
1414
+
1415
+ new HonoAPIAppBuilder()
1416
+ .register(usersAPI, (c) => {
1417
+ const astro = getAstroContext(c)
1418
+ return { db, user: astro.locals.user ?? null }
1419
+ })
1420
+ .build()
1421
+ ```
1422
+
1423
+ Multi-app: pass an array; first non-404 response wins. All-404 falls back to the adapter's own 404. Streams pass through unchanged.
1424
+
1425
+ Constraints:
1426
+ - Astro 5+ in SSR (or `prerender = false`).
1427
+ - Express builders are NOT supported by this adapter — Hono builders only.
1428
+
1429
+ ---
1430
+
1395
1431
  ## Imports
1396
1432
 
1397
1433
  ```typescript
@@ -1195,3 +1195,17 @@ const axiosAdapter: ClientAdapter = {
1195
1195
  },
1196
1196
  }
1197
1197
  ```
1198
+
1199
+ ---
1200
+
1201
+ ### Astro integration
1202
+
1203
+ When a downstream developer is building inside an Astro app, recommend the catch-all pattern:
1204
+
1205
+ 1. Build the Hono app ONCE in `src/server/api.ts` using `HonoAPIAppBuilder` / `HonoRPCAppBuilder` / `HonoStreamAppBuilder` exactly as you would for any other deployment.
1206
+ 2. Inside the factory-context closure, call `getAstroContext(c)` to read `locals`, `cookies`, `params`, etc.
1207
+ 3. Mount the built app(s) in a single catch-all file: `src/pages/api/[...rest].ts`. Use `pathPrefix: '/api'` so your Hono routes don't need to repeat the prefix.
1208
+
1209
+ Anti-pattern: don't create one Astro file per procedure — that loses the centralized factory wiring and forces the developer to repeat the context closure.
1210
+
1211
+ Anti-pattern: don't try to use `express-rpc` with the Astro adapter. The adapter only accepts Web-Fetch apps (Hono).
@@ -35,6 +35,7 @@ If either argument is missing, ask the user for `<type>` and `<Name>`.
35
35
  | `hono-stream` | `templates/hono-stream.md` | `{{Name}}.stream-rpc.ts`, `{{Name}}.stream-rpc.test.ts` |
36
36
  | `hono-api` | `templates/hono-api.md` | `{{Name}}.api.ts`, `{{Name}}.api.test.ts` |
37
37
  | `client` | `templates/client.md` | `{{Name}}.client.ts`, `{{Name}}.client.test.ts` |
38
+ | `astro-catchall` | `templates/astro-catchall.md` | `src/pages/api/[...rest].ts` |
38
39
 
39
40
  ## Rules
40
41
 
@@ -0,0 +1,23 @@
1
+ # Astro Catch-All Template
2
+
3
+ Catch-all Astro API route that serves every ts-procedures route from a single file.
4
+
5
+ ## Implementation — `src/pages/api/[...rest].ts`
6
+
7
+ ```typescript
8
+ // src/pages/api/[...rest].ts
9
+ //
10
+ // Catch-all entry point that serves every ts-procedures route from a single Astro file.
11
+ // Adjust `pathPrefix` to match the directory the file lives in (e.g., '/api' for
12
+ // src/pages/api/[...rest].ts; '/v1' for src/pages/v1/[...rest].ts).
13
+ //
14
+ // Build your Hono app(s) ONCE in a sibling module, then drop them in here.
15
+
16
+ import { createAstroHandler } from 'ts-procedures/astro'
17
+ import { apiApp } from '../../server/api' // ← your built Hono app(s)
18
+
19
+ export const { ALL } = createAstroHandler({
20
+ apps: apiApp,
21
+ pathPrefix: '/api',
22
+ })
23
+ ```
@@ -192,6 +192,10 @@ const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
192
192
  // GET /api/users/:id → 200, POST /api/users → 201
193
193
  ```
194
194
 
195
+ ## Astro adapter
196
+
197
+ Catch-all endpoint pattern. Build Hono apps once with HonoAPI/RPC/Stream builders, mount via `createAstroHandler` in `src/pages/api/[...rest].ts` with `pathPrefix: '/api'`. Read Astro context inside factory closures with `getAstroContext(c)`. Multi-app dispatch is first-non-404-wins. Express builders are not supported.
198
+
195
199
  ## Error Handling
196
200
 
197
201
  | Error Class | Trigger | HTTP Status |
@@ -192,6 +192,10 @@ const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
192
192
  // GET /api/users/:id → 200, POST /api/users → 201
193
193
  ```
194
194
 
195
+ ## Astro adapter
196
+
197
+ Catch-all endpoint pattern. Build Hono apps once with HonoAPI/RPC/Stream builders, mount via `createAstroHandler` in `src/pages/api/[...rest].ts` with `pathPrefix: '/api'`. Read Astro context inside factory closures with `getAstroContext(c)`. Multi-app dispatch is first-non-404-wins. Express builders are not supported.
198
+
195
199
  ## Error Handling
196
200
 
197
201
  | Error Class | Trigger | HTTP Status |
@@ -0,0 +1,19 @@
1
+ import type { APIContext } from 'astro';
2
+ import type { Context as HonoContext } from 'hono';
3
+ /**
4
+ * Internal — stash the Astro APIContext for the Request that Hono will receive.
5
+ *
6
+ * The key MUST be the post-rewrite Request (the one passed to `app.fetch`),
7
+ * because that is what Hono exposes via `c.req.raw` and what `getAstroContext`
8
+ * reads back. When no `pathPrefix` rewrite happens, this is the same Request
9
+ * that Astro originally invoked the adapter with.
10
+ */
11
+ export declare function setAstroContext(request: Request, apiContext: APIContext): void;
12
+ /**
13
+ * Reads Astro's APIContext from inside a Hono factory-context closure.
14
+ *
15
+ * Throws when the request is not in scope — typically because the Hono app
16
+ * is being served outside the Astro adapter (e.g., bound to a Node listener
17
+ * or via a different adapter) and the closure was invoked there.
18
+ */
19
+ export declare function getAstroContext(c: HonoContext): APIContext;
@@ -0,0 +1,28 @@
1
+ const astroContextMap = new WeakMap();
2
+ /**
3
+ * Internal — stash the Astro APIContext for the Request that Hono will receive.
4
+ *
5
+ * The key MUST be the post-rewrite Request (the one passed to `app.fetch`),
6
+ * because that is what Hono exposes via `c.req.raw` and what `getAstroContext`
7
+ * reads back. When no `pathPrefix` rewrite happens, this is the same Request
8
+ * that Astro originally invoked the adapter with.
9
+ */
10
+ export function setAstroContext(request, apiContext) {
11
+ astroContextMap.set(request, apiContext);
12
+ }
13
+ /**
14
+ * Reads Astro's APIContext from inside a Hono factory-context closure.
15
+ *
16
+ * Throws when the request is not in scope — typically because the Hono app
17
+ * is being served outside the Astro adapter (e.g., bound to a Node listener
18
+ * or via a different adapter) and the closure was invoked there.
19
+ */
20
+ export function getAstroContext(c) {
21
+ const ctx = astroContextMap.get(c.req.raw);
22
+ if (!ctx) {
23
+ throw new Error('getAstroContext was called outside an Astro request scope. ' +
24
+ 'Ensure this Hono app is being served via createAstroHandler when this closure runs.');
25
+ }
26
+ return ctx;
27
+ }
28
+ //# sourceMappingURL=astro-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astro-context.js","sourceRoot":"","sources":["../../../../src/implementations/http/astro/astro-context.ts"],"names":[],"mappings":"AAGA,MAAM,eAAe,GAAG,IAAI,OAAO,EAAuB,CAAA;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,UAAsB;IACtE,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,CAAc;IAC5C,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC3D,qFAAqF,CACxF,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Hono } from 'hono';
2
+ import type { APIRoute } from 'astro';
3
+ export type AstroAdapterConfig = {
4
+ /** One or more built Hono apps. Order matters in first-match dispatch. */
5
+ apps: Hono | Hono[];
6
+ /**
7
+ * Path prefix matching the Astro catch-all mount point.
8
+ * For src/pages/api/[...rest].ts use '/api'. The adapter strips this
9
+ * before delegating, so Hono routes do NOT need to repeat the prefix.
10
+ *
11
+ * Normalization: leading and trailing slashes are optional; '/api',
12
+ * 'api', and '/api/' are equivalent. Undefined or '/' means no rewrite.
13
+ */
14
+ pathPrefix?: string;
15
+ };
16
+ export type AstroHandlers = {
17
+ ALL: APIRoute;
18
+ GET: APIRoute;
19
+ POST: APIRoute;
20
+ PUT: APIRoute;
21
+ PATCH: APIRoute;
22
+ DELETE: APIRoute;
23
+ HEAD: APIRoute;
24
+ OPTIONS: APIRoute;
25
+ };
26
+ export declare function createAstroHandler(config: AstroAdapterConfig): AstroHandlers;
@@ -0,0 +1,28 @@
1
+ import { setAstroContext } from './astro-context.js';
2
+ import { stripPrefix } from './rewrite-request.js';
3
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
4
+ export function createAstroHandler(config) {
5
+ const apps = Array.isArray(config.apps) ? config.apps : [config.apps];
6
+ if (apps.length === 0) {
7
+ throw new Error('createAstroHandler: `apps` must contain at least one Hono app.');
8
+ }
9
+ const ALL = async (apiContext) => {
10
+ const rewritten = stripPrefix(apiContext.request, config.pathPrefix);
11
+ if (rewritten === null) {
12
+ return new Response(null, { status: 404 });
13
+ }
14
+ setAstroContext(rewritten, apiContext);
15
+ for (const app of apps) {
16
+ const res = await app.fetch(rewritten);
17
+ if (res.status !== 404)
18
+ return res;
19
+ }
20
+ return new Response(null, { status: 404 });
21
+ };
22
+ const handlers = { ALL };
23
+ for (const method of HTTP_METHODS) {
24
+ handlers[method] = ALL;
25
+ }
26
+ return handlers;
27
+ }
28
+ //# sourceMappingURL=create-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-handler.js","sourceRoot":"","sources":["../../../../src/implementations/http/astro/create-handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AA2BlD,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAU,CAAA;AAE1F,MAAM,UAAU,kBAAkB,CAAC,MAA0B;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAErE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;IACnF,CAAC;IAED,MAAM,GAAG,GAAa,KAAK,EAAE,UAAU,EAAE,EAAE;QACzC,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QACpE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC5C,CAAC;QACD,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;QAEtC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YACtC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,GAAG,CAAA;QACpC,CAAC;QACD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5C,CAAC,CAAA;IAED,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAmB,CAAA;IACzC,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QAClC,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAA;IACxB,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createAstroHandler } from './create-handler.js';
2
+ export type { AstroAdapterConfig, AstroHandlers } from './create-handler.js';
3
+ export { getAstroContext } from './astro-context.js';
@@ -0,0 +1,6 @@
1
+ // Public surface for ts-procedures/astro.
2
+ // Note: setAstroContext is intentionally NOT re-exported — it's an
3
+ // internal helper used only by createAstroHandler.
4
+ export { createAstroHandler } from './create-handler.js';
5
+ export { getAstroContext } from './astro-context.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/astro/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,mEAAmE;AACnE,mDAAmD;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,295 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { Hono } from 'hono';
3
+ import { Type } from 'typebox';
4
+ import { stripPrefix } from './rewrite-request.js';
5
+ import { setAstroContext, getAstroContext } from './astro-context.js';
6
+ import { createAstroHandler } from './create-handler.js';
7
+ import { Procedures } from '../../../index.js';
8
+ import { HonoAPIAppBuilder } from '../hono-api/index.js';
9
+ import { HonoStreamAppBuilder } from '../hono-stream/index.js';
10
+ // Minimal stand-in for Astro's APIContext for unit tests.
11
+ function fakeApiContext(overrides = {}) {
12
+ return {
13
+ locals: { user: { id: 'u-1' } },
14
+ cookies: {},
15
+ params: {},
16
+ request: new Request('https://example.test/'),
17
+ url: new URL('https://example.test/'),
18
+ redirect: () => new Response(null, { status: 302 }),
19
+ ...overrides,
20
+ };
21
+ }
22
+ function buildSimpleUserApi() {
23
+ const API = Procedures();
24
+ API.Create('GetUser', {
25
+ path: '/users/:id',
26
+ method: 'get',
27
+ schema: {
28
+ input: { pathParams: Type.Object({ id: Type.String() }) },
29
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
30
+ },
31
+ }, async (ctx, { pathParams }) => {
32
+ const u = ctx.db.get(pathParams.id);
33
+ if (!u)
34
+ throw new Error('not found');
35
+ return u;
36
+ });
37
+ const db = new Map([['1', { id: '1', name: 'Ada' }]]);
38
+ return new HonoAPIAppBuilder().register(API, () => ({ db })).build();
39
+ }
40
+ describe('stripPrefix', () => {
41
+ test('returns original request when prefix is undefined', () => {
42
+ const req = new Request('https://example.test/api/users/1');
43
+ expect(stripPrefix(req, undefined)).toBe(req);
44
+ });
45
+ test('returns original request when normalized prefix is "/"', () => {
46
+ const req = new Request('https://example.test/users/1');
47
+ expect(stripPrefix(req, '/')).toBe(req);
48
+ expect(stripPrefix(req, '')).toBe(req);
49
+ });
50
+ test('strips a leading-slash prefix', () => {
51
+ const req = new Request('https://example.test/api/users/1');
52
+ const out = stripPrefix(req, '/api');
53
+ expect(new URL(out.url).pathname).toBe('/users/1');
54
+ });
55
+ test('strips a no-slash prefix (normalized)', () => {
56
+ const req = new Request('https://example.test/api/users/1');
57
+ const out = stripPrefix(req, 'api');
58
+ expect(new URL(out.url).pathname).toBe('/users/1');
59
+ });
60
+ test('strips a trailing-slash prefix (normalized)', () => {
61
+ const req = new Request('https://example.test/api/users/1');
62
+ const out = stripPrefix(req, '/api/');
63
+ expect(new URL(out.url).pathname).toBe('/users/1');
64
+ });
65
+ test('exact-match prefix becomes "/"', () => {
66
+ const req = new Request('https://example.test/api');
67
+ const out = stripPrefix(req, '/api');
68
+ expect(new URL(out.url).pathname).toBe('/');
69
+ });
70
+ test('returns null when path does not start with prefix (404 short-circuit)', () => {
71
+ const req = new Request('https://example.test/other/users/1');
72
+ expect(stripPrefix(req, '/api')).toBeNull();
73
+ });
74
+ test('returns null when path shares prefix characters but not a segment boundary', () => {
75
+ // /apikey starts with /api but is not under /api — must NOT rewrite.
76
+ expect(stripPrefix(new Request('https://example.test/apikey/secret'), '/api')).toBeNull();
77
+ expect(stripPrefix(new Request('https://example.test/api-v2/x'), '/api')).toBeNull();
78
+ expect(stripPrefix(new Request('https://example.test/api_internal'), '/api')).toBeNull();
79
+ });
80
+ test('preserves the query string through the rewrite', () => {
81
+ const req = new Request('https://example.test/api/users?limit=10&tag=a&tag=b');
82
+ const out = stripPrefix(req, '/api');
83
+ const url = new URL(out.url);
84
+ expect(url.pathname).toBe('/users');
85
+ expect(url.search).toBe('?limit=10&tag=a&tag=b');
86
+ });
87
+ test('preserves the request method', () => {
88
+ const req = new Request('https://example.test/api/users', { method: 'POST' });
89
+ const out = stripPrefix(req, '/api');
90
+ expect(out.method).toBe('POST');
91
+ });
92
+ test('preserves headers', () => {
93
+ const req = new Request('https://example.test/api/users', {
94
+ method: 'POST',
95
+ headers: { 'X-Trace-Id': 'abc-123', 'Content-Type': 'application/json' },
96
+ });
97
+ const out = stripPrefix(req, '/api');
98
+ expect(out.headers.get('X-Trace-Id')).toBe('abc-123');
99
+ expect(out.headers.get('Content-Type')).toBe('application/json');
100
+ });
101
+ });
102
+ describe('astro-context', () => {
103
+ test('getAstroContext returns the APIContext stashed for the request seen by Hono', async () => {
104
+ const req = new Request('https://example.test/probe');
105
+ const apiContext = fakeApiContext();
106
+ setAstroContext(req, apiContext);
107
+ const app = new Hono();
108
+ let observed = null;
109
+ app.get('/probe', (c) => {
110
+ observed = getAstroContext(c);
111
+ return c.json({ ok: true });
112
+ });
113
+ const res = await app.fetch(req);
114
+ expect(res.status).toBe(200);
115
+ expect(observed).toBe(apiContext);
116
+ });
117
+ test('getAstroContext throws when called outside an Astro request scope', async () => {
118
+ const app = new Hono();
119
+ let captured = null;
120
+ app.get('/probe', (c) => {
121
+ try {
122
+ getAstroContext(c);
123
+ }
124
+ catch (err) {
125
+ captured = err;
126
+ }
127
+ return c.json({ ok: true });
128
+ });
129
+ await app.fetch(new Request('https://example.test/probe'));
130
+ expect(captured).toBeInstanceOf(Error);
131
+ expect(captured.message).toMatch(/outside an Astro request scope/i);
132
+ });
133
+ });
134
+ describe('createAstroHandler — single app, no prefix', () => {
135
+ test('exports ALL plus the seven HTTP method handlers', () => {
136
+ const handlers = createAstroHandler({ apps: buildSimpleUserApi() });
137
+ expect(typeof handlers.ALL).toBe('function');
138
+ for (const m of ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']) {
139
+ expect(typeof handlers[m]).toBe('function');
140
+ }
141
+ });
142
+ test('GET roundtrips through the underlying Hono app', async () => {
143
+ const { ALL } = createAstroHandler({ apps: buildSimpleUserApi() });
144
+ const apiContext = fakeApiContext({
145
+ request: new Request('https://example.test/users/1'),
146
+ url: new URL('https://example.test/users/1'),
147
+ });
148
+ const res = await ALL(apiContext);
149
+ expect(res.status).toBe(200);
150
+ expect(await res.json()).toEqual({ id: '1', name: 'Ada' });
151
+ });
152
+ });
153
+ describe('createAstroHandler — pathPrefix + getAstroContext', () => {
154
+ test('strips pathPrefix before delegating', async () => {
155
+ // Underlying Hono app has no /api in its routes.
156
+ const app = buildSimpleUserApi();
157
+ const { ALL } = createAstroHandler({ apps: app, pathPrefix: '/api' });
158
+ const apiContext = fakeApiContext({
159
+ request: new Request('https://example.test/api/users/1'),
160
+ url: new URL('https://example.test/api/users/1'),
161
+ });
162
+ const res = await ALL(apiContext);
163
+ expect(res.status).toBe(200);
164
+ expect(await res.json()).toEqual({ id: '1', name: 'Ada' });
165
+ });
166
+ test('returns 404 directly when request path is outside the prefix', async () => {
167
+ const { ALL } = createAstroHandler({ apps: buildSimpleUserApi(), pathPrefix: '/api' });
168
+ const apiContext = fakeApiContext({
169
+ request: new Request('https://example.test/somewhere-else'),
170
+ url: new URL('https://example.test/somewhere-else'),
171
+ });
172
+ const res = await ALL(apiContext);
173
+ expect(res.status).toBe(404);
174
+ });
175
+ test('factory closure can read APIContext via getAstroContext', async () => {
176
+ const API = Procedures();
177
+ API.Create('WhoAmI', { path: '/whoami', method: 'get', schema: { returnType: Type.Object({ userId: Type.Union([Type.String(), Type.Null()]) }) } }, async (ctx) => ({ userId: ctx.user?.id ?? null }));
178
+ const app = new HonoAPIAppBuilder()
179
+ .register(API, (c) => {
180
+ const astro = getAstroContext(c);
181
+ return { user: astro.locals.user ?? null };
182
+ })
183
+ .build();
184
+ const { ALL } = createAstroHandler({ apps: app });
185
+ const apiContext = fakeApiContext({
186
+ locals: { user: { id: 'u-42' } },
187
+ request: new Request('https://example.test/whoami'),
188
+ url: new URL('https://example.test/whoami'),
189
+ });
190
+ const res = await ALL(apiContext);
191
+ expect(res.status).toBe(200);
192
+ expect(await res.json()).toEqual({ userId: 'u-42' });
193
+ });
194
+ });
195
+ describe('createAstroHandler — multi-app dispatch', () => {
196
+ function makeAppWithRoute(path, status, body) {
197
+ const app = new Hono();
198
+ app.all(path, (c) => c.json(body, status));
199
+ return app;
200
+ }
201
+ test('first 404 falls through to the second app', async () => {
202
+ const a = makeAppWithRoute('/only-a', 200, { from: 'a' });
203
+ const b = makeAppWithRoute('/only-b', 200, { from: 'b' });
204
+ const { ALL } = createAstroHandler({ apps: [a, b] });
205
+ const apiContext = fakeApiContext({
206
+ request: new Request('https://example.test/only-b'),
207
+ url: new URL('https://example.test/only-b'),
208
+ });
209
+ const res = await ALL(apiContext);
210
+ expect(res.status).toBe(200);
211
+ expect(await res.json()).toEqual({ from: 'b' });
212
+ });
213
+ test('a non-404 response from the first app short-circuits dispatch', async () => {
214
+ const a = makeAppWithRoute('/error', 500, { boom: true });
215
+ const b = makeAppWithRoute('/error', 200, { from: 'b' });
216
+ const { ALL } = createAstroHandler({ apps: [a, b] });
217
+ const apiContext = fakeApiContext({
218
+ request: new Request('https://example.test/error'),
219
+ url: new URL('https://example.test/error'),
220
+ });
221
+ const res = await ALL(apiContext);
222
+ expect(res.status).toBe(500);
223
+ expect(await res.json()).toEqual({ boom: true });
224
+ });
225
+ test('all-404 returns the adapter\'s own 404 with empty body', async () => {
226
+ const a = makeAppWithRoute('/x', 200, { from: 'a' });
227
+ const b = makeAppWithRoute('/y', 200, { from: 'b' });
228
+ const { ALL } = createAstroHandler({ apps: [a, b] });
229
+ const apiContext = fakeApiContext({
230
+ request: new Request('https://example.test/nope'),
231
+ url: new URL('https://example.test/nope'),
232
+ });
233
+ const res = await ALL(apiContext);
234
+ expect(res.status).toBe(404);
235
+ expect(await res.text()).toBe('');
236
+ });
237
+ });
238
+ describe('createAstroHandler — streams', () => {
239
+ test('SSE events from a HonoStream builder pass through the adapter', async () => {
240
+ const STREAM = Procedures();
241
+ STREAM.CreateStream('Counter', {
242
+ scope: 'counter',
243
+ version: 1,
244
+ schema: {
245
+ yieldType: Type.Object({ n: Type.Number() }),
246
+ },
247
+ }, async function* () {
248
+ yield { n: 1 };
249
+ yield { n: 2 };
250
+ yield { n: 3 };
251
+ });
252
+ const streamApp = new HonoStreamAppBuilder().register(STREAM, () => ({})).build();
253
+ const { ALL } = createAstroHandler({ apps: streamApp });
254
+ const apiContext = fakeApiContext({
255
+ request: new Request('https://example.test/counter/counter/1', { method: 'GET' }),
256
+ url: new URL('https://example.test/counter/counter/1'),
257
+ });
258
+ const res = await ALL(apiContext);
259
+ expect(res.status).toBe(200);
260
+ expect(res.headers.get('content-type')).toContain('text/event-stream');
261
+ // Drain the SSE body and assert all three events appear in order.
262
+ const text = await res.text();
263
+ expect(text).toContain('event: Counter');
264
+ expect(text).toContain('data: {"n":1}');
265
+ expect(text).toContain('data: {"n":2}');
266
+ expect(text).toContain('data: {"n":3}');
267
+ });
268
+ });
269
+ describe('createAstroHandler — abort signal', () => {
270
+ test("aborting the incoming request aborts the procedure handler's ctx.signal", async () => {
271
+ let observedSignal;
272
+ const aborted = new Promise((resolve) => {
273
+ const API = Procedures();
274
+ API.Create('Hang', { path: '/hang', method: 'get', schema: { returnType: Type.Object({ ok: Type.Boolean() }) } }, async (ctx) => {
275
+ observedSignal = ctx.signal;
276
+ ctx.signal?.addEventListener('abort', () => resolve(), { once: true });
277
+ // Wait for abort or a generous timeout.
278
+ await new Promise((r) => setTimeout(r, 200));
279
+ return { ok: true };
280
+ });
281
+ const app = new HonoAPIAppBuilder().register(API, () => ({})).build();
282
+ const { ALL } = createAstroHandler({ apps: app });
283
+ const controller = new AbortController();
284
+ const apiContext = fakeApiContext({
285
+ request: new Request('https://example.test/hang', { signal: controller.signal }),
286
+ url: new URL('https://example.test/hang'),
287
+ });
288
+ ALL(apiContext).catch(() => { });
289
+ setTimeout(() => controller.abort(), 20);
290
+ });
291
+ await aborted;
292
+ expect(observedSignal?.aborted).toBe(true);
293
+ });
294
+ });
295
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../../src/implementations/http/astro/index.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns a Request with `prefix` stripped from the URL pathname.
3
+ *
4
+ * - `prefix` undefined or normalizing to '/' → returns the original request unchanged.
5
+ * - Path does not start with the normalized prefix at a segment boundary → returns `null`
6
+ * (caller short-circuits to 404). E.g. with prefix '/api', the request '/apikey'
7
+ * returns null because 'key' is not preceded by a path separator.
8
+ * - Otherwise → returns a new Request with the rewritten URL and method/headers/body/signal/duplex
9
+ * preserved via `new Request(url, init)`.
10
+ *
11
+ * Normalization: leading and trailing slashes are optional. '/api', 'api', and '/api/' are equivalent.
12
+ */
13
+ export declare function stripPrefix(request: Request, prefix: string | undefined): Request | null;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Returns a Request with `prefix` stripped from the URL pathname.
3
+ *
4
+ * - `prefix` undefined or normalizing to '/' → returns the original request unchanged.
5
+ * - Path does not start with the normalized prefix at a segment boundary → returns `null`
6
+ * (caller short-circuits to 404). E.g. with prefix '/api', the request '/apikey'
7
+ * returns null because 'key' is not preceded by a path separator.
8
+ * - Otherwise → returns a new Request with the rewritten URL and method/headers/body/signal/duplex
9
+ * preserved via `new Request(url, init)`.
10
+ *
11
+ * Normalization: leading and trailing slashes are optional. '/api', 'api', and '/api/' are equivalent.
12
+ */
13
+ export function stripPrefix(request, prefix) {
14
+ if (prefix === undefined)
15
+ return request;
16
+ const normalized = '/' + prefix.replace(/^\/|\/$/g, '');
17
+ if (normalized === '/')
18
+ return request;
19
+ const url = new URL(request.url);
20
+ if (!url.pathname.startsWith(normalized))
21
+ return null;
22
+ // Segment-boundary guard: the character right after the prefix in the path
23
+ // MUST be either undefined (exact match) or '/' (real segment boundary).
24
+ // Without this, '/apikey' would falsely match prefix '/api'.
25
+ const nextChar = url.pathname[normalized.length];
26
+ if (nextChar !== undefined && nextChar !== '/')
27
+ return null;
28
+ const rest = url.pathname.slice(normalized.length);
29
+ url.pathname = rest === '' ? '/' : rest;
30
+ return new Request(url, request);
31
+ }
32
+ //# sourceMappingURL=rewrite-request.js.map