Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

View file

@ -0,0 +1,2 @@
export { useNitroApp } from "./internal/app";
export type { NitroApp } from "nitropack/types";

View file

@ -0,0 +1 @@
export { useNitroApp } from "./internal/app.mjs";

View file

@ -0,0 +1 @@
export { cachedEventHandler, cachedFunction, defineCachedEventHandler, defineCachedFunction, } from "./internal/cache";

View file

@ -0,0 +1,6 @@
export {
cachedEventHandler,
cachedFunction,
defineCachedEventHandler,
defineCachedFunction
} from "./internal/cache.mjs";

View file

@ -0,0 +1 @@
export { useAppConfig, useRuntimeConfig } from "./internal/config";

View file

@ -0,0 +1 @@
export { useAppConfig, useRuntimeConfig } from "./internal/config.mjs";

View file

@ -0,0 +1 @@
export { useEvent } from "./internal/context";

View file

@ -0,0 +1 @@
export { useEvent } from "./internal/context.mjs";

View file

@ -0,0 +1 @@
export { useDatabase } from "./internal/database";

View file

@ -0,0 +1 @@
export { useDatabase } from "./internal/database.mjs";

View file

@ -0,0 +1,6 @@
export { defineNitroErrorHandler } from "./internal/error/utils";
/**
* @deprecated This export is only provided for backward compatibility and will be removed in v3.
*/
declare const _default: NitroErrorHandler;
export default _default;

View file

