equipped 5.2.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/cjs/server/impls/base.cjs +1 -1
- package/dist/cjs/server/impls/base.cjs.map +1 -1
- package/dist/cjs/server/impls/base.min.cjs +1 -1
- package/dist/cjs/server/impls/base.min.cjs.map +1 -1
- package/dist/cjs/utilities/authProviders.cjs +2 -2
- package/dist/cjs/utilities/authProviders.cjs.map +1 -1
- package/dist/cjs/utilities/authProviders.min.cjs +1 -1
- package/dist/cjs/utilities/authProviders.min.cjs.map +1 -1
- package/dist/esm/server/impls/base.min.mjs +1 -1
- package/dist/esm/server/impls/base.min.mjs.map +1 -1
- package/dist/esm/server/impls/base.mjs +1 -1
- package/dist/esm/server/impls/base.mjs.map +1 -1
- package/dist/esm/utilities/authProviders.min.mjs +1 -1
- package/dist/esm/utilities/authProviders.min.mjs.map +1 -1
- package/dist/esm/utilities/authProviders.mjs +2 -2
- package/dist/esm/utilities/authProviders.mjs.map +1 -1
- package/dist/types/server/impls/base.js +1 -1
- package/dist/types/utilities/authProviders.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [5.2.1](https://github.com/kevinand11/equipped/compare/v5.2.0...v5.2.1) (2026-02-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* only log error if it is not an equipped error ([ab8f517](https://github.com/kevinand11/equipped/commit/ab8f517b0ea4412453a89df759943e9d2c9df736))
|
|
11
|
+
|
|
5
12
|
## [5.2.0](https://github.com/kevinand11/equipped/compare/v5.1.11...v5.2.0) (2026-02-03)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -183,7 +183,7 @@ class Server {
|
|
|
183
183
|
throw new (0, _indexcjs.NotFoundError)(`Route ${request.path} not found`);
|
|
184
184
|
});
|
|
185
185
|
this.implementations.registerErrorHandler(async (error, _, res) => {
|
|
186
|
-
_indexcjs3.Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
186
|
+
if (!(error instanceof _indexcjs.EquippedError)) _indexcjs3.Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
187
187
|
const response = error instanceof _indexcjs.RequestError ? new (0, _requestscjs.Response)({
|
|
188
188
|
body: error.serializedErrors,
|
|
189
189
|
status: error.statusCode
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/server/impls/base.ts","/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.cjs"],"names":[],"mappings":"AAEA,iyBAAiC;AACjC,qCAAuC;AACvC,4FAAsB;AACtB,oCAAwC;AAExC,kDAA2D;AAC3D,qDAAyB;AACzB,wDAA2C;AAC3C,6DAA0D;AAC1D,4CAA+C;AAE/C,8CAAuC;AACvC,0CAAuB;AACvB,4CAA8B;AAC9B,wCAAkF;AAKlF,MAAM,cAAA,EAAgB,MAAA,CAAO,OAAA,CAAQ,qBAAW,CAAA,CAC9C,MAAA,CAAO,CAAC,CAAC,EAAE,KAAK,CAAA,EAAA,GAAM,MAAA,EAAQ,GAAG,CAAA,CACjC,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,EAAA,GAAA,CAAO;AAAA,EACvB,MAAA,EAAQ,KAAA;AAAA,EACR,WAAA,EAAa,kBAAA;AAAA,EACb,IAAA,EAAM,WAAA,CAAE,IAAA,CAAK,WAAA,CAAE,KAAA,CAAM,WAAA,CAAE,MAAA,CAAO,EAAE,OAAA,EAAS,WAAA,CAAE,MAAA,CAAO,CAAA,EAAG,KAAA,EAAO,WAAA,CAAE,QAAA,CAAS,WAAA,CAAE,MAAA,CAAO,CAAC,EAAE,CAAC,CAAC,CAAA,EAAG;AAAA,IACvF,MAAA,EAAQ,CAAA,OAAA,EAAU,GAAG,CAAA,QAAA,CAAA;AAAA,IACrB,WAAA,EAAa,CAAA,EAAA;AACb,EAAA;AACA;AAEgD;AAejD,EAAA;AACQ,IAAA;AACA,IAAA;AASM,IAAA;AACT,IAAA;AACC,IAAA;AACQ,IAAA;AACT,IAAA;AACN,EAAA;AA9B0C,EAAA;AAC3B,EAAA;AACf,EAAA;AACA,EAAA;AACU,EAAA;AACW,EAAA;AACb,IAAA;AACE,MAAA;AACE,MAAA;AACV,MAAA;AACD,IAAA;AACD,EAAA;AAqBa,EAAA;AACC,IAAA;AACd,EAAA;AAEgC,EAAA;AACxB,IAAA;AACM,MAAA;AACH,QAAA;AAEF,QAAA;AACG,QAAA;AACF,UAAA;AAEP,QAAA;AACS,wBAAA;AAED,QAAA;AAEH,QAAA;AACC,QAAA;AACD,QAAA;AACE,UAAA;AACF,UAAA;AACH,YAAA;AACM,YAAA;AACA,YAAA;AAIN,YAAA;AACD,UAAA;AACK,YAAA;AACH,cAAA;AACA,cAAA;AAIA,cAAA;AACD,YAAA;AACM,YAAA;AACP,UAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACF,EAAA;AAEe,EAAA;AACR,IAAA;AACA,IAAA;AACO,IAAA;AACT,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAMA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACR,IAAA;AACc,IAAA;AACP,MAAA;AACE,MAAA;AAEA,MAAA;AACP,QAAA;AACA,QAAA;AACD,MAAA;AACQ,MAAA;AACD,QAAA;AACN,QAAA;AACO,UAAA;AACE,UAAA;AACD,UAAA;AACP,QAAA;AACD,QAAA;AACS,UAAA;AACR,UAAA;AACQ,UAAA;AACP,QAAA;AACH,MAAA;AACA,IAAA;AACK,IAAA;AACI,IAAA;AACJ,IAAA;AACI,IAAA;AACJ,IAAA;AACO,MAAA;AACN,MAAA;AAAmC,QAAA;AACtC,QAAA;AACO,UAAA;AACR,UAAA;AACO,UAAA;AACD,UAAA;AACN,UAAA;AACA,QAAA;AACF,MAAA;AAEK,MAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACR,IAAA;AACM,IAAA;AACO,MAAA;AACH,MAAA;AACT,MAAA;AAEM,MAAA;AAAoC,QAAA;AACvC,QAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AAEK,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACR,IAAA;AACO,IAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACC,IAAA;AACR,EAAA;AAEc,EAAA;AACA,IAAA;AACP,IAAA;AACM,IAAA;AACH,IAAA;AACH,MAAA;AACI,QAAA;AACF,QAAA;AACG,QAAA;AAED,UAAA;AACN,UAAA;AACA,QAAA;AACF,MAAA;AAEG,IAAA;AACE,MAAA;AACI,MAAA;AACV,IAAA;AACI,IAAA;AACK,MAAA;AACH,MAAA;AAGI,QAAA;AACE,QAAA;AAEJ,MAAA;AACK,QAAA;AACD,QAAA;AACR,MAAA;AACG,MAAA;AACP,IAAA;AAEa,IAAA;AACR,IAAA;AACO,IAAA;AACN,IAAA;AACR,EAAA;AACD;AChDkB;AACA;AACA","file":"/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.cjs","sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tInstance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n",null]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/server/impls/base.ts","/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.cjs"],"names":[],"mappings":"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","file":"/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.cjs","sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tif (!(error instanceof EquippedError)) Instance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n",null]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }require('cors');var _socketio = require('socket.io');var _supertest = require('supertest'); var _supertest2 = _interopRequireDefault(_supertest);var _valleyed = require('valleyed');var _indexmincjs = require('../../errors/index.min.cjs');var _indexmincjs3 = require('../../instance/index.min.cjs');var _indexmincjs5 = require('../../validations/index.min.cjs');var _valleyedmincjs = require('../../validations/valleyed.min.cjs');var _openapimincjs = require('../openapi.min.cjs');var _requestsmincjs = require('../requests.min.cjs');require('../routes');var _socketsmincjs = require('../sockets.min.cjs');var _typesmincjs = require('../types.min.cjs');const x=Object.entries(_typesmincjs.StatusCodes).filter(([,f])=>f>399).map(([f,
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }require('cors');var _socketio = require('socket.io');var _supertest = require('supertest'); var _supertest2 = _interopRequireDefault(_supertest);var _valleyed = require('valleyed');var _indexmincjs = require('../../errors/index.min.cjs');var _indexmincjs3 = require('../../instance/index.min.cjs');var _indexmincjs5 = require('../../validations/index.min.cjs');var _valleyedmincjs = require('../../validations/valleyed.min.cjs');var _openapimincjs = require('../openapi.min.cjs');var _requestsmincjs = require('../requests.min.cjs');require('../routes');var _socketsmincjs = require('../sockets.min.cjs');var _typesmincjs = require('../types.min.cjs');const x=Object.entries(_typesmincjs.StatusCodes).filter(([,f])=>f>399).map(([f,r])=>({status:r,contentType:"application/json",pipe:_valleyed.v.meta(_valleyed.v.array(_valleyed.v.object({message:_valleyed.v.string(),field:_valleyed.v.optional(_valleyed.v.string())})),{$refId:`Errors.${f}Response`,description:`${f} Response`})}));class Y{constructor(r,s,n){this.config=s;this.implementations=n;this.server=r,this.#e=new (0, _openapimincjs.OpenApi)(s);const p=new (0, _socketio.Server)(r,{cors:this.cors});this.socket=new (0, _socketsmincjs.SocketEmitter)(p,s),this.addRouter(this.#e.router())}#t=[];#s=new Map;#e;get cors(){return{origin:_optionalChain([this, 'access', _ => _.config, 'access', _2 => _2.cors, 'optionalAccess', _3 => _3.origin]),methods:(_nullishCoalesce(_optionalChain([this, 'access', _4 => _4.config, 'access', _5 => _5.cors, 'optionalAccess', _6 => _6.methods]), () => (Object.values(_typesmincjs.Methods)))).filter(r=>r!==_typesmincjs.Methods.options).map(r=>r.toUpperCase()),credentials:_optionalChain([this, 'access', _7 => _7.config, 'access', _8 => _8.cors, 'optionalAccess', _9 => _9.credentials])}}addRouter(...r){r.map(s=>s.routes).forEach(s=>this.addRoute(...s))}addRoute(...r){r.forEach(s=>{this.#t.push(async()=>{const{method:n,path:p,schema:o={},onError:c,middlewares:d=[]}=s,i=`(${n.toUpperCase()}) ${this.#e.cleanPath(p)}`;if(this.#s.get(i))throw new (0, _indexmincjs.EquippedError)(`Route key ${i} already registered. All route keys must be unique`,{route:s,key:i});d.forEach(h=>_optionalChain([h, 'access', _10 => _10.onSetup, 'optionalCall', _11 => _11(s)])),_optionalChain([c, 'optionalAccess', _12 => _12.onSetup, 'optionalCall', _13 => _13(s)]);const{validateRequest:R,validateResponse:q,jsonSchema:v}=this.#o(n,o);this.#s.set(i,!0),await this.#e.register(s,v),this.implementations.registerRoute(n,this.#e.cleanPath(p),async(h,w)=>{const g=await R(await this.implementations.parseRequest(h));try{for(const u of d)await u.cb(g,this.config);const e=await s.handler(g,this.config),t=e instanceof _requestsmincjs.Response?e:new (0, _requestsmincjs.Response)({body:e,status:_typesmincjs.StatusCodes.Ok,headers:{},piped:!1});return await this.implementations.handleResponse(w,await q(t))}catch(e){if(_optionalChain([c, 'optionalAccess', _14 => _14.cb])){const t=await c.cb(g,this.config,e),u=t instanceof _requestsmincjs.Response?t:new (0, _requestsmincjs.Response)({body:t,status:_typesmincjs.StatusCodes.BadRequest,headers:{}});return await this.implementations.handleResponse(w,await q(u))}throw e}})})})}#o(r,s){const n=_nullishCoalesce(_optionalChain([s, 'optionalAccess', _15 => _15.defaultStatusCode]), () => (_typesmincjs.StatusCodes.Ok)),p=_nullishCoalesce(_optionalChain([s, 'optionalAccess', _16 => _16.defaultContentType]), () => ("application/json"));let o=n,c=p;const d={response:{},request:{}},i={},R={};[{key:"params",type:"request"},{key:"headers",type:"request"},{key:"cookies",type:"request"},{key:"query",type:"request"},{key:"body",type:"request",skip:![_typesmincjs.Methods.post,_typesmincjs.Methods.put,_typesmincjs.Methods.patch].includes(r)},{key:"response",type:"response"},{key:"responseHeaders",type:"response"},{key:"responseCookies",type:"response"}].forEach(e=>{const t=_nullishCoalesce(s[e.key], () => (_valleyed.v.any()));if(!e.skip&&(e.type==="request"&&(i[e.key]=t,d.request[e.key]=_valleyed.v.schema(t)),e.type==="response")){const u=x.concat({status:n,contentType:c,pipe:t});R[e.key]=_valleyed.v.any().pipe(m=>{const E=_optionalChain([u, 'access', _17 => _17.find, 'call', _18 => _18(C=>C.status===o), 'optionalAccess', _19 => _19.pipe]);if(!E)throw _valleyed.PipeError.root(`schema not defined for status code: ${o}`,m);return _valleyed.v.assert(E,m)}),d.response[e.key]=u.map(m=>({status:m.status,contentType:m.contentType,schema:_valleyed.v.schema(m.pipe)}))}});const v=_valleyed.v.object(i);_valleyed.v.compile(v,{allErrors:!0});const h=_valleyed.v.object(R);return _valleyed.v.compile(h,{allErrors:!0}),{jsonSchema:d,validateRequest:async e=>{if(!Object.keys(i))return e;const t=_valleyedmincjs.requestLocalStorage.run(e,()=>_valleyed.v.validate(v,{params:e.params,headers:e.headers,query:e.query,body:e.body,cookies:e.cookies}));if(!t.valid)throw _indexmincjs5.pipeErrorToValidationError.call(void 0, t.error);return e.params=t.value.params,e.headers=t.value.headers,e.query=t.value.query,e.body=t.value.body,e.cookies=t.value.cookies,e},validateResponse:async e=>{if(!Object.keys(R))return e;o=e.status,c=e.contentType;const t=_valleyedmincjs.responseLocalStorage.run(e,()=>_valleyed.v.validate(h,{responseHeaders:e.headers,responseCookies:Object.fromEntries(Object.entries(e.cookies).map(([u,m])=>[u,m.value])),response:e.body}));if(!t.valid)throw _indexmincjs5.pipeErrorToValidationError.call(void 0, t.error);return e.body=t.value.response,e.headers=t.value.responseHeaders,e.cookieValues=t.value.responseCookies,e}}}test(){return _supertest2.default.call(void 0, this.server)}async start(){const r=this.config.port,s=_indexmincjs3.Instance.get(),{app:n}=s.settings;this.config.healthPath&&this.addRoute({method:_typesmincjs.Methods.get,path:this.config.healthPath,handler:async o=>o.res({body:`${s.id}(${n.name}) service running`,contentType:"text/plain"})}),this.implementations.registerNotFoundHandler(async o=>{const c=await this.implementations.parseRequest(o);throw new (0, _indexmincjs.NotFoundError)(`Route ${c.path} not found`)}),this.implementations.registerErrorHandler(async(o,c,d)=>{o instanceof _indexmincjs.EquippedError||_indexmincjs3.Instance.get().log.error({error:o},"Uncaught error in route handler");const i=o instanceof _indexmincjs.RequestError?new (0, _requestsmincjs.Response)({body:o.serializedErrors,status:o.statusCode}):new (0, _requestsmincjs.Response)({body:[{message:"Something went wrong",data:o.message}],status:_typesmincjs.StatusCodes.BadRequest});return await this.implementations.handleResponse(d,i)}),await Promise.all(this.#t.map(o=>o()));const p=await this.implementations.start(r);return p&&_indexmincjs3.Instance.get().log.info(`${s.id}(${n.name}) service listening on port ${r}`),p}}exports.Server = Y;
|
|
2
2
|
//# sourceMappingURL=base.min.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"names":["v","server","config","implementations"],"mappings":"AAEA,iyBAAiC,qCACM,4FACjB,oCACkB,yDAG/B,4DACA,+DACqB,oEAGP,mDAEvB,qDACS,qBAAsD,mDAM7D,+CAGa,MAAA,CAAA,CAAA,MACb,CAAA,OAAQ,CAAA,wBAAA,CAAKA,CAAAA,MAAE,CAAMA,CAAAA,CAAE,CAAA,CAAA,CAAA,CAAA,EAAA,CAAO,CAAE,GAAA,CAAA,CAAA,GAASA,CAAAA,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAO,CAAG,CAAA,MAAOA,CAAE,CAAA,CAAA,WAAW,CAAA,kBAChE,CAAA,IAAA,CAAA,WAAA,CAAA,IAAa,CAAA,WAAA,CAAA,KAAA,CAAA,WACrB,CAAA,MAAA,CAAA,CAAA,OAAmB,CAAA,WAAA,CAAA,MAAA,CAAA,CACpB,CAAC,KAGI,CAAA,WAAA,CAAA,QAcN,CAAA,WAAA,CAAA,MACCC,CAAAA,CACQC,CAAAA,CACAC,CAAAA,CAQP,CATO,CAAA,MAAA,CAAA,CAAA,OACA,EAAA,CAAA,CAAA,QAAA,CAAA,CAAA,WASR,CAAK,CAAA,EAAA","file":"/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.min.cjs","sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tInstance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"names":["v","server","config","implementations"],"mappings":"AAEA,iyBAAiC,qCACM,4FACjB,oCACkB,yDAG/B,4DACA,+DACqB,oEAGP,mDAEvB,qDACS,qBAAsD,mDAM7D,+CAGa,MAAA,CAAA,CAAA,MACb,CAAA,OAAQ,CAAA,wBAAA,CAAKA,CAAAA,MAAE,CAAMA,CAAAA,CAAE,CAAA,CAAA,CAAA,CAAA,EAAA,CAAO,CAAE,GAAA,CAAA,CAAA,GAASA,CAAAA,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAO,CAAG,CAAA,MAAOA,CAAE,CAAA,CAAA,WAAW,CAAA,kBAChE,CAAA,IAAA,CAAA,WAAA,CAAA,IAAa,CAAA,WAAA,CAAA,KAAA,CAAA,WACrB,CAAA,MAAA,CAAA,CAAA,OAAmB,CAAA,WAAA,CAAA,MAAA,CAAA,CACpB,CAAC,KAGI,CAAA,WAAA,CAAA,QAcN,CAAA,WAAA,CAAA,MACCC,CAAAA,CACQC,CAAAA,CACAC,CAAAA,CAQP,CATO,CAAA,MAAA,CAAA,CAAA,OACA,EAAA,CAAA,CAAA,QAAA,CAAA,CAAA,WASR,CAAK,CAAA,EAAA","file":"/home/runner/work/equipped/equipped/dist/cjs/server/impls/base.min.cjs","sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tif (!(error instanceof EquippedError)) Instance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"]}
|
|
@@ -15,7 +15,7 @@ const signinWithApple = async (idToken) => {
|
|
|
15
15
|
try {
|
|
16
16
|
const APPLE_BASE = "https://appleid.apple.com";
|
|
17
17
|
const json = _jsonwebtoken2.default.decode(idToken, { complete: true });
|
|
18
|
-
if (!_optionalChain([json, 'optionalAccess', _ => _.header])) throw new
|
|
18
|
+
if (!_optionalChain([json, 'optionalAccess', _ => _.header])) throw new (0, _indexcjs.EquippedError)("Missing JWT header", { idToken, json });
|
|
19
19
|
const { kid, alg } = json.header;
|
|
20
20
|
const publicKey = await _jwksrsa2.default.call(void 0, { jwksUri: `${APPLE_BASE}/auth/keys`, cache: true }).getSigningKey(kid).then((key) => key.getPublicKey()).catch(() => null);
|
|
21
21
|
if (!publicKey) throw new (0, _indexcjs.EquippedError)("no publicKey", { idToken, publicKey, json });
|
|
@@ -41,7 +41,7 @@ const signinWithFacebook = async (accessToken, fields = []) => {
|
|
|
41
41
|
throw new (0, _indexcjs.EquippedError)("Failed to sign in with facebook", { accessToken, fields }, err);
|
|
42
42
|
});
|
|
43
43
|
const isValidData = fields.every((key) => key in data);
|
|
44
|
-
if (!isValidData) throw new
|
|
44
|
+
if (!isValidData) throw new (0, _indexcjs.EquippedError)("Incomplete scope for access token", { accessToken, fields, data });
|
|
45
45
|
data.email_verified = "true";
|
|
46
46
|
return data;
|
|
47
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utilities/authProviders.ts","/home/runner/work/equipped/equipped/dist/cjs/utilities/authProviders.cjs"],"names":[],"mappings":"AAAA,yvBAAkB;AAClB,wGAAgB;AAChB,qFAAuB;AAEvB,+CAA8B;AAEvB,MAAM,iBAAA,EAAmB,MAAA,CAAO,OAAA,EAAA,GAAoB;AAC1D,EAAA,MAAM,QAAA,EAAU,CAAA,iDAAA,EAAoD,OAAO,CAAA,CAAA;AAClB,EAAA;AACiB,IAAA;AACzE,EAAA;AACsB,EAAA;AACD,EAAA;AACf,EAAA;AAQR;AAE0D;AACrD,EAAA;AACgB,IAAA;AACgC,IAAA;
|
|
1
|
+
{"version":3,"sources":["../../../src/utilities/authProviders.ts","/home/runner/work/equipped/equipped/dist/cjs/utilities/authProviders.cjs"],"names":[],"mappings":"AAAA,yvBAAkB;AAClB,wGAAgB;AAChB,qFAAuB;AAEvB,+CAA8B;AAEvB,MAAM,iBAAA,EAAmB,MAAA,CAAO,OAAA,EAAA,GAAoB;AAC1D,EAAA,MAAM,QAAA,EAAU,CAAA,iDAAA,EAAoD,OAAO,CAAA,CAAA;AAClB,EAAA;AACiB,IAAA;AACzE,EAAA;AACsB,EAAA;AACD,EAAA;AACf,EAAA;AAQR;AAE0D;AACrD,EAAA;AACgB,IAAA;AACgC,IAAA;AACgB,IAAA;AACzC,IAAA;AAC+C,IAAA;AAIN,IAAA;AACK,IAAA;AACT,IAAA;AACU,IAAA;AACf,IAAA;AAEnD,IAAA;AAMM,EAAA;AAC2D,IAAA;AACzE,EAAA;AACD;AAE0F;AAC7B,EAAA;AAElD,EAAA;AACA,IAAA;AACH,IAAA;AACG,IAAA;AACgB,MAAA;AACT,MAAA;AACf,IAAA;AAEe,EAAA;AAC2D,IAAA;AAC1E,EAAA;AACmD,EAAA;AACX,EAAA;AACpB,EAAA;AACf,EAAA;AASR;AC7B8E;AACA;AACA;AACA;AACA","file":"/home/runner/work/equipped/equipped/dist/cjs/utilities/authProviders.cjs","sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new EquippedError('Missing JWT header', { idToken, json })\n\t\tconst { kid, alg } = json.header\n\t\tconst publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true })\n\t\t\t.getSigningKey(kid)\n\t\t\t.then((key) => key.getPublicKey())\n\t\t\t.catch(() => null)\n\t\tif (!publicKey) throw new EquippedError('no publicKey', { idToken, publicKey, json })\n\t\tconst data = jwt.verify(idToken, publicKey, { algorithms: [alg as any] }) as Record<string, any>\n\t\tif (!data) throw new EquippedError('no data', { idToken, data })\n\t\tif (data.iss !== APPLE_BASE) throw new EquippedError('iss doesnt match', { idToken, data })\n\t\tif (data.exp * 1000 < Date.now()) throw new EquippedError('expired idToken', { idToken, data })\n\t\t// TODO: Find out how to get profile data from api\n\t\treturn data as {\n\t\t\temail?: string\n\t\t\tsub: string\n\t\t\temail_verified?: 'true' | 'false'\n\t\t\tis_private_email?: 'true' | 'false'\n\t\t} & Record<string, any>\n\t} catch (err) {\n\t\tthrow new EquippedError('Failed to sign in with apple', { idToken }, err)\n\t}\n}\n\nexport const signinWithFacebook = async (accessToken: string, fields = [] as string[]) => {\n\tfields = [...new Set([...fields, 'name', 'picture', 'email'])]\n\tconst { data } = await axios\n\t\t.request({\n\t\t\tmethod: 'get',\n\t\t\turl: 'https://graph.facebook.com/v15.0/me',\n\t\t\tparams: {\n\t\t\t\tfields: fields.join(','),\n\t\t\t\taccess_token: accessToken,\n\t\t\t},\n\t\t})\n\t\t.catch((err) => {\n\t\t\tthrow new EquippedError('Failed to sign in with facebook', { accessToken, fields }, err)\n\t\t})\n\tconst isValidData = fields.every((key) => key in data)\n\tif (!isValidData) throw new EquippedError('Incomplete scope for access token', { accessToken, fields, data })\n\tdata.email_verified = 'true'\n\treturn data as {\n\t\tid: string\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tname: string\n\t\tpicture: {\n\t\t\tdata: { height: number; is_silhouette: boolean; url: string; width: number }\n\t\t}\n\t} & Record<string, any>\n}\n",null]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _axios = require('axios'); var _axios2 = _interopRequireDefault(_axios);var _jsonwebtoken = require('jsonwebtoken'); var _jsonwebtoken2 = _interopRequireDefault(_jsonwebtoken);var _jwksrsa = require('jwks-rsa'); var _jwksrsa2 = _interopRequireDefault(_jwksrsa);var _indexmincjs = require('../errors/index.min.cjs');const f=async t=>{const i=`https://oauth2.googleapis.com/tokeninfo?id_token=${t}`,{data:e}=await _axios2.default.get(i).catch(s=>{throw new (0, _indexmincjs.EquippedError)("Failed to sign in with google",{idToken:t},s)});return e.first_name=e.given_name,e.last_name=e.family_name,e},d= exports.signinWithApple =async t=>{try{const i="https://appleid.apple.com",e=_jsonwebtoken2.default.decode(t,{complete:!0});if(!_optionalChain([e, 'optionalAccess', _ => _.header]))throw new
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _axios = require('axios'); var _axios2 = _interopRequireDefault(_axios);var _jsonwebtoken = require('jsonwebtoken'); var _jsonwebtoken2 = _interopRequireDefault(_jsonwebtoken);var _jwksrsa = require('jwks-rsa'); var _jwksrsa2 = _interopRequireDefault(_jwksrsa);var _indexmincjs = require('../errors/index.min.cjs');const f=async t=>{const i=`https://oauth2.googleapis.com/tokeninfo?id_token=${t}`,{data:e}=await _axios2.default.get(i).catch(s=>{throw new (0, _indexmincjs.EquippedError)("Failed to sign in with google",{idToken:t},s)});return e.first_name=e.given_name,e.last_name=e.family_name,e},d= exports.signinWithApple =async t=>{try{const i="https://appleid.apple.com",e=_jsonwebtoken2.default.decode(t,{complete:!0});if(!_optionalChain([e, 'optionalAccess', _ => _.header]))throw new (0, _indexmincjs.EquippedError)("Missing JWT header",{idToken:t,json:e});const{kid:s,alg:n}=e.header,o=await _jwksrsa2.default.call(void 0, {jwksUri:`${i}/auth/keys`,cache:!0}).getSigningKey(s).then(l=>l.getPublicKey()).catch(()=>null);if(!o)throw new (0, _indexmincjs.EquippedError)("no publicKey",{idToken:t,publicKey:o,json:e});const r=_jsonwebtoken2.default.verify(t,o,{algorithms:[n]});if(!r)throw new (0, _indexmincjs.EquippedError)("no data",{idToken:t,data:r});if(r.iss!==i)throw new (0, _indexmincjs.EquippedError)("iss doesnt match",{idToken:t,data:r});if(r.exp*1e3<Date.now())throw new (0, _indexmincjs.EquippedError)("expired idToken",{idToken:t,data:r});return r}catch(i){throw new (0, _indexmincjs.EquippedError)("Failed to sign in with apple",{idToken:t},i)}},y= exports.signinWithFacebook =async(t,i=[])=>{i=[...new Set([...i,"name","picture","email"])];const{data:e}=await _axios2.default.request({method:"get",url:"https://graph.facebook.com/v15.0/me",params:{fields:i.join(","),access_token:t}}).catch(n=>{throw new (0, _indexmincjs.EquippedError)("Failed to sign in with facebook",{accessToken:t,fields:i},n)});if(!i.every(n=>n in e))throw new (0, _indexmincjs.EquippedError)("Incomplete scope for access token",{accessToken:t,fields:i,data:e});return e.email_verified="true",e};exports.signinWithApple = d; exports.signinWithFacebook = y; exports.signinWithGoogle = f;
|
|
2
2
|
//# sourceMappingURL=authProviders.min.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"names":["idToken"],"mappings":"AAAA,yvBAAkB,wGACF,qFACO,sDAIgBA,MACtC,CAAA,CAAA,MAAgB,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA,iDACC,EAAA,CAAA,CAAA,CAAA","file":"/home/runner/work/equipped/equipped/dist/cjs/utilities/authProviders.min.cjs","sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new
|
|
1
|
+
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"names":["idToken"],"mappings":"AAAA,yvBAAkB,wGACF,qFACO,sDAIgBA,MACtC,CAAA,CAAA,MAAgB,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA,iDACC,EAAA,CAAA,CAAA,CAAA","file":"/home/runner/work/equipped/equipped/dist/cjs/utilities/authProviders.min.cjs","sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new EquippedError('Missing JWT header', { idToken, json })\n\t\tconst { kid, alg } = json.header\n\t\tconst publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true })\n\t\t\t.getSigningKey(kid)\n\t\t\t.then((key) => key.getPublicKey())\n\t\t\t.catch(() => null)\n\t\tif (!publicKey) throw new EquippedError('no publicKey', { idToken, publicKey, json })\n\t\tconst data = jwt.verify(idToken, publicKey, { algorithms: [alg as any] }) as Record<string, any>\n\t\tif (!data) throw new EquippedError('no data', { idToken, data })\n\t\tif (data.iss !== APPLE_BASE) throw new EquippedError('iss doesnt match', { idToken, data })\n\t\tif (data.exp * 1000 < Date.now()) throw new EquippedError('expired idToken', { idToken, data })\n\t\t// TODO: Find out how to get profile data from api\n\t\treturn data as {\n\t\t\temail?: string\n\t\t\tsub: string\n\t\t\temail_verified?: 'true' | 'false'\n\t\t\tis_private_email?: 'true' | 'false'\n\t\t} & Record<string, any>\n\t} catch (err) {\n\t\tthrow new EquippedError('Failed to sign in with apple', { idToken }, err)\n\t}\n}\n\nexport const signinWithFacebook = async (accessToken: string, fields = [] as string[]) => {\n\tfields = [...new Set([...fields, 'name', 'picture', 'email'])]\n\tconst { data } = await axios\n\t\t.request({\n\t\t\tmethod: 'get',\n\t\t\turl: 'https://graph.facebook.com/v15.0/me',\n\t\t\tparams: {\n\t\t\t\tfields: fields.join(','),\n\t\t\t\taccess_token: accessToken,\n\t\t\t},\n\t\t})\n\t\t.catch((err) => {\n\t\t\tthrow new EquippedError('Failed to sign in with facebook', { accessToken, fields }, err)\n\t\t})\n\tconst isValidData = fields.every((key) => key in data)\n\tif (!isValidData) throw new EquippedError('Incomplete scope for access token', { accessToken, fields, data })\n\tdata.email_verified = 'true'\n\treturn data as {\n\t\tid: string\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tname: string\n\t\tpicture: {\n\t\t\tdata: { height: number; is_silhouette: boolean; url: string; width: number }\n\t\t}\n\t} & Record<string, any>\n}\n"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"cors";import{Server as
|
|
1
|
+
import"cors";import{Server as j}from"socket.io";import O from"supertest";import{PipeError as $,v as a}from"valleyed";import{EquippedError as S,NotFoundError as D,RequestError as T}from "../../errors/index.min.mjs";import{Instance as b}from "../../instance/index.min.mjs";import{pipeErrorToValidationError as P}from "../../validations/index.min.mjs";import{requestLocalStorage as H,responseLocalStorage as A}from "../../validations/valleyed.min.mjs";import{OpenApi as M}from "../openapi.min.mjs";import{Response as y}from "../requests.min.mjs";import"../routes";import{SocketEmitter as V}from "../sockets.min.mjs";import{Methods as l,StatusCodes as k}from "../types.min.mjs";const x=Object.entries(k).filter(([,f])=>f>399).map(([f,r])=>({status:r,contentType:"application/json",pipe:a.meta(a.array(a.object({message:a.string(),field:a.optional(a.string())})),{$refId:`Errors.${f}Response`,description:`${f} Response`})}));class Y{constructor(r,s,n){this.config=s;this.implementations=n;this.server=r,this.#e=new M(s);const p=new j(r,{cors:this.cors});this.socket=new V(p,s),this.addRouter(this.#e.router())}#t=[];#s=new Map;#e;socket;server;get cors(){return{origin:this.config.cors?.origin,methods:(this.config.cors?.methods??Object.values(l)).filter(r=>r!==l.options).map(r=>r.toUpperCase()),credentials:this.config.cors?.credentials}}addRouter(...r){r.map(s=>s.routes).forEach(s=>this.addRoute(...s))}addRoute(...r){r.forEach(s=>{this.#t.push(async()=>{const{method:n,path:p,schema:o={},onError:c,middlewares:d=[]}=s,i=`(${n.toUpperCase()}) ${this.#e.cleanPath(p)}`;if(this.#s.get(i))throw new S(`Route key ${i} already registered. All route keys must be unique`,{route:s,key:i});d.forEach(h=>h.onSetup?.(s)),c?.onSetup?.(s);const{validateRequest:R,validateResponse:q,jsonSchema:v}=this.#o(n,o);this.#s.set(i,!0),await this.#e.register(s,v),this.implementations.registerRoute(n,this.#e.cleanPath(p),async(h,w)=>{const g=await R(await this.implementations.parseRequest(h));try{for(const u of d)await u.cb(g,this.config);const e=await s.handler(g,this.config),t=e instanceof y?e:new y({body:e,status:k.Ok,headers:{},piped:!1});return await this.implementations.handleResponse(w,await q(t))}catch(e){if(c?.cb){const t=await c.cb(g,this.config,e),u=t instanceof y?t:new y({body:t,status:k.BadRequest,headers:{}});return await this.implementations.handleResponse(w,await q(u))}throw e}})})})}#o(r,s){const n=s?.defaultStatusCode??k.Ok,p=s?.defaultContentType??"application/json";let o=n,c=p;const d={response:{},request:{}},i={},R={};[{key:"params",type:"request"},{key:"headers",type:"request"},{key:"cookies",type:"request"},{key:"query",type:"request"},{key:"body",type:"request",skip:![l.post,l.put,l.patch].includes(r)},{key:"response",type:"response"},{key:"responseHeaders",type:"response"},{key:"responseCookies",type:"response"}].forEach(e=>{const t=s[e.key]??a.any();if(!e.skip&&(e.type==="request"&&(i[e.key]=t,d.request[e.key]=a.schema(t)),e.type==="response")){const u=x.concat({status:n,contentType:c,pipe:t});R[e.key]=a.any().pipe(m=>{const E=u.find(C=>C.status===o)?.pipe;if(!E)throw $.root(`schema not defined for status code: ${o}`,m);return a.assert(E,m)}),d.response[e.key]=u.map(m=>({status:m.status,contentType:m.contentType,schema:a.schema(m.pipe)}))}});const v=a.object(i);a.compile(v,{allErrors:!0});const h=a.object(R);return a.compile(h,{allErrors:!0}),{jsonSchema:d,validateRequest:async e=>{if(!Object.keys(i))return e;const t=H.run(e,()=>a.validate(v,{params:e.params,headers:e.headers,query:e.query,body:e.body,cookies:e.cookies}));if(!t.valid)throw P(t.error);return e.params=t.value.params,e.headers=t.value.headers,e.query=t.value.query,e.body=t.value.body,e.cookies=t.value.cookies,e},validateResponse:async e=>{if(!Object.keys(R))return e;o=e.status,c=e.contentType;const t=A.run(e,()=>a.validate(h,{responseHeaders:e.headers,responseCookies:Object.fromEntries(Object.entries(e.cookies).map(([u,m])=>[u,m.value])),response:e.body}));if(!t.valid)throw P(t.error);return e.body=t.value.response,e.headers=t.value.responseHeaders,e.cookieValues=t.value.responseCookies,e}}}test(){return O(this.server)}async start(){const r=this.config.port,s=b.get(),{app:n}=s.settings;this.config.healthPath&&this.addRoute({method:l.get,path:this.config.healthPath,handler:async o=>o.res({body:`${s.id}(${n.name}) service running`,contentType:"text/plain"})}),this.implementations.registerNotFoundHandler(async o=>{const c=await this.implementations.parseRequest(o);throw new D(`Route ${c.path} not found`)}),this.implementations.registerErrorHandler(async(o,c,d)=>{o instanceof S||b.get().log.error({error:o},"Uncaught error in route handler");const i=o instanceof T?new y({body:o.serializedErrors,status:o.statusCode}):new y({body:[{message:"Something went wrong",data:o.message}],status:k.BadRequest});return await this.implementations.handleResponse(d,i)}),await Promise.all(this.#t.map(o=>o()));const p=await this.implementations.start(r);return p&&b.get().log.info(`${s.id}(${n.name}) service listening on port ${r}`),p}}export{Y as Server};
|
|
2
2
|
//# sourceMappingURL=base.min.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tInstance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"],"mappings":"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","names":["SocketServer","supertest","PipeError","v","EquippedError","NotFoundError","RequestError","Instance","pipeErrorToValidationError","requestLocalStorage","responseLocalStorage","OpenApi","Response","SocketEmitter","Methods","StatusCodes","errorsSchemas","value","key","Server","server","config","implementations","#openapi","socketInstance","#queue","#routesByKey","m","routers","router","routes","route","method","path","schema","onError","middlewares","validateRequest","validateResponse","jsonSchema","#resolveSchema","req","res","request","middleware","rawRes","response","error","rawResponse","defaultStatusCode","defaultContentType","status","contentType","requestPipeDefs","responsePipeDefs","def","pipe","pipeRecords","input","p","r","record","requestPipe","responsePipe","validity","val","port","instance","app","_","cb","started"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tif (!(error instanceof EquippedError)) Instance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"],"mappings":"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","names":["SocketServer","supertest","PipeError","v","EquippedError","NotFoundError","RequestError","Instance","pipeErrorToValidationError","requestLocalStorage","responseLocalStorage","OpenApi","Response","SocketEmitter","Methods","StatusCodes","errorsSchemas","value","key","Server","server","config","implementations","#openapi","socketInstance","#queue","#routesByKey","m","routers","router","routes","route","method","path","schema","onError","middlewares","validateRequest","validateResponse","jsonSchema","#resolveSchema","req","res","request","middleware","rawRes","response","error","rawResponse","defaultStatusCode","defaultContentType","status","contentType","requestPipeDefs","responsePipeDefs","def","pipe","pipeRecords","input","p","r","record","requestPipe","responsePipe","validity","val","port","instance","app","_","cb","started"]}
|
|
@@ -183,7 +183,7 @@ class Server {
|
|
|
183
183
|
throw new NotFoundError(`Route ${request.path} not found`);
|
|
184
184
|
});
|
|
185
185
|
this.implementations.registerErrorHandler(async (error, _, res) => {
|
|
186
|
-
Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
186
|
+
if (!(error instanceof EquippedError)) Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
187
187
|
const response = error instanceof RequestError ? new Response({
|
|
188
188
|
body: error.serializedErrors,
|
|
189
189
|
status: error.statusCode
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tInstance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/server/impls/base.ts"],"sourcesContent":["import type http from 'node:http'\n\nimport { type CorsOptions } from 'cors'\nimport { Server as SocketServer } from 'socket.io'\nimport supertest from 'supertest'\nimport { type Pipe, PipeError, v } from 'valleyed'\n\nimport { EquippedError, NotFoundError, RequestError } from '../../errors'\nimport { Instance } from '../../instance'\nimport { pipeErrorToValidationError } from '../../validations'\nimport { requestLocalStorage, responseLocalStorage } from '../../validations/valleyed'\nimport { OpenApi, type OpenApiSchemaDef } from '../openapi'\nimport type { ServerConfig } from '../pipes'\nimport { type Request, Response } from '../requests'\nimport { Router } from '../routes'\nimport { SocketEmitter } from '../sockets'\nimport { Methods, type MethodsEnum, type Route, type RouteDef, StatusCodes } from '../types'\n\ntype RequestValidator = (req: Request<any>) => Promise<Request<any>>\ntype ResponseValidator = (res: Response<any>) => Promise<Response<any>>\n\nconst errorsSchemas = Object.entries(StatusCodes)\n\t.filter(([, value]) => value > 399)\n\t.map(([key, value]) => ({\n\t\tstatus: value,\n\t\tcontentType: 'application/json',\n\t\tpipe: v.meta(v.array(v.object({ message: v.string(), field: v.optional(v.string()) })), {\n\t\t\t$refId: `Errors.${key}Response`,\n\t\t\tdescription: `${key} Response`,\n\t\t}) as Pipe<any, any>,\n\t}))\n\nexport abstract class Server<Req = any, Res = any> {\n\t#queue: (() => void | Promise<void>)[] = []\n\t#routesByKey = new Map<string, boolean>()\n\t#openapi: OpenApi\n\tsocket: SocketEmitter\n\tprotected server: http.Server\n\tprotected get cors() {\n\t\treturn {\n\t\t\torigin: this.config.cors?.origin,\n\t\t\tmethods: (this.config.cors?.methods ?? Object.values(Methods)).filter((m) => m !== Methods.options).map((m) => m.toUpperCase()),\n\t\t\tcredentials: this.config.cors?.credentials,\n\t\t} satisfies CorsOptions\n\t}\n\n\tconstructor(\n\t\tserver: http.Server,\n\t\tprivate config: ServerConfig,\n\t\tprivate implementations: {\n\t\t\tparseRequest: (req: Req) => Promise<Request<any>>\n\t\t\thandleResponse: (res: Res, response: Response<any>) => Promise<void>\n\t\t\tregisterRoute: (method: MethodsEnum, path: string, cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterErrorHandler: (cb: (error: Error, req: Req, res: Res) => Promise<void>) => void\n\t\t\tregisterNotFoundHandler: (cb: (req: Req, res: Res) => Promise<void>) => void\n\t\t\tstart: (port: number) => Promise<boolean>\n\t\t},\n\t) {\n\t\tthis.server = server\n\t\tthis.#openapi = new OpenApi(config)\n\t\tconst socketInstance = new SocketServer(server, { cors: this.cors })\n\t\tthis.socket = new SocketEmitter(socketInstance, config)\n\t\tthis.addRouter(this.#openapi.router())\n\t}\n\n\taddRouter(...routers: Router<any>[]) {\n\t\trouters.map((router) => router.routes).forEach((routes) => this.addRoute(...routes))\n\t}\n\n\taddRoute<T extends RouteDef>(...routes: Route<T>[]) {\n\t\troutes.forEach((route) => {\n\t\t\tthis.#queue.push(async () => {\n\t\t\t\tconst { method, path, schema = {}, onError, middlewares = [] } = route\n\n\t\t\t\tconst key = `(${method.toUpperCase()}) ${this.#openapi.cleanPath(path)}`\n\t\t\t\tif (this.#routesByKey.get(key))\n\t\t\t\t\tthrow new EquippedError(`Route key ${key} already registered. All route keys must be unique`, { route, key })\n\n\t\t\t\tmiddlewares.forEach((m) => m.onSetup?.(route as any))\n\t\t\t\tonError?.onSetup?.(route as any)\n\n\t\t\t\tconst { validateRequest, validateResponse, jsonSchema } = this.#resolveSchema(method, schema)\n\n\t\t\t\tthis.#routesByKey.set(key, true)\n\t\t\t\tawait this.#openapi.register(route, jsonSchema)\n\t\t\t\tthis.implementations.registerRoute(method, this.#openapi.cleanPath(path), async (req: Req, res: Res) => {\n\t\t\t\t\tconst request = await validateRequest(await this.implementations.parseRequest(req))\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (const middleware of middlewares) await middleware.cb(request, this.config)\n\t\t\t\t\t\tconst rawRes = await route.handler(request, this.config)\n\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\trawRes instanceof Response\n\t\t\t\t\t\t\t\t? rawRes\n\t\t\t\t\t\t\t\t: new Response({ body: rawRes, status: StatusCodes.Ok, headers: {}, piped: false })\n\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (onError?.cb) {\n\t\t\t\t\t\t\tconst rawResponse = await onError.cb(request, this.config, error as Error)\n\t\t\t\t\t\t\tconst response =\n\t\t\t\t\t\t\t\trawResponse instanceof Response\n\t\t\t\t\t\t\t\t\t? rawResponse\n\t\t\t\t\t\t\t\t\t: new Response({ body: rawResponse, status: StatusCodes.BadRequest, headers: {} })\n\t\t\t\t\t\t\treturn await this.implementations.handleResponse(res, await validateResponse(response))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t#resolveSchema(method: MethodsEnum, schema: RouteDef) {\n\t\tconst defaultStatusCode = schema?.defaultStatusCode ?? StatusCodes.Ok\n\t\tconst defaultContentType = schema?.defaultContentType ?? 'application/json'\n\t\tlet status = defaultStatusCode\n\t\tlet contentType = defaultContentType\n\t\tconst jsonSchema: OpenApiSchemaDef = { response: {}, request: {} }\n\t\tconst requestPipeDefs: Pick<RouteDef, 'body' | 'headers' | 'query' | 'params' | 'cookies'> = {}\n\t\tconst responsePipeDefs: Pick<RouteDef, 'response' | 'responseHeaders' | 'responseCookies'> = {}\n\n\t\tconst defs: {\n\t\t\tkey: Exclude<keyof RouteDef, `default${string}` | 'context'>\n\t\t\ttype: keyof OpenApiSchemaDef\n\t\t\tskip?: boolean\n\t\t}[] = [\n\t\t\t{ key: 'params', type: 'request' },\n\t\t\t{ key: 'headers', type: 'request' },\n\t\t\t{ key: 'cookies', type: 'request' },\n\t\t\t{ key: 'query', type: 'request' },\n\t\t\t{ key: 'body', type: 'request', skip: !(<MethodsEnum[]>[Methods.post, Methods.put, Methods.patch]).includes(method) },\n\t\t\t{ key: 'response', type: 'response' },\n\t\t\t{ key: 'responseHeaders', type: 'response' },\n\t\t\t{ key: 'responseCookies', type: 'response' },\n\t\t]\n\t\tdefs.forEach((def) => {\n\t\t\tconst pipe = schema[def.key] ?? v.any()\n\t\t\tif (def.skip) return\n\n\t\t\tif (def.type === 'request') {\n\t\t\t\trequestPipeDefs[def.key] = pipe\n\t\t\t\tjsonSchema.request[def.key as keyof typeof jsonSchema.request] = v.schema(pipe)\n\t\t\t}\n\t\t\tif (def.type === 'response') {\n\t\t\t\tconst pipeRecords = errorsSchemas.concat({ status: defaultStatusCode, contentType, pipe })\n\t\t\t\tresponsePipeDefs[def.key] = v.any().pipe((input) => {\n\t\t\t\t\tconst p = pipeRecords.find((r) => r.status === status)?.pipe\n\t\t\t\t\tif (!p) throw PipeError.root(`schema not defined for status code: ${status}`, input)\n\t\t\t\t\treturn v.assert(p, input)\n\t\t\t\t})\n\t\t\t\tjsonSchema.response[def.key as keyof typeof jsonSchema.response] = pipeRecords.map((record) => ({\n\t\t\t\t\tstatus: record.status,\n\t\t\t\t\tcontentType: record.contentType,\n\t\t\t\t\tschema: v.schema(record.pipe),\n\t\t\t\t}))\n\t\t\t}\n\t\t})\n\t\tconst requestPipe = v.object(requestPipeDefs)\n\t\tv.compile(requestPipe, { allErrors: true })\n\t\tconst responsePipe = v.object(responsePipeDefs)\n\t\tv.compile(responsePipe, { allErrors: true })\n\t\tconst validateRequest: RequestValidator = async (request) => {\n\t\t\tif (!Object.keys(requestPipeDefs)) return request\n\t\t\tconst validity = requestLocalStorage.run(request, () =>\n\t\t\t\tv.validate(requestPipe, {\n\t\t\t\t\tparams: request.params,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tquery: request.query,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t\tcookies: request.cookies,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\trequest.params = validity.value.params!\n\t\t\trequest.headers = validity.value.headers!\n\t\t\trequest.query = validity.value.query!\n\t\t\trequest.body = validity.value.body!\n\t\t\trequest.cookies = validity.value.cookies!\n\t\t\treturn request\n\t\t}\n\t\tconst validateResponse: ResponseValidator = async (response) => {\n\t\t\tif (!Object.keys(responsePipeDefs)) return response\n\t\t\tstatus = response.status\n\t\t\tcontentType = response.contentType\n\n\t\t\tconst validity = responseLocalStorage.run(response, () =>\n\t\t\t\tv.validate(responsePipe, {\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseCookies: Object.fromEntries(Object.entries(response.cookies).map(([key, val]) => [key, val.value] as const)),\n\t\t\t\t\tresponse: response.body,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!validity.valid) throw pipeErrorToValidationError(validity.error)\n\t\t\tresponse.body = validity.value.response!\n\t\t\tresponse.headers = validity.value.responseHeaders!\n\t\t\tresponse.cookieValues = validity.value.responseCookies!\n\t\t\treturn response\n\t\t}\n\t\treturn {\n\t\t\tjsonSchema,\n\t\t\tvalidateRequest,\n\t\t\tvalidateResponse,\n\t\t}\n\t}\n\n\ttest() {\n\t\treturn supertest(this.server)\n\t}\n\n\tasync start() {\n\t\tconst port = this.config.port\n\t\tconst instance = Instance.get()\n\t\tconst { app } = instance.settings\n\t\tif (this.config.healthPath)\n\t\t\tthis.addRoute({\n\t\t\t\tmethod: Methods.get,\n\t\t\t\tpath: this.config.healthPath,\n\t\t\t\thandler: async (req) =>\n\t\t\t\t\treq.res({\n\t\t\t\t\t\tbody: `${instance.id}(${app.name}) service running`,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t}),\n\t\t\t})\n\n\t\tthis.implementations.registerNotFoundHandler(async (req) => {\n\t\t\tconst request = await this.implementations.parseRequest(req)\n\t\t\tthrow new NotFoundError(`Route ${request.path} not found`)\n\t\t})\n\t\tthis.implementations.registerErrorHandler(async (error, _, res) => {\n\t\t\tif (!(error instanceof EquippedError)) Instance.get().log.error({ error }, 'Uncaught error in route handler')\n\t\t\tconst response =\n\t\t\t\terror instanceof RequestError\n\t\t\t\t\t? new Response({\n\t\t\t\t\t\t\tbody: error.serializedErrors,\n\t\t\t\t\t\t\tstatus: error.statusCode,\n\t\t\t\t\t\t})\n\t\t\t\t\t: new Response({\n\t\t\t\t\t\t\tbody: [{ message: 'Something went wrong', data: error.message }],\n\t\t\t\t\t\t\tstatus: StatusCodes.BadRequest,\n\t\t\t\t\t\t})\n\t\t\treturn await this.implementations.handleResponse(res, response)\n\t\t})\n\n\t\tawait Promise.all(this.#queue.map((cb) => cb()))\n\t\tconst started = await this.implementations.start(port)\n\t\tif (started) Instance.get().log.info(`${instance.id}(${app.name}) service listening on port ${port}`)\n\t\treturn started\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import c from"axios";import g from"jsonwebtoken";import
|
|
1
|
+
import c from"axios";import g from"jsonwebtoken";import h from"jwks-rsa";import{EquippedError as a}from "../errors/index.min.mjs";const f=async t=>{const i=`https://oauth2.googleapis.com/tokeninfo?id_token=${t}`,{data:e}=await c.get(i).catch(s=>{throw new a("Failed to sign in with google",{idToken:t},s)});return e.first_name=e.given_name,e.last_name=e.family_name,e},d=async t=>{try{const i="https://appleid.apple.com",e=g.decode(t,{complete:!0});if(!e?.header)throw new a("Missing JWT header",{idToken:t,json:e});const{kid:s,alg:n}=e.header,o=await h({jwksUri:`${i}/auth/keys`,cache:!0}).getSigningKey(s).then(l=>l.getPublicKey()).catch(()=>null);if(!o)throw new a("no publicKey",{idToken:t,publicKey:o,json:e});const r=g.verify(t,o,{algorithms:[n]});if(!r)throw new a("no data",{idToken:t,data:r});if(r.iss!==i)throw new a("iss doesnt match",{idToken:t,data:r});if(r.exp*1e3<Date.now())throw new a("expired idToken",{idToken:t,data:r});return r}catch(i){throw new a("Failed to sign in with apple",{idToken:t},i)}},y=async(t,i=[])=>{i=[...new Set([...i,"name","picture","email"])];const{data:e}=await c.request({method:"get",url:"https://graph.facebook.com/v15.0/me",params:{fields:i.join(","),access_token:t}}).catch(n=>{throw new a("Failed to sign in with facebook",{accessToken:t,fields:i},n)});if(!i.every(n=>n in e))throw new a("Incomplete scope for access token",{accessToken:t,fields:i,data:e});return e.email_verified="true",e};export{d as signinWithApple,y as signinWithFacebook,f as signinWithGoogle};
|
|
2
2
|
//# sourceMappingURL=authProviders.min.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new
|
|
1
|
+
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new EquippedError('Missing JWT header', { idToken, json })\n\t\tconst { kid, alg } = json.header\n\t\tconst publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true })\n\t\t\t.getSigningKey(kid)\n\t\t\t.then((key) => key.getPublicKey())\n\t\t\t.catch(() => null)\n\t\tif (!publicKey) throw new EquippedError('no publicKey', { idToken, publicKey, json })\n\t\tconst data = jwt.verify(idToken, publicKey, { algorithms: [alg as any] }) as Record<string, any>\n\t\tif (!data) throw new EquippedError('no data', { idToken, data })\n\t\tif (data.iss !== APPLE_BASE) throw new EquippedError('iss doesnt match', { idToken, data })\n\t\tif (data.exp * 1000 < Date.now()) throw new EquippedError('expired idToken', { idToken, data })\n\t\t// TODO: Find out how to get profile data from api\n\t\treturn data as {\n\t\t\temail?: string\n\t\t\tsub: string\n\t\t\temail_verified?: 'true' | 'false'\n\t\t\tis_private_email?: 'true' | 'false'\n\t\t} & Record<string, any>\n\t} catch (err) {\n\t\tthrow new EquippedError('Failed to sign in with apple', { idToken }, err)\n\t}\n}\n\nexport const signinWithFacebook = async (accessToken: string, fields = [] as string[]) => {\n\tfields = [...new Set([...fields, 'name', 'picture', 'email'])]\n\tconst { data } = await axios\n\t\t.request({\n\t\t\tmethod: 'get',\n\t\t\turl: 'https://graph.facebook.com/v15.0/me',\n\t\t\tparams: {\n\t\t\t\tfields: fields.join(','),\n\t\t\t\taccess_token: accessToken,\n\t\t\t},\n\t\t})\n\t\t.catch((err) => {\n\t\t\tthrow new EquippedError('Failed to sign in with facebook', { accessToken, fields }, err)\n\t\t})\n\tconst isValidData = fields.every((key) => key in data)\n\tif (!isValidData) throw new EquippedError('Incomplete scope for access token', { accessToken, fields, data })\n\tdata.email_verified = 'true'\n\treturn data as {\n\t\tid: string\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tname: string\n\t\tpicture: {\n\t\t\tdata: { height: number; is_silhouette: boolean; url: string; width: number }\n\t\t}\n\t} & Record<string, any>\n}\n"],"mappings":"AAAA,OAAOA,MAAW,QAClB,OAAOC,MAAS,eAChB,OAAOC,MAAgB,WAEvB,OAAS,iBAAAC,MAAqB,YAEvB,MAAMC,EAAmB,MAAOC,GAAoB,CAC1D,MAAMC,EAAU,oDAAoDD,CAAO,GACrE,CAAE,KAAAE,CAAK,EAAI,MAAMP,EAAM,IAAIM,CAAO,EAAE,MAAOE,GAAQ,CACxD,MAAM,IAAIL,EAAc,gCAAiC,CAAE,QAAAE,CAAQ,EAAGG,CAAG,CAC1E,CAAC,EACD,OAAAD,EAAK,WAAaA,EAAK,WACvBA,EAAK,UAAYA,EAAK,YACfA,CAQR,EAEaE,EAAkB,MAAOJ,GAAoB,CACzD,GAAI,CACH,MAAMK,EAAa,4BACbC,EAAOV,EAAI,OAAOI,EAAS,CAAE,SAAU,EAAK,CAAC,EACnD,GAAI,CAACM,GAAM,OAAQ,MAAM,IAAIR,EAAc,qBAAsB,CAAE,QAAAE,EAAS,KAAAM,CAAK,CAAC,EAClF,KAAM,CAAE,IAAAC,EAAK,IAAAC,CAAI,EAAIF,EAAK,OACpBG,EAAY,MAAMZ,EAAW,CAAE,QAAS,GAAGQ,CAAU,aAAc,MAAO,EAAK,CAAC,EACpF,cAAcE,CAAG,EACjB,KAAMG,GAAQA,EAAI,aAAa,CAAC,EAChC,MAAM,IAAM,IAAI,EAClB,GAAI,CAACD,EAAW,MAAM,IAAIX,EAAc,eAAgB,CAAE,QAAAE,EAAS,UAAAS,EAAW,KAAAH,CAAK,CAAC,EACpF,MAAMJ,EAAON,EAAI,OAAOI,EAASS,EAAW,CAAE,WAAY,CAACD,CAAU,CAAE,CAAC,EACxE,GAAI,CAACN,EAAM,MAAM,IAAIJ,EAAc,UAAW,CAAE,QAAAE,EAAS,KAAAE,CAAK,CAAC,EAC/D,GAAIA,EAAK,MAAQG,EAAY,MAAM,IAAIP,EAAc,mBAAoB,CAAE,QAAAE,EAAS,KAAAE,CAAK,CAAC,EAC1F,GAAIA,EAAK,IAAM,IAAO,KAAK,IAAI,EAAG,MAAM,IAAIJ,EAAc,kBAAmB,CAAE,QAAAE,EAAS,KAAAE,CAAK,CAAC,EAE9F,OAAOA,CAMR,OAASC,EAAK,CACb,MAAM,IAAIL,EAAc,+BAAgC,CAAE,QAAAE,CAAQ,EAAGG,CAAG,CACzE,CACD,EAEaQ,EAAqB,MAAOC,EAAqBC,EAAS,CAAC,IAAkB,CACzFA,EAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAGA,EAAQ,OAAQ,UAAW,OAAO,CAAC,CAAC,EAC7D,KAAM,CAAE,KAAAX,CAAK,EAAI,MAAMP,EACrB,QAAQ,CACR,OAAQ,MACR,IAAK,sCACL,OAAQ,CACP,OAAQkB,EAAO,KAAK,GAAG,EACvB,aAAcD,CACf,CACD,CAAC,EACA,MAAOT,GAAQ,CACf,MAAM,IAAIL,EAAc,kCAAmC,CAAE,YAAAc,EAAa,OAAAC,CAAO,EAAGV,CAAG,CACxF,CAAC,EAEF,GAAI,CADgBU,EAAO,MAAOH,GAAQA,KAAOR,CAAI,EACnC,MAAM,IAAIJ,EAAc,oCAAqC,CAAE,YAAAc,EAAa,OAAAC,EAAQ,KAAAX,CAAK,CAAC,EAC5G,OAAAA,EAAK,eAAiB,OACfA,CASR","names":["axios","jwt","jwksClient","EquippedError","signinWithGoogle","idToken","authUrl","data","err","signinWithApple","APPLE_BASE","json","kid","alg","publicKey","key","signinWithFacebook","accessToken","fields"]}
|
|
@@ -15,7 +15,7 @@ const signinWithApple = async (idToken) => {
|
|
|
15
15
|
try {
|
|
16
16
|
const APPLE_BASE = "https://appleid.apple.com";
|
|
17
17
|
const json = jwt.decode(idToken, { complete: true });
|
|
18
|
-
if (!json?.header) throw new
|
|
18
|
+
if (!json?.header) throw new EquippedError("Missing JWT header", { idToken, json });
|
|
19
19
|
const { kid, alg } = json.header;
|
|
20
20
|
const publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true }).getSigningKey(kid).then((key) => key.getPublicKey()).catch(() => null);
|
|
21
21
|
if (!publicKey) throw new EquippedError("no publicKey", { idToken, publicKey, json });
|
|
@@ -41,7 +41,7 @@ const signinWithFacebook = async (accessToken, fields = []) => {
|
|
|
41
41
|
throw new EquippedError("Failed to sign in with facebook", { accessToken, fields }, err);
|
|
42
42
|
});
|
|
43
43
|
const isValidData = fields.every((key) => key in data);
|
|
44
|
-
if (!isValidData) throw new
|
|
44
|
+
if (!isValidData) throw new EquippedError("Incomplete scope for access token", { accessToken, fields, data });
|
|
45
45
|
data.email_verified = "true";
|
|
46
46
|
return data;
|
|
47
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new
|
|
1
|
+
{"version":3,"sources":["../../../src/utilities/authProviders.ts"],"sourcesContent":["import axios from 'axios'\nimport jwt from 'jsonwebtoken'\nimport jwksClient from 'jwks-rsa'\n\nimport { EquippedError } from '../errors'\n\nexport const signinWithGoogle = async (idToken: string) => {\n\tconst authUrl = `https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`\n\tconst { data } = await axios.get(authUrl).catch((err) => {\n\t\tthrow new EquippedError('Failed to sign in with google', { idToken }, err)\n\t})\n\tdata.first_name = data.given_name\n\tdata.last_name = data.family_name\n\treturn data as {\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tfirst_name: string\n\t\tlast_name: string\n\t\tpicture: string\n\t\tsub: string\n\t} & Record<string, any>\n}\n\nexport const signinWithApple = async (idToken: string) => {\n\ttry {\n\t\tconst APPLE_BASE = 'https://appleid.apple.com'\n\t\tconst json = jwt.decode(idToken, { complete: true })\n\t\tif (!json?.header) throw new EquippedError('Missing JWT header', { idToken, json })\n\t\tconst { kid, alg } = json.header\n\t\tconst publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true })\n\t\t\t.getSigningKey(kid)\n\t\t\t.then((key) => key.getPublicKey())\n\t\t\t.catch(() => null)\n\t\tif (!publicKey) throw new EquippedError('no publicKey', { idToken, publicKey, json })\n\t\tconst data = jwt.verify(idToken, publicKey, { algorithms: [alg as any] }) as Record<string, any>\n\t\tif (!data) throw new EquippedError('no data', { idToken, data })\n\t\tif (data.iss !== APPLE_BASE) throw new EquippedError('iss doesnt match', { idToken, data })\n\t\tif (data.exp * 1000 < Date.now()) throw new EquippedError('expired idToken', { idToken, data })\n\t\t// TODO: Find out how to get profile data from api\n\t\treturn data as {\n\t\t\temail?: string\n\t\t\tsub: string\n\t\t\temail_verified?: 'true' | 'false'\n\t\t\tis_private_email?: 'true' | 'false'\n\t\t} & Record<string, any>\n\t} catch (err) {\n\t\tthrow new EquippedError('Failed to sign in with apple', { idToken }, err)\n\t}\n}\n\nexport const signinWithFacebook = async (accessToken: string, fields = [] as string[]) => {\n\tfields = [...new Set([...fields, 'name', 'picture', 'email'])]\n\tconst { data } = await axios\n\t\t.request({\n\t\t\tmethod: 'get',\n\t\t\turl: 'https://graph.facebook.com/v15.0/me',\n\t\t\tparams: {\n\t\t\t\tfields: fields.join(','),\n\t\t\t\taccess_token: accessToken,\n\t\t\t},\n\t\t})\n\t\t.catch((err) => {\n\t\t\tthrow new EquippedError('Failed to sign in with facebook', { accessToken, fields }, err)\n\t\t})\n\tconst isValidData = fields.every((key) => key in data)\n\tif (!isValidData) throw new EquippedError('Incomplete scope for access token', { accessToken, fields, data })\n\tdata.email_verified = 'true'\n\treturn data as {\n\t\tid: string\n\t\temail: string\n\t\temail_verified: 'true' | 'false'\n\t\tname: string\n\t\tpicture: {\n\t\t\tdata: { height: number; is_silhouette: boolean; url: string; width: number }\n\t\t}\n\t} & Record<string, any>\n}\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,OAAO,SAAS;AAChB,OAAO,gBAAgB;AAEvB,SAAS,qBAAqB;AAEvB,MAAM,mBAAmB,OAAO,YAAoB;AAC1D,QAAM,UAAU,oDAAoD,OAAO;AAC3E,QAAM,EAAE,KAAK,IAAI,MAAM,MAAM,IAAI,OAAO,EAAE,MAAM,CAAC,QAAQ;AACxD,UAAM,IAAI,cAAc,iCAAiC,EAAE,QAAQ,GAAG,GAAG;AAAA,EAC1E,CAAC;AACD,OAAK,aAAa,KAAK;AACvB,OAAK,YAAY,KAAK;AACtB,SAAO;AAQR;AAEO,MAAM,kBAAkB,OAAO,YAAoB;AACzD,MAAI;AACH,UAAM,aAAa;AACnB,UAAM,OAAO,IAAI,OAAO,SAAS,EAAE,UAAU,KAAK,CAAC;AACnD,QAAI,CAAC,MAAM,OAAQ,OAAM,IAAI,cAAc,sBAAsB,EAAE,SAAS,KAAK,CAAC;AAClF,UAAM,EAAE,KAAK,IAAI,IAAI,KAAK;AAC1B,UAAM,YAAY,MAAM,WAAW,EAAE,SAAS,GAAG,UAAU,cAAc,OAAO,KAAK,CAAC,EACpF,cAAc,GAAG,EACjB,KAAK,CAAC,QAAQ,IAAI,aAAa,CAAC,EAChC,MAAM,MAAM,IAAI;AAClB,QAAI,CAAC,UAAW,OAAM,IAAI,cAAc,gBAAgB,EAAE,SAAS,WAAW,KAAK,CAAC;AACpF,UAAM,OAAO,IAAI,OAAO,SAAS,WAAW,EAAE,YAAY,CAAC,GAAU,EAAE,CAAC;AACxE,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,WAAW,EAAE,SAAS,KAAK,CAAC;AAC/D,QAAI,KAAK,QAAQ,WAAY,OAAM,IAAI,cAAc,oBAAoB,EAAE,SAAS,KAAK,CAAC;AAC1F,QAAI,KAAK,MAAM,MAAO,KAAK,IAAI,EAAG,OAAM,IAAI,cAAc,mBAAmB,EAAE,SAAS,KAAK,CAAC;AAE9F,WAAO;AAAA,EAMR,SAAS,KAAK;AACb,UAAM,IAAI,cAAc,gCAAgC,EAAE,QAAQ,GAAG,GAAG;AAAA,EACzE;AACD;AAEO,MAAM,qBAAqB,OAAO,aAAqB,SAAS,CAAC,MAAkB;AACzF,WAAS,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,QAAQ,QAAQ,WAAW,OAAO,CAAC,CAAC;AAC7D,QAAM,EAAE,KAAK,IAAI,MAAM,MACrB,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,QAAQ;AAAA,MACP,QAAQ,OAAO,KAAK,GAAG;AAAA,MACvB,cAAc;AAAA,IACf;AAAA,EACD,CAAC,EACA,MAAM,CAAC,QAAQ;AACf,UAAM,IAAI,cAAc,mCAAmC,EAAE,aAAa,OAAO,GAAG,GAAG;AAAA,EACxF,CAAC;AACF,QAAM,cAAc,OAAO,MAAM,CAAC,QAAQ,OAAO,IAAI;AACrD,MAAI,CAAC,YAAa,OAAM,IAAI,cAAc,qCAAqC,EAAE,aAAa,QAAQ,KAAK,CAAC;AAC5G,OAAK,iBAAiB;AACtB,SAAO;AASR;","names":[]}
|
|
@@ -183,7 +183,7 @@ class Server {
|
|
|
183
183
|
throw new NotFoundError(`Route ${request.path} not found`);
|
|
184
184
|
});
|
|
185
185
|
this.implementations.registerErrorHandler(async (error, _, res) => {
|
|
186
|
-
Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
186
|
+
if (!(error instanceof EquippedError)) Instance.get().log.error({ error }, "Uncaught error in route handler");
|
|
187
187
|
const response = error instanceof RequestError ? new Response({
|
|
188
188
|
body: error.serializedErrors,
|
|
189
189
|
status: error.statusCode
|
|
@@ -15,7 +15,7 @@ const signinWithApple = async (idToken) => {
|
|
|
15
15
|
try {
|
|
16
16
|
const APPLE_BASE = "https://appleid.apple.com";
|
|
17
17
|
const json = jwt.decode(idToken, { complete: true });
|
|
18
|
-
if (!json?.header) throw new
|
|
18
|
+
if (!json?.header) throw new EquippedError("Missing JWT header", { idToken, json });
|
|
19
19
|
const { kid, alg } = json.header;
|
|
20
20
|
const publicKey = await jwksClient({ jwksUri: `${APPLE_BASE}/auth/keys`, cache: true }).getSigningKey(kid).then((key) => key.getPublicKey()).catch(() => null);
|
|
21
21
|
if (!publicKey) throw new EquippedError("no publicKey", { idToken, publicKey, json });
|
|
@@ -41,7 +41,7 @@ const signinWithFacebook = async (accessToken, fields = []) => {
|
|
|
41
41
|
throw new EquippedError("Failed to sign in with facebook", { accessToken, fields }, err);
|
|
42
42
|
});
|
|
43
43
|
const isValidData = fields.every((key) => key in data);
|
|
44
|
-
if (!isValidData) throw new
|
|
44
|
+
if (!isValidData) throw new EquippedError("Incomplete scope for access token", { accessToken, fields, data });
|
|
45
45
|
data.email_verified = "true";
|
|
46
46
|
return data;
|
|
47
47
|
};
|