@c9up/ream 0.1.33 → 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.
- package/dist/Application.d.ts +68 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +142 -6
- package/dist/Application.js.map +1 -1
- package/dist/ConfigLoader.d.ts +4 -0
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +0 -8
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HealthCheck.d.ts +14 -7
- package/dist/HealthCheck.d.ts.map +1 -1
- package/dist/HealthCheck.js +21 -12
- package/dist/HealthCheck.js.map +1 -1
- package/dist/HttpKernel.d.ts +12 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +15 -4
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +8 -0
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +11 -0
- package/dist/Ignitor.js.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.d.ts +13 -2
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.js +50 -30
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
- package/dist/bodyparser/MultipartFile.d.ts +65 -6
- package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
- package/dist/bodyparser/MultipartFile.js +100 -7
- package/dist/bodyparser/MultipartFile.js.map +1 -1
- package/dist/console/BaseCommand.d.ts +8 -1
- package/dist/console/BaseCommand.d.ts.map +1 -1
- package/dist/console/BaseCommand.js +1 -1
- package/dist/console/BaseCommand.js.map +1 -1
- package/dist/console/Kernel.d.ts +15 -0
- package/dist/console/Kernel.d.ts.map +1 -1
- package/dist/console/Kernel.js +17 -0
- package/dist/console/Kernel.js.map +1 -1
- package/dist/directories.d.ts +52 -0
- package/dist/directories.d.ts.map +1 -0
- package/dist/directories.js +38 -0
- package/dist/directories.js.map +1 -0
- package/dist/events/Emitter.d.ts +49 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +117 -7
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsBuffer.d.ts +47 -0
- package/dist/events/EventsBuffer.d.ts.map +1 -0
- package/dist/events/EventsBuffer.js +107 -0
- package/dist/events/EventsBuffer.js.map +1 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +1 -0
- package/dist/events/index.js.map +1 -1
- package/dist/health/BaseCheck.d.ts +19 -0
- package/dist/health/BaseCheck.d.ts.map +1 -0
- package/dist/health/BaseCheck.js +24 -0
- package/dist/health/BaseCheck.js.map +1 -0
- package/dist/health/HealthChecks.d.ts +15 -0
- package/dist/health/HealthChecks.d.ts.map +1 -0
- package/dist/health/HealthChecks.js +69 -0
- package/dist/health/HealthChecks.js.map +1 -0
- package/dist/health/Result.d.ts +27 -0
- package/dist/health/Result.d.ts.map +1 -0
- package/dist/health/Result.js +55 -0
- package/dist/health/Result.js.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts +29 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.js +58 -0
- package/dist/health/checks/DiskSpaceCheck.js.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts +21 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.js +55 -0
- package/dist/health/checks/MemoryHeapCheck.js.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts +21 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.js +55 -0
- package/dist/health/checks/MemoryRSSCheck.js.map +1 -0
- package/dist/health/checks/memory.d.ts +24 -0
- package/dist/health/checks/memory.d.ts.map +1 -0
- package/dist/health/checks/memory.js +88 -0
- package/dist/health/checks/memory.js.map +1 -0
- package/dist/health/index.d.ts +9 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +8 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/tracing.d.ts +12 -0
- package/dist/health/tracing.d.ts.map +1 -0
- package/dist/health/tracing.js +11 -0
- package/dist/health/tracing.js.map +1 -0
- package/dist/health/types.d.ts +51 -0
- package/dist/health/types.d.ts.map +1 -0
- package/dist/health/types.js +5 -0
- package/dist/health/types.js.map +1 -0
- package/dist/helpers/bytes.d.ts +28 -0
- package/dist/helpers/bytes.d.ts.map +1 -0
- package/dist/helpers/bytes.js +63 -0
- package/dist/helpers/bytes.js.map +1 -0
- package/dist/helpers/string.d.ts +4 -0
- package/dist/helpers/string.d.ts.map +1 -1
- package/dist/helpers/string.js +2 -0
- package/dist/helpers/string.js.map +1 -1
- package/dist/http/HttpContext.d.ts +38 -2
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +81 -3
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/Request.d.ts +57 -3
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +51 -38
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +82 -2
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +240 -13
- package/dist/http/Response.js.map +1 -1
- package/dist/http/SseStream.d.ts +10 -0
- package/dist/http/SseStream.d.ts.map +1 -1
- package/dist/http/SseStream.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/MigrationRegistry.d.ts +28 -0
- package/dist/migrations/MigrationRegistry.d.ts.map +1 -0
- package/dist/migrations/MigrationRegistry.js +41 -0
- package/dist/migrations/MigrationRegistry.js.map +1 -0
- package/dist/migrations/types.d.ts +64 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +15 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/router/Router.d.ts +112 -9
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +206 -28
- package/dist/router/Router.js.map +1 -1
- package/dist/security/CookieSigner.d.ts +43 -4
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +68 -10
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/session/Session.d.ts +46 -1
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +57 -4
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +19 -9
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/drivers/CookieDriver.d.ts +1 -1
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +3 -3
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
- package/dist/session/drivers/DatabaseDriver.js +2 -2
- package/dist/session/drivers/DatabaseDriver.js.map +1 -1
- package/dist/session/drivers/FileDriver.d.ts +1 -1
- package/dist/session/drivers/FileDriver.d.ts.map +1 -1
- package/dist/session/drivers/FileDriver.js +7 -5
- package/dist/session/drivers/FileDriver.js.map +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +1 -1
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +1 -1
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +11 -4
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +5 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +169 -6
- package/src/ConfigLoader.ts +6 -0
- package/src/HealthCheck.ts +27 -14
- package/src/HttpKernel.ts +27 -4
- package/src/Ignitor.ts +17 -0
- package/src/bodyparser/BodyParserMiddleware.ts +70 -36
- package/src/bodyparser/MultipartFile.ts +125 -8
- package/src/console/BaseCommand.ts +8 -1
- package/src/console/Kernel.ts +18 -0
- package/src/directories.ts +81 -0
- package/src/events/Emitter.ts +129 -13
- package/src/events/EventsBuffer.ts +131 -0
- package/src/events/index.ts +7 -0
- package/src/health/BaseCheck.ts +30 -0
- package/src/health/HealthChecks.ts +83 -0
- package/src/health/Result.ts +69 -0
- package/src/health/checks/DiskSpaceCheck.ts +69 -0
- package/src/health/checks/MemoryHeapCheck.ts +68 -0
- package/src/health/checks/MemoryRSSCheck.ts +68 -0
- package/src/health/checks/memory.ts +115 -0
- package/src/health/index.ts +13 -0
- package/src/health/tracing.ts +15 -0
- package/src/health/types.ts +51 -0
- package/src/helpers/bytes.ts +71 -0
- package/src/helpers/string.ts +2 -0
- package/src/http/HttpContext.ts +86 -4
- package/src/http/Request.ts +82 -10
- package/src/http/Response.ts +283 -14
- package/src/http/SseStream.ts +10 -0
- package/src/index.ts +27 -1
- package/src/migrations/MigrationRegistry.ts +49 -0
- package/src/migrations/types.ts +68 -0
- package/src/router/Router.ts +241 -32
- package/src/security/CookieSigner.ts +92 -18
- package/src/session/Session.ts +71 -5
- package/src/session/SessionMiddleware.ts +19 -9
- package/src/session/drivers/CookieDriver.ts +4 -4
- package/src/session/drivers/DatabaseDriver.ts +3 -3
- package/src/session/drivers/FileDriver.ts +8 -6
- package/src/session/drivers/MemoryDriver.ts +2 -2
- package/src/session/drivers/RedisDriver.ts +12 -5
- package/tsconfig.app.json +13 -13
- package/tsconfig.base.json +19 -0
- package/tsconfig.client.json +16 -0
- 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 }
|
package/src/helpers/string.ts
CHANGED
|
@@ -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,
|
package/src/http/HttpContext.ts
CHANGED
|
@@ -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
|
-
/**
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
|
@@ -218,6 +285,14 @@ export class HttpContext extends Macroable {
|
|
|
218
285
|
|
|
219
286
|
/** Route parameters extracted from the URL pattern. */
|
|
220
287
|
/** Matched route parameters — `*` holds the segments it swallowed, as an array. */
|
|
288
|
+
/**
|
|
289
|
+
* Route parameters.
|
|
290
|
+
*
|
|
291
|
+
* `string | string[]`, not AdonisJS's `any`: a catch-all `*` really is the
|
|
292
|
+
* ARRAY of segments on both sides, so the `any` upstream only hides it. Use
|
|
293
|
+
* {@link Request.param} when you want the single-value form — it joins a
|
|
294
|
+
* catch-all with `/` and returns `string | undefined`.
|
|
295
|
+
*/
|
|
221
296
|
readonly params: MatchedParams
|
|
222
297
|
|
|
223
298
|
/** Information about the matched route. */
|
|
@@ -335,7 +410,14 @@ export class HttpContext extends Macroable {
|
|
|
335
410
|
// And the reverse, so `request.fresh()`/`stale()` delegate to it, plus the
|
|
336
411
|
// matched-route info for `request.matchesRoute()`.
|
|
337
412
|
this.request.setResponse(this.response)
|
|
338
|
-
|
|
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
|
+
})
|
|
339
421
|
// APP_KEY-backed encryption / signed-URL services + the base logger are
|
|
340
422
|
// resolved asynchronously by HttpKernel and injected via the setters above
|
|
341
423
|
// (the container is async now, so a constructor can't resolve them itself).
|