@ -0,0 +1,89 @@
import {
send,
setResponseHeader,
setResponseHeaders,
setResponseStatus
} from "h3";
import { defineNitroErrorHandler } from "./internal/error/utils.mjs";
import { isJsonRequest, normalizeError } from "./utils.mjs";
export { defineNitroErrorHandler } from "./internal/error/utils.mjs";
const isDev = process.env.NODE_ENV === "development";
export default defineNitroErrorHandler(
function defaultNitroErrorHandler(error, event) {
const { stack, statusCode, statusMessage, message } = normalizeError(
error,
isDev
);
const showDetails = isDev && statusCode !== 404;
const errorObject = {
url: event.path || "",
statusCode,
statusMessage,
message,
stack: showDetails ? stack.map((i) => i.text) : void 0
};
if (error.unhandled || error.fatal) {
const tags = [
"[request error]",
error.unhandled && "[unhandled]",
error.fatal && "[fatal]"
].filter(Boolean).join(" ");
console.error(
tags,
error.message + "\n" + stack.map((l) => " " + l.text).join(" \n")
);
}
if (statusCode === 404) {
setResponseHeader(event, "Cache-Control", "no-cache");
}
setResponseHeaders(event, {
// Disable the execution of any js
"Content-Security-Policy": "script-src 'none'; frame-ancestors 'none';",
// Prevent browser from guessing the MIME types of resources.
"X-Content-Type-Options": "nosniff",
// Prevent error page from being embedded in an iframe
"X-Frame-Options": "DENY",
// Prevent browsers from sending the Referer header
"Referrer-Policy": "no-referrer"
});
setResponseStatus(event, statusCode, statusMessage);
if (isJsonRequest(event)) {
setResponseHeader(event, "Content-Type", "application/json");
return send(event, JSON.stringify(errorObject));
}
setResponseHeader(event, "Content-Type", "text/html");
return send(event, renderHTMLError(errorObject));
}
);
function renderHTMLError(error) {
const statusCode = error.statusCode || 500;
const statusMessage = error.statusMessage || "Request Error";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${statusCode} ${statusMessage}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico/css/pico.min.css">
</head>
<body>
<main class="container">
<dialog open>
<article>
<header>
<h2>${statusCode} ${statusMessage}</h2>
</header>
<code>
${error.message}<br><br>
${"\n" + (error.stack || []).map((i) => `&nbsp;&nbsp;${i}`).join("<br>")}
</code>
<footer>
<a href="/" onclick="event.preventDefault();history.back();">Go Back</a>
</footer>
</article>
</dialog>
</main>
</body>
</html>
`;
}

View file

@ -0,0 +1,12 @@
export { useNitroApp } from "./internal/app";
export { useRuntimeConfig, useAppConfig } from "./internal/config";
export { useStorage } from "./internal/storage";
export { defineNitroPlugin } from "./internal/plugin";
export { defineRouteMeta } from "./internal/meta";
export { defineNitroErrorHandler } from "./internal/error/utils";
export { defineRenderHandler } from "./internal/renderer";
export { getRouteRules } from "./internal/route-rules";
export { useEvent } from "./internal/context";
export { defineTask, runTask } from "./internal/task";
export { useDatabase } from "./internal/database";
export { defineCachedFunction, defineCachedEventHandler, cachedFunction, cachedEventHandler, } from "./internal/cache";

View file

@ -0,0 +1,17 @@
export { useNitroApp } from "./internal/app.mjs";
export { useRuntimeConfig, useAppConfig } from "./internal/config.mjs";
export { useStorage } from "./internal/storage.mjs";
export { defineNitroPlugin } from "./internal/plugin.mjs";
export { defineRouteMeta } from "./internal/meta.mjs";
export { defineNitroErrorHandler } from "./internal/error/utils.mjs";
export { defineRenderHandler } from "./internal/renderer.mjs";
export { getRouteRules } from "./internal/route-rules.mjs";
export { useEvent } from "./internal/context.mjs";
export { defineTask, runTask } from "./internal/task.mjs";
export { useDatabase } from "./internal/database.mjs";
export {
defineCachedFunction,
defineCachedEventHandler,
cachedFunction,
cachedEventHandler
} from "./internal/cache.mjs";

View file

@ -0,0 +1,3 @@
import type { NitroApp } from "nitropack/types";
export declare const nitroApp: NitroApp;
export declare function useNitroApp(): NitroApp;

View file

@ -0,0 +1,170 @@
import destr from "destr";
import {
createApp,
createRouter,
fetchWithEvent,
isEvent,
lazyEventHandler,
toNodeListener
} from "h3";
import { createHooks } from "hookable";
import { Headers, createFetch } from "ofetch";
import {
fetchNodeRequestHandler,
callNodeRequestHandler
} from "node-mock-http";
import { cachedEventHandler } from "./cache.mjs";
import { useRuntimeConfig } from "./config.mjs";
import { nitroAsyncContext } from "./context.mjs";
import { createRouteRulesHandler, getRouteRulesForPath } from "./route-rules.mjs";
import { normalizeFetchResponse } from "./utils.mjs";
import errorHandler from "#nitro-internal-virtual/error-handler";
import { plugins } from "#nitro-internal-virtual/plugins";
import { handlers } from "#nitro-internal-virtual/server-handlers";
function createNitroApp() {
const config = useRuntimeConfig();
const hooks = createHooks();
const captureError = (error, context = {}) => {
const promise = hooks.callHookParallel("error", error, context).catch((error_) => {
console.error("Error while capturing another error", error_);
});
if (context.event && isEvent(context.event)) {
const errors = context.event.context.nitro?.errors;
if (errors) {
errors.push({ error, context });
}
if (context.event.waitUntil) {
context.event.waitUntil(promise);
}
}
};
const h3App = createApp({
debug: destr(process.env.DEBUG),
onError: (error, event) => {
captureError(error, { event, tags: ["request"] });
return errorHandler(error, event);
},
onRequest: async (event) => {
event.context.nitro = event.context.nitro || { errors: [] };
const fetchContext = event.node.req?.__unenv__;
if (fetchContext?._platform) {
event.context = {
_platform: fetchContext?._platform,
// #3335
...fetchContext._platform,
...event.context
};
}
if (!event.context.waitUntil && fetchContext?.waitUntil) {
event.context.waitUntil = fetchContext.waitUntil;
}
event.fetch = (req, init) => fetchWithEvent(event, req, init, { fetch: localFetch });
event.$fetch = (req, init) => fetchWithEvent(event, req, init, {
fetch: $fetch
});
event.waitUntil = (promise) => {
if (!event.context.nitro._waitUntilPromises) {
event.context.nitro._waitUntilPromises = [];
}
event.context.nitro._waitUntilPromises.push(promise);
if (event.context.waitUntil) {
event.context.waitUntil(promise);
}
};
event.captureError = (error, context) => {
captureError(error, { event, ...context });
};
await nitroApp.hooks.callHook("request", event).catch((error) => {
captureError(error, { event, tags: ["request"] });
});
},
onBeforeResponse: async (event, response) => {
await nitroApp.hooks.callHook("beforeResponse", event, response).catch((error) => {
captureError(error, { event, tags: ["request", "response"] });
});
},
onAfterResponse: async (event, response) => {
await nitroApp.hooks.callHook("afterResponse", event, response).catch((error) => {
captureError(error, { event, tags: ["request", "response"] });
});
}
});
const router = createRouter({
preemptive: true
});
const nodeHandler = toNodeListener(h3App);
const localCall = (aRequest) => callNodeRequestHandler(
nodeHandler,
aRequest
);
const localFetch = (input, init) => {
if (!input.toString().startsWith("/")) {
return globalThis.fetch(input, init);
}
return fetchNodeRequestHandler(
nodeHandler,
input,
init
).then((response) => normalizeFetchResponse(response));
};
const $fetch = createFetch({
fetch: localFetch,
Headers,
defaults: { baseURL: config.app.baseURL }
});
globalThis.$fetch = $fetch;
h3App.use(createRouteRulesHandler({ localFetch }));
for (const h of handlers) {
let handler = h.lazy ? lazyEventHandler(h.handler) : h.handler;
if (h.middleware || !h.route) {
const middlewareBase = (config.app.baseURL + (h.route || "/")).replace(
/\/+/g,
"/"
);
h3App.use(middlewareBase, handler);
} else {
const routeRules = getRouteRulesForPath(
h.route.replace(/:\w+|\*\*/g, "_")
);
if (routeRules.cache) {
handler = cachedEventHandler(handler, {
group: "nitro/routes",
...routeRules.cache
});
}
router.use(h.route, handler, h.method);
}
}
h3App.use(config.app.baseURL, router.handler);
if (import.meta._asyncContext) {
const _handler = h3App.handler;
h3App.handler = (event) => {
const ctx = { event };
return nitroAsyncContext.callAsync(ctx, () => _handler(event));
};
}
const app = {
hooks,
h3App,
router,
localCall,
localFetch,
captureError
};
return app;
}
function runNitroPlugins(nitroApp2) {
for (const plugin of plugins) {
try {
plugin(nitroApp2);
} catch (error) {
nitroApp2.captureError(error, { tags: ["plugin"] });
throw error;
}
}
}
export const nitroApp = createNitroApp();
export function useNitroApp() {
return nitroApp;
}
runNitroPlugins(nitroApp);

View file

@ -0,0 +1,8 @@
import { type EventHandler } from "h3";
import type { EventHandlerRequest, EventHandlerResponse } from "h3";
import type { CacheOptions, CachedEventHandlerOptions } from "nitropack/types";
export declare function defineCachedFunction<T, ArgsT extends unknown[] = any[]>(fn: (...args: ArgsT) => T | Promise<T>, opts?: CacheOptions<T, ArgsT>): (...args: ArgsT) => Promise<T>;
export declare function cachedFunction<T, ArgsT extends unknown[] = any[]>(fn: (...args: ArgsT) => T | Promise<T>, opts?: CacheOptions<T>): (...args: ArgsT) => Promise<T | undefined>;
export declare function defineCachedEventHandler<Request extends EventHandlerRequest = EventHandlerRequest, Response = EventHandlerResponse>(handler: EventHandler<Request, Response>, opts?: CachedEventHandlerOptions<Response>): EventHandler<Omit<Request, "body">, Response>;
export declare function defineCachedEventHandler<Request = Omit<EventHandlerRequest, "body">, Response = EventHandlerResponse>(handler: EventHandler<Request extends EventHandlerRequest ? Request : EventHandlerRequest, Request extends EventHandlerRequest ? Response : Request>, opts?: CachedEventHandlerOptions<Request extends EventHandlerRequest ? Response : Request>): EventHandler<Request extends EventHandlerRequest ? Request : EventHandlerRequest, Request extends EventHandlerRequest ? Response : Request>;
export declare const cachedEventHandler: typeof defineCachedEventHandler;

View file

@ -0,0 +1,346 @@
import {
createEvent,
defineEventHandler,
fetchWithEvent,
handleCacheHeaders,
isEvent,
splitCookiesString
} from "h3";
import { parseURL } from "ufo";
import { useNitroApp } from "./app.mjs";
import { useStorage } from "./storage.mjs";
import { hash } from "./hash.mjs";
function defaultCacheOptions() {
return {
name: "_",
base: "/cache",
swr: true,
maxAge: 1
};
}
export function defineCachedFunction(fn, opts = {}) {
opts = { ...defaultCacheOptions(), ...opts };
const pending = {};
const group = opts.group || "nitro/functions";
const name = opts.name || fn.name || "_";
const integrity = opts.integrity || hash([fn, opts]);
const validate = opts.validate || ((entry) => entry.value !== void 0);
async function get(key, resolver, shouldInvalidateCache, event) {
const cacheKey = [opts.base, group, name, key + ".json"].filter(Boolean).join(":").replace(/:\/$/, ":index");
let entry = await useStorage().getItem(cacheKey).catch((error) => {
console.error(`[cache] Cache read error.`, error);
useNitroApp().captureError(error, { event, tags: ["cache"] });
}) || {};
if (typeof entry !== "object") {
entry = {};
const error = new Error("Malformed data read from cache.");
console.error("[cache]", error);
useNitroApp().captureError(error, { event, tags: ["cache"] });
}
const ttl = (opts.maxAge ?? 0) * 1e3;
if (ttl) {
entry.expires = Date.now() + ttl;
}
const expired = shouldInvalidateCache || entry.integrity !== integrity || ttl && Date.now() - (entry.mtime || 0) > ttl || validate(entry) === false;
const _resolve = async () => {
const isPending = pending[key];
if (!isPending) {
if (entry.value !== void 0 && (opts.staleMaxAge || 0) >= 0 && opts.swr === false) {
entry.value = void 0;
entry.integrity = void 0;
entry.mtime = void 0;
entry.expires = void 0;
}
pending[key] = Promise.resolve(resolver());
}
try {
entry.value = await pending[key];
} catch (error) {
if (!isPending) {
delete pending[key];
}
throw error;
}
if (!isPending) {
entry.mtime = Date.now();
entry.integrity = integrity;
delete pending[key];
if (validate(entry) !== false) {
let setOpts;
if (opts.maxAge && !opts.swr) {
setOpts = { ttl: opts.maxAge };
}
const promise = useStorage().setItem(cacheKey, entry, setOpts).catch((error) => {
console.error(`[cache] Cache write error.`, error);
useNitroApp().captureError(error, { event, tags: ["cache"] });
});
if (event?.waitUntil) {
event.waitUntil(promise);
}
}
}
};
const _resolvePromise = expired ? _resolve() : Promise.resolve();
if (entry.value === void 0) {
await _resolvePromise;
} else if (expired && event && event.waitUntil) {
event.waitUntil(_resolvePromise);
}
if (opts.swr && validate(entry) !== false) {
_resolvePromise.catch((error) => {
console.error(`[cache] SWR handler error.`, error);
useNitroApp().captureError(error, { event, tags: ["cache"] });
});
return entry;
}
return _resolvePromise.then(() => entry);
}
return async (...args) => {
const shouldBypassCache = await opts.shouldBypassCache?.(...args);
if (shouldBypassCache) {
return fn(...args);
}
const key = await (opts.getKey || getKey)(...args);
const shouldInvalidateCache = await opts.shouldInvalidateCache?.(...args);
const entry = await get(
key,
() => fn(...args),
shouldInvalidateCache,
args[0] && isEvent(args[0]) ? args[0] : void 0
);
let value = entry.value;
if (opts.transform) {
value = await opts.transform(entry, ...args) || value;
}
return value;
};
}
export function cachedFunction(fn, opts = {}) {
return defineCachedFunction(fn, opts);
}
function getKey(...args) {
return args.length > 0 ? hash(args) : "";
}
function escapeKey(key) {
return String(key).replace(/\W/g, "");
}
export function defineCachedEventHandler(handler, opts = defaultCacheOptions()) {
const variableHeaderNames = (opts.varies || []).filter(Boolean).map((h) => h.toLowerCase()).sort();
const _opts = {
...opts,
getKey: async (event) => {
const customKey = await opts.getKey?.(event);
if (customKey) {
return escapeKey(customKey);
}
const _path = event.node.req.originalUrl || event.node.req.url || event.path;
let _pathname;
try {
_pathname = escapeKey(decodeURI(parseURL(_path).pathname)).slice(0, 16) || "index";
} catch {
_pathname = "-";
}
const _hashedPath = `${_pathname}.${hash(_path)}`;
const _headers = variableHeaderNames.map((header) => [header, event.node.req.headers[header]]).map(([name, value]) => `${escapeKey(name)}.${hash(value)}`);
return [_hashedPath, ..._headers].join(":");
},
validate: (entry) => {
if (!entry.value) {
return false;
}
if (entry.value.code >= 400) {
return false;
}
if (entry.value.body === void 0) {
return false;
}
if (entry.value.headers.etag === "undefined" || entry.value.headers["last-modified"] === "undefined") {
return false;
}
return true;
},
group: opts.group || "nitro/handlers",
integrity: opts.integrity || hash([handler, opts])
};
const _cachedHandler = cachedFunction(
async (incomingEvent) => {
const variableHeaders = {};
for (const header of variableHeaderNames) {
const value = incomingEvent.node.req.headers[header];
if (value !== void 0) {
variableHeaders[header] = value;
}
}
const reqProxy = cloneWithProxy(incomingEvent.node.req, {
headers: variableHeaders
});
const resHeaders = {};
let _resSendBody;
const resProxy = cloneWithProxy(incomingEvent.node.res, {
statusCode: 200,
writableEnded: false,
writableFinished: false,
headersSent: false,
closed: false,
getHeader(name) {
return resHeaders[name];
},
setHeader(name, value) {
resHeaders[name] = value;
return this;
},
getHeaderNames() {
return Object.keys(resHeaders);
},
hasHeader(name) {
return name in resHeaders;
},
removeHeader(name) {
delete resHeaders[name];
},
getHeaders() {
return resHeaders;
},
end(chunk, arg2, arg3) {
if (typeof chunk === "string") {
_resSendBody = chunk;
}
if (typeof arg2 === "function") {
arg2();
}
if (typeof arg3 === "function") {
arg3();
}
return this;
},
write(chunk, arg2, arg3) {
if (typeof chunk === "string") {
_resSendBody = chunk;
}
if (typeof arg2 === "function") {
arg2(void 0);
}
if (typeof arg3 === "function") {
arg3();
}
return true;
},
writeHead(statusCode, headers2) {
this.statusCode = statusCode;
if (headers2) {
if (Array.isArray(headers2) || typeof headers2 === "string") {
throw new TypeError("Raw headers is not supported.");
}
for (const header in headers2) {
const value = headers2[header];
if (value !== void 0) {
this.setHeader(
header,
value
);
}
}
}
return this;
}
});
const event = createEvent(reqProxy, resProxy);
event.fetch = (url, fetchOptions) => fetchWithEvent(event, url, fetchOptions, {
fetch: useNitroApp().localFetch
});
event.$fetch = (url, fetchOptions) => fetchWithEvent(event, url, fetchOptions, {
fetch: globalThis.$fetch
});
event.waitUntil = incomingEvent.waitUntil;
event.context = incomingEvent.context;
event.context.cache = {
options: _opts
};
const body = await handler(event) || _resSendBody;
const headers = event.node.res.getHeaders();
headers.etag = String(
headers.Etag || headers.etag || `W/"${hash(body)}"`
);
headers["last-modified"] = String(
headers["Last-Modified"] || headers["last-modified"] || (/* @__PURE__ */ new Date()).toUTCString()
);
const cacheControl = [];
if (opts.swr) {
if (opts.maxAge) {
cacheControl.push(`s-maxage=${opts.maxAge}`);
}
if (opts.staleMaxAge) {
cacheControl.push(`stale-while-revalidate=${opts.staleMaxAge}`);
} else {
cacheControl.push("stale-while-revalidate");
}
} else if (opts.maxAge) {
cacheControl.push(`max-age=${opts.maxAge}`);
}
if (cacheControl.length > 0) {
headers["cache-control"] = cacheControl.join(", ");
}
const cacheEntry = {
code: event.node.res.statusCode,
headers,
body
};
return cacheEntry;
},
_opts
);
return defineEventHandler(async (event) => {
if (opts.headersOnly) {
if (handleCacheHeaders(event, { maxAge: opts.maxAge })) {
return;
}
return handler(event);
}
const response = await _cachedHandler(
event
);
if (event.node.res.headersSent || event.node.res.writableEnded) {
return response.body;
}
if (handleCacheHeaders(event, {
modifiedTime: new Date(response.headers["last-modified"]),
etag: response.headers.etag,
maxAge: opts.maxAge
})) {
return;
}
event.node.res.statusCode = response.code;
for (const name in response.headers) {
const value = response.headers[name];
if (name === "set-cookie") {
event.node.res.appendHeader(
name,
splitCookiesString(value)
);
} else {
if (value !== void 0) {
event.node.res.setHeader(name, value);
}
}
}
return response.body;
});
}
function cloneWithProxy(obj, overrides) {
return new Proxy(obj, {
get(target, property, receiver) {
if (property in overrides) {
return overrides[property];
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
if (property in overrides) {
overrides[property] = value;
return true;
}
return Reflect.set(target, property, value, receiver);
}
});
}
export const cachedEventHandler = defineCachedEventHandler;

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,4 @@
import { $fetch } from "ofetch";
if (!globalThis.$fetch) {
globalThis.$fetch = $fetch;
}

View file

@ -0,0 +1,6 @@
import type { H3Event } from "h3";
import type { NitroRuntimeConfig } from "nitropack/types";
export declare function useRuntimeConfig<T extends NitroRuntimeConfig = NitroRuntimeConfig>(event?: H3Event): T;
export declare function useAppConfig(event?: H3Event): any;
declare const _default: any;
export default _default;

View file

@ -0,0 +1,58 @@
import { klona } from "klona";
import { appConfig as _inlineAppConfig } from "#nitro-internal-virtual/app-config";
import { applyEnv } from "./utils.env.mjs";
const _inlineRuntimeConfig = process.env.RUNTIME_CONFIG;
const envOptions = {
prefix: "NITRO_",
altPrefix: _inlineRuntimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_",
envExpansion: _inlineRuntimeConfig.nitro.envExpansion ?? process.env.NITRO_ENV_EXPANSION ?? false
};
const _sharedRuntimeConfig = _deepFreeze(
applyEnv(klona(_inlineRuntimeConfig), envOptions)
);
export function useRuntimeConfig(event) {
if (!event) {
return _sharedRuntimeConfig;
}
if (event.context.nitro.runtimeConfig) {
return event.context.nitro.runtimeConfig;
}
const runtimeConfig = klona(_inlineRuntimeConfig);
applyEnv(runtimeConfig, envOptions);
event.context.nitro.runtimeConfig = runtimeConfig;
return runtimeConfig;
}
const _sharedAppConfig = _deepFreeze(klona(_inlineAppConfig));
export function useAppConfig(event) {
if (!event) {
return _sharedAppConfig;
}
if (event.context.nitro.appConfig) {
return event.context.nitro.appConfig;
}
const appConfig = klona(_inlineAppConfig);
event.context.nitro.appConfig = appConfig;
return appConfig;
}
function _deepFreeze(object) {
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
const value = object[name];
if (value && typeof value === "object") {
_deepFreeze(value);
}
}
return Object.freeze(object);
}
export default new Proxy(/* @__PURE__ */ Object.create(null), {
get: (_, prop) => {
console.warn(
"Please use `useRuntimeConfig()` instead of accessing config directly."
);
const runtimeConfig = useRuntimeConfig();
if (prop in runtimeConfig) {
return runtimeConfig[prop];
}
return void 0;
}
});

View file

@ -0,0 +1,12 @@
import { type H3Event } from "h3";
export declare const nitroAsyncContext: import("unctx/index").UseContext<NitroAsyncContext>;
/**
*
* Access to the current Nitro request event.
*
* @experimental
* - Requires `experimental.asyncContext: true` config to work.
* - Works in Node.js and limited runtimes only
*
*/
export declare function useEvent(): H3Event;

View file

@ -0,0 +1,17 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { createError } from "h3";
import { getContext } from "unctx";
export const nitroAsyncContext = getContext("nitro-app", {
asyncContext: import.meta._asyncContext,
AsyncLocalStorage: import.meta._asyncContext ? AsyncLocalStorage : void 0
});
export function useEvent() {
try {
return nitroAsyncContext.use().event;
} catch {
const hint = import.meta._asyncContext ? "Note: This is an experimental feature and might be broken on non-Node.js environments." : "Enable the experimental flag using `experimental.asyncContext: true`.";
throw createError({
message: `Nitro request context is not available. ${hint}`
});
}
}

View file

@ -0,0 +1,2 @@
import type { Database } from "db0";
export declare function useDatabase(name?: string): Database;

View file

@ -0,0 +1,14 @@
import { createDatabase } from "db0";
import { connectionConfigs } from "#nitro-internal-virtual/database";
const instances = /* @__PURE__ */ Object.create(null);
export function useDatabase(name = "default") {
if (instances[name]) {
return instances[name];
}
if (!connectionConfigs[name]) {
throw new Error(`Database connection "${name}" not configured.`);
}
return instances[name] = createDatabase(
connectionConfigs[name].connector(connectionConfigs[name].options || {})
);
}

View file

@ -0,0 +1,2 @@
declare const _default: NitroAppPlugin;
export default _default;

View file

@ -0,0 +1,5 @@
import { createDebugger } from "hookable";
import { defineNitroPlugin } from "./plugin.mjs";
export default defineNitroPlugin((nitro) => {
createDebugger(nitro.hooks, { tag: "nitro-runtime" });
});

View file

@ -0,0 +1,9 @@
import { type H3Event, type H3Error } from "h3";
import { type InternalHandlerResponse } from "./utils";
declare const _default: NitroErrorHandler;
export default _default;
export declare function defaultHandler(error: H3Error, event: H3Event, opts?: {
silent?: boolean;
json?: boolean;
}): Promise<InternalHandlerResponse>;
export declare function loadStackTrace(error: any): Promise<void>;

View file

@ -0,0 +1,134 @@
import {
send,
getRequestHeader,
getRequestHeaders,
getRequestURL,
getResponseHeader,
setResponseHeaders,
setResponseStatus
} from "h3";
import { readFile } from "node:fs/promises";
import { resolve, dirname } from "node:path";
import consola from "consola";
import { ErrorParser } from "youch-core";
import { Youch } from "youch";
import { SourceMapConsumer } from "source-map";
import { defineNitroErrorHandler } from "./utils.mjs";
export default defineNitroErrorHandler(
async function defaultNitroErrorHandler(error, event) {
const res = await defaultHandler(error, event);
if (!event.node?.res.headersSent) {
setResponseHeaders(event, res.headers);
}
setResponseStatus(event, res.status, res.statusText);
return send(
event,
typeof res.body === "string" ? res.body : JSON.stringify(res.body, null, 2)
);
}
);
export async function defaultHandler(error, event, opts) {
const isSensitive = error.unhandled || error.fatal;
const statusCode = error.statusCode || 500;
const statusMessage = error.statusMessage || "Server Error";
const url = getRequestURL(event, { xForwardedHost: true, xForwardedProto: true });
if (statusCode === 404) {
const baseURL = import.meta.baseURL || "/";
if (/^\/[^/]/.test(baseURL) && !url.pathname.startsWith(baseURL)) {
const redirectTo = `${baseURL}${url.pathname.slice(1)}${url.search}`;
return {
status: 302,
statusText: "Found",
headers: { location: redirectTo },
body: `Redirecting...`
};
}
}
await loadStackTrace(error).catch(consola.error);
const youch = new Youch();
if (isSensitive && !opts?.silent) {
const tags = [error.unhandled && "[unhandled]", error.fatal && "[fatal]"].filter(Boolean).join(" ");
const ansiError = await (await youch.toANSI(error)).replaceAll(process.cwd(), ".");
consola.error(
`[request error] ${tags} [${event.method}] ${url}
`,
ansiError
);
}
const useJSON = opts?.json || !getRequestHeader(event, "accept")?.includes("text/html");
const headers = {
"content-type": useJSON ? "application/json" : "text/html",
// Prevent browser from guessing the MIME types of resources.
"x-content-type-options": "nosniff",
// Prevent error page from being embedded in an iframe
"x-frame-options": "DENY",
// Prevent browsers from sending the Referer header
"referrer-policy": "no-referrer",
// Disable the execution of any js
"content-security-policy": "script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self';"
};
if (statusCode === 404 || !getResponseHeader(event, "cache-control")) {
headers["cache-control"] = "no-cache";
}
const body = useJSON ? {
error: true,
url,
statusCode,
statusMessage,
message: error.message,
data: error.data,
stack: error.stack?.split("\n").map((line) => line.trim())
} : await youch.toHTML(error, {
request: {
url: url.href,
method: event.method,
headers: getRequestHeaders(event)
}
});
return {
status: statusCode,
statusText: statusMessage,
headers,
body
};
}
export async function loadStackTrace(error) {
if (!(error instanceof Error)) {
return;
}
const parsed = await new ErrorParser().defineSourceLoader(sourceLoader).parse(error);
const stack = error.message + "\n" + parsed.frames.map((frame) => fmtFrame(frame)).join("\n");
Object.defineProperty(error, "stack", { value: stack });
if (error.cause) {
await loadStackTrace(error.cause).catch(consola.error);
}
}
async function sourceLoader(frame) {
if (!frame.fileName || frame.fileType !== "fs" || frame.type === "native") {
return;
}
if (frame.type === "app") {
const rawSourceMap = await readFile(`${frame.fileName}.map`, "utf8").catch(() => {
});
if (rawSourceMap) {
const consumer = await new SourceMapConsumer(rawSourceMap);
const originalPosition = consumer.originalPositionFor({ line: frame.lineNumber, column: frame.columnNumber });
if (originalPosition.source && originalPosition.line) {
frame.fileName = resolve(dirname(frame.fileName), originalPosition.source);
frame.lineNumber = originalPosition.line;
frame.columnNumber = originalPosition.column || 0;
}
}
}
const contents = await readFile(frame.fileName, "utf8").catch(() => {
});
return contents ? { contents } : void 0;
}
function fmtFrame(frame) {
if (frame.type === "native") {
return frame.raw;
}
const src = `${frame.fileName || ""}:${frame.lineNumber}:${frame.columnNumber})`;
return frame.functionName ? `at ${frame.functionName} (${src}` : `at ${src}`;
}

View file

@ -0,0 +1,8 @@
import { type H3Error, type H3Event } from "h3";
import { type InternalHandlerResponse } from "./utils";
declare const _default: NitroErrorHandler;
export default _default;
export declare function defaultHandler(error: H3Error, event: H3Event, opts?: {
silent?: boolean;
json?: boolean;
}): InternalHandlerResponse;

View file

@ -0,0 +1,68 @@
import {
getRequestURL,
getResponseHeader,
send,
setResponseHeaders,
setResponseStatus
} from "h3";
import { defineNitroErrorHandler } from "./utils.mjs";
export default defineNitroErrorHandler(
function defaultNitroErrorHandler(error, event) {
const res = defaultHandler(error, event);
setResponseHeaders(event, res.headers);
setResponseStatus(event, res.status, res.statusText);
return send(event, JSON.stringify(res.body, null, 2));
}
);
export function defaultHandler(error, event, opts) {
const isSensitive = error.unhandled || error.fatal;
const statusCode = error.statusCode || 500;
const statusMessage = error.statusMessage || "Server Error";
const url = getRequestURL(event, { xForwardedHost: true, xForwardedProto: true });
if (statusCode === 404) {
const baseURL = import.meta.baseURL || "/";
if (/^\/[^/]/.test(baseURL) && !url.pathname.startsWith(baseURL)) {
const redirectTo = `${baseURL}${url.pathname.slice(1)}${url.search}`;
return {
status: 302,
statusText: "Found",
headers: { location: redirectTo },
body: `Redirecting...`
};
}
}
if (isSensitive && !opts?.silent) {
const tags = [error.unhandled && "[unhandled]", error.fatal && "[fatal]"].filter(Boolean).join(" ");
console.error(`[request error] ${tags} [${event.method}] ${url}
`, error);
}
const headers = {
"content-type": "application/json",
// Prevent browser from guessing the MIME types of resources.
"x-content-type-options": "nosniff",
// Prevent error page from being embedded in an iframe
"x-frame-options": "DENY",
// Prevent browsers from sending the Referer header
"referrer-policy": "no-referrer",
// Disable the execution of any js
"content-security-policy": "script-src 'none'; frame-ancestors 'none';"
};
setResponseStatus(event, statusCode, statusMessage);
if (statusCode === 404 || !getResponseHeader(event, "cache-control")) {
headers["cache-control"] = "no-cache";
}
const body = {
error: true,
url: url.href,
statusCode,
statusMessage,
message: isSensitive ? "Server Error" : error.message,
data: isSensitive ? void 0 : error.data
};
return {
status: statusCode,
statusText: statusMessage,
headers,
body
};
}

View file

@ -0,0 +1,8 @@
import type { NitroErrorHandler } from "nitropack/types";
export declare function defineNitroErrorHandler(handler: NitroErrorHandler): NitroErrorHandler;
export type InternalHandlerResponse = {
status: number;
statusText: string;
headers: Record<string, string>;
body: string | Record<string, any>;
};

View file

@ -0,0 +1,3 @@
export function defineNitroErrorHandler(handler) {
return handler;
}

View file

@ -0,0 +1,2 @@
export declare function serialize(object: any): string;
export declare function hash(value: any): any;

View file

@ -0,0 +1,183 @@
import { digest } from "ohash";
const Hasher = /* @__PURE__ */ (() => {
class Hasher2 {
buff = "";
#context = /* @__PURE__ */ new Map();
write(str) {
this.buff += str;
}
dispatch(value) {
const type = value === null ? "null" : typeof value;
return this[type](value);
}
object(object) {
if (object && typeof object.toJSON === "function") {
return this.object(object.toJSON());
}
const objString = Object.prototype.toString.call(object);
let objType = "";
const objectLength = objString.length;
objType = objectLength < 10 ? "unknown:[" + objString + "]" : objString.slice(8, objectLength - 1);
objType = objType.toLowerCase();
let objectNumber = null;
if ((objectNumber = this.#context.get(object)) === void 0) {
this.#context.set(object, this.#context.size);
} else {
return this.dispatch("[CIRCULAR:" + objectNumber + "]");
}
if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(object)) {
this.write("buffer:");
return this.write(object.toString("utf8"));
}
if (objType !== "object" && objType !== "function" && objType !== "asyncfunction") {
if (this[objType]) {
this[objType](object);
} else {
this.unknown(object, objType);
}
} else {
const keys = Object.keys(object).sort();
const extraKeys = [];
this.write("object:" + (keys.length + extraKeys.length) + ":");
const dispatchForKey = (key) => {
this.dispatch(key);
this.write(":");
this.dispatch(object[key]);
this.write(",");
};
for (const key of keys) {
dispatchForKey(key);
}
for (const key of extraKeys) {
dispatchForKey(key);
}
}
}
array(arr, unordered) {
unordered = unordered === void 0 ? false : unordered;
this.write("array:" + arr.length + ":");
if (!unordered || arr.length <= 1) {
for (const entry of arr) {
this.dispatch(entry);
}
return;
}
const contextAdditions = /* @__PURE__ */ new Map();
const entries = arr.map((entry) => {
const hasher = new Hasher2();
hasher.dispatch(entry);
for (const [key, value] of hasher.#context) {
contextAdditions.set(key, value);
}
return hasher.toString();
});
this.#context = contextAdditions;
entries.sort();
return this.array(entries, false);
}
date(date) {
return this.write("date:" + date.toJSON());
}
symbol(sym) {
return this.write("symbol:" + sym.toString());
}
unknown(value, type) {
this.write(type);
if (!value) {
return;
}
this.write(":");
if (value && typeof value.entries === "function") {
return this.array(
[...value.entries()],
true
/* ordered */
);
}
}
error(err) {
return this.write("error:" + err.toString());
}
boolean(bool) {
return this.write("bool:" + bool);
}
string(string) {
this.write("string:" + string.length + ":");
this.write(string);
}
function(fn) {
this.write("fn:");
if (isNativeFunction(fn)) {
this.dispatch("[native]");
} else {
this.dispatch(fn.toString());
}
}
number(number) {
return this.write("number:" + number);
}
null() {
return this.write("Null");
}
undefined() {
return this.write("Undefined");
}
regexp(regex) {
return this.write("regex:" + regex.toString());
}
arraybuffer(arr) {
this.write("arraybuffer:");
return this.dispatch(new Uint8Array(arr));
}
url(url) {
return this.write("url:" + url.toString());
}
map(map) {
this.write("map:");
const arr = [...map];
return this.array(arr, false);
}
set(set) {
this.write("set:");
const arr = [...set];
return this.array(arr, false);
}
bigint(number) {
return this.write("bigint:" + number.toString());
}
}
for (const type of [
"uint8array",
"uint8clampedarray",
"unt8array",
"uint16array",
"unt16array",
"uint32array",
"unt32array",
"float32array",
"float64array"
]) {
Hasher2.prototype[type] = function(arr) {
this.write(type + ":");
return this.array([...arr], false);
};
}
function isNativeFunction(f) {
if (typeof f !== "function") {
return false;
}
return Function.prototype.toString.call(f).slice(
-15
/* "[native code] }".length */
) === "[native code] }";
}
return Hasher2;
})();
export function serialize(object) {
const hasher = new Hasher();
hasher.dispatch(object);
return hasher.buff;
}
export function hash(value) {
return digest(typeof value === "string" ? value : serialize(value)).replace(/[-_]/g, "").slice(0, 10);
}

View file

@ -0,0 +1,6 @@
export { trapUnhandledNodeErrors, normalizeCookieHeader, requestHasBody, joinHeaders, toBuffer, } from "./utils";
export { normalizeLambdaIncomingHeaders, normalizeLambdaOutgoingHeaders, normalizeLambdaOutgoingBody, } from "./utils.lambda";
export { startScheduleRunner, runCronTasks } from "./task";
export { getAzureParsedCookiesFromHeaders } from "./utils.azure";
export { getGracefulShutdownConfig, setupGracefulShutdown } from "./shutdown";
export { getRouteRulesForPath } from "./route-rules";

View file

@ -0,0 +1,16 @@
export {
trapUnhandledNodeErrors,
normalizeCookieHeader,
requestHasBody,
joinHeaders,
toBuffer
} from "./utils.mjs";
export {
normalizeLambdaIncomingHeaders,
normalizeLambdaOutgoingHeaders,
normalizeLambdaOutgoingBody
} from "./utils.lambda.mjs";
export { startScheduleRunner, runCronTasks } from "./task.mjs";
export { getAzureParsedCookiesFromHeaders } from "./utils.azure.mjs";
export { getGracefulShutdownConfig, setupGracefulShutdown } from "./shutdown.mjs";
export { getRouteRulesForPath } from "./route-rules.mjs";

View file

@ -0,0 +1,16 @@
/**
* Gracefully shuts down `server` when the process receives
* the passed signals
*
* @param {http.Server} server
* @param {object} opts
* signals: string (each signal separated by SPACE)
* timeout: timeout value for forceful shutdown in ms
* forceExit: force process.exit() - otherwise just let event loop clear
* development: boolean value (if true, no graceful shutdown to speed up development
* preShutdown: optional function. Needs to return a promise. - HTTP sockets are still available and untouched
* onShutdown: optional function. Needs to return a promise.
* finally: optional function, handled at the end of the shutdown.
*/
declare function GracefulShutdown(server: any, opts: any): () => any;
export default GracefulShutdown;

View file

@ -0,0 +1,213 @@
import http from "node:http";
const debug = (...args) => {
};
function GracefulShutdown(server, opts) {
opts = opts || {};
const options = Object.assign(
{
signals: "SIGINT SIGTERM",
timeout: 3e4,
development: false,
forceExit: true,
onShutdown: (signal) => Promise.resolve(signal),
preShutdown: (signal) => Promise.resolve(signal)
},
opts
);
let isShuttingDown = false;
const connections = {};
let connectionCounter = 0;
const secureConnections = {};
let secureConnectionCounter = 0;
let failed = false;
let finalRun = false;
function onceFactory() {
let called = false;
return (emitter, events, callback) => {
function call() {
if (!called) {
called = true;
return Reflect.apply(callback, this, arguments);
}
}
for (const e of events) {
emitter.on(e, call);
}
};
}
const signals = options.signals.split(" ").map((s) => s.trim()).filter((s) => s.length > 0);
const once = onceFactory();
once(process, signals, (signal) => {
debug("received shut down signal", signal);
shutdown(signal).then(() => {
if (options.forceExit) {
process.exit(failed ? 1 : 0);
}
}).catch((error) => {
debug("server shut down error occurred", error);
process.exit(1);
});
});
function isFunction(functionToCheck) {
const getType = Object.prototype.toString.call(functionToCheck);
return /^\[object\s([A-Za-z]+)?Function]$/.test(getType);
}
function destroy(socket, force = false) {
if (socket._isIdle && isShuttingDown || force) {
socket.destroy();
if (socket.server instanceof http.Server) {
delete connections[socket._connectionId];
} else {
delete secureConnections[socket._connectionId];
}
}
}
function destroyAllConnections(force = false) {
debug("Destroy Connections : " + (force ? "forced close" : "close"));
let counter = 0;
let secureCounter = 0;
for (const key of Object.keys(connections)) {
const socket = connections[key];
const serverResponse = socket._httpMessage;
if (serverResponse && !force) {
if (!serverResponse.headersSent) {
serverResponse.setHeader("connection", "close");
}
} else {
counter++;
destroy(socket);
}
}
debug("Connections destroyed : " + counter);
debug("Connection Counter : " + connectionCounter);
for (const key of Object.keys(secureConnections)) {
const socket = secureConnections[key];
const serverResponse = socket._httpMessage;
if (serverResponse && !force) {
if (!serverResponse.headersSent) {
serverResponse.setHeader("connection", "close");
}
} else {
secureCounter++;
destroy(socket);
}
}
debug("Secure Connections destroyed : " + secureCounter);
debug("Secure Connection Counter : " + secureConnectionCounter);
}
server.on("request", (req, res) => {
req.socket._isIdle = false;
if (isShuttingDown && !res.headersSent) {
res.setHeader("connection", "close");
}
res.on("finish", () => {
req.socket._isIdle = true;
destroy(req.socket);
});
});
server.on("connection", (socket) => {
if (isShuttingDown) {
socket.destroy();
} else {
const id = connectionCounter++;
socket._isIdle = true;
socket._connectionId = id;
connections[id] = socket;
socket.once("close", () => {
delete connections[socket._connectionId];
});
}
});
server.on("secureConnection", (socket) => {
if (isShuttingDown) {
socket.destroy();
} else {
const id = secureConnectionCounter++;
socket._isIdle = true;
socket._connectionId = id;
secureConnections[id] = socket;
socket.once("close", () => {
delete secureConnections[socket._connectionId];
});
}
});
process.on("close", () => {
debug("closed");
});
function shutdown(sig) {
function cleanupHttp() {
destroyAllConnections();
debug("Close http server");
return new Promise((resolve, reject) => {
server.close((err) => {
if (err) {
return reject(err);
}
return resolve(true);
});
});
}
debug("shutdown signal - " + sig);
if (options.development) {
debug("DEV-Mode - immediate forceful shutdown");
return process.exit(0);
}
function finalHandler() {
if (!finalRun) {
finalRun = true;
if (options.finally && isFunction(options.finally)) {
debug("executing finally()");
options.finally();
}
}
return Promise.resolve();
}
function waitForReadyToShutDown(totalNumInterval) {
debug(`waitForReadyToShutDown... ${totalNumInterval}`);
if (totalNumInterval === 0) {
debug(
`Could not close connections in time (${options.timeout}ms), will forcefully shut down`
);
return Promise.resolve(true);
}
const allConnectionsClosed = Object.keys(connections).length === 0 && Object.keys(secureConnections).length === 0;
if (allConnectionsClosed) {
debug("All connections closed. Continue to shutting down");
return Promise.resolve(false);
}
debug("Schedule the next waitForReadyToShutdown");
return new Promise((resolve) => {
setTimeout(() => {
resolve(waitForReadyToShutDown(totalNumInterval - 1));
}, 250);
});
}
if (isShuttingDown) {
return Promise.resolve();
}
debug("shutting down");
return options.preShutdown(sig).then(() => {
isShuttingDown = true;
cleanupHttp();
}).then(() => {
const pollIterations = options.timeout ? Math.round(options.timeout / 250) : 0;
return waitForReadyToShutDown(pollIterations);
}).then((force) => {
debug("Do onShutdown now");
if (force) {
destroyAllConnections(force);
}
return options.onShutdown(sig);
}).then(finalHandler).catch((error) => {
const errString = typeof error === "string" ? error : JSON.stringify(error);
debug(errString);
failed = true;
throw errString;
});
}
function shutdownManual() {
return shutdown("manual");
}
return shutdownManual;
}
export default GracefulShutdown;

View file

@ -0,0 +1,2 @@
import type { NitroRouteMeta } from "nitropack/types";
export declare function defineRouteMeta(meta: NitroRouteMeta): NitroRouteMeta;

View file

@ -0,0 +1,3 @@
export function defineRouteMeta(meta) {
return meta;
}

View file

@ -0,0 +1,3 @@
import type { NitroAppPlugin } from "nitropack/types";
export declare function defineNitroPlugin(def: NitroAppPlugin): NitroAppPlugin;
export declare const nitroPlugin: typeof defineNitroPlugin;

View file

@ -0,0 +1,4 @@
export function defineNitroPlugin(def) {
return def;
}
export const nitroPlugin = defineNitroPlugin;

View file

@ -0,0 +1,2 @@
import type { RenderHandler } from "nitropack/types";
export declare function defineRenderHandler(render: RenderHandler): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;

View file

@ -0,0 +1,48 @@
import {
eventHandler,
getResponseStatus,
send,
setResponseHeader,
setResponseHeaders,
setResponseStatus
} from "h3";
import { useNitroApp } from "./app.mjs";
import { useRuntimeConfig } from "./config.mjs";
export function defineRenderHandler(render) {
const runtimeConfig = useRuntimeConfig();
return eventHandler(async (event) => {
const nitroApp = useNitroApp();
const ctx = { event, render, response: void 0 };
await nitroApp.hooks.callHook("render:before", ctx);
if (!ctx.response) {
if (event.path === `${runtimeConfig.app.baseURL}favicon.ico`) {
setResponseHeader(event, "Content-Type", "image/x-icon");
return send(
event,
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
);
}
ctx.response = await ctx.render(event);
if (!ctx.response) {
const _currentStatus = getResponseStatus(event);
setResponseStatus(event, _currentStatus === 200 ? 500 : _currentStatus);
return send(
event,
"No response returned from render handler: " + event.path
);
}
}
await nitroApp.hooks.callHook("render:response", ctx.response, ctx);
if (ctx.response.headers) {
setResponseHeaders(event, ctx.response.headers);
}
if (ctx.response.statusCode || ctx.response.statusMessage) {
setResponseStatus(
event,
ctx.response.statusCode,
ctx.response.statusMessage
);
}
return ctx.response.body;
});
}

View file

@ -0,0 +1,14 @@
import { type H3Event } from "h3";
import type { NitroRouteRules } from "nitropack/types";
export declare function createRouteRulesHandler(ctx: {
localFetch: typeof globalThis.fetch;
}): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any> | undefined>;
export declare function getRouteRules(event: H3Event): NitroRouteRules;
type DeepReadonly<T> = T extends Record<string, any> ? {
readonly [K in keyof T]: DeepReadonly<T[K]>;
} : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : T;
/**
* @param path - The path to match against route rules. This should not contain a query string.
*/
export declare function getRouteRulesForPath(path: string): DeepReadonly<NitroRouteRules>;
export {};

View file

@ -0,0 +1,67 @@
import defu from "defu";
import {
eventHandler,
proxyRequest,
sendRedirect,
setHeaders
} from "h3";
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
import { getQuery, joinURL, withQuery, withoutBase } from "ufo";
import { useRuntimeConfig } from "./config.mjs";
const config = useRuntimeConfig();
const _routeRulesMatcher = toRouteMatcher(
createRadixRouter({ routes: config.nitro.routeRules })
);
export function createRouteRulesHandler(ctx) {
return eventHandler((event) => {
const routeRules = getRouteRules(event);
if (routeRules.headers) {
setHeaders(event, routeRules.headers);
}
if (routeRules.redirect) {
let target = routeRules.redirect.to;
if (target.endsWith("/**")) {
let targetPath = event.path;
const strpBase = routeRules.redirect._redirectStripBase;
if (strpBase) {
targetPath = withoutBase(targetPath, strpBase);
}
target = joinURL(target.slice(0, -3), targetPath);
} else if (event.path.includes("?")) {
const query = getQuery(event.path);
target = withQuery(target, query);
}
return sendRedirect(event, target, routeRules.redirect.statusCode);
}
if (routeRules.proxy) {
let target = routeRules.proxy.to;
if (target.endsWith("/**")) {
let targetPath = event.path;
const strpBase = routeRules.proxy._proxyStripBase;
if (strpBase) {
targetPath = withoutBase(targetPath, strpBase);
}
target = joinURL(target.slice(0, -3), targetPath);
} else if (event.path.includes("?")) {
const query = getQuery(event.path);
target = withQuery(target, query);
}
return proxyRequest(event, target, {
fetch: ctx.localFetch,
...routeRules.proxy
});
}
});
}
export function getRouteRules(event) {
event.context._nitro = event.context._nitro || {};
if (!event.context._nitro.routeRules) {
event.context._nitro.routeRules = getRouteRulesForPath(
withoutBase(event.path.split("?")[0], useRuntimeConfig().app.baseURL)
);
}
return event.context._nitro.routeRules;
}
export function getRouteRulesForPath(path) {
return defu({}, ..._routeRulesMatcher.matchAll(path).reverse());
}

View file

@ -0,0 +1,2 @@
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, OpenAPI3>;
export default _default;

View file

@ -0,0 +1,100 @@
import { eventHandler, getRequestURL } from "h3";
import { joinURL } from "ufo";
import { defu } from "defu";
import { handlersMeta } from "#nitro-internal-virtual/server-handlers-meta";
import { useRuntimeConfig } from "../config.mjs";
export default eventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event);
const base = runtimeConfig.app?.baseURL;
const url = joinURL(getRequestURL(event).origin, base);
const meta = {
title: "Nitro Server Routes",
...runtimeConfig.nitro?.openAPI?.meta
};
const {
paths,
globals: { components, ...globalsRest }
} = getHandlersMeta();
const extensible = Object.fromEntries(
Object.entries(globalsRest).filter(([key]) => key.startsWith("x-"))
);
return {
openapi: "3.1.0",
info: {
title: meta?.title,
version: meta?.version,
description: meta?.description
},
servers: [
{
url,
description: "Local Development Server",
variables: {}
}
],
paths,
components,
...extensible
};
});
function getHandlersMeta() {
const paths = {};
let globals = {};
for (const h of handlersMeta) {
const { route, parameters } = normalizeRoute(h.route || "");
const tags = defaultTags(h.route || "");
const method = (h.method || "get").toLowerCase();
const { $global, ...openAPI } = h.meta?.openAPI || {};
const item = {
[method]: {
tags,
parameters,
responses: {
200: { description: "OK" }
},
...openAPI
}
};
if ($global) {
globals = defu($global, globals);
}
if (paths[route] === void 0) {
paths[route] = item;
} else {
Object.assign(paths[route], item);
}
}
return { paths, globals };
}
function normalizeRoute(_route) {
const parameters = [];
let anonymousCtr = 0;
const route = _route.replace(/:(\w+)/g, (_, name) => `{${name}}`).replace(/\/(\*)\//g, () => `/{param${++anonymousCtr}}/`).replace(/\*\*{/, "{").replace(/\/(\*\*)$/g, () => `/{*param${++anonymousCtr}}`);
const paramMatches = route.matchAll(/{(\*?\w+)}/g);
for (const match of paramMatches) {
const name = match[1];
if (!parameters.some((p) => p.name === name)) {
parameters.push({
name,
in: "path",
required: true,
schema: { type: "string" }
});
}
}
return {
route,
parameters
};
}
function defaultTags(route) {
const tags = [];
if (route.startsWith("/api/")) {
tags.push("API Routes");
} else if (route.startsWith("/_")) {
tags.push("Internal");
} else {
tags.push("App Routes");
}
return tags;
}

View file

@ -0,0 +1,2 @@
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, string>;
export default _default;

View file

@ -0,0 +1,193 @@
import { eventHandler } from "h3";
import { useRuntimeConfig } from "../config.mjs";
export default eventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event);
const title = runtimeConfig.nitro.openAPI?.meta?.title || "API Reference";
const description = runtimeConfig.nitro.openAPI?.meta?.description || "";
const openAPIEndpoint = runtimeConfig.nitro.openAPI?.route || "./_openapi.json";
const _config = runtimeConfig.nitro.openAPI?.ui?.scalar;
const scalarConfig = {
..._config,
url: openAPIEndpoint,
// @ts-expect-error (missing types?)
spec: { url: openAPIEndpoint, ..._config?.spec }
};
return (
/* html */
`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="${description}" />
<title>${title}</title>
<style>
${scalarConfig.theme ? null : customTheme}
</style>
</head>
<body>
<script
id="api-reference"
data-configuration="${JSON.stringify(scalarConfig).split('"').join("&quot;")}"
><\/script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
</body>
</html>`
);
});
const customTheme = (
/* css */
`/* basic theme */
.light-mode,
.light-mode .dark-mode {
--theme-background-1: #fff;
--theme-background-2: #fafafa;
--theme-background-3: rgb(245 245 245);
--theme-color-1: #2a2f45;
--theme-color-2: #757575;
--theme-color-3: #8e8e8e;
--theme-color-accent: #ef4444;
--theme-background-accent: transparent;
--theme-border-color: rgba(0, 0, 0, 0.1);
}
.dark-mode {
--theme-background-1: #171717;
--theme-background-2: #262626;
--theme-background-3: #2e2e2e;
--theme-color-1: rgba(255, 255, 255, 0.9);
--theme-color-2: rgba(255, 255, 255, 0.62);
--theme-color-3: rgba(255, 255, 255, 0.44);
--theme-color-accent: #f87171;
--theme-background-accent: transparent;
--theme-border-color: rgba(255, 255, 255, 0.1);
}
/* Document Sidebar */
.light-mode .t-doc__sidebar,
.dark-mode .t-doc__sidebar {
--sidebar-background-1: var(--theme-background-1);
--sidebar-color-1: var(--theme-color-1);
--sidebar-color-2: var(--theme-color-2);
--sidebar-border-color: var(--theme-border-color);
--sidebar-item-hover-background: transparent;
--sidebar-item-hover-color: var(--sidebar-color-1);
--sidebar-item-active-background: var(--theme-background-accent);
--sidebar-color-active: var(--theme-color-accent);
--sidebar-search-background: transparent;
--sidebar-search-color: var(--theme-color-3);
--sidebar-search-border-color: var(--theme-border-color);
}
/* advanced */
.light-mode .dark-mode,
.light-mode {
--theme-color-green: #91b859;
--theme-color-red: #e53935;
--theme-color-yellow: #e2931d;
--theme-color-blue: #6182b8;
--theme-color-orange: #f76d47;
--theme-color-purple: #9c3eda;
}
.dark-mode {
--theme-color-green: #c3e88d;
--theme-color-red: #f07178;
--theme-color-yellow: #ffcb6b;
--theme-color-blue: #82aaff;
--theme-color-orange: #f78c6c;
--theme-color-purple: #c792ea;
}
/* custom-theme */
.section-container:nth-of-type(2)
~ .section-container
.scalar-card
.scalar-card-header {
--theme-background-2: var(--theme-background-1) !important;
}
.section-flare {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
animation: spin 39s linear infinite;
transition: all 0.3s ease-in-out;
opacity: 1;
}
.section-flare-item:nth-of-type(1),
.section-flare-item:nth-of-type(2),
.section-flare-item:nth-of-type(3) {
content: "";
width: 1000px;
height: 1000px;
position: absolute;
top: 0;
right: 0;
border-radius: 50%;
background: var(--default-theme-background-1);
display: block;
opacity: 1;
filter: blur(48px);
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
-webkit-transform: translate3d(0, 0, 0);
-webkit-transform: translateZ(0);
perspective: 1000;
transform: translate3d(0, 0, 0);
transform: translateZ(0);
}
.section-flare-item:nth-of-type(2) {
top: initial;
right: initial;
background: #ef4444;
width: 700px;
height: 700px;
opacity: 0.3;
animation: sectionflare 37s linear infinite;
}
.section-flare-item:nth-of-type(1) {
top: initial;
right: initial;
bottom: 0;
left: -20px;
background: #ef4444;
width: 500px;
height: 500px;
opacity: 0.3;
animation: sectionflare2 38s linear infinite;
}
@keyframes sectionflare {
0%,
100% {
transform: translate3d(0, 0, 0);
}
50% {
transform: translate3d(525px, 525px, 0);
}
}
@keyframes sectionflare2 {
0%,
100% {
transform: translate3d(700px, 700px, 0);
}
50% {
transform: translate3d(0, 0, 0);
}
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
.section-container:nth-of-type(2) {
overflow: hidden;
}`
);

View file

@ -0,0 +1,2 @@
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, string>;
export default _default;

View file

@ -0,0 +1,43 @@
import { eventHandler } from "h3";
import { useRuntimeConfig } from "../config.mjs";
export default eventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event);
const title = runtimeConfig.nitro.openAPI?.meta?.title || "API Reference";
const description = runtimeConfig.nitro.openAPI?.meta?.description || "";
const openAPIEndpoint = runtimeConfig.nitro.openAPI?.route || "./_openapi.json";
const CDN_BASE = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@^5";
return (
/* html */
`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="${description}" />
<title>${title}</title>
<link rel="stylesheet" href="${CDN_BASE}/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="${CDN_BASE}/swagger-ui-bundle.js" crossorigin><\/script>
<script
src="${CDN_BASE}/swagger-ui-standalone-preset.js"
crossorigin
><\/script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: ${JSON.stringify(openAPIEndpoint)},
dom_id: "#swagger-ui",
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset,
],
layout2: "StandaloneLayout",
});
};
<\/script>
</body>
</html> `
);
});

View file

@ -0,0 +1,9 @@
import type { Server as HttpServer } from "node:http";
import type { NitroApp } from "nitropack/types";
export declare function getGracefulShutdownConfig(): {
disabled: boolean;
signals: string[];
timeout: number;
forceExit: boolean;
};
export declare function setupGracefulShutdown(listener: HttpServer, nitroApp: NitroApp): void;

View file

@ -0,0 +1,34 @@
import gracefulShutdown from "./lib/http-graceful-shutdown.mjs";
export function getGracefulShutdownConfig() {
return {
disabled: !!process.env.NITRO_SHUTDOWN_DISABLED,
signals: (process.env.NITRO_SHUTDOWN_SIGNALS || "SIGTERM SIGINT").split(" ").map((s) => s.trim()),
timeout: Number.parseInt(process.env.NITRO_SHUTDOWN_TIMEOUT || "", 10) || 3e4,
forceExit: !process.env.NITRO_SHUTDOWN_NO_FORCE_EXIT
};
}
export function setupGracefulShutdown(listener, nitroApp) {
const shutdownConfig = getGracefulShutdownConfig();
if (shutdownConfig.disabled) {
return;
}
gracefulShutdown(listener, {
signals: shutdownConfig.signals.join(" "),
timeout: shutdownConfig.timeout,
forceExit: shutdownConfig.forceExit,
onShutdown: async () => {
await new Promise((resolve) => {
const timeout = setTimeout(() => {
console.warn("Graceful shutdown timeout, force exiting...");
resolve();
}, shutdownConfig.timeout);
nitroApp.hooks.callHook("close").catch((error) => {
console.error(error);
}).finally(() => {
clearTimeout(timeout);
resolve();
});
});
}
});
}

View file

@ -0,0 +1,2 @@
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, any>;
export default _default;

View file

@ -0,0 +1,87 @@
import {
createError,
eventHandler,
getRequestHeader,
getResponseHeader,
removeResponseHeader,
setResponseHeader,
appendResponseHeader,
setResponseStatus
} from "h3";
import {
decodePath,
joinURL,
parseURL,
withLeadingSlash,
withoutTrailingSlash
} from "ufo";
import {
getAsset,
isPublicAssetURL,
readAsset
} from "#nitro-internal-virtual/public-assets";
const METHODS = /* @__PURE__ */ new Set(["HEAD", "GET"]);
const EncodingMap = { gzip: ".gz", br: ".br" };
export default eventHandler((event) => {
if (event.method && !METHODS.has(event.method)) {
return;
}
let id = decodePath(
withLeadingSlash(withoutTrailingSlash(parseURL(event.path).pathname))
);
let asset;
const encodingHeader = String(
getRequestHeader(event, "accept-encoding") || ""
);
const encodings = [
...encodingHeader.split(",").map((e) => EncodingMap[e.trim()]).filter(Boolean).sort(),
""
];
if (encodings.length > 1) {
appendResponseHeader(event, "Vary", "Accept-Encoding");
}
for (const encoding of encodings) {
for (const _id of [id + encoding, joinURL(id, "index.html" + encoding)]) {
const _asset = getAsset(_id);
if (_asset) {
asset = _asset;
id = _id;
break;
}
}
}
if (!asset) {
if (isPublicAssetURL(id)) {
removeResponseHeader(event, "Cache-Control");
throw createError({ statusCode: 404 });
}
return;
}
const ifNotMatch = getRequestHeader(event, "if-none-match") === asset.etag;
if (ifNotMatch) {
setResponseStatus(event, 304, "Not Modified");
return "";
}
const ifModifiedSinceH = getRequestHeader(event, "if-modified-since");
const mtimeDate = new Date(asset.mtime);
if (ifModifiedSinceH && asset.mtime && new Date(ifModifiedSinceH) >= mtimeDate) {
setResponseStatus(event, 304, "Not Modified");
return "";
}
if (asset.type && !getResponseHeader(event, "Content-Type")) {
setResponseHeader(event, "Content-Type", asset.type);
}
if (asset.etag && !getResponseHeader(event, "ETag")) {
setResponseHeader(event, "ETag", asset.etag);
}
if (asset.mtime && !getResponseHeader(event, "Last-Modified")) {
setResponseHeader(event, "Last-Modified", mtimeDate.toUTCString());
}
if (asset.encoding && !getResponseHeader(event, "Content-Encoding")) {
setResponseHeader(event, "Content-Encoding", asset.encoding);
}
if (asset.size > 0 && !getResponseHeader(event, "Content-Length")) {
setResponseHeader(event, "Content-Length", asset.size);
}
return readAsset(id);
});

View file

@ -0,0 +1,2 @@
import type { Storage, StorageValue } from "unstorage";
export declare function useStorage<T extends StorageValue = StorageValue>(base?: string): Storage<T>;

View file

@ -0,0 +1,5 @@
import { prefixStorage } from "unstorage";
import { storage } from "#nitro-internal-virtual/storage";
export function useStorage(base = "") {
return base ? prefixStorage(storage, base) : storage;
}

View file

@ -0,0 +1,17 @@
import type { Task, TaskContext, TaskPayload, TaskResult } from "nitropack/types";
/** @experimental */
export declare function defineTask<RT = unknown>(def: Task<RT>): Task<RT>;
/** @experimental */
export declare function runTask<RT = unknown>(name: string, { payload, context, }?: {
payload?: TaskPayload;
context?: TaskContext;
}): Promise<TaskResult<RT>>;
/** @experimental */
export declare function startScheduleRunner(): void;
/** @experimental */
export declare function getCronTasks(cron: string): string[];
/** @experimental */
export declare function runCronTasks(cron: string, ctx: {
payload?: TaskPayload;
context?: TaskContext;
}): Promise<TaskResult[]>;

View file

@ -0,0 +1,73 @@
import { Cron } from "croner";
import { createError } from "h3";
import { isTest } from "std-env";
import { scheduledTasks, tasks } from "#nitro-internal-virtual/tasks";
export function defineTask(def) {
if (typeof def.run !== "function") {
def.run = () => {
throw new TypeError("Task must implement a `run` method!");
};
}
return def;
}
const __runningTasks__ = {};
export async function runTask(name, {
payload = {},
context = {}
} = {}) {
if (__runningTasks__[name]) {
return __runningTasks__[name];
}
if (!(name in tasks)) {
throw createError({
message: `Task \`${name}\` is not available!`,
statusCode: 404
});
}
if (!tasks[name].resolve) {
throw createError({
message: `Task \`${name}\` is not implemented!`,
statusCode: 501
});
}
const handler = await tasks[name].resolve();
const taskEvent = { name, payload, context };
__runningTasks__[name] = handler.run(taskEvent);
try {
const res = await __runningTasks__[name];
return res;
} finally {
delete __runningTasks__[name];
}
}
export function startScheduleRunner() {
if (!scheduledTasks || scheduledTasks.length === 0 || isTest) {
return;
}
const payload = {
scheduledTime: Date.now()
};
for (const schedule of scheduledTasks) {
const cron = new Cron(schedule.cron, async () => {
await Promise.all(
schedule.tasks.map(
(name) => runTask(name, {
payload,
context: {}
}).catch((error) => {
console.error(
`Error while running scheduled task "${name}"`,
error
);
})
)
);
});
}
}
export function getCronTasks(cron) {
return (scheduledTasks || []).find((task) => task.cron === cron)?.tasks || [];
}
export function runCronTasks(cron, ctx) {
return Promise.all(getCronTasks(cron).map((name) => runTask(name, ctx)));
}

