UNPKG

elysia

Version:

Ergonomic Framework for Human

1,751 lines 60.6 kB
import { Memoirist } from "memoirist"; import{ t }from"./type-system.mjs"; import{ sucrose }from"./sucrose.mjs"; import{ BunAdapter }from"./adapter/bun/index.mjs"; import{ WebStandardAdapter }from"./adapter/web-standard/index.mjs"; import{ env }from"./universal/env.mjs"; import{ cloneInference, coercePrimitiveRoot, deduplicateChecksum, fnToContainer, getLoosePath, localHookToLifeCycleStore, mergeDeep, mergeSchemaValidator, PromiseGroup, promoteEvent, stringToStructureCoercions, isNotEmpty, replaceSchemaType, compressHistoryHook, encodePath }from"./utils.mjs"; import{ composeHandler, composeGeneralHandler, composeErrorHandler }from"./compose.mjs"; import{ createTracer }from"./trace.mjs"; import{ mergeHook, getSchemaValidator, getResponseSchemaValidator, checksum, mergeLifeCycle, filterGlobalHook, asHookType, traceBackMacro, replaceUrlPath, createMacroManager, getCookieValidator }from"./utils.mjs"; import{ createDynamicErrorHandler, createDynamicHandler }from"./dynamic-handle.mjs"; import{ ERROR_CODE, ValidationError }from"./error.mjs"; class Elysia { constructor(config = {}) { this.server = null; this.dependencies = {}; this._routes = {}; this._types = { Prefix: "", Singleton: {}, Definitions: {}, Metadata: {} }; this._ephemeral = {}; this._volatile = {}; this.singleton = { decorator: {}, store: {}, derive: {}, resolve: {} }; this.definitions = { typebox: t.Module({}), type: {}, error: {} }; this.extender = { macros: [], higherOrderFunctions: [] }; this.validator = { global: null, scoped: null, local: null, getCandidate() { return mergeSchemaValidator( mergeSchemaValidator(this.global, this.scoped), this.local ); } }; this.event = {}; this.telemetry = { stack: void 0 }; this.router = { "~http": void 0, get http() { if (!this["~http"]) this["~http"] = new Memoirist({ lazy: true }); return this["~http"]; }, "~dynamic": void 0, // Use in non-AOT mode get dynamic() { if (!this["~dynamic"]) this["~dynamic"] = new Memoirist(); return this["~dynamic"]; }, static: { http: { static: {}, // handlers: [] as ComposedHandler[], map: {}, all: "" }, // Static WS Router is consists of pathname and websocket handler index to compose ws: {} }, history: [] }; this.routeTree = /* @__PURE__ */ new Map(); this.inference = { body: false, cookie: false, headers: false, query: false, set: false, server: false, request: false, route: false }; this["~parser"] = {}; this.handle = async (request) => this.fetch(request); /** * Use handle can be either sync or async to save performance. * * Beside benchmark purpose, please use 'handle' instead. */ this.fetch = (request) => { return (this.fetch = this.config.aot ? composeGeneralHandler(this) : createDynamicHandler(this))(request); }; this.handleError = async (context, error2) => { return (this.handleError = this.config.aot ? composeErrorHandler(this) : createDynamicErrorHandler(this))(context, error2); }; this.outerErrorHandler = (error2) => new Response(error2.message || error2.name || "Error", { // @ts-ignore status: error2?.status ?? 500 }); /** * ### listen * Assign current instance to port and start serving * * --- * @example * ```typescript * new Elysia() * .get("/", () => 'hi') * .listen(3000) * ``` */ this.listen = (options, callback) => { this["~adapter"].listen(this)(options, callback); return this; }; /** * ### stop * Stop server from serving * * --- * @example * ```typescript * const app = new Elysia() * .get("/", () => 'hi') * .listen(3000) * * // Sometime later * app.stop() * ``` * * @example * ```typescript * const app = new Elysia() * .get("/", () => 'hi') * .listen(3000) * * app.stop(true) // Abruptly any requests inflight * ``` */ this.stop = async (closeActiveConnections) => { if (!this.server) throw new Error( "Elysia isn't running. Call `app.listen` to start the server." ); if (this.server) { this.server.stop(closeActiveConnections); this.server = null; if (this.event.stop?.length) for (let i = 0; i < this.event.stop.length; i++) this.event.stop[i].fn(this); } }; if (config.tags) { if (!config.detail) config.detail = { tags: config.tags }; else config.detail.tags = config.tags; } if (config.nativeStaticResponse === void 0) config.nativeStaticResponse = true; this.config = {}; this.applyConfig(config ?? {}); this["~adapter"] = config.adapter ?? (typeof Bun !== "undefined" ? BunAdapter : WebStandardAdapter); if (config?.analytic && (config?.name || config?.seed !== void 0)) this.telemetry.stack = new Error().stack; } get store() { return this.singleton.store; } get decorator() { return this.singleton.decorator; } get routes() { return this.router.history; } getGlobalRoutes() { return this.router.history; } getServer() { return this.server; } getParent() { return null; } get promisedModules() { if (!this._promisedModules) this._promisedModules = new PromiseGroup(); return this._promisedModules; } env(model, _env = env) { const validator = getSchemaValidator(model, { modules: this.definitions.typebox, dynamic: true, additionalProperties: true, coerce: true }); if (validator.Check(_env) === false) { const error2 = new ValidationError("env", model, _env); throw new Error(error2.all.map((x) => x.summary).join("\n")); } return this; } /** * @private DO_NOT_USE_OR_YOU_WILL_BE_FIRED * @version 1.1.0 * * ! Do not use unless you know exactly what you are doing * ? Add Higher order function to Elysia.fetch */ wrap(fn) { this.extender.higherOrderFunctions.push({ checksum: checksum( JSON.stringify({ name: this.config.name, seed: this.config.seed, content: fn.toString() }) ), fn }); return this; } applyMacro(localHook) { if (this.extender.macros.length) { const manage = createMacroManager({ globalHook: this.event, localHook }); const manager = { events: { global: this.event, local: localHook }, get onParse() { return manage("parse"); }, get onTransform() { return manage("transform"); }, get onBeforeHandle() { return manage("beforeHandle"); }, get onAfterHandle() { return manage("afterHandle"); }, get mapResponse() { return manage("mapResponse"); }, get onAfterResponse() { return manage("afterResponse"); }, get onError() { return manage("error"); } }; for (const macro of this.extender.macros) traceBackMacro(macro.fn(manager), localHook, manage); } } applyConfig(config) { this.config = { prefix: "", aot: env.ELYSIA_AOT !== "false", normalize: true, ...config, cookie: { path: "/", ...config?.cookie }, experimental: config?.experimental ?? {}, seed: config?.seed === void 0 ? "" : config?.seed }; return this; } get models() { const models = {}; for (const name of Object.keys(this.definitions.type)) models[name] = getSchemaValidator( // @ts-expect-error this.definitions.typebox.Import(name) ); models.modules = this.definitions.typebox; return models; } add(method, path, handle, localHook, { allowMeta = false, skipPrefix = false } = { allowMeta: false, skipPrefix: false }) { localHook = compressHistoryHook(localHookToLifeCycleStore(localHook)); if (path !== "" && path.charCodeAt(0) !== 47) path = "/" + path; if (this.config.prefix && !skipPrefix) path = this.config.prefix + path; if (localHook?.type) switch (localHook.type) { case "text": localHook.type = "text/plain"; break; case "json": localHook.type = "application/json"; break; case "formdata": localHook.type = "multipart/form-data"; break; case "urlencoded": localHook.type = "application/x-www-form-urlencoded"; break; case "arrayBuffer": localHook.type = "application/octet-stream"; break; default: break; } const models = this.definitions.type; const dynamic = !this.config.aot; const instanceValidator = { ...this.validator.getCandidate() }; const cloned = { body: localHook?.body ?? instanceValidator?.body, headers: localHook?.headers ?? instanceValidator?.headers, params: localHook?.params ?? instanceValidator?.params, query: localHook?.query ?? instanceValidator?.query, cookie: localHook?.cookie ?? instanceValidator?.cookie, response: localHook?.response ?? instanceValidator?.response }; const cookieValidator = () => cloned.cookie ? getCookieValidator({ modules, validator: cloned.cookie, defaultConfig: this.config.cookie, config: cloned.cookie?.config ?? {}, dynamic, models }) : void 0; const normalize = this.config.normalize; const modules = this.definitions.typebox; const validator = this.config.precompile === true || typeof this.config.precompile === "object" && this.config.precompile.schema === true ? { body: getSchemaValidator(cloned.body, { modules, dynamic, models, normalize, additionalCoerce: coercePrimitiveRoot() }), headers: getSchemaValidator(cloned.headers, { modules, dynamic, models, additionalProperties: !this.config.normalize, coerce: true, additionalCoerce: stringToStructureCoercions() }), params: getSchemaValidator(cloned.params, { modules, dynamic, models, coerce: true, additionalCoerce: stringToStructureCoercions() }), query: getSchemaValidator(cloned.query, { modules, dynamic, models, normalize, coerce: true, additionalCoerce: stringToStructureCoercions() }), cookie: cookieValidator(), response: getResponseSchemaValidator(cloned.response, { modules, dynamic, models, normalize }) } : { createBody() { if (this.body) return this.body; return this.body = getSchemaValidator( cloned.body, { modules, dynamic, models, normalize, additionalCoerce: coercePrimitiveRoot() } ); }, createHeaders() { if (this.headers) return this.headers; return this.headers = getSchemaValidator( cloned.headers, { modules, dynamic, models, additionalProperties: !normalize, coerce: true, additionalCoerce: stringToStructureCoercions() } ); }, createParams() { if (this.params) return this.params; return this.params = getSchemaValidator( cloned.params, { modules, dynamic, models, coerce: true, additionalCoerce: stringToStructureCoercions() } ); }, createQuery() { if (this.query) return this.query; return this.query = getSchemaValidator( cloned.query, { modules, dynamic, models, coerce: true, additionalCoerce: stringToStructureCoercions() } ); }, createCookie() { if (this.cookie) return this.cookie; return this.cookie = cookieValidator(); }, createResponse() { if (this.response) return this.response; return this.response = getResponseSchemaValidator( cloned.response, { modules, dynamic, models, normalize } ); } }; localHook = mergeHook( localHook, compressHistoryHook(instanceValidator) ); if (localHook.tags) { if (!localHook.detail) localHook.detail = { tags: localHook.tags }; else localHook.detail.tags = localHook.tags; } if (isNotEmpty(this.config.detail)) localHook.detail = mergeDeep( Object.assign({}, this.config.detail), localHook.detail ); this.applyMacro(localHook); const hooks = compressHistoryHook(mergeHook(this.event, localHook)); if (this.config.aot === false) { this.router.dynamic.add(method, path, { validator, hooks, content: localHook?.type, handle, route: path }); const encoded = encodePath(path, { dynamic: true }); if (path !== encoded) { this.router.dynamic.add(method, encoded, { validator, hooks, content: localHook?.type, handle, route: path }); } if (this.config.strictPath === false) { const loosePath = getLoosePath(path); this.router.dynamic.add(method, loosePath, { validator, hooks, content: localHook?.type, handle, route: path }); const encoded2 = encodePath(loosePath); if (loosePath !== encoded2) this.router.dynamic.add(method, loosePath, { validator, hooks, content: localHook?.type, handle, route: path }); } this.router.history.push({ method, path, composed: null, handler: handle, hooks }); return; } const shouldPrecompile = this.config.precompile === true || typeof this.config.precompile === "object" && this.config.precompile.compose === true; const inference = cloneInference(this.inference); const adapter = this["~adapter"].handler; const staticHandler = typeof handle !== "function" && typeof adapter.createStaticHandler === "function" ? adapter.createStaticHandler(handle, hooks, this.setHeaders) : void 0; const nativeStaticHandler = typeof handle !== "function" ? adapter.createNativeStaticHandler?.( handle, hooks, this.setHeaders ) : void 0; if (this.config.nativeStaticResponse === true && nativeStaticHandler && (method === "GET" || method === "ALL")) this.router.static.http.static[path] = nativeStaticHandler(); let compile = (asManifest = false) => composeHandler({ app: this, path, method, hooks, validator, handler: typeof handle !== "function" && typeof adapter.createStaticHandler !== "function" ? () => handle : handle, allowMeta, inference, asManifest }); let oldIndex; if (this.routeTree.has(method + path)) for (let i = 0; i < this.router.history.length; i++) { const route = this.router.history[i]; if (route.path === path && route.method === method) { oldIndex = i; break; } } else this.routeTree.set(method + path, this.router.history.length); const history = this.router.history; const index = oldIndex ?? this.router.history.length; const mainHandler = shouldPrecompile ? compile() : (ctx) => { const temp = (history[index].composed = compile())(ctx); compile = void 0; return temp; }; if (shouldPrecompile) compile = void 0; const isWebSocket = method === "$INTERNALWS"; if (oldIndex !== void 0) this.router.history[oldIndex] = // @ts-ignore Object.assign( { method, path, composed: mainHandler, handler: handle, hooks }, localHook.webSocket ? { websocket: localHook.websocket } : {} ); else this.router.history.push( // @ts-ignore Object.assign( { method, path, composed: mainHandler, handler: handle, hooks }, localHook.webSocket ? { websocket: localHook.websocket } : {} ) ); const staticRouter = this.router.static.http; const handler = { handler: shouldPrecompile ? mainHandler : void 0, compile() { return this.handler = compile(); } }; if (isWebSocket) { this.router.http.add("ws", path, handler); if (!this.config.strictPath) this.router.http.add("ws", getLoosePath(path), handler); const encoded = encodePath(path, { dynamic: true }); if (encoded !== path) this.router.http.add("ws", encoded, handler); return; } if (path.indexOf(":") === -1 && path.indexOf("*") === -1) { if (!staticRouter.map[path]) staticRouter.map[path] = { code: "" }; const ctx = staticHandler ? "" : "c"; if (method === "ALL") staticRouter.map[path].all = `default:return ht[${index}].composed(${ctx}) `; else staticRouter.map[path].code = `case '${method}':return ht[${index}].composed(${ctx}) ${staticRouter.map[path].code}`; if (!this.config.strictPath && this.config.nativeStaticResponse === true && nativeStaticHandler && (method === "GET" || method === "ALL")) this.router.static.http.static[getLoosePath(path)] = nativeStaticHandler(); } else { this.router.http.add(method, path, handler); if (!this.config.strictPath) { const loosePath = getLoosePath(path); if (this.config.nativeStaticResponse === true && staticHandler && (method === "GET" || method === "ALL")) this.router.static.http.static[loosePath] = staticHandler(); this.router.http.add(method, loosePath, handler); } const encoded = encodePath(path, { dynamic: true }); if (path !== encoded) { this.router.http.add(method, encoded, handler); if (this.config.nativeStaticResponse === true && staticHandler && (method === "GET" || method === "ALL")) this.router.static.http.static[encoded] = staticHandler(); this.router.http.add(method, encoded, handler); } } } headers(header) { if (!header) return this; if (!this.setHeaders) this.setHeaders = {}; this.setHeaders = mergeDeep(this.setHeaders, header); return this; } /** * ### start | Life cycle event * Called after server is ready for serving * * --- * @example * ```typescript * new Elysia() * .onStart(({ server }) => { * console.log("Running at ${server?.url}:${server?.port}") * }) * .listen(3000) * ``` */ onStart(handler) { this.on("start", handler); return this; } /** * ### request | Life cycle event * Called on every new request is accepted * * --- * @example * ```typescript * new Elysia() * .onRequest(({ method, url }) => { * saveToAnalytic({ method, url }) * }) * ``` */ onRequest(handler) { this.on("request", handler); return this; } onParse(options, handler) { if (!handler) { if (typeof options === "string") return this.on("parse", this["~parser"][options]); return this.on("parse", options); } return this.on( options, "parse", handler ); } /** * ### parse | Life cycle event * Callback function to handle body parsing * * If truthy value is returned, will be assigned to `context.body` * Otherwise will skip the callback and look for the next one. * * Equivalent to Express's body parser * * --- * @example * ```typescript * new Elysia() * .onParse((request, contentType) => { * if(contentType === "application/json") * return request.json() * }) * ``` */ parser(name, parser) { this["~parser"][name] = parser; return this; } onTransform(options, handler) { if (!handler) return this.on("transform", options); return this.on( options, "transform", handler ); } resolve(optionsOrResolve, resolve) { if (!resolve) { resolve = optionsOrResolve; optionsOrResolve = { as: "local" }; } const hook = { subType: "resolve", fn: resolve }; return this.onBeforeHandle(optionsOrResolve, hook); } mapResolve(optionsOrResolve, mapper) { if (!mapper) { mapper = optionsOrResolve; optionsOrResolve = { as: "local" }; } const hook = { subType: "mapResolve", fn: mapper }; return this.onBeforeHandle(optionsOrResolve, hook); } onBeforeHandle(options, handler) { if (!handler) return this.on("beforeHandle", options); return this.on( options, "beforeHandle", handler ); } onAfterHandle(options, handler) { if (!handler) return this.on("afterHandle", options); return this.on( options, "afterHandle", handler ); } mapResponse(options, handler) { if (!handler) return this.on("mapResponse", options); return this.on( options, "mapResponse", handler ); } onAfterResponse(options, handler) { if (!handler) return this.on("afterResponse", options); return this.on( options, "afterResponse", handler ); } /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ trace(options, handler) { if (!handler) { handler = options; options = { as: "local" }; } if (!Array.isArray(handler)) handler = [handler]; for (const fn of handler) this.on( options, "trace", createTracer(fn) ); return this; } error(name, error2) { switch (typeof name) { case "string": error2.prototype[ERROR_CODE] = name; this.definitions.error[name] = error2; return this; case "function": this.definitions.error = name(this.definitions.error); return this; } for (const [code, error3] of Object.entries(name)) { error3.prototype[ERROR_CODE] = code; this.definitions.error[code] = error3; } return this; } /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError(options, handler) { if (!handler) return this.on("error", options); return this.on( options, "error", handler ); } /** * ### stop | Life cycle event * Called after server stop serving request * * --- * @example * ```typescript * new Elysia() * .onStop((app) => { * cleanup() * }) * ``` */ onStop(handler) { this.on("stop", handler); return this; } on(optionsOrType, typeOrHandlers, handlers) { let type; switch (typeof optionsOrType) { case "string": type = optionsOrType; handlers = typeOrHandlers; break; case "object": type = typeOrHandlers; if (!Array.isArray(typeOrHandlers) && typeof typeOrHandlers === "object") handlers = typeOrHandlers; break; } if (Array.isArray(handlers)) handlers = fnToContainer(handlers); else { if (typeof handlers === "function") handlers = [ { fn: handlers } ]; else handlers = [handlers]; } const handles = handlers; for (const handle of handles) { handle.scope = typeof optionsOrType === "string" ? "local" : optionsOrType?.as ?? "local"; if (type === "resolve" || type === "derive") handle.subType = type; } if (type !== "trace") sucrose( { [type]: handles.map((x) => x.fn) }, this.inference ); for (const handle of handles) { const fn = asHookType(handle, "global", { skipIfHasType: true }); switch (type) { case "start": this.event.start ??= []; this.event.start.push(fn); break; case "request": this.event.request ??= []; this.event.request.push(fn); break; case "parse": this.event.parse ??= []; this.event.parse.push(fn); break; case "transform": this.event.transform ??= []; this.event.transform.push(fn); break; // @ts-expect-error case "derive": this.event.transform ??= []; this.event.transform.push( fnToContainer(fn, "derive") ); break; case "beforeHandle": this.event.beforeHandle ??= []; this.event.beforeHandle.push(fn); break; // @ts-expect-error // eslint-disable-next-line sonarjs/no-duplicated-branches case "resolve": this.event.beforeHandle ??= []; this.event.beforeHandle.push( fnToContainer(fn, "resolve") ); break; case "afterHandle": this.event.afterHandle ??= []; this.event.afterHandle.push(fn); break; case "mapResponse": this.event.mapResponse ??= []; this.event.mapResponse.push(fn); break; case "afterResponse": this.event.afterResponse ??= []; this.event.afterResponse.push(fn); break; case "trace": this.event.trace ??= []; this.event.trace.push(fn); break; case "error": this.event.error ??= []; this.event.error.push(fn); break; case "stop": this.event.stop ??= []; this.event.stop.push(fn); break; } } return this; } /** * @deprecated use `Elysia.as` instead * * Will be removed in Elysia 1.2 */ propagate() { promoteEvent(this.event.parse); promoteEvent(this.event.transform); promoteEvent(this.event.beforeHandle); promoteEvent(this.event.afterHandle); promoteEvent(this.event.mapResponse); promoteEvent(this.event.afterResponse); promoteEvent(this.event.trace); promoteEvent(this.event.error); return this; } as(type) { const castType = { plugin: "scoped", scoped: "scoped", global: "global" }[type]; promoteEvent(this.event.parse, castType); promoteEvent(this.event.transform, castType); promoteEvent(this.event.beforeHandle, castType); promoteEvent(this.event.afterHandle, castType); promoteEvent(this.event.mapResponse, castType); promoteEvent(this.event.afterResponse, castType); promoteEvent(this.event.trace, castType); promoteEvent(this.event.error, castType); if (type === "plugin") { this.validator.scoped = mergeSchemaValidator( this.validator.scoped, this.validator.local ); this.validator.local = null; } else if (type === "global") { this.validator.global = mergeSchemaValidator( this.validator.global, mergeSchemaValidator( this.validator.scoped, this.validator.local ) ); this.validator.scoped = null; this.validator.local = null; } return this; } /** * ### group * Encapsulate and group path with prefix * * --- * @example * ```typescript * new Elysia() * .group('/v1', app => app * .get('/', () => 'Hi') * .get('/name', () => 'Elysia') * }) * ``` */ group(prefix, schemaOrRun, run) { const instance = new Elysia({ ...this.config, prefix: "" }); instance.singleton = { ...this.singleton }; instance.definitions = { ...this.definitions }; instance.getServer = () => this.getServer(); instance.inference = cloneInference(this.inference); instance.extender = { ...this.extender }; const isSchema = typeof schemaOrRun === "object"; const sandbox = (isSchema ? run : schemaOrRun)(instance); this.singleton = mergeDeep(this.singleton, instance.singleton); this.definitions = mergeDeep(this.definitions, instance.definitions); if (sandbox.event.request?.length) this.event.request = [ ...this.event.request || [], ...sandbox.event.request || [] ]; if (sandbox.event.mapResponse?.length) this.event.mapResponse = [ ...this.event.mapResponse || [], ...sandbox.event.mapResponse || [] ]; this.model(sandbox.definitions.type); Object.values(instance.router.history).forEach( ({ method, path, handler, hooks }) => { path = (isSchema ? "" : this.config.prefix) + prefix + path; if (isSchema) { const hook = schemaOrRun; const localHook = hooks; this.add( method, path, handler, mergeHook(hook, { ...localHook || {}, error: !localHook.error ? sandbox.event.error : Array.isArray(localHook.error) ? [ ...localHook.error || {}, ...sandbox.event.error || {} ] : [ localHook.error, ...sandbox.event.error || {} ] }) ); } else { this.add( method, path, handler, mergeHook(hooks, { error: sandbox.event.error }), { skipPrefix: true } ); } } ); return this; } /** * ### guard * Encapsulate and pass hook into all child handler * * --- * @example * ```typescript * import { t } from 'elysia' * * new Elysia() * .guard({ * schema: { * body: t.Object({ * username: t.String(), * password: t.String() * }) * } * }, app => app * .get("/", () => 'Hi') * .get("/name", () => 'Elysia') * }) * ``` */ guard(hook, run) { if (!run) { if (typeof hook === "object") { this.applyMacro(hook); const type = hook.as ?? "local"; this.validator[type] = { body: hook.body ?? this.validator[type]?.body, headers: hook.headers ?? this.validator[type]?.headers, params: hook.params ?? this.validator[type]?.params, query: hook.query ?? this.validator[type]?.query, response: hook.response ?? this.validator[type]?.response, cookie: hook.cookie ?? this.validator[type]?.cookie }; if (hook.parse) this.on({ as: type }, "parse", hook.parse); if (hook.transform) this.on({ as: type }, "transform", hook.transform); if (hook.derive) this.on({ as: type }, "derive", hook.derive); if (hook.beforeHandle) this.on({ as: type }, "beforeHandle", hook.beforeHandle); if (hook.resolve) this.on({ as: type }, "resolve", hook.resolve); if (hook.afterHandle) this.on({ as: type }, "afterHandle", hook.afterHandle); if (hook.mapResponse) this.on({ as: type }, "mapResponse", hook.mapResponse); if (hook.afterResponse) this.on({ as: type }, "afterResponse", hook.afterResponse); if (hook.error) this.on({ as: type }, "error", hook.error); if (hook.detail) { if (this.config.detail) this.config.detail = mergeDeep( Object.assign({}, this.config.detail), hook.detail ); else this.config.detail = hook.detail; } if (hook?.tags) { if (!this.config.detail) this.config.detail = { tags: hook.tags }; else this.config.detail.tags = hook.tags; } return this; } return this.guard({}, hook); } const instance = new Elysia({ ...this.config, prefix: "" }); instance.singleton = { ...this.singleton }; instance.definitions = { ...this.definitions }; instance.inference = cloneInference(this.inference); instance.extender = { ...this.extender }; const sandbox = run(instance); this.singleton = mergeDeep(this.singleton, instance.singleton); this.definitions = mergeDeep(this.definitions, instance.definitions); sandbox.getServer = () => this.server; if (sandbox.event.request?.length) this.event.request = [ ...this.event.request || [], ...sandbox.event.request || [] ]; if (sandbox.event.mapResponse?.length) this.event.mapResponse = [ ...this.event.mapResponse || [], ...sandbox.event.mapResponse || [] ]; this.model(sandbox.definitions.type); Object.values(instance.router.history).forEach( ({ method, path, handler, hooks: localHook }) => { this.add( method, path, handler, mergeHook(hook, { ...localHook || {}, error: !localHook.error ? sandbox.event.error : Array.isArray(localHook.error) ? [ ...localHook.error || {}, ...sandbox.event.error || [] ] : [ localHook.error, ...sandbox.event.error || [] ] }) ); } ); return this; } /** * ### use * Merge separate logic of Elysia with current * * --- * @example * ```typescript * const plugin = (app: Elysia) => app * .get('/plugin', () => 'hi') * * new Elysia() * .use(plugin) * ``` */ use(plugin, options) { if (Array.isArray(plugin)) { let app = this; for (const p of plugin) app = app.use(p); return app; } if (options?.scoped) return this.guard({}, (app) => app.use(plugin)); if (Array.isArray(plugin)) { let current = this; for (const p of plugin) current = this.use(p); return current; } if (plugin instanceof Promise) { this.promisedModules.add( plugin.then((plugin2) => { if (typeof plugin2 === "function") return plugin2(this); if (plugin2 instanceof Elysia) return this._use(plugin2).compile(); if (plugin2.constructor.name === "Elysia") return this._use( plugin2 ).compile(); if (typeof plugin2.default === "function") return plugin2.default(this); if (plugin2.default instanceof Elysia) return this._use(plugin2.default); if (plugin2.constructor.name === "Elysia") return this._use(plugin2.default); if (plugin2.constructor.name === "_Elysia") return this._use(plugin2.default); try { return this._use(plugin2.default); } catch (error2) { console.error( 'Invalid plugin type. Expected Elysia instance, function, or module with "default" as Elysia instance or function that returns Elysia instance.' ); throw error2; } }).then((v) => { if (v && typeof v.compile === "function") v.compile(); return v; }) ); return this; } return this._use(plugin); } propagatePromiseModules(plugin) { if (plugin.promisedModules.size <= 0) return this; for (const promise of plugin.promisedModules.promises) this.promisedModules.add( promise.then((v) => { if (!v) return; const t3 = this._use(v); if (t3 instanceof Promise) return t3.then((v2) => { if (v2) v2.compile(); else v.compile(); }); return v.compile(); }) ); return this; } _use(plugin) { if (typeof plugin === "function") { const instance = plugin(this); if (instance instanceof Promise) { this.promisedModules.add( instance.then((plugin2) => { if (plugin2 instanceof Elysia) { plugin2.getServer = () => this.getServer(); plugin2.getGlobalRoutes = () => this.getGlobalRoutes(); plugin2.model(this.definitions.type); plugin2.error(this.definitions.error); for (const { method, path, handler, hooks } of Object.values(plugin2.router.history)) this.add( method, path, handler, mergeHook(hooks, { error: plugin2.event.error }) ); plugin2.compile(); if (plugin2 === this) return; this.propagatePromiseModules(plugin2); return plugin2; } if (typeof plugin2 === "function") return plugin2( this ); if (typeof plugin2.default === "function") return plugin2.default( this ); return this._use(plugin2); }).then((v) => { if (v && typeof v.compile === "function") v.compile(); return v; }) ); return this; } return instance; } this.propagatePromiseModules(plugin); const { name, seed } = plugin.config; plugin.getParent = () => this; plugin.getServer = () => this.getServer(); plugin.getGlobalRoutes = () => this.getGlobalRoutes(); plugin.model(this.definitions.type); plugin.error(this.definitions.error); this["~parser"] = { ...plugin["~parser"], ...this["~parser"] }; this.headers(plugin.setHeaders); if (name) { if (!(name in this.dependencies)) this.dependencies[name] = []; const current = seed !== void 0 ? checksum(name + JSON.stringify(seed)) : 0; if (!this.dependencies[name].some( ({ checksum: checksum3 }) => current === checksum3 )) { this.extender.macros = this.extender.macros.concat( plugin.extender.macros ); this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions ); } } else { this.extender.macros = this.extender.macros.concat( plugin.extender.macros ); this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions ); } deduplicateChecksum(this.extender.macros); deduplicateChecksum(this.extender.higherOrderFunctions); const hofHashes = []; for (let i = 0; i < this.extender.higherOrderFunctions.length; i++) { const hof = this.extender.higherOrderFunctions[i]; if (hof.checksum) { if (hofHashes.includes(hof.checksum)) { this.extender.higherOrderFunctions.splice(i, 1); i--; } hofHashes.push(hof.checksum); } } this.inference = { body: this.inference.body || plugin.inference.body, cookie: this.inference.cookie || plugin.inference.cookie, headers: this.inference.headers || plugin.inference.headers, query: this.inference.query || plugin.inference.query, set: this.inference.set || plugin.inference.set, server: this.inference.server || plugin.inference.server, request: this.inference.request || plugin.inference.request, route: this.inference.route || plugin.inference.route }; this.decorate(plugin.singleton.decorator); this.state(plugin.singleton.store); this.model(plugin.definitions.type); this.error(plugin.definitions.error); plugin.extender.macros = this.extender.macros.concat( plugin.extender.macros ); for (const { method, path, handler, hooks } of Object.values( plugin.router.history )) { this.add( method, path, handler, mergeHook(hooks, { error: plugin.event.error }) ); } if (name) { if (!(name in this.dependencies)) this.dependencies[name] = []; const current = seed !== void 0 ? checksum(name + JSON.stringify(seed)) : 0; if (this.dependencies[name].some( ({ checksum: checksum3 }) => current === checksum3 )) return this; this.dependencies[name].push( this.config?.analytic ? { name: plugin.config.name, seed: plugin.config.seed, checksum: current, dependencies: plugin.dependencies, stack: plugin.telemetry.stack, routes: plugin.router.history, decorators: plugin.singleton, store: plugin.singleton.store, error: plugin.definitions.error, derive: plugin.event.transform?.filter((x) => x?.subType === "derive").map((x) => ({ fn: x.toString(), stack: new Error().stack ?? "" })), resolve: plugin.event.transform?.filter((x) => x?.subType === "resolve").map((x) => ({ fn: x.toString(), stack: new Error().stack ?? "" })) } : { name: plugin.config.name, seed: plugin.config.seed, checksum: current, dependencies: plugin.dependencies } ); this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event), current ); } else { this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event) ); } this.validator.global = mergeHook(this.validator.global, { ...plugin.validator.global }); this.validator.local = mergeHook(this.validator.local, { ...plugin.validator.scoped }); return this; } macro(macro) { if (typeof macro === "function") { const hook = { checksum: checksum( JSON.stringify({ name: this.config.name, seed: this.config.seed, content: macro.toString() }) ), fn: macro }; this.extender.macros.push(hook); } else if (typeof macro === "object") { for (const name of Object.keys(macro)) if (typeof macro[name] === "object") { const actualValue = { ...macro[name] }; macro[name] = (v) => { if (v === true) return actualValue; }; } const hook = { checksum: checksum( JSON.stringify({ name: this.config.name, seed: this.config.seed, content: Object.entries(macro).map(([k, v]) => `${k}+${v}`).join(",") }) ), fn: () => macro }; this.extender.macros.push(hook); } return this; } mount(path, handle) { if (path instanceof Elysia || typeof path === "function" || path.length === 0 || path === "/") { const run = typeof path === "function" ? path : path instanceof Elysia ? path.compile().fetch : handle instanceof Elysia ? handle.compile().fetch : handle; const handler2 = ({ request, path: path2 }) => run( new Request(replaceUrlPath(request.url, path2), { method: request.method, headers: request.headers, signal: request.signal, credentials: request.credentials, referrerPolicy: request.referrerPolicy, duplex: request.duplex, redirect: request.redirect, mode: request.mode, keepalive: request.keepalive, integrity: request.integrity, body: request.body }) ); this.all("/*", handler2, { parse: "none" }); return this; } if (!handle) return this; const length = path.length - (path.endsWith("*") ? 1 : 0); if (handle instanceof Elysia) handle = handle.compile().fetch; const handler = ({ request, path: path2 }) => handle( new Request( replaceUrlPath(request.url, path2.slice(length) || "/"), { method: request.method, headers: request.headers, signal: request.signal, credentials: request.credentials, referrerPolicy: request.referrerPolicy, duplex: request.duplex, redirect: request.redirect, mode: request.mode, keepalive: request.keepalive, integrity: request.integrity, body: request.body } ) ); this.all(path, handler, { parse: "none" }); this.all(path + (path.endsWith("/") ? "*" : "/*"), handler, { parse: "none" }); return this; } /** * ### get * Register handler for path with method [GET] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .get('/', () => 'hi') * .get('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ get(path, handler, hook) { this.add("GET", path, handler, hook); return this; } /** * ### post * Register handler for path with method [POST] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .post('/', () => 'hi') * .post('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ post(path, handler, hook) { this.add("POST", path, handler, hook); return this; } /** * ### put * Register handler for path with method [PUT] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .put('/', () => 'hi') * .put('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ put(path, handler, hook) { this.add("PUT", path, handler, hook); return this; } /** * ### patch * Register handler for path with method [PATCH] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .patch('/', () => 'hi') * .patch('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ patch(path, handler, hook) { this.add("PATCH", path, handler, hook); return this; } /** * ### delete * Register handler for path with method [DELETE] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .delete('/', () => 'hi') * .delete('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ delete(path, handler, hook) { this.add("DELETE", path, handler, hook); return this; } /** * ### options * Register handler for path with method [POST] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .options('/', () => 'hi') * .options('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ options(path, handler, hook) { this.add("OPTIONS", path, handler, hook); return this; } /** * ### all * Register handler for path with method [ALL] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .all('/', () => 'hi') * .all('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ all(path, handler, hook) { this.add("ALL", path, handler, hook); return this; } /** * ### head * Register handler for path with method [HEAD] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .head('/', () => 'hi') * .head('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ head(path, handler, hook) { this.add("HEAD", path, handler, hook); return this; } /** * ### connect * Register handler for path with method [CONNECT] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .connect('/', () => 'hi') * .connect('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ connect(path, handler, hook) { this.add("CONNECT", path, handler, hook); return this; } /** * ### route * Register handler for path with method [ROUTE] * * --- *