@arkstack/driver-h3 0.5.1 → 0.5.3

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
@@ -1,5 +1,7 @@
1
1
  # @arkstack/driver-h3
2
2
 
3
+ [![@arkstack/driver-h3](https://img.shields.io/npm/dt/@arkstack/driver-h3?style=flat-square&label=@arkstack/driver-h3&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@arkstack/driver-h3)](https://www.npmjs.com/package/@arkstack/driver-h3)
4
+
3
5
  H3 driver for Arkstack, providing H3-based runtime integration for the framework.
4
6
 
5
7
  ## Auth Middleware
package/dist/app.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { H3Event } from 'h3'
2
+
3
+ declare module '@arkstack/common' {
4
+ interface HookRegistry {
5
+ 'middleware:auth': {
6
+ before: (event: H3Event) => void
7
+ after: (event: H3Event) => void
8
+ error: (error: unknown, event: H3Event) => void
9
+ }
10
+ }
11
+ }
12
+
13
+ declare module '@arkstack/foundry' {
14
+ interface HookRegistry {
15
+ 'middleware:auth': {
16
+ before: (event: H3Event) => void
17
+ after: (event: H3Event) => void
18
+ error: (error: unknown, event: H3Event) => void
19
+ }
20
+ }
21
+ }
22
+
23
+ declare global {
24
+ var tunnelUrl: () => string
25
+ }
@@ -0,0 +1,98 @@
1
+ import { n as MiddlewareConfig, t as Middleware$2 } from "./types-DWUchLdg.js";
2
+ import { ArkstackKitDriver, ArkstackRouteListOptions, PromiseOrValue } from "@arkstack/contract";
3
+ import { H3, H3Event, HTTPError, HTTPResponse } from "h3";
4
+ import { Router } from "clear-router/h3";
5
+ import { H3App, Handler, HttpContext, Middleware as Middleware$1 } from "clear-router/types/h3";
6
+ import { Route } from "clear-router";
7
+
8
+ //#region src/error-handler.d.ts
9
+ declare const defaultErrorHandler: (err: HTTPError | Error | string, event: H3Event) => Promise<HTTPResponse | {
10
+ error: boolean;
11
+ message: string;
12
+ status: "error";
13
+ code: number;
14
+ errors?: unknown;
15
+ stack?: string;
16
+ }>;
17
+ //#endregion
18
+ //#region src/Router.d.ts
19
+ declare class Router$1 extends Router {
20
+ static bind(app: H3): Promise<H3App>;
21
+ static list(_options: ArkstackRouteListOptions | undefined, app: H3): Promise<Route<HttpContext, Middleware$1, Handler>[]>;
22
+ }
23
+ //#endregion
24
+ //#region src/index.d.ts
25
+ type H3Middleware = Middleware$1 | [Middleware$1, Record<string, any>];
26
+ interface H3DriverOptions {
27
+ bindRouter: (app: H3) => PromiseOrValue<void>;
28
+ mountPublicAssets?: (app: H3, publicPath: string) => PromiseOrValue<void>;
29
+ createApp?: () => H3;
30
+ onError?: (err: Error | string, event: H3Event) => unknown;
31
+ }
32
+ declare class H3EventResponse {
33
+ response: Response;
34
+ status: number;
35
+ statusText?: string;
36
+ constructor(response: Response);
37
+ get headers(): Headers;
38
+ }
39
+ /**
40
+ * The H3Driver class implements the ArkstackKitDriver contract for the H3 framework.
41
+ */
42
+ declare class H3Driver extends ArkstackKitDriver<H3, H3Middleware> {
43
+ readonly name = "h3";
44
+ private tunnel_url?;
45
+ private readonly options;
46
+ /**
47
+ * Creates an instance of H3Driver.
48
+ *
49
+ * @param options
50
+ */
51
+ constructor(options: H3DriverOptions);
52
+ /**
53
+ * Creates an H3 application instance.
54
+ *
55
+ * @returns
56
+ */
57
+ createApp(): H3;
58
+ /**
59
+ * Mounts static assets from the specified public path to the H3 application.
60
+ *
61
+ * @param app
62
+ * @param publicPath
63
+ */
64
+ mountPublicAssets(app: H3, publicPath: string): PromiseOrValue<void>;
65
+ /**
66
+ * Binds the router to the H3 application using the provided bindRouter function.
67
+ *
68
+ * @param app
69
+ */
70
+ bindRouter(app: H3): PromiseOrValue<void>;
71
+ /**
72
+ * Applies middleware to the H3 application.
73
+ *
74
+ * @param app
75
+ * @param middleware
76
+ */
77
+ applyMiddleware(app: H3, middleware: H3Middleware | Middleware$2 | MiddlewareConfig): void;
78
+ /**
79
+ * If trafic has been proxied via ngrok, this will return the tunnel URL.
80
+ *
81
+ * @returns
82
+ */
83
+ geTunnelUrl(): string | undefined;
84
+ /**
85
+ * Starts the H3 server on the specified port.
86
+ *
87
+ * The bind host can be overridden with the `APP_HOST` (or `HOST`) env
88
+ * variable. It defaults to `0.0.0.0` so the server is reachable on all
89
+ * network interfaces, which platforms like Railway require for their
90
+ * healthcheck proxy to reach the app.
91
+ *
92
+ * @param app
93
+ * @param port
94
+ */
95
+ start(app: H3, port: number): Promise<void>;
96
+ }
97
+ //#endregion
98
+ export { Router$1 as a, H3Middleware as i, H3DriverOptions as n, defaultErrorHandler as o, H3EventResponse as r, H3Driver as t };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { a as Router, i as H3Middleware, n as H3DriverOptions, o as defaultErrorHandler, r as H3EventResponse, t as H3Driver } from "./index-B3NOMsXC.js";
1
+ /// <reference path="./app.d.ts" />
2
+ import { a as Router, i as H3Middleware, n as H3DriverOptions, o as defaultErrorHandler, r as H3EventResponse, t as H3Driver } from "./index-BZYQAccK.js";
2
3
  export { H3Driver, H3DriverOptions, H3EventResponse, H3Middleware, Router, defaultErrorHandler };
package/dist/index.js CHANGED
@@ -1,13 +1,32 @@
1
- import { t as staticAssetHandler } from "./middlewares-DIYzSTD5.js";
1
+ import { t as staticAssetHandler } from "./middlewares-BORI5o6J.js";
2
2
  import { ArkstackKitDriver } from "@arkstack/contract";
3
3
  import { H3, HTTPResponse, serve, toResponse } from "h3";
4
- import { ErrorHandler, Logger, importFile, renderError } from "@arkstack/common";
5
- import { join } from "node:path";
4
+ import { ErrorHandler, Logger, devTlsCredentials, env, localNetworkAddress, renderError, resolveRuntimeModule } from "@arkstack/common";
5
+ import ngrok from "@ngrok/ngrok";
6
+ import { resolveMiddleware } from "@arkstack/http";
7
+ import { registerPlugin } from "resora";
6
8
  import { Router as Router$1 } from "clear-router/h3";
7
9
  import { clearRouterH3Plugin } from "@resora/plugin-clear-router";
8
- import { registerPlugin } from "resora";
9
10
  //#region src/error-handler.ts
10
- const defaultErrorHandler = (err, event) => {
11
+ const webMiddlewareKey = Symbol.for("arkstack:http:web");
12
+ const isRecord = (value) => {
13
+ return typeof value === "object" && value !== null;
14
+ };
15
+ const isWebRequest = (value) => {
16
+ if (!isRecord(value)) return false;
17
+ return value[webMiddlewareKey] === true || value.arkstackWeb === true || isWebRequest(value.context) || isWebRequest(value.req);
18
+ };
19
+ const redirectBackTarget = (event) => {
20
+ return event.req.headers.get("referer") || event.req.headers.get("referrer") || "/";
21
+ };
22
+ const flashValidationState = async (err, event, errors) => {
23
+ const session = event.httpSession ?? event.session ?? event.context?.httpSession ?? event.context?.session;
24
+ if (!session) return;
25
+ if (errors) session.addErrors?.(errors);
26
+ else session.addValidationErrors?.(err);
27
+ await session.save?.();
28
+ };
29
+ const defaultErrorHandler = async (err, event) => {
11
30
  const responseBody = ErrorHandler.createErrorPayload(err);
12
31
  if (ErrorHandler.shouldLogError(err)) ErrorHandler.logUnhandledError(err, {
13
32
  headers: Object.fromEntries(event.req.headers.entries()),
@@ -17,7 +36,16 @@ const defaultErrorHandler = (err, event) => {
17
36
  if (process.env.NODE_ENV === "development") console.error(responseBody);
18
37
  const code = ErrorHandler.normalizeStatusCode(responseBody.code);
19
38
  event.res.status = code;
20
- if ((event.req.headers.get("accept") ?? "").includes("application/json") || event.req._url?.pathname?.startsWith("/api")) return {
39
+ const expectsJson = (event.req.headers.get("accept") ?? "").includes("application/json") || event.req._url?.pathname?.startsWith("/api");
40
+ if (code === 422 && !expectsJson && isWebRequest(event)) {
41
+ await flashValidationState(err, event, responseBody.errors);
42
+ event.res.status = 302;
43
+ return new HTTPResponse(null, {
44
+ status: 302,
45
+ headers: { Location: redirectBackTarget(event) }
46
+ });
47
+ }
48
+ if (expectsJson) return {
21
49
  ...responseBody,
22
50
  error: true,
23
51
  message: responseBody.message
@@ -38,12 +66,10 @@ Router$1.configure({ inferParamName: true });
38
66
  var Router = class extends Router$1 {
39
67
  static async bind(app) {
40
68
  try {
41
- await Router$1.group("/api", async () => {
42
- await importFile(join(process.cwd(), "src/routes/api.ts"));
43
- });
44
- await Router$1.group("/", async () => {
45
- await importFile(join(process.cwd(), "src/routes/web.ts"));
46
- });
69
+ await Router$1.group("/api", resolveRuntimeModule("src/routes/api.ts"));
70
+ } catch {}
71
+ try {
72
+ await Router$1.group("/", resolveRuntimeModule("src/routes/web.ts"));
47
73
  } catch {}
48
74
  return Router$1.apply(app);
49
75
  }
@@ -72,6 +98,7 @@ var H3EventResponse = class {
72
98
  */
73
99
  var H3Driver = class extends ArkstackKitDriver {
74
100
  name = "h3";
101
+ tunnel_url;
75
102
  options;
76
103
  /**
77
104
  * Creates an instance of H3Driver.
@@ -118,12 +145,13 @@ var H3Driver = class extends ArkstackKitDriver {
118
145
  const mw = Array.isArray(middleware) ? middleware[0] : middleware;
119
146
  const conf = Array.isArray(middleware) && middleware[1] ? middleware[1] : {};
120
147
  if (typeof mw === "function") {
121
- app.use(mw, conf);
148
+ app.use(resolveMiddleware(mw), conf);
122
149
  return;
123
150
  }
124
151
  for (const [pos, entries] of Object.entries(middleware)) for (const entry of entries) {
125
- const mw = Array.isArray(entry) ? entry[0] : entry;
152
+ const instance = Array.isArray(entry) ? entry[0] : entry;
126
153
  const conf = Array.isArray(entry) && entry[1] ? entry[1] : {};
154
+ const mw = resolveMiddleware(instance);
127
155
  if (pos === "after") app.use(async (evt, next) => {
128
156
  evt[Symbol.for("h3.internal.event.res")] = new H3EventResponse(await toResponse(await next(), evt));
129
157
  await mw(evt, next);
@@ -133,21 +161,73 @@ var H3Driver = class extends ArkstackKitDriver {
133
161
  }
134
162
  }
135
163
  /**
136
- * Starts the H3 server on the specified port.
164
+ * If trafic has been proxied via ngrok, this will return the tunnel URL.
137
165
  *
138
- * @param app
139
- * @param port
166
+ * @returns
167
+ */
168
+ geTunnelUrl() {
169
+ return this.tunnel_url;
170
+ }
171
+ /**
172
+ * Starts the H3 server on the specified port.
173
+ *
174
+ * The bind host can be overridden with the `APP_HOST` (or `HOST`) env
175
+ * variable. It defaults to `0.0.0.0` so the server is reachable on all
176
+ * network interfaces, which platforms like Railway require for their
177
+ * healthcheck proxy to reach the app.
178
+ *
179
+ * @param app
180
+ * @param port
140
181
  */
141
- start(app, port) {
142
- serve(app, {
182
+ async start(app, port) {
183
+ const host = env("APP_HOST", env("HOST", "0.0.0.0"));
184
+ const secure = env("APP_SECURE", false) === true;
185
+ const tunneled = env("TUNNEL", false);
186
+ const scheme = secure ? "https" : "http";
187
+ const tls = secure ? await devTlsCredentials() : void 0;
188
+ await serve(app, {
143
189
  port,
144
- silent: true
145
- }).ready().then(() => {
146
- Logger.log([["Server is running on", "white"], [`http://localhost:${port}`, "cyan"]], " ");
147
- });
190
+ hostname: host,
191
+ silent: true,
192
+ ...tls ? {
193
+ protocol: "https",
194
+ tls: {
195
+ cert: tls.cert,
196
+ key: tls.key
197
+ }
198
+ } : {}
199
+ }).ready();
200
+ let log = startupLogLines(scheme, host, port);
201
+ if (tunneled === true) {
202
+ const url = (await ngrok.forward({
203
+ addr: port,
204
+ authtoken: env("NGROK_AUTHTOKEN"),
205
+ domain: env("NGROK_DOMAIN")
206
+ })).url();
207
+ if (url) {
208
+ log = log.concat(Logger.log([["Trafic has been tunnelled to", "white"], [url, "green"]], " ", false));
209
+ process.env.TUNNEL_URL = url;
210
+ this.tunnel_url = url;
211
+ globalThis.tunnelUrl = () => url;
212
+ }
213
+ }
214
+ console.log(log.join("\n"));
215
+ }
216
+ };
217
+ /**
218
+ * Build the "Server is running" startup lines, adding a local-network URL when
219
+ * the server is bound to all interfaces (`0.0.0.0`/`::`) so it is reachable from
220
+ * other devices.
221
+ */
222
+ const startupLogLines = (scheme, host, port) => {
223
+ const bindsAll = host === "0.0.0.0" || host === "::";
224
+ const localHost = bindsAll ? "localhost" : host;
225
+ const lines = [Logger.log([["Server is running on", "white"], [`${scheme}://${localHost}:${port}`, "cyan"]], " ", false)];
226
+ if (bindsAll) {
227
+ const address = localNetworkAddress();
228
+ if (address) lines.push(Logger.log([["Network access via", "white"], [`${scheme}://${address}:${port}`, "cyan"]], " ", false));
148
229
  }
230
+ return lines;
149
231
  };
150
232
  //#endregion
151
233
  export { H3Driver, H3EventResponse, Router, defaultErrorHandler };
152
-
153
- //# sourceMappingURL=index.js.map
@@ -1,10 +1,13 @@
1
- import { i as H3Middleware } from "../index-B3NOMsXC.js";
1
+ import { i as H3Middleware } from "../index-BZYQAccK.js";
2
2
  import * as _$h3 from "h3";
3
- import { H3Event } from "h3";
3
+ import { EventHandlerRequest, H3Event } from "h3";
4
4
  import { NextFunction } from "clear-router/types/h3";
5
5
 
6
6
  //#region src/middlewares/auth.d.ts
7
7
  declare const auth: (event: H3Event, next: () => unknown | Promise<unknown>) => Promise<unknown>;
8
+ declare class AuthMiddleware {
9
+ handler(event: H3Event, next: () => unknown | Promise<unknown>): Promise<unknown>;
10
+ }
8
11
  //#endregion
9
12
  //#region src/middlewares/cors.d.ts
10
13
  declare const cors: (options?: {
@@ -17,16 +20,75 @@ declare const cors: (options?: {
17
20
  optionsSuccessStatus?: number;
18
21
  preflightContinue?: boolean;
19
22
  }) => (event: H3Event, next: NextFunction) => Promise<void>;
23
+ declare class CorsMiddleware {
24
+ private options;
25
+ constructor(options?: {
26
+ origin?: string | string[] | RegExp | boolean;
27
+ methods?: string[] | string;
28
+ allowedHeaders?: string[] | string | null;
29
+ exposedHeaders?: string[] | string;
30
+ credentials?: boolean;
31
+ maxAge?: number | string;
32
+ optionsSuccessStatus?: number;
33
+ preflightContinue?: boolean;
34
+ });
35
+ handler(event: H3Event<EventHandlerRequest>, next: NextFunction): Promise<void>;
36
+ }
37
+ //#endregion
38
+ //#region src/middlewares/inertia.d.ts
39
+ /**
40
+ * Bind the Inertia request context for H3.
41
+ *
42
+ * Normalizes the H3 event into the adapter's driver-agnostic shape and runs the
43
+ * downstream handler inside Inertia's async-local context (mirroring the `resora`
44
+ * middleware), so `inertia()` / `Inertia.*` resolve the active request.
45
+ *
46
+ * It also upgrades a `302` redirect returned for a `PUT`/`PATCH`/`DELETE` Inertia
47
+ * visit to `303 See Other`, which the Inertia client requires to follow the
48
+ * redirect with a `GET`. (Prefer `Inertia.redirect()` / `Inertia.back()`, which
49
+ * already emit the correct status.)
50
+ *
51
+ * `@arkstack/inertia` is imported dynamically so the package stays optional.
52
+ */
53
+ declare const inertia: () => (event: H3Event, next: NextFunction) => Promise<unknown>;
54
+ declare class InertiaMiddleware {
55
+ handler(event: H3Event, next: NextFunction): Promise<unknown>;
56
+ }
20
57
  //#endregion
21
58
  //#region src/middlewares/request-logger.d.ts
59
+ /**
60
+ * Middleware to log incoming requests and their response times.
61
+ *
62
+ * @param config Configuration options for the request logger middleware.
63
+ * @param config.allowInProduction If true, the logger will also log requests in production environment. Default is false.
64
+ * @returns H3Middleware function
65
+ */
22
66
  declare const requestLogger: ({
23
67
  allowInProduction
24
68
  }?: {
25
69
  allowInProduction?: boolean;
26
70
  }) => H3Middleware;
71
+ declare class RequestLoggerMiddleware {
72
+ private options;
73
+ constructor(options?: {
74
+ allowInProduction?: boolean;
75
+ });
76
+ handler(): H3Middleware;
77
+ }
78
+ //#endregion
79
+ //#region src/middlewares/resora.d.ts
80
+ /**
81
+ * Apply the application's resora configuration (`src/config/resources.ts`) and
82
+ * bind the per-request `{ req, res }` context so Resources can build URLs and
83
+ * pagination links.
84
+ *
85
+ * Replaces the manual `Resource.setCtx(...)` wiring: resora's runtime config is
86
+ * applied from `config('resources')`, and the request is run within resora's
87
+ * async context so downstream handlers resolve the correct context.
88
+ */
89
+ declare const resora: () => (event: H3Event, next: NextFunction) => Promise<unknown>;
27
90
  //#endregion
28
91
  //#region src/middlewares/static-asset-handler.d.ts
29
92
  declare const staticAssetHandler: (publicPath?: string) => (event: H3Event) => Promise<_$h3.HTTPResponse | undefined> | undefined;
30
93
  //#endregion
31
- export { auth, cors, requestLogger, staticAssetHandler };
32
- //# sourceMappingURL=index.d.ts.map
94
+ export { AuthMiddleware, CorsMiddleware, InertiaMiddleware, RequestLoggerMiddleware, auth, cors, inertia, requestLogger, resora, staticAssetHandler };
@@ -1,2 +1,2 @@
1
- import { i as auth, n as requestLogger, r as cors, t as staticAssetHandler } from "../middlewares-DIYzSTD5.js";
2
- export { auth, cors, requestLogger, staticAssetHandler };
1
+ import { a as InertiaMiddleware, c as cors, i as requestLogger, l as AuthMiddleware, n as resora, o as inertia, r as RequestLoggerMiddleware, s as CorsMiddleware, t as staticAssetHandler, u as auth } from "../middlewares-BORI5o6J.js";
2
+ export { AuthMiddleware, CorsMiddleware, InertiaMiddleware, RequestLoggerMiddleware, auth, cors, inertia, requestLogger, resora, staticAssetHandler };
@@ -1,10 +1,13 @@
1
+ import { Arkstack } from "@arkstack/contract";
1
2
  import { serveStatic } from "h3";
2
- import { Hook, Logger, nodeEnv } from "@arkstack/common";
3
- import { Auth, AuthenticationException } from "@arkstack/auth";
4
- import { readFile, stat } from "node:fs/promises";
3
+ import { Logger, config, nodeEnv, resolveRuntimeDir } from "@arkstack/common";
4
+ import { Hook } from "@arkstack/foundry";
5
+ import { applyRuntimeConfig, getDefaultConfig, runWithCtx, setCtx } from "resora";
5
6
  import { join, resolve } from "node:path";
7
+ import { readFile, stat } from "node:fs/promises";
6
8
  //#region src/middlewares/auth.ts
7
9
  const auth = async (event, next) => {
10
+ const { Auth, AuthenticationException } = await import("@arkstack/auth");
8
11
  try {
9
12
  const token = readBearerToken(event.req.headers.get("authorization"));
10
13
  if (!token) throw new AuthenticationException("Unauthenticated", {
@@ -27,10 +30,12 @@ const auth = async (event, next) => {
27
30
  const user = await auth.authorizeToken(token);
28
31
  event.context.user = user;
29
32
  event.context.auth = auth;
33
+ event.context.session = auth.session();
30
34
  event.context.authUser = user;
31
35
  event.context.authToken = token;
32
36
  event.req.user = user;
33
37
  event.req.auth = auth;
38
+ event.req.session = event.context.session;
34
39
  event.req.authUser = user;
35
40
  event.req.authToken = token;
36
41
  if (Hook.has("middleware:auth", "after")) Hook.get("middleware:auth", "after")?.(event);
@@ -40,6 +45,11 @@ const auth = async (event, next) => {
40
45
  throw error;
41
46
  }
42
47
  };
48
+ var AuthMiddleware = class {
49
+ handler(event, next) {
50
+ return auth(event, next);
51
+ }
52
+ };
43
53
  const readBearerToken = (authorization) => {
44
54
  if (!authorization?.startsWith("Bearer ")) return null;
45
55
  return authorization.substring(7);
@@ -253,6 +263,52 @@ const cors = (options = {}) => async (event, next) => {
253
263
  next();
254
264
  }
255
265
  };
266
+ var CorsMiddleware = class {
267
+ options;
268
+ constructor(options = {}) {
269
+ this.options = options;
270
+ }
271
+ handler(event, next) {
272
+ return cors(this.options).call(this.handler, event, next);
273
+ }
274
+ };
275
+ //#endregion
276
+ //#region src/middlewares/inertia.ts
277
+ /**
278
+ * Bind the Inertia request context for H3.
279
+ *
280
+ * Normalizes the H3 event into the adapter's driver-agnostic shape and runs the
281
+ * downstream handler inside Inertia's async-local context (mirroring the `resora`
282
+ * middleware), so `inertia()` / `Inertia.*` resolve the active request.
283
+ *
284
+ * It also upgrades a `302` redirect returned for a `PUT`/`PATCH`/`DELETE` Inertia
285
+ * visit to `303 See Other`, which the Inertia client requires to follow the
286
+ * redirect with a `GET`. (Prefer `Inertia.redirect()` / `Inertia.back()`, which
287
+ * already emit the correct status.)
288
+ *
289
+ * `@arkstack/inertia` is imported dynamically so the package stays optional.
290
+ */
291
+ const inertia = () => {
292
+ return async (event, next) => {
293
+ const { runInertia, shouldUpgradeRedirect } = await import("@arkstack/inertia");
294
+ const sourceUrl = event.req._url ? event.req._url.pathname + event.req._url.search : event.req.url || "/";
295
+ const request = {
296
+ method: String(event.req.method ?? "GET").toUpperCase(),
297
+ url: sourceUrl,
298
+ header: (name) => event.req.headers.get(name) ?? void 0
299
+ };
300
+ return runInertia(request, async () => {
301
+ const result = await next();
302
+ if (request.header("x-inertia") === "true" && result && typeof result === "object" && typeof result.statusCode === "number" && shouldUpgradeRedirect(request.method, result.statusCode)) result.statusCode = 303;
303
+ return result;
304
+ });
305
+ };
306
+ };
307
+ var InertiaMiddleware = class {
308
+ handler(event, next) {
309
+ return inertia()(event, next);
310
+ }
311
+ };
256
312
  //#endregion
257
313
  //#region src/middlewares/request-logger.ts
258
314
  const colors = {
@@ -270,7 +326,8 @@ const colors = {
270
326
  * @returns H3Middleware function
271
327
  */
272
328
  const requestLogger = ({ allowInProduction = false } = {}) => async (event, next) => {
273
- if (nodeEnv() === "prod" && !allowInProduction) return next();
329
+ const VERBOSE = process.env.VERBOSITY != "0";
330
+ if (nodeEnv() === "prod" && !allowInProduction || !VERBOSE) return next();
274
331
  await next();
275
332
  const start = Date.now();
276
333
  const req = event.req;
@@ -283,10 +340,51 @@ const requestLogger = ({ allowInProduction = false } = {}) => async (event, next
283
340
  [`- ${duration}ms`, "dim"]
284
341
  ], " ");
285
342
  };
343
+ var RequestLoggerMiddleware = class {
344
+ options;
345
+ constructor(options = {}) {
346
+ this.options = options;
347
+ }
348
+ handler() {
349
+ return requestLogger(this.options);
350
+ }
351
+ };
352
+ //#endregion
353
+ //#region src/middlewares/resora.ts
354
+ /**
355
+ * Apply the application's resora configuration (`src/config/resources.ts`) and
356
+ * bind the per-request `{ req, res }` context so Resources can build URLs and
357
+ * pagination links.
358
+ *
359
+ * Replaces the manual `Resource.setCtx(...)` wiring: resora's runtime config is
360
+ * applied from `config('resources')`, and the request is run within resora's
361
+ * async context so downstream handlers resolve the correct context.
362
+ */
363
+ const resora = () => {
364
+ let resources;
365
+ return async (event, next) => {
366
+ try {
367
+ if (!resources) {
368
+ resources = {
369
+ ...getDefaultConfig(),
370
+ ...config("resources", {})
371
+ };
372
+ if (typeof resources.resourcesDir === "string") resources.resourcesDir = resolveRuntimeDir(resources.resourcesDir);
373
+ }
374
+ applyRuntimeConfig(resources);
375
+ } catch {}
376
+ const ctx = {
377
+ req: event.req,
378
+ res: event.res
379
+ };
380
+ setCtx(ctx);
381
+ return runWithCtx(ctx, () => next());
382
+ };
383
+ };
286
384
  //#endregion
287
385
  //#region src/middlewares/static-asset-handler.ts
288
386
  const staticAssetHandler = (publicPath = "public") => {
289
- const rootPath = resolve(process.cwd(), publicPath);
387
+ const rootPath = resolve(Arkstack.rootDir(), publicPath);
290
388
  return (event) => {
291
389
  const { pathname } = new URL(event.req.url);
292
390
  if (!/\.[a-zA-Z0-9]+$/.test(pathname)) return;
@@ -311,6 +409,4 @@ const staticAssetHandler = (publicPath = "public") => {
311
409
  };
312
410
  };
313
411
  //#endregion
314
- export { auth as i, requestLogger as n, cors as r, staticAssetHandler as t };
315
-
316
- //# sourceMappingURL=middlewares-DIYzSTD5.js.map
412
+ export { InertiaMiddleware as a, cors as c, requestLogger as i, AuthMiddleware as l, resora as n, inertia as o, RequestLoggerMiddleware as r, CorsMiddleware as s, staticAssetHandler as t, auth as u };
@@ -0,0 +1,10 @@
1
+ import { ArkstackMiddlewareConfig } from "@arkstack/contract";
2
+ import { Middleware } from "h3";
3
+ import { ClassMiddleware } from "clear-router/types/basic";
4
+ import { H3Middleware } from "@arkstack/driver-h3";
5
+
6
+ //#region src/types.d.ts
7
+ type Middleware$1 = Middleware | ClassMiddleware<Middleware>;
8
+ type MiddlewareConfig = ArkstackMiddlewareConfig<Middleware$1> | ArkstackMiddlewareConfig<H3Middleware>;
9
+ //#endregion
10
+ export { MiddlewareConfig as n, Middleware$1 as t };
@@ -0,0 +1,2 @@
1
+ import { n as MiddlewareConfig, t as Middleware } from "./types-DWUchLdg.js";
2
+ export { Middleware, MiddlewareConfig };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/driver-h3",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "H3 driver for Arkstack, providing H3-based runtime integration for the framework.",
6
6
  "homepage": "https://arkstack.toneflix.net",
@@ -33,26 +33,33 @@
33
33
  "exports": {
34
34
  ".": "./dist/index.js",
35
35
  "./middlewares": "./dist/middlewares/index.js",
36
+ "./types": "./dist/types.js",
36
37
  "./package.json": "./package.json"
37
38
  },
38
39
  "dependencies": {
39
- "clear-router": "^2.6.4",
40
- "@resora/plugin-clear-router": "^1.0.14",
41
- "resora": "^1.2.4",
42
- "@arkstack/contract": "^0.5.1"
40
+ "@ngrok/ngrok": "^1.7.0",
41
+ "@resora/plugin-clear-router": "^1.0.66",
42
+ "clear-router": "^2.9.0",
43
+ "resora": "^1.3.27",
44
+ "@arkstack/contract": "^0.5.3"
43
45
  },
44
46
  "peerDependencies": {
45
- "h3": "2.0.1-rc.16",
46
- "@arkstack/common": "^0.5.1",
47
- "@arkstack/auth": "^0.5.1"
47
+ "h3": "2.0.1-rc.22",
48
+ "@arkstack/foundry": "^0.5.3",
49
+ "@arkstack/auth": "^0.5.3",
50
+ "@arkstack/inertia": "^0.5.3",
51
+ "@arkstack/common": "^0.5.3"
48
52
  },
49
53
  "peerDependenciesMeta": {
50
54
  "@arkstack/auth": {
51
55
  "optional": true
56
+ },
57
+ "@arkstack/inertia": {
58
+ "optional": true
52
59
  }
53
60
  },
54
61
  "scripts": {
55
- "build": "tsdown",
62
+ "build": "tsdown --config-loader unrun",
56
63
  "version:patch": "pnpm version patch"
57
64
  }
58
65
  }
@@ -1,50 +0,0 @@
1
- import { ArkstackKitDriver, ArkstackMiddlewareConfig, ArkstackRouteListOptions, PromiseOrValue } from "@arkstack/contract";
2
- import { H3, H3Event, HTTPError, HTTPResponse } from "h3";
3
- import { Router } from "clear-router/h3";
4
- import { H3App, Handler, HttpContext, Middleware } from "clear-router/types/h3";
5
- import { Route } from "clear-router";
6
-
7
- //#region src/error-handler.d.ts
8
- declare const defaultErrorHandler: (err: HTTPError | Error | string, event: H3Event) => HTTPResponse | {
9
- error: boolean;
10
- message: string;
11
- status: "error";
12
- code: number;
13
- errors?: unknown;
14
- stack?: string;
15
- };
16
- //#endregion
17
- //#region src/Router.d.ts
18
- declare class Router$1 extends Router {
19
- static bind(app: H3): Promise<H3App>;
20
- static list(_options: ArkstackRouteListOptions | undefined, app: H3): Promise<Route<HttpContext, Middleware, Handler>[]>;
21
- }
22
- //#endregion
23
- //#region src/index.d.ts
24
- type H3Middleware = Middleware | [Middleware, Record<string, any>];
25
- interface H3DriverOptions {
26
- bindRouter: (app: H3) => PromiseOrValue<void>;
27
- mountPublicAssets?: (app: H3, publicPath: string) => PromiseOrValue<void>;
28
- createApp?: () => H3;
29
- onError?: (err: Error | string, event: H3Event) => unknown;
30
- }
31
- declare class H3EventResponse {
32
- response: Response;
33
- status: number;
34
- statusText?: string;
35
- constructor(response: Response);
36
- get headers(): Headers;
37
- }
38
- declare class H3Driver extends ArkstackKitDriver<H3, H3Middleware> {
39
- readonly name = "h3";
40
- private readonly options;
41
- constructor(options: H3DriverOptions);
42
- createApp(): H3;
43
- mountPublicAssets(app: H3, publicPath: string): PromiseOrValue<void>;
44
- bindRouter(app: H3): PromiseOrValue<void>;
45
- applyMiddleware(app: H3, middleware: H3Middleware | ArkstackMiddlewareConfig<H3Middleware>): void;
46
- start(app: H3, port: number): void;
47
- }
48
- //#endregion
49
- export { Router$1 as a, H3Middleware as i, H3DriverOptions as n, defaultErrorHandler as o, H3EventResponse as r, H3Driver as t };
50
- //# sourceMappingURL=index-B3NOMsXC.d.ts.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["ClearRouter"],"sources":["../src/error-handler.ts","../src/Router.ts","../src/index.ts"],"sourcesContent":["import {\n ErrorHandler,\n renderError,\n} from '@arkstack/common'\nimport { H3Event, HTTPError, HTTPResponse } from 'h3'\n\nexport const defaultErrorHandler = (err: HTTPError | Error | string, event: H3Event) => {\n const responseBody = ErrorHandler.createErrorPayload(err)\n\n if (ErrorHandler.shouldLogError(err)) {\n ErrorHandler.logUnhandledError(err, {\n headers: Object.fromEntries(event.req.headers.entries()),\n method: event.req.method,\n url: event.req.url,\n }, 'Unhandled H3 request error')\n }\n\n if (process.env.NODE_ENV === 'development') console.error(responseBody)\n\n const code = ErrorHandler.normalizeStatusCode(responseBody.code)\n event.res.status = code\n\n const acceptsHeader = event.req.headers.get('accept') ?? ''\n const expectsJson = acceptsHeader.includes('application/json') || event.req._url?.pathname?.startsWith('/api')\n\n if (expectsJson) {\n return {\n ...responseBody,\n error: true,\n message: responseBody.message,\n }\n }\n\n return new HTTPResponse(renderError({\n message: String(responseBody.message),\n stack: typeof responseBody.stack === 'string' ? responseBody.stack : undefined,\n code,\n }), {\n status: code,\n headers: {\n 'Content-Type': 'text/html',\n },\n })\n}\n\nexport default defaultErrorHandler\n","import { ArkstackRouteListOptions } from '@arkstack/contract'\nimport { Router as ClearRouter } from 'clear-router/h3'\nimport { H3 } from 'h3'\nimport type { H3App, Handler, HttpContext, Middleware } from 'clear-router/types/h3'\nimport { type Route } from 'clear-router'\nimport { clearRouterH3Plugin } from '@resora/plugin-clear-router'\nimport { importFile } from '@arkstack/common'\nimport { join } from 'node:path'\nimport { registerPlugin } from 'resora'\n\nregisterPlugin(clearRouterH3Plugin)\nClearRouter.configure({\n inferParamName: true\n})\n\nexport class Router extends ClearRouter {\n static async bind (app: H3): Promise<H3App> {\n try {\n // Register API routes\n await ClearRouter.group('/api', async () => {\n await importFile(join(process.cwd(), 'src/routes/api.ts'))\n })\n\n // Register web routes\n await ClearRouter.group('/', async () => {\n await importFile(join(process.cwd(), 'src/routes/web.ts'))\n })\n\n } catch { /** */ }\n\n // Apply the registered routes to the H3 application\n const router = ClearRouter.apply(app)\n\n return router\n }\n\n static async list (\n _options: ArkstackRouteListOptions = {}, app: H3\n ): Promise<Route<HttpContext, Middleware, Handler>[]> {\n await this.bind(app)\n\n return this.allRoutes() as never\n }\n}\n","import { ArkstackKitDriver, ArkstackMiddlewareConfig, PromiseOrValue } from '@arkstack/contract'\nimport { H3, H3Event, serve, toResponse } from 'h3'\n\nimport { Middleware as H3BaseMiddleware } from 'clear-router/types/h3'\nimport { Logger } from '@arkstack/common'\nimport { defaultErrorHandler } from './error-handler'\nimport { staticAssetHandler } from './middlewares'\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type H3Middleware = H3BaseMiddleware | [H3BaseMiddleware, Record<string, any>];\n\nexport interface H3DriverOptions {\n bindRouter: (app: H3) => PromiseOrValue<void>;\n mountPublicAssets?: (app: H3, publicPath: string) => PromiseOrValue<void>;\n createApp?: () => H3;\n onError?: (err: Error | string, event: H3Event) => unknown;\n}\n\nexport class H3EventResponse {\n status: number = 200\n statusText?: string\n\n constructor(public response: Response) {\n this.status = response.status\n this.statusText = response.statusText\n }\n\n get headers (): Headers {\n return this.response.headers\n }\n}\n\n/**\n * The H3Driver class implements the ArkstackKitDriver contract for the H3 framework.\n */\nexport class H3Driver extends ArkstackKitDriver<H3, H3Middleware> {\n readonly name = 'h3'\n private readonly options: H3DriverOptions\n\n /**\n * Creates an instance of H3Driver.\n * \n * @param options \n */\n constructor(options: H3DriverOptions) {\n super()\n this.options = options\n }\n\n /**\n * Creates an H3 application instance.\n * \n * @returns \n */\n createApp (): H3 {\n return this.options.createApp?.() ?? new H3({\n onError: this.options.onError ?? defaultErrorHandler,\n })\n }\n\n /**\n * Mounts static assets from the specified public path to the H3 application.\n * \n * @param app \n * @param publicPath \n */\n mountPublicAssets (app: H3, publicPath: string): PromiseOrValue<void> {\n if (this.options.mountPublicAssets) {\n return this.options.mountPublicAssets(app, publicPath)\n }\n\n app.use(staticAssetHandler(publicPath))\n }\n\n /**\n * Binds the router to the H3 application using the provided bindRouter function.\n * \n * @param app \n */\n bindRouter (app: H3): PromiseOrValue<void> {\n return this.options.bindRouter(app)\n }\n\n /**\n * Applies middleware to the H3 application.\n * \n * @param app \n * @param middleware \n */\n applyMiddleware (\n app: H3,\n middleware: H3Middleware | ArkstackMiddlewareConfig<H3Middleware>,\n ): void {\n const mw = Array.isArray(middleware) ? middleware[0] : middleware\n const conf = Array.isArray(middleware) && middleware[1] ? middleware[1] : {}\n\n if (typeof mw === 'function') {\n app.use(mw, conf)\n\n return\n }\n\n for (const [pos, entries] of Object.entries(middleware) as [string, H3Middleware[]][]) {\n for (const entry of entries) {\n const mw = Array.isArray(entry) ? entry[0] : entry\n const conf = Array.isArray(entry) && entry[1] ? entry[1] : {}\n\n if (pos === 'after') {\n app.use(async (evt, next) => {\n const response = await toResponse(await next(), evt)\n // evt.res.status = response.status\n evt[Symbol.for('h3.internal.event.res') as never] = new H3EventResponse(response) as never\n await mw(evt, next)\n next()\n }, conf)\n } else {\n app.use(mw, conf)\n }\n }\n }\n }\n\n /**\n * Starts the H3 server on the specified port.\n * \n * @param app \n * @param port \n */\n start (app: H3, port: number): void {\n serve(app, { port, silent: true }).ready().then(() => {\n Logger.log([\n ['Server is running on', 'white'],\n [`http://localhost:${port}`, 'cyan']\n ], ' ')\n })\n }\n}\n\nexport * from './error-handler'\nexport * from './Router'\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"middlewares-DIYzSTD5.js","names":[],"sources":["../src/middlewares/auth.ts","../src/utils/vary.ts","../src/middlewares/cors.ts","../src/middlewares/request-logger.ts","../src/middlewares/static-asset-handler.ts"],"sourcesContent":["import { Auth, AuthenticationException } from '@arkstack/auth'\n\nimport type { H3Event } from 'h3'\nimport { Hook } from '@arkstack/common'\n\nexport const auth = async (event: H3Event, next: () => unknown | Promise<unknown>) => {\n try {\n const token = readBearerToken(event.req.headers.get('authorization'))\n\n if (!token) {\n throw new AuthenticationException('Unauthenticated', {\n req: {\n headers: event.req.headers,\n method: event.req.method,\n url: event.req.url,\n path: getEventPath(event),\n },\n status: 401,\n })\n }\n\n if (Hook.has('middleware:auth', 'before'))\n Hook.get('middleware:auth', 'before')?.(event)\n\n const requestSource = {\n headers: event.req.headers,\n method: event.req.method,\n url: event.req.url,\n path: getEventPath(event),\n }\n const auth = Auth.make().setRequest(requestSource)\n const user = await auth.authorizeToken(token)\n\n event.context.user = user\n event.context.auth = auth\n event.context.authUser = user\n event.context.authToken = token;\n (event.req as any).user = user;\n (event.req as any).auth = auth;\n (event.req as any).authUser = user;\n (event.req as any).authToken = token\n\n if (Hook.has('middleware:auth', 'after'))\n Hook.get('middleware:auth', 'after')?.(event)\n\n return await next()\n } catch (error) {\n if (Hook.has('middleware:auth', 'error'))\n Hook.get('middleware:auth', 'error')?.(error, event)\n\n throw error\n }\n}\n\nconst readBearerToken = (authorization: string | null) => {\n if (!authorization?.startsWith('Bearer ')) {\n return null\n }\n\n return authorization.substring(7)\n}\n\nconst getEventPath = (event: H3Event) => event.req._url?.pathname\n","/*!\n * vary\n * Copyright(c) 2014-2017 Douglas Christopher Wilson\n * MIT Licensed\n */\nimport { H3Event } from 'h3'\n\n/**\n * RegExp to match field-name in RFC 7230 sec 3.2\n *\n * field-name = token\n * token = 1*tchar\n * tchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\"\n * / \"+\" / \"-\" / \".\" / \"^\" / \"_\" / \"`\" / \"|\" / \"~\"\n * / DIGIT / ALPHA\n * ; any VCHAR, except delimiters\n */\n\nconst FIELD_NAME_REGEXP = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/**\n * Append a field to a vary header.\n *\n * @param header\n * @param field\n * @return\n * @public\n */\nexport const append = (header: string, field: string | string[]) => {\n if (typeof header !== 'string') {\n throw new TypeError('header argument is required')\n }\n\n if (!field) {\n throw new TypeError('field argument is required')\n }\n\n // get fields array\n const fields = !Array.isArray(field) ? parse(String(field)) : field\n\n // assert on invalid field names\n for (let j = 0; j < fields.length; j++) {\n if (!FIELD_NAME_REGEXP.test(fields[j])) {\n throw new TypeError('field argument contains an invalid header name')\n }\n }\n\n // existing, unspecified vary\n if (header === '*') {\n return header\n }\n\n // enumerate current values\n let val = header\n const vals = parse(header.toLowerCase())\n\n // unspecified vary\n if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) {\n return '*'\n }\n\n for (let i = 0; i < fields.length; i++) {\n const fld = fields[i].toLowerCase()\n\n // append value (case-preserving)\n if (vals.indexOf(fld) === -1) {\n vals.push(fld)\n val = val ? val + ', ' + fields[i] : fields[i]\n }\n }\n\n return val\n}\n\n/**\n * Parse a vary header into an array.\n *\n * @param header\n * @return\n * @private\n */\n\nexport const parse = (header: string) => {\n let end = 0\n const list = []\n let start = 0\n\n // gather tokens\n for (let i = 0, len = header.length; i < len; i++) {\n switch (header.charCodeAt(i)) {\n case 0x20 /* */:\n if (start === end) {\n start = end = i + 1\n }\n break\n case 0x2c /* , */:\n list.push(header.substring(start, end))\n start = end = i + 1\n break\n default:\n end = i + 1\n break\n }\n }\n\n // final token\n list.push(header.substring(start, end))\n\n return list\n}\n\n/**\n * Mark that a request is varied on a header field.\n *\n * @param res\n * @param field\n * @public\n */\n\nexport const vary = (res?: H3Event['res'], field?: string | string[]) => {\n if (!res || !res.headers.get || !res.headers.set) {\n throw new TypeError('res argument is required')\n }\n\n // get existing header\n let val = res.headers.get('Vary') || ''\n const header = Array.isArray(val) ? val.join(', ') : String(val)\n\n if (!field) {\n return res.headers.set('Vary', '*')\n }\n\n // set new header\n if ((val = append(header, field))) {\n res.headers.set('Vary', val)\n }\n}\n","import { H3Event } from 'h3'\nimport { NextFunction } from 'clear-router/types/h3'\nimport { vary } from '../utils/vary'\n\nconst isString = (s: any) => {\n return typeof s === 'string' || s instanceof String\n}\n\nconst isOriginAllowed = (origin: string, allowedOrigin: string | string[] | RegExp | boolean) => {\n if (Array.isArray(allowedOrigin)) {\n for (let i = 0; i < allowedOrigin.length; ++i) {\n if (isOriginAllowed(origin, allowedOrigin[i])) {\n return true\n }\n }\n\n return false\n } else if (isString(allowedOrigin)) {\n return origin === allowedOrigin\n } else if (allowedOrigin instanceof RegExp) {\n return allowedOrigin.test(origin)\n } else {\n return !!allowedOrigin\n }\n}\n\nconst configureOrigin = (\n options: { origin?: string | string[] | RegExp | boolean },\n req: H3Event['req'],\n) => {\n let isAllowed: boolean\n const requestOrigin = req.headers.get('origin') || '*',\n headers = []\n\n if (!options.origin || options.origin === '*') {\n // allow any origin\n headers.push([\n {\n key: 'Access-Control-Allow-Origin',\n value: '*',\n },\n ])\n } else if (isString(options.origin)) {\n // fixed origin\n headers.push([\n {\n key: 'Access-Control-Allow-Origin',\n value: options.origin,\n },\n ])\n headers.push([\n {\n key: 'Vary',\n value: 'Origin',\n },\n ])\n } else {\n isAllowed = isOriginAllowed(requestOrigin, options.origin)\n // reflect origin\n headers.push([\n {\n key: 'Access-Control-Allow-Origin',\n value: isAllowed ? requestOrigin : false,\n },\n ])\n headers.push([\n {\n key: 'Vary',\n value: 'Origin',\n },\n ])\n }\n\n return headers\n}\n\nconst configureMethods = (options: { methods?: string[] | string }) => {\n let methods = options.methods\n if (Array.isArray(methods)) {\n methods = methods.join(',') // .methods is an array, so turn it into a string\n }\n\n return {\n key: 'Access-Control-Allow-Methods',\n value: methods,\n }\n}\n\nconst configureCredentials = (options: { credentials?: boolean }) => {\n if (options.credentials === true) {\n return {\n key: 'Access-Control-Allow-Credentials',\n value: 'true',\n }\n }\n\n return null\n}\n\nconst configureAllowedHeaders = (\n options: { allowedHeaders?: string[] | string | null; headers?: string[] | string | null },\n req: H3Event['req'],\n) => {\n let allowedHeaders = options.allowedHeaders || options.headers\n const headers = []\n\n if (!allowedHeaders) {\n allowedHeaders = req.headers.get('access-control-request-headers') // .headers wasn't specified, so reflect the request headers\n headers.push([\n {\n key: 'Vary',\n value: 'Access-Control-Request-Headers',\n },\n ])\n } else if (Array.isArray(allowedHeaders)) {\n allowedHeaders = allowedHeaders.join(',') // .headers is an array, so turn it into a string\n }\n if (allowedHeaders && allowedHeaders.length) {\n headers.push([\n {\n key: 'Access-Control-Allow-Headers',\n value: allowedHeaders,\n },\n ])\n }\n\n return headers\n}\n\nfunction configureExposedHeaders (options: { exposedHeaders?: string[] | string }) {\n let headers = options.exposedHeaders\n if (!headers) {\n return null\n } else if (Array.isArray(headers)) {\n headers = headers.join(',') // .headers is an array, so turn it into a string\n }\n if (headers && headers.length) {\n return {\n key: 'Access-Control-Expose-Headers',\n value: headers,\n }\n }\n\n return null\n}\n\nfunction configureMaxAge (options: { maxAge?: number | string }) {\n const maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString()\n if (maxAge && maxAge.length) {\n return {\n key: 'Access-Control-Max-Age',\n value: maxAge,\n }\n }\n\n return null\n}\n\nfunction applyHeaders (headers: any[], res: H3Event['res']) {\n for (let i = 0, n = headers.length; i < n; i++) {\n const header = headers[i]\n if (header) {\n if (Array.isArray(header)) {\n applyHeaders(header, res)\n } else if (header.key === 'Vary' && header.value) {\n vary(res, header.value)\n } else if (header.value) {\n res.headers.set(header.key, header.value)\n }\n }\n }\n}\n\nexport const cors =\n (\n options: {\n origin?: string | string[] | RegExp | boolean;\n methods?: string[] | string;\n allowedHeaders?: string[] | string | null;\n exposedHeaders?: string[] | string;\n credentials?: boolean;\n maxAge?: number | string;\n optionsSuccessStatus?: number;\n preflightContinue?: boolean;\n } = {},\n ) =>\n async (event: H3Event, next: NextFunction) => {\n const headers = [],\n method = event.req.method && event.req.method.toUpperCase && event.req.method.toUpperCase()\n\n if (method === 'OPTIONS') {\n // preflight\n headers.push(configureOrigin(options, event.req))\n headers.push(configureCredentials(options))\n headers.push(configureMethods(options))\n headers.push(configureAllowedHeaders(options, event.req))\n headers.push(configureMaxAge(options))\n headers.push(configureExposedHeaders(options))\n applyHeaders(headers, event.res)\n\n if (options.preflightContinue) {\n next()\n } else {\n // Safari (and potentially other browsers) need content-length 0,\n // for 204 or they just hang waiting for a body\n event.res.status = options.optionsSuccessStatus\n event.res.headers.set('Content-Length', '0')\n }\n } else {\n // actual response\n headers.push(configureOrigin(options, event.req))\n headers.push(configureCredentials(options))\n headers.push(configureExposedHeaders(options))\n applyHeaders(headers, event.res)\n next()\n }\n }\n","import { Logger, nodeEnv } from '@arkstack/common'\n\nimport { H3Middleware } from '..'\n\nconst colors: Record<string, 'green' | 'blue' | 'yellow' | 'red' | 'cyan'> = {\n GET: 'green',\n POST: 'blue',\n PUT: 'yellow',\n DELETE: 'red',\n PATCH: 'cyan',\n}\n\n/**\n * Middleware to log incoming requests and their response times.\n * \n * @param config Configuration options for the request logger middleware.\n * @param config.allowInProduction If true, the logger will also log requests in production environment. Default is false. \n * @returns H3Middleware function\n */\nexport const requestLogger = ({\n allowInProduction = false,\n}: {\n allowInProduction?: boolean\n} = {}): H3Middleware => async (event, next) => {\n if (nodeEnv() === 'prod' && !allowInProduction) return next()\n\n await next()\n\n const start = Date.now()\n const req = event.req\n const status = event.res.status || 200\n const duration = Date.now() - start\n\n Logger.log([\n [`[${req.method}]`, colors[req.method] || 'green'],\n [req.url, 'cyan'],\n [status.toString(), status >= 500 ? 'red' : status >= 400 ? 'yellow' : 'green'],\n [`- ${duration}ms`, 'dim']\n ], ' ')\n}","import { H3Event, serveStatic } from 'h3'\nimport { readFile, stat } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\n\nexport const staticAssetHandler = (publicPath: string = 'public') => {\n const rootPath = resolve(process.cwd(), publicPath)\n\n return (event: H3Event) => {\n const { pathname } = new URL(event.req.url)\n\n if (!/\\.[a-zA-Z0-9]+$/.test(pathname)) return\n if (pathname.startsWith('/.') || pathname.includes('..')) return\n\n event.res.headers.set('Cache-Control', 'public, max-age=31536000, immutable')\n event.res.headers.set('Access-Control-Allow-Origin', '*')\n event.res.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')\n event.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')\n\n return serveStatic(event, {\n indexNames: ['/index.html'],\n getContents: (id) => {\n const relativePath = id.replace(/^\\/+/, '')\n const file = join(rootPath, relativePath)\n\n return readFile(file).catch(() => null) as never\n },\n getMeta: async (id) => {\n const relativePath = id.replace(/^\\/+/, '')\n const file = join(rootPath, relativePath)\n const stats = await stat(file).catch(() => undefined)\n\n if (stats?.isFile()) {\n return {\n size: stats.size,\n mtime: stats.mtimeMs,\n }\n }\n },\n })\n }\n}"],"mappings":"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"}