View file

@ -0,0 +1,2 @@
declare const _default: NitroAppPlugin;
export default _default;

View file

@ -0,0 +1,29 @@
import { eventHandler } from "h3";
import { defineNitroPlugin } from "./plugin.mjs";
const globalTiming = globalThis.__timing__ || {
start: () => 0,
end: () => 0,
metrics: []
};
const timingMiddleware = eventHandler((event) => {
const start = globalTiming.start();
const _end = event.node.res.end;
event.node.res.end = function(chunk, encoding, cb) {
const metrics = [
["Generate", globalTiming.end(start)],
...globalTiming.metrics
];
const serverTiming = metrics.map((m) => `-;dur=${m[1]};desc="${encodeURIComponent(m[0])}"`).join(", ");
if (!event.node.res.headersSent) {
event.node.res.setHeader("Server-Timing", serverTiming);
}
_end.call(event.node.res, chunk, encoding, cb);
return this;
}.bind(event.node.res);
});
export default defineNitroPlugin((nitro) => {
nitro.h3App.stack.unshift({
route: "/",
handler: timingMiddleware
});
});

View file

@ -0,0 +1,2 @@
import type { Cookie } from "@azure/functions";
export declare function getAzureParsedCookiesFromHeaders(headers: Record<string, number | string | string[] | undefined>): Cookie[];

