@c9up/ream 0.1.34 → 0.2.0

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 (210) hide show
  1. package/dist/Application.d.ts +68 -0
  2. package/dist/Application.d.ts.map +1 -1
  3. package/dist/Application.js +142 -6
  4. package/dist/Application.js.map +1 -1
  5. package/dist/HealthCheck.d.ts +14 -7
  6. package/dist/HealthCheck.d.ts.map +1 -1
  7. package/dist/HealthCheck.js +21 -12
  8. package/dist/HealthCheck.js.map +1 -1
  9. package/dist/HttpKernel.d.ts +12 -0
  10. package/dist/HttpKernel.d.ts.map +1 -1
  11. package/dist/HttpKernel.js +15 -4
  12. package/dist/HttpKernel.js.map +1 -1
  13. package/dist/Ignitor.d.ts +8 -0
  14. package/dist/Ignitor.d.ts.map +1 -1
  15. package/dist/Ignitor.js +11 -0
  16. package/dist/Ignitor.js.map +1 -1
  17. package/dist/bodyparser/BodyParserMiddleware.d.ts +13 -2
  18. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
  19. package/dist/bodyparser/BodyParserMiddleware.js +50 -30
  20. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
  21. package/dist/bodyparser/MultipartFile.d.ts +65 -6
  22. package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
  23. package/dist/bodyparser/MultipartFile.js +100 -7
  24. package/dist/bodyparser/MultipartFile.js.map +1 -1
  25. package/dist/console/BaseCommand.d.ts +8 -1
  26. package/dist/console/BaseCommand.d.ts.map +1 -1
  27. package/dist/console/BaseCommand.js +1 -1
  28. package/dist/console/BaseCommand.js.map +1 -1
  29. package/dist/console/Kernel.d.ts +15 -0
  30. package/dist/console/Kernel.d.ts.map +1 -1
  31. package/dist/console/Kernel.js +17 -0
  32. package/dist/console/Kernel.js.map +1 -1
  33. package/dist/directories.d.ts +52 -0
  34. package/dist/directories.d.ts.map +1 -0
  35. package/dist/directories.js +38 -0
  36. package/dist/directories.js.map +1 -0
  37. package/dist/events/Emitter.d.ts +49 -6
  38. package/dist/events/Emitter.d.ts.map +1 -1
  39. package/dist/events/Emitter.js +117 -7
  40. package/dist/events/Emitter.js.map +1 -1
  41. package/dist/events/EventsBuffer.d.ts +47 -0
  42. package/dist/events/EventsBuffer.d.ts.map +1 -0
  43. package/dist/events/EventsBuffer.js +107 -0
  44. package/dist/events/EventsBuffer.js.map +1 -0
  45. package/dist/events/index.d.ts +2 -0
  46. package/dist/events/index.d.ts.map +1 -1
  47. package/dist/events/index.js +1 -0
  48. package/dist/events/index.js.map +1 -1
  49. package/dist/health/BaseCheck.d.ts +19 -0
  50. package/dist/health/BaseCheck.d.ts.map +1 -0
  51. package/dist/health/BaseCheck.js +24 -0
  52. package/dist/health/BaseCheck.js.map +1 -0
  53. package/dist/health/HealthChecks.d.ts +15 -0
  54. package/dist/health/HealthChecks.d.ts.map +1 -0
  55. package/dist/health/HealthChecks.js +69 -0
  56. package/dist/health/HealthChecks.js.map +1 -0
  57. package/dist/health/Result.d.ts +27 -0
  58. package/dist/health/Result.d.ts.map +1 -0
  59. package/dist/health/Result.js +55 -0
  60. package/dist/health/Result.js.map +1 -0
  61. package/dist/health/checks/DiskSpaceCheck.d.ts +29 -0
  62. package/dist/health/checks/DiskSpaceCheck.d.ts.map +1 -0
  63. package/dist/health/checks/DiskSpaceCheck.js +58 -0
  64. package/dist/health/checks/DiskSpaceCheck.js.map +1 -0
  65. package/dist/health/checks/MemoryHeapCheck.d.ts +21 -0
  66. package/dist/health/checks/MemoryHeapCheck.d.ts.map +1 -0
  67. package/dist/health/checks/MemoryHeapCheck.js +55 -0
  68. package/dist/health/checks/MemoryHeapCheck.js.map +1 -0
  69. package/dist/health/checks/MemoryRSSCheck.d.ts +21 -0
  70. package/dist/health/checks/MemoryRSSCheck.d.ts.map +1 -0
  71. package/dist/health/checks/MemoryRSSCheck.js +55 -0
  72. package/dist/health/checks/MemoryRSSCheck.js.map +1 -0
  73. package/dist/health/checks/memory.d.ts +24 -0
  74. package/dist/health/checks/memory.d.ts.map +1 -0
  75. package/dist/health/checks/memory.js +88 -0
  76. package/dist/health/checks/memory.js.map +1 -0
  77. package/dist/health/index.d.ts +9 -0
  78. package/dist/health/index.d.ts.map +1 -0
  79. package/dist/health/index.js +8 -0
  80. package/dist/health/index.js.map +1 -0
  81. package/dist/health/tracing.d.ts +12 -0
  82. package/dist/health/tracing.d.ts.map +1 -0
  83. package/dist/health/tracing.js +11 -0
  84. package/dist/health/tracing.js.map +1 -0
  85. package/dist/health/types.d.ts +51 -0
  86. package/dist/health/types.d.ts.map +1 -0
  87. package/dist/health/types.js +5 -0
  88. package/dist/health/types.js.map +1 -0
  89. package/dist/helpers/bytes.d.ts +28 -0
  90. package/dist/helpers/bytes.d.ts.map +1 -0
  91. package/dist/helpers/bytes.js +63 -0
  92. package/dist/helpers/bytes.js.map +1 -0
  93. package/dist/helpers/string.d.ts +4 -0
  94. package/dist/helpers/string.d.ts.map +1 -1
  95. package/dist/helpers/string.js +2 -0
  96. package/dist/helpers/string.js.map +1 -1
  97. package/dist/http/HttpContext.d.ts +30 -2
  98. package/dist/http/HttpContext.d.ts.map +1 -1
  99. package/dist/http/HttpContext.js +73 -3
  100. package/dist/http/HttpContext.js.map +1 -1
  101. package/dist/http/Request.d.ts +42 -3
  102. package/dist/http/Request.d.ts.map +1 -1
  103. package/dist/http/Request.js +51 -7
  104. package/dist/http/Request.js.map +1 -1
  105. package/dist/http/Response.d.ts +82 -2
  106. package/dist/http/Response.d.ts.map +1 -1
  107. package/dist/http/Response.js +240 -13
  108. package/dist/http/Response.js.map +1 -1
  109. package/dist/http/SseStream.d.ts +10 -0
  110. package/dist/http/SseStream.d.ts.map +1 -1
  111. package/dist/http/SseStream.js.map +1 -1
  112. package/dist/index.d.ts +7 -2
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +4 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/migrations/MigrationRegistry.d.ts +28 -0
  117. package/dist/migrations/MigrationRegistry.d.ts.map +1 -0
  118. package/dist/migrations/MigrationRegistry.js +41 -0
  119. package/dist/migrations/MigrationRegistry.js.map +1 -0
  120. package/dist/migrations/types.d.ts +64 -0
  121. package/dist/migrations/types.d.ts.map +1 -0
  122. package/dist/migrations/types.js +15 -0
  123. package/dist/migrations/types.js.map +1 -0
  124. package/dist/router/Router.d.ts +112 -9
  125. package/dist/router/Router.d.ts.map +1 -1
  126. package/dist/router/Router.js +206 -28
  127. package/dist/router/Router.js.map +1 -1
  128. package/dist/security/CookieSigner.d.ts +43 -4
  129. package/dist/security/CookieSigner.d.ts.map +1 -1
  130. package/dist/security/CookieSigner.js +68 -10
  131. package/dist/security/CookieSigner.js.map +1 -1
  132. package/dist/session/Session.d.ts +46 -1
  133. package/dist/session/Session.d.ts.map +1 -1
  134. package/dist/session/Session.js +57 -4
  135. package/dist/session/Session.js.map +1 -1
  136. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  137. package/dist/session/SessionMiddleware.js +19 -9
  138. package/dist/session/SessionMiddleware.js.map +1 -1
  139. package/dist/session/drivers/CookieDriver.d.ts +1 -1
  140. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  141. package/dist/session/drivers/CookieDriver.js +3 -3
  142. package/dist/session/drivers/CookieDriver.js.map +1 -1
  143. package/dist/session/drivers/DatabaseDriver.d.ts +1 -1
  144. package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
  145. package/dist/session/drivers/DatabaseDriver.js +2 -2
  146. package/dist/session/drivers/DatabaseDriver.js.map +1 -1
  147. package/dist/session/drivers/FileDriver.d.ts +1 -1
  148. package/dist/session/drivers/FileDriver.d.ts.map +1 -1
  149. package/dist/session/drivers/FileDriver.js +7 -5
  150. package/dist/session/drivers/FileDriver.js.map +1 -1
  151. package/dist/session/drivers/MemoryDriver.d.ts +1 -1
  152. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
  153. package/dist/session/drivers/MemoryDriver.js +1 -1
  154. package/dist/session/drivers/MemoryDriver.js.map +1 -1
  155. package/dist/session/drivers/RedisDriver.d.ts +1 -1
  156. package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
  157. package/dist/session/drivers/RedisDriver.js +11 -4
  158. package/dist/session/drivers/RedisDriver.js.map +1 -1
  159. package/events.win32-x64-msvc.node +0 -0
  160. package/index.darwin-arm64.node +0 -0
  161. package/index.darwin-x64.node +0 -0
  162. package/index.linux-arm64-gnu.node +0 -0
  163. package/index.linux-x64-gnu.node +0 -0
  164. package/index.win32-x64-msvc.node +0 -0
  165. package/package.json +5 -2
  166. package/scheduler.win32-x64-msvc.node +0 -0
  167. package/src/Application.ts +169 -6
  168. package/src/HealthCheck.ts +27 -14
  169. package/src/HttpKernel.ts +27 -4
  170. package/src/Ignitor.ts +17 -0
  171. package/src/bodyparser/BodyParserMiddleware.ts +70 -36
  172. package/src/bodyparser/MultipartFile.ts +125 -8
  173. package/src/console/BaseCommand.ts +8 -1
  174. package/src/console/Kernel.ts +18 -0
  175. package/src/directories.ts +81 -0
  176. package/src/events/Emitter.ts +129 -13
  177. package/src/events/EventsBuffer.ts +131 -0
  178. package/src/events/index.ts +7 -0
  179. package/src/health/BaseCheck.ts +30 -0
  180. package/src/health/HealthChecks.ts +83 -0
  181. package/src/health/Result.ts +69 -0
  182. package/src/health/checks/DiskSpaceCheck.ts +69 -0
  183. package/src/health/checks/MemoryHeapCheck.ts +68 -0
  184. package/src/health/checks/MemoryRSSCheck.ts +68 -0
  185. package/src/health/checks/memory.ts +115 -0
  186. package/src/health/index.ts +13 -0
  187. package/src/health/tracing.ts +15 -0
  188. package/src/health/types.ts +51 -0
  189. package/src/helpers/bytes.ts +71 -0
  190. package/src/helpers/string.ts +2 -0
  191. package/src/http/HttpContext.ts +78 -4
  192. package/src/http/Request.ts +62 -10
  193. package/src/http/Response.ts +283 -14
  194. package/src/http/SseStream.ts +10 -0
  195. package/src/index.ts +27 -1
  196. package/src/migrations/MigrationRegistry.ts +49 -0
  197. package/src/migrations/types.ts +68 -0
  198. package/src/router/Router.ts +241 -32
  199. package/src/security/CookieSigner.ts +92 -18
  200. package/src/session/Session.ts +71 -5
  201. package/src/session/SessionMiddleware.ts +19 -9
  202. package/src/session/drivers/CookieDriver.ts +4 -4
  203. package/src/session/drivers/DatabaseDriver.ts +3 -3
  204. package/src/session/drivers/FileDriver.ts +8 -6
  205. package/src/session/drivers/MemoryDriver.ts +2 -2
  206. package/src/session/drivers/RedisDriver.ts +12 -5
  207. package/tsconfig.app.json +13 -13
  208. package/tsconfig.base.json +19 -0
  209. package/tsconfig.client.json +16 -0
  210. package/tsconfig.package.json +15 -0
