@xylabs/express 4.3.14

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.
Files changed (198) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +62 -0
  3. package/dist/node/AWS/getEnvFromAws.d.ts +2 -0
  4. package/dist/node/AWS/getEnvFromAws.d.ts.map +1 -0
  5. package/dist/node/AWS/index.d.ts +2 -0
  6. package/dist/node/AWS/index.d.ts.map +1 -0
  7. package/dist/node/Handler/StatusCodeHandlers/index.d.ts +2 -0
  8. package/dist/node/Handler/StatusCodeHandlers/index.d.ts.map +1 -0
  9. package/dist/node/Handler/StatusCodeHandlers/notImplemented.d.ts +3 -0
  10. package/dist/node/Handler/StatusCodeHandlers/notImplemented.d.ts.map +1 -0
  11. package/dist/node/Handler/asyncHandler.d.ts +11 -0
  12. package/dist/node/Handler/asyncHandler.d.ts.map +1 -0
  13. package/dist/node/Handler/errorToJsonHandler.d.ts +4 -0
  14. package/dist/node/Handler/errorToJsonHandler.d.ts.map +1 -0
  15. package/dist/node/Handler/index.d.ts +4 -0
  16. package/dist/node/Handler/index.d.ts.map +1 -0
  17. package/dist/node/HttpUtil/getHttpHeader.d.ts +12 -0
  18. package/dist/node/HttpUtil/getHttpHeader.d.ts.map +1 -0
  19. package/dist/node/HttpUtil/index.d.ts +2 -0
  20. package/dist/node/HttpUtil/index.d.ts.map +1 -0
  21. package/dist/node/Logger/LogFormats/LocalDev/index.d.ts +2 -0
  22. package/dist/node/Logger/LogFormats/LocalDev/index.d.ts.map +1 -0
  23. package/dist/node/Logger/LogFormats/LocalDev/logFormatLocalDev.d.ts +2 -0
  24. package/dist/node/Logger/LogFormats/LocalDev/logFormatLocalDev.d.ts.map +1 -0
  25. package/dist/node/Logger/LogFormats/Rollbar/index.d.ts +2 -0
  26. package/dist/node/Logger/LogFormats/Rollbar/index.d.ts.map +1 -0
  27. package/dist/node/Logger/LogFormats/Rollbar/logFormatRollbar.d.ts +2 -0
  28. package/dist/node/Logger/LogFormats/Rollbar/logFormatRollbar.d.ts.map +1 -0
  29. package/dist/node/Logger/LogFormats/Structured/index.d.ts +2 -0
  30. package/dist/node/Logger/LogFormats/Structured/index.d.ts.map +1 -0
  31. package/dist/node/Logger/LogFormats/Structured/logFormatStructured.d.ts +2 -0
  32. package/dist/node/Logger/LogFormats/Structured/logFormatStructured.d.ts.map +1 -0
  33. package/dist/node/Logger/LogFormats/index.d.ts +4 -0
  34. package/dist/node/Logger/LogFormats/index.d.ts.map +1 -0
  35. package/dist/node/Logger/Logger.d.ts +13 -0
  36. package/dist/node/Logger/Logger.d.ts.map +1 -0
  37. package/dist/node/Logger/LoggerMeta.d.ts +2 -0
  38. package/dist/node/Logger/LoggerMeta.d.ts.map +1 -0
  39. package/dist/node/Logger/LoggerOptions.d.ts +7 -0
  40. package/dist/node/Logger/LoggerOptions.d.ts.map +1 -0
  41. package/dist/node/Logger/LoggerVerbosity.d.ts +2 -0
  42. package/dist/node/Logger/LoggerVerbosity.d.ts.map +1 -0
  43. package/dist/node/Logger/Transports/Rollbar/RollbarTransport.d.ts +11 -0
  44. package/dist/node/Logger/Transports/Rollbar/RollbarTransport.d.ts.map +1 -0
  45. package/dist/node/Logger/Transports/Rollbar/canGetDefaultRollbarTransport.d.ts +4 -0
  46. package/dist/node/Logger/Transports/Rollbar/canGetDefaultRollbarTransport.d.ts.map +1 -0
  47. package/dist/node/Logger/Transports/Rollbar/getDefaultRollbarTransport.d.ts +5 -0
  48. package/dist/node/Logger/Transports/Rollbar/getDefaultRollbarTransport.d.ts.map +1 -0
  49. package/dist/node/Logger/Transports/Rollbar/index.d.ts +4 -0
  50. package/dist/node/Logger/Transports/Rollbar/index.d.ts.map +1 -0
  51. package/dist/node/Logger/Transports/index.d.ts +2 -0
  52. package/dist/node/Logger/Transports/index.d.ts.map +1 -0
  53. package/dist/node/Logger/WinstonVerbosity.d.ts +6 -0
  54. package/dist/node/Logger/WinstonVerbosity.d.ts.map +1 -0
  55. package/dist/node/Logger/WrappedWinstonLogger.d.ts +16 -0
  56. package/dist/node/Logger/WrappedWinstonLogger.d.ts.map +1 -0
  57. package/dist/node/Logger/getDefaultLogger.d.ts +3 -0
  58. package/dist/node/Logger/getDefaultLogger.d.ts.map +1 -0
  59. package/dist/node/Logger/getLogger.d.ts +4 -0
  60. package/dist/node/Logger/getLogger.d.ts.map +1 -0
  61. package/dist/node/Logger/index.d.ts +7 -0
  62. package/dist/node/Logger/index.d.ts.map +1 -0
  63. package/dist/node/Logger/toWinstonVerbosity.d.ts +4 -0
  64. package/dist/node/Logger/toWinstonVerbosity.d.ts.map +1 -0
  65. package/dist/node/Model/ExpressError.d.ts +4 -0
  66. package/dist/node/Model/ExpressError.d.ts.map +1 -0
  67. package/dist/node/Model/index.d.ts +2 -0
  68. package/dist/node/Model/index.d.ts.map +1 -0
  69. package/dist/node/Performance/Counters.d.ts +8 -0
  70. package/dist/node/Performance/Counters.d.ts.map +1 -0
  71. package/dist/node/Performance/Profiler.d.ts +5 -0
  72. package/dist/node/Performance/Profiler.d.ts.map +1 -0
  73. package/dist/node/Performance/index.d.ts +3 -0
  74. package/dist/node/Performance/index.d.ts.map +1 -0
  75. package/dist/node/Util/compactObject.d.ts +2 -0
  76. package/dist/node/Util/compactObject.d.ts.map +1 -0
  77. package/dist/node/Util/index.d.ts +3 -0
  78. package/dist/node/Util/index.d.ts.map +1 -0
  79. package/dist/node/Util/tryParse.d.ts +5 -0
  80. package/dist/node/Util/tryParse.d.ts.map +1 -0
  81. package/dist/node/index.d.ts +9 -0
  82. package/dist/node/index.d.ts.map +1 -0
  83. package/dist/node/index.mjs +408 -0
  84. package/dist/node/index.mjs.map +1 -0
  85. package/dist/node/middleware/caseInsensitiveRouting/caseInsensitiveRouting.d.ts +14 -0
  86. package/dist/node/middleware/caseInsensitiveRouting/caseInsensitiveRouting.d.ts.map +1 -0
  87. package/dist/node/middleware/caseInsensitiveRouting/index.d.ts +2 -0
  88. package/dist/node/middleware/caseInsensitiveRouting/index.d.ts.map +1 -0
  89. package/dist/node/middleware/customPoweredByHeader/customPoweredByHeader.d.ts +15 -0
  90. package/dist/node/middleware/customPoweredByHeader/customPoweredByHeader.d.ts.map +1 -0
  91. package/dist/node/middleware/customPoweredByHeader/index.d.ts +2 -0
  92. package/dist/node/middleware/customPoweredByHeader/index.d.ts.map +1 -0
  93. package/dist/node/middleware/index.d.ts +6 -0
  94. package/dist/node/middleware/index.d.ts.map +1 -0
  95. package/dist/node/middleware/jsonBodyParser/index.d.ts +2 -0
  96. package/dist/node/middleware/jsonBodyParser/index.d.ts.map +1 -0
  97. package/dist/node/middleware/jsonBodyParser/jsonBodyParser.d.ts +33 -0
  98. package/dist/node/middleware/jsonBodyParser/jsonBodyParser.d.ts.map +1 -0
  99. package/dist/node/middleware/metrics/counters.d.ts +3 -0
  100. package/dist/node/middleware/metrics/counters.d.ts.map +1 -0
  101. package/dist/node/middleware/metrics/index.d.ts +3 -0
  102. package/dist/node/middleware/metrics/index.d.ts.map +1 -0
  103. package/dist/node/middleware/metrics/responseProfiler.d.ts +15 -0
  104. package/dist/node/middleware/metrics/responseProfiler.d.ts.map +1 -0
  105. package/dist/node/middleware/standardResponses/getResponseMetadata.d.ts +3 -0
  106. package/dist/node/middleware/standardResponses/getResponseMetadata.d.ts.map +1 -0
  107. package/dist/node/middleware/standardResponses/index.d.ts +5 -0
  108. package/dist/node/middleware/standardResponses/index.d.ts.map +1 -0
  109. package/dist/node/middleware/standardResponses/jsonApi/error.d.ts +51 -0
  110. package/dist/node/middleware/standardResponses/jsonApi/error.d.ts.map +1 -0
  111. package/dist/node/middleware/standardResponses/jsonApi/index.d.ts +6 -0
  112. package/dist/node/middleware/standardResponses/jsonApi/index.d.ts.map +1 -0
  113. package/dist/node/middleware/standardResponses/jsonApi/links.d.ts +7 -0
  114. package/dist/node/middleware/standardResponses/jsonApi/links.d.ts.map +1 -0
  115. package/dist/node/middleware/standardResponses/jsonApi/relationship.d.ts +38 -0
  116. package/dist/node/middleware/standardResponses/jsonApi/relationship.d.ts.map +1 -0
  117. package/dist/node/middleware/standardResponses/jsonApi/resourceIdentifier.d.ts +16 -0
  118. package/dist/node/middleware/standardResponses/jsonApi/resourceIdentifier.d.ts.map +1 -0
  119. package/dist/node/middleware/standardResponses/jsonApi/response.d.ts +44 -0
  120. package/dist/node/middleware/standardResponses/jsonApi/response.d.ts.map +1 -0
  121. package/dist/node/middleware/standardResponses/standardErrors.d.ts +4 -0
  122. package/dist/node/middleware/standardResponses/standardErrors.d.ts.map +1 -0
  123. package/dist/node/middleware/standardResponses/standardResponses.d.ts +18 -0
  124. package/dist/node/middleware/standardResponses/standardResponses.d.ts.map +1 -0
  125. package/package.json +101 -0
  126. package/src/AWS/getEnvFromAws.ts +24 -0
  127. package/src/AWS/index.ts +1 -0
  128. package/src/AWS/spec/getEnvFromAws.spec.ts +16 -0
  129. package/src/Handler/StatusCodeHandlers/index.ts +1 -0
  130. package/src/Handler/StatusCodeHandlers/notImplemented.ts +6 -0
  131. package/src/Handler/asyncHandler.ts +21 -0
  132. package/src/Handler/errorToJsonHandler.ts +14 -0
  133. package/src/Handler/index.ts +3 -0
  134. package/src/HttpUtil/getHttpHeader.ts +26 -0
  135. package/src/HttpUtil/index.ts +1 -0
  136. package/src/Logger/LogFormats/LocalDev/index.ts +1 -0
  137. package/src/Logger/LogFormats/LocalDev/logFormatLocalDev.ts +11 -0
  138. package/src/Logger/LogFormats/LocalDev/spec/logFormatLocalDev.spec.ts +13 -0
  139. package/src/Logger/LogFormats/Rollbar/index.ts +1 -0
  140. package/src/Logger/LogFormats/Rollbar/logFormatRollbar.ts +5 -0
  141. package/src/Logger/LogFormats/Rollbar/spec/logFormatRollbar.spec.ts +13 -0
  142. package/src/Logger/LogFormats/Structured/index.ts +1 -0
  143. package/src/Logger/LogFormats/Structured/logFormatStructured.ts +7 -0
  144. package/src/Logger/LogFormats/Structured/spec/logFormatStructured.spec.ts +13 -0
  145. package/src/Logger/LogFormats/index.ts +3 -0
  146. package/src/Logger/Logger.ts +15 -0
  147. package/src/Logger/LoggerMeta.ts +1 -0
  148. package/src/Logger/LoggerOptions.ts +7 -0
  149. package/src/Logger/LoggerVerbosity.ts +1 -0
  150. package/src/Logger/Transports/Rollbar/RollbarTransport.ts +22 -0
  151. package/src/Logger/Transports/Rollbar/canGetDefaultRollbarTransport.ts +3 -0
  152. package/src/Logger/Transports/Rollbar/getDefaultRollbarTransport.ts +10 -0
  153. package/src/Logger/Transports/Rollbar/index.ts +3 -0
  154. package/src/Logger/Transports/Rollbar/spec/RollbarTransport.spec.ts +30 -0
  155. package/src/Logger/Transports/Rollbar/spec/canGetDefaultRollbarTransport.spec.ts +18 -0
  156. package/src/Logger/Transports/Rollbar/spec/getDefaultRollbarTransport.spec.ts +16 -0
  157. package/src/Logger/Transports/index.ts +1 -0
  158. package/src/Logger/WinstonVerbosity.ts +5 -0
  159. package/src/Logger/WrappedWinstonLogger.ts +16 -0
  160. package/src/Logger/getDefaultLogger.ts +13 -0
  161. package/src/Logger/getLogger.ts +55 -0
  162. package/src/Logger/index.ts +6 -0
  163. package/src/Logger/spec/getDefaultLogger.spec.ts +14 -0
  164. package/src/Logger/spec/getLogger.spec.ts +25 -0
  165. package/src/Logger/spec/toWinstonVerbosity.spec.ts +17 -0
  166. package/src/Logger/toWinstonVerbosity.ts +6 -0
  167. package/src/Model/ExpressError.ts +3 -0
  168. package/src/Model/index.ts +1 -0
  169. package/src/Performance/Counters.ts +36 -0
  170. package/src/Performance/Profiler.ts +10 -0
  171. package/src/Performance/index.ts +2 -0
  172. package/src/Util/compactObject.ts +9 -0
  173. package/src/Util/index.ts +2 -0
  174. package/src/Util/tryParse.ts +21 -0
  175. package/src/index.ts +8 -0
  176. package/src/middleware/caseInsensitiveRouting/caseInsensitiveRouting.ts +21 -0
  177. package/src/middleware/caseInsensitiveRouting/index.ts +1 -0
  178. package/src/middleware/customPoweredByHeader/customPoweredByHeader.ts +29 -0
  179. package/src/middleware/customPoweredByHeader/index.ts +1 -0
  180. package/src/middleware/index.ts +5 -0
  181. package/src/middleware/jsonBodyParser/index.ts +1 -0
  182. package/src/middleware/jsonBodyParser/jsonBodyParser.ts +57 -0
  183. package/src/middleware/jsonBodyParser/spec/jsonBodyParser.spec.ts +26 -0
  184. package/src/middleware/metrics/counters.ts +25 -0
  185. package/src/middleware/metrics/index.ts +2 -0
  186. package/src/middleware/metrics/responseProfiler.ts +23 -0
  187. package/src/middleware/standardResponses/getResponseMetadata.ts +18 -0
  188. package/src/middleware/standardResponses/index.ts +4 -0
  189. package/src/middleware/standardResponses/jsonApi/README.md +4 -0
  190. package/src/middleware/standardResponses/jsonApi/error.ts +52 -0
  191. package/src/middleware/standardResponses/jsonApi/index.ts +5 -0
  192. package/src/middleware/standardResponses/jsonApi/links.ts +6 -0
  193. package/src/middleware/standardResponses/jsonApi/relationship.ts +43 -0
  194. package/src/middleware/standardResponses/jsonApi/resourceIdentifier.ts +15 -0
  195. package/src/middleware/standardResponses/jsonApi/response.ts +49 -0
  196. package/src/middleware/standardResponses/standardErrors.ts +25 -0
  197. package/src/middleware/standardResponses/standardResponses.ts +23 -0
  198. package/xy.config.ts +10 -0