View file

@ -0,0 +1,51 @@
import { parse } from "cookie-es";
import { splitCookiesString } from "h3";
export function getAzureParsedCookiesFromHeaders(headers) {
const setCookieHeader = headers["set-cookie"];
if (!setCookieHeader || typeof setCookieHeader === "number" || setCookieHeader.length === 0) {
return [];
}
const azureCookies = [];
for (const setCookieStr of splitCookiesString(setCookieHeader)) {
const setCookie = Object.entries(parse(setCookieStr));
if (setCookie.length === 0) {
continue;
}
const [[key, value], ..._setCookieOptions] = setCookie;
const setCookieOptions = Object.fromEntries(
_setCookieOptions.map(([k, v]) => [k.toLowerCase(), v])
);
const cookieObject = {
name: key,
value,
domain: setCookieOptions.domain,
path: setCookieOptions.path,
expires: parseNumberOrDate(setCookieOptions.expires),
sameSite: setCookieOptions.samesite,
maxAge: parseNumber(setCookieOptions["max-age"]),
secure: setCookieStr.includes("Secure") ? true : void 0,
httpOnly: setCookieStr.includes("HttpOnly") ? true : void 0
};
azureCookies.push(cookieObject);
}
return azureCookies;
}
function parseNumberOrDate(expires) {
const expiresAsNumber = parseNumber(expires);
if (expiresAsNumber !== void 0) {
return expiresAsNumber;
}
const expiresAsDate = new Date(expires);
if (!Number.isNaN(expiresAsDate.getTime())) {
return expiresAsDate;
}
}
function parseNumber(maxAge) {
if (!maxAge) {
return void 0;
}
const maxAgeAsNumber = Number(maxAge);
if (!Number.isNaN(maxAgeAsNumber)) {
return maxAgeAsNumber;
}
}

