@boon4681/giri 0.0.3-alpha-5 → 0.0.3-alpha-7
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 +22 -0
- package/dist/adapters/hono.d.ts +6 -11
- package/dist/adapters/hono.js.map +1 -1
- package/dist/cli.js +118 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +120 -44
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +3 -3
- package/dist/runtime.js +54 -25
- package/dist/runtime.js.map +1 -1
- package/dist/runtime.mjs +54 -25
- package/dist/runtime.mjs.map +1 -1
- package/dist/{types-DZ-14IP6.d.ts → types-CL0hTMdC.d.ts} +45 -3
- package/dist/{validation-Cma_ytxd.d.ts → validation-j3KnjVYf.d.ts} +14 -3
- package/dist/validators/valibot.d.ts +1 -1
- package/dist/validators/valibot.js.map +1 -1
- package/dist/validators/zod.d.ts +1 -1
- package/dist/validators/zod.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -174,6 +174,28 @@ export const auth = defineMiddleware<{ userId: string }>(
|
|
|
174
174
|
);
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
`defineMiddleware` can also own a body or query validator. Validation runs before the middleware,
|
|
178
|
+
`c.req.valid(...)` is typed inside it, and every downstream handler receives the same typed input.
|
|
179
|
+
The validator is also included in generated OpenAPI:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const pagination = defineMiddleware(
|
|
183
|
+
{
|
|
184
|
+
query: zod.query(z.object({
|
|
185
|
+
page: z.coerce.number().int().positive(),
|
|
186
|
+
})),
|
|
187
|
+
},
|
|
188
|
+
async (c, next) => {
|
|
189
|
+
const { page } = c.req.valid("query");
|
|
190
|
+
c.set("page", page);
|
|
191
|
+
await next();
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Only one applied layer may own each target (`body` or `query`). Giri reports a conflict during app
|
|
197
|
+
construction if middleware and a route export, or two middleware, define the same target.
|
|
198
|
+
|
|
177
199
|
Hide a route or subtree from `openapi.json` with `export const openapi = false` (in a verb file
|
|
178
200
|
or a `+shared.ts`). Hidden routes still serve normally.
|
|
179
201
|
|
package/dist/adapters/hono.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextVariableMap, Hono, MiddlewareHandler } from 'hono';
|
|
2
|
-
import { M as Middleware, V as ValidatedInput, G as GiriAdapter } from '../types-
|
|
2
|
+
import { M as Middleware, V as ValidatedInput, G as GiriAdapter } from '../types-CL0hTMdC.js';
|
|
3
3
|
|
|
4
4
|
type HonoGiriApp = Hono;
|
|
5
5
|
type HonoContextVars = {
|
|
@@ -8,12 +8,12 @@ type HonoContextVars = {
|
|
|
8
8
|
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
9
9
|
/**
|
|
10
10
|
* The `Variables` a single Hono middleware declares on its own `Env` (`MiddlewareHandler<{ Variables }>`).
|
|
11
|
-
* This
|
|
12
|
-
*
|
|
11
|
+
* This scoped typing travels with the handler. A bare `MiddlewareHandler` has no scoped variables,
|
|
12
|
+
* so it falls back to Hono's `ContextVariableMap` for plugins such as `@hono/oauth-providers`.
|
|
13
13
|
*/
|
|
14
14
|
type HonoHandlerVars<H> = H extends MiddlewareHandler<infer E> ? E extends {
|
|
15
15
|
Variables: infer V;
|
|
16
|
-
} ? IsAny<V> extends true ?
|
|
16
|
+
} ? IsAny<V> extends true ? HonoContextVars : V extends Record<string, unknown> ? V : HonoContextVars : HonoContextVars : HonoContextVars;
|
|
17
17
|
/** Intersect the scoped Variables of every handler passed to `fromHono`. */
|
|
18
18
|
type MergeHandlerVars<H extends readonly unknown[]> = H extends readonly [infer Head, ...infer Tail] ? HonoHandlerVars<Head> & MergeHandlerVars<Tail> : {};
|
|
19
19
|
/**
|
|
@@ -37,13 +37,8 @@ type MergeHandlerVars<H extends readonly unknown[]> = H extends readonly [infer
|
|
|
37
37
|
*
|
|
38
38
|
* Vars are inferred from each handler's own `Env["Variables"]` (Hono's scoped var typing), so a
|
|
39
39
|
* middleware typed `MiddlewareHandler<{ Variables: { … } }>` types `c.get` automatically, scoped to
|
|
40
|
-
* this folder's chain.
|
|
41
|
-
*
|
|
42
|
-
* The inference deliberately does NOT read Hono's global `ContextVariableMap`: plugins augment that
|
|
43
|
-
* map process-wide (e.g. `@hono/oauth-providers` adds `user-google`), so reading it would leak every
|
|
44
|
-
* plugin's vars onto every route carrying any `fromHono` middleware. For such plugins (their handler
|
|
45
|
-
* is a bare `MiddlewareHandler`), pass the vars explicitly: `fromHono<{ "user-google": GoogleUser }>(…)`.
|
|
46
|
-
* Use `fromHono<HonoContextVars>(…)` to opt into the whole global map.
|
|
40
|
+
* this folder's chain. Bare middleware falls back to Hono's `ContextVariableMap`, which preserves
|
|
41
|
+
* plugin augmentations such as `user-google` from `@hono/oauth-providers`.
|
|
47
42
|
*/
|
|
48
43
|
declare function fromHono<Vars extends Record<string, unknown> = never, H extends MiddlewareHandler[] = MiddlewareHandler[]>(...handlers: H): Middleware<Record<string, string>, ValidatedInput, [Vars] extends [never] ? MergeHandlerVars<H> : Vars>;
|
|
49
44
|
declare function hono(): GiriAdapter<HonoGiriApp>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/hono.ts","../../src/types.ts","../../src/context.ts","../../src/logger.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 { parse, parseSigned, serialize, serializeSigned } from 'hono/utils/cookie';\nimport {\n composeMiddleware,\n createContext,\n isTypedResponse,\n toResponse,\n typedResponseToResponse,\n} from '../context';\nimport { log } from '../logger';\nimport { nativeContextBrand } from '../types';\nimport type {\n Context as GiriContext,\n CookieJarFactory,\n GiriAdapter,\n GiriRouteRegistration,\n Middleware,\n ValidatedInput,\n} from '../types';\nimport { prepareRequestInput } from '../validation';\n\nconst honoCookieJar: CookieJarFactory = ({ request, append, secret }) => {\n const header = request.headers.get('cookie') ?? '';\n const requireSecret = (): string => {\n if (!secret) {\n throw new Error('Signed cookies require `cookieSecret` in giri.config.');\n }\n return secret;\n };\n\n return {\n get: (name) => parse(header, name)[name],\n all: () => parse(header),\n set: (name, value, options) => append(serialize(name, value, options)),\n delete: (name, options) =>\n append(serialize(name, '', { ...options, maxAge: 0, expires: new Date(0) })),\n getSigned: async (name) => (await parseSigned(header, requireSecret(), name))[name],\n setSigned: async (name, value, options) =>\n append(await serializeSigned(name, value, requireSecret(), options)),\n };\n};\n\nexport type HonoGiriApp = Hono;\nexport type HonoContextVars = { [K in keyof ContextVariableMap]: ContextVariableMap[K] };\n\ntype IsAny<T> = 0 extends 1 & T ? true : false;\n\n/**\n * The `Variables` a single Hono middleware declares on its own `Env` (`MiddlewareHandler<{ Variables }>`).\n * This is Hono's *scoped* var typing - unlike the global `ContextVariableMap`, it travels with the\n * handler. A handler with no typed Variables (e.g. `cors()`, whose `Env` is `any`) contributes `{}`.\n */\ntype HonoHandlerVars<H> = H extends MiddlewareHandler<infer E>\n ? E extends { Variables: infer V }\n ? IsAny<V> extends true\n ? {}\n : V extends Record<string, unknown>\n ? V\n : {}\n : {}\n : {};\n\n/** Intersect the scoped Variables of every handler passed to `fromHono`. */\ntype MergeHandlerVars<H extends readonly unknown[]> = H extends readonly [infer Head, ...infer Tail]\n ? HonoHandlerVars<Head> & MergeHandlerVars<Tail>\n : {};\n\nasync function routeHandler(honoContext: HonoContext, route: GiriRouteRegistration): Promise<Response> {\n try {\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 cookieSecret: route.cookieSecret,\n cookies: honoCookieJar,\n });\n const result = await composeMiddleware(route.middleware, route.handle, context);\n return toResponse(result, context);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n log.error(`${route.method} ${route.path} - ${err.message}`, 'request');\n console.error(err.stack ?? err);\n return new Response('Internal Server Error', { status: 500 });\n }\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: …, scope: [\"openid\"] })),\n * );\n * // downstream handler (only under this folder): 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 *\n * Vars are inferred from each handler's own `Env[\"Variables\"]` (Hono's scoped var typing), so a\n * middleware typed `MiddlewareHandler<{ Variables: { … } }>` types `c.get` automatically, scoped to\n * this folder's chain. A bare `cors()` (whose `Env` is `any`) contributes nothing.\n *\n * The inference deliberately does NOT read Hono's global `ContextVariableMap`: plugins augment that\n * map process-wide (e.g. `@hono/oauth-providers` adds `user-google`), so reading it would leak every\n * plugin's vars onto every route carrying any `fromHono` middleware. For such plugins (their handler\n * is a bare `MiddlewareHandler`), pass the vars explicitly: `fromHono<{ \"user-google\": GoogleUser }>(…)`.\n * Use `fromHono<HonoContextVars>(…)` to opt into the whole global map.\n */\nexport function fromHono<\n Vars extends Record<string, unknown> = never,\n H extends MiddlewareHandler[] = MiddlewareHandler[],\n>(\n ...handlers: H\n): Middleware<Record<string, string>, ValidatedInput, [Vars] extends [never] ? MergeHandlerVars<H> : 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' | 'html';\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\n/** Attributes for a `Set-Cookie` header. `path` defaults to `/`. */\nexport interface CookieOptions {\n domain?: string;\n path?: string;\n /** Lifetime in seconds. */\n maxAge?: number;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none';\n partitioned?: boolean;\n priority?: 'Low' | 'Medium' | 'High' | 'low' | 'medium' | 'high';\n}\n\n/**\n * Cookie read/write, implemented per adapter with its runtime's native helpers. giri core\n * supplies the {@link CookieSink} (where to read from / write to); the adapter owns encoding.\n */\nexport interface CookieJar {\n get(name: string): string | undefined;\n all(): Record<string, string>;\n set(name: string, value: string, options?: CookieOptions): void;\n delete(name: string, options?: CookieOptions): void;\n getSigned(name: string): Promise<string | false | undefined>;\n setSigned(name: string, value: string, options?: CookieOptions): Promise<void>;\n}\n\n/** What core hands an adapter's cookie jar: the request to read from, the response sink to write to. */\nexport interface CookieSink {\n /** The incoming request, for reading the `Cookie` header. */\n request: Request;\n /** Append one already-serialized `Set-Cookie` header value to the response. */\n append(setCookieHeader: string): void;\n /** The configured `cookieSecret`, if any (for signed cookies). */\n secret?: string;\n}\n\n/** Builds a {@link CookieJar} bound to one request's {@link CookieSink}. Each adapter provides one. */\nexport type CookieJarFactory = (sink: CookieSink) => CookieJar;\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 /** Read a request cookie by name, or `undefined` if absent. */\n cookie(name: string): string | undefined;\n /** All request cookies as a name: value map. */\n cookies(): Record<string, string>;\n /**\n * Read and verify a signed cookie. Resolves to the original value, `false` if the\n * signature was tampered with, or `undefined` if the cookie is absent. Requires\n * `cookieSecret` in `giri.config`.\n */\n signedCookie(name: string): Promise<string | false | undefined>;\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 /** An HTML response (`text/html`). Like `text`, the body is a string. */\n html<S extends StatusCode = 200>(\n html: string,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<string, S, 'html'>;\n /** A raw-body response - string, stream, buffer, FormData, … (not documented in OpenAPI). */\n body(data: BodyInit | null, status?: StatusCode, headers?: HeadersInit): Response;\n /** Alias of `body`, mirroring Hono's `c.newResponse`. */\n newResponse(data: BodyInit | null, status?: StatusCode, headers?: HeadersInit): Response;\n /** A redirect (defaults to 302) with the `Location` header set. */\n redirect(location: string, status?: StatusCode): Response;\n /** A 404 Not Found response. */\n notFound(): Response;\n /**\n * Set a response header applied to whatever this handler returns. Pass `{ append: true }` to add\n * another value (e.g. `Set-Cookie`); omit `value` to delete. Mirrors Hono's `c.header`.\n */\n header(name: string, value?: string, options?: { append?: boolean }): void;\n /** Default status for `body`/`redirect`, and for `json`/`text`/`html` when no status arg is given. */\n status(code: StatusCode): void;\n /**\n * Set a response cookie via `Set-Cookie`. Pass `value: null` to delete it (send the\n * same `path`/`domain` you set it with). Stacks with other cookies set this request.\n */\n cookie(name: string, value: string | null, options?: CookieOptions): void;\n /** Set an HMAC-signed cookie. Requires `cookieSecret` in `giri.config`. */\n signedCookie(name: string, value: string, options?: CookieOptions): Promise<void>;\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 /**\n * OpenAPI tags - the grouping in doc viewers. On a `+shared.ts` they apply to every route in the\n * folder; the chain is merged and de-duplicated, so a route's tags add to\n * its folders'.\n */\n tags?: string[];\n /** Short operation summary. Cascades down the chain (a verb file overrides its folders). */\n summary?: string;\n /** Longer operation description. Cascades down the chain (a verb file overrides its folders). */\n description?: string;\n /** Marks the operation(s) deprecated. On a `+shared.ts` it deprecates the whole folder. */\n deprecated?: boolean;\n /** Unique operationId. Verb-file only - it is never inherited from a `+shared.ts`. */\n operationId?: string;\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 /** Secret for signing/verifying cookies (`c.signedCookie`), from `config.cookieSecret`. */\n cookieSecret?: string;\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 /** Secret used to sign/verify cookies via `c.signedCookie` / `c.req.signedCookie`. */\n cookieSecret?: string;\n}\n\nexport interface GiriPaths {\n cwd: string;\n routesDir: string;\n outDir: string;\n}\n","import {\n type CookieJar,\n type CookieJarFactory,\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\n/** Imperative response state set via `c.header()` / `c.status()`, merged in `toResponse`. */\ninterface PendingResponse {\n headers: Headers;\n status?: StatusCode;\n}\n\nconst pendingResponseBrand: unique symbol = Symbol('giri.pending-response');\n\nfunction getPending(context: Context): PendingResponse | undefined {\n return (context as unknown as Record<symbol, PendingResponse | undefined>)[pendingResponseBrand];\n}\n\n/** Used when the active adapter provides no cookie jar; every cookie call throws. */\nconst unsupportedCookieJar: CookieJar = {\n get: cookiesUnsupported,\n all: cookiesUnsupported,\n set: cookiesUnsupported,\n delete: cookiesUnsupported,\n getSigned: cookiesUnsupported,\n setSigned: cookiesUnsupported,\n};\n\nfunction cookiesUnsupported(): never {\n throw new Error('The active adapter does not support cookies.');\n}\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 /** Secret for `c.signedCookie` / `c.req.signedCookie` (from `config.cookieSecret`). */\n cookieSecret?: string;\n /** The adapter's cookie jar, built from its runtime's native helpers. */\n cookies?: CookieJarFactory;\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 // Headers/status set imperatively via c.header()/c.status(); merged into the final response.\n const pending: PendingResponse = { headers: new Headers() };\n const defaultStatus = (): StatusCode => pending.status ?? 200;\n\n // Cookies are a runtime concern: the adapter owns reading/encoding/signing via its native\n // helpers. Core only hands it the sink (request in, Set-Cookie onto the pending response).\n // No adapter jar => cookies aren't supported, and using them throws.\n const cookies: CookieJar = options.cookies\n ? options.cookies({\n request: options.request,\n append: (header) => pending.headers.append('set-cookie', header),\n secret: options.cookieSecret,\n })\n : unsupportedCookieJar;\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 cookie: (name) => cookies.get(name),\n cookies: () => cookies.all(),\n signedCookie: (name) => cookies.getSigned(name),\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, headers) =>\n createTypedResponse(data, (status ?? defaultStatus()) as never, 'json', headers),\n text: (text, status, headers) =>\n createTypedResponse(text, (status ?? defaultStatus()) as never, 'text', headers),\n html: (html, status, headers) =>\n createTypedResponse(html, (status ?? defaultStatus()) as never, 'html', headers),\n body: (data, status, headers) =>\n new Response(data, { status: status ?? defaultStatus(), headers }),\n newResponse: (data, status, headers) =>\n new Response(data, { status: status ?? defaultStatus(), headers }),\n redirect: (location, status) =>\n new Response(null, { status: status ?? 302, headers: { Location: location } }),\n notFound: () => new Response('404 Not Found', { status: 404 }),\n header: (name, value, options) => {\n if (value === undefined) {\n pending.headers.delete(name);\n } else if (options?.append) {\n pending.headers.append(name, value);\n } else {\n pending.headers.set(name, value);\n }\n },\n status: (code) => {\n pending.status = code;\n },\n cookie: (name, value, options) => {\n if (value === null) {\n cookies.delete(name, options);\n } else {\n cookies.set(name, value, options);\n }\n },\n signedCookie: (name, value, options) => cookies.setSigned(name, value, options),\n };\n\n (context as unknown as Record<symbol, unknown>)[nativeContextBrand] = options.native;\n (context as unknown as Record<symbol, unknown>)[pendingResponseBrand] = pending;\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 if (response.format === 'html' && !headers.has('content-type')) {\n headers.set('content-type', 'text/html; 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\n/**\n * Convert a handler's return value to a real `Response`, then merge any headers set imperatively via\n * `c.header()` (the response's own headers win; pending ones fill the gaps). Pass the `context` so\n * those imperative headers are applied; without it the response is returned unchanged.\n */\nexport function toResponse(response: HandlerResponse, context?: Context): Response {\n const base = isTypedResponse(response) ? typedResponseToResponse(response) : response;\n const pending = context ? getPending(context) : undefined;\n if (!pending) {\n return base;\n }\n\n let hasPending = false;\n pending.headers.forEach(() => {\n hasPending = true;\n });\n if (!hasPending) {\n return base;\n }\n\n const headers = new Headers(base.headers);\n pending.headers.forEach((value, key) => {\n // Set-Cookie is multi-valued; forEach coalesces it into one comma-joined string, so\n // handle it separately below to keep each cookie its own header.\n if (key === 'set-cookie') {\n return;\n }\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n });\n for (const cookie of pending.headers.getSetCookie?.() ?? []) {\n headers.append('set-cookie', cookie);\n }\n return new Response(base.body, { status: base.status, statusText: base.statusText, headers });\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","/**\n * A tiny, dependency-free logger styled after Vite's dev output:\n * `HH:MM:SS [giri] (scope) message`. Colors auto-disable for non-TTY output, `NO_COLOR`,\n * or `TERM=dumb`, so piped/CI logs stay clean.\n */\n\nconst noColor =\n !process.stdout.isTTY ||\n process.env.NO_COLOR !== undefined ||\n process.env.TERM === 'dumb' ||\n process.env.FORCE_COLOR === '0';\n\nfunction paint(open: number, close: number): (text: string) => string {\n return (text) => (noColor ? text : `\\x1b[${open}m${text}\\x1b[${close}m`);\n}\n\nexport const color = {\n dim: paint(2, 22),\n bold: paint(1, 22),\n red: paint(31, 39),\n green: paint(32, 39),\n yellow: paint(33, 39),\n blue: paint(34, 39),\n magenta: paint(35, 39),\n cyan: paint(36, 39),\n gray: paint(90, 39),\n};\n\n/** Green for paths/values, like Vite highlights updated files. */\nexport const highlight = (text: string): string => color.green(text);\n/** Dim for secondary details (counts, durations). */\nexport const muted = (text: string): string => color.dim(text);\n\nfunction timestamp(): string {\n const now = new Date();\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;\n}\n\nconst TAG = 'giri';\n\nfunction line(tag: string, message: string, scope?: string): string {\n const parts = [color.gray(timestamp()), tag];\n if (scope) {\n parts.push(color.dim(`(${scope})`));\n }\n parts.push(message);\n return parts.join(' ');\n}\n\nconst tag = {\n info: color.bold(color.cyan(`[${TAG}]`)),\n warn: color.bold(color.yellow(`[${TAG}]`)),\n error: color.bold(color.red(`[${TAG}]`)),\n};\n\n/** Preserve stack frames for real errors while still accepting arbitrary thrown values. */\nexport function formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.stack ?? `${error.name}: ${error.message}`;\n }\n return String(error);\n}\n\nexport const log = {\n info(message: string, scope?: string): void {\n console.log(line(tag.info, message, scope));\n },\n success(message: string, scope?: string): void {\n console.log(line(tag.info, color.green(message), scope));\n },\n warn(message: string, scope?: string): void {\n console.warn(line(tag.warn, color.yellow(message), scope));\n },\n error(message: string, scope?: string): void {\n console.error(line(tag.error, color.red(message), scope));\n },\n ready(url: string): void {\n console.log(line(tag.info, `${color.green('ready')} on ${color.cyan(url)}`));\n },\n change(verb: string, path: string, count?: number): void {\n const suffix = count && count > 1 ? ` ${color.dim(`(x${count})`)}` : '';\n console.log(line(tag.info, `${color.green(verb)} ${highlight(path)}${suffix}`, 'watch'));\n },\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":["options","tag","serveNode"]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/hono.ts","../../src/types.ts","../../src/context.ts","../../src/logger.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 { parse, parseSigned, serialize, serializeSigned } from 'hono/utils/cookie';\nimport {\n composeMiddleware,\n createContext,\n isTypedResponse,\n toResponse,\n typedResponseToResponse,\n} from '../context';\nimport { log } from '../logger';\nimport { nativeContextBrand } from '../types';\nimport type {\n Context as GiriContext,\n CookieJarFactory,\n GiriAdapter,\n GiriRouteRegistration,\n Middleware,\n ValidatedInput,\n} from '../types';\nimport { prepareRequestInput } from '../validation';\n\nconst honoCookieJar: CookieJarFactory = ({ request, append, secret }) => {\n const header = request.headers.get('cookie') ?? '';\n const requireSecret = (): string => {\n if (!secret) {\n throw new Error('Signed cookies require `cookieSecret` in giri.config.');\n }\n return secret;\n };\n\n return {\n get: (name) => parse(header, name)[name],\n all: () => parse(header),\n set: (name, value, options) => append(serialize(name, value, options)),\n delete: (name, options) =>\n append(serialize(name, '', { ...options, maxAge: 0, expires: new Date(0) })),\n getSigned: async (name) => (await parseSigned(header, requireSecret(), name))[name],\n setSigned: async (name, value, options) =>\n append(await serializeSigned(name, value, requireSecret(), options)),\n };\n};\n\nexport type HonoGiriApp = Hono;\nexport type HonoContextVars = { [K in keyof ContextVariableMap]: ContextVariableMap[K] };\n\ntype IsAny<T> = 0 extends 1 & T ? true : false;\n\n/**\n * The `Variables` a single Hono middleware declares on its own `Env` (`MiddlewareHandler<{ Variables }>`).\n * This scoped typing travels with the handler. A bare `MiddlewareHandler` has no scoped variables,\n * so it falls back to Hono's `ContextVariableMap` for plugins such as `@hono/oauth-providers`.\n */\ntype HonoHandlerVars<H> = H extends MiddlewareHandler<infer E>\n ? E extends { Variables: infer V }\n ? IsAny<V> extends true\n ? HonoContextVars\n : V extends Record<string, unknown>\n ? V\n : HonoContextVars\n : HonoContextVars\n : HonoContextVars;\n\n/** Intersect the scoped Variables of every handler passed to `fromHono`. */\ntype MergeHandlerVars<H extends readonly unknown[]> = H extends readonly [infer Head, ...infer Tail]\n ? HonoHandlerVars<Head> & MergeHandlerVars<Tail>\n : {};\n\nasync function routeHandler(honoContext: HonoContext, route: GiriRouteRegistration): Promise<Response> {\n try {\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 cookieSecret: route.cookieSecret,\n cookies: honoCookieJar,\n });\n const result = await composeMiddleware(route.middleware, route.handle, context);\n return toResponse(result, context);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n log.error(`${route.method} ${route.path} - ${err.message}`, 'request');\n console.error(err.stack ?? err);\n return new Response('Internal Server Error', { status: 500 });\n }\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: …, scope: [\"openid\"] })),\n * );\n * // downstream handler (only under this folder): 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 *\n * Vars are inferred from each handler's own `Env[\"Variables\"]` (Hono's scoped var typing), so a\n * middleware typed `MiddlewareHandler<{ Variables: { … } }>` types `c.get` automatically, scoped to\n * this folder's chain. Bare middleware falls back to Hono's `ContextVariableMap`, which preserves\n * plugin augmentations such as `user-google` from `@hono/oauth-providers`.\n */\nexport function fromHono<\n Vars extends Record<string, unknown> = never,\n H extends MiddlewareHandler[] = MiddlewareHandler[],\n>(\n ...handlers: H\n): Middleware<Record<string, string>, ValidatedInput, [Vars] extends [never] ? MergeHandlerVars<H> : 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' | 'html';\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\n/** Attributes for a `Set-Cookie` header. `path` defaults to `/`. */\nexport interface CookieOptions {\n domain?: string;\n path?: string;\n /** Lifetime in seconds. */\n maxAge?: number;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none';\n partitioned?: boolean;\n priority?: 'Low' | 'Medium' | 'High' | 'low' | 'medium' | 'high';\n}\n\n/**\n * Cookie read/write, implemented per adapter with its runtime's native helpers. giri core\n * supplies the {@link CookieSink} (where to read from / write to); the adapter owns encoding.\n */\nexport interface CookieJar {\n get(name: string): string | undefined;\n all(): Record<string, string>;\n set(name: string, value: string, options?: CookieOptions): void;\n delete(name: string, options?: CookieOptions): void;\n getSigned(name: string): Promise<string | false | undefined>;\n setSigned(name: string, value: string, options?: CookieOptions): Promise<void>;\n}\n\n/** What core hands an adapter's cookie jar: the request to read from, the response sink to write to. */\nexport interface CookieSink {\n /** The incoming request, for reading the `Cookie` header. */\n request: Request;\n /** Append one already-serialized `Set-Cookie` header value to the response. */\n append(setCookieHeader: string): void;\n /** The configured `cookieSecret`, if any (for signed cookies). */\n secret?: string;\n}\n\n/** Builds a {@link CookieJar} bound to one request's {@link CookieSink}. Each adapter provides one. */\nexport type CookieJarFactory = (sink: CookieSink) => CookieJar;\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 /** Read a request cookie by name, or `undefined` if absent. */\n cookie(name: string): string | undefined;\n /** All request cookies as a name: value map. */\n cookies(): Record<string, string>;\n /**\n * Read and verify a signed cookie. Resolves to the original value, `false` if the\n * signature was tampered with, or `undefined` if the cookie is absent. Requires\n * `cookieSecret` in `giri.config`.\n */\n signedCookie(name: string): Promise<string | false | undefined>;\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 /** An HTML response (`text/html`). Like `text`, the body is a string. */\n html<S extends StatusCode = 200>(\n html: string,\n status?: S,\n headers?: HeadersInit,\n ): TypedResponse<string, S, 'html'>;\n /** A raw-body response - string, stream, buffer, FormData, … (not documented in OpenAPI). */\n body(data: BodyInit | null, status?: StatusCode, headers?: HeadersInit): Response;\n /** Alias of `body`, mirroring Hono's `c.newResponse`. */\n newResponse(data: BodyInit | null, status?: StatusCode, headers?: HeadersInit): Response;\n /** A redirect (defaults to 302) with the `Location` header set. */\n redirect(location: string, status?: StatusCode): Response;\n /** A 404 Not Found response. */\n notFound(): Response;\n /**\n * Set a response header applied to whatever this handler returns. Pass `{ append: true }` to add\n * another value (e.g. `Set-Cookie`); omit `value` to delete. Mirrors Hono's `c.header`.\n */\n header(name: string, value?: string, options?: { append?: boolean }): void;\n /** Default status for `body`/`redirect`, and for `json`/`text`/`html` when no status arg is given. */\n status(code: StatusCode): void;\n /**\n * Set a response cookie via `Set-Cookie`. Pass `value: null` to delete it (send the\n * same `path`/`domain` you set it with). Stacks with other cookies set this request.\n */\n cookie(name: string, value: string | null, options?: CookieOptions): void;\n /** Set an HMAC-signed cookie. Requires `cookieSecret` in `giri.config`. */\n signedCookie(name: string, value: string, options?: CookieOptions): Promise<void>;\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 /** Validate and expose this request body before the middleware chain runs. */\n body?: GiriBodySchema;\n /** Validate and expose these query parameters before the middleware chain runs. */\n query?: GiriInputSchema;\n openapi?: MiddlewareOpenApi;\n}\n\ndeclare const middlewareTypesBrand: unique symbol;\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 readonly [middlewareTypesBrand]?: {\n params: Params;\n input: Input;\n vars: Vars;\n };\n body?: GiriBodySchema;\n query?: GiriInputSchema;\n openapi?: MiddlewareOpenApi;\n}\n\n/** The context vars a middleware injects (its `Vars` type parameter). */\nexport type VarsOf<M> = M extends {\n readonly [middlewareTypesBrand]?: { vars: infer V extends Record<string, unknown> };\n}\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<any, any, 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/** Derive validated input from middleware metadata added by `defineMiddleware({ body, query }, ...)`. */\nexport type InputOfMiddleware<M> = PruneNever<{\n body: M extends { body: infer B } ? ValidBody<B> : never;\n query: M extends { query: infer Q } ? ValidQuery<Q> : never;\n}>;\n\n/** Intersect the validated inputs contributed by a tuple built with `stack(...)`. */\nexport type MergeStackInput<T> = T extends readonly [infer Head, ...infer Rest]\n ? InputOfMiddleware<Head> & MergeStackInput<Rest>\n : {};\n\n/** Derive validated input contributed by a middleware function or stack. */\nexport type InferStackInput<T> = T extends readonly [unknown, ...unknown[]]\n ? MergeStackInput<T>\n : InputOfMiddleware<T>;\n\n/** The validated input contributed by a module's own `middleware` export. */\nexport type MiddlewareInputOf<M> = M extends { middleware: infer Stack }\n ? InferStackInput<Stack>\n : {};\n\n/** Derive the callback input type from `defineMiddleware` options. */\nexport type MiddlewareOptionsInput<O> = PruneNever<{\n body: O extends { body: infer B } ? ValidBody<B> : never;\n query: O extends { query: infer Q } ? ValidQuery<Q> : never;\n}>;\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 /**\n * OpenAPI tags - the grouping in doc viewers. On a `+shared.ts` they apply to every route in the\n * folder; the chain is merged and de-duplicated, so a route's tags add to\n * its folders'.\n */\n tags?: string[];\n /** Short operation summary. Cascades down the chain (a verb file overrides its folders). */\n summary?: string;\n /** Longer operation description. Cascades down the chain (a verb file overrides its folders). */\n description?: string;\n /** Marks the operation(s) deprecated. On a `+shared.ts` it deprecates the whole folder. */\n deprecated?: boolean;\n /** Unique operationId. Verb-file only - it is never inherited from a `+shared.ts`. */\n operationId?: string;\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 /** Secret for signing/verifying cookies (`c.signedCookie`), from `config.cookieSecret`. */\n cookieSecret?: string;\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 /** Secret used to sign/verify cookies via `c.signedCookie` / `c.req.signedCookie`. */\n cookieSecret?: string;\n}\n\nexport interface GiriPaths {\n cwd: string;\n routesDir: string;\n outDir: string;\n}\n","import {\n type CookieJar,\n type CookieJarFactory,\n type Services,\n type Context,\n type HandlerResponse,\n type GiriBodySchema,\n type GiriInputSchema,\n type Middleware,\n type MiddlewareOptions,\n type MiddlewareOptionsInput,\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\n/** Imperative response state set via `c.header()` / `c.status()`, merged in `toResponse`. */\ninterface PendingResponse {\n headers: Headers;\n status?: StatusCode;\n}\n\nconst pendingResponseBrand: unique symbol = Symbol('giri.pending-response');\n\nfunction getPending(context: Context): PendingResponse | undefined {\n return (context as unknown as Record<symbol, PendingResponse | undefined>)[pendingResponseBrand];\n}\n\n/** Used when the active adapter provides no cookie jar; every cookie call throws. */\nconst unsupportedCookieJar: CookieJar = {\n get: cookiesUnsupported,\n all: cookiesUnsupported,\n set: cookiesUnsupported,\n delete: cookiesUnsupported,\n getSigned: cookiesUnsupported,\n setSigned: cookiesUnsupported,\n};\n\nfunction cookiesUnsupported(): never {\n throw new Error('The active adapter does not support cookies.');\n}\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 /** Secret for `c.signedCookie` / `c.req.signedCookie` (from `config.cookieSecret`). */\n cookieSecret?: string;\n /** The adapter's cookie jar, built from its runtime's native helpers. */\n cookies?: CookieJarFactory;\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 // Headers/status set imperatively via c.header()/c.status(); merged into the final response.\n const pending: PendingResponse = { headers: new Headers() };\n const defaultStatus = (): StatusCode => pending.status ?? 200;\n\n // Cookies are a runtime concern: the adapter owns reading/encoding/signing via its native\n // helpers. Core only hands it the sink (request in, Set-Cookie onto the pending response).\n // No adapter jar => cookies aren't supported, and using them throws.\n const cookies: CookieJar = options.cookies\n ? options.cookies({\n request: options.request,\n append: (header) => pending.headers.append('set-cookie', header),\n secret: options.cookieSecret,\n })\n : unsupportedCookieJar;\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 cookie: (name) => cookies.get(name),\n cookies: () => cookies.all(),\n signedCookie: (name) => cookies.getSigned(name),\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, headers) =>\n createTypedResponse(data, (status ?? defaultStatus()) as never, 'json', headers),\n text: (text, status, headers) =>\n createTypedResponse(text, (status ?? defaultStatus()) as never, 'text', headers),\n html: (html, status, headers) =>\n createTypedResponse(html, (status ?? defaultStatus()) as never, 'html', headers),\n body: (data, status, headers) =>\n new Response(data, { status: status ?? defaultStatus(), headers }),\n newResponse: (data, status, headers) =>\n new Response(data, { status: status ?? defaultStatus(), headers }),\n redirect: (location, status) =>\n new Response(null, { status: status ?? 302, headers: { Location: location } }),\n notFound: () => new Response('404 Not Found', { status: 404 }),\n header: (name, value, options) => {\n if (value === undefined) {\n pending.headers.delete(name);\n } else if (options?.append) {\n pending.headers.append(name, value);\n } else {\n pending.headers.set(name, value);\n }\n },\n status: (code) => {\n pending.status = code;\n },\n cookie: (name, value, options) => {\n if (value === null) {\n cookies.delete(name, options);\n } else {\n cookies.set(name, value, options);\n }\n },\n signedCookie: (name, value, options) => cookies.setSigned(name, value, options),\n };\n\n (context as unknown as Record<symbol, unknown>)[nativeContextBrand] = options.native;\n (context as unknown as Record<symbol, unknown>)[pendingResponseBrand] = pending;\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 if (response.format === 'html' && !headers.has('content-type')) {\n headers.set('content-type', 'text/html; 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\n/**\n * Convert a handler's return value to a real `Response`, then merge any headers set imperatively via\n * `c.header()` (the response's own headers win; pending ones fill the gaps). Pass the `context` so\n * those imperative headers are applied; without it the response is returned unchanged.\n */\nexport function toResponse(response: HandlerResponse, context?: Context): Response {\n const base = isTypedResponse(response) ? typedResponseToResponse(response) : response;\n const pending = context ? getPending(context) : undefined;\n if (!pending) {\n return base;\n }\n\n let hasPending = false;\n pending.headers.forEach(() => {\n hasPending = true;\n });\n if (!hasPending) {\n return base;\n }\n\n const headers = new Headers(base.headers);\n pending.headers.forEach((value, key) => {\n // Set-Cookie is multi-valued; forEach coalesces it into one comma-joined string, so\n // handle it separately below to keep each cookie its own header.\n if (key === 'set-cookie') {\n return;\n }\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n });\n for (const cookie of pending.headers.getSetCookie?.() ?? []) {\n headers.append('set-cookie', cookie);\n }\n return new Response(base.body, { status: base.status, statusText: base.statusText, headers });\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\ntype MiddlewareMetadata<Options extends MiddlewareOptions> =\n ('body' extends keyof Options ? { readonly body: Options['body'] & GiriBodySchema } : {}) &\n ('query' extends keyof Options ? { readonly query: Options['query'] & GiriInputSchema } : {});\n\ntype DefinedMiddleware<\n Options extends MiddlewareOptions,\n Vars extends Record<string, unknown>,\n> = Middleware<Record<string, string>, MiddlewareOptionsInput<Options>, Vars> &\n MiddlewareMetadata<Options>;\n\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n middleware: AnyMiddleware<Vars>,\n): AnyMiddleware<Vars>;\nexport function defineMiddleware<const Options extends MiddlewareOptions>(\n options: Options,\n middleware: Middleware<Record<string, string>, MiddlewareOptionsInput<Options>, {}>,\n): DefinedMiddleware<Options, {}>;\nexport function defineMiddleware<\n Vars extends Record<string, unknown>,\n const Options extends MiddlewareOptions,\n>(\n options: Options,\n middleware: Middleware<Record<string, string>, MiddlewareOptionsInput<Options>, Vars>,\n): DefinedMiddleware<Options, Vars>;\nexport function defineMiddleware<Vars extends Record<string, unknown> = {}>(\n options: MiddlewareOptions & { body?: never; query?: never },\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.body = optionsOrMiddleware.body;\n maybeMiddleware.query = optionsOrMiddleware.query;\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// Input and Vars are contravariant because they sit in the callback's context parameter.\n// Keep both open so middleware with validator-owned input and injected vars can share a stack.\nexport function stack<T extends Middleware<any, any, any>[]>(...middleware: T): T {\n return middleware;\n}\n","/**\n * A tiny, dependency-free logger styled after Vite's dev output:\n * `HH:MM:SS [giri] (scope) message`. Colors auto-disable for non-TTY output, `NO_COLOR`,\n * or `TERM=dumb`, so piped/CI logs stay clean.\n */\n\nconst noColor =\n !process.stdout.isTTY ||\n process.env.NO_COLOR !== undefined ||\n process.env.TERM === 'dumb' ||\n process.env.FORCE_COLOR === '0';\n\nfunction paint(open: number, close: number): (text: string) => string {\n return (text) => (noColor ? text : `\\x1b[${open}m${text}\\x1b[${close}m`);\n}\n\nexport const color = {\n dim: paint(2, 22),\n bold: paint(1, 22),\n red: paint(31, 39),\n green: paint(32, 39),\n yellow: paint(33, 39),\n blue: paint(34, 39),\n magenta: paint(35, 39),\n cyan: paint(36, 39),\n gray: paint(90, 39),\n};\n\n/** Green for paths/values, like Vite highlights updated files. */\nexport const highlight = (text: string): string => color.green(text);\n/** Dim for secondary details (counts, durations). */\nexport const muted = (text: string): string => color.dim(text);\n\nfunction timestamp(): string {\n const now = new Date();\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;\n}\n\nconst TAG = 'giri';\n\nfunction line(tag: string, message: string, scope?: string): string {\n const parts = [color.gray(timestamp()), tag];\n if (scope) {\n parts.push(color.dim(`(${scope})`));\n }\n parts.push(message);\n return parts.join(' ');\n}\n\nconst tag = {\n info: color.bold(color.cyan(`[${TAG}]`)),\n warn: color.bold(color.yellow(`[${TAG}]`)),\n error: color.bold(color.red(`[${TAG}]`)),\n};\n\n/** Preserve stack frames for real errors while still accepting arbitrary thrown values. */\nexport function formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.stack ?? `${error.name}: ${error.message}`;\n }\n return String(error);\n}\n\nexport const log = {\n info(message: string, scope?: string): void {\n console.log(line(tag.info, message, scope));\n },\n success(message: string, scope?: string): void {\n console.log(line(tag.info, color.green(message), scope));\n },\n warn(message: string, scope?: string): void {\n console.warn(line(tag.warn, color.yellow(message), scope));\n },\n error(message: string, scope?: string): void {\n console.error(line(tag.error, color.red(message), scope));\n },\n ready(url: string): void {\n console.log(line(tag.info, `${color.green('ready')} on ${color.cyan(url)}`));\n },\n change(verb: string, path: string, count?: number): void {\n const suffix = count && count > 1 ? ` ${color.dim(`(x${count})`)}` : '';\n console.log(line(tag.info, `${color.green(verb)} ${highlight(path)}${suffix}`, 'watch'));\n },\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\nexport interface RouteInputSource {\n label: string;\n body?: unknown;\n query?: unknown;\n}\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\n/**\n * Resolve route and middleware validators into the single input contract exposed by\n * `c.req.valid`. A target can only have one owner so validated data is never silently replaced.\n */\nexport function resolveRouteInput(sources: readonly RouteInputSource[]): RouteInput | undefined {\n const input: RouteInput = {};\n const owners: Partial<Record<keyof RouteInput, string>> = {};\n\n for (const source of sources) {\n if (source.body !== undefined) {\n if (!isGiriBodySchema(source.body)) {\n throw new Error(\n `${source.label}: \"body\" must be wrapped with a validator, e.g. \\`zod.body({ json: ... })\\` from @boon4681/giri/validators/zod.`,\n );\n }\n if (input.body) {\n throw new Error(\n `${source.label}: body validator conflicts with the body validator from ${owners.body}.`,\n );\n }\n input.body = source.body;\n owners.body = source.label;\n }\n\n if (source.query !== undefined) {\n if (!isGiriInputSchema(source.query)) {\n throw new Error(\n `${source.label}: \"query\" must be wrapped with a validator, e.g. \\`zod.query(...)\\` from @boon4681/giri/validators/zod.`,\n );\n }\n if (input.query) {\n throw new Error(\n `${source.label}: query validator conflicts with the query validator from ${owners.query}.`,\n );\n }\n input.query = source.query;\n owners.query = source.label;\n }\n }\n\n return input.body || input.query ? input : undefined;\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAmC;AACnC,kBAAqB;AAErB,oBAA+D;;;ACGxD,IAAM,qBAAoC,uBAAO,IAAI,qBAAqB;AAC1E,IAAM,qBAAoC,uBAAO,IAAI,qBAAqB;AA4P1E,IAAM,mBAAkC,uBAAO,IAAI,mBAAmB;;;AChP7E,IAAM,kBAAkB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAQzD,IAAM,uBAAsC,uBAAO,uBAAuB;AAE1E,SAAS,WAAW,SAA+C;AAC/D,SAAQ,QAAmE,oBAAoB;AACnG;AAGA,IAAM,uBAAkC;AAAA,EACpC,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AACf;AAEA,SAAS,qBAA4B;AACjC,QAAM,IAAI,MAAM,8CAA8C;AAClE;AAkBO,SAAS,oBAId,MAAS,QAAW,QAAW,SAA+C;AAC5E,SAAO;AAAA,IACH,CAAC,kBAAkB,GAAG,EAAE,MAAM,QAAQ,OAAO;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;AAEO,SAAS,gBAAgB,OAAiD;AAC7E,SAAO,QAAQ,SAAS,OAAO,UAAU,YAAY,sBAAsB,KAAK;AACpF;AAEO,SAAS,cAGd,SAAsE;AACpE,QAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ,GAAG;AACvC,QAAM,QAAQ,oBAAI,IAAqB;AACvC,QAAM,YAAY,QAAQ,aAAc,CAAC;AAGzC,QAAM,UAA2B,EAAE,SAAS,IAAI,QAAQ,EAAE;AAC1D,QAAM,gBAAgB,MAAkB,QAAQ,UAAU;AAK1D,QAAM,UAAqB,QAAQ,UAC7B,QAAQ,QAAQ;AAAA,IACZ,SAAS,QAAQ;AAAA,IACjB,QAAQ,CAAC,WAAW,QAAQ,QAAQ,OAAO,cAAc,MAAM;AAAA,IAC/D,QAAQ,QAAQ;AAAA,EACpB,CAAC,IACD;AAEN,QAAM,UAAkC;AAAA,IACpC,QAAQ,QAAQ,UAAW,CAAC;AAAA,IAC5B,KAAK,QAAQ,OAAQ,CAAC;AAAA,IACtB,KAAK;AAAA,MACD,KAAK,QAAQ;AAAA,MACb;AAAA,MACA,QAAQ,QAAQ,QAAQ;AAAA,MACxB,QAAQ,CAAC,SAAS,QAAQ,QAAQ,QAAQ,IAAI,IAAI;AAAA,MAClD,MAAM,MAAmB,QAAQ,QAAQ,KAAK;AAAA,MAC9C,MAAM,MAAM,QAAQ,QAAQ,KAAK;AAAA,MACjC,aAAa,MAAM,QAAQ,QAAQ,YAAY;AAAA,MAC/C,UAAU,MAAM,QAAQ,QAAQ,SAAS;AAAA,MACzC,OAAO,CAAC,QAAQ;AACZ,YAAI,EAAE,OAAO,YAAY;AACrB,gBAAM,IAAI,MAAM,gBAAgB,OAAO,GAAG,CAAC,oCAAoC;AAAA,QACnF;AACA,eAAO,UAAU,GAAG;AAAA,MACxB;AAAA,MACA,QAAQ,CAAC,SAAS,QAAQ,IAAI,IAAI;AAAA,MAClC,SAAS,MAAM,QAAQ,IAAI;AAAA,MAC3B,cAAc,CAAC,SAAS,QAAQ,UAAU,IAAI;AAAA,IAClD;AAAA,IACA,KAAK,CAAC,KAAa,UAAmB;AAClC,YAAM,IAAI,KAAK,KAAK;AAAA,IACxB;AAAA,IACA,KAAK,CAAC,QAAgB,MAAM,IAAI,GAAG;AAAA,IACnC,MAAM,CAAC,MAAM,QAAQ,YACjB,oBAAoB,MAAO,UAAU,cAAc,GAAa,QAAQ,OAAO;AAAA,IACnF,MAAM,CAAC,MAAM,QAAQ,YACjB,oBAAoB,MAAO,UAAU,cAAc,GAAa,QAAQ,OAAO;AAAA,IACnF,MAAM,CAAC,MAAM,QAAQ,YACjB,oBAAoB,MAAO,UAAU,cAAc,GAAa,QAAQ,OAAO;AAAA,IACnF,MAAM,CAAC,MAAM,QAAQ,YACjB,IAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,cAAc,GAAG,QAAQ,CAAC;AAAA,IACrE,aAAa,CAAC,MAAM,QAAQ,YACxB,IAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,cAAc,GAAG,QAAQ,CAAC;AAAA,IACrE,UAAU,CAAC,UAAU,WACjB,IAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,KAAK,SAAS,EAAE,UAAU,SAAS,EAAE,CAAC;AAAA,IACjF,UAAU,MAAM,IAAI,SAAS,iBAAiB,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7D,QAAQ,CAAC,MAAM,OAAOA,aAAY;AAC9B,UAAI,UAAU,QAAW;AACrB,gBAAQ,QAAQ,OAAO,IAAI;AAAA,MAC/B,WAAWA,UAAS,QAAQ;AACxB,gBAAQ,QAAQ,OAAO,MAAM,KAAK;AAAA,MACtC,OAAO;AACH,gBAAQ,QAAQ,IAAI,MAAM,KAAK;AAAA,MACnC;AAAA,IACJ;AAAA,IACA,QAAQ,CAAC,SAAS;AACd,cAAQ,SAAS;AAAA,IACrB;AAAA,IACA,QAAQ,CAAC,MAAM,OAAOA,aAAY;AAC9B,UAAI,UAAU,MAAM;AAChB,gBAAQ,OAAO,MAAMA,QAAO;AAAA,MAChC,OAAO;AACH,gBAAQ,IAAI,MAAM,OAAOA,QAAO;AAAA,MACpC;AAAA,IACJ;AAAA,IACA,cAAc,CAAC,MAAM,OAAOA,aAAY,QAAQ,UAAU,MAAM,OAAOA,QAAO;AAAA,EAClF;AAEA,EAAC,QAA+C,kBAAkB,IAAI,QAAQ;AAC9E,EAAC,QAA+C,oBAAoB,IAAI;AAExE,SAAO;AACX;AAEO,SAAS,wBAAwB,UAA4C;AAChF,QAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAE5C,MAAI,SAAS,WAAW,UAAU,CAAC,QAAQ,IAAI,cAAc,GAAG;AAC5D,YAAQ,IAAI,gBAAgB,iCAAiC;AAAA,EACjE;AAEA,MAAI,SAAS,WAAW,UAAU,CAAC,QAAQ,IAAI,cAAc,GAAG;AAC5D,YAAQ,IAAI,gBAAgB,2BAA2B;AAAA,EAC3D;AAEA,MAAI,SAAS,WAAW,UAAU,CAAC,QAAQ,IAAI,cAAc,GAAG;AAC5D,YAAQ,IAAI,gBAAgB,0BAA0B;AAAA,EAC1D;AAEA,QAAM,OAAO,gBAAgB,IAAI,SAAS,MAAM,IAC1C,OACA,SAAS,WAAW,SAChB,KAAK,UAAU,SAAS,IAAI,IAC5B,OAAO,SAAS,IAAI;AAE9B,SAAO,IAAI,SAAS,MAAM;AAAA,IACtB,QAAQ,SAAS;AAAA,IACjB;AAAA,EACJ,CAAC;AACL;AAOO,SAAS,WAAW,UAA2B,SAA6B;AAC/E,QAAM,OAAO,gBAAgB,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AAC7E,QAAM,UAAU,UAAU,WAAW,OAAO,IAAI;AAChD,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,MAAI,aAAa;AACjB,UAAQ,QAAQ,QAAQ,MAAM;AAC1B,iBAAa;AAAA,EACjB,CAAC;AACD,MAAI,CAAC,YAAY;AACb,WAAO;AAAA,EACX;AAEA,QAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,UAAQ,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AAGpC,QAAI,QAAQ,cAAc;AACtB;AAAA,IACJ;AACA,QAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACnB,cAAQ,IAAI,KAAK,KAAK;AAAA,IAC1B;AAAA,EACJ,CAAC;AACD,aAAW,UAAU,QAAQ,QAAQ,eAAe,KAAK,CAAC,GAAG;AACzD,YAAQ,OAAO,cAAc,MAAM;AAAA,EACvC;AACA,SAAO,IAAI,SAAS,KAAK,MAAM,EAAE,QAAQ,KAAK,QAAQ,YAAY,KAAK,YAAY,QAAQ,CAAC;AAChG;AAEA,eAAsB,kBAClB,YACA,QACA,SACwB;AACxB,MAAI,QAAQ;AACZ,MAAI;AAEJ,QAAM,WAAW,OAAO,MAA+C;AACnE,QAAI,KAAK,OAAO;AACZ,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACtE;AACA,YAAQ;AAER,QAAI,MAAM,WAAW,QAAQ;AACzB,eAAS,MAAM,OAAO,OAAO;AAC7B,aAAO;AAAA,IACX;AAEA,UAAM,WAAW,MAAM,WAAW,CAAC,EAAE,SAAS,MAAM,SAAS,IAAI,CAAC,CAAC;AACnE,QAAI,aAAa,QAAW;AACxB,eAAS;AACT,aAAO;AAAA,IACX;AACA,WAAO;AAAA,EACX;AAEA,QAAM,SAAS,CAAC;AAEhB,MAAI,WAAW,QAAW;AACtB,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACnE;AAEA,SAAO;AACX;;;ACvQA,IAAM,UACF,CAAC,QAAQ,OAAO,SAChB,QAAQ,IAAI,aAAa,UACzB,QAAQ,IAAI,SAAS,UACrB,QAAQ,IAAI,gBAAgB;AAEhC,SAAS,MAAM,MAAc,OAAyC;AAClE,SAAO,CAAC,SAAU,UAAU,OAAO,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK;AACxE;AAEO,IAAM,QAAQ;AAAA,EACjB,KAAK,MAAM,GAAG,EAAE;AAAA,EAChB,MAAM,MAAM,GAAG,EAAE;AAAA,EACjB,KAAK,MAAM,IAAI,EAAE;AAAA,EACjB,OAAO,MAAM,IAAI,EAAE;AAAA,EACnB,QAAQ,MAAM,IAAI,EAAE;AAAA,EACpB,MAAM,MAAM,IAAI,EAAE;AAAA,EAClB,SAAS,MAAM,IAAI,EAAE;AAAA,EACrB,MAAM,MAAM,IAAI,EAAE;AAAA,EAClB,MAAM,MAAM,IAAI,EAAE;AACtB;AAGO,IAAM,YAAY,CAAC,SAAyB,MAAM,MAAM,IAAI;AAInE,SAAS,YAAoB;AACzB,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,MAAM,CAAC,MAAsB,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,SAAO,GAAG,IAAI,IAAI,SAAS,CAAC,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,CAAC;AACnF;AAEA,IAAM,MAAM;AAEZ,SAAS,KAAKC,MAAa,SAAiB,OAAwB;AAChE,QAAM,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC,GAAGA,IAAG;AAC3C,MAAI,OAAO;AACP,UAAM,KAAK,MAAM,IAAI,IAAI,KAAK,GAAG,CAAC;AAAA,EACtC;AACA,QAAM,KAAK,OAAO;AAClB,SAAO,MAAM,KAAK,GAAG;AACzB;AAEA,IAAM,MAAM;AAAA,EACR,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC;AAAA,EACvC,MAAM,MAAM,KAAK,MAAM,OAAO,IAAI,GAAG,GAAG,CAAC;AAAA,EACzC,OAAO,MAAM,KAAK,MAAM,IAAI,IAAI,GAAG,GAAG,CAAC;AAC3C;AAUO,IAAM,MAAM;AAAA,EACf,KAAK,SAAiB,OAAsB;AACxC,YAAQ,IAAI,KAAK,IAAI,MAAM,SAAS,KAAK,CAAC;AAAA,EAC9C;AAAA,EACA,QAAQ,SAAiB,OAAsB;AAC3C,YAAQ,IAAI,KAAK,IAAI,MAAM,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC;AAAA,EAC3D;AAAA,EACA,KAAK,SAAiB,OAAsB;AACxC,YAAQ,KAAK,KAAK,IAAI,MAAM,MAAM,OAAO,OAAO,GAAG,KAAK,CAAC;AAAA,EAC7D;AAAA,EACA,MAAM,SAAiB,OAAsB;AACzC,YAAQ,MAAM,KAAK,IAAI,OAAO,MAAM,IAAI,OAAO,GAAG,KAAK,CAAC;AAAA,EAC5D;AAAA,EACA,MAAM,KAAmB;AACrB,YAAQ,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,OAAO,CAAC,OAAO,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC;AAAA,EAC/E;AAAA,EACA,OAAO,MAAc,MAAc,OAAsB;AACrD,UAAM,SAAS,SAAS,QAAQ,IAAI,IAAI,MAAM,IAAI,KAAK,KAAK,GAAG,CAAC,KAAK;AACrE,YAAQ,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,GAAG,MAAM,IAAI,OAAO,CAAC;AAAA,EAC3F;AACJ;;;AC1CO,SAAS,kBAAkB,OAA0C;AACxE,SAAO;AAAA,IACH,SACI,OAAO,UAAU,YAChB,MAAkC,gBAAgB,MAAM;AAAA,EACjE;AACJ;AA8DA,IAAM,uBAAwD;AAAA,EAC1D,oBAAoB;AAAA,EACpB,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,cAAc;AAClB;AAEA,SAAS,sBAAsB,QAAoD;AAC/E,MAAI,CAAC,QAAQ;AACT,WAAO;AAAA,EACX;AACA,QAAM,OAAO,OAAO,MAAM,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY;AACxD,SAAO,qBAAqB,IAAI;AACpC;AAGA,SAAS,eAAe,MAAyC;AAC7D,QAAM,SAAkC,CAAC;AACzC,OAAK,QAAQ,CAAC,OAAO,QAAQ;AACzB,UAAM,UAAU,OAAO,GAAG;AAC1B,QAAI,YAAY,QAAW;AACvB,aAAO,GAAG,IAAI;AAAA,IAClB,WAAW,MAAM,QAAQ,OAAO,GAAG;AAC/B,cAAQ,KAAK,KAAK;AAAA,IACtB,OAAO;AACH,aAAO,GAAG,IAAI,CAAC,SAAS,KAAK;AAAA,IACjC;AAAA,EACJ,CAAC;AACD,SAAO;AACX;AAEA,eAAe,YAAY,SAAkB,aAAgD;AACzF,QAAM,SAAS,QAAQ,MAAM;AAC7B,MAAI,gBAAgB,QAAQ;AACxB,WAAO,OAAO,KAAK;AAAA,EACvB;AACA,MAAI,gBAAgB,QAAQ;AACxB,WAAO,OAAO,KAAK;AAAA,EACvB;AACA,SAAO,eAAe,MAAM,OAAO,SAAS,CAAC;AACjD;AAEA,SAAS,YAAY,KAA6C;AAC9D,QAAM,SAA4C,CAAC;AACnD,aAAW,CAAC,KAAK,KAAK,KAAK,IAAI,cAAc;AACzC,UAAM,UAAU,OAAO,GAAG;AAC1B,QAAI,YAAY,QAAW;AACvB,aAAO,GAAG,IAAI;AAAA,IAClB,WAAW,MAAM,QAAQ,OAAO,GAAG;AAC/B,cAAQ,KAAK,KAAK;AAAA,IACtB,OAAO;AACH,aAAO,GAAG,IAAI,CAAC,SAAS,KAAK;AAAA,IACjC;AAAA,EACJ;AACA,SAAO;AACX;AAEA,eAAe,cACX,QACA,OACA,OAC8B;AAC9B,MAAI,CAAC,kBAAkB,MAAM,GAAG;AAC5B,UAAM,IAAI;AAAA,MACN,SAAS,KAAK,iEAAiE,KAAK;AAAA,IACxF;AAAA,EACJ;AACA,SAAO,OAAO,SAAS,KAAK;AAChC;AAEA,eAAsB,oBAAoB,SAAkB,OAAmD;AAC3G,QAAM,YAA4B,CAAC;AAEnC,MAAI,OAAO,OAAO;AACd,UAAM,QAAQ,YAAY,IAAI,IAAI,QAAQ,GAAG,CAAC;AAC9C,UAAM,SAAS,MAAM,cAAc,MAAM,OAAO,OAAO,OAAO;AAC9D,QAAI,CAAC,OAAO,IAAI;AACZ,aAAO;AAAA,QACH,IAAI;AAAA,QACJ,UAAU;AAAA,UACN,EAAE,SAAS,6BAA6B,QAAQ,OAAO,OAAO;AAAA,UAC9D;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AACA,cAAU,QAAQ,OAAO;AAAA,EAC7B;AAEA,MAAI,OAAO,MAAM;AACb,UAAM,WAAW,MAAM,KAAK;AAC5B,UAAM,WAAW,OAAO,KAAK,QAAQ;AACrC,UAAM,YAAY,sBAAsB,QAAQ,QAAQ,IAAI,cAAc,CAAC;AAG3E,UAAM,SACF,aAAa,SAAS,SAAS,IAAI,YAAY,SAAS,OAAO,SAAS;AAE5E,QAAI,CAAC,QAAQ;AACT,aAAO;AAAA,QACH,IAAI;AAAA,QACJ,UAAU;AAAA,UACN,EAAE,SAAS,2BAA2B,QAAQ,EAAE,UAAU,SAAS,EAAE;AAAA,UACrE;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,QAAI;AACJ,QAAI;AACA,gBAAU,MAAM,YAAY,SAAS,MAAM;AAAA,IAC/C,SAAS,OAAO;AACZ,aAAO;AAAA,QACH,IAAI;AAAA,QACJ,UAAU;AAAA,UACN,EAAE,SAAS,yBAAyB,QAAQ,MAAM;AAAA,UAClD;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,UAAM,SAAS,MAAM,cAAc,SAAS,MAAM,GAAG,SAAS,MAAM;AACpE,QAAI,CAAC,OAAO,IAAI;AACZ,aAAO;AAAA,QACH,IAAI;AAAA,QACJ,UAAU;AAAA,UACN,EAAE,SAAS,yBAAyB,QAAQ,OAAO,OAAO;AAAA,UAC1D;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,cAAU,OAAO,SAAS,SAAS,IAAI,EAAE,MAAM,QAAQ,MAAM,OAAO,MAAM,IAAI,OAAO;AAAA,EACzF;AAEA,SAAO,EAAE,IAAI,MAAM,UAAU;AACjC;;;AJlOA,IAAM,gBAAkC,CAAC,EAAE,SAAS,QAAQ,OAAO,MAAM;AACrE,QAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAChD,QAAM,gBAAgB,MAAc;AAChC,QAAI,CAAC,QAAQ;AACT,YAAM,IAAI,MAAM,uDAAuD;AAAA,IAC3E;AACA,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,KAAK,CAAC,aAAS,qBAAM,QAAQ,IAAI,EAAE,IAAI;AAAA,IACvC,KAAK,UAAM,qBAAM,MAAM;AAAA,IACvB,KAAK,CAAC,MAAM,OAAO,YAAY,WAAO,yBAAU,MAAM,OAAO,OAAO,CAAC;AAAA,IACrE,QAAQ,CAAC,MAAM,YACX,WAAO,yBAAU,MAAM,IAAI,EAAE,GAAG,SAAS,QAAQ,GAAG,SAAS,oBAAI,KAAK,CAAC,EAAE,CAAC,CAAC;AAAA,IAC/E,WAAW,OAAO,UAAU,UAAM,2BAAY,QAAQ,cAAc,GAAG,IAAI,GAAG,IAAI;AAAA,IAClF,WAAW,OAAO,MAAM,OAAO,YAC3B,OAAO,UAAM,+BAAgB,MAAM,OAAO,cAAc,GAAG,OAAO,CAAC;AAAA,EAC3E;AACJ;AA2BA,eAAe,aAAa,aAA0B,OAAiD;AACnG,MAAI;AACA,UAAM,WAAW,MAAM,oBAAoB,YAAY,IAAI,KAAK,MAAM,KAAK;AAC3E,QAAI,CAAC,SAAS,IAAI;AACd,aAAO,WAAW,SAAS,QAAQ;AAAA,IACvC;AAEA,UAAM,UAAU,cAAc;AAAA,MAC1B,SAAS,YAAY,IAAI;AAAA,MACzB,QAAQ,YAAY,IAAI,MAAM;AAAA,MAC9B,WAAW,SAAS;AAAA,MACpB,KAAK,MAAM;AAAA,MACX,QAAQ;AAAA,MACR,cAAc,MAAM;AAAA,MACpB,SAAS;AAAA,IACb,CAAC;AACD,UAAM,SAAS,MAAM,kBAAkB,MAAM,YAAY,MAAM,QAAQ,OAAO;AAC9E,WAAO,WAAW,QAAQ,OAAO;AAAA,EACrC,SAAS,OAAO;AACZ,UAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,QAAI,MAAM,GAAG,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,IAAI,SAAS;AACrE,YAAQ,MAAM,IAAI,SAAS,GAAG;AAC9B,WAAO,IAAI,SAAS,yBAAyB,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChE;AACJ;AAEA,SAAS,aAAa,aAA0B,aAAgC;AAC5E,QAAM,OAAO,YAAY;AACzB,MAAI,CAAC,MAAM;AACP;AAAA,EACJ;AACA,aAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACjC,gBAAY,IAAI,KAAK,KAAK,GAAG,CAAC;AAAA,EAClC;AACJ;AA0BO,SAAS,YAIT,UACoG;AACvG,MAAI,SAAS,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACvE;AAEA,SAAO,OAAO,GAAG,aAAa;AAC1B,UAAM,cAAe,EAAyC,kBAAkB;AAGhF,QAAI,CAAC,aAAa;AACd,YAAM,IAAI;AAAA,QACN;AAAA,MACJ;AAAA,IACJ;AAEA,UAAM,OAAO,YAA2B;AACpC,mBAAa,aAAa,CAAC;AAC3B,YAAM,SAAS,MAAM,SAAS;AAC9B,UAAI,kBAAkB,UAAU;AAC5B,oBAAY,MAAM;AAAA,MACtB,WAAW,gBAAgB,MAAM,GAAG;AAChC,oBAAY,MAAM,wBAAwB,MAAM;AAAA,MACpD;AAAA,IACJ;AAEA,UAAM,WAAW,CAAC,UAAoC;AAClD,YAAM,UAAU,SAAS,KAAK;AAC9B,UAAI,CAAC,SAAS;AACV,eAAO,KAAK;AAAA,MAChB;AACA,aAAO,QAAQ,QAAQ,QAAQ,aAAa,MAAM,SAAS,QAAQ,CAAC,CAAkB,CAAC;AAAA,IAC3F;AAEA,UAAM,WAAW,MAAM,SAAS,CAAC;AACjC,iBAAa,aAAa,CAAC;AAC3B,WAAO,oBAAoB,WAAW,WAAW,YAAY;AAAA,EACjE;AACJ;AAEA,SAAS,kBAAkB,KAAW,OAAoC;AAGtE,QAAM,UAAuB,CAAC,MAAM,aAAa,GAAG,KAAK;AACzD,QAAM,SAAS,MAAM,OAAO,YAAY;AACxC,QAAM,aAAa;AAEnB,MAAI,UAAU,OAAO,OAAO,WAAW,MAAM,MAAM,YAAY;AAC3D,eAAW,MAAM,EAAE,MAAM,MAAM,OAAO;AACtC;AAAA,EACJ;AAEA,QAAM,IAAI,MAAM,iCAAiC,MAAM,MAAM,GAAG;AACpE;AAEO,SAAS,OAAiC;AAC7C,SAAO;AAAA,IACH,MAAM;AAAA,IACN,WAAW,MAAM,IAAI,iBAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC3C,UAAU;AAAA,IACV,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,GAAG;AAAA,IACxC,OAAO,CAAC,SAAS,SAAS,aAAa;AACnC,YAAM,aAAS,mBAAAC;AAAA,QACX;AAAA,UACI,OAAO;AAAA,UACP,MAAM,QAAQ;AAAA,UACd,UAAU,QAAQ;AAAA,QACtB;AAAA,QACA,WAAW,CAAC,SAAS,SAAS,EAAE,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC,IAAI;AAAA,MAChF;AAEA,aAAO;AAAA,QACH,OAAO,MAAM;AACT,iBAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,mBAAO,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAAA,UAC/D,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":["options","tag","serveNode"]}
|
package/dist/cli.js
CHANGED
|
@@ -151,6 +151,15 @@ function buildObjectSchema(type, ctx) {
|
|
|
151
151
|
}
|
|
152
152
|
return schema;
|
|
153
153
|
}
|
|
154
|
+
function toJsonReturnType(type, ctx) {
|
|
155
|
+
const symbol = ctx.checker.getPropertyOfType(type, "toJSON");
|
|
156
|
+
if (!symbol) {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
const methodType = ctx.checker.getTypeOfSymbolAtLocation(symbol, ctx.location);
|
|
160
|
+
const [signature] = methodType.getCallSignatures();
|
|
161
|
+
return signature ? ctx.checker.getReturnTypeOfSignature(signature) : void 0;
|
|
162
|
+
}
|
|
154
163
|
function defName(type) {
|
|
155
164
|
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
156
165
|
const name = symbol?.getName();
|
|
@@ -164,6 +173,12 @@ function walkObject(type, ctx) {
|
|
|
164
173
|
if (isDateType(type)) {
|
|
165
174
|
return { type: "string", format: "date-time" };
|
|
166
175
|
}
|
|
176
|
+
if (!checker.isArrayType(type) && !checker.isTupleType(type)) {
|
|
177
|
+
const jsonReturn = toJsonReturnType(type, ctx);
|
|
178
|
+
if (jsonReturn) {
|
|
179
|
+
return walkType(jsonReturn, ctx);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
167
182
|
if (checker.isArrayType(type)) {
|
|
168
183
|
const [element] = checker.getTypeArguments(type);
|
|
169
184
|
return { type: "array", items: element ? walkType(element, ctx) : {} };
|
|
@@ -818,6 +833,41 @@ function isGiriBodySchema(value) {
|
|
|
818
833
|
value && typeof value === "object" && value[bodySchemaBrand] === true
|
|
819
834
|
);
|
|
820
835
|
}
|
|
836
|
+
function resolveRouteInput(sources) {
|
|
837
|
+
const input = {};
|
|
838
|
+
const owners = {};
|
|
839
|
+
for (const source of sources) {
|
|
840
|
+
if (source.body !== void 0) {
|
|
841
|
+
if (!isGiriBodySchema(source.body)) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
`${source.label}: "body" must be wrapped with a validator, e.g. \`zod.body({ json: ... })\` from @boon4681/giri/validators/zod.`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
if (input.body) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`${source.label}: body validator conflicts with the body validator from ${owners.body}.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
input.body = source.body;
|
|
852
|
+
owners.body = source.label;
|
|
853
|
+
}
|
|
854
|
+
if (source.query !== void 0) {
|
|
855
|
+
if (!isGiriInputSchema(source.query)) {
|
|
856
|
+
throw new Error(
|
|
857
|
+
`${source.label}: "query" must be wrapped with a validator, e.g. \`zod.query(...)\` from @boon4681/giri/validators/zod.`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (input.query) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`${source.label}: query validator conflicts with the query validator from ${owners.query}.`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
input.query = source.query;
|
|
866
|
+
owners.query = source.label;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return input.body || input.query ? input : void 0;
|
|
870
|
+
}
|
|
821
871
|
|
|
822
872
|
// src/app.ts
|
|
823
873
|
function loadModule(file, force = true) {
|
|
@@ -851,31 +901,15 @@ function normalizeMiddleware(value, file) {
|
|
|
851
901
|
}
|
|
852
902
|
throw new Error(`Middleware export in ${file} must be a function or an array of functions.`);
|
|
853
903
|
}
|
|
854
|
-
function
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
`${file}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
throw new Error(
|
|
864
|
-
`${file}: "query" must be wrapped with a validator, e.g. \`export const query = zod.query(...)\` from @boon4681/giri/validators/zod.`
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
function routeInput(routeModule, file) {
|
|
869
|
-
const input = {};
|
|
870
|
-
if (routeModule.body !== void 0) {
|
|
871
|
-
assertBodySchema(routeModule.body, file);
|
|
872
|
-
input.body = routeModule.body;
|
|
873
|
-
}
|
|
874
|
-
if (routeModule.query !== void 0) {
|
|
875
|
-
assertQuerySchema(routeModule.query, file);
|
|
876
|
-
input.query = routeModule.query;
|
|
877
|
-
}
|
|
878
|
-
return input.body || input.query ? input : void 0;
|
|
904
|
+
function routeInput(routeModule, middleware, file) {
|
|
905
|
+
return resolveRouteInput([
|
|
906
|
+
...middleware.map((fn, index) => ({
|
|
907
|
+
label: `${file} middleware[${index}]`,
|
|
908
|
+
body: fn.body,
|
|
909
|
+
query: fn.query
|
|
910
|
+
})),
|
|
911
|
+
{ label: file, body: routeModule.body, query: routeModule.query }
|
|
912
|
+
]);
|
|
879
913
|
}
|
|
880
914
|
function aliasValues(value) {
|
|
881
915
|
return Array.isArray(value) ? value : [value];
|
|
@@ -986,12 +1020,13 @@ async function buildGiriApp(config, options = {}) {
|
|
|
986
1020
|
(file) => normalizeMiddleware(loadShared(file).middleware, file)
|
|
987
1021
|
);
|
|
988
1022
|
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
1023
|
+
const middleware = [...folderMiddleware, ...verbMiddleware];
|
|
989
1024
|
return {
|
|
990
1025
|
method: route.method,
|
|
991
1026
|
path: route.path,
|
|
992
1027
|
handle: routeModule.handle,
|
|
993
|
-
middleware
|
|
994
|
-
input: routeInput(routeModule, route.file),
|
|
1028
|
+
middleware,
|
|
1029
|
+
input: routeInput(routeModule, middleware, route.file),
|
|
995
1030
|
services: options.services,
|
|
996
1031
|
cookieSecret: config.cookieSecret
|
|
997
1032
|
};
|
|
@@ -1387,6 +1422,10 @@ function middlewareVarsType(typesDir, sharedFile) {
|
|
|
1387
1422
|
const spec = JSON.stringify(moduleSpecifier(typesDir, sharedFile));
|
|
1388
1423
|
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
1389
1424
|
}
|
|
1425
|
+
function middlewareInputType(typesDir, sharedFile) {
|
|
1426
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, sharedFile));
|
|
1427
|
+
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackInput<M> : {})`;
|
|
1428
|
+
}
|
|
1390
1429
|
function ownSharedFile(dir, sharedFiles) {
|
|
1391
1430
|
for (let index = sharedFiles.length - 1; index >= 0; index -= 1) {
|
|
1392
1431
|
if ((0, import_node_path8.dirname)(sharedFiles[index]) === dir) {
|
|
@@ -1407,10 +1446,26 @@ function varsType(paths, file, dir, sharedFiles) {
|
|
|
1407
1446
|
}
|
|
1408
1447
|
return parts.length > 0 ? parts.join("\n & ") : "{}";
|
|
1409
1448
|
}
|
|
1449
|
+
function inputType(paths, file, dir, sharedFiles) {
|
|
1450
|
+
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1451
|
+
const parts = [];
|
|
1452
|
+
if (dir !== paths.routesDir) {
|
|
1453
|
+
parts.push(`import(${JSON.stringify(importPath(file, typeFilePath(paths, (0, import_node_path8.dirname)(dir))))}).Input`);
|
|
1454
|
+
}
|
|
1455
|
+
const ownShared = ownSharedFile(dir, sharedFiles);
|
|
1456
|
+
if (ownShared) {
|
|
1457
|
+
parts.push(middlewareInputType(typesDir, ownShared));
|
|
1458
|
+
}
|
|
1459
|
+
return parts.length > 0 ? parts.join("\n & ") : "{}";
|
|
1460
|
+
}
|
|
1410
1461
|
function methodExports(typesDir, verbs) {
|
|
1411
1462
|
return verbs.map(({ method, file }) => {
|
|
1412
1463
|
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
1413
|
-
const input =
|
|
1464
|
+
const input = [
|
|
1465
|
+
"Input",
|
|
1466
|
+
`import("@boon4681/giri").RouteInputOf<typeof import(${spec})>`,
|
|
1467
|
+
`import("@boon4681/giri").MiddlewareInputOf<typeof import(${spec})>`
|
|
1468
|
+
].join(" & ");
|
|
1414
1469
|
const vars = `Vars & import("@boon4681/giri").MiddlewareVarsOf<typeof import(${spec})>`;
|
|
1415
1470
|
return `export type ${method} = import("@boon4681/giri").Handle<Params, ${input}, ${vars}>;`;
|
|
1416
1471
|
});
|
|
@@ -1424,10 +1479,11 @@ async function writeParamTypes(paths, folders) {
|
|
|
1424
1479
|
`export type Params = ${paramsType(params)};`,
|
|
1425
1480
|
"export type RouteParams = Params;",
|
|
1426
1481
|
`export type Vars = ${varsType(paths, file, dir, sharedFiles)};`,
|
|
1482
|
+
`export type Input = ${inputType(paths, file, dir, sharedFiles)};`,
|
|
1427
1483
|
"export type Middleware<Injects extends Record<string, unknown> = {}> =",
|
|
1428
1484
|
' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
|
|
1429
|
-
'export type Handle<
|
|
1430
|
-
' import("@boon4681/giri").Handle<Params, Input, Vars>;'
|
|
1485
|
+
'export type Handle<OwnInput extends import("@boon4681/giri").ValidatedInput = Input> =',
|
|
1486
|
+
' import("@boon4681/giri").Handle<Params, Input & OwnInput, Vars>;'
|
|
1431
1487
|
];
|
|
1432
1488
|
if (verbs.length > 0) {
|
|
1433
1489
|
lines.push(...methodExports(typesDir, verbs));
|
|
@@ -1534,10 +1590,18 @@ function middlewareFunctions(value) {
|
|
|
1534
1590
|
}
|
|
1535
1591
|
return [];
|
|
1536
1592
|
}
|
|
1537
|
-
function readInput(routeModule) {
|
|
1593
|
+
function readInput(routeModule, middleware, label) {
|
|
1594
|
+
const resolved = resolveRouteInput([
|
|
1595
|
+
...middleware.map((fn, index) => ({
|
|
1596
|
+
label: `${label} middleware[${index}]`,
|
|
1597
|
+
body: fn.body,
|
|
1598
|
+
query: fn.query
|
|
1599
|
+
})),
|
|
1600
|
+
{ label, body: routeModule.body, query: routeModule.query }
|
|
1601
|
+
]);
|
|
1538
1602
|
const input = {};
|
|
1539
|
-
const body = bodyToJsonSchemas(
|
|
1540
|
-
const query = inputToJsonSchema(
|
|
1603
|
+
const body = bodyToJsonSchemas(resolved?.body);
|
|
1604
|
+
const query = inputToJsonSchema(resolved?.query);
|
|
1541
1605
|
if (body) {
|
|
1542
1606
|
input.body = body;
|
|
1543
1607
|
}
|
|
@@ -1798,8 +1862,13 @@ function extractStaticMeta(route, routeModule, loadShared) {
|
|
|
1798
1862
|
}
|
|
1799
1863
|
function extractRuntimeSharedMeta(route, routeModule, loadShared) {
|
|
1800
1864
|
const meta = {};
|
|
1865
|
+
const middleware = collectMiddleware(route, {}, loadShared);
|
|
1866
|
+
const input = readInput({}, middleware, route.file);
|
|
1801
1867
|
const security = collectSecurity(route, {}, loadShared);
|
|
1802
1868
|
const { hidden, meta: openapi } = resolveOpenApi(route, { openapi: routeModule.openapi }, loadShared);
|
|
1869
|
+
if (input) {
|
|
1870
|
+
meta.input = input;
|
|
1871
|
+
}
|
|
1803
1872
|
if (security) {
|
|
1804
1873
|
meta.security = security;
|
|
1805
1874
|
}
|
|
@@ -1857,16 +1926,7 @@ function resolveOpenApi(route, routeModule, loadShared) {
|
|
|
1857
1926
|
return { hidden, meta };
|
|
1858
1927
|
}
|
|
1859
1928
|
function collectSecurity(route, routeModule, loadShared) {
|
|
1860
|
-
const
|
|
1861
|
-
routeModule.config?.skipInherited
|
|
1862
|
-
);
|
|
1863
|
-
const middleware = [];
|
|
1864
|
-
if (!skipInherited) {
|
|
1865
|
-
for (const file of route.sharedFiles) {
|
|
1866
|
-
middleware.push(...middlewareFunctions(loadShared(file).middleware));
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
middleware.push(...middlewareFunctions(routeModule.middleware));
|
|
1929
|
+
const middleware = collectMiddleware(route, routeModule, loadShared);
|
|
1870
1930
|
const security = [];
|
|
1871
1931
|
const securitySchemes = {};
|
|
1872
1932
|
for (const fn of middleware) {
|
|
@@ -1884,6 +1944,19 @@ function collectSecurity(route, routeModule, loadShared) {
|
|
|
1884
1944
|
}
|
|
1885
1945
|
return security.length > 0 || Object.keys(securitySchemes).length > 0 ? { security, securitySchemes } : void 0;
|
|
1886
1946
|
}
|
|
1947
|
+
function collectMiddleware(route, routeModule, loadShared) {
|
|
1948
|
+
const skipInherited = Boolean(
|
|
1949
|
+
routeModule.config?.skipInherited
|
|
1950
|
+
);
|
|
1951
|
+
const middleware = [];
|
|
1952
|
+
if (!skipInherited) {
|
|
1953
|
+
for (const file of route.sharedFiles) {
|
|
1954
|
+
middleware.push(...middlewareFunctions(loadShared(file).middleware));
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
middleware.push(...middlewareFunctions(routeModule.middleware));
|
|
1958
|
+
return middleware;
|
|
1959
|
+
}
|
|
1887
1960
|
async function extractRouteMeta(config, paths, routes) {
|
|
1888
1961
|
const byFile = /* @__PURE__ */ new Map();
|
|
1889
1962
|
const remainingRoutes = [];
|
|
@@ -1937,7 +2010,8 @@ async function extractRouteMeta(config, paths, routes) {
|
|
|
1937
2010
|
try {
|
|
1938
2011
|
const routeModule = loadModule2(route.file);
|
|
1939
2012
|
const meta = {};
|
|
1940
|
-
const
|
|
2013
|
+
const middleware = collectMiddleware(route, routeModule, loadShared);
|
|
2014
|
+
const input = readInput(routeModule, middleware, route.file);
|
|
1941
2015
|
const security = collectSecurity(route, routeModule, loadShared);
|
|
1942
2016
|
const { hidden, meta: openapi } = resolveOpenApi(route, routeModule, loadShared);
|
|
1943
2017
|
if (input) {
|