@boon4681/giri 0.0.1-hotfix-1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A stupid attempt from a stupid man who lack of foresight trying to make a backend framework.
4
4
 
5
+ ![NPM Version](https://img.shields.io/npm/v/%40boon4681%2Fgiri)
6
+
5
7
  <img width="128" src="https://raw.githubusercontent.com/boon4681/giri/refs/heads/main/.image/logo.png" />
6
8
 
7
9
  ## F*CK NPM PUBLISH i have to change this package name from `guri` to `giri` to `@boon4681/giri` because of the name collision and there is no contact about request a package name that hit npm stupid filter.
@@ -17,7 +19,7 @@ Because I can, and I am too lazy to write an OpenAPI spec. Write handlers, retur
17
19
  yarn add @boon4681/giri hono @hono/node-server zod
18
20
  ```
19
21
 
20
- `hono`, `@hono/node-server`, `zod`, `valibot`, and `typescript` are optional peers install
22
+ `hono`, `@hono/node-server`, `zod`, `valibot`, and `typescript` are optional peers - install
21
23
  only what you use.
22
24
 
23
25
  ## Quick start
@@ -36,7 +38,7 @@ curl http://localhost:3000/
36
38
 
37
39
  ## Config
38
40
 
39
- `giri.config.ts` is declarative it is loaded at build time, so keep it cheap and free of
41
+ `giri.config.ts` is declarative - it is loaded at build time, so keep it cheap and free of
40
42
  side effects (no DB drivers here; see [Lifecycle](#lifecycle)).
41
43
 
42
44
  ```ts
@@ -71,7 +73,7 @@ src/routes/
71
73
  ```
72
74
 
73
75
  - `[id]` folder becomes the param `:id`; params nest down the path.
74
- - Files without a `+` prefix are not routes colocate helpers freely.
76
+ - Files without a `+` prefix are not routes - colocate helpers freely.
75
77
 
76
78
  A verb file has one shape: the `handle` named export is the handler. Everything else is an
77
79
  optional named export, so the trivial case is one line and complexity is additive.
@@ -93,12 +95,12 @@ export const handle: Handle = (c) => {
93
95
 
94
96
  Giri owns `c`, so the return type is the schema on every backend:
95
97
 
96
- - `c.json(data, status?)` / `c.text(text, status?)` return value carries the status in its type.
97
- - `c.params` typed from the folder path.
98
- - `c.req.valid("body" | "query")` parsed, typed input (see below).
98
+ - `c.json(data, status?)` / `c.text(text, status?)` - return value carries the status in its type.
99
+ - `c.params` - typed from the folder path.
100
+ - `c.req.valid("body" | "query")` - parsed, typed input (see below).
99
101
  - `c.req.header(name)`, `c.req.url`, etc.
100
- - `c.get(key)` / `c.set(key, value)` per-request vars from middleware.
101
- - `c.app` app-wide services from `src/main.ts` `init()` (see [Lifecycle](#lifecycle)).
102
+ - `c.get(key)` / `c.set(key, value)` - per-request vars from middleware.
103
+ - `c.app` - app-wide services from `src/main.ts` `init()` (see [Lifecycle](#lifecycle)).
102
104
 
103
105
  ## Inputs
104
106
 
@@ -130,8 +132,8 @@ runtime. An unwrapped schema is rejected at build time.
130
132
 
131
133
  Middleware use giri's `(c, next)` shape and live in two places:
132
134
 
133
- - **Broad:** `export const middleware` in a folder's `+shared.ts` applies to the whole subtree.
134
- - **Precise:** `export const middleware` in a verb file applies to that one verb.
135
+ - **Broad:** `export const middleware` in a folder's `+shared.ts` - applies to the whole subtree.
136
+ - **Precise:** `export const middleware` in a verb file - applies to that one verb.
135
137
 
136
138
  Use `stack(...)` instead of a plain array so injected vars keep their types and propagate to
137
139
  downstream handlers. Run order: inherited `+shared.ts` (root to leaf), then the verb's
@@ -151,7 +153,7 @@ export const middleware = stack(requestId);
151
153
  // every handler below now sees c.get("requestId"): string
152
154
  ```
153
155
 
154
- Tag a middleware with `defineMiddleware` to feed OpenAPI security automatically a route that
156
+ Tag a middleware with `defineMiddleware` to feed OpenAPI security automatically - a route that
155
157
  uses it shows the scheme, a public route does not.
156
158
 
157
159
  ```ts
@@ -177,7 +179,7 @@ or a `+shared.ts`). Hidden routes still serve normally.
177
179
 
178
180
  ## Lifecycle
179
181
 
180
- `src/main.ts` is the optional home for imperative startup opening pools, validating env,
182
+ `src/main.ts` is the optional home for imperative startup - opening pools, validating env,
181
183
  graceful shutdown. Giri owns the serve and calls these hooks; the adapter still binds the port.
182
184
 
183
185
  ```ts
@@ -185,7 +187,7 @@ graceful shutdown. Giri owns the serve and calls these hooks; the adapter still
185
187
  import type { Services } from "@boon4681/giri";
186
188
 
187
189
  export const init = () => {
188
- // leave init unannotated its return type is the source of truth for c.app
190
+ // leave init unannotated - its return type is the source of truth for c.app
189
191
  return { db: connectDb(process.env.DATABASE_URL) };
190
192
  };
191
193
 
@@ -196,7 +198,7 @@ export const teardown = (services: Services) => {
196
198
 
197
199
  Flow: load `main.ts` -> `await init()` -> hold services -> adapter serves -> `teardown` on
198
200
  exit. `init` runs once and is not re-run on watch rebuilds. The returned object reaches every
199
- handler as a typed `c.app`, inferred from `init`'s return no declaration needed.
201
+ handler as a typed `c.app`, inferred from `init`'s return - no declaration needed.
200
202
 
201
203
  ## CLI
202
204
 
@@ -205,14 +207,14 @@ handler as a typed `c.app`, inferred from `init`'s return — no declaration nee
205
207
  | `giri init` | Scaffold `giri.config.ts`, a starter route, tsconfig paths, and `.gitignore`. |
206
208
  | `giri sync` | Scan `src/routes` and regenerate `.giri/` (manifest, param types, `openapi.json`). |
207
209
  | `giri serve` | `sync`, run `init()`, then serve via the adapter. Watches `src/` and re-syncs. |
208
- | `giri build` | Planned currently a no-op. |
210
+ | `giri build` | Planned - currently a no-op. |
209
211
 
210
212
  `giri serve` flags: `--port <n>`, `--host <addr>`, `--no-watch`.
211
213
 
212
214
  ## Generated output (`.giri/`)
213
215
 
214
216
  Everything derived lives in `.giri/` at the project root: param `.d.ts` per route, the route
215
- manifest, and the assembled `openapi.json`. It is gitignored and rebuilt on demand never edit
217
+ manifest, and the assembled `openapi.json`. It is gitignored and rebuilt on demand - never edit
216
218
  it, only import from it.
217
219
 
218
220
  ## Example
@@ -1,7 +1,30 @@
1
- import { Hono } from 'hono';
2
- import { G as GiriAdapter } from '../types-BrUMxh5u.js';
1
+ import { ContextVariableMap, Hono, MiddlewareHandler } from 'hono';
2
+ import { M as Middleware, V as ValidatedInput, G as GiriAdapter } from '../types-DkrKD1S4.js';
3
3
 
4
4
  type HonoGiriApp = Hono;
5
+ type HonoContextVars = {
6
+ [K in keyof ContextVariableMap]: ContextVariableMap[K];
7
+ };
8
+ /**
9
+ * Wrap one or more native Hono middleware as a single giri `Middleware`, so the existing Hono
10
+ * ecosystem (`@hono/oauth-providers`, CORS, etc.) runs unchanged on a giri route:
11
+ *
12
+ * ```ts
13
+ * // routes/auth/google/+shared.ts
14
+ * import { fromHono } from "@boon4681/giri/adapters/hono";
15
+ * import { googleAuth } from "@hono/oauth-providers/google";
16
+ *
17
+ * export const middleware = stack(
18
+ * fromHono(googleAuth({ client_id: …, client_secret: …, scope: ["openid", "email"] })),
19
+ * );
20
+ * // downstream handler: const user = c.get("user-google");
21
+ * ```
22
+ *
23
+ * It runs the Hono middleware against the real Hono context (cookies, `c.redirect`, `c.req.query`
24
+ * all work), then mirrors any vars it set onto giri's `c` for downstream `c.get`. Only valid on the
25
+ * Hono adapter - throws on any other backend.
26
+ */
27
+ declare function fromHono<Vars extends Record<string, unknown> = HonoContextVars>(...handlers: MiddlewareHandler[]): Middleware<Record<string, string>, ValidatedInput, Vars>;
5
28
  declare function hono(): GiriAdapter<HonoGiriApp>;
6
29
 
7
- export { type HonoGiriApp, hono };
30
+ export { type HonoContextVars, type HonoGiriApp, fromHono, hono };
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/adapters/hono.ts
21
21
  var hono_exports = {};
22
22
  __export(hono_exports, {
23
+ fromHono: () => fromHono,
23
24
  hono: () => hono
24
25
  });
25
26
  module.exports = __toCommonJS(hono_exports);
@@ -28,6 +29,7 @@ var import_hono = require("hono");
28
29
 
29
30
  // src/types.ts
30
31
  var typedResponseBrand = /* @__PURE__ */ Symbol.for("giri.typed-response");
32
+ var nativeContextBrand = /* @__PURE__ */ Symbol.for("giri.native-context");
31
33
  var inputSchemaBrand = /* @__PURE__ */ Symbol.for("giri.input-schema");
32
34
 
33
35
  // src/context.ts
@@ -48,7 +50,7 @@ function createContext(options) {
48
50
  const url = new URL(options.request.url);
49
51
  const store = /* @__PURE__ */ new Map();
50
52
  const validated = options.validated ?? {};
51
- return {
53
+ const context = {
52
54
  params: options.params ?? {},
53
55
  app: options.app ?? {},
54
56
  req: {
@@ -74,6 +76,8 @@ function createContext(options) {
74
76
  json: (data, status = 200, headers) => createTypedResponse(data, status, "json", headers),
75
77
  text: (text, status = 200, headers) => createTypedResponse(text, status, "text", headers)
76
78
  };
79
+ context[nativeContextBrand] = options.native;
80
+ return context;
77
81
  }
78
82
  function typedResponseToResponse(response) {
79
83
  const headers = new Headers(response.headers);
@@ -254,11 +258,53 @@ async function routeHandler(honoContext, route) {
254
258
  request: honoContext.req.raw,
255
259
  params: honoContext.req.param(),
256
260
  validated: prepared.validated,
257
- app: route.services
261
+ app: route.services,
262
+ native: honoContext
258
263
  });
259
264
  const result = await composeMiddleware(route.middleware, route.handle, context);
260
265
  return toResponse(result);
261
266
  }
267
+ function syncHonoVars(honoContext, giriContext) {
268
+ const vars = honoContext.var;
269
+ if (!vars) {
270
+ return;
271
+ }
272
+ for (const key of Object.keys(vars)) {
273
+ giriContext.set(key, vars[key]);
274
+ }
275
+ }
276
+ function fromHono(...handlers) {
277
+ if (handlers.length === 0) {
278
+ throw new Error("fromHono() requires at least one Hono middleware.");
279
+ }
280
+ return async (c, giriNext) => {
281
+ const honoContext = c[nativeContextBrand];
282
+ if (!honoContext) {
283
+ throw new Error(
284
+ "fromHono() can only run on the Hono adapter - no native Hono context found on the giri context."
285
+ );
286
+ }
287
+ const tail = async () => {
288
+ syncHonoVars(honoContext, c);
289
+ const result = await giriNext();
290
+ if (result instanceof Response) {
291
+ honoContext.res = result;
292
+ } else if (isTypedResponse(result)) {
293
+ honoContext.res = typedResponseToResponse(result);
294
+ }
295
+ };
296
+ const dispatch = (index) => {
297
+ const handler = handlers[index];
298
+ if (!handler) {
299
+ return tail();
300
+ }
301
+ return Promise.resolve(handler(honoContext, () => dispatch(index + 1)));
302
+ };
303
+ const returned = await dispatch(0);
304
+ syncHonoVars(honoContext, c);
305
+ return returned instanceof Response ? returned : honoContext.res;
306
+ };
307
+ }
262
308
  function registerHonoRoute(app, route) {
263
309
  const handler = (c) => routeHandler(c, route);
264
310
  const method = route.method.toLowerCase();
@@ -296,6 +342,7 @@ function hono() {
296
342
  }
297
343
  // Annotate the CommonJS export names for ESM import in node:
298
344
  0 && (module.exports = {
345
+ fromHono,
299
346
  hono
300
347
  });
301
348
  //# sourceMappingURL=hono.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/hono.ts","../../src/types.ts","../../src/context.ts","../../src/validation.ts"],"sourcesContent":["import { serve as serveNode } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport type { Context as HonoContext } from 'hono';\nimport { composeMiddleware, createContext, toResponse } from '../context';\nimport type { GiriAdapter, GiriRouteRegistration } from '../types';\nimport { prepareRequestInput } from '../validation';\n\nexport type HonoGiriApp = Hono;\n\nasync function routeHandler(honoContext: HonoContext, route: GiriRouteRegistration): Promise<Response> {\n const prepared = await prepareRequestInput(honoContext.req.raw, route.input);\n if (!prepared.ok) {\n return toResponse(prepared.response);\n }\n\n const context = createContext({\n request: honoContext.req.raw,\n params: honoContext.req.param() as Record<string, string>,\n validated: prepared.validated,\n app: route.services,\n });\n const result = await composeMiddleware(route.middleware, route.handle, context);\n return toResponse(result);\n}\n\nfunction registerHonoRoute(app: Hono, route: GiriRouteRegistration): void {\n type HonoHandler = (c: HonoContext) => Promise<Response>;\n\n const handler: HonoHandler = (c) => routeHandler(c, route);\n const method = route.method.toLowerCase();\n const appMethods = app as never as Record<string, (path: string, handler: HonoHandler) => void>;\n\n if (method in app && typeof appMethods[method] === 'function') {\n appMethods[method](route.path, handler);\n return;\n }\n\n throw new Error(`Hono adapter does not support ${route.method}.`);\n}\n\nexport function hono(): GiriAdapter<HonoGiriApp> {\n return {\n name: 'hono',\n createApp: () => new Hono({ strict: false }),\n register: registerHonoRoute,\n fetch: async (app, req) => app.fetch(req),\n serve: (handler, options, onListen) => {\n const server = serveNode(\n {\n fetch: handler,\n port: options.port,\n hostname: options.hostname,\n },\n onListen ? (info) => onListen({ address: info.address, port: info.port }) : undefined,\n );\n\n return {\n close: () => {\n return new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n })\n }\n };\n },\n };\n}\n","export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';\n\nexport type StatusCode = number;\n\nexport type ResponseFormat = 'json' | 'text';\n\nexport const typedResponseBrand: unique symbol = Symbol.for('giri.typed-response') as never;\n\nexport interface TypedResponse<\n T,\n S extends StatusCode = StatusCode,\n F extends ResponseFormat = ResponseFormat,\n> {\n readonly [typedResponseBrand]: {\n data: T;\n status: S;\n format: F;\n };\n readonly data: T;\n readonly status: S;\n readonly format: F;\n readonly headers?: HeadersInit;\n}\n\nexport type HandlerResponse = Response | TypedResponse<unknown, StatusCode, ResponseFormat>;\n\nexport interface ValidatedInput {\n /**\n * The validated request body. For a single declared content-type it's that schema's\n * output; for several it's a discriminated union `{ type; data }` (see `ValidBody`).\n */\n body?: unknown;\n query?: unknown;\n}\n\nexport interface GiriRequest<Input extends ValidatedInput = ValidatedInput> {\n raw: Request;\n url: URL;\n method: string;\n header(name: string): string | null;\n json<T = unknown>(): Promise<T>;\n text(): Promise<string>;\n arrayBuffer(): Promise<ArrayBuffer>;\n formData(): Promise<FormData>;\n valid<K extends keyof Input & ('body' | 'query')>(key: K): Input[K];\n}\n\ndeclare global {\n /**\n * Global registration surface for app-wide types. `giri sync` augments\n * `Giri.Register[\"app\"]` from `src/main.ts` `init()` return type so `c.app` is\n * typed without per-route generics (the registration pattern).\n */\n namespace Giri {\n interface Register {}\n }\n}\n\n/**\n * The app-wide services container, the type of `c.app`. `giri sync` infers it from\n * `src/main.ts`'s `init()` return type (via the global `Giri.Register` augmentation);\n * until then it falls back to an open record. Leave `init` unannotated (its return is\n * the source of truth) and annotate `teardown`'s parameter with this:\n *\n * ```ts\n * export const init = () => ({ db }); // inferred\n * export const teardown = (services: Services) => services.db.close();\n * ```\n */\nexport type Services = Giri.Register extends { app: infer A }\n ? A\n : Record<string, unknown>;\n\nexport interface Context<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> {\n params: Params;\n /** App-wide services from `src/main.ts`'s `init()`, seeded into every request. */\n app: Services;\n req: GiriRequest<Input>;\n // Context vars (`c.set`/`c.get`). Keys declared by middleware (`Vars`) are typed;\n // any other key stays open (`unknown`) so untracked keys still work.\n set<K extends keyof Vars & string>(key: K, value: Vars[K]): void;\n set<K extends string>(key: K, value: unknown): void;\n get<K extends keyof Vars & string>(key: K): Vars[K];\n get<V = unknown>(key: string): V;\n json<T, S extends StatusCode = 200>(\n data: T,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<T, S, 'json'>;\n text<S extends StatusCode = 200>(\n text: string,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<string, S, 'text'>;\n}\n\nexport type Handle<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> = (c: Context<Params, Input, Vars>) => HandlerResponse | Promise<HandlerResponse>;\n\nexport type Next = () => Promise<HandlerResponse | void>;\n\n/** An OpenAPI security requirement, e.g. `{ bearerAuth: [] }`. */\nexport type SecurityRequirement = Record<string, string[]>;\n\nexport interface MiddlewareOpenApi {\n /** Security requirements this middleware enforces */\n security?: SecurityRequirement[];\n /** Optional scheme definitions, merged into `components.securitySchemes` so the doc is self-contained. */\n securitySchemes?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface MiddlewareOptions {\n openapi?: MiddlewareOpenApi;\n}\n\nexport interface Middleware<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> {\n (c: Context<Params, Input, Vars>, next: Next): HandlerResponse | void | Promise<HandlerResponse | void>;\n openapi?: MiddlewareOpenApi;\n}\n\n/** The context vars a middleware injects (its `Vars` type parameter). */\nexport type VarsOf<M> = M extends Middleware<Record<string, string>, ValidatedInput, infer V>\n ? V\n : {};\n\n/** Intersect the injected vars of a tuple of middleware (built with `stack(...)`). */\nexport type MergeStack<T> = T extends readonly [infer Head, ...infer Rest]\n ? VarsOf<Head> & MergeStack<Rest>\n : {};\n\n/**\n * Merge the vars from a middleware stack export. A plain `Middleware[]` (not a `stack(...)` tuple)\n */\nexport type InferStackVars<T> = T extends readonly [unknown, ...unknown[]] ? MergeStack<T> : {};\n\n/**\n * The vars injected by a module own `middleware` export (a `stack(...)`). Used by the\n * generated per-method handle so a verb file's own `export const middleware` types\n * `c.get`/`c.set`, on top of the folder's `+shared.ts` chain.\n */\nexport type MiddlewareVarsOf<M> = M extends { middleware: infer Stack }\n ? InferStackVars<Stack>\n : {};\n\n/** A JSON Schema object (JSON Schema 2020-12 / OpenAPI 3.1 dialect). */\nexport type JsonSchema = Record<string, unknown>;\n\nexport const inputSchemaBrand: unique symbol = Symbol.for('giri.input-schema') as never;\n\nexport type InputValidationResult<Output = unknown> =\n | { ok: true; value: Output }\n | { ok: false; issues: unknown };\n\n/**\n * A input schema every wrapper form (`body`/`query`) export takes. A vendor\n * adapter (`@boon4681/giri/validators/zod`, `@boon4681/giri/validators/valibot`, …) returns one; build a\n * custom one with `defineInputSchema`. giri core depends only on this interface, never\n * on a validator library. `validate` is the runtime check; `toJsonSchema` feeds OpenAPI.\n */\nexport interface GiriInputSchema<Output = unknown> {\n readonly [inputSchemaBrand]: true;\n validate(value: unknown): InputValidationResult<Output> | Promise<InputValidationResult<Output>>;\n toJsonSchema(): JsonSchema;\n}\n\n/** Extract the validated output type of a giri input schema: `Infer<typeof body>`. */\nexport type Infer<T> = T extends GiriInputSchema<infer Output> ? Output : never;\n\nexport type BodyContentType = 'json' | 'form' | 'urlencoded' | 'text';\n\nexport const bodySchemaBrand: unique symbol = Symbol.for('giri.body-schema') as never;\n\n/**\n * A request body declared as a set of accepted content-types wrapped form `body`\n * takes (`zod.body({ json, form })`). One key means that encoding only; several mean the\n * endpoint accepts any of them, dispatched at runtime on the request `Content-Type`.\n * Each entry is a plain `GiriInputSchema`, so `validate`/`toJsonSchema` work per content-type.\n */\nexport interface GiriBodySchema<\n Outputs extends Partial<Record<BodyContentType, unknown>> = Partial<Record<BodyContentType, unknown>>,\n> {\n readonly [bodySchemaBrand]: true;\n readonly contents: { [K in keyof Outputs & BodyContentType]: GiriInputSchema<Outputs[K]> };\n}\n\n/** True when `T` is a union of more than one member. */\ntype IsUnion<T, U = T> = T extends unknown ? ([U] extends [T] ? false : true) : never;\n\n/**\n * The validated body a handler receives. A single declared content-type yields that\n * schema's output directly; several yield a discriminated union keyed by content-type.\n */\nexport type ValidBody<B> = B extends GiriBodySchema<infer Outputs>\n ? IsUnion<keyof Outputs> extends true\n ? { [K in keyof Outputs]: { type: K; data: Outputs[K] } }[keyof Outputs]\n : Outputs[keyof Outputs]\n : never;\n\n/** The validated query a handler receives. */\nexport type ValidQuery<Q> = Q extends GiriInputSchema<infer Output> ? Output : never;\n\n/** Drop keys whose value resolved to `never` (an input the route didn't declare). */\ntype PruneNever<T> = { [K in keyof T as [T[K]] extends [never] ? never : K]: T[K] };\n\n/**\n * Derive a route's `ValidatedInput` from a module's `body`/`query` exports. The generated\n * per-method `$types` handle (`POST`, `GET`, …) uses this so handlers infer `c.req.valid`\n * with no manual generic.\n */\nexport type RouteInputOf<M> = PruneNever<{\n body: M extends { body: infer B } ? ValidBody<B> : never;\n query: M extends { query: infer Q } ? ValidQuery<Q> : never;\n}>;\n\nexport interface RouteInput {\n body?: GiriBodySchema;\n query?: GiriInputSchema;\n}\n\nexport interface RouteOpenApi {\n /** Omit this route from the generated `openapi.json` (it still serves normally). */\n hidden?: boolean;\n // Room to grow: summary, description, tags, deprecated, operationId, …\n}\n\nexport type RouteOpenApiConfig = RouteOpenApi | boolean;\n\nexport interface GiriRouteRegistration {\n method: HttpMethod;\n path: string;\n handle: Handle;\n middleware: Middleware[];\n input?: RouteInput;\n /** App-wide services to seed onto `c.app` (same instance for every route). */\n services?: Services;\n}\n\nexport type GiriFetchHandler = (req: Request) => Response | Promise<Response>;\n\nexport interface GiriServeOptions {\n port: number;\n hostname?: string;\n}\n\nexport interface GiriServerInfo {\n address: string;\n port: number;\n}\n\nexport interface GiriServer {\n close(): void | Promise<void>;\n}\n\nexport interface GiriAdapter<App> {\n name?: string;\n createApp(): App;\n register(app: App, route: GiriRouteRegistration): void;\n fetch(app: App, req: Request): Promise<Response>;\n /**\n * Bind the configured backend's runtime to a port and start serving.\n * giri core stays runtime-agnostic: it hands the adapter a request handler\n * (so hot-reload keeps working) and the adapter owns the actual server.\n */\n serve(\n handler: GiriFetchHandler,\n options: GiriServeOptions,\n onListen?: (info: GiriServerInfo) => void,\n ): GiriServer;\n}\n\nexport interface GiriConfig<App = unknown> {\n adapter: GiriAdapter<App>;\n alias?: Record<string, string | string[]>;\n outDir?: string;\n server?: {\n port?: number;\n hostname?: string;\n };\n errorSchema?: unknown;\n}\n\nexport interface GiriPaths {\n cwd: string;\n routesDir: string;\n outDir: string;\n}\n","import {\n type Services,\n type Context,\n type HandlerResponse,\n type Middleware,\n type MiddlewareOptions,\n type ResponseFormat,\n type StatusCode,\n type TypedResponse,\n type ValidatedInput,\n typedResponseBrand,\n} from './types';\n\nconst BODYLESS_STATUS = new Set([101, 103, 204, 205, 304]);\n\nexport interface CreateContextOptions<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n> {\n request: Request;\n params?: Params;\n validated?: Input;\n app?: Services;\n}\n\nexport function createTypedResponse<\n T,\n S extends StatusCode,\n F extends ResponseFormat,\n>(data: T, status: S, format: F, headers?: HeadersInit): TypedResponse<T, S, F> {\n return {\n [typedResponseBrand]: { data, status, format },\n data,\n status,\n format,\n headers,\n };\n}\n\nexport function isTypedResponse(value: unknown): value is TypedResponse<unknown> {\n return Boolean(value && typeof value === 'object' && typedResponseBrand in value);\n}\n\nexport function createContext<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n>(options: CreateContextOptions<Params, Input>): Context<Params, Input> {\n const url = new URL(options.request.url);\n const store = new Map<string, unknown>();\n const validated = options.validated ?? ({} as Input);\n\n return {\n params: options.params ?? ({} as Params),\n app: options.app ?? ({} as Services),\n req: {\n raw: options.request,\n url,\n method: options.request.method,\n header: (name) => options.request.headers.get(name),\n json: <T = unknown>() => options.request.json() as Promise<T>,\n text: () => options.request.text(),\n arrayBuffer: () => options.request.arrayBuffer(),\n formData: () => options.request.formData(),\n valid: (key) => {\n if (!(key in validated)) {\n throw new Error(`No validated ${String(key)} data is available for this route.`);\n }\n return validated[key];\n },\n },\n set: (key: string, value: unknown) => {\n store.set(key, value);\n },\n get: (key: string) => store.get(key) as never,\n json: (data, status = 200 as never, headers) =>\n createTypedResponse(data, status, 'json', headers),\n text: (text, status = 200 as never, headers) =>\n createTypedResponse(text, status, 'text', headers),\n };\n}\n\nexport function typedResponseToResponse(response: TypedResponse<unknown>): Response {\n const headers = new Headers(response.headers);\n\n if (response.format === 'json' && !headers.has('content-type')) {\n headers.set('content-type', 'application/json; charset=utf-8');\n }\n\n if (response.format === 'text' && !headers.has('content-type')) {\n headers.set('content-type', 'text/plain; charset=utf-8');\n }\n\n const body = BODYLESS_STATUS.has(response.status)\n ? null\n : response.format === 'json'\n ? JSON.stringify(response.data)\n : String(response.data);\n\n return new Response(body, {\n status: response.status,\n headers,\n });\n}\n\nexport function toResponse(response: HandlerResponse): Response {\n return isTypedResponse(response) ? typedResponseToResponse(response) : response;\n}\n\nexport async function composeMiddleware(\n middleware: Middleware[],\n handle: (c: Context) => HandlerResponse | Promise<HandlerResponse>,\n context: Context,\n): Promise<HandlerResponse> {\n let index = -1;\n let result: HandlerResponse | undefined;\n\n const dispatch = async (i: number): Promise<HandlerResponse | void> => {\n if (i <= index) {\n throw new Error('next() called multiple times in giri middleware.');\n }\n index = i;\n\n if (i === middleware.length) {\n result = await handle(context);\n return result;\n }\n\n const returned = await middleware[i](context, () => dispatch(i + 1));\n if (returned !== undefined) {\n result = returned;\n return returned;\n }\n return result;\n };\n\n await dispatch(0);\n\n if (result === undefined) {\n throw new Error('Route completed without returning a response.');\n }\n\n return result;\n}\n\ntype AnyMiddleware<Vars extends Record<string, unknown>> = Middleware<\n Record<string, string>,\n ValidatedInput,\n Vars\n>;\n\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n middleware: AnyMiddleware<Vars>,\n): AnyMiddleware<Vars>;\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n options: MiddlewareOptions,\n middleware: AnyMiddleware<Vars>,\n): AnyMiddleware<Vars>;\nexport function defineMiddleware(\n optionsOrMiddleware: MiddlewareOptions | Middleware,\n maybeMiddleware?: Middleware,\n): Middleware {\n if (typeof optionsOrMiddleware === 'function') {\n return optionsOrMiddleware;\n }\n\n if (!maybeMiddleware) {\n throw new Error('defineMiddleware(options, middleware) requires a middleware function.');\n }\n\n maybeMiddleware.openapi = optionsOrMiddleware.openapi;\n return maybeMiddleware;\n}\n\n/**\n * Group middleware into an ordered stack, preserving each element's type as a tuple so\n * the injected context vars (`defineMiddleware<Vars>` / `Middleware<…, Vars>`) propagate\n * to downstream handlers. Use it for `+shared.ts` and verb `middleware` exports:\n * `export const middleware = stack(auth, requireAdmin)`.\n */\n// `Vars` is contravariant (it sits in the `c` parameter), so the constraint must leave it\n// open (`any`) otherwise a middleware that injects vars isn't assignable to the element type.\nexport function stack<T extends Middleware<Record<string, string>, ValidatedInput, any>[]>(...middleware: T): T {\n return middleware;\n}\n","import {\n type BodyContentType,\n type GiriBodySchema,\n type GiriInputSchema,\n type InputValidationResult,\n type RouteInput,\n type TypedResponse,\n type ValidatedInput,\n bodySchemaBrand,\n inputSchemaBrand,\n} from './types';\nimport { createTypedResponse } from './context';\n\ninterface PreparedInput {\n ok: true;\n validated: ValidatedInput;\n}\n\ninterface FailedInput {\n ok: false;\n response: TypedResponse<{ message: string; issues: unknown }, 400 | 415, 'json'>;\n}\n\nexport type PreparedRequestInput = PreparedInput | FailedInput;\n\n/**\n * Build a giri input schema from a `validate` + `toJsonSchema` pair. Vendor adapters use\n * this; you can call it directly to make a custom validator. The brand is a global symbol,\n * so a hand-rolled `{ [Symbol.for(\"giri.input-schema\")]: true, validate, toJsonSchema }` works too.\n */\nexport function defineInputSchema<Output>(\n schema: Omit<GiriInputSchema<Output>, typeof inputSchemaBrand>,\n): GiriInputSchema<Output> {\n return { [inputSchemaBrand]: true, ...schema };\n}\n\nexport function isGiriInputSchema(value: unknown): value is GiriInputSchema {\n return Boolean(\n value &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[inputSchemaBrand] === true,\n );\n}\n\n/**\n * Build a giri body schema from per-content-type input schemas. Validator adapters use this `zod.body({ json, form })`\n */\nexport function defineBodySchema<Outputs extends Partial<Record<BodyContentType, unknown>>>(\n contents: GiriBodySchema<Outputs>['contents'],\n): GiriBodySchema<Outputs> {\n return { [bodySchemaBrand]: true, contents };\n}\n\nexport function isGiriBodySchema(value: unknown): value is GiriBodySchema {\n return Boolean(\n value &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[bodySchemaBrand] === true,\n );\n}\n\nconst MIME_TO_CONTENT_TYPE: Record<string, BodyContentType> = {\n 'application/json': 'json',\n 'multipart/form-data': 'form',\n 'application/x-www-form-urlencoded': 'urlencoded',\n 'text/plain': 'text',\n};\n\nfunction contentTypeFromHeader(header: string | null): BodyContentType | undefined {\n if (!header) {\n return undefined;\n }\n const mime = header.split(';', 1)[0].trim().toLowerCase();\n return MIME_TO_CONTENT_TYPE[mime];\n}\n\n/** Flatten a `FormData` into a plain object, collapsing repeated fields into arrays. */\nfunction formDataObject(form: FormData): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n form.forEach((value, key) => {\n const current = result[key];\n if (current === undefined) {\n result[key] = value;\n } else if (Array.isArray(current)) {\n current.push(value);\n } else {\n result[key] = [current, value];\n }\n });\n return result;\n}\n\nasync function readRawBody(request: Request, contentType: BodyContentType): Promise<unknown> {\n const cloned = request.clone();\n if (contentType === 'json') {\n return cloned.json();\n }\n if (contentType === 'text') {\n return cloned.text();\n }\n return formDataObject(await cloned.formData());\n}\n\nfunction queryObject(url: URL): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n for (const [key, value] of url.searchParams) {\n const current = result[key];\n if (current === undefined) {\n result[key] = value;\n } else if (Array.isArray(current)) {\n current.push(value);\n } else {\n result[key] = [current, value];\n }\n }\n return result;\n}\n\nasync function runValidation(\n schema: GiriInputSchema,\n value: unknown,\n label: string,\n): Promise<InputValidationResult> {\n if (!isGiriInputSchema(schema)) {\n throw new Error(\n `giri: ${label} schema must be wrapped with a validator, e.g. \\`export const ${label} = zod(...)\\` from @boon4681/giri/validators/zod.`,\n );\n }\n return schema.validate(value);\n}\n\nexport async function prepareRequestInput(request: Request, input?: RouteInput): Promise<PreparedRequestInput> {\n const validated: ValidatedInput = {};\n\n if (input?.query) {\n const query = queryObject(new URL(request.url));\n const result = await runValidation(input.query, query, 'query');\n if (!result.ok) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid query parameters.', issues: result.issues },\n 400,\n 'json',\n ),\n };\n }\n validated.query = result.value;\n }\n\n if (input?.body) {\n const contents = input.body.contents as Record<BodyContentType, GiriInputSchema>;\n const declared = Object.keys(contents) as BodyContentType[];\n const requested = contentTypeFromHeader(request.headers.get('content-type'));\n // Pick the schema matching the request's content-type; fall back to JSON when the\n // header is missing/unrecognized but JSON is on offer (so header-less posts still work).\n const chosen: BodyContentType | undefined =\n requested && contents[requested] ? requested : contents.json ? 'json' : undefined;\n\n if (!chosen) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Unsupported media type.', issues: { accepted: declared } },\n 415,\n 'json',\n ),\n };\n }\n\n let rawBody: unknown;\n try {\n rawBody = await readRawBody(request, chosen);\n } catch (error) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid request body.', issues: error },\n 400,\n 'json',\n ),\n };\n }\n\n const result = await runValidation(contents[chosen], rawBody, 'body');\n if (!result.ok) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid request body.', issues: result.issues },\n 400,\n 'json',\n ),\n };\n }\n\n validated.body = declared.length > 1 ? { type: chosen, data: result.value } : result.value;\n }\n\n return { ok: true, validated };\n}\n"],"mappings":"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","names":["serveNode"]}
1
+ {"version":3,"sources":["../../src/adapters/hono.ts","../../src/types.ts","../../src/context.ts","../../src/validation.ts"],"sourcesContent":["import { serve as serveNode } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport type { Context as HonoContext, ContextVariableMap, MiddlewareHandler } from 'hono';\nimport {\n composeMiddleware,\n createContext,\n isTypedResponse,\n toResponse,\n typedResponseToResponse,\n} from '../context';\nimport { nativeContextBrand } from '../types';\nimport type {\n Context as GiriContext,\n GiriAdapter,\n GiriRouteRegistration,\n Middleware,\n ValidatedInput,\n} from '../types';\nimport { prepareRequestInput } from '../validation';\n\nexport type HonoGiriApp = Hono;\nexport type HonoContextVars = { [K in keyof ContextVariableMap]: ContextVariableMap[K] };\n\nasync function routeHandler(honoContext: HonoContext, route: GiriRouteRegistration): Promise<Response> {\n const prepared = await prepareRequestInput(honoContext.req.raw, route.input);\n if (!prepared.ok) {\n return toResponse(prepared.response);\n }\n\n const context = createContext({\n request: honoContext.req.raw,\n params: honoContext.req.param() as Record<string, string>,\n validated: prepared.validated,\n app: route.services,\n native: honoContext,\n });\n const result = await composeMiddleware(route.middleware, route.handle, context);\n return toResponse(result);\n}\n\nfunction syncHonoVars(honoContext: HonoContext, giriContext: GiriContext): void {\n const vars = honoContext.var as Record<string, unknown> | undefined;\n if (!vars) {\n return;\n }\n for (const key of Object.keys(vars)) {\n giriContext.set(key, vars[key]);\n }\n}\n\n/**\n * Wrap one or more native Hono middleware as a single giri `Middleware`, so the existing Hono\n * ecosystem (`@hono/oauth-providers`, CORS, etc.) runs unchanged on a giri route:\n *\n * ```ts\n * // routes/auth/google/+shared.ts\n * import { fromHono } from \"@boon4681/giri/adapters/hono\";\n * import { googleAuth } from \"@hono/oauth-providers/google\";\n *\n * export const middleware = stack(\n * fromHono(googleAuth({ client_id: …, client_secret: …, scope: [\"openid\", \"email\"] })),\n * );\n * // downstream handler: const user = c.get(\"user-google\");\n * ```\n *\n * It runs the Hono middleware against the real Hono context (cookies, `c.redirect`, `c.req.query`\n * all work), then mirrors any vars it set onto giri's `c` for downstream `c.get`. Only valid on the\n * Hono adapter - throws on any other backend.\n */\nexport function fromHono<Vars extends Record<string, unknown> = HonoContextVars>(\n ...handlers: MiddlewareHandler[]\n): Middleware<Record<string, string>, ValidatedInput, Vars> {\n if (handlers.length === 0) {\n throw new Error('fromHono() requires at least one Hono middleware.');\n }\n\n return async (c, giriNext) => {\n const honoContext = (c as unknown as Record<symbol, unknown>)[nativeContextBrand] as\n | HonoContext\n | undefined;\n if (!honoContext) {\n throw new Error(\n 'fromHono() can only run on the Hono adapter - no native Hono context found on the giri context.',\n );\n }\n\n const tail = async (): Promise<void> => {\n syncHonoVars(honoContext, c);\n const result = await giriNext();\n if (result instanceof Response) {\n honoContext.res = result;\n } else if (isTypedResponse(result)) {\n honoContext.res = typedResponseToResponse(result);\n }\n };\n\n const dispatch = (index: number): Promise<unknown> => {\n const handler = handlers[index];\n if (!handler) {\n return tail();\n }\n return Promise.resolve(handler(honoContext, () => dispatch(index + 1) as Promise<void>));\n };\n\n const returned = await dispatch(0);\n syncHonoVars(honoContext, c);\n return returned instanceof Response ? returned : honoContext.res;\n };\n}\n\nfunction registerHonoRoute(app: Hono, route: GiriRouteRegistration): void {\n type HonoHandler = (c: HonoContext) => Promise<Response>;\n\n const handler: HonoHandler = (c) => routeHandler(c, route);\n const method = route.method.toLowerCase();\n const appMethods = app as never as Record<string, (path: string, handler: HonoHandler) => void>;\n\n if (method in app && typeof appMethods[method] === 'function') {\n appMethods[method](route.path, handler);\n return;\n }\n\n throw new Error(`Hono adapter does not support ${route.method}.`);\n}\n\nexport function hono(): GiriAdapter<HonoGiriApp> {\n return {\n name: 'hono',\n createApp: () => new Hono({ strict: false }),\n register: registerHonoRoute,\n fetch: async (app, req) => app.fetch(req),\n serve: (handler, options, onListen) => {\n const server = serveNode(\n {\n fetch: handler,\n port: options.port,\n hostname: options.hostname,\n },\n onListen ? (info) => onListen({ address: info.address, port: info.port }) : undefined,\n );\n\n return {\n close: () => {\n return new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n })\n }\n };\n },\n };\n}\n","export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';\n\nexport type StatusCode = number;\n\nexport type ResponseFormat = 'json' | 'text';\n\nexport const typedResponseBrand: unique symbol = Symbol.for('giri.typed-response') as never;\nexport const nativeContextBrand: unique symbol = Symbol.for('giri.native-context') as never;\n\nexport interface TypedResponse<\n T,\n S extends StatusCode = StatusCode,\n F extends ResponseFormat = ResponseFormat,\n> {\n readonly [typedResponseBrand]: {\n data: T;\n status: S;\n format: F;\n };\n readonly data: T;\n readonly status: S;\n readonly format: F;\n readonly headers?: HeadersInit;\n}\n\nexport type HandlerResponse = Response | TypedResponse<unknown, StatusCode, ResponseFormat>;\n\nexport interface ValidatedInput {\n /**\n * The validated request body. For a single declared content-type it's that schema's\n * output; for several it's a discriminated union `{ type; data }` (see `ValidBody`).\n */\n body?: unknown;\n query?: unknown;\n}\n\nexport interface GiriRequest<Input extends ValidatedInput = ValidatedInput> {\n raw: Request;\n url: URL;\n method: string;\n header(name: string): string | null;\n json<T = unknown>(): Promise<T>;\n text(): Promise<string>;\n arrayBuffer(): Promise<ArrayBuffer>;\n formData(): Promise<FormData>;\n valid<K extends keyof Input & ('body' | 'query')>(key: K): Input[K];\n}\n\ndeclare global {\n /**\n * Global registration surface for app-wide types. `giri sync` augments\n * `Giri.Register[\"app\"]` from `src/main.ts` `init()` return type so `c.app` is\n * typed without per-route generics (the registration pattern).\n */\n namespace Giri {\n interface Register {}\n }\n}\n\n/**\n * The app-wide services container, the type of `c.app`. `giri sync` infers it from\n * `src/main.ts`'s `init()` return type (via the global `Giri.Register` augmentation);\n * until then it falls back to an open record. Leave `init` unannotated (its return is\n * the source of truth) and annotate `teardown`'s parameter with this:\n *\n * ```ts\n * export const init = () => ({ db }); // inferred\n * export const teardown = (services: Services) => services.db.close();\n * ```\n */\nexport type Services = Giri.Register extends { app: infer A }\n ? A\n : Record<string, unknown>;\n\nexport interface Context<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> {\n params: Params;\n /** App-wide services from `src/main.ts`'s `init()`, seeded into every request. */\n app: Services;\n req: GiriRequest<Input>;\n // Context vars (`c.set`/`c.get`). Keys declared by middleware (`Vars`) are typed;\n // any other key stays open (`unknown`) so untracked keys still work.\n set<K extends keyof Vars & string>(key: K, value: Vars[K]): void;\n set<K extends string>(key: K, value: unknown): void;\n get<K extends keyof Vars & string>(key: K): Vars[K];\n get<V = unknown>(key: string): V;\n json<T, S extends StatusCode = 200>(\n data: T,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<T, S, 'json'>;\n text<S extends StatusCode = 200>(\n text: string,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<string, S, 'text'>;\n}\n\nexport type Handle<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> = (c: Context<Params, Input, Vars>) => HandlerResponse | Promise<HandlerResponse>;\n\nexport type Next = () => Promise<HandlerResponse | void>;\n\n/** An OpenAPI security requirement, e.g. `{ bearerAuth: [] }`. */\nexport type SecurityRequirement = Record<string, string[]>;\n\nexport interface MiddlewareOpenApi {\n /** Security requirements this middleware enforces */\n security?: SecurityRequirement[];\n /** Optional scheme definitions, merged into `components.securitySchemes` so the doc is self-contained. */\n securitySchemes?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface MiddlewareOptions {\n openapi?: MiddlewareOpenApi;\n}\n\nexport interface Middleware<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n Vars extends Record<string, unknown> = {},\n> {\n (c: Context<Params, Input, Vars>, next: Next): HandlerResponse | void | Promise<HandlerResponse | void>;\n openapi?: MiddlewareOpenApi;\n}\n\n/** The context vars a middleware injects (its `Vars` type parameter). */\nexport type VarsOf<M> = M extends Middleware<Record<string, string>, ValidatedInput, infer V>\n ? V\n : {};\n\n/** Intersect the injected vars of a tuple of middleware (built with `stack(...)`). */\nexport type MergeStack<T> = T extends readonly [infer Head, ...infer Rest]\n ? VarsOf<Head> & MergeStack<Rest>\n : {};\n\n/**\n * Merge the injected vars of a `middleware` export. A `stack(...)` tuple is merged element-wise;\n * a single bare middleware (`export const middleware = fromHono(...)`) contributes its own vars; a\n * plain `Middleware[]` (not a `stack(...)` tuple) contributes nothing - its element types are lost.\n */\nexport type InferStackVars<T> = T extends readonly [unknown, ...unknown[]]\n ? MergeStack<T>\n : T extends Middleware<Record<string, string>, ValidatedInput, any>\n ? VarsOf<T>\n : {};\n\n/**\n * The vars injected by a module own `middleware` export (a `stack(...)`). Used by the\n * generated per-method handle so a verb file's own `export const middleware` types\n * `c.get`/`c.set`, on top of the folder's `+shared.ts` chain.\n */\nexport type MiddlewareVarsOf<M> = M extends { middleware: infer Stack }\n ? InferStackVars<Stack>\n : {};\n\n/** A JSON Schema object (JSON Schema 2020-12 / OpenAPI 3.1 dialect). */\nexport type JsonSchema = Record<string, unknown>;\n\nexport const inputSchemaBrand: unique symbol = Symbol.for('giri.input-schema') as never;\n\nexport type InputValidationResult<Output = unknown> =\n | { ok: true; value: Output }\n | { ok: false; issues: unknown };\n\n/**\n * A input schema every wrapper form (`body`/`query`) export takes. A vendor\n * adapter (`@boon4681/giri/validators/zod`, `@boon4681/giri/validators/valibot`, …) returns one; build a\n * custom one with `defineInputSchema`. giri core depends only on this interface, never\n * on a validator library. `validate` is the runtime check; `toJsonSchema` feeds OpenAPI.\n */\nexport interface GiriInputSchema<Output = unknown> {\n readonly [inputSchemaBrand]: true;\n validate(value: unknown): InputValidationResult<Output> | Promise<InputValidationResult<Output>>;\n toJsonSchema(): JsonSchema;\n}\n\n/** Extract the validated output type of a giri input schema: `Infer<typeof body>`. */\nexport type Infer<T> = T extends GiriInputSchema<infer Output> ? Output : never;\n\nexport type BodyContentType = 'json' | 'form' | 'urlencoded' | 'text';\n\nexport const bodySchemaBrand: unique symbol = Symbol.for('giri.body-schema') as never;\n\n/**\n * A request body declared as a set of accepted content-types wrapped form `body`\n * takes (`zod.body({ json, form })`). One key means that encoding only; several mean the\n * endpoint accepts any of them, dispatched at runtime on the request `Content-Type`.\n * Each entry is a plain `GiriInputSchema`, so `validate`/`toJsonSchema` work per content-type.\n */\nexport interface GiriBodySchema<\n Outputs extends Partial<Record<BodyContentType, unknown>> = Partial<Record<BodyContentType, unknown>>,\n> {\n readonly [bodySchemaBrand]: true;\n readonly contents: { [K in keyof Outputs & BodyContentType]: GiriInputSchema<Outputs[K]> };\n}\n\n/** True when `T` is a union of more than one member. */\ntype IsUnion<T, U = T> = T extends unknown ? ([U] extends [T] ? false : true) : never;\n\n/**\n * The validated body a handler receives. A single declared content-type yields that\n * schema's output directly; several yield a discriminated union keyed by content-type.\n */\nexport type ValidBody<B> = B extends GiriBodySchema<infer Outputs>\n ? IsUnion<keyof Outputs> extends true\n ? { [K in keyof Outputs]: { type: K; data: Outputs[K] } }[keyof Outputs]\n : Outputs[keyof Outputs]\n : never;\n\n/** The validated query a handler receives. */\nexport type ValidQuery<Q> = Q extends GiriInputSchema<infer Output> ? Output : never;\n\n/** Drop keys whose value resolved to `never` (an input the route didn't declare). */\ntype PruneNever<T> = { [K in keyof T as [T[K]] extends [never] ? never : K]: T[K] };\n\n/**\n * Derive a route's `ValidatedInput` from a module's `body`/`query` exports. The generated\n * per-method `$types` handle (`POST`, `GET`, …) uses this so handlers infer `c.req.valid`\n * with no manual generic.\n */\nexport type RouteInputOf<M> = PruneNever<{\n body: M extends { body: infer B } ? ValidBody<B> : never;\n query: M extends { query: infer Q } ? ValidQuery<Q> : never;\n}>;\n\nexport interface RouteInput {\n body?: GiriBodySchema;\n query?: GiriInputSchema;\n}\n\nexport interface RouteOpenApi {\n /** Omit this route from the generated `openapi.json` (it still serves normally). */\n hidden?: boolean;\n // Room to grow: summary, description, tags, deprecated, operationId, …\n}\n\nexport type RouteOpenApiConfig = RouteOpenApi | boolean;\n\nexport interface GiriRouteRegistration {\n method: HttpMethod;\n path: string;\n handle: Handle;\n middleware: Middleware[];\n input?: RouteInput;\n /** App-wide services to seed onto `c.app` (same instance for every route). */\n services?: Services;\n}\n\nexport type GiriFetchHandler = (req: Request) => Response | Promise<Response>;\n\nexport interface GiriServeOptions {\n port: number;\n hostname?: string;\n}\n\nexport interface GiriServerInfo {\n address: string;\n port: number;\n}\n\nexport interface GiriServer {\n close(): void | Promise<void>;\n}\n\nexport interface GiriAdapter<App> {\n name?: string;\n createApp(): App;\n register(app: App, route: GiriRouteRegistration): void;\n fetch(app: App, req: Request): Promise<Response>;\n /**\n * Bind the configured backend's runtime to a port and start serving.\n * giri core stays runtime-agnostic: it hands the adapter a request handler\n * (so hot-reload keeps working) and the adapter owns the actual server.\n */\n serve(\n handler: GiriFetchHandler,\n options: GiriServeOptions,\n onListen?: (info: GiriServerInfo) => void,\n ): GiriServer;\n}\n\nexport interface GiriConfig<App = unknown> {\n adapter: GiriAdapter<App>;\n alias?: Record<string, string | string[]>;\n outDir?: string;\n server?: {\n port?: number;\n hostname?: string;\n };\n errorSchema?: unknown;\n}\n\nexport interface GiriPaths {\n cwd: string;\n routesDir: string;\n outDir: string;\n}\n","import {\n type Services,\n type Context,\n type HandlerResponse,\n type Middleware,\n type MiddlewareOptions,\n type ResponseFormat,\n type StatusCode,\n type TypedResponse,\n type ValidatedInput,\n nativeContextBrand,\n typedResponseBrand,\n} from './types';\n\nconst BODYLESS_STATUS = new Set([101, 103, 204, 205, 304]);\n\nexport interface CreateContextOptions<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n> {\n request: Request;\n params?: Params;\n validated?: Input;\n app?: Services;\n /** The adapter's native per-request context, stashed for backend-specific bridges. */\n native?: unknown;\n}\n\nexport function createTypedResponse<\n T,\n S extends StatusCode,\n F extends ResponseFormat,\n>(data: T, status: S, format: F, headers?: HeadersInit): TypedResponse<T, S, F> {\n return {\n [typedResponseBrand]: { data, status, format },\n data,\n status,\n format,\n headers,\n };\n}\n\nexport function isTypedResponse(value: unknown): value is TypedResponse<unknown> {\n return Boolean(value && typeof value === 'object' && typedResponseBrand in value);\n}\n\nexport function createContext<\n Params extends Record<string, string> = Record<string, string>,\n Input extends ValidatedInput = ValidatedInput,\n>(options: CreateContextOptions<Params, Input>): Context<Params, Input> {\n const url = new URL(options.request.url);\n const store = new Map<string, unknown>();\n const validated = options.validated ?? ({} as Input);\n\n const context: Context<Params, Input> = {\n params: options.params ?? ({} as Params),\n app: options.app ?? ({} as Services),\n req: {\n raw: options.request,\n url,\n method: options.request.method,\n header: (name) => options.request.headers.get(name),\n json: <T = unknown>() => options.request.json() as Promise<T>,\n text: () => options.request.text(),\n arrayBuffer: () => options.request.arrayBuffer(),\n formData: () => options.request.formData(),\n valid: (key) => {\n if (!(key in validated)) {\n throw new Error(`No validated ${String(key)} data is available for this route.`);\n }\n return validated[key];\n },\n },\n set: (key: string, value: unknown) => {\n store.set(key, value);\n },\n get: (key: string) => store.get(key) as never,\n json: (data, status = 200 as never, headers) =>\n createTypedResponse(data, status, 'json', headers),\n text: (text, status = 200 as never, headers) =>\n createTypedResponse(text, status, 'text', headers),\n };\n \n (context as unknown as Record<symbol, unknown>)[nativeContextBrand] = options.native;\n\n return context;\n}\n\nexport function typedResponseToResponse(response: TypedResponse<unknown>): Response {\n const headers = new Headers(response.headers);\n\n if (response.format === 'json' && !headers.has('content-type')) {\n headers.set('content-type', 'application/json; charset=utf-8');\n }\n\n if (response.format === 'text' && !headers.has('content-type')) {\n headers.set('content-type', 'text/plain; charset=utf-8');\n }\n\n const body = BODYLESS_STATUS.has(response.status)\n ? null\n : response.format === 'json'\n ? JSON.stringify(response.data)\n : String(response.data);\n\n return new Response(body, {\n status: response.status,\n headers,\n });\n}\n\nexport function toResponse(response: HandlerResponse): Response {\n return isTypedResponse(response) ? typedResponseToResponse(response) : response;\n}\n\nexport async function composeMiddleware(\n middleware: Middleware[],\n handle: (c: Context) => HandlerResponse | Promise<HandlerResponse>,\n context: Context,\n): Promise<HandlerResponse> {\n let index = -1;\n let result: HandlerResponse | undefined;\n\n const dispatch = async (i: number): Promise<HandlerResponse | void> => {\n if (i <= index) {\n throw new Error('next() called multiple times in giri middleware.');\n }\n index = i;\n\n if (i === middleware.length) {\n result = await handle(context);\n return result;\n }\n\n const returned = await middleware[i](context, () => dispatch(i + 1));\n if (returned !== undefined) {\n result = returned;\n return returned;\n }\n return result;\n };\n\n await dispatch(0);\n\n if (result === undefined) {\n throw new Error('Route completed without returning a response.');\n }\n\n return result;\n}\n\ntype AnyMiddleware<Vars extends Record<string, unknown>> = Middleware<\n Record<string, string>,\n ValidatedInput,\n Vars\n>;\n\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n middleware: AnyMiddleware<Vars>,\n): AnyMiddleware<Vars>;\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n options: MiddlewareOptions,\n middleware: AnyMiddleware<Vars>,\n): AnyMiddleware<Vars>;\nexport function defineMiddleware(\n optionsOrMiddleware: MiddlewareOptions | Middleware,\n maybeMiddleware?: Middleware,\n): Middleware {\n if (typeof optionsOrMiddleware === 'function') {\n return optionsOrMiddleware;\n }\n\n if (!maybeMiddleware) {\n throw new Error('defineMiddleware(options, middleware) requires a middleware function.');\n }\n\n maybeMiddleware.openapi = optionsOrMiddleware.openapi;\n return maybeMiddleware;\n}\n\n/**\n * Group middleware into an ordered stack, preserving each element's type as a tuple so\n * the injected context vars (`defineMiddleware<Vars>` / `Middleware<…, Vars>`) propagate\n * to downstream handlers. Use it for `+shared.ts` and verb `middleware` exports:\n * `export const middleware = stack(auth, requireAdmin)`.\n */\n// `Vars` is contravariant (it sits in the `c` parameter), so the constraint must leave it\n// open (`any`) otherwise a middleware that injects vars isn't assignable to the element type.\nexport function stack<T extends Middleware<Record<string, string>, ValidatedInput, any>[]>(...middleware: T): T {\n return middleware;\n}\n","import {\n type BodyContentType,\n type GiriBodySchema,\n type GiriInputSchema,\n type InputValidationResult,\n type RouteInput,\n type TypedResponse,\n type ValidatedInput,\n bodySchemaBrand,\n inputSchemaBrand,\n} from './types';\nimport { createTypedResponse } from './context';\n\ninterface PreparedInput {\n ok: true;\n validated: ValidatedInput;\n}\n\ninterface FailedInput {\n ok: false;\n response: TypedResponse<{ message: string; issues: unknown }, 400 | 415, 'json'>;\n}\n\nexport type PreparedRequestInput = PreparedInput | FailedInput;\n\n/**\n * Build a giri input schema from a `validate` + `toJsonSchema` pair. Vendor adapters use\n * this; you can call it directly to make a custom validator. The brand is a global symbol,\n * so a hand-rolled `{ [Symbol.for(\"giri.input-schema\")]: true, validate, toJsonSchema }` works too.\n */\nexport function defineInputSchema<Output>(\n schema: Omit<GiriInputSchema<Output>, typeof inputSchemaBrand>,\n): GiriInputSchema<Output> {\n return { [inputSchemaBrand]: true, ...schema };\n}\n\nexport function isGiriInputSchema(value: unknown): value is GiriInputSchema {\n return Boolean(\n value &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[inputSchemaBrand] === true,\n );\n}\n\n/**\n * Build a giri body schema from per-content-type input schemas. Validator adapters use this `zod.body({ json, form })`\n */\nexport function defineBodySchema<Outputs extends Partial<Record<BodyContentType, unknown>>>(\n contents: GiriBodySchema<Outputs>['contents'],\n): GiriBodySchema<Outputs> {\n return { [bodySchemaBrand]: true, contents };\n}\n\nexport function isGiriBodySchema(value: unknown): value is GiriBodySchema {\n return Boolean(\n value &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[bodySchemaBrand] === true,\n );\n}\n\nconst MIME_TO_CONTENT_TYPE: Record<string, BodyContentType> = {\n 'application/json': 'json',\n 'multipart/form-data': 'form',\n 'application/x-www-form-urlencoded': 'urlencoded',\n 'text/plain': 'text',\n};\n\nfunction contentTypeFromHeader(header: string | null): BodyContentType | undefined {\n if (!header) {\n return undefined;\n }\n const mime = header.split(';', 1)[0].trim().toLowerCase();\n return MIME_TO_CONTENT_TYPE[mime];\n}\n\n/** Flatten a `FormData` into a plain object, collapsing repeated fields into arrays. */\nfunction formDataObject(form: FormData): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n form.forEach((value, key) => {\n const current = result[key];\n if (current === undefined) {\n result[key] = value;\n } else if (Array.isArray(current)) {\n current.push(value);\n } else {\n result[key] = [current, value];\n }\n });\n return result;\n}\n\nasync function readRawBody(request: Request, contentType: BodyContentType): Promise<unknown> {\n const cloned = request.clone();\n if (contentType === 'json') {\n return cloned.json();\n }\n if (contentType === 'text') {\n return cloned.text();\n }\n return formDataObject(await cloned.formData());\n}\n\nfunction queryObject(url: URL): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n for (const [key, value] of url.searchParams) {\n const current = result[key];\n if (current === undefined) {\n result[key] = value;\n } else if (Array.isArray(current)) {\n current.push(value);\n } else {\n result[key] = [current, value];\n }\n }\n return result;\n}\n\nasync function runValidation(\n schema: GiriInputSchema,\n value: unknown,\n label: string,\n): Promise<InputValidationResult> {\n if (!isGiriInputSchema(schema)) {\n throw new Error(\n `giri: ${label} schema must be wrapped with a validator, e.g. \\`export const ${label} = zod(...)\\` from @boon4681/giri/validators/zod.`,\n );\n }\n return schema.validate(value);\n}\n\nexport async function prepareRequestInput(request: Request, input?: RouteInput): Promise<PreparedRequestInput> {\n const validated: ValidatedInput = {};\n\n if (input?.query) {\n const query = queryObject(new URL(request.url));\n const result = await runValidation(input.query, query, 'query');\n if (!result.ok) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid query parameters.', issues: result.issues },\n 400,\n 'json',\n ),\n };\n }\n validated.query = result.value;\n }\n\n if (input?.body) {\n const contents = input.body.contents as Record<BodyContentType, GiriInputSchema>;\n const declared = Object.keys(contents) as BodyContentType[];\n const requested = contentTypeFromHeader(request.headers.get('content-type'));\n // Pick the schema matching the request's content-type; fall back to JSON when the\n // header is missing/unrecognized but JSON is on offer (so header-less posts still work).\n const chosen: BodyContentType | undefined =\n requested && contents[requested] ? requested : contents.json ? 'json' : undefined;\n\n if (!chosen) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Unsupported media type.', issues: { accepted: declared } },\n 415,\n 'json',\n ),\n };\n }\n\n let rawBody: unknown;\n try {\n rawBody = await readRawBody(request, chosen);\n } catch (error) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid request body.', issues: error },\n 400,\n 'json',\n ),\n };\n }\n\n const result = await runValidation(contents[chosen], rawBody, 'body');\n if (!result.ok) {\n return {\n ok: false,\n response: createTypedResponse(\n { message: 'Invalid request body.', issues: result.issues },\n 400,\n 'json',\n ),\n };\n }\n\n validated.body = declared.length > 1 ? { type: chosen, data: result.value } : result.value;\n }\n\n return { ok: true, validated };\n}\n"],"mappings":"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","names":["serveNode"]}