View file

@ -0,0 +1,9 @@
import type { Readable } from "node:stream";
export declare function requestHasBody(request: globalThis.Request): boolean;
export declare function useRequestBody(request: globalThis.Request): Promise<any>;
export declare function trapUnhandledNodeErrors(): void;
export declare function joinHeaders(value: number | string | string[]): string;
export declare function normalizeFetchResponse(response: Response): Response;
export declare function normalizeCookieHeader(header?: number | string | string[]): string[];
export declare function normalizeCookieHeaders(headers: Headers): Headers;
export declare function toBuffer(data: ReadableStream | Readable | Uint8Array): Promise<Buffer<ArrayBufferLike>> | Buffer<ArrayBuffer>;

View file

@ -0,0 +1,7 @@
export type EnvOptions = {
prefix?: string;
altPrefix?: string;
envExpansion?: boolean;
};
export declare function getEnv(key: string, opts: EnvOptions): unknown;
export declare function applyEnv(obj: Record<string, any>, opts: EnvOptions, parentKey?: string): Record<string, any>;

View file

@ -0,0 +1,39 @@
import destr from "destr";
import { snakeCase } from "scule";
export function getEnv(key, opts) {
const envKey = snakeCase(key).toUpperCase();
return destr(
process.env[opts.prefix + envKey] ?? process.env[opts.altPrefix + envKey]
);
}
function _isObject(input) {
return typeof input === "object" && !Array.isArray(input);
}
export function applyEnv(obj, opts, parentKey = "") {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key;
const envValue = getEnv(subKey, opts);
if (_isObject(obj[key])) {
if (_isObject(envValue)) {
obj[key] = { ...obj[key], ...envValue };
applyEnv(obj[key], opts, subKey);
} else if (envValue === void 0) {
applyEnv(obj[key], opts, subKey);
} else {
obj[key] = envValue ?? obj[key];
}
} else {
obj[key] = envValue ?? obj[key];
}
if (opts.envExpansion && typeof obj[key] === "string") {
obj[key] = _expandFromEnv(obj[key]);
}
}
return obj;
}
const envExpandRx = /\{\{([^{}]*)\}\}/g;
function _expandFromEnv(value) {
return value.replace(envExpandRx, (match, key) => {
return process.env[key] || match;
});
}