@@ -0,0 +1,68 @@
1
+ import v8 from 'node:v8'
2
+ import { BaseCheck } from '../BaseCheck.js'
3
+ import type { HealthCheckResult } from '../types.js'
4
+ import { assertPercentage, parseByteThreshold, runMemoryCheck } from './memory.js'
5
+
6
+ /**
7
+ * Watches the V8 heap (AdonisJS `MemoryHeapCheck`).
8
+ */
9
+ export class MemoryHeapCheck extends BaseCheck {
10
+ #warnThresholdBytes: number | null = 250 * 1024 ** 2
11
+ #failThresholdBytes: number | null = 300 * 1024 ** 2
12
+ #warnThresholdPercentage: number | null = null
13
+ #failThresholdPercentage: number | null = null
14
+ #computeFn: () => NodeJS.MemoryUsage = () => process.memoryUsage()
15
+
16
+ name = 'Memory heap check'
17
+
18
+ /** Warn above this size — `'300 mb'` or a byte count. Clears any percentage. */
19
+ warnWhenExceeds(value: string | number): this {
20
+ this.#warnThresholdBytes = parseByteThreshold(value, 'warnWhenExceeds')
21
+ this.#warnThresholdPercentage = null
22
+ return this
23
+ }
24
+
25
+ /** Fail above this size. Clears any percentage. */
26
+ failWhenExceeds(value: string | number): this {
27
+ this.#failThresholdBytes = parseByteThreshold(value, 'failWhenExceeds')
28
+ this.#failThresholdPercentage = null
29
+ return this
30
+ }
31
+
32
+ /** Warn above this share of the heap ceiling. Clears any byte threshold. */
33
+ warnWhenExceedsPercentage(valueInPercentage: number): this {
34
+ assertPercentage(valueInPercentage, 'Warn')
35
+ this.#warnThresholdPercentage = valueInPercentage
36
+ this.#warnThresholdBytes = null
37
+ return this
38
+ }
39
+
40
+ /** Fail above this share of the heap ceiling. Clears any byte threshold. */
41
+ failWhenExceedsPercentage(valueInPercentage: number): this {
42
+ assertPercentage(valueInPercentage, 'Fail')
43
+ this.#failThresholdPercentage = valueInPercentage
44
+ this.#failThresholdBytes = null
45
+ return this
46
+ }
47
+
48
+ /** Replace how the reading is taken, so a test can drive the thresholds. */
49
+ compute(callback: () => NodeJS.MemoryUsage): this {
50
+ this.#computeFn = callback
51
+ return this
52
+ }
53
+
54
+ async run(): Promise<HealthCheckResult> {
55
+ const { heapUsed } = this.#computeFn()
56
+ return runMemoryCheck(
57
+ heapUsed,
58
+ {
59
+ warnBytes: this.#warnThresholdBytes,
60
+ failBytes: this.#failThresholdBytes,
61
+ warnPercentage: this.#warnThresholdPercentage,
62
+ failPercentage: this.#failThresholdPercentage,
63
+ },
64
+ { subject: 'Heap', ceilingKey: 'heapInBytes', ceilingName: 'maxHeapSize' },
65
+ () => v8.getHeapStatistics().heap_size_limit,
66
+ )
67
+ }
68
+ }
@@ -0,0 +1,68 @@
1
+ import os from 'node:os'
2
+ import { BaseCheck } from '../BaseCheck.js'
3
+ import type { HealthCheckResult } from '../types.js'
4
+ import { assertPercentage, parseByteThreshold, runMemoryCheck } from './memory.js'
5
+
6
+ /**
7
+ * Watches the process' resident set size (AdonisJS `MemoryRSSCheck`).
8
+ */
9
+ export class MemoryRSSCheck extends BaseCheck {
10
+ #warnThresholdBytes: number | null = 320 * 1024 ** 2
11
+ #failThresholdBytes: number | null = 350 * 1024 ** 2
12
+ #warnThresholdPercentage: number | null = null
13
+ #failThresholdPercentage: number | null = null
14
+ #computeFn: () => NodeJS.MemoryUsage = () => process.memoryUsage()
15
+
16
+ name = 'Memory RSS check'
17
+
18
+ /** Warn above this size — `'300 mb'` or a byte count. Clears any percentage. */
19
+ warnWhenExceeds(value: string | number): this {
20
+ this.#warnThresholdBytes = parseByteThreshold(value, 'warnWhenExceeds')
21
+ this.#warnThresholdPercentage = null
22
+ return this
23
+ }
24
+
25
+ /** Fail above this size. Clears any percentage. */
26
+ failWhenExceeds(value: string | number): this {
27
+ this.#failThresholdBytes = parseByteThreshold(value, 'failWhenExceeds')
28
+ this.#failThresholdPercentage = null
29
+ return this
30
+ }
31
+
32
+ /** Warn above this share of total system memory. Clears any byte threshold. */
33
+ warnWhenExceedsPercentage(valueInPercentage: number): this {
34
+ assertPercentage(valueInPercentage, 'Warn')
35
+ this.#warnThresholdPercentage = valueInPercentage
36
+ this.#warnThresholdBytes = null
37
+ return this
38
+ }
39
+
40
+ /** Fail above this share of total system memory. Clears any byte threshold. */
41
+ failWhenExceedsPercentage(valueInPercentage: number): this {
42
+ assertPercentage(valueInPercentage, 'Fail')
43
+ this.#failThresholdPercentage = valueInPercentage
44
+ this.#failThresholdBytes = null
45
+ return this
46
+ }
47
+
48
+ /** Replace how the reading is taken, so a test can drive the thresholds. */
49
+ compute(callback: () => NodeJS.MemoryUsage): this {
50
+ this.#computeFn = callback
51
+ return this
52
+ }
53
+
54
+ async run(): Promise<HealthCheckResult> {
55
+ const { rss } = this.#computeFn()
56
+ return runMemoryCheck(
57
+ rss,
58
+ {
59
+ warnBytes: this.#warnThresholdBytes,
60
+ failBytes: this.#failThresholdBytes,
61
+ warnPercentage: this.#warnThresholdPercentage,
62
+ failPercentage: this.#failThresholdPercentage,
63
+ },
64
+ { subject: 'RSS', ceilingKey: 'memoryInBytes', ceilingName: 'totalSystemMemory' },
65
+ () => os.totalmem(),
66
+ )
67
+ }
68
+ }
@@ -0,0 +1,115 @@
1
+ import bytes from '../../helpers/bytes.js'
2
+ import { Result } from '../Result.js'
3
+ import type { HealthCheckResult } from '../types.js'
4
+
5
+ /**
6
+ * The shared body of the two memory checks.
7
+ *
8
+ * Upstream spells this logic out twice, once in `MemoryRSSCheck` and once in
9
+ * `MemoryHeapCheck`; only the reading, the ceiling it is compared against, and
10
+ * two strings differ. The public surface of both classes is unchanged — this is
11
+ * an internal detail, not a deviation.
12
+ */
13
+ export type MemoryThresholds = {
14
+ warnBytes: number | null
15
+ failBytes: number | null
16
+ warnPercentage: number | null
17
+ failPercentage: number | null
18
+ }
19
+
20
+ export function runMemoryCheck(
21
+ used: number,
22
+ thresholds: MemoryThresholds,
23
+ labels: { subject: string; ceilingKey: string; ceilingName: string },
24
+ readCeiling: () => number,
25
+ ): HealthCheckResult {
26
+ const compareAsPercentage =
27
+ thresholds.warnPercentage !== null || thresholds.failPercentage !== null
28
+
29
+ let warnThreshold: number
30
+ let failThreshold: number
31
+ let valueToCompare: number
32
+ let ceiling: number | null = null
33
+ let usedPercentage: number | null = null
34
+
35
+ if (compareAsPercentage) {
36
+ ceiling = readCeiling()
37
+ if (ceiling === 0) {
38
+ return Result.failed(`Cannot determine ${labels.ceilingName} (0 bytes).`).mergeMetaData({
39
+ [labels.ceilingKey]: { used, [labels.ceilingName]: ceiling },
40
+ })
41
+ }
42
+ usedPercentage = Math.floor((used / ceiling) * 100)
43
+ valueToCompare = usedPercentage
44
+
45
+ if (thresholds.warnPercentage !== null) warnThreshold = thresholds.warnPercentage
46
+ else if (thresholds.warnBytes !== null)
47
+ warnThreshold = Math.floor((thresholds.warnBytes / ceiling) * 100)
48
+ else return Result.failed('Warning threshold for percentage comparison is missing.')
49
+
50
+ if (thresholds.failPercentage !== null) failThreshold = thresholds.failPercentage
51
+ else if (thresholds.failBytes !== null)
52
+ failThreshold = Math.floor((thresholds.failBytes / ceiling) * 100)
53
+ else return Result.failed('Failure threshold for percentage comparison is missing.')
54
+ } else {
55
+ valueToCompare = used
56
+ if (thresholds.warnBytes === null) return Result.failed('Warning threshold (bytes) is missing.')
57
+ warnThreshold = thresholds.warnBytes
58
+ if (thresholds.failBytes === null) return Result.failed('Failure threshold (bytes) is missing.')
59
+ failThreshold = thresholds.failBytes
60
+ }
61
+
62
+ const metaData: Record<string, unknown> = {}
63
+ if (compareAsPercentage) {
64
+ metaData.sizeInPercentage = {
65
+ used: usedPercentage,
66
+ failureThreshold: failThreshold,
67
+ warningThreshold: warnThreshold,
68
+ }
69
+ if (ceiling !== null) {
70
+ metaData[labels.ceilingKey] = {
71
+ used,
72
+ [labels.ceilingName]: ceiling,
73
+ failureThreshold: Math.floor((failThreshold / 100) * ceiling),
74
+ warningThreshold: Math.floor((warnThreshold / 100) * ceiling),
75
+ }
76
+ }
77
+ } else {
78
+ metaData.memoryInBytes = {
79
+ used,
80
+ failureThreshold: failThreshold,
81
+ warningThreshold: warnThreshold,
82
+ }
83
+ }
84
+
85
+ const show = (value: number, threshold: number) =>
86
+ compareAsPercentage
87
+ ? { used: `${value}%`, limit: `${threshold}%` }
88
+ : { used: bytes.format(used), limit: bytes.format(threshold) }
89
+
90
+ if (valueToCompare >= failThreshold) {
91
+ const { used: u, limit } = show(valueToCompare, failThreshold)
92
+ return Result.failed(
93
+ `${labels.subject} usage is ${u}, which is above the threshold of ${limit}`,
94
+ ).mergeMetaData(metaData)
95
+ }
96
+ if (valueToCompare >= warnThreshold) {
97
+ const { used: u, limit } = show(valueToCompare, warnThreshold)
98
+ return Result.warning(
99
+ `${labels.subject} usage is ${u}, which is above the threshold of ${limit}`,
100
+ ).mergeMetaData(metaData)
101
+ }
102
+ return Result.ok(`${labels.subject} usage is under defined thresholds`).mergeMetaData(metaData)
103
+ }
104
+
105
+ /** The threshold setters both memory checks expose, shared verbatim. */
106
+ export function parseByteThreshold(value: string | number, method: string): number {
107
+ const parsed = bytes.parse(value)
108
+ if (parsed === null) throw new Error(`Invalid byte value for ${method}: ${value}`)
109
+ return parsed
110
+ }
111
+
112
+ export function assertPercentage(value: number, label: string): void {
113
+ if (value < 0 || value > 100)
114
+ throw new Error(`${label} threshold percentage must be between 0 and 100.`)
115
+ }
@@ -0,0 +1,13 @@
1
+ export { BaseCheck } from './BaseCheck.js'
2
+ export { DiskSpaceCheck } from './checks/DiskSpaceCheck.js'
3
+ export { MemoryHeapCheck } from './checks/MemoryHeapCheck.js'
4
+ export { MemoryRSSCheck } from './checks/MemoryRSSCheck.js'
5
+ export { HealthChecks } from './HealthChecks.js'
6
+ export { Result } from './Result.js'
7
+ export * as tracingChannels from './tracing.js'
8
+ export type {
9
+ HealthCheckContract,
10
+ HealthCheckReport,
11
+ HealthCheckResult,
12
+ HealthCheckTracingData,
13
+ } from './types.js'
@@ -0,0 +1,15 @@
1
+ import diagnostics_channel from 'node:diagnostics_channel'
2
+ import type { HealthCheckTracingData } from './types.js'
3
+
4
+ /**
5
+ * A diagnostics channel carrying every check run, so an APM can time checks
6
+ * without the app wiring anything.
7
+ *
8
+ * NAMED DEVIATION — the channel is `ream.health.check`, not upstream's
9
+ * `adonisjs.health.check`. A channel name is product identity, and a subscriber
10
+ * listening for both frameworks in one process must be able to tell them apart.
11
+ */
12
+ export const healthCheck = diagnostics_channel.tracingChannel<
13
+ 'ream.health.check',
14
+ HealthCheckTracingData
15
+ >('ream.health.check')
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Health-check contracts, mirroring `@adonisjs/core/health`.
3
+ */
4
+
5
+ /** The outcome of running one check. */
6
+ export type HealthCheckResult = {
7
+ /** A summary of the check result. */
8
+ message: string
9
+ /** The status of the check. */
10
+ status: 'ok' | 'warning' | 'error'
11
+ /** When this check finished. */
12
+ finishedAt: Date
13
+ /** Free-form data the check wants to expose alongside its verdict. */
14
+ meta?: Record<string, unknown>
15
+ }
16
+
17
+ /** The report produced by running every registered check. */
18
+ export type HealthCheckReport = {
19
+ /** False as soon as one check reports `error`. A warning stays healthy. */
20
+ isHealthy: boolean
21
+ /** `error` beats `warning` beats `ok`. */
22
+ status: 'ok' | 'warning' | 'error'
23
+ /** When the whole report was computed. */
24
+ finishedAt: Date
25
+ /** Identifying data for the process that produced the report. */
26
+ debugInfo: {
27
+ pid: number
28
+ ppid?: number
29
+ /** Seconds the process has been running. */
30
+ uptime: number
31
+ version: string
32
+ platform: string
33
+ }
34
+ /** Each check's result, plus whether it came from the cache. */
35
+ checks: ({ isCached: boolean; name: string } & HealthCheckResult)[]
36
+ }
37
+
38
+ /** What every check must implement. */
39
+ export interface HealthCheckContract {
40
+ /** A unique name for the check. */
41
+ name: string
42
+ /** Seconds to reuse a previous result for. Undefined means never cache. */
43
+ cacheDuration?: number
44
+ /** Perform the check. */
45
+ run(): Promise<HealthCheckResult>
46
+ }
47
+
48
+ /** What a tracing subscriber receives. */
49
+ export type HealthCheckTracingData = {
50
+ check: HealthCheckContract
51
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Byte-size parsing and formatting, as `string.bytes` in AdonisJS.
3
+ *
4
+ * Upstream reaches for `@poppinss/string`, which wraps the `bytes` package.
5
+ * Ream carries the ~40 lines instead of the dependency, matching that package's
6
+ * semantics exactly: binary units (1 kb = 1024 b), a bare number string read as
7
+ * bytes, and `null` — never a throw — when the input cannot be read.
8
+ */
9
+
10
+ const UNITS = {
11
+ b: 1,
12
+ kb: 1024,
13
+ mb: 1024 ** 2,
14
+ gb: 1024 ** 3,
15
+ tb: 1024 ** 4,
16
+ pb: 1024 ** 5,
17
+ } as const
18
+
19
+ type Unit = keyof typeof UNITS
20
+
21
+ const PARSE = /^((?:-|\+)?(?:\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i
22
+ /** Trailing `.00`, or the zeros trailing a real decimal. */
23
+ const TRAILING_ZEROS = /(?:\.0*|(\.[^0]+)0+)$/
24
+
25
+ /**
26
+ * Read a human-readable size into a number of bytes.
27
+ *
28
+ * A number passes through untouched. A string with no unit is read as bytes,
29
+ * so `'1024'` and `1024` agree. Returns `null` when there is no number to find.
30
+ */
31
+ export function parse(value: string | number): number | null {
32
+ if (typeof value === 'number') return Number.isNaN(value) ? null : value
33
+ if (typeof value !== 'string') return null
34
+
35
+ const matched = PARSE.exec(value)
36
+ const amount = matched ? Number.parseFloat(matched[1]) : Number.parseInt(value, 10)
37
+ if (Number.isNaN(amount)) return null
38
+
39
+ const unit = (matched ? matched[2].toLowerCase() : 'b') as Unit
40
+ return Math.floor(UNITS[unit] * amount)
41
+ }
42
+
43
+ /**
44
+ * Render a number of bytes for a human, picking the largest unit that leaves a
45
+ * value at or above 1. Returns `null` for a non-finite input.
46
+ */
47
+ export function format(valueInBytes: number, options?: { decimalPlaces?: number }): string | null {
48
+ if (!Number.isFinite(valueInBytes)) return null
49
+
50
+ const magnitude = Math.abs(valueInBytes)
51
+ const unit: Unit =
52
+ magnitude >= UNITS.pb
53
+ ? 'pb'
54
+ : magnitude >= UNITS.tb
55
+ ? 'tb'
56
+ : magnitude >= UNITS.gb
57
+ ? 'gb'
58
+ : magnitude >= UNITS.mb
59
+ ? 'mb'
60
+ : magnitude >= UNITS.kb
61
+ ? 'kb'
62
+ : 'b'
63
+
64
+ const rendered = (valueInBytes / UNITS[unit])
65
+ .toFixed(options?.decimalPlaces ?? 2)
66
+ .replace(TRAILING_ZEROS, '$1')
67
+
68
+ return `${rendered}${unit.toUpperCase()}`
69
+ }
70
+
71
+ export default { parse, format }
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto'
2
+ import bytes from './bytes.js'
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // Internal helpers
@@ -441,6 +442,7 @@ export function interpolate(template: string, data: Record<string, unknown>): st
441
442
  // ---------------------------------------------------------------------------
442
443
 
443
444
  const string = {
445
+ bytes,
444
446
  camelCase,
445
447
  snakeCase,
446
448
  pascalCase,
@@ -168,9 +168,31 @@ export interface RouteInfo {
168
168
  action?: string
169
169
  }
170
170
 
171
+ /**
172
+ * The `'ControllerName.method'` identity of a route, or undefined for an inline
173
+ * handler. This is what `request.matchesRoute()` matches on besides the name and
174
+ * the pattern, the way AdonisJS matches `route.handler.reference`.
175
+ */
176
+ function controllerReference(route: RouteInfo): string | undefined {
177
+ const controller = route.controller as { name?: string } | undefined
178
+ if (!controller?.name || !route.action) return undefined
179
+ return `${controller.name}.${route.action}`
180
+ }
181
+
171
182
  /** Ambient per-request context store (AdonisJS `HttpContext` ALS accessor). */
172
183
  const httpContextStorage = new AsyncLocalStorage<HttpContext>()
173
184
 
185
+ /**
186
+ * Whether the ambient context is being tracked.
187
+ *
188
+ * NAMED DEVIATION — on by default, where AdonisJS makes it opt-in through
189
+ * `useAsyncLocalStorage` in `config/app.ts`. Ream's own middleware and service
190
+ * accessors read the ambient context, so defaulting it off would break the
191
+ * framework's own wiring rather than just an app's. Turn it off with
192
+ * {@link HttpContext.useAsyncLocalStorage} to reclaim the tracking cost.
193
+ */
194
+ let asyncLocalStorageEnabled = true
195
+
174
196
  export class HttpContext extends Macroable {
175
197
  /**
176
198
  * @internal Run `fn` with `ctx` as the ambient HttpContext, so code deep in
@@ -179,16 +201,50 @@ export class HttpContext extends Macroable {
179
201
  * request pipeline in this.
180
202
  */
181
203
  static run<T>(ctx: HttpContext, fn: () => T): T {
204
+ if (!asyncLocalStorageEnabled) return fn()
182
205
  return httpContextStorage.run(ctx, fn)
183
206
  }
184
207
 
185
- /** The ambient HttpContext, or `undefined` outside a request (AdonisJS `HttpContext.get()`). */
186
- static get(): HttpContext | undefined {
187
- return httpContextStorage.getStore()
208
+ /**
209
+ * Whether the ambient context is being tracked (AdonisJS
210
+ * `HttpContext.usingAsyncLocalStorage`). When false, {@link get} always
211
+ * answers `null` and {@link getOrFail} always throws.
212
+ */
213
+ static get usingAsyncLocalStorage(): boolean {
214
+ return asyncLocalStorageEnabled
215
+ }
216
+
217
+ /**
218
+ * Turn ambient-context tracking on or off.
219
+ *
220
+ * AdonisJS decides this from `useAsyncLocalStorage` in `config/app.ts`; ream
221
+ * has it on by default (see the note on the storage above) and exposes the
222
+ * switch instead of a config key, so a benchmark or a worker process can
223
+ * drop the tracking without a config file.
224
+ */
225
+ static useAsyncLocalStorage(enabled: boolean): void {
226
+ asyncLocalStorageEnabled = enabled
227
+ }
228
+
229
+ /**
230
+ * The ambient HttpContext, or `null` outside a request (AdonisJS
231
+ * `HttpContext.get()`).
232
+ *
233
+ * `null`, not `undefined`: upstream's contract is `HttpContext | null`, and a
234
+ * migrated `=== null` check has to keep working.
235
+ */
236
+ static get(): HttpContext | null {
237
+ if (!asyncLocalStorageEnabled) return null
238
+ return httpContextStorage.getStore() ?? null
188
239
  }
189
240
 
190
241
  /** The ambient HttpContext, or throw outside a request (AdonisJS `HttpContext.getOrFail()`). */
191
242
  static getOrFail(): HttpContext {
243
+ if (!asyncLocalStorageEnabled) {
244
+ throw new Error(
245
+ '[E_HTTP_CONTEXT_NOT_FOUND] HttpContext.getOrFail() called while ambient-context tracking is off. Re-enable it with HttpContext.useAsyncLocalStorage(true), or pass ctx explicitly.',
246
+ )
247
+ }
192
248
  const ctx = httpContextStorage.getStore()
193
249
  if (!ctx) {
194
250
  throw new Error(
@@ -207,6 +263,17 @@ export class HttpContext extends Macroable {
207
263
  return httpContextStorage.exit(fn)
208
264
  }
209
265
 
266
+ /**
267
+ * A one-line summary of the request (AdonisJS `inspect`).
268
+ *
269
+ * What a log line or a debugger label wants: the verb, the path, and which
270
+ * route answered.
271
+ */
272
+ inspect(): string {
273
+ const route = this.route.name ?? this.route.pattern
274
+ return `${this.request.method()} ${this.request.url()} (${route})`
275
+ }
276
+
210
277
  /** Unique request/correlation ID. */
211
278
  readonly id: string
212
279
 
@@ -343,7 +410,14 @@ export class HttpContext extends Macroable {
343
410
  // And the reverse, so `request.fresh()`/`stale()` delegate to it, plus the
344
411
  // matched-route info for `request.matchesRoute()`.
345
412
  this.request.setResponse(this.response)
346
- this.request.setRouteInfo({ name: route.name, pattern: route.pattern })
413
+ // The back-references AdonisJS exposes as `request.ctx` / `response.ctx`.
414
+ this.request.ctx = this
415
+ this.response.ctx = this
416
+ this.request.setRouteInfo({
417
+ name: route.name,
418
+ pattern: route.pattern,
419
+ reference: controllerReference(route),
420
+ })
347
421
  // APP_KEY-backed encryption / signed-URL services + the base logger are
348
422
  // resolved asynchronously by HttpKernel and injected via the setters above
349
423
  // (the container is async now, so a constructor can't resolve them itself).
@@ -66,7 +66,7 @@ export class Request extends Macroable {
66
66
  #signedUrl?: SignedUrl
67
67
  #allowMethodSpoofing = false
68
68
  #trustProxy = false
69
- #routeInfo?: { name?: string; pattern: string }
69
+ #routeInfo?: { name?: string; pattern: string; reference?: string }
70
70
  #response?: { fresh(): boolean }
71
71
  #parsedBody: Dict<unknown> | undefined
72
72
  #parsedQs: Dict<unknown> | undefined
@@ -122,13 +122,32 @@ export class Request extends Macroable {
122
122
  return this.#raw.method
123
123
  }
124
124
 
125
+ /**
126
+ * The context this request belongs to (AdonisJS `request.ctx`).
127
+ *
128
+ * Wired by HttpContext. Optional because a Request built by hand — a test,
129
+ * a fixture — has no context around it.
130
+ */
131
+ ctx?: object
132
+
133
+ /**
134
+ * Seat the parsed body (AdonisJS `setInitialBody`).
135
+ *
136
+ * What a body parser calls once it has decoded the payload. An alias of
137
+ * {@link setParsedBody}, which is the name ream used first — both are here so
138
+ * a middleware written against either resolves.
139
+ */
140
+ setInitialBody(body: Dict<unknown>): void {
141
+ this.setParsedBody(body)
142
+ }
143
+
125
144
  /** @internal Give the request access to its response — wired by HttpContext (for `fresh()`). */
126
145
  setResponse(response: { fresh(): boolean }): void {
127
146
  this.#response = response
128
147
  }
129
148
 
130
149
  /** @internal Record the matched route — wired by HttpContext (for `matchesRoute()`). */
131
- setRouteInfo(info: { name?: string; pattern: string }): void {
150
+ setRouteInfo(info: { name?: string; pattern: string; reference?: string }): void {
132
151
  this.#routeInfo = info
133
152
  }
134
153
 
@@ -146,10 +165,26 @@ export class Request extends Macroable {
146
165
  return !this.fresh()
147
166
  }
148
167
 
149
- /** True when the matched route's name or pattern equals `identifier` (AdonisJS `matchesRoute`). */
150
- matchesRoute(identifier: string): boolean {
151
- if (!this.#routeInfo) return false
152
- return this.#routeInfo.name === identifier || this.#routeInfo.pattern === identifier
168
+ /**
169
+ * True when the matched route's name, pattern, or controller reference is one
170
+ * of `identifier` (AdonisJS `matchesRoute`).
171
+ *
172
+ * Takes a list as well as a single value, as upstream does — the usual call
173
+ * is "am I on any of these routes", and a caller with several had to write
174
+ * the `.some()` themselves.
175
+ *
176
+ * The reference is the `'ControllerName.method'` form the router already
177
+ * accepts as a handler, which is the identity AdonisJS matches on through
178
+ * `route.handler.reference`. Absent for an inline handler, which has no name
179
+ * to be addressed by.
180
+ */
181
+ matchesRoute(identifier: string | string[]): boolean {
182
+ const info = this.#routeInfo
183
+ if (!info) return false
184
+ const candidates = Array.isArray(identifier) ? identifier : [identifier]
185
+ return candidates.some(
186
+ (one) => info.name === one || info.pattern === one || info.reference === one,
187
+ )
153
188
  }
154
189
 
155
190
  /**
@@ -161,8 +196,15 @@ export class Request extends Macroable {
161
196
  this.#allowMethodSpoofing = enabled
162
197
  }
163
198
 
164
- /** Request URL (path + query string). */
165
- url(includeQs = true): string {
199
+ /**
200
+ * The request URL — the PATHNAME by default, as in AdonisJS
201
+ * (`url(includeQueryString)`, false unless asked). Ream defaulted to
202
+ * including the query string, so a value used as a cache key, a log line or
203
+ * a route comparison silently carried the query with it.
204
+ *
205
+ * Pass `true` for path + query; {@link completeUrl} adds the origin.
206
+ */
207
+ url(includeQs = false): string {
166
208
  if (includeQs && this.#raw.query) {
167
209
  return `${this.#raw.path}?${this.#raw.query}`
168
210
  }
@@ -330,6 +372,10 @@ export class Request extends Macroable {
330
372
  * Signed cookie value, verified with APP_KEY (AdonisJS default). Returns
331
373
  * `defaultValue` (or null) when absent OR when the signature is invalid
332
374
  * (tampered / not signed).
375
+ *
376
+ * NAMED DEVIATION — the fallback is `??`, where AdonisJS writes `||`. A
377
+ * cookie legitimately holding `0`, `false` or `""` is a value, not an
378
+ * absence, and upstream hands back the default for all three. Kept.
333
379
  */
334
380
  cookie(name: string): string | null
335
381
  cookie(name: string, defaultValue: string): string
@@ -398,7 +444,9 @@ export class Request extends Macroable {
398
444
  */
399
445
  hasValidSignature(purpose?: string): boolean {
400
446
  if (!this.#signedUrl) return false
401
- return this.#signedUrl.verify(this.url(), purpose)
447
+ // `url(true)`: the signature covers the path AND the query string —
448
+ // the expiry and the signature itself live in the query.
449
+ return this.#signedUrl.verify(this.url(true), purpose)
402
450
  }
403
451
 
404
452
  /** Get the raw body as a string (decoded from base64 if binary). */
@@ -683,7 +731,11 @@ export class Request extends Macroable {
683
731
  all(): Dict<unknown> {
684
732
  if (!this.#merged) {
685
733
  this.#ensureParsedBody()
686
- this.#merged = { ...this.qs(), ...this.#parsedBody }
734
+ // `{ ...body, ...qs }` — the QUERY STRING wins, as in AdonisJS
735
+ // (`#requestData = { ...#requestBody, ...#requestQs }`). Ream had it the
736
+ // other way round, so `?id=1` with a body `{ id: 2 }` read 2 here and 1
737
+ // there — silently, on the request field an app trusts most.
738
+ this.#merged = { ...this.#parsedBody, ...this.qs() }
687
739
  // Snapshot the first-seen input as the immutable "original" (flash old-input).
688
740
  if (this.#original === undefined) this.#original = { ...this.#merged }
689
741
  }