@vpalmisano/throttler 0.0.7

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/src/config.ts ADDED
@@ -0,0 +1,179 @@
1
+ import convict, { addFormats } from 'convict'
2
+ import { ipaddress, url } from 'convict-format-with-validator'
3
+ import { existsSync } from 'fs'
4
+
5
+ import { logger } from './utils'
6
+ const log = logger('throttler:config')
7
+
8
+ const float = {
9
+ name: 'float',
10
+ coerce: (v: string) => parseFloat(v),
11
+ validate: (v: number) => {
12
+ if (!Number.isFinite(v)) throw new Error(`Invalid float: ${v}`)
13
+ },
14
+ }
15
+
16
+ const index = {
17
+ name: 'index',
18
+ coerce: (v: unknown) => v,
19
+ validate: (v: boolean | string | number) => {
20
+ if (typeof v === 'string') {
21
+ if (v === 'true' || v === 'false' || v === '') return
22
+ if (v.indexOf('-') !== -1) {
23
+ v.split('-').forEach(n => {
24
+ if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
25
+ throw new Error(`Invalid index: ${n}`)
26
+ })
27
+ return
28
+ }
29
+ if (v.indexOf(',') !== -1) {
30
+ v.split(',').forEach(n => {
31
+ if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
32
+ throw new Error(`Invalid index: ${n}`)
33
+ })
34
+ return
35
+ }
36
+ if (isNaN(parseInt(v)) || !isFinite(parseInt(v)))
37
+ throw new Error(`Invalid index: ${v}`)
38
+ } else if (typeof v === 'number' || typeof v === 'boolean') {
39
+ return
40
+ }
41
+ throw new Error(`Invalid index: ${v}`)
42
+ },
43
+ }
44
+
45
+ addFormats({ ipaddress, url, float, index })
46
+
47
+ // config schema
48
+ const configSchema = convict({
49
+ runDuration: {
50
+ doc: `If greater than 0, the test will stop after the provided number of \
51
+ seconds.`,
52
+ format: 'nat',
53
+ default: 0,
54
+ env: 'RUN_DURATION',
55
+ arg: 'run-duration',
56
+ },
57
+ throttleConfig: {
58
+ doc: `A JSON5 string with a valid network throttling configuration. \
59
+ Example: \
60
+
61
+ \`\`\`javascript
62
+ [{
63
+ sessions: '0-1',
64
+ device: 'eth0',
65
+ protocol: 'udp',
66
+ capture: 'capture.pcap',
67
+ up: {
68
+ rate: 1000,
69
+ delay: 50,
70
+ loss: 5,
71
+ queue: 10,
72
+ },
73
+ down: [
74
+ { rate: 2000, delay: 50, loss: 2, queue: 20 },
75
+ { rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },
76
+ ]
77
+ }]
78
+ \`\`\`
79
+ The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2". \
80
+ The device, protocol, up, down fields are optional. When device is not set, the default route device will be used. If protocol is specified ('udp' or 'tcp'), \
81
+ only the packets with the specified protocol will be affected by the shaping rules. \
82
+ \
83
+ `,
84
+ format: String,
85
+ nullable: true,
86
+ default: '',
87
+ env: 'THROTTLE_CONFIG',
88
+ arg: 'throttle-config',
89
+ },
90
+ commandConfig: {
91
+ doc: `The commands configuration.\
92
+ Example: \
93
+
94
+ \`\`\`javascript
95
+ [{
96
+ session: 0,
97
+ command: "firefox https://www.speedtest.net",
98
+ }]
99
+ \`\`\`
100
+ `,
101
+ format: String,
102
+ nullable: true,
103
+ default: '',
104
+ env: 'COMMAND_CONFIG',
105
+ arg: 'command-config',
106
+ },
107
+ })
108
+
109
+ type ConfigDocs = Record<
110
+ string,
111
+ { doc: string; format: string; default: string }
112
+ >
113
+
114
+ /**
115
+ * Formats the schema documentation, calling the same function recursively.
116
+ * @param docs the documentation object to extend
117
+ * @param property the root property
118
+ * @param schema the config schema fragment
119
+ * @return the documentation object
120
+ */
121
+ function formatDocs(
122
+ docs: ConfigDocs,
123
+ property: string | null,
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ schema: any,
126
+ ): ConfigDocs {
127
+ if (schema._cvtProperties) {
128
+ Object.entries(schema._cvtProperties).forEach(([name, value]) => {
129
+ formatDocs(docs, `${property ? `${property}.` : ''}${name}`, value)
130
+ })
131
+ return docs
132
+ }
133
+
134
+ if (property) {
135
+ docs[property] =
136
+ // eslint-disable-line no-param-reassign
137
+ {
138
+ doc: schema.doc,
139
+ format: JSON.stringify(schema.format, null, 2),
140
+ default: JSON.stringify(schema.default, null, 2),
141
+ }
142
+ }
143
+ return docs
144
+ }
145
+
146
+ /**
147
+ * It returns the formatted configuration docs.
148
+ */
149
+ export function getConfigDocs(): ConfigDocs {
150
+ return formatDocs({}, null, configSchema.getSchema())
151
+ }
152
+
153
+ const schemaProperties = configSchema.getProperties()
154
+
155
+ /** [[include:config.md]] */
156
+ export type Config = typeof schemaProperties
157
+
158
+ /**
159
+ * Loads the config object.
160
+ */
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ export function loadConfig(filePath?: string, values?: any): Config {
163
+ if (filePath && existsSync(filePath)) {
164
+ log.debug(`Loading config from ${filePath}`)
165
+ configSchema.loadFile(filePath)
166
+ } else if (values) {
167
+ log.debug('Loading config from values.')
168
+ configSchema.load(values)
169
+ } else {
170
+ log.debug('Using default values.')
171
+ configSchema.load({})
172
+ }
173
+
174
+ configSchema.validate({ allowed: 'strict' })
175
+ const config = configSchema.getProperties()
176
+
177
+ log.debug('Using config:', config)
178
+ return config
179
+ }
@@ -0,0 +1,51 @@
1
+ import { writeFile } from 'fs/promises'
2
+
3
+ import { getConfigDocs } from './config'
4
+
5
+ function formatJson(data: string): string {
6
+ return `\`${data.replace(/\n/g, '')}\``
7
+ }
8
+
9
+ let data = `
10
+ The configuration properties are applied in the following order (from higher to
11
+ lower precedence):
12
+
13
+ - arguments passed to the executable in kebab case (e.g. \`--run-duration\`);
14
+ - environment variables in uppercase snake format (e.g. \`RUN_DURATION\`);
15
+ - \`config.json\` configuration file;
16
+ - default values.
17
+
18
+ `
19
+
20
+ const configDocs = getConfigDocs()
21
+ Object.entries(configDocs).forEach(entry => {
22
+ const [name, value] = entry
23
+ data += `\
24
+ ## ${name}
25
+ ${value.doc}
26
+
27
+ *Type*: \`${
28
+ value.format === '"nat"'
29
+ ? 'positive int'
30
+ : value.format.replace(/^"(.+)"$/, '$1')
31
+ }\`
32
+
33
+ *Default*: ${formatJson(value.default)}
34
+
35
+ `
36
+ })
37
+
38
+ data += `
39
+
40
+ ---
41
+
42
+ `
43
+
44
+ writeFile('docs/config.md', data).then(
45
+ () => {
46
+ console.log('done'); // eslint-disable-line
47
+ },
48
+ err => {
49
+ console.error(`Error writing file: ${err.message}`); // eslint-disable-line
50
+ },
51
+ )
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './app'
2
+ export * from './config'
3
+ export * from './throttle'
4
+ export * from './utils'