dripfeed 0.1.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/cli.mjs ADDED
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+ import { _ as createJsonReporter, g as createMarkdownReporter, n as computeStats, s as loadDripfeedConfig, t as createSoakTest, u as createSqliteStorage, v as createConsoleReporter } from "./runner-ByEGj869.mjs";
3
+ import { defineCommand, runMain } from "citty";
4
+ //#region src/cli.ts
5
+ const VALID_REPORT_FORMATS = [
6
+ "console",
7
+ "json",
8
+ "markdown"
9
+ ];
10
+ const VALID_EXPORT_FORMATS = ["csv", "json"];
11
+ const validateFormat = (format, valid, command) => {
12
+ if (!valid.includes(format)) {
13
+ process.stderr.write(`Unsupported format "${format}" for ${command}. Use: ${valid.join(", ")}\n`);
14
+ process.exit(1);
15
+ }
16
+ };
17
+ runMain(defineCommand({
18
+ meta: {
19
+ name: "dripfeed",
20
+ version: "0.1.0",
21
+ description: "SQLite-native API soak testing. Drip, not firehose."
22
+ },
23
+ subCommands: {
24
+ run: defineCommand({
25
+ meta: {
26
+ name: "run",
27
+ description: "Start a soak test"
28
+ },
29
+ args: {
30
+ duration: {
31
+ type: "string",
32
+ alias: "d",
33
+ description: "Test duration (e.g. \"30s\", \"5m\", \"2h\")"
34
+ },
35
+ interval: {
36
+ type: "string",
37
+ alias: "i",
38
+ description: "Request interval (e.g. \"3s\", \"500ms\")"
39
+ },
40
+ db: {
41
+ type: "string",
42
+ description: "SQLite database path"
43
+ },
44
+ report: {
45
+ type: "string",
46
+ alias: "r",
47
+ description: "Report format: console, json, markdown",
48
+ default: "console"
49
+ },
50
+ output: {
51
+ type: "string",
52
+ alias: "o",
53
+ description: "Report output file path"
54
+ },
55
+ quiet: {
56
+ type: "boolean",
57
+ alias: "q",
58
+ description: "Suppress live output",
59
+ default: false
60
+ }
61
+ },
62
+ run: async ({ args }) => {
63
+ const reportFormat = args.report ?? "console";
64
+ validateFormat(reportFormat, VALID_REPORT_FORMATS, "report");
65
+ const overrides = {};
66
+ if (args.duration) overrides.duration = args.duration;
67
+ if (args.interval) overrides.interval = args.interval;
68
+ if (args.db) overrides.db = args.db;
69
+ let config;
70
+ try {
71
+ config = await loadDripfeedConfig(overrides);
72
+ } catch (err) {
73
+ if (err && typeof err === "object" && "issues" in err) {
74
+ process.stderr.write("Invalid config. Run `dripfeed init` to create a starter config.\n");
75
+ const { issues } = err;
76
+ process.stderr.write(`Details: ${JSON.stringify(issues, null, 2)}\n`);
77
+ } else process.stderr.write(`Config error: ${err instanceof Error ? err.message : err}\n`);
78
+ process.exit(1);
79
+ }
80
+ const reporters = [];
81
+ const shouldQuiet = args.quiet || reportFormat !== "console";
82
+ if (!shouldQuiet) reporters.push(createConsoleReporter());
83
+ if (reportFormat === "json") reporters.push(createJsonReporter(args.output));
84
+ else if (reportFormat === "markdown") reporters.push(createMarkdownReporter(args.output));
85
+ if (!shouldQuiet) {
86
+ const interval = config.interval ?? "3s";
87
+ const duration = args.duration ? ` for ${args.duration}` : "";
88
+ process.stdout.write(`\ndripfeed v0.1.0 — every ${interval}${duration} | Ctrl+C to stop\n\n`);
89
+ }
90
+ if ((await createSoakTest(config, reporters).run({ duration: args.duration })).thresholds?.some((t) => !t.passed)) process.exit(1);
91
+ }
92
+ }),
93
+ init: defineCommand({
94
+ meta: {
95
+ name: "init",
96
+ description: "Generate a starter dripfeed config file"
97
+ },
98
+ args: { format: {
99
+ type: "string",
100
+ description: "Config format: ts, json",
101
+ default: "ts"
102
+ } },
103
+ run: async ({ args }) => {
104
+ const { writeFile, access } = await import("node:fs/promises");
105
+ const format = args.format ?? "ts";
106
+ if (format !== "ts" && format !== "json") {
107
+ process.stderr.write(`Unsupported format "${format}". Use: ts, json\n`);
108
+ process.exit(1);
109
+ }
110
+ const filename = format === "ts" ? "dripfeed.config.ts" : "dripfeed.config.json";
111
+ try {
112
+ await access(filename);
113
+ process.stderr.write(`${filename} already exists. Delete it first or use a different format.\n`);
114
+ process.exit(1);
115
+ } catch {}
116
+ if (format === "ts") await writeFile(filename, `import type { DripfeedConfig } from 'dripfeed';
117
+
118
+ const config: DripfeedConfig = {
119
+ \tinterval: '3s',
120
+ \ttimeout: '30s',
121
+ \tstorage: 'sqlite',
122
+ \trotation: 'weighted-random',
123
+ \tendpoints: [
124
+ \t\t{
125
+ \t\t\tname: 'health',
126
+ \t\t\turl: 'https://api.example.com/health',
127
+ \t\t},
128
+ \t\t{
129
+ \t\t\tname: 'users',
130
+ \t\t\turl: 'https://api.example.com/v1/users',
131
+ \t\t\tweight: 3,
132
+ \t\t},
133
+ \t],
134
+ \tthresholds: {
135
+ \t\terror_rate: '< 1%',
136
+ \t\tp95: '< 500ms',
137
+ \t},
138
+ };
139
+
140
+ export default config;
141
+ `);
142
+ else await writeFile(filename, JSON.stringify({
143
+ interval: "3s",
144
+ timeout: "30s",
145
+ storage: "sqlite",
146
+ rotation: "weighted-random",
147
+ endpoints: [{
148
+ name: "health",
149
+ url: "https://api.example.com/health"
150
+ }, {
151
+ name: "users",
152
+ url: "https://api.example.com/v1/users",
153
+ weight: 3
154
+ }],
155
+ thresholds: {
156
+ error_rate: "< 1%",
157
+ p95: "< 500ms"
158
+ }
159
+ }, null, 2));
160
+ process.stdout.write(`Created ${filename}\n`);
161
+ }
162
+ }),
163
+ report: defineCommand({
164
+ meta: {
165
+ name: "report",
166
+ description: "Generate a report from an existing SQLite database"
167
+ },
168
+ args: {
169
+ db: {
170
+ type: "string",
171
+ description: "SQLite database path",
172
+ default: "dripfeed-results.db"
173
+ },
174
+ format: {
175
+ type: "string",
176
+ description: "Report format: console, json, markdown",
177
+ default: "console"
178
+ },
179
+ output: {
180
+ type: "string",
181
+ alias: "o",
182
+ description: "Output file path"
183
+ }
184
+ },
185
+ run: async ({ args }) => {
186
+ const format = args.format ?? "console";
187
+ validateFormat(format, VALID_REPORT_FORMATS, "report");
188
+ const storage = createSqliteStorage(args.db ?? "dripfeed-results.db");
189
+ await storage.init();
190
+ const results = await storage.getAll();
191
+ await storage.close();
192
+ if (results.length === 0) {
193
+ process.stdout.write("No results found in database.\n");
194
+ return;
195
+ }
196
+ const stats = computeStats(results, new Date(results[0]?.timestamp ?? Date.now()), void 0, new Date(results[results.length - 1]?.timestamp ?? Date.now()));
197
+ if (format === "console") createConsoleReporter().onComplete(stats);
198
+ else if (format === "json") createJsonReporter(args.output).onComplete(stats);
199
+ else if (format === "markdown") createMarkdownReporter(args.output).onComplete(stats);
200
+ if (args.output) process.stdout.write(`Report written to ${args.output}\n`);
201
+ }
202
+ }),
203
+ export: defineCommand({
204
+ meta: {
205
+ name: "export",
206
+ description: "Export results from SQLite to CSV or JSON"
207
+ },
208
+ args: {
209
+ db: {
210
+ type: "string",
211
+ description: "SQLite database path",
212
+ default: "dripfeed-results.db"
213
+ },
214
+ format: {
215
+ type: "string",
216
+ description: "Export format: csv, json",
217
+ default: "csv"
218
+ },
219
+ output: {
220
+ type: "string",
221
+ alias: "o",
222
+ description: "Output file path"
223
+ }
224
+ },
225
+ run: async ({ args }) => {
226
+ const format = args.format ?? "csv";
227
+ validateFormat(format, VALID_EXPORT_FORMATS, "export");
228
+ const { writeFile } = await import("node:fs/promises");
229
+ const storage = createSqliteStorage(args.db ?? "dripfeed-results.db");
230
+ await storage.init();
231
+ const results = await storage.getAll();
232
+ await storage.close();
233
+ let output;
234
+ if (format === "json") output = JSON.stringify(results, null, 2);
235
+ else {
236
+ const headers = [
237
+ "timestamp",
238
+ "endpoint",
239
+ "method",
240
+ "url",
241
+ "status",
242
+ "duration_ms",
243
+ "error",
244
+ "response_body"
245
+ ];
246
+ const escapeCsv = (s) => {
247
+ if (s === null) return "";
248
+ return s.includes(",") || s.includes("\"") || s.includes("\n") ? `"${s.replace(/"/g, "\"\"")}"` : s;
249
+ };
250
+ const rows = results.map((r) => [
251
+ r.timestamp,
252
+ r.endpoint,
253
+ r.method,
254
+ r.url,
255
+ r.status ?? "",
256
+ r.duration_ms,
257
+ escapeCsv(r.error),
258
+ escapeCsv(r.response_body)
259
+ ].join(","));
260
+ output = [headers.join(","), ...rows].join("\n");
261
+ }
262
+ if (args.output) {
263
+ await writeFile(args.output, output);
264
+ process.stdout.write(`Exported ${results.length} results to ${args.output}\n`);
265
+ } else process.stdout.write(`${output}\n`);
266
+ }
267
+ })
268
+ }
269
+ }));
270
+ //#endregion
271
+ export {};
272
+
273
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty';\nimport { createConsoleReporter } from './adapters/reporters/console.js';\nimport type { Reporter } from './adapters/reporters/interface.js';\nimport { createJsonReporter } from './adapters/reporters/json.js';\nimport { createMarkdownReporter } from './adapters/reporters/markdown.js';\nimport { createSqliteStorage } from './adapters/storage/sqlite.js';\nimport { loadDripfeedConfig, type ParsedConfig } from './core/config.js';\nimport { createSoakTest } from './core/runner.js';\nimport { computeStats } from './utils/stats.js';\n\nconst VALID_REPORT_FORMATS = ['console', 'json', 'markdown'] as const;\nconst VALID_EXPORT_FORMATS = ['csv', 'json'] as const;\n\nconst validateFormat = (format: string, valid: readonly string[], command: string) => {\n\tif (!valid.includes(format)) {\n\t\tprocess.stderr.write(\n\t\t\t`Unsupported format \"${format}\" for ${command}. Use: ${valid.join(', ')}\\n`,\n\t\t);\n\t\tprocess.exit(1);\n\t}\n};\n\nconst run = defineCommand({\n\tmeta: { name: 'run', description: 'Start a soak test' },\n\targs: {\n\t\tduration: { type: 'string', alias: 'd', description: 'Test duration (e.g. \"30s\", \"5m\", \"2h\")' },\n\t\tinterval: { type: 'string', alias: 'i', description: 'Request interval (e.g. \"3s\", \"500ms\")' },\n\t\tdb: { type: 'string', description: 'SQLite database path' },\n\t\treport: {\n\t\t\ttype: 'string',\n\t\t\talias: 'r',\n\t\t\tdescription: 'Report format: console, json, markdown',\n\t\t\tdefault: 'console',\n\t\t},\n\t\toutput: { type: 'string', alias: 'o', description: 'Report output file path' },\n\t\tquiet: { type: 'boolean', alias: 'q', description: 'Suppress live output', default: false },\n\t},\n\trun: async ({ args }) => {\n\t\tconst reportFormat = args.report ?? 'console';\n\t\tvalidateFormat(reportFormat, VALID_REPORT_FORMATS, 'report');\n\n\t\tconst overrides: Record<string, unknown> = {};\n\t\tif (args.duration) overrides.duration = args.duration;\n\t\tif (args.interval) overrides.interval = args.interval;\n\t\tif (args.db) overrides.db = args.db;\n\n\t\tlet config: ParsedConfig | undefined;\n\t\ttry {\n\t\t\tconfig = await loadDripfeedConfig(overrides);\n\t\t} catch (err) {\n\t\t\tif (err && typeof err === 'object' && 'issues' in err) {\n\t\t\t\tprocess.stderr.write('Invalid config. Run `dripfeed init` to create a starter config.\\n');\n\t\t\t\tconst { issues } = err as { issues: unknown };\n\t\t\t\tprocess.stderr.write(`Details: ${JSON.stringify(issues, null, 2)}\\n`);\n\t\t\t} else {\n\t\t\t\tprocess.stderr.write(`Config error: ${err instanceof Error ? err.message : err}\\n`);\n\t\t\t}\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tconst reporters: Reporter[] = [];\n\t\t// Auto-quiet when using json/markdown report to avoid mixing outputs\n\t\tconst shouldQuiet = args.quiet || reportFormat !== 'console';\n\t\tif (!shouldQuiet) {\n\t\t\treporters.push(createConsoleReporter());\n\t\t}\n\n\t\tif (reportFormat === 'json') {\n\t\t\treporters.push(createJsonReporter(args.output));\n\t\t} else if (reportFormat === 'markdown') {\n\t\t\treporters.push(createMarkdownReporter(args.output));\n\t\t}\n\n\t\t// Startup banner (only in console mode)\n\t\tif (!shouldQuiet) {\n\t\t\tconst interval = config.interval ?? '3s';\n\t\t\tconst duration = args.duration ? ` for ${args.duration}` : '';\n\t\t\tprocess.stdout.write(`\\ndripfeed v0.1.0 — every ${interval}${duration} | Ctrl+C to stop\\n\\n`);\n\t\t}\n\n\t\tconst test = createSoakTest(config, reporters);\n\t\tconst stats = await test.run({ duration: args.duration });\n\n\t\tif (stats.thresholds?.some((t) => !t.passed)) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t},\n});\n\nconst init = defineCommand({\n\tmeta: { name: 'init', description: 'Generate a starter dripfeed config file' },\n\targs: {\n\t\tformat: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Config format: ts, json',\n\t\t\tdefault: 'ts',\n\t\t},\n\t},\n\trun: async ({ args }) => {\n\t\tconst { writeFile, access } = await import('node:fs/promises');\n\t\tconst format = args.format ?? 'ts';\n\n\t\tif (format !== 'ts' && format !== 'json') {\n\t\t\tprocess.stderr.write(`Unsupported format \"${format}\". Use: ts, json\\n`);\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tconst filename = format === 'ts' ? 'dripfeed.config.ts' : 'dripfeed.config.json';\n\n\t\t// Check if file exists\n\t\ttry {\n\t\t\tawait access(filename);\n\t\t\tprocess.stderr.write(\n\t\t\t\t`${filename} already exists. Delete it first or use a different format.\\n`,\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t} catch {\n\t\t\t// File doesn't exist, proceed\n\t\t}\n\n\t\tif (format === 'ts') {\n\t\t\tconst content = `import type { DripfeedConfig } from 'dripfeed';\n\nconst config: DripfeedConfig = {\n\\tinterval: '3s',\n\\ttimeout: '30s',\n\\tstorage: 'sqlite',\n\\trotation: 'weighted-random',\n\\tendpoints: [\n\\t\\t{\n\\t\\t\\tname: 'health',\n\\t\\t\\turl: 'https://api.example.com/health',\n\\t\\t},\n\\t\\t{\n\\t\\t\\tname: 'users',\n\\t\\t\\turl: 'https://api.example.com/v1/users',\n\\t\\t\\tweight: 3,\n\\t\\t},\n\\t],\n\\tthresholds: {\n\\t\\terror_rate: '< 1%',\n\\t\\tp95: '< 500ms',\n\\t},\n};\n\nexport default config;\n`;\n\t\t\tawait writeFile(filename, content);\n\t\t} else {\n\t\t\tconst content = {\n\t\t\t\tinterval: '3s',\n\t\t\t\ttimeout: '30s',\n\t\t\t\tstorage: 'sqlite',\n\t\t\t\trotation: 'weighted-random',\n\t\t\t\tendpoints: [\n\t\t\t\t\t{ name: 'health', url: 'https://api.example.com/health' },\n\t\t\t\t\t{ name: 'users', url: 'https://api.example.com/v1/users', weight: 3 },\n\t\t\t\t],\n\t\t\t\tthresholds: { error_rate: '< 1%', p95: '< 500ms' },\n\t\t\t};\n\t\t\tawait writeFile(filename, JSON.stringify(content, null, 2));\n\t\t}\n\n\t\tprocess.stdout.write(`Created ${filename}\\n`);\n\t},\n});\n\nconst report = defineCommand({\n\tmeta: { name: 'report', description: 'Generate a report from an existing SQLite database' },\n\targs: {\n\t\tdb: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'SQLite database path',\n\t\t\tdefault: 'dripfeed-results.db',\n\t\t},\n\t\tformat: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Report format: console, json, markdown',\n\t\t\tdefault: 'console',\n\t\t},\n\t\toutput: { type: 'string', alias: 'o', description: 'Output file path' },\n\t},\n\trun: async ({ args }) => {\n\t\tconst format = args.format ?? 'console';\n\t\tvalidateFormat(format, VALID_REPORT_FORMATS, 'report');\n\n\t\tconst dbPath = args.db ?? 'dripfeed-results.db';\n\t\tconst storage = createSqliteStorage(dbPath);\n\t\tawait storage.init();\n\t\tconst results = await storage.getAll();\n\t\tawait storage.close();\n\n\t\tif (results.length === 0) {\n\t\t\tprocess.stdout.write('No results found in database.\\n');\n\t\t\treturn;\n\t\t}\n\n\t\t// Use first result timestamp as start, last as end for accurate duration\n\t\tconst firstTimestamp = new Date(results[0]?.timestamp ?? Date.now());\n\t\tconst lastTimestamp = new Date(results[results.length - 1]?.timestamp ?? Date.now());\n\t\tconst stats = computeStats(results, firstTimestamp, undefined, lastTimestamp);\n\n\t\tif (format === 'console') {\n\t\t\tcreateConsoleReporter().onComplete(stats);\n\t\t} else if (format === 'json') {\n\t\t\tcreateJsonReporter(args.output).onComplete(stats);\n\t\t} else if (format === 'markdown') {\n\t\t\tcreateMarkdownReporter(args.output).onComplete(stats);\n\t\t}\n\n\t\tif (args.output) {\n\t\t\tprocess.stdout.write(`Report written to ${args.output}\\n`);\n\t\t}\n\t},\n});\n\nconst exportCmd = defineCommand({\n\tmeta: { name: 'export', description: 'Export results from SQLite to CSV or JSON' },\n\targs: {\n\t\tdb: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'SQLite database path',\n\t\t\tdefault: 'dripfeed-results.db',\n\t\t},\n\t\tformat: { type: 'string', description: 'Export format: csv, json', default: 'csv' },\n\t\toutput: { type: 'string', alias: 'o', description: 'Output file path' },\n\t},\n\trun: async ({ args }) => {\n\t\tconst format = args.format ?? 'csv';\n\t\tvalidateFormat(format, VALID_EXPORT_FORMATS, 'export');\n\n\t\tconst { writeFile } = await import('node:fs/promises');\n\t\tconst dbPath = args.db ?? 'dripfeed-results.db';\n\t\tconst storage = createSqliteStorage(dbPath);\n\t\tawait storage.init();\n\t\tconst results = await storage.getAll();\n\t\tawait storage.close();\n\n\t\tlet output: string;\n\n\t\tif (format === 'json') {\n\t\t\toutput = JSON.stringify(results, null, 2);\n\t\t} else {\n\t\t\tconst headers = [\n\t\t\t\t'timestamp',\n\t\t\t\t'endpoint',\n\t\t\t\t'method',\n\t\t\t\t'url',\n\t\t\t\t'status',\n\t\t\t\t'duration_ms',\n\t\t\t\t'error',\n\t\t\t\t'response_body',\n\t\t\t];\n\t\t\tconst escapeCsv = (s: string | null) => {\n\t\t\t\tif (s === null) return '';\n\t\t\t\treturn s.includes(',') || s.includes('\"') || s.includes('\\n')\n\t\t\t\t\t? `\"${s.replace(/\"/g, '\"\"')}\"`\n\t\t\t\t\t: s;\n\t\t\t};\n\t\t\tconst rows = results.map((r) =>\n\t\t\t\t[\n\t\t\t\t\tr.timestamp,\n\t\t\t\t\tr.endpoint,\n\t\t\t\t\tr.method,\n\t\t\t\t\tr.url,\n\t\t\t\t\tr.status ?? '',\n\t\t\t\t\tr.duration_ms,\n\t\t\t\t\tescapeCsv(r.error),\n\t\t\t\t\tescapeCsv(r.response_body),\n\t\t\t\t].join(','),\n\t\t\t);\n\t\t\toutput = [headers.join(','), ...rows].join('\\n');\n\t\t}\n\n\t\tif (args.output) {\n\t\t\tawait writeFile(args.output, output);\n\t\t\tprocess.stdout.write(`Exported ${results.length} results to ${args.output}\\n`);\n\t\t} else {\n\t\t\tprocess.stdout.write(`${output}\\n`);\n\t\t}\n\t},\n});\n\nconst main = defineCommand({\n\tmeta: {\n\t\tname: 'dripfeed',\n\t\tversion: '0.1.0',\n\t\tdescription: 'SQLite-native API soak testing. Drip, not firehose.',\n\t},\n\tsubCommands: { run, init, report, export: exportCmd },\n});\n\nrunMain(main);\n"],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,21 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_runner = require("./runner-Dc1JRBps.cjs");
3
+ exports.computeStats = require_runner.computeStats;
4
+ exports.configSchema = require_runner.configSchema;
5
+ exports.createConsoleReporter = require_runner.createConsoleReporter;
6
+ exports.createJsonReporter = require_runner.createJsonReporter;
7
+ exports.createJsonStorage = require_runner.createJsonStorage;
8
+ exports.createMarkdownReporter = require_runner.createMarkdownReporter;
9
+ exports.createMemoryStorage = require_runner.createMemoryStorage;
10
+ exports.createSoakTest = require_runner.createSoakTest;
11
+ exports.createSqliteStorage = require_runner.createSqliteStorage;
12
+ exports.createStorage = require_runner.createStorage;
13
+ exports.isBun = require_runner.isBun;
14
+ exports.isDeno = require_runner.isDeno;
15
+ exports.isNode = require_runner.isNode;
16
+ exports.isSuccess = require_runner.isSuccess;
17
+ exports.loadDripfeedConfig = require_runner.loadDripfeedConfig;
18
+ exports.parseConfig = require_runner.parseConfig;
19
+ exports.parseDuration = require_runner.parseDuration;
20
+ exports.percentile = require_runner.percentile;
21
+ exports.timedFetch = require_runner.timedFetch;
@@ -0,0 +1,187 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/core/types.d.ts
4
+ interface RequestResult {
5
+ timestamp: string;
6
+ endpoint: string;
7
+ method: string;
8
+ url: string;
9
+ status: number | null;
10
+ duration_ms: number;
11
+ response_body: string | null;
12
+ error: string | null;
13
+ }
14
+ interface EndpointConfig {
15
+ name: string;
16
+ url: string;
17
+ method?: string;
18
+ headers?: Record<string, string>;
19
+ body?: unknown;
20
+ timeout?: string;
21
+ weight?: number;
22
+ }
23
+ interface ThresholdConfig {
24
+ error_rate?: string;
25
+ p50?: string;
26
+ p95?: string;
27
+ p99?: string;
28
+ max?: string;
29
+ }
30
+ interface DripfeedConfig {
31
+ interval?: string;
32
+ duration?: string;
33
+ timeout?: string;
34
+ storage?: 'sqlite' | 'json' | 'memory';
35
+ db?: string;
36
+ rotation?: 'weighted-random' | 'round-robin' | 'sequential';
37
+ headers?: Record<string, string>;
38
+ endpoints: EndpointConfig[];
39
+ thresholds?: ThresholdConfig;
40
+ }
41
+ /** Check if an HTTP status code represents a successful response */
42
+ declare const isSuccess: (status: number | null) => boolean;
43
+ interface EndpointStats {
44
+ name: string;
45
+ requests: number;
46
+ avg_ms: number;
47
+ p95_ms: number;
48
+ error_count: number;
49
+ }
50
+ interface ErrorGroup {
51
+ endpoint: string;
52
+ status: number | null;
53
+ count: number;
54
+ sample_body: string | null;
55
+ }
56
+ interface ThresholdResult {
57
+ name: string;
58
+ target: string;
59
+ actual: string;
60
+ passed: boolean;
61
+ }
62
+ interface LatencyStats {
63
+ min: number;
64
+ avg: number;
65
+ p50: number;
66
+ p95: number;
67
+ p99: number;
68
+ max: number;
69
+ }
70
+ interface SoakStats {
71
+ duration_s: number;
72
+ total_requests: number;
73
+ success_count: number;
74
+ failure_count: number;
75
+ uptime_pct: number;
76
+ latency: LatencyStats;
77
+ status_codes: Record<number, number>;
78
+ endpoints: EndpointStats[];
79
+ errors: ErrorGroup[];
80
+ thresholds?: ThresholdResult[];
81
+ }
82
+ interface SoakTestHandle {
83
+ start: () => Promise<void>;
84
+ stop: () => Promise<SoakStats>;
85
+ run: (opts?: {
86
+ duration?: string;
87
+ }) => Promise<SoakStats>;
88
+ }
89
+ //#endregion
90
+ //#region src/adapters/reporters/interface.d.ts
91
+ interface Reporter {
92
+ onRequest(result: RequestResult, counts: {
93
+ ok: number;
94
+ fail: number;
95
+ }): void;
96
+ onComplete(stats: SoakStats): void;
97
+ }
98
+ //#endregion
99
+ //#region src/adapters/reporters/console.d.ts
100
+ declare const createConsoleReporter: () => Reporter;
101
+ //#endregion
102
+ //#region src/adapters/reporters/json.d.ts
103
+ declare const createJsonReporter: (outputPath?: string) => Reporter;
104
+ //#endregion
105
+ //#region src/adapters/reporters/markdown.d.ts
106
+ declare const createMarkdownReporter: (outputPath?: string) => Reporter;
107
+ //#endregion
108
+ //#region src/adapters/storage/interface.d.ts
109
+ interface StorageAdapter {
110
+ init(): Promise<void>;
111
+ record(result: RequestResult): Promise<void>;
112
+ getAll(): Promise<RequestResult[]>;
113
+ close(): Promise<void>;
114
+ }
115
+ //#endregion
116
+ //#region src/adapters/storage/json.d.ts
117
+ declare const createJsonStorage: (filePath: string) => StorageAdapter;
118
+ //#endregion
119
+ //#region src/adapters/storage/memory.d.ts
120
+ declare const createMemoryStorage: () => StorageAdapter;
121
+ //#endregion
122
+ //#region src/adapters/storage/sqlite.d.ts
123
+ declare const createSqliteStorage: (dbPath: string) => StorageAdapter;
124
+ //#endregion
125
+ //#region src/adapters/storage/index.d.ts
126
+ declare const createStorage: (config: DripfeedConfig) => StorageAdapter;
127
+ //#endregion
128
+ //#region src/core/config.d.ts
129
+ declare const configSchema: z.ZodObject<{
130
+ interval: z.ZodDefault<z.ZodString>;
131
+ duration: z.ZodOptional<z.ZodString>;
132
+ timeout: z.ZodDefault<z.ZodString>;
133
+ storage: z.ZodDefault<z.ZodEnum<{
134
+ sqlite: "sqlite";
135
+ json: "json";
136
+ memory: "memory";
137
+ }>>;
138
+ db: z.ZodOptional<z.ZodString>;
139
+ rotation: z.ZodDefault<z.ZodEnum<{
140
+ "weighted-random": "weighted-random";
141
+ "round-robin": "round-robin";
142
+ sequential: "sequential";
143
+ }>>;
144
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
145
+ endpoints: z.ZodArray<z.ZodObject<{
146
+ name: z.ZodString;
147
+ url: z.ZodString;
148
+ method: z.ZodDefault<z.ZodString>;
149
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
150
+ body: z.ZodOptional<z.ZodUnknown>;
151
+ timeout: z.ZodOptional<z.ZodString>;
152
+ weight: z.ZodDefault<z.ZodNumber>;
153
+ }, z.core.$strip>>;
154
+ thresholds: z.ZodOptional<z.ZodObject<{
155
+ error_rate: z.ZodOptional<z.ZodString>;
156
+ p50: z.ZodOptional<z.ZodString>;
157
+ p95: z.ZodOptional<z.ZodString>;
158
+ p99: z.ZodOptional<z.ZodString>;
159
+ max: z.ZodOptional<z.ZodString>;
160
+ }, z.core.$strip>>;
161
+ }, z.core.$strip>;
162
+ type ParsedConfig = z.infer<typeof configSchema>;
163
+ /** Parse and validate a raw config object into a fully-typed ParsedConfig with defaults applied.
164
+ * Use this when creating a soak test programmatically without a config file. */
165
+ declare const parseConfig: (raw: unknown) => ParsedConfig;
166
+ declare const loadDripfeedConfig: (overrides?: Partial<ParsedConfig>) => Promise<ParsedConfig>;
167
+ //#endregion
168
+ //#region src/core/runner.d.ts
169
+ declare const createSoakTest: (config: ParsedConfig, reporters?: Reporter[]) => SoakTestHandle;
170
+ //#endregion
171
+ //#region src/utils/duration.d.ts
172
+ declare const parseDuration: (input: string) => number;
173
+ //#endregion
174
+ //#region src/utils/http.d.ts
175
+ declare const timedFetch: (endpoint: EndpointConfig, globalHeaders?: Record<string, string>, timeout?: string) => Promise<RequestResult>;
176
+ //#endregion
177
+ //#region src/utils/runtime.d.ts
178
+ declare const isBun: boolean;
179
+ declare const isDeno: boolean;
180
+ declare const isNode: boolean;
181
+ //#endregion
182
+ //#region src/utils/stats.d.ts
183
+ declare const percentile: (sorted: number[], p: number) => number;
184
+ declare const computeStats: (results: RequestResult[], startTime: Date, thresholds?: ThresholdConfig, endTime?: Date) => SoakStats;
185
+ //#endregion
186
+ export { type DripfeedConfig, type EndpointConfig, type EndpointStats, type ErrorGroup, type LatencyStats, type ParsedConfig, type Reporter, type RequestResult, type SoakStats, type SoakTestHandle, type StorageAdapter, type ThresholdConfig, type ThresholdResult, computeStats, configSchema, createConsoleReporter, createJsonReporter, createJsonStorage, createMarkdownReporter, createMemoryStorage, createSoakTest, createSqliteStorage, createStorage, isBun, isDeno, isNode, isSuccess, loadDripfeedConfig, parseConfig, parseDuration, percentile, timedFetch };
187
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/core/types.ts","../src/adapters/reporters/interface.ts","../src/adapters/reporters/console.ts","../src/adapters/reporters/json.ts","../src/adapters/reporters/markdown.ts","../src/adapters/storage/interface.ts","../src/adapters/storage/json.ts","../src/adapters/storage/memory.ts","../src/adapters/storage/sqlite.ts","../src/adapters/storage/index.ts","../src/core/config.ts","../src/core/runner.ts","../src/utils/duration.ts","../src/utils/http.ts","../src/utils/runtime.ts","../src/utils/stats.ts"],"mappings":"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"}