@boon4681/giri 0.0.3-alpha-3 → 0.0.3-alpha-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,46 @@ yarn sync
228
228
  yarn dev
229
229
  ```
230
230
 
231
+ ## Portable runtime
232
+
233
+ `@boon4681/giri/runtime` is the portable composition layer for generated integrations. It does
234
+ not implement routing itself: it accepts a complete `GiriAdapter`, creates that adapter's app, and
235
+ registers statically imported route modules. Hono is the supported adapter today:
236
+
237
+ ```ts
238
+ import { createApp } from "@boon4681/giri/runtime";
239
+ import { hono } from "@boon4681/giri/adapters/hono";
240
+ import * as rootShared from "./src/routes/+shared";
241
+ import * as usersGet from "./src/routes/users/+get";
242
+
243
+ export const app = createApp({
244
+ adapter: hono(),
245
+ services: { source: "playground" },
246
+ routes: [
247
+ {
248
+ method: "GET",
249
+ path: "/api/users",
250
+ module: usersGet,
251
+ shared: [rootShared],
252
+ },
253
+ ],
254
+ });
255
+ ```
256
+
257
+ The result is a normal Fetch application:
258
+
259
+ ```ts
260
+ const response = await app.fetch(new Request("https://example.test/api/users"));
261
+ ```
262
+
263
+ `app` is the native Hono application, so SvelteKit or Next.js can forward a framework request
264
+ directly to `app.fetch(request)`. This is the low-level target for virtual modules such as
265
+ `@giri/project-1`; the Vite integration should generate the route descriptor array.
266
+
267
+ The shipped Hono adapter also owns its Node server binding. A browser playground should provide a
268
+ different `GiriAdapter` implementation whose `createApp`, `register`, `fetch`, and `serve` methods
269
+ bind to the desired browser runtime; `createApp` itself does not special-case that environment.
270
+
231
271
  ## License
232
272
 
233
- MIT
273
+ MIT
@@ -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-BvRph0mx.js';
2
+ import { M as Middleware, V as ValidatedInput, G as GiriAdapter } from '../types-DZ-14IP6.js';
3
3
 
4
4
  type HonoGiriApp = Hono;
5
5
  type HonoContextVars = {
package/dist/index.d.ts CHANGED
@@ -1,65 +1,6 @@
1
- import { M as Middleware, C as Context, H as HandlerResponse, V as ValidatedInput, S as Services, c as CookieJarFactory, d as StatusCode, R as ResponseFormat, T as TypedResponse, e as MiddlewareOptions, B as BodyContentType, a as GiriBodySchema, b as GiriInputSchema, i as inputSchemaBrand, f as RouteInput, g as HttpMethod, h as GiriConfig, j as GiriPaths, k as SecurityRequirement } from './types-BvRph0mx.js';
2
- export { l as CookieJar, m as CookieOptions, n as CookieSink, G as GiriAdapter, o as GiriFetchHandler, p as GiriRequest, q as GiriRouteRegistration, r as GiriServeOptions, s as GiriServer, t as GiriServerInfo, u as Handle, I as Infer, v as InferStackVars, w as InputValidationResult, J as JsonSchema, x as MergeStack, y as MiddlewareOpenApi, z as MiddlewareVarsOf, N as Next, A as RouteInputOf, D as RouteOpenApi, E as RouteOpenApiConfig, F as ValidBody, K as ValidQuery, L as VarsOf } from './types-BvRph0mx.js';
3
-
4
- interface CreateContextOptions<Params extends Record<string, string> = Record<string, string>, Input extends ValidatedInput = ValidatedInput> {
5
- request: Request;
6
- params?: Params;
7
- validated?: Input;
8
- app?: Services;
9
- /** The adapter's native per-request context, stashed for backend-specific bridges. */
10
- native?: unknown;
11
- /** Secret for `c.signedCookie` / `c.req.signedCookie` (from `config.cookieSecret`). */
12
- cookieSecret?: string;
13
- /** The adapter's cookie jar, built from its runtime's native helpers. */
14
- cookies?: CookieJarFactory;
15
- }
16
- declare function createTypedResponse<T, S extends StatusCode, F extends ResponseFormat>(data: T, status: S, format: F, headers?: HeadersInit): TypedResponse<T, S, F>;
17
- declare function isTypedResponse(value: unknown): value is TypedResponse<unknown>;
18
- declare function createContext<Params extends Record<string, string> = Record<string, string>, Input extends ValidatedInput = ValidatedInput>(options: CreateContextOptions<Params, Input>): Context<Params, Input>;
19
- declare function typedResponseToResponse(response: TypedResponse<unknown>): Response;
20
- /**
21
- * Convert a handler's return value to a real `Response`, then merge any headers set imperatively via
22
- * `c.header()` (the response's own headers win; pending ones fill the gaps). Pass the `context` so
23
- * those imperative headers are applied; without it the response is returned unchanged.
24
- */
25
- declare function toResponse(response: HandlerResponse, context?: Context): Response;
26
- declare function composeMiddleware(middleware: Middleware[], handle: (c: Context) => HandlerResponse | Promise<HandlerResponse>, context: Context): Promise<HandlerResponse>;
27
- type AnyMiddleware<Vars extends Record<string, unknown>> = Middleware<Record<string, string>, ValidatedInput, Vars>;
28
- declare function defineMiddleware<Vars extends Record<string, unknown> = {}>(middleware: AnyMiddleware<Vars>): AnyMiddleware<Vars>;
29
- declare function defineMiddleware<Vars extends Record<string, unknown> = {}>(options: MiddlewareOptions, middleware: AnyMiddleware<Vars>): AnyMiddleware<Vars>;
30
- /**
31
- * Group middleware into an ordered stack, preserving each element's type as a tuple so
32
- * the injected context vars (`defineMiddleware<Vars>` / `Middleware<…, Vars>`) propagate
33
- * to downstream handlers. Use it for `+shared.ts` and verb `middleware` exports:
34
- * `export const middleware = stack(auth, requireAdmin)`.
35
- */
36
- declare function stack<T extends Middleware<Record<string, string>, ValidatedInput, any>[]>(...middleware: T): T;
37
-
38
- interface PreparedInput {
39
- ok: true;
40
- validated: ValidatedInput;
41
- }
42
- interface FailedInput {
43
- ok: false;
44
- response: TypedResponse<{
45
- message: string;
46
- issues: unknown;
47
- }, 400 | 415, 'json'>;
48
- }
49
- type PreparedRequestInput = PreparedInput | FailedInput;
50
- /**
51
- * Build a giri input schema from a `validate` + `toJsonSchema` pair. Vendor adapters use
52
- * this; you can call it directly to make a custom validator. The brand is a global symbol,
53
- * so a hand-rolled `{ [Symbol.for("giri.input-schema")]: true, validate, toJsonSchema }` works too.
54
- */
55
- declare function defineInputSchema<Output>(schema: Omit<GiriInputSchema<Output>, typeof inputSchemaBrand>): GiriInputSchema<Output>;
56
- declare function isGiriInputSchema(value: unknown): value is GiriInputSchema;
57
- /**
58
- * Build a giri body schema from per-content-type input schemas. Validator adapters use this `zod.body({ json, form })`
59
- */
60
- declare function defineBodySchema<Outputs extends Partial<Record<BodyContentType, unknown>>>(contents: GiriBodySchema<Outputs>['contents']): GiriBodySchema<Outputs>;
61
- declare function isGiriBodySchema(value: unknown): value is GiriBodySchema;
62
- declare function prepareRequestInput(request: Request, input?: RouteInput): Promise<PreparedRequestInput>;
1
+ import { H as HttpMethod, h as GiriConfig, S as Services, j as GiriPaths, B as BodyContentType, A as SecurityRequirement } from './types-DZ-14IP6.js';
2
+ export { C as Context, d as CookieJar, e as CookieJarFactory, f as CookieOptions, g as CookieSink, G as GiriAdapter, b as GiriBodySchema, i as GiriFetchHandler, c as GiriInputSchema, k as GiriRequest, l as GiriRouteRegistration, m as GiriServeOptions, n as GiriServer, o as GiriServerInfo, a as Handle, p as HandlerResponse, I as Infer, q as InferStackVars, r as InputValidationResult, J as JsonSchema, s as MergeStack, M as Middleware, t as MiddlewareOpenApi, u as MiddlewareOptions, v as MiddlewareVarsOf, N as Next, w as RouteInput, x as RouteInputOf, y as RouteOpenApi, z as RouteOpenApiConfig, D as StatusCode, T as TypedResponse, E as ValidBody, F as ValidQuery, V as ValidatedInput, K as VarsOf } from './types-DZ-14IP6.js';
3
+ export { c as composeMiddleware, a as createContext, b as createTypedResponse, d as defineBodySchema, e as defineInputSchema, f as defineMiddleware, i as isGiriBodySchema, g as isGiriInputSchema, h as isTypedResponse, p as prepareRequestInput, s as stack, t as toResponse, j as typedResponseToResponse } from './validation-Cma_ytxd.js';
63
4
 
64
5
  interface RouteParam {
65
6
  name: string;
@@ -189,4 +130,4 @@ declare function runInit(lifecycle: GiriLifecycle): Promise<Services>;
189
130
 
190
131
  declare function defineConfig<App>(config: GiriConfig<App>): GiriConfig<App>;
191
132
 
192
- export { BodyContentType, Context, CookieJarFactory, GiriBodySchema, GiriConfig, GiriInputSchema, type GiriLifecycle, GiriPaths, HandlerResponse, HttpMethod, Middleware, MiddlewareOptions, RouteInput, SecurityRequirement, Services, StatusCode, TypedResponse, ValidatedInput, buildGiriApp, composeMiddleware, createContext, createTypedResponse, defineBodySchema, defineConfig, defineInputSchema, defineMiddleware, isGiriBodySchema, isGiriInputSchema, isTypedResponse, loadLifecycle, prepareRequestInput, resolveGiriPaths, runInit, scanRoutes, stack, syncProject, toResponse, typedResponseToResponse };
133
+ export { BodyContentType, GiriConfig, type GiriLifecycle, GiriPaths, HttpMethod, SecurityRequirement, Services, buildGiriApp, defineConfig, loadLifecycle, resolveGiriPaths, runInit, scanRoutes, syncProject };
@@ -0,0 +1,49 @@
1
+ export { c as composeMiddleware, a as createContext, b as createTypedResponse, d as defineBodySchema, e as defineInputSchema, f as defineMiddleware, i as isGiriBodySchema, g as isGiriInputSchema, h as isTypedResponse, p as prepareRequestInput, s as stack, t as toResponse, j as typedResponseToResponse } from './validation-Cma_ytxd.js';
2
+ import { G as GiriAdapter, H as HttpMethod, a as Handle, M as Middleware, b as GiriBodySchema, c as GiriInputSchema, S as Services } from './types-DZ-14IP6.js';
3
+ export { B as BodyContentType, C as Context, d as CookieJar, e as CookieJarFactory, f as CookieOptions, g as CookieSink, h as GiriConfig, i as GiriFetchHandler, j as GiriPaths, k as GiriRequest, l as GiriRouteRegistration, m as GiriServeOptions, n as GiriServer, o as GiriServerInfo, p as HandlerResponse, I as Infer, q as InferStackVars, r as InputValidationResult, J as JsonSchema, s as MergeStack, t as MiddlewareOpenApi, u as MiddlewareOptions, v as MiddlewareVarsOf, N as Next, R as ResponseFormat, w as RouteInput, x as RouteInputOf, y as RouteOpenApi, z as RouteOpenApiConfig, A as SecurityRequirement, D as StatusCode, T as TypedResponse, E as ValidBody, F as ValidQuery, V as ValidatedInput, K as VarsOf, L as bodySchemaBrand, O as inputSchemaBrand, P as nativeContextBrand, Q as typedResponseBrand } from './types-DZ-14IP6.js';
4
+
5
+ type AnyHandle = Handle<any, any, any>;
6
+ type AnyMiddleware = Middleware<any, any, any>;
7
+ interface GiriRuntimeRouteModule {
8
+ readonly handle: AnyHandle;
9
+ readonly middleware?: AnyMiddleware | readonly AnyMiddleware[];
10
+ readonly body?: GiriBodySchema;
11
+ readonly query?: GiriInputSchema;
12
+ readonly config?: {
13
+ readonly skipInherited?: boolean;
14
+ };
15
+ }
16
+ interface GiriRuntimeSharedModule {
17
+ readonly middleware?: AnyMiddleware | readonly AnyMiddleware[];
18
+ }
19
+ /**
20
+ * A route descriptor generated by a host integration.
21
+ *
22
+ * `module` and every `shared` entry should be statically imported module namespaces so Vite and
23
+ * other bundlers can discover the complete route graph.
24
+ */
25
+ interface GiriRuntimeRoute {
26
+ readonly method: HttpMethod;
27
+ readonly path: string;
28
+ readonly module: GiriRuntimeRouteModule;
29
+ readonly shared?: readonly GiriRuntimeSharedModule[];
30
+ }
31
+ interface CreateGiriAppOptions<App> {
32
+ /** The complete runtime adapter that owns the app, routing, request dispatch, and serving. */
33
+ readonly adapter: GiriAdapter<App>;
34
+ /** Statically imported route modules, normally emitted by a virtual module or code generator. */
35
+ readonly routes: readonly GiriRuntimeRoute[];
36
+ /** App-wide services to seed onto `c.app` for every registered route. */
37
+ readonly services?: Services;
38
+ /** Secret passed to the adapter for signed cookie support. */
39
+ readonly cookieSecret?: string;
40
+ }
41
+ /**
42
+ * Construct a backend-native app and register Giri's generated route modules on it.
43
+ *
44
+ * Giri does not route requests here. The supplied adapter owns the runtime; this function is only
45
+ * the portable composition step used by generated and virtual modules.
46
+ */
47
+ declare function createApp<App>(options: CreateGiriAppOptions<App>): App;
48
+
49
+ export { type CreateGiriAppOptions, GiriAdapter, GiriBodySchema, GiriInputSchema, type GiriRuntimeRoute, type GiriRuntimeRouteModule, type GiriRuntimeSharedModule, Handle, HttpMethod, Middleware, Services, createApp };
@@ -0,0 +1,464 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/runtime.ts
21
+ var runtime_exports = {};
22
+ __export(runtime_exports, {
23
+ composeMiddleware: () => composeMiddleware,
24
+ createApp: () => createApp,
25
+ createContext: () => createContext,
26
+ createTypedResponse: () => createTypedResponse,
27
+ defineBodySchema: () => defineBodySchema,
28
+ defineInputSchema: () => defineInputSchema,
29
+ defineMiddleware: () => defineMiddleware,
30
+ isGiriBodySchema: () => isGiriBodySchema,
31
+ isGiriInputSchema: () => isGiriInputSchema,
32
+ isTypedResponse: () => isTypedResponse,
33
+ prepareRequestInput: () => prepareRequestInput,
34
+ stack: () => stack,
35
+ toResponse: () => toResponse,
36
+ typedResponseToResponse: () => typedResponseToResponse
37
+ });
38
+ module.exports = __toCommonJS(runtime_exports);
39
+
40
+ // src/types.ts
41
+ var typedResponseBrand = /* @__PURE__ */ Symbol.for("giri.typed-response");
42
+ var nativeContextBrand = /* @__PURE__ */ Symbol.for("giri.native-context");
43
+ var inputSchemaBrand = /* @__PURE__ */ Symbol.for("giri.input-schema");
44
+ var bodySchemaBrand = /* @__PURE__ */ Symbol.for("giri.body-schema");
45
+
46
+ // src/context.ts
47
+ var BODYLESS_STATUS = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
48
+ var pendingResponseBrand = /* @__PURE__ */ Symbol("giri.pending-response");
49
+ function getPending(context) {
50
+ return context[pendingResponseBrand];
51
+ }
52
+ var unsupportedCookieJar = {
53
+ get: cookiesUnsupported,
54
+ all: cookiesUnsupported,
55
+ set: cookiesUnsupported,
56
+ delete: cookiesUnsupported,
57
+ getSigned: cookiesUnsupported,
58
+ setSigned: cookiesUnsupported
59
+ };
60
+ function cookiesUnsupported() {
61
+ throw new Error("The active adapter does not support cookies.");
62
+ }
63
+ function createTypedResponse(data, status, format, headers) {
64
+ return {
65
+ [typedResponseBrand]: { data, status, format },
66
+ data,
67
+ status,
68
+ format,
69
+ headers
70
+ };
71
+ }
72
+ function isTypedResponse(value) {
73
+ return Boolean(value && typeof value === "object" && typedResponseBrand in value);
74
+ }
75
+ function createContext(options) {
76
+ const url = new URL(options.request.url);
77
+ const store = /* @__PURE__ */ new Map();
78
+ const validated = options.validated ?? {};
79
+ const pending = { headers: new Headers() };
80
+ const defaultStatus = () => pending.status ?? 200;
81
+ const cookies = options.cookies ? options.cookies({
82
+ request: options.request,
83
+ append: (header) => pending.headers.append("set-cookie", header),
84
+ secret: options.cookieSecret
85
+ }) : unsupportedCookieJar;
86
+ const context = {
87
+ params: options.params ?? {},
88
+ app: options.app ?? {},
89
+ req: {
90
+ raw: options.request,
91
+ url,
92
+ method: options.request.method,
93
+ header: (name) => options.request.headers.get(name),
94
+ json: () => options.request.json(),
95
+ text: () => options.request.text(),
96
+ arrayBuffer: () => options.request.arrayBuffer(),
97
+ formData: () => options.request.formData(),
98
+ valid: (key) => {
99
+ if (!(key in validated)) {
100
+ throw new Error(`No validated ${String(key)} data is available for this route.`);
101
+ }
102
+ return validated[key];
103
+ },
104
+ cookie: (name) => cookies.get(name),
105
+ cookies: () => cookies.all(),
106
+ signedCookie: (name) => cookies.getSigned(name)
107
+ },
108
+ set: (key, value) => {
109
+ store.set(key, value);
110
+ },
111
+ get: (key) => store.get(key),
112
+ json: (data, status, headers) => createTypedResponse(data, status ?? defaultStatus(), "json", headers),
113
+ text: (text, status, headers) => createTypedResponse(text, status ?? defaultStatus(), "text", headers),
114
+ html: (html, status, headers) => createTypedResponse(html, status ?? defaultStatus(), "html", headers),
115
+ body: (data, status, headers) => new Response(data, { status: status ?? defaultStatus(), headers }),
116
+ newResponse: (data, status, headers) => new Response(data, { status: status ?? defaultStatus(), headers }),
117
+ redirect: (location, status) => new Response(null, { status: status ?? 302, headers: { Location: location } }),
118
+ notFound: () => new Response("404 Not Found", { status: 404 }),
119
+ header: (name, value, options2) => {
120
+ if (value === void 0) {
121
+ pending.headers.delete(name);
122
+ } else if (options2?.append) {
123
+ pending.headers.append(name, value);
124
+ } else {
125
+ pending.headers.set(name, value);
126
+ }
127
+ },
128
+ status: (code) => {
129
+ pending.status = code;
130
+ },
131
+ cookie: (name, value, options2) => {
132
+ if (value === null) {
133
+ cookies.delete(name, options2);
134
+ } else {
135
+ cookies.set(name, value, options2);
136
+ }
137
+ },
138
+ signedCookie: (name, value, options2) => cookies.setSigned(name, value, options2)
139
+ };
140
+ context[nativeContextBrand] = options.native;
141
+ context[pendingResponseBrand] = pending;
142
+ return context;
143
+ }
144
+ function typedResponseToResponse(response) {
145
+ const headers = new Headers(response.headers);
146
+ if (response.format === "json" && !headers.has("content-type")) {
147
+ headers.set("content-type", "application/json; charset=utf-8");
148
+ }
149
+ if (response.format === "text" && !headers.has("content-type")) {
150
+ headers.set("content-type", "text/plain; charset=utf-8");
151
+ }
152
+ if (response.format === "html" && !headers.has("content-type")) {
153
+ headers.set("content-type", "text/html; charset=utf-8");
154
+ }
155
+ const body = BODYLESS_STATUS.has(response.status) ? null : response.format === "json" ? JSON.stringify(response.data) : String(response.data);
156
+ return new Response(body, {
157
+ status: response.status,
158
+ headers
159
+ });
160
+ }
161
+ function toResponse(response, context) {
162
+ const base = isTypedResponse(response) ? typedResponseToResponse(response) : response;
163
+ const pending = context ? getPending(context) : void 0;
164
+ if (!pending) {
165
+ return base;
166
+ }
167
+ let hasPending = false;
168
+ pending.headers.forEach(() => {
169
+ hasPending = true;
170
+ });
171
+ if (!hasPending) {
172
+ return base;
173
+ }
174
+ const headers = new Headers(base.headers);
175
+ pending.headers.forEach((value, key) => {
176
+ if (key === "set-cookie") {
177
+ return;
178
+ }
179
+ if (!headers.has(key)) {
180
+ headers.set(key, value);
181
+ }
182
+ });
183
+ for (const cookie of pending.headers.getSetCookie?.() ?? []) {
184
+ headers.append("set-cookie", cookie);
185
+ }
186
+ return new Response(base.body, { status: base.status, statusText: base.statusText, headers });
187
+ }
188
+ async function composeMiddleware(middleware, handle, context) {
189
+ let index = -1;
190
+ let result;
191
+ const dispatch = async (i) => {
192
+ if (i <= index) {
193
+ throw new Error("next() called multiple times in giri middleware.");
194
+ }
195
+ index = i;
196
+ if (i === middleware.length) {
197
+ result = await handle(context);
198
+ return result;
199
+ }
200
+ const returned = await middleware[i](context, () => dispatch(i + 1));
201
+ if (returned !== void 0) {
202
+ result = returned;
203
+ return returned;
204
+ }
205
+ return result;
206
+ };
207
+ await dispatch(0);
208
+ if (result === void 0) {
209
+ throw new Error("Route completed without returning a response.");
210
+ }
211
+ return result;
212
+ }
213
+ function defineMiddleware(optionsOrMiddleware, maybeMiddleware) {
214
+ if (typeof optionsOrMiddleware === "function") {
215
+ return optionsOrMiddleware;
216
+ }
217
+ if (!maybeMiddleware) {
218
+ throw new Error("defineMiddleware(options, middleware) requires a middleware function.");
219
+ }
220
+ maybeMiddleware.openapi = optionsOrMiddleware.openapi;
221
+ return maybeMiddleware;
222
+ }
223
+ function stack(...middleware) {
224
+ return middleware;
225
+ }
226
+
227
+ // src/validation.ts
228
+ function defineInputSchema(schema) {
229
+ return { [inputSchemaBrand]: true, ...schema };
230
+ }
231
+ function isGiriInputSchema(value) {
232
+ return Boolean(
233
+ value && typeof value === "object" && value[inputSchemaBrand] === true
234
+ );
235
+ }
236
+ function defineBodySchema(contents) {
237
+ return { [bodySchemaBrand]: true, contents };
238
+ }
239
+ function isGiriBodySchema(value) {
240
+ return Boolean(
241
+ value && typeof value === "object" && value[bodySchemaBrand] === true
242
+ );
243
+ }
244
+ var MIME_TO_CONTENT_TYPE = {
245
+ "application/json": "json",
246
+ "multipart/form-data": "form",
247
+ "application/x-www-form-urlencoded": "urlencoded",
248
+ "text/plain": "text"
249
+ };
250
+ function contentTypeFromHeader(header) {
251
+ if (!header) {
252
+ return void 0;
253
+ }
254
+ const mime = header.split(";", 1)[0].trim().toLowerCase();
255
+ return MIME_TO_CONTENT_TYPE[mime];
256
+ }
257
+ function formDataObject(form) {
258
+ const result = {};
259
+ form.forEach((value, key) => {
260
+ const current = result[key];
261
+ if (current === void 0) {
262
+ result[key] = value;
263
+ } else if (Array.isArray(current)) {
264
+ current.push(value);
265
+ } else {
266
+ result[key] = [current, value];
267
+ }
268
+ });
269
+ return result;
270
+ }
271
+ async function readRawBody(request, contentType) {
272
+ const cloned = request.clone();
273
+ if (contentType === "json") {
274
+ return cloned.json();
275
+ }
276
+ if (contentType === "text") {
277
+ return cloned.text();
278
+ }
279
+ return formDataObject(await cloned.formData());
280
+ }
281
+ function queryObject(url) {
282
+ const result = {};
283
+ for (const [key, value] of url.searchParams) {
284
+ const current = result[key];
285
+ if (current === void 0) {
286
+ result[key] = value;
287
+ } else if (Array.isArray(current)) {
288
+ current.push(value);
289
+ } else {
290
+ result[key] = [current, value];
291
+ }
292
+ }
293
+ return result;
294
+ }
295
+ async function runValidation(schema, value, label) {
296
+ if (!isGiriInputSchema(schema)) {
297
+ throw new Error(
298
+ `giri: ${label} schema must be wrapped with a validator, e.g. \`export const ${label} = zod(...)\` from @boon4681/giri/validators/zod.`
299
+ );
300
+ }
301
+ return schema.validate(value);
302
+ }
303
+ async function prepareRequestInput(request, input) {
304
+ const validated = {};
305
+ if (input?.query) {
306
+ const query = queryObject(new URL(request.url));
307
+ const result = await runValidation(input.query, query, "query");
308
+ if (!result.ok) {
309
+ return {
310
+ ok: false,
311
+ response: createTypedResponse(
312
+ { message: "Invalid query parameters.", issues: result.issues },
313
+ 400,
314
+ "json"
315
+ )
316
+ };
317
+ }
318
+ validated.query = result.value;
319
+ }
320
+ if (input?.body) {
321
+ const contents = input.body.contents;
322
+ const declared = Object.keys(contents);
323
+ const requested = contentTypeFromHeader(request.headers.get("content-type"));
324
+ const chosen = requested && contents[requested] ? requested : contents.json ? "json" : void 0;
325
+ if (!chosen) {
326
+ return {
327
+ ok: false,
328
+ response: createTypedResponse(
329
+ { message: "Unsupported media type.", issues: { accepted: declared } },
330
+ 415,
331
+ "json"
332
+ )
333
+ };
334
+ }
335
+ let rawBody;
336
+ try {
337
+ rawBody = await readRawBody(request, chosen);
338
+ } catch (error) {
339
+ return {
340
+ ok: false,
341
+ response: createTypedResponse(
342
+ { message: "Invalid request body.", issues: error },
343
+ 400,
344
+ "json"
345
+ )
346
+ };
347
+ }
348
+ const result = await runValidation(contents[chosen], rawBody, "body");
349
+ if (!result.ok) {
350
+ return {
351
+ ok: false,
352
+ response: createTypedResponse(
353
+ { message: "Invalid request body.", issues: result.issues },
354
+ 400,
355
+ "json"
356
+ )
357
+ };
358
+ }
359
+ validated.body = declared.length > 1 ? { type: chosen, data: result.value } : result.value;
360
+ }
361
+ return { ok: true, validated };
362
+ }
363
+
364
+ // src/runtime.ts
365
+ function normalizeRoutePath(path) {
366
+ if (!path.startsWith("/")) {
367
+ throw new Error(`Giri runtime route paths must start with "/": ${path}`);
368
+ }
369
+ return path;
370
+ }
371
+ function normalizeMiddleware(value, label) {
372
+ if (value === void 0) {
373
+ return [];
374
+ }
375
+ if (typeof value === "function") {
376
+ return [value];
377
+ }
378
+ if (!Array.isArray(value) || value.some((middleware) => typeof middleware !== "function")) {
379
+ throw new Error(`${label} middleware must be a function or an array of functions.`);
380
+ }
381
+ return [...value];
382
+ }
383
+ function routeInput(route) {
384
+ const input = {};
385
+ if (route.module.body !== void 0) {
386
+ if (!isGiriBodySchema(route.module.body)) {
387
+ throw new Error(
388
+ `${route.method} ${route.path}: body must be a wrapped Giri body schema.`
389
+ );
390
+ }
391
+ input.body = route.module.body;
392
+ }
393
+ if (route.module.query !== void 0) {
394
+ if (!isGiriInputSchema(route.module.query)) {
395
+ throw new Error(
396
+ `${route.method} ${route.path}: query must be a wrapped Giri input schema.`
397
+ );
398
+ }
399
+ input.query = route.module.query;
400
+ }
401
+ return input.body || input.query ? input : void 0;
402
+ }
403
+ function routeRegistration(route, options) {
404
+ const path = normalizeRoutePath(route.path);
405
+ if (!route.module || typeof route.module.handle !== "function") {
406
+ throw new Error(`${route.method} ${path}: route module must export a handle function.`);
407
+ }
408
+ const inherited = route.module.config?.skipInherited ? [] : (route.shared ?? []).flatMap(
409
+ (shared, index) => normalizeMiddleware(shared.middleware, `${route.method} ${path} shared[${index}]`)
410
+ );
411
+ return {
412
+ method: route.method,
413
+ path,
414
+ handle: route.module.handle,
415
+ middleware: [
416
+ ...inherited,
417
+ ...normalizeMiddleware(route.module.middleware, `${route.method} ${path}`)
418
+ ],
419
+ input: routeInput(route),
420
+ services: options.services,
421
+ cookieSecret: options.cookieSecret
422
+ };
423
+ }
424
+ function createApp(options) {
425
+ if (!options.adapter || typeof options.adapter.createApp !== "function") {
426
+ throw new Error("Giri runtime requires an adapter with createApp().");
427
+ }
428
+ if (typeof options.adapter.register !== "function") {
429
+ throw new Error("Giri runtime requires an adapter with register().");
430
+ }
431
+ const registered = /* @__PURE__ */ new Set();
432
+ const registrations = options.routes.map((route) => {
433
+ const registration = routeRegistration(route, options);
434
+ const key = `${registration.method} ${registration.path}`;
435
+ if (registered.has(key)) {
436
+ throw new Error(`Duplicate Giri runtime route: ${key}`);
437
+ }
438
+ registered.add(key);
439
+ return registration;
440
+ });
441
+ const app = options.adapter.createApp();
442
+ for (const registration of registrations) {
443
+ options.adapter.register(app, registration);
444
+ }
445
+ return app;
446
+ }
447
+ // Annotate the CommonJS export names for ESM import in node:
448
+ 0 && (module.exports = {
449
+ composeMiddleware,
450
+ createApp,
451
+ createContext,
452
+ createTypedResponse,
453
+ defineBodySchema,
454
+ defineInputSchema,
455
+ defineMiddleware,
456
+ isGiriBodySchema,
457
+ isGiriInputSchema,
458
+ isTypedResponse,
459
+ prepareRequestInput,
460
+ stack,
461
+ toResponse,
462
+ typedResponseToResponse
463
+ });
464
+ //# sourceMappingURL=runtime.js.map