@@ -0,0 +1,10 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import Rollbar from 'rollbar'
3
+
4
+ import { RollbarTransport } from './RollbarTransport.ts'
5
+
6
+ export const getDefaultRollbarTransport = (env: { [key: string]: string | undefined }): RollbarTransport => {
7
+ const accessToken = assertEx(env.ROLLBAR_ACCESS_TOKEN, 'Missing ROLLBAR_ACCESS_TOKEN ENV VAR')
8
+ const rollbar: Rollbar = new Rollbar({ accessToken })
9
+ return new RollbarTransport({}, rollbar)
10
+ }
@@ -0,0 +1,3 @@
1
+ export * from './canGetDefaultRollbarTransport.ts'
2
+ export * from './getDefaultRollbarTransport.ts'
3
+ export * from './RollbarTransport.ts'
@@ -0,0 +1,30 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import Rollbar from 'rollbar'
4
+ import {
5
+ beforeEach,
6
+ describe, expect, it,
7
+ vi,
8
+ } from 'vitest'
9
+ import type { MockProxy } from 'vitest-mock-extended'
10
+ import { mock } from 'vitest-mock-extended'
11
+
12
+ import { RollbarTransport } from '../RollbarTransport.ts'
13
+
14
+ const accessToken = process.env.ROLLBAR_ACCESS_TOKEN
15
+ const unitTestSentinelLoggingString = 'error log from unit test'
16
+
17
+ describe('RollbarTransport', () => {
18
+ let rollbar: MockProxy<Rollbar> | Rollbar
19
+ let sut: RollbarTransport
20
+ beforeEach(() => {
21
+ rollbar = accessToken ? new Rollbar({ accessToken, environment: 'development' }) : mock<Rollbar>()
22
+ sut = new RollbarTransport({}, rollbar)
23
+ })
24
+ it('logs', () => {
25
+ expect(sut).toBeObject()
26
+ const nextMock = vi.fn()
27
+ sut.log({ message: unitTestSentinelLoggingString }, nextMock)
28
+ expect(nextMock).toHaveBeenCalledOnce()
29
+ })
30
+ })
@@ -0,0 +1,18 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import { canGetDefaultRollbarTransport } from '../canGetDefaultRollbarTransport.ts'
8
+
9
+ describe('canGetDefaultRollbarTransport', () => {
10
+ it('returns true if the transport could be created', () => {
11
+ const env = { ROLLBAR_ACCESS_TOKEN: 'something' }
12
+ expect(canGetDefaultRollbarTransport(env)).toBeTrue()
13
+ })
14
+ it('returns false if the transport could not be crated', () => {
15
+ const env = { ROLLBAR_ACCESS_TOKEN: undefined }
16
+ expect(canGetDefaultRollbarTransport(env)).toBeFalse()
17
+ })
18
+ })
@@ -0,0 +1,16 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import { getDefaultRollbarTransport } from '../getDefaultRollbarTransport.ts'
8
+
9
+ describe('getDefaultRollbarTransport', () => {
10
+ it('returns the transport', () => {
11
+ const env = { ROLLBAR_ACCESS_TOKEN: 'something' }
12
+ const transport = getDefaultRollbarTransport(env)
13
+ expect(transport).toBeObject()
14
+ expect(transport.log).toBeFunction()
15
+ })
16
+ })
@@ -0,0 +1 @@
1
+ export * from './Rollbar/index.ts'
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Follows NPM log levels
3
+ * https://docs.npmjs.com/cli/v8/using-npm/logging#loglevel
4
+ */
5
+ export type WinstonVerbosity = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly'
@@ -0,0 +1,16 @@
1
+ import type { Logger as Winston } from 'winston'
2
+
3
+ import type { LogFunction, Logger } from './Logger'
4
+
5
+ /**
6
+ * Wrap Winston logger methods to adapt to familiar
7
+ * console logging methods
8
+ */
9
+ export class WrappedWinstonLogger implements Logger {
10
+ constructor(protected readonly winston: Winston) {}
11
+ debug: LogFunction = message => this.winston.debug(message)
12
+ error: LogFunction = message => this.winston.error(message)
13
+ info: LogFunction = message => this.winston.info(message)
14
+ log: LogFunction = message => this.winston.info(message)
15
+ warn: LogFunction = message => this.winston.warn(message)
16
+ }
@@ -0,0 +1,13 @@
1
+ import { getLogger } from './getLogger.ts'
2
+ import type { Logger } from './Logger.ts'
3
+ import type { WrappedWinstonLogger } from './WrappedWinstonLogger.ts'
4
+
5
+ /**
6
+ * Static instance to prevent multiple instances of the same logger
7
+ * with the same config
8
+ */
9
+ let defaultLogger: WrappedWinstonLogger
10
+ export const getDefaultLogger = (): Logger => {
11
+ if (defaultLogger) return defaultLogger
12
+ return getLogger()
13
+ }
@@ -0,0 +1,55 @@
1
+ import { createLogger, transports as winstonTransports } from 'winston'
2
+ import type TransportStream from 'winston-transport'
3
+
4
+ import { logFormatLocalDev, logFormatStructured } from './LogFormats/index.ts'
5
+ import type { Logger } from './Logger.ts'
6
+ import type { LoggerVerbosity } from './LoggerVerbosity.ts'
7
+ import { toWinstonVerbosity } from './toWinstonVerbosity.ts'
8
+ import { canGetDefaultRollbarTransport, getDefaultRollbarTransport } from './Transports/index.ts'
9
+ import type { WinstonVerbosity } from './WinstonVerbosity.ts'
10
+ import { WrappedWinstonLogger } from './WrappedWinstonLogger.ts'
11
+
12
+ const exitOnError = false
13
+ const handleRejections = true
14
+
15
+ const { Console } = winstonTransports
16
+ const consoleTransport = new Console()
17
+ const format = process.env.NODE_ENV === 'development' ? logFormatLocalDev : logFormatStructured
18
+ const transports: TransportStream[] = [consoleTransport]
19
+ if (canGetDefaultRollbarTransport(process.env)) {
20
+ try {
21
+ const rollbarTransport = getDefaultRollbarTransport(process.env)
22
+ transports.push(rollbarTransport)
23
+ } catch {
24
+ // NOTE: No error here, just gracefully adding logger if ENV VARs
25
+ // were preset
26
+ }
27
+ }
28
+
29
+ const loggers: Record<WinstonVerbosity, Logger | undefined> = {
30
+ debug: undefined,
31
+ error: undefined,
32
+ http: undefined,
33
+ info: undefined,
34
+ silly: undefined,
35
+ verbose: undefined,
36
+ warn: undefined,
37
+ }
38
+
39
+ export const getLogger = (minVerbosity: LoggerVerbosity = 'info'): Logger => {
40
+ const level = toWinstonVerbosity(minVerbosity)
41
+ const existing = loggers[level]
42
+ if (existing) return existing
43
+ const logger = new WrappedWinstonLogger(
44
+ createLogger({
45
+ exitOnError,
46
+ format,
47
+ handleRejections,
48
+ level,
49
+ rejectionHandlers: transports,
50
+ transports,
51
+ }),
52
+ )
53
+ loggers[level] = logger
54
+ return logger
55
+ }
@@ -0,0 +1,6 @@
1
+ export * from './getDefaultLogger.ts'
2
+ export * from './getLogger.ts'
3
+ export * from './Logger.ts'
4
+ export * from './LoggerMeta.ts'
5
+ export * from './LoggerOptions.ts'
6
+ export * from './LoggerVerbosity.ts'
@@ -0,0 +1,14 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import { getDefaultLogger } from '../getDefaultLogger.ts'
8
+
9
+ describe('getDefaultLogger', () => {
10
+ it('provides a default logger', () => {
11
+ const logger = getDefaultLogger()
12
+ expect(logger).toBeObject()
13
+ })
14
+ })
@@ -0,0 +1,25 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { mockDeep } from 'vitest-mock-extended'
3
+ globalThis.console = mockDeep<Console>()
4
+ import '@xylabs/vitest-extended'
5
+
6
+ import {
7
+ describe, expect, it,
8
+ } from 'vitest'
9
+ import type { Logger } from 'winston'
10
+
11
+ import { getLogger } from '../getLogger.ts'
12
+
13
+ type LoggerKey = keyof Logger
14
+ const loggerKeys: LoggerKey[] = ['error', 'warn', 'log', 'info', 'debug']
15
+
16
+ describe('getLogger', () => {
17
+ describe('verbosity', () => {
18
+ it.each(loggerKeys)('logs log with %s verbosity', (verbosity: LoggerKey) => {
19
+ const logger = getLogger('all')
20
+ const logMethod = (logger as any)[verbosity]
21
+ expect(logMethod).toBeFunction()
22
+ logMethod(`${new String(verbosity)} log from unit test`)
23
+ })
24
+ })
25
+ })
@@ -0,0 +1,17 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import type { LoggerVerbosity } from '../LoggerVerbosity.ts'
8
+ import { toWinstonVerbosity } from '../toWinstonVerbosity.ts'
9
+
10
+ const loggerKeys: LoggerVerbosity[] = ['error', 'warn', 'info', 'debug', 'all']
11
+
12
+ describe('toWinstonVerbosity', () => {
13
+ it.each(loggerKeys)('provides a default logger', (verbosity) => {
14
+ const actual = toWinstonVerbosity(verbosity)
15
+ expect(actual).toBeString()
16
+ })
17
+ })
@@ -0,0 +1,6 @@
1
+ import type { LoggerVerbosity } from './LoggerVerbosity.ts'
2
+ import type { WinstonVerbosity } from './WinstonVerbosity.ts'
3
+
4
+ export const toWinstonVerbosity = (loggerVerbosity: LoggerVerbosity): WinstonVerbosity => {
5
+ return loggerVerbosity === 'all' ? 'silly' : loggerVerbosity
6
+ }
@@ -0,0 +1,3 @@
1
+ export interface ExpressError extends Error {
2
+ statusCode?: number
3
+ }
@@ -0,0 +1 @@
1
+ export * from './ExpressError.ts'
@@ -0,0 +1,36 @@
1
+ export class Counters {
2
+ static counters: Record<string, number> = {}
3
+
4
+ static inc(name: string, count = 1) {
5
+ this.catchError(name, (name: string) => {
6
+ this.counters[name] = (this.counters[name] ?? 0) + count
7
+ })
8
+ }
9
+
10
+ static max(name: string, count: number) {
11
+ this.catchError(name, (name: string) => {
12
+ const currentValue = this.counters[name]
13
+ if (currentValue === undefined || count > currentValue) {
14
+ this.counters[name] = count
15
+ }
16
+ })
17
+ }
18
+
19
+ static min(name: string, count: number) {
20
+ this.catchError(name, (name: string) => {
21
+ const currentValue = this.counters[name]
22
+ if (currentValue === undefined || count < currentValue) {
23
+ this.counters[name] = count
24
+ }
25
+ })
26
+ }
27
+
28
+ private static catchError = (name: string, func: (name: string) => void) => {
29
+ try {
30
+ func(name)
31
+ } catch {
32
+ this.counters[name] = 0
33
+ this.inc('CountersErrors')
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,10 @@
1
+ export class Profiler {
2
+ stats: Record<string, number> = {}
3
+
4
+ async profile<T>(name: string, promise: Promise<T>) {
5
+ const start = Date.now()
6
+ const result = await promise
7
+ this.stats[name] = Date.now() - start
8
+ return result
9
+ }
10
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Counters.ts'
2
+ export * from './Profiler.ts'
@@ -0,0 +1,9 @@
1
+ export const compactObject = <T extends Record<string, unknown>>(obj: T) => {
2
+ const result: Record<string, unknown> = {}
3
+ for (const key in obj) {
4
+ if (obj[key] !== undefined && obj[key] !== null) {
5
+ result[key] = obj[key]
6
+ }
7
+ }
8
+ return result as T
9
+ }
@@ -0,0 +1,2 @@
1
+ export * from './compactObject.ts'
2
+ export * from './tryParse.ts'
@@ -0,0 +1,21 @@
1
+ export type ParseFunc<T = number> = (value: string) => T
2
+
3
+ export const tryParse = <T = number>(func: ParseFunc<T>, value?: string) => {
4
+ try {
5
+ const result = value ? func(value) : null
6
+ if (!Number.isNaN(result) && result !== null) {
7
+ return result
8
+ }
9
+ } catch {
10
+ return
11
+ }
12
+ return
13
+ }
14
+
15
+ export const tryParseFloat = (value?: string) => {
16
+ return tryParse(Number.parseFloat, value)
17
+ }
18
+
19
+ export const tryParseInt = (value?: string) => {
20
+ return tryParse(Number.parseInt, value)
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './AWS/index.ts'
2
+ export * from './Handler/index.ts'
3
+ export * from './HttpUtil/index.ts'
4
+ export * from './Logger/index.ts'
5
+ export * from './middleware/index.ts'
6
+ export * from './Model/index.ts'
7
+ export * from './Performance/index.ts'
8
+ export * from './Util/index.ts'
@@ -0,0 +1,21 @@
1
+ import type { Express } from 'express'
2
+
3
+ const setting = 'case sensitive routing'
4
+
5
+ /**
6
+ * Enable case sensitivity. When enabled, "/Foo" and "/foo" are different
7
+ * routes. When disabled, "/Foo" and "/foo" are treated the same.
8
+ * @param app The Express app to disable the header on.
9
+ */
10
+ export const enableCaseSensitiveRouting = (app: Express) => {
11
+ app.enable(setting)
12
+ }
13
+
14
+ /**
15
+ * Disable case sensitivity. When enabled, "/Foo" and "/foo" are different
16
+ * routes. When disabled, "/Foo" and "/foo" are treated the same.
17
+ * @param app The Express app to disable the header on.
18
+ */
19
+ export const disableCaseSensitiveRouting = (app: Express) => {
20
+ app.disable(setting)
21
+ }
@@ -0,0 +1 @@
1
+ export * from './caseInsensitiveRouting.ts'
@@ -0,0 +1,29 @@
1
+ import type {
2
+ Express, NextFunction, Request, Response,
3
+ } from 'express'
4
+
5
+ const header = 'X-Powered-By'
6
+ const setting = 'x-powered-by'
7
+
8
+ /**
9
+ * By default Express appends the `X-Powered-By: Express` header to
10
+ * all responses. Calling this method enables that behavior.
11
+ * @param app The Express app to disable the header on.
12
+ */
13
+ export const enableExpressDefaultPoweredByHeader = (app: Express) => {
14
+ app.enable(setting)
15
+ }
16
+
17
+ /**
18
+ * By default Express appends the `X-Powered-By: Express` header to
19
+ * all responses. Calling this method disables that behavior.
20
+ * @param app The Express app to disable the header on.
21
+ */
22
+ export const disableExpressDefaultPoweredByHeader = (app: Express) => {
23
+ app.disable(setting)
24
+ }
25
+
26
+ export const customPoweredByHeader = (req: Request, res: Response, next: NextFunction) => {
27
+ res.setHeader(header, 'XYO')
28
+ next()
29
+ }
@@ -0,0 +1 @@
1
+ export * from './customPoweredByHeader.ts'
@@ -0,0 +1,5 @@
1
+ export * from './caseInsensitiveRouting/index.ts'
2
+ export * from './customPoweredByHeader/index.ts'
3
+ export * from './jsonBodyParser/index.ts'
4
+ export * from './metrics/index.ts'
5
+ export * from './standardResponses/index.ts'
@@ -0,0 +1 @@
1
+ export * from './jsonBodyParser.ts'
@@ -0,0 +1,57 @@
1
+ import type { OptionsJson } from 'body-parser'
2
+ import bodyParser from 'body-parser'
3
+ import type { NextHandleFunction } from 'connect'
4
+
5
+ /**
6
+ * The default maximum request body size for the JSON Body Parser
7
+ */
8
+ export const DefaultJsonBodyParserOptionsLimit = '100kb'
9
+
10
+ /**
11
+ * The default MIME types for the JSON Body Parser
12
+ */
13
+ export const DefaultJsonBodyParserOptionsTypes = ['application/json', 'text/json']
14
+
15
+ /**
16
+ * The default options for the JSON Body Parser
17
+ */
18
+ export const DefaultJsonBodyParserOptions: OptionsJson = {
19
+ limit: DefaultJsonBodyParserOptionsLimit,
20
+ type: DefaultJsonBodyParserOptionsTypes,
21
+ }
22
+
23
+ /**
24
+ * Gets the default JSON Body Parser options merged with the supplied options
25
+ * with the supplied options taking precedence
26
+ * @param options The options to override the default JSON Body Parser options with
27
+ * @returns The combined JSON Body Parser options with the supplied values taking
28
+ * precedence over the default
29
+ */
30
+ export const getJsonBodyParserOptions = (options?: Partial<OptionsJson>): OptionsJson => {
31
+ return options ? { ...DefaultJsonBodyParserOptions, ...options } : DefaultJsonBodyParserOptions
32
+ }
33
+
34
+ /**
35
+ * Get a JSON Body Parser connect middleware handler
36
+ * @param options The options for the JSON Body Parser
37
+ * @returns A middleware function that parses JSON bodies
38
+ */
39
+ export const getJsonBodyParser = (options: OptionsJson = DefaultJsonBodyParserOptions): NextHandleFunction => {
40
+ // Create closed instance of bodyParser to prevent instantiation of new instance on every request
41
+ const parser = bodyParser.json(options)
42
+
43
+ return (req, res, next) => {
44
+ // If we do not trap this error, then it dumps too much to log, usually happens if request aborted
45
+ try {
46
+ parser(req, res, next)
47
+ } catch (ex) {
48
+ const error = ex as Error
49
+ console.log(`bodyParser failed [${error?.name}]: ${error?.message}`)
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * A JSON Body Parser middleware handler initialized with the default options
56
+ */
57
+ export const jsonBodyParser = getJsonBodyParser()
@@ -0,0 +1,26 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ import { DefaultJsonBodyParserOptions, getJsonBodyParserOptions } from '../jsonBodyParser.ts'
8
+
9
+ describe('jsonBodyParser', () => {
10
+ describe('getJsonBodyParserOptions', () => {
11
+ it('returns default options if none supplied', () => {
12
+ // Act
13
+ const result = getJsonBodyParserOptions()
14
+ // Assert
15
+ expect(result).toEqual(DefaultJsonBodyParserOptions)
16
+ })
17
+ it('returns merged options if options supplied', () => {
18
+ // Arrange
19
+ const options = { limit: '1mb' }
20
+ // Act
21
+ const result = getJsonBodyParserOptions(options)
22
+ // Assert
23
+ expect(result).toEqual({ ...DefaultJsonBodyParserOptions, ...options })
24
+ })
25
+ })
26
+ })
@@ -0,0 +1,25 @@
1
+ import type {
2
+ Application, NextFunction, Request, Response,
3
+ } from 'express'
4
+
5
+ import { Counters } from '../../Performance/index.ts'
6
+
7
+ export const useRequestCounters = (app: Application): void => {
8
+ // Configure Global counters
9
+ app.use((req: Request, res: Response, next: NextFunction) => {
10
+ Counters.inc(req.path)
11
+ Counters.inc('_calls')
12
+ next()
13
+ })
14
+
15
+ app.get('/stats', (req: Request, res: Response, next: NextFunction) => {
16
+ /* #swagger.tags = ['Metrics'] */
17
+ /* #swagger.summary = 'Get the counters for single instance of diviner' */
18
+ res.json({
19
+ alive: true,
20
+ avgTime: `${((Counters.counters['_totalTime'] ?? 0) / (Counters.counters['_calls'] ?? 1)).toFixed(2)}ms`,
21
+ counters: Counters.counters,
22
+ })
23
+ next()
24
+ })
25
+ }
@@ -0,0 +1,2 @@
1
+ export * from './counters.ts'
2
+ export * from './responseProfiler.ts'
@@ -0,0 +1,23 @@
1
+ import type {
2
+ NextFunction, Request, Response,
3
+ } from 'express'
4
+
5
+ /**
6
+ * Connect middleware to enable profiling of response lifecycle timing. To effectively profile
7
+ * the response timing, this middleware needs to be called first when initializing your Express
8
+ * App
9
+ * @example
10
+ * const app = express()
11
+ * app.use(responseProfiler)
12
+ * // other initialization ...
13
+ * @param _req The request
14
+ * @param res The response
15
+ * @param next The next function
16
+ */
17
+ export const responseProfiler = (_req: Request, res: Response, next: NextFunction) => {
18
+ if (!res.locals?.meta) {
19
+ res.locals.meta = {}
20
+ }
21
+ res.locals.meta.profile = { startTime: Date.now() }
22
+ next()
23
+ }
@@ -0,0 +1,18 @@
1
+ import type { Response } from 'express'
2
+
3
+ export const getResponseMetadata = (res: Response): Record<string, unknown> => {
4
+ const meta: Record<string, unknown> = res.locals?.meta || {}
5
+ // NOTE: We should do this somewhere else to better separate concerns
6
+ const profile = res.locals.meta?.profile
7
+ if (profile) {
8
+ const startTime = profile?.startTime
9
+ if (startTime) {
10
+ const endTime = Date.now()
11
+ const duration = endTime - startTime
12
+ res.locals.meta.profile = {
13
+ duration, endTime, startTime,
14
+ }
15
+ }
16
+ }
17
+ return meta
18
+ }
@@ -0,0 +1,4 @@
1
+ export * from './getResponseMetadata.ts'
2
+ export * from './jsonApi/index.ts'
3
+ export * from './standardErrors.ts'
4
+ export * from './standardResponses.ts'
@@ -0,0 +1,4 @@
1
+ # JSON API
2
+
3
+ The following interfaces/types attempt to capture the
4
+ [JSON API 1.0](https://jsonapi.org/) specification as closely as possible
@@ -0,0 +1,52 @@
1
+ import type { ApiLinks } from './links.ts'
2
+
3
+ /**
4
+ * An object containing references to the source of the error
5
+ */
6
+ export interface Source {
7
+ /**
8
+ * A string indicating which URI query parameter caused the error.
9
+ */
10
+ parameter?: string
11
+ /**
12
+ * A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object,
13
+ * or "/data/attributes/title" for a specific attribute].
14
+ */
15
+ pointer?: string
16
+ }
17
+
18
+ export interface ApiError {
19
+ /**
20
+ * An application-specific error code, expressed as a string value.
21
+ */
22
+ code?: string
23
+ /**
24
+ * A human-readable explanation specific to this occurrence of the problem. Like title, this field's value can be localized.
25
+ */
26
+ detail?: string
27
+ /**
28
+ * A unique identifier for this particular occurrence of the problem.
29
+ */
30
+ id?: string
31
+ /**
32
+ * A links object containing the following members:
33
+ * about: a link that leads to further details about this particular occurrence of the problem
34
+ */
35
+ links?: ApiLinks
36
+ /**
37
+ * A meta object containing non-standard meta-information about the error.
38
+ */
39
+ meta?: Record<string, unknown>
40
+ /**
41
+ * An object containing references to the source of the error, optionally including any of the following members:
42
+ */
43
+ source?: Source
44
+ /**
45
+ * The HTTP status code applicable to this problem, expressed as a string value.
46
+ */
47
+ status?: string
48
+ /**
49
+ * A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
50
+ */
51
+ title?: string
52
+ }