View file

@ -0,0 +1,10 @@
import type { Readable } from "node:stream";
import type { APIGatewayProxyEventHeaders } from "aws-lambda";
export declare function normalizeLambdaIncomingHeaders(headers?: APIGatewayProxyEventHeaders): Record<string, string | string[] | undefined>;
export declare function normalizeLambdaOutgoingHeaders(headers: Record<string, number | string | string[] | undefined>, stripCookies?: boolean): {
[k: string]: string;
};
export declare function normalizeLambdaOutgoingBody(body: BodyInit | ReadableStream | Buffer | Readable | Uint8Array | null | undefined, headers: Record<string, number | string | string[] | undefined>): Promise<{
type: "text" | "binary";
body: string;
}>;

View file

@ -0,0 +1,30 @@
import { toBuffer } from "./utils.mjs";
export function normalizeLambdaIncomingHeaders(headers) {
return Object.fromEntries(
Object.entries(headers || {}).map(([key, value]) => [
key.toLowerCase(),
value
])
);
}
export function normalizeLambdaOutgoingHeaders(headers, stripCookies = false) {
const entries = stripCookies ? Object.entries(headers).filter(([key]) => !["set-cookie"].includes(key)) : Object.entries(headers);
return Object.fromEntries(
entries.map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : String(v)])
);
}
export async function normalizeLambdaOutgoingBody(body, headers) {
if (typeof body === "string") {
return { type: "text", body };
}
if (!body) {
return { type: "text", body: "" };
}
const buffer = await toBuffer(body);
const contentType = headers["content-type"] || "";
return isTextType(contentType) ? { type: "text", body: buffer.toString("utf8") } : { type: "binary", body: buffer.toString("base64") };
}
const TEXT_TYPE_RE = /^text\/|\/(javascript|json|xml)|utf-?8/;
function isTextType(contentType = "") {
return TEXT_TYPE_RE.test(contentType);
}

View file

@ -0,0 +1,101 @@
import { splitCookiesString } from "h3";
import { useNitroApp } from "./app.mjs";
const METHOD_WITH_BODY_RE = /post|put|patch/i;
const TEXT_MIME_RE = /application\/text|text\/html/;
const JSON_MIME_RE = /application\/json/;
export function requestHasBody(request) {
return METHOD_WITH_BODY_RE.test(request.method);
}
export async function useRequestBody(request) {
const contentType = request.headers.get("content-type") || "";
if (contentType.includes("form")) {
const formData = await request.formData();
const body = /* @__PURE__ */ Object.create(null);
for (const entry of formData.entries()) {
body[entry[0]] = entry[1];
}
return body;
}
if (JSON_MIME_RE.test(contentType)) {
return request.json();
}
if (TEXT_MIME_RE.test(contentType)) {
return request.text();
}
const blob = await request.blob();
return URL.createObjectURL(blob);
}
function _captureError(error, type) {
console.error(`[${type}]`, error);
useNitroApp().captureError(error, { tags: [type] });
}
export function trapUnhandledNodeErrors() {
process.on(
"unhandledRejection",
(error) => _captureError(error, "unhandledRejection")
);
process.on(
"uncaughtException",
(error) => _captureError(error, "uncaughtException")
);
}
export function joinHeaders(value) {
return Array.isArray(value) ? value.join(", ") : String(value);
}
export function normalizeFetchResponse(response) {
if (!response.headers.has("set-cookie")) {
return response;
}
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: normalizeCookieHeaders(response.headers)
});
}
export function normalizeCookieHeader(header = "") {
return splitCookiesString(joinHeaders(header));
}
export function normalizeCookieHeaders(headers) {
const outgoingHeaders = new Headers();
for (const [name, header] of headers) {
if (name === "set-cookie") {
for (const cookie of normalizeCookieHeader(header)) {
outgoingHeaders.append("set-cookie", cookie);
}
} else {
outgoingHeaders.set(name, joinHeaders(header));
}
}
return outgoingHeaders;
}
export function toBuffer(data) {
if ("pipeTo" in data && typeof data.pipeTo === "function") {
return new Promise((resolve, reject) => {
const chunks = [];
data.pipeTo(
new WritableStream({
write(chunk) {
chunks.push(chunk);
},
close() {
resolve(Buffer.concat(chunks));
},
abort(reason) {
reject(reason);
}
})
).catch(reject);
});
}
if ("pipe" in data && typeof data.pipe === "function") {
return new Promise((resolve, reject) => {
const chunks = [];
data.on("data", (chunk) => {
chunks.push(chunk);
}).on("end", () => {
resolve(Buffer.concat(chunks));
}).on("error", reject);
});
}
return Buffer.from(data);
}

View file

@ -0,0 +1 @@
export { defineNitroPlugin, nitroPlugin } from "./internal/plugin";

View file

@ -0,0 +1 @@
export { defineNitroPlugin, nitroPlugin } from "./internal/plugin.mjs";

View file

@ -0,0 +1 @@
export { useStorage } from "./internal/storage";

View file

@ -0,0 +1 @@
export { useStorage } from "./internal/storage.mjs";

View file

@ -0,0 +1 @@
export { defineTask, runTask } from "./internal/task";

View file

@ -0,0 +1 @@
export { defineTask, runTask } from "./internal/task.mjs";

View file

@ -0,0 +1,17 @@
import type { H3Event } from "h3";
/**
* @deprecated This util is only provided for backward compatibility and will be removed in v3.
*/
export declare function isJsonRequest(event: H3Event): boolean;
/**
* @deprecated This util is only provided for backward compatibility and will be removed in v3.
*/
export declare function normalizeError(error: any, isDev?: boolean): {
stack: {
text: string;
internal: boolean;
}[];
statusCode: any;
statusMessage: any;
message: any;
};

View file

@ -0,0 +1,30 @@
import { getRequestHeader } from "h3";
export function isJsonRequest(event) {
if (hasReqHeader(event, "accept", "text/html")) {
return false;
}
return hasReqHeader(event, "accept", "application/json") || hasReqHeader(event, "user-agent", "curl/") || hasReqHeader(event, "user-agent", "httpie/") || hasReqHeader(event, "sec-fetch-mode", "cors") || event.path.startsWith("/api/") || event.path.endsWith(".json");
}
function hasReqHeader(event, name, includes) {
const value = getRequestHeader(event, name);
return value && typeof value === "string" && value.toLowerCase().includes(includes);
}
export function normalizeError(error, isDev) {
const cwd = typeof process.cwd === "function" ? process.cwd() : "/";
const stack = !isDev && !import.meta.prerender && (error.unhandled || error.fatal) ? [] : (error.stack || "").split("\n").splice(1).filter((line) => line.includes("at ")).map((line) => {
const text = line.replace(cwd + "/", "./").replace("webpack:/", "").replace("file://", "").trim();
return {
text,
internal: line.includes("node_modules") && !line.includes(".cache") || line.includes("internal") || line.includes("new Promise")
};
});
const statusCode = error.statusCode || 500;
const statusMessage = error.statusMessage ?? (statusCode === 404 ? "Not Found" : "");
const message = !isDev && error.unhandled ? "internal server error" : error.message || error.toString();
return {
stack,
statusCode,
statusMessage,
message
};
}