UNPKG

elysia

Version:

Ergonomic Framework for Human

1,432 lines 65.6 kB
var _a; import { Memoirist } from "memoirist"; import { Kind } from "@sinclair/typebox"; import fastDecodeURIComponent from "fast-decode-uri-component"; import { t } from "./type-system/index.mjs"; import { mergeInference, 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, deduplicateChecksum, fnToContainer, getLoosePath, localHookToLifeCycleStore, mergeDeep, mergeSchemaValidator, PromiseGroup, promoteEvent, isNotEmpty, encodePath, lifeCycleToArray, supportPerMethodInlineHandler, redirect, emptySchema, insertStandaloneValidator } from "./utils.mjs"; import { getSchemaValidator, getResponseSchemaValidator, getCookieValidator, hasType, resolveSchema } from "./schema.mjs"; import { composeHandler, composeGeneralHandler, composeErrorHandler } from "./compose.mjs"; import { createTracer } from "./trace.mjs"; import { mergeHook, checksum, mergeLifeCycle, filterGlobalHook, asHookType, replaceUrlPath } from "./utils.mjs"; import { createDynamicErrorHandler, createDynamicHandler } from "./dynamic-handle.mjs"; import { status, ERROR_CODE, ValidationError } from "./error.mjs"; import { coercePrimitiveRoot, coerceFormData, queryCoercions, stringToStructureCoercions } from "./replace-schema.mjs"; _a = Symbol.dispose; const _Elysia = class _Elysia { constructor(config = {}) { this.server = null; this.dependencies = {}; this["~Prefix"] = ""; this["~Singleton"] = null; this["~Definitions"] = null; this["~Metadata"] = null; this["~Ephemeral"] = null; this["~Volatile"] = null; this["~Routes"] = null; this.singleton = { decorator: {}, store: {}, derive: {}, resolve: {} }; this.definitions = { typebox: t.Module({}), type: {}, error: {} }; this.extender = { macro: {}, higherOrderFunctions: [] }; this.validator = { global: null, scoped: null, local: null, getCandidate() { return !this.global && !this.scoped && !this.local ? { body: void 0, headers: void 0, params: void 0, query: void 0, cookie: void 0, response: void 0 } : mergeSchemaValidator( mergeSchemaValidator(this.global, this.scoped), this.local ); } }; this.standaloneValidator = { global: null, scoped: null, local: null }; this.event = {}; this.router = { "~http": void 0, get http() { return this["~http"] || (this["~http"] = new Memoirist({ lazy: !0, onParam: fastDecodeURIComponent })), this["~http"]; }, "~dynamic": void 0, // Use in non-AOT mode get dynamic() { return this["~dynamic"] || (this["~dynamic"] = new Memoirist({ onParam: fastDecodeURIComponent })), this["~dynamic"]; }, // Static Router static: {}, // Native Static Response response: {}, history: [] }; this.routeTree = {}; this.inference = { body: !1, cookie: !1, headers: !1, query: !1, set: !1, server: !1, path: !1, route: !1, url: !1 }; this["~parser"] = {}; this.handle = async (request) => this.fetch(request); this.handleError = async (context, error) => (this.handleError = this.config.aot ? composeErrorHandler(this) : createDynamicErrorHandler(this))(context, error); /** * ### 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), 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) => (await this["~adapter"].stop?.(this, closeActiveConnections), this); this[_a] = () => { this.server && this.stop(); }; config.tags && (config.detail ? config.detail.tags = config.tags : config.detail = { tags: config.tags }), this.config = { aot: env.ELYSIA_AOT !== "false", nativeStaticResponse: !0, encodeSchema: !0, normalize: !0, ...config, prefix: config.prefix ? config.prefix.charCodeAt(0) === 47 ? config.prefix : `/${config.prefix}` : void 0, cookie: { path: "/", ...config?.cookie }, experimental: config?.experimental ?? {}, seed: config?.seed === void 0 ? "" : config?.seed }, this["~adapter"] = config.adapter ?? (typeof Bun < "u" ? BunAdapter : WebStandardAdapter), 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; } getGlobalDefinitions() { return this.definitions; } getServer() { return this.server; } getParent() { return null; } get promisedModules() { return this._promisedModules || (this._promisedModules = new PromiseGroup(console.error, () => { })), this._promisedModules; } env(model, _env = env) { if (getSchemaValidator(model, { modules: this.definitions.typebox, dynamic: !0, additionalProperties: !0, coerce: !0, sanitize: () => this.config.sanitize }).Check(_env) === !1) { const error = new ValidationError("env", model, _env); throw new Error(error.all.map((x) => x.summary).join(` `)); } 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) { return this.extender.higherOrderFunctions.push({ checksum: checksum( JSON.stringify({ name: this.config.name, seed: this.config.seed, content: fn.toString() }) ), fn }), this; } get models() { const models = {}; for (const name of Object.keys(this.definitions.type)) models[name] = getSchemaValidator( this.definitions.typebox.Import(name), { models: this.definitions.type } ); return models.modules = this.definitions.typebox, models; } add(method, path, handle, localHook, options) { const skipPrefix = options?.skipPrefix ?? !1, allowMeta = options?.allowMeta ?? !1; localHook ??= {}, this.applyMacro(localHook); let standaloneValidators = []; if (localHook.standaloneValidator && (standaloneValidators = standaloneValidators.concat( localHook.standaloneValidator )), this.standaloneValidator.local && (standaloneValidators = standaloneValidators.concat( this.standaloneValidator.local )), this.standaloneValidator.scoped && (standaloneValidators = standaloneValidators.concat( this.standaloneValidator.scoped )), this.standaloneValidator.global && (standaloneValidators = standaloneValidators.concat( this.standaloneValidator.global )), path !== "" && path.charCodeAt(0) !== 47 && (path = "/" + path), this.config.prefix && !skipPrefix && (path = this.config.prefix + path), 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 instanceValidator = this.validator.getCandidate(), 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 }, shouldPrecompile = this.config.precompile === !0 || typeof this.config.precompile == "object" && this.config.precompile.compose === !0, createValidator = () => { const models = this.definitions.type, dynamic = !this.config.aot, normalize = this.config.normalize, modules = this.definitions.typebox, sanitize = () => this.config.sanitize, cookieValidator = () => { if (cloned.cookie || standaloneValidators.find((x) => x.cookie)) return getCookieValidator({ modules, validator: cloned.cookie, defaultConfig: this.config.cookie, normalize, config: cloned.cookie?.config ?? {}, dynamic, models, validators: standaloneValidators.map((x) => x.cookie), sanitize }); }; return shouldPrecompile ? { body: getSchemaValidator(cloned.body, { modules, dynamic, models, normalize, additionalCoerce: (() => { const resolved = resolveSchema( cloned.body, models, modules ); return resolved && Kind in resolved && (hasType("File", resolved) || hasType("Files", resolved)) ? coerceFormData() : coercePrimitiveRoot(); })(), validators: standaloneValidators.map((x) => x.body), sanitize }), headers: getSchemaValidator(cloned.headers, { modules, dynamic, models, additionalProperties: !0, coerce: !0, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.headers ), sanitize }), params: getSchemaValidator(cloned.params, { modules, dynamic, models, coerce: !0, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.params ), sanitize }), query: getSchemaValidator(cloned.query, { modules, dynamic, models, normalize, coerce: !0, additionalCoerce: queryCoercions(), validators: standaloneValidators.map( (x) => x.query ), sanitize }), cookie: cookieValidator(), response: getResponseSchemaValidator(cloned.response, { modules, dynamic, models, normalize, validators: standaloneValidators.map( (x) => x.response ), sanitize }) } : { createBody() { return this.body ? this.body : this.body = getSchemaValidator( cloned.body, { modules, dynamic, models, normalize, additionalCoerce: (() => { const resolved = resolveSchema( cloned.body, models, modules ); return resolved && Kind in resolved && (hasType("File", resolved) || hasType("Files", resolved)) ? coerceFormData() : coercePrimitiveRoot(); })(), validators: standaloneValidators.map( (x) => x.body ), sanitize } ); }, createHeaders() { return this.headers ? this.headers : this.headers = getSchemaValidator( cloned.headers, { modules, dynamic, models, normalize, additionalProperties: !normalize, coerce: !0, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.headers ), sanitize } ); }, createParams() { return this.params ? this.params : this.params = getSchemaValidator( cloned.params, { modules, dynamic, models, normalize, coerce: !0, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.params ), sanitize } ); }, createQuery() { return this.query ? this.query : this.query = getSchemaValidator( cloned.query, { modules, dynamic, models, normalize, coerce: !0, additionalCoerce: queryCoercions(), validators: standaloneValidators.map( (x) => x.query ), sanitize } ); }, createCookie() { return this.cookie ? this.cookie : this.cookie = cookieValidator(); }, createResponse() { return this.response ? this.response : this.response = getResponseSchemaValidator( cloned.response, { modules, dynamic, models, normalize, validators: standaloneValidators.map( (x) => x.response ), sanitize } ); } }; }; (instanceValidator.body || instanceValidator.cookie || instanceValidator.headers || instanceValidator.params || instanceValidator.query || instanceValidator.response) && (localHook = mergeHook(localHook, instanceValidator)), localHook.tags && (localHook.detail ? localHook.detail.tags = localHook.tags : localHook.detail = { tags: localHook.tags }), isNotEmpty(this.config.detail) && (localHook.detail = mergeDeep( Object.assign({}, this.config.detail), localHook.detail )); const hooks = isNotEmpty(this.event) ? mergeHook(this.event, localHookToLifeCycleStore(localHook)) : { ...lifeCycleToArray(localHookToLifeCycleStore(localHook)) }; if (standaloneValidators.length && Object.assign(hooks, { standaloneValidator: standaloneValidators }), this.config.aot === !1) { const validator = createValidator(); this.router.dynamic.add(method, path, { validator, hooks, content: localHook?.type, handle, route: path }); const encoded = encodePath(path, { dynamic: !0 }); if (path !== encoded && this.router.dynamic.add(method, encoded, { validator, hooks, content: localHook?.type, handle, route: path }), !this.config.strictPath) { const loosePath = getLoosePath(path); this.router.dynamic.add(method, loosePath, { validator, hooks, content: localHook?.type, handle, route: path }); const encoded2 = encodePath(loosePath); 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, compile: void 0, hooks }); return; } const adapter = this["~adapter"].handler, nativeStaticHandler = typeof handle != "function" ? () => { const context = { redirect, request: this["~adapter"].isWebStandard ? new Request(`http://ely.sia${path}`, { method }) : void 0, server: null, set: { headers: Object.assign({}, this.setHeaders) }, status, store: this.store }; try { this.event.request?.map((x) => { if (typeof x.fn == "function") return x.fn(context); if (typeof x == "function") return x(context); }); } catch (error) { let res; context.error = error, this.event.error?.some((x) => { if (typeof x.fn == "function") return res = x.fn(context); if (typeof x == "function") return res = x(context); }), res !== void 0 && (handle = res); } const fn = adapter.createNativeStaticHandler?.( handle, hooks, context.set ); return fn instanceof Promise ? fn.then((fn2) => { if (fn2) return fn2; }) : fn?.(); } : void 0, useNativeStaticResponse = this.config.nativeStaticResponse === !0, addResponsePath = (path2) => { !useNativeStaticResponse || !nativeStaticHandler || (supportPerMethodInlineHandler ? this.router.response[path2] ? this.router.response[path2][method] = nativeStaticHandler() : this.router.response[path2] = { [method]: nativeStaticHandler() } : this.router.response[path2] = nativeStaticHandler()); }; addResponsePath(path); let _compiled; const compile = () => { if (_compiled) return _compiled; const compiled = composeHandler({ app: this, path, method, hooks, validator: createValidator(), handler: typeof handle != "function" && typeof adapter.createStaticHandler != "function" ? () => handle : handle, allowMeta, inference: this.inference }); return this.router.history[index] && (_compiled = this.router.history[index].composed = compiled), compiled; }; let oldIndex; if (`${method}_${path}` in this.routeTree) for (let i = 0; i < this.router.history.length; i++) { const route2 = this.router.history[i]; if (route2.path === path && route2.method === method) { oldIndex = i; break; } } else this.routeTree[`${method}_${path}`] = this.router.history.length; const index = oldIndex ?? this.router.history.length, route = this.router.history, mainHandler = shouldPrecompile ? compile() : (ctx) => _compiled ? _compiled(ctx) : (route[index].composed = compile())(ctx); oldIndex !== void 0 ? this.router.history[oldIndex] = Object.assign( { method, path, composed: mainHandler, compile, handler: handle, hooks }, standaloneValidators.length ? { standaloneValidators } : void 0, localHook.webSocket ? { websocket: localHook.websocket } : void 0 ) : this.router.history.push( Object.assign( { method, path, composed: mainHandler, compile, handler: handle, hooks }, localHook.webSocket ? { websocket: localHook.websocket } : void 0 ) ); const handler = { handler: shouldPrecompile ? route[index].composed : void 0, compile() { return this.handler = compile(); } }, staticRouter = this.router.static, isStaticPath = path.indexOf(":") === -1 && path.indexOf("*") === -1; if (method === "WS") { if (isStaticPath) { path in staticRouter ? staticRouter[path][method] = index : staticRouter[path] = { [method]: index }; return; } this.router.http.add("WS", path, handler), this.config.strictPath || this.router.http.add("WS", getLoosePath(path), handler); const encoded = encodePath(path, { dynamic: !0 }); path !== encoded && this.router.http.add("WS", encoded, handler); return; } if (isStaticPath) path in staticRouter ? staticRouter[path][method] = index : staticRouter[path] = { [method]: index }, this.config.strictPath || addResponsePath(getLoosePath(path)); else { if (this.router.http.add(method, path, handler), !this.config.strictPath) { const loosePath = getLoosePath(path); addResponsePath(loosePath), this.router.http.add(method, loosePath, handler); } const encoded = encodePath(path, { dynamic: !0 }); path !== encoded && (this.router.http.add(method, encoded, handler), addResponsePath(encoded)); } } headers(header) { return header ? (this.setHeaders || (this.setHeaders = {}), this.setHeaders = mergeDeep(this.setHeaders, header), this) : 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) { return this.on("start", handler), this; } onRequest(handler) { return this.on("request", handler), this; } onParse(options, handler) { return handler ? this.on( options, "parse", handler ) : typeof options == "string" ? this.on("parse", this["~parser"][options]) : this.on("parse", options); } /** * ### 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) { return this["~parser"][name] = parser, this; } onTransform(options, handler) { return handler ? this.on( options, "transform", handler ) : this.on("transform", options); } resolve(optionsOrResolve, resolve) { resolve || (resolve = optionsOrResolve, optionsOrResolve = { as: "local" }); const hook = { subType: "resolve", fn: resolve }; return this.onBeforeHandle(optionsOrResolve, hook); } mapResolve(optionsOrResolve, mapper) { mapper || (mapper = optionsOrResolve, optionsOrResolve = { as: "local" }); const hook = { subType: "mapResolve", fn: mapper }; return this.onBeforeHandle(optionsOrResolve, hook); } onBeforeHandle(options, handler) { return handler ? this.on( options, "beforeHandle", handler ) : this.on("beforeHandle", options); } onAfterHandle(options, handler) { return handler ? this.on( options, "afterHandle", handler ) : this.on("afterHandle", options); } mapResponse(options, handler) { return handler ? this.on( options, "mapResponse", handler ) : this.on("mapResponse", options); } onAfterResponse(options, handler) { return handler ? this.on( options, "afterResponse", handler ) : this.on("afterResponse", options); } /** * ### 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) { handler || (handler = options, options = { as: "local" }), Array.isArray(handler) || (handler = [handler]); for (const fn of handler) this.on( options, "trace", createTracer(fn) ); return this; } error(name, error) { switch (typeof name) { case "string": return error.prototype[ERROR_CODE] = name, this.definitions.error[name] = error, this; case "function": return this.definitions.error = name(this.definitions.error), this; } for (const [code, error2] of Object.entries(name)) error2.prototype[ERROR_CODE] = code, this.definitions.error[code] = error2; 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) { return handler ? this.on( options, "error", handler ) : this.on("error", options); } /** * ### stop | Life cycle event * Called after server stop serving request * * --- * @example * ```typescript * new Elysia() * .onStop((app) => { * cleanup() * }) * ``` */ onStop(handler) { return this.on("stop", handler), this; } on(optionsOrType, typeOrHandlers, handlers) { let type; switch (typeof optionsOrType) { case "string": type = optionsOrType, handlers = typeOrHandlers; break; case "object": type = typeOrHandlers, !Array.isArray(typeOrHandlers) && typeof typeOrHandlers == "object" && (handlers = typeOrHandlers); break; } Array.isArray(handlers) ? handlers = fnToContainer(handlers) : typeof handlers == "function" ? handlers = [ { fn: handlers } ] : handlers = [handlers]; const handles = handlers; for (const handle of handles) handle.scope = typeof optionsOrType == "string" ? "local" : optionsOrType?.as ?? "local", (type === "resolve" || type === "derive") && (handle.subType = type); type !== "trace" && (this.inference = sucrose( { [type]: handles.map((x) => x.fn) }, this.inference, this.config.sucrose )); for (const handle of handles) { const fn = asHookType(handle, "global", { skipIfHasType: !0 }); 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; } as(type) { return promoteEvent(this.event.parse, type), promoteEvent(this.event.transform, type), promoteEvent(this.event.beforeHandle, type), promoteEvent(this.event.afterHandle, type), promoteEvent(this.event.mapResponse, type), promoteEvent(this.event.afterResponse, type), promoteEvent(this.event.trace, type), promoteEvent(this.event.error, type), type === "scoped" ? (this.validator.scoped = mergeSchemaValidator( this.validator.scoped, this.validator.local ), this.validator.local = null, this.standaloneValidator.local !== null && (this.standaloneValidator.scoped ||= [], this.standaloneValidator.scoped.push( ...this.standaloneValidator.local ), this.standaloneValidator.local = null)) : type === "global" && (this.validator.global = mergeSchemaValidator( this.validator.global, mergeSchemaValidator( this.validator.scoped, this.validator.local ) ), this.validator.scoped = null, this.validator.local = null, this.standaloneValidator.local !== null && (this.standaloneValidator.scoped ||= [], this.standaloneValidator.scoped.push( ...this.standaloneValidator.local ), this.standaloneValidator.local = null), this.standaloneValidator.scoped !== null && (this.standaloneValidator.global ||= [], this.standaloneValidator.global.push( ...this.standaloneValidator.scoped ), this.standaloneValidator.scoped = null)), 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 }, instance["~parser"] = this["~parser"], instance.standaloneValidator = { local: [...this.standaloneValidator.local ?? []], scoped: [...this.standaloneValidator.scoped ?? []], global: [...this.standaloneValidator.global ?? []] }; const isSchema = typeof schemaOrRun == "object", sandbox = (isSchema ? run : schemaOrRun)(instance); return this.singleton = mergeDeep(this.singleton, instance.singleton), this.definitions = mergeDeep(this.definitions, instance.definitions), sandbox.event.request?.length && (this.event.request = [ ...this.event.request || [], ...sandbox.event.request || [] ]), 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 }) => { if (path = (isSchema ? "" : this.config.prefix ?? "") + prefix + path, isSchema) { const { body, headers, query, params, cookie, response, ...hook } = schemaOrRun, localHook = hooks; this.applyMacro(hook); const hasStandaloneSchema = body || headers || query || params || cookie || response; this.add( method, path, handler, mergeHook(hook, { ...localHook || {}, error: localHook.error ? Array.isArray(localHook.error) ? [ ...localHook.error ?? [], ...sandbox.event.error ?? [] ] : [ localHook.error, ...sandbox.event.error ?? [] ] : sandbox.event.error, // Merge macro's standaloneValidator with local and group schema standaloneValidator: hook.standaloneValidator || localHook.standaloneValidator || hasStandaloneSchema ? [ ...hook.standaloneValidator ?? [], ...localHook.standaloneValidator ?? [], ...hasStandaloneSchema ? [ { body, headers, query, params, cookie, response } ] : [] ] : void 0 }), void 0 ); } else this.add( method, path, handler, mergeHook(hooks, { error: sandbox.event.error }), { skipPrefix: !0 } ); } ), this; } /** * ### guard * Encapsulate and pass hook into all child handler * * --- * @example * ```typescript * import { t } from 'elysia' * * new Elysia() * .guard({ * 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), hook.detail && (this.config.detail ? this.config.detail = mergeDeep( Object.assign({}, this.config.detail), hook.detail ) : this.config.detail = hook.detail), hook.tags && (this.config.detail ? this.config.detail.tags = hook.tags : this.config.detail = { tags: hook.tags }); const type = hook.as ?? "local"; if (hook.schema === "standalone") { this.standaloneValidator[type] || (this.standaloneValidator[type] = []); const response = hook?.response ? typeof hook.response == "string" || Kind in hook.response || "~standard" in hook.response ? { 200: hook.response } : hook?.response : void 0; this.standaloneValidator[type].push({ body: hook.body, headers: hook.headers, params: hook.params, query: hook.query, response, cookie: hook.cookie }); } else 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 }; return hook.parse && this.on({ as: type }, "parse", hook.parse), hook.transform && this.on({ as: type }, "transform", hook.transform), hook.derive && this.on({ as: type }, "derive", hook.derive), hook.beforeHandle && this.on({ as: type }, "beforeHandle", hook.beforeHandle), hook.resolve && this.on({ as: type }, "resolve", hook.resolve), hook.afterHandle && this.on({ as: type }, "afterHandle", hook.afterHandle), hook.mapResponse && this.on({ as: type }, "mapResponse", hook.mapResponse), hook.afterResponse && this.on({ as: type }, "afterResponse", hook.afterResponse), hook.error && this.on({ as: type }, "error", hook.error), 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 }, instance.getServer = () => this.getServer(); const sandbox = run(instance); return this.singleton = mergeDeep(this.singleton, instance.singleton), this.definitions = mergeDeep(this.definitions, instance.definitions), sandbox.getServer = () => this.server, sandbox.event.request?.length && (this.event.request = [ ...this.event.request || [], ...sandbox.event.request || [] ]), 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 }) => { const { body, headers, query, params, cookie, response, ...guardHook } = hook, hasStandaloneSchema = body || headers || query || params || cookie || response; this.add( method, path, handler, mergeHook(guardHook, { ...localHook || {}, error: localHook.error ? Array.isArray(localHook.error) ? [ ...localHook.error ?? [], ...sandbox.event.error ?? [] ] : [ localHook.error, ...sandbox.event.error ?? [] ] : sandbox.event.error, standaloneValidator: hasStandaloneSchema ? [ ...localHook.standaloneValidator ?? [], { body, headers, query, params, cookie, response } ] : localHook.standaloneValidator }) ); } ), 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) { if (!plugin) return this; if (Array.isArray(plugin)) { let app = this; for (const p of plugin) app = app.use(p); return app; } return 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 (error) { throw console.error( 'Invalid plugin type. Expected Elysia instance, function, or module with "default" as Elysia instance or function that returns Elysia instance.' ), error; } }).then((v) => (v && typeof v.compile == "function" && v.compile(), v)) ), this) : 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); return t3 instanceof Promise ? t3.then((v2) => { v2 ? v2.compile() : v.compile(); }) : v.compile(); }) ); return this; } _use(plugin) { if (typeof plugin == "function") { const instance = plugin(this); return instance instanceof Promise ? (this.promisedModules.add( instance.then((plugin2) => { if (plugin2 instanceof _Elysia) { plugin2.getServer = () => this.getServer(), plugin2.getGlobalRoutes = () => this.getGlobalRoutes(), plugin2.getGlobalDefinitions = () => this.getGlobalDefinitions(), 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, hooks, void 0 ); return plugin2 === this ? void 0 : (this.propagatePromiseModules(plugin2), plugin2); } return typeof plugin2 == "function" ? plugin2( this ) : typeof plugin2.default == "function" ? plugin2.default( this ) : this._use(plugin2); }).then((v) => (v && typeof v.compile == "function" && v.compile(), v)) ), this) : instance; } this.propagatePromiseModules(plugin); const name = plugin.config.name, seed = plugin.config.seed; if (plugin.getParent = () => this, plugin.getServer = () => this.getServer(), plugin.getGlobalRoutes = () => this.getGlobalRoutes(), plugin.getGlobalDefinitions = () => this.getGlobalDefinitions(), plugin.standaloneValidator?.scoped && (this.standaloneValidator.local ? this.standaloneValidator.local = this.standaloneValidator.local.concat( plugin.standaloneValidator.scoped ) : this.standaloneValidator.local = plugin.standaloneValidator.scoped), plugin.standaloneValidator?.global && (this.standaloneValidator.global ? this.standaloneValidator.global = this.standaloneValidator.global.concat( plugin.standaloneValidator.global ) : this.standaloneValidator.global = plugin.standaloneValidator.global), isNotEmpty(plugin["~parser"]) && (this["~parser"] = { ...plugin["~parser"], ...this["~parser"] }), plugin.setHeaders && this.headers(plugin.setHeaders), name) { name in this.dependencies || (this.dependencies[name] = []); const current = seed !== void 0 ? checksum(name + JSON.stringify(seed)) : 0; this.dependencies[name].some( ({ checksum: checksum3 }) => current === checksum3 ) || (this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro }, this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions )); } else isNotEmpty(plugin.extender.macro) && (this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro }), plugin.extender.higherOrderFunctions.length && (this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions )); if (plugin.extender.higherOrderFunctions.length) { deduplicateChecksum(this.extender.higherOrderFunctions); const hofHashes = []; for (let i = 0; i < this.extender.higherOrderFunctions.length; i++) { const hof = this.extender.higherOrderFunctions[i]; hof.checksum && (hofHashes.includes(hof.checksum) && (this.extender.higherOrderFunctions.splice(i, 1), i--), hofHashes.push(hof.checksum)); } hofHashes.length = 0; } this.inference = mergeInference(this.inference, plugin.inference), isNotEmpty(plugin.singleton.decorator) && this.decorate(plugin.singleton.decorator), isNotEmpty(plugin.singleton.store) && this.state(plugin.singleton.store), isNotEmpty(plugin.definitions.type) && this.model(plugin.definitions.type), isNotEmpty(plugin.definitions.error) && this.error(plugin.definitions.error), isNotEmpty(plugin.extender.macro) && (this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro }); for (const { method, path, handler, hooks } of Object.values( plugin.router.history )) this.add(method, path, handler, hooks); if (name) { 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 } ), isNotEmpty(plugin.event) && (this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event), current )); } else isNotEmpty(plugin.event) && (this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event) )); return plugin.validator.global && (this.validator.global = mergeHook(this.validator.global, { ...plugin.validator.global })), plugin.validator.scoped && (this.validator.local = mergeHook(this.validator.local, { ...plugin.validator.scoped })), this; } macro(macroOrName, macro) { if (typeof macroOrName == "string" && !macro) throw new Error("Macro function is required"); return typeof macroOrName == "string" ? this.extender.macro[macroOrName] = macro : this.extender.macro = { ...this.extender.macro, ...macroOrName }, this; } applyMacro(localHook, appliable = localHook, { iteration = 0, applied = {} } = {}) { if (iteration >= 16) return; const macro = this.extender.macro; for (let [key, value] of Object.entries(appliable)) { if (!(key in macro)) continue; const macroHook = typeof macro[key] == "function" ? macro[key](value) : macro[key]; if (!macroHook || typeof macro[key] == "object" && value === !1) return; const seed = checksum(key + JSON.stringify(macroHook.seed ?? value)); if (!(seed in applied)) { applied[seed] = !0; for (let [k, value2] of Object.entries(macroHook)) if (k !== "seed") { if (k in emptySchema) { insertStandaloneValidator( localHook, k, value2 ), delete localHook[key]; continue; } if (k === "introspect") { value2?.(localHook), delete localHook[key]; continue; } if (k === "detail") { localHook.detail || (localHook.detail = {}), localHook.detail = mergeDeep(localHook.detail, value2, { mergeArray: !0 }), delete localHook[key]; continue; } if (k in macro) { this.applyMacro( localHook, { [k]: value2 }, { applied, iteration: iteration + 1 } ), delete localHook[key]; continue; } switch ((k === "derive" || k === "resolve") && typeof value2 == "function" && (value2 = { fn: value2, subType: k }), typeof localHook[k]) { case "function": localHook[k] = [localHook[k], value2]; break; case "object": Array.isArray(localHook[k]) ? localHook[k].push(value2) : localHook[k] = [localHook[k], value2]; break; case "undefined": localHook[k] = value2; break; } delete localHook[key]; } } } } mount(path, handleOrConfig, config) { if (path instanceof _Elysia || typeof path == "function" || path.length === 0 || path === "/") { const run = typeof path == "function" ? path : path instanceof _Elysia ? path.compile().fetch : handleOrConfig instanceof _Elysia ? handleOrConfig.compile().fetch : typeof handleOrConfig == "function" ? handleOrConfig : (() => { throw new Error("Invalid handler"); })(), 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 }) ); return this.route("ALL", "/*", handler2, { parse: "none", ...config, detail: { ...config?.detail, hide: !0 }, config: { mount: run } }), this; } const handle = handleOrConfig instanceof _Elysia ? handleOrConfig.compile().fetch : typeof handleOrConfig == "function" ? handleOrConfig : (() => { throw new Error("Invalid handler"); })(), length