@event-driven-io/emmett-expressjs 0.43.0-beta.14 → 0.43.0-beta.15
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/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["ProblemDocument","EmmettError","ProblemDocument","EmmettError"],"sources":["../src/middlewares/problemDetailsMiddleware.ts","../src/application.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue) ? getWeakETagValue(eTagValue) : eTagValue;\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body:\n 'createdId' in options\n ? { id: options.createdId, ...(options.body ?? {}) }\n : options.body,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const sendNoContent = (\n response: Response,\n options?: NoContentHttpResponseOptions,\n): void => send(response, 204, options);\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport {\n EmmettError,\n getInMemoryEventStore,\n type EventStore,\n type InMemoryEventStore,\n} from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nfunction apiE2ESpecificationFor<\n Store extends EventStore = InMemoryEventStore,\n>(options: {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiE2ESpecification;\n/** @deprecated Use `ApiE2ESpecification.for({ getEventStore, getApplication })` instead */\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiE2ESpecification;\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n optionsOrGetApplication:\n | (() => Store)\n | {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n },\n getApplication?: (eventStore: Store) => Application,\n): ApiE2ESpecification {\n const resolveApplication = (): Application => {\n if (typeof optionsOrGetApplication === 'function' && getApplication) {\n const eventStore = optionsOrGetApplication();\n return getApplication(eventStore);\n }\n\n if (typeof optionsOrGetApplication !== 'object') {\n throw new EmmettError(\n 'Invalid arguments provided to apiE2ESpecificationFor. Expected either an options object or a getEventStore function and getApplication function.',\n );\n }\n\n const eventStore =\n optionsOrGetApplication.getEventStore?.() ?? getInMemoryEventStore();\n return optionsOrGetApplication.getApplication(eventStore as Store);\n };\n\n return (...givenRequests: TestRequest[]) => {\n const application = resolveApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (verify: ApiE2ESpecificationAssert): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n}\n\nexport const ApiE2ESpecification = {\n for: apiE2ESpecificationFor,\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(options: {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiSpecification<EventType>;\n/** @deprecated Use `ApiSpecification.for({ getEventStore, getApplication })` instead */\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiSpecification<EventType>;\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n optionsOrGetEventStore:\n | {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n }\n | (() => Store),\n getApplication?: (eventStore: Store) => Application,\n): ApiSpecification<EventType> {\n const resolveStoreAndApplication = (): {\n eventStore: ReturnType<typeof WrapEventStore>;\n application: Application;\n } => {\n if (typeof optionsOrGetEventStore === 'function') {\n const eventStore = WrapEventStore(optionsOrGetEventStore());\n return { eventStore, application: getApplication!(eventStore) };\n }\n const eventStore = WrapEventStore(optionsOrGetEventStore.getEventStore());\n return {\n eventStore,\n application: optionsOrGetEventStore.getApplication(eventStore),\n };\n };\n\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const { eventStore, application } = resolveStoreAndApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n}\n\nexport const ApiSpecification = {\n for: apiSpecificationFor,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAa,4BACV,cAEC,OACA,SACA,UACA,UACS;CACT,IAAI;AAEJ,KAAI,SAAU,kBAAiB,SAAS,OAAO,QAAQ;AAEvD,kBACE,kBAAkB,oCAAoC,MAAM;AAE9D,aAAY,UAAU,eAAe,QAAQ,EAAE,SAAS,gBAAgB,CAAC;;AAG7E,MAAa,uCACX,UACoB;CACpB,IAAI,aAAa;AAEjB,KACE,eAAe,+CACN,MAAM,UAAU,IACzB,MAAM,aAAa,OACnB,MAAM,YAAY,IAElB,cAAa,MAAM;AAGrB,QAAO,IAAIA,qCAAgB;EACzB,QAAQ,MAAM;EACd,QAAQ;EACT,CAAC;;;;;ACrBJ,MAAa,kBAAkB,YAAgC;CAC7D,MAAM,4BAA4B;CAElC,MAAM,EACJ,MACA,UACA,0BACA,uBACA,8BACA,oCACE;CAEJ,MAAM,8BAAiB;AAIvB,KAAI,IAAI,QAAQ,4BAA4B,MAAM;AAGlD,KAAI,CAAC,sBAAuB,KAAI,IAAI,gBAAQ,MAAM,CAAC;AAGnD,KAAI,CAAC,6BACH,KAAI,IACF,gBAAQ,WAAW,EACjB,UAAU,MACX,CAAC,CACH;AAEH,MAAK,MAAM,OAAO,KAChB,KAAI,OAAO;AAEb,KAAI,IAAI,OAAO;AAGf,KAAI,CAAC,gCACH,KAAI,IAAI,yBAAyB,SAAS,CAAC;AAE7C,QAAO;;AAOT,MAAa,YACX,KACA,UAA2B,EAAE,MAAM,KAAM,KACtC;CACH,MAAM,EAAE,SAAS;CACjB,MAAM,SAAS,aAAK,aAAa,IAAI;AAErC,QAAO,GAAG,mBAAmB;AAC3B,UAAQ,KAAK,sBAAsB;GACnC;AAEF,QAAO,OAAO,OAAO,KAAK;;;;;ACpE5B,MAAa,cAAc;CACzB,UAAU;CACV,cAAc;CACd,MAAM;CACP;AAKD,MAAa,gBAAgB;AAE7B,IAAkB,aAAX;AACL;AACA;AACA;;KACD;AAED,MAAa,cAAc,SAAiC;AAC1D,QAAO,cAAc,KAAK,KAAK;;AAGjC,MAAa,oBAAoB,SAAuB;CACtD,MAAM,SAAS,cAAc,KAAK,KAAK;AACvC,KAAI,WAAW,QAAQ,OAAO,WAAW,EACvC,OAAM,IAAIC,oCAAY;EACpB,WAAWA,oCAAY,MAAM;EAC7B;EACD,CAAC;AAEJ,QAAO,OAAO;;AAGhB,MAAa,cAAc,UAA8C;AACvE,QAAO,MAAM,MAAM;;AAGrB,MAAa,sBAAsB,YAA2B;CAC5D,MAAM,OAAO,QAAQ,QAAQ,YAAY;AAEzC,KAAI,SAAS,OACX,OAAM,IAAIA,oCAAY;EACpB,WAAWA,oCAAY,MAAM;EAC7B;EACD,CAAC;AAGJ,QAAO;;AAGT,MAAa,yBAAyB,YAA2B;CAC/D,MAAM,OAAO,QAAQ,QAAQ,YAAY;AAEzC,KAAI,SAAS,OACX,OAAM,IAAIA,oCAAY;EACpB,WAAWA,oCAAY,MAAM;EAC7B;EACD,CAAC;AAGJ,QAAQ,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK;;AAG1C,MAAa,WAAW,UAAoB,SAAqB;AAC/D,UAAS,UAAU,YAAY,MAAM,KAAK;;AAG5C,MAAa,2BAA2B,YAA6B;CACnE,MAAM,YAAkB,mBAAmB,QAAQ;AAEnD,QAAO,WAAW,UAAU,GAAG,iBAAiB,UAAU,GAAG;;;;;ACxD/D,MAAa,MACmB,WAC9B,OACE,SACA,UACA,UACkB;AAGlB,SAFoB,MAAM,QAAQ,QAAQ,OAAO,QAAQ,CAAC,EAEvC,SAAS;;AAIhC,MAAa,MACV,aACA,aAAuB;AACtB,MAAK,UAAU,KAAK,QAAQ;;AAGhC,MAAa,WACV,aACA,aAAuB;AACtB,aAAY,UAAU,QAAQ;;AAGlC,MAAa,YACV,aACA,aAAuB;AACtB,cAAa,UAAU,QAAQ;;AAGnC,MAAa,aACX,YACiB,aAAa,KAAK,QAAQ;AAE7C,MAAa,gBACV,YAAoB,aACpB,aAAuB;AACtB,MAAK,UAAU,YAAY,QAAQ;;AAOvC,MAAa,cACX,YACiB,YAAY,KAAK,QAAQ;AAE5C,MAAa,aAAa,YACxB,YAAY,KAAK,QAAQ;AAE3B,MAAa,YAAY,YACvB,YAAY,KAAK,QAAQ;AAE3B,MAAa,YAAY,YACvB,YAAY,KAAK,QAAQ;AAE3B,MAAa,sBACX,YACiB,YAAY,KAAK,QAAQ;AAE5C,MAAa,eACV,YAAoB,aACpB,aAAuB;AACtB,aAAY,UAAU,YAAY,QAAQ;;;;;ACvE9C,MAAa,6BAAkD,EAAE;AAYjE,MAAa,oCAAgE,EAC3E,gBAAgB,kBACjB;AAaD,MAAa,eACX,UACA,EAAE,MAAM,GAAG,cAEX,KAAK,UAAU,KAAK;CAClB,UACE,SAAS,UACL,QAAQ,MACR,GAAG,SAAS,IAAI,IAAI,GAAG,QAAQ;CACrC,MACE,eAAe,UACX;EAAE,IAAI,QAAQ;EAAW,GAAI,QAAQ,QAAQ,EAAE;EAAG,GAClD,QAAQ;CACd;CACD,CAAC;AAMJ,MAAa,gBACX,UACA,YACS,KAAK,UAAU,KAAK,QAAQ;AAIvC,MAAa,iBACX,UACA,YACS,KAAK,UAAU,KAAK,QAAQ;AAEvC,MAAa,QACX,UACA,YACA,YACS;CACT,MAAM,EAAE,UAAU,MAAM,SAAS,WAAW;AAE5C,KAAI,KAAM,SAAQ,UAAU,KAAK;AACjC,KAAI,SAAU,UAAS,UAAU,YAAY,SAAS;AAEtD,KAAI,MAAM;AACR,WAAS,aAAa;AACtB,WAAS,KAAK,KAAK;OAEnB,UAAS,WAAW,WAAW;;AAInC,MAAa,eACX,UACA,YACA,YACS;AACT,WAAU,WAAW;CAErB,MAAM,EAAE,UAAU,SAAS;CAE3B,MAAM,iBACJ,aAAa,UACT,QAAQ,UACR,IAAIC,qCAAgB;EAClB,QAAQ,QAAQ;EAChB,QAAQ;EACT,CAAC;AAGR,KAAI,KAAM,SAAQ,UAAU,KAAK;AACjC,KAAI,SAAU,UAAS,UAAU,YAAY,SAAS;AAEtD,UAAS,UAAU,gBAAgB,2BAA2B;AAE9D,UAAS,aAAa;AACtB,UAAS,KAAK,eAAe;;;;;AClF/B,SAAS,uBACP,yBAMA,gBACqB;CACrB,MAAM,2BAAwC;AAC5C,MAAI,OAAO,4BAA4B,cAAc,eAEnD,QAAO,eADY,yBAAyB,CACX;AAGnC,MAAI,OAAO,4BAA4B,SACrC,OAAM,IAAIC,oCACR,mJACD;EAGH,MAAM,aACJ,wBAAwB,iBAAiB,wDAA2B;AACtE,SAAO,wBAAwB,eAAe,WAAoB;;AAGpE,SAAQ,GAAG,kBAAiC;EAC1C,MAAM,cAAc,oBAAoB;AAExC,SAAO,EACL,OAAO,iBAA8B;GACnC,MAAM,SAAS,YAAY;AACzB,SAAK,MAAM,aAAa,cACtB,OAAM,iCAAoB,YAAY,CAAC;AAGzC,WAAO,oCAAuB,YAAY,CAAC;;AAG7C,UAAO,EACL,MAAM,OAAO,WAAqD;IAChE,MAAM,WAAW,MAAM,QAAQ;AAE/B,WAAO,SAAS,cAAc;AAG5B,SAFkB,UAAU,SAAS,KAEnB,MAAO,gBAAO,MAAM;MACtC;MAEL;KAEJ;;;AAIL,MAAa,sBAAsB,EACjC,KAAK,wBACN;;;;ACrED,MAAa,kBACX,UACA,WAC+B;AAC/B,QAAO,CAAC,UAAU,OAAO;;AAc3B,MAAa,UACX,UACA,WAC+B;AAC/B,QAAO,CAAC,UAAU,OAAO;;AAG3B,MAAa,mBACX,UACA,WAC+B;AAC/B,QAAO,CAAC,UAAU,OAAO;;AAG3B,MAAa,kBAET,YACA,aAED,aAA6B;CAC5B,MAAM,EAAE,MAAM,YAAY,WAAW,EAAE;AACvC,0CAAY,YAAY,SAAS,YAAY,8BAA8B;AAC3E,KAAI,KAAM,4CAAc,SAAS,MAAM,KAAK;AAC5C,KAAI,QAAS,4CAAc,SAAS,SAAS,QAAQ;;AAGzD,MAAa,eACX,WACA,mBAEA,eACE,WACA,iBAAiB,EAAE,MAAM,gBAAgB,GAAG,OAC7C;AA6BH,SAAS,oBAIP,wBAMA,gBAC6B;CAC7B,MAAM,mCAGD;AACH,MAAI,OAAO,2BAA2B,YAAY;GAChD,MAAM,yDAA4B,wBAAwB,CAAC;AAC3D,UAAO;IAAE;IAAY,aAAa,eAAgB,WAAW;IAAE;;EAEjE,MAAM,yDAA4B,uBAAuB,eAAe,CAAC;AACzE,SAAO;GACL;GACA,aAAa,uBAAuB,eAAe,WAAW;GAC/D;;AAGH,SAAQ,GAAG,iBAA+C;EACxD,MAAM,EAAE,YAAY,gBAAgB,4BAA4B;AAEhE,SAAO,EACL,OAAO,iBAA8B;GACnC,MAAM,SAAS,YAAY;AACzB,SAAK,MAAM,CAAC,YAAY,WAAW,aACjC,OAAM,WAAW,MAAM,YAAY,OAAO;AAG5C,WAAO,oCAAuB,YAAY,CAAC;;AAG7C,UAAO,EACL,MAAM,OACJ,WACkB;IAClB,MAAM,WAAW,MAAM,QAAQ;AAE/B,QAAI,OAAO,WAAW,YAGpB;SAFkB,OAAO,SAAS,KAEhB,MAAO,2CAAa;eAC7B,MAAM,QAAQ,OAAO,EAAE;KAChC,MAAM,CAAC,OAAO,GAAG,QAAQ;AAEzB,SAAI,OAAO,UAAU,YAGnB;UAFkB,MAAM,SAAS,KAEf,MAAO,2CAAa;;KAGxC,MAAM,SAAS,OAAO,UAAU,aAAa,OAAO;AAEpD,gDACE,MAAM,KAAK,WAAW,eAAe,QAAQ,CAAC,EAC9C,OACD;;MAGN;KAEJ;;;AAIL,MAAa,mBAAmB,EAC9B,KAAK,qBACN"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["ProblemDocument","EmmettError","ProblemDocument","EmmettError"],"sources":["../src/middlewares/problemDetailsMiddleware.ts","../src/application.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue) ? getWeakETagValue(eTagValue) : eTagValue;\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body:\n 'createdId' in options\n ? { id: options.createdId, ...(options.body ?? {}) }\n : options.body,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const sendNoContent = (\n response: Response,\n options?: NoContentHttpResponseOptions,\n): void => send(response, 204, options);\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport {\n EmmettError,\n getInMemoryEventStore,\n type EventStore,\n type InMemoryEventStore,\n} from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nfunction apiE2ESpecificationFor<\n Store extends EventStore = InMemoryEventStore,\n>(options: {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiE2ESpecification;\n/** @deprecated Use `ApiE2ESpecification.for({ getEventStore, getApplication })` instead */\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiE2ESpecification;\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n optionsOrGetApplication:\n | (() => Store)\n | {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n },\n getApplication?: (eventStore: Store) => Application,\n): ApiE2ESpecification {\n const resolveApplication = (): Application => {\n if (typeof optionsOrGetApplication === 'function' && getApplication) {\n const eventStore = optionsOrGetApplication();\n return getApplication(eventStore);\n }\n\n if (typeof optionsOrGetApplication !== 'object') {\n throw new EmmettError(\n 'Invalid arguments provided to apiE2ESpecificationFor. Expected either an options object or a getEventStore function and getApplication function.',\n );\n }\n\n const eventStore =\n optionsOrGetApplication.getEventStore?.() ?? getInMemoryEventStore();\n return optionsOrGetApplication.getApplication(eventStore as Store);\n };\n\n return (...givenRequests: TestRequest[]) => {\n const application = resolveApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (verify: ApiE2ESpecificationAssert): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n}\n\nexport const ApiE2ESpecification = {\n for: apiE2ESpecificationFor,\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(options: {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiSpecification<EventType>;\n/** @deprecated Use `ApiSpecification.for({ getEventStore, getApplication })` instead */\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiSpecification<EventType>;\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n optionsOrGetEventStore:\n | {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n }\n | (() => Store),\n getApplication?: (eventStore: Store) => Application,\n): ApiSpecification<EventType> {\n const resolveStoreAndApplication = (): {\n eventStore: ReturnType<typeof WrapEventStore>;\n application: Application;\n } => {\n if (typeof optionsOrGetEventStore === 'function') {\n const eventStore = WrapEventStore(optionsOrGetEventStore());\n return { eventStore, application: getApplication!(eventStore) };\n }\n const eventStore = WrapEventStore(optionsOrGetEventStore.getEventStore());\n return {\n eventStore,\n application: optionsOrGetEventStore.getApplication(eventStore),\n };\n };\n\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const { eventStore, application } = resolveStoreAndApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n}\n\nexport const ApiSpecification = {\n for: apiSpecificationFor,\n};\n"],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/middlewares/problemDetailsMiddleware.ts","../src/application.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue) ? getWeakETagValue(eTagValue) : eTagValue;\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body:\n 'createdId' in options\n ? { id: options.createdId, ...(options.body ?? {}) }\n : options.body,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const sendNoContent = (\n response: Response,\n options?: NoContentHttpResponseOptions,\n): void => send(response, 204, options);\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport {\n EmmettError,\n getInMemoryEventStore,\n type EventStore,\n type InMemoryEventStore,\n} from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nfunction apiE2ESpecificationFor<\n Store extends EventStore = InMemoryEventStore,\n>(options: {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiE2ESpecification;\n/** @deprecated Use `ApiE2ESpecification.for({ getEventStore, getApplication })` instead */\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiE2ESpecification;\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n optionsOrGetApplication:\n | (() => Store)\n | {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n },\n getApplication?: (eventStore: Store) => Application,\n): ApiE2ESpecification {\n const resolveApplication = (): Application => {\n if (typeof optionsOrGetApplication === 'function' && getApplication) {\n const eventStore = optionsOrGetApplication();\n return getApplication(eventStore);\n }\n\n if (typeof optionsOrGetApplication !== 'object') {\n throw new EmmettError(\n 'Invalid arguments provided to apiE2ESpecificationFor. Expected either an options object or a getEventStore function and getApplication function.',\n );\n }\n\n const eventStore =\n optionsOrGetApplication.getEventStore?.() ?? getInMemoryEventStore();\n return optionsOrGetApplication.getApplication(eventStore as Store);\n };\n\n return (...givenRequests: TestRequest[]) => {\n const application = resolveApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (verify: ApiE2ESpecificationAssert): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n}\n\nexport const ApiE2ESpecification = {\n for: apiE2ESpecificationFor,\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(options: {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiSpecification<EventType>;\n/** @deprecated Use `ApiSpecification.for({ getEventStore, getApplication })` instead */\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiSpecification<EventType>;\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n optionsOrGetEventStore:\n | {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n }\n | (() => Store),\n getApplication?: (eventStore: Store) => Application,\n): ApiSpecification<EventType> {\n const resolveStoreAndApplication = (): {\n eventStore: ReturnType<typeof WrapEventStore>;\n application: Application;\n } => {\n if (typeof optionsOrGetEventStore === 'function') {\n const eventStore = WrapEventStore(optionsOrGetEventStore());\n return { eventStore, application: getApplication!(eventStore) };\n }\n const eventStore = WrapEventStore(optionsOrGetEventStore.getEventStore());\n return {\n eventStore,\n application: optionsOrGetEventStore.getApplication(eventStore),\n };\n };\n\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const { eventStore, application } = resolveStoreAndApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n}\n\nexport const ApiSpecification = {\n for: apiSpecificationFor,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/middlewares/problemDetailsMiddleware.ts","../src/application.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue) ? getWeakETagValue(eTagValue) : eTagValue;\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body:\n 'createdId' in options\n ? { id: options.createdId, ...(options.body ?? {}) }\n : options.body,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const sendNoContent = (\n response: Response,\n options?: NoContentHttpResponseOptions,\n): void => send(response, 204, options);\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport {\n EmmettError,\n getInMemoryEventStore,\n type EventStore,\n type InMemoryEventStore,\n} from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nfunction apiE2ESpecificationFor<\n Store extends EventStore = InMemoryEventStore,\n>(options: {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiE2ESpecification;\n/** @deprecated Use `ApiE2ESpecification.for({ getEventStore, getApplication })` instead */\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiE2ESpecification;\nfunction apiE2ESpecificationFor<Store extends EventStore = InMemoryEventStore>(\n optionsOrGetApplication:\n | (() => Store)\n | {\n getEventStore?: () => Store;\n getApplication: (eventStore: Store) => Application;\n },\n getApplication?: (eventStore: Store) => Application,\n): ApiE2ESpecification {\n const resolveApplication = (): Application => {\n if (typeof optionsOrGetApplication === 'function' && getApplication) {\n const eventStore = optionsOrGetApplication();\n return getApplication(eventStore);\n }\n\n if (typeof optionsOrGetApplication !== 'object') {\n throw new EmmettError(\n 'Invalid arguments provided to apiE2ESpecificationFor. Expected either an options object or a getEventStore function and getApplication function.',\n );\n }\n\n const eventStore =\n optionsOrGetApplication.getEventStore?.() ?? getInMemoryEventStore();\n return optionsOrGetApplication.getApplication(eventStore as Store);\n };\n\n return (...givenRequests: TestRequest[]) => {\n const application = resolveApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (verify: ApiE2ESpecificationAssert): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n}\n\nexport const ApiE2ESpecification = {\n for: apiE2ESpecificationFor,\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(options: {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n}): ApiSpecification<EventType>;\n/** @deprecated Use `ApiSpecification.for({ getEventStore, getApplication })` instead */\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n): ApiSpecification<EventType>;\nfunction apiSpecificationFor<\n EventType extends Event = Event,\n Store extends EventStore = EventStore,\n>(\n optionsOrGetEventStore:\n | {\n getEventStore: () => Store;\n getApplication: (eventStore: Store) => Application;\n }\n | (() => Store),\n getApplication?: (eventStore: Store) => Application,\n): ApiSpecification<EventType> {\n const resolveStoreAndApplication = (): {\n eventStore: ReturnType<typeof WrapEventStore>;\n application: Application;\n } => {\n if (typeof optionsOrGetEventStore === 'function') {\n const eventStore = WrapEventStore(optionsOrGetEventStore());\n return { eventStore, application: getApplication!(eventStore) };\n }\n const eventStore = WrapEventStore(optionsOrGetEventStore.getEventStore());\n return {\n eventStore,\n application: optionsOrGetEventStore.getApplication(eventStore),\n };\n };\n\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const { eventStore, application } = resolveStoreAndApplication();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n}\n\nexport const ApiSpecification = {\n for: apiSpecificationFor,\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event-driven-io/emmett-expressjs",
|
|
3
|
-
"version": "0.43.0-beta.
|
|
3
|
+
"version": "0.43.0-beta.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Emmett - Event Sourcing development made simple",
|
|
6
6
|
"scripts": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dependencies": {},
|
|
50
50
|
"devDependencies": {},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@event-driven-io/emmett": "0.43.0-beta.
|
|
52
|
+
"@event-driven-io/emmett": "0.43.0-beta.15",
|
|
53
53
|
"@types/express": "^4.17.21",
|
|
54
54
|
"@types/supertest": "^7.2.0",
|
|
55
55
|
"express": "^4.22.1",
|