@devilsdev/rag-pipeline-utils 2.6.1 → 2.7.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 (90) hide show
  1. package/BENCHMARKS.md +7 -1
  2. package/CHANGELOG.md +135 -0
  3. package/README.md +37 -24
  4. package/dist/index.cjs +4164 -1825
  5. package/dist/index.d.ts +265 -256
  6. package/dist/index.mjs +1 -1
  7. package/dist/types/cache/memory-cache.d.ts +4 -1
  8. package/dist/types/cache/redis-cache.d.ts +4 -1
  9. package/dist/types/cli/services/instrument.d.ts +14 -0
  10. package/dist/types/cli/services/rag-service.d.ts +140 -0
  11. package/dist/types/cli/shared/config-context.d.ts +9 -0
  12. package/dist/types/config/component-factory.d.ts +57 -0
  13. package/dist/types/config/enhanced-ragrc-schema.d.ts +648 -0
  14. package/dist/types/config/load-config.d.ts +18 -0
  15. package/dist/types/config/normalize-config.d.ts +9 -0
  16. package/dist/types/core/observability/event-logger.d.ts +211 -0
  17. package/dist/types/core/observability/metrics.d.ts +585 -0
  18. package/dist/types/core/performance/parallel-processor.d.ts +92 -0
  19. package/dist/types/core/plugin-registry.d.ts +5 -0
  20. package/dist/types/dag/core/execution-scheduler.d.ts +3 -1
  21. package/dist/types/dag/dag-engine.d.ts +15 -2
  22. package/dist/types/dev/dev-server.d.ts +136 -0
  23. package/dist/types/dev/hot-reload.d.ts +226 -0
  24. package/dist/types/ecosystem/plugin-analytics-dashboard.d.ts +141 -0
  25. package/dist/types/ecosystem/plugin-hub.d.ts +161 -0
  26. package/dist/types/ecosystem/plugin-installer.d.ts +40 -0
  27. package/dist/types/ecosystem/plugin-probe.d.ts +21 -0
  28. package/dist/types/ecosystem/plugin-scanner.d.ts +30 -0
  29. package/dist/types/ecosystem/tar-extract.d.ts +24 -0
  30. package/dist/types/enterprise/audit-logging.d.ts +217 -0
  31. package/dist/types/evaluate/compare.d.ts +140 -0
  32. package/dist/types/evaluate/dataset.d.ts +43 -0
  33. package/dist/types/evaluate/evaluator.d.ts +14 -0
  34. package/dist/types/evaluate/report.d.ts +56 -0
  35. package/dist/types/evaluate/synthetic.d.ts +64 -0
  36. package/dist/types/loader/csv-loader.d.ts +10 -0
  37. package/dist/types/loader/document-reader.d.ts +18 -0
  38. package/dist/types/loader/html-loader.d.ts +21 -0
  39. package/dist/types/loader/markdown-loader.d.ts +21 -0
  40. package/dist/types/retrieval/bm25-search.d.ts +4 -0
  41. package/dist/types/retrieval/file-vector-store.d.ts +4 -1
  42. package/dist/types/retrieval/hybrid-retriever.d.ts +4 -0
  43. package/dist/types/retrieval/pgvector-retriever.d.ts +123 -0
  44. package/dist/types/retrieval/qdrant-retriever.d.ts +106 -0
  45. package/dist/types/security/validator.d.ts +11 -1
  46. package/dist/types/testing.d.ts +2 -0
  47. package/dist/types/utils/batch-processor.d.ts +205 -0
  48. package/dist/types/utils/connection-pool.d.ts +158 -0
  49. package/dist/types/utils/error-formatter.d.ts +312 -0
  50. package/dist/types/utils/logger.d.ts +29 -4
  51. package/dist/types/utils/rate-limiter.d.ts +13 -8
  52. package/dist/types/utils/retry.d.ts +10 -0
  53. package/package.json +13 -3
  54. package/src/cache/memory-cache.js +4 -1
  55. package/src/cache/redis-cache.js +4 -1
  56. package/src/cli/enhanced-cli-commands.js +79 -0
  57. package/src/cli/handlers/ai-handler.js +2 -2
  58. package/src/cli/handlers/eval-handler.js +201 -0
  59. package/src/cli/handlers/generate-handler.js +103 -0
  60. package/src/cli/services/benchmark.js +2 -2
  61. package/src/cli/services/rag-service.js +59 -10
  62. package/src/cli/shared/cli-utils.js +40 -1
  63. package/src/config/component-factory.js +32 -0
  64. package/src/connectors/local-embedder.js +20 -13
  65. package/src/core/observability/event-logger.js +135 -1
  66. package/src/core/observability/metrics.js +223 -5
  67. package/src/core/performance/parallel-processor.js +2 -0
  68. package/src/core/plugin-registry.js +5 -1
  69. package/src/dag/core/execution-scheduler.js +2 -0
  70. package/src/dag/dag-engine.js +13 -0
  71. package/src/dev/dev-server.js +6 -0
  72. package/src/ecosystem/plugin-hub.js +4 -0
  73. package/src/enterprise/audit-logging.js +5 -0
  74. package/src/evaluate/compare.js +395 -0
  75. package/src/evaluate/dataset.js +207 -0
  76. package/src/evaluate/report.js +350 -0
  77. package/src/evaluate/synthetic.js +246 -0
  78. package/src/index.js +17 -0
  79. package/src/retrieval/bm25-search.js +4 -0
  80. package/src/retrieval/file-vector-store.js +4 -1
  81. package/src/retrieval/hybrid-retriever.js +4 -0
  82. package/src/retrieval/pgvector-retriever.js +420 -0
  83. package/src/retrieval/qdrant-retriever.js +361 -0
  84. package/src/security/validator.js +8 -0
  85. package/src/testing.js +2 -0
  86. package/src/utils/batch-processor.js +7 -3
  87. package/src/utils/error-formatter.js +48 -20
  88. package/src/utils/logger.js +31 -1
  89. package/src/utils/rate-limiter.js +9 -4
  90. package/src/utils/retry.js +10 -1
@@ -0,0 +1,312 @@
1
+ export namespace ERROR_CODES {
2
+ let CONFIG_NOT_FOUND: string;
3
+ let CONFIG_INVALID_JSON: string;
4
+ let CONFIG_VALIDATION_FAILED: string;
5
+ let CONFIG_MISSING_REQUIRED: string;
6
+ let CONFIG_INVALID_TYPE: string;
7
+ let CONFIG_INVALID_VALUE: string;
8
+ let PLUGIN_NOT_FOUND: string;
9
+ let PLUGIN_LOAD_FAILED: string;
10
+ let PLUGIN_INVALID_EXPORT: string;
11
+ let PLUGIN_EXECUTION_FAILED: string;
12
+ let PLUGIN_INITIALIZATION_FAILED: string;
13
+ let PLUGIN_CONTRACT_VIOLATION: string;
14
+ let PLUGIN_DEPENDENCY_MISSING: string;
15
+ let PIPELINE_CREATION_FAILED: string;
16
+ let PIPELINE_EXECUTION_FAILED: string;
17
+ let PIPELINE_STAGE_FAILED: string;
18
+ let PIPELINE_INVALID_STAGE: string;
19
+ let VALIDATION_FAILED: string;
20
+ let VALIDATION_SCHEMA_ERROR: string;
21
+ let VALIDATION_CONSTRAINT_ERROR: string;
22
+ let FS_FILE_NOT_FOUND: string;
23
+ let FS_DIRECTORY_NOT_FOUND: string;
24
+ let FS_PERMISSION_DENIED: string;
25
+ let FS_READ_ERROR: string;
26
+ let FS_WRITE_ERROR: string;
27
+ let NETWORK_REQUEST_FAILED: string;
28
+ let NETWORK_TIMEOUT: string;
29
+ let NETWORK_INVALID_RESPONSE: string;
30
+ let HOT_RELOAD_FAILED: string;
31
+ let HOT_RELOAD_STATE_ERROR: string;
32
+ let HOT_RELOAD_WATCH_ERROR: string;
33
+ let DAG_CYCLE_DETECTED: string;
34
+ let DAG_NODE_NOT_FOUND: string;
35
+ let DAG_EXECUTION_FAILED: string;
36
+ let DAG_INVALID_TOPOLOGY: string;
37
+ let AUTH_INVALID_TOKEN: string;
38
+ let AUTH_TOKEN_EXPIRED: string;
39
+ let AUTH_INSUFFICIENT_PERMISSIONS: string;
40
+ let UNKNOWN_ERROR: string;
41
+ }
42
+ /**
43
+ * Message, suggestion and documentation templates keyed by error code.
44
+ * Placeholders such as {path} are filled from the context passed to
45
+ * formatError. Codes without an entry fall back to UNKNOWN_ERROR.
46
+ */
47
+ export const ERROR_TEMPLATES: {
48
+ [ERROR_CODES.CONFIG_NOT_FOUND]: {
49
+ message: string;
50
+ suggestions: string[];
51
+ documentation: string;
52
+ };
53
+ [ERROR_CODES.CONFIG_INVALID_JSON]: {
54
+ message: string;
55
+ suggestions: string[];
56
+ documentation: string;
57
+ };
58
+ [ERROR_CODES.CONFIG_VALIDATION_FAILED]: {
59
+ message: string;
60
+ suggestions: string[];
61
+ documentation: string;
62
+ };
63
+ [ERROR_CODES.CONFIG_MISSING_REQUIRED]: {
64
+ message: string;
65
+ suggestions: string[];
66
+ documentation: string;
67
+ };
68
+ [ERROR_CODES.CONFIG_INVALID_TYPE]: {
69
+ message: string;
70
+ suggestions: string[];
71
+ documentation: string;
72
+ };
73
+ [ERROR_CODES.PLUGIN_NOT_FOUND]: {
74
+ message: string;
75
+ suggestions: string[];
76
+ documentation: string;
77
+ };
78
+ [ERROR_CODES.PLUGIN_LOAD_FAILED]: {
79
+ message: string;
80
+ suggestions: string[];
81
+ documentation: string;
82
+ };
83
+ [ERROR_CODES.PLUGIN_INVALID_EXPORT]: {
84
+ message: string;
85
+ suggestions: string[];
86
+ documentation: string;
87
+ };
88
+ [ERROR_CODES.PLUGIN_EXECUTION_FAILED]: {
89
+ message: string;
90
+ suggestions: string[];
91
+ documentation: string;
92
+ };
93
+ [ERROR_CODES.PLUGIN_CONTRACT_VIOLATION]: {
94
+ message: string;
95
+ suggestions: string[];
96
+ documentation: string;
97
+ };
98
+ [ERROR_CODES.PLUGIN_DEPENDENCY_MISSING]: {
99
+ message: string;
100
+ suggestions: string[];
101
+ documentation: string;
102
+ };
103
+ [ERROR_CODES.PIPELINE_CREATION_FAILED]: {
104
+ message: string;
105
+ suggestions: string[];
106
+ documentation: string;
107
+ };
108
+ [ERROR_CODES.PIPELINE_EXECUTION_FAILED]: {
109
+ message: string;
110
+ suggestions: string[];
111
+ documentation: string;
112
+ };
113
+ [ERROR_CODES.PIPELINE_STAGE_FAILED]: {
114
+ message: string;
115
+ suggestions: string[];
116
+ documentation: string;
117
+ };
118
+ [ERROR_CODES.FS_FILE_NOT_FOUND]: {
119
+ message: string;
120
+ suggestions: string[];
121
+ documentation: string;
122
+ };
123
+ [ERROR_CODES.FS_DIRECTORY_NOT_FOUND]: {
124
+ message: string;
125
+ suggestions: string[];
126
+ documentation: string;
127
+ };
128
+ [ERROR_CODES.FS_PERMISSION_DENIED]: {
129
+ message: string;
130
+ suggestions: string[];
131
+ documentation: string;
132
+ };
133
+ [ERROR_CODES.HOT_RELOAD_FAILED]: {
134
+ message: string;
135
+ suggestions: string[];
136
+ documentation: string;
137
+ };
138
+ [ERROR_CODES.HOT_RELOAD_STATE_ERROR]: {
139
+ message: string;
140
+ suggestions: string[];
141
+ documentation: string;
142
+ };
143
+ [ERROR_CODES.DAG_CYCLE_DETECTED]: {
144
+ message: string;
145
+ suggestions: string[];
146
+ documentation: string;
147
+ };
148
+ [ERROR_CODES.DAG_NODE_NOT_FOUND]: {
149
+ message: string;
150
+ suggestions: string[];
151
+ documentation: string;
152
+ };
153
+ [ERROR_CODES.VALIDATION_FAILED]: {
154
+ message: string;
155
+ suggestions: string[];
156
+ documentation: string;
157
+ };
158
+ [ERROR_CODES.AUTH_INVALID_TOKEN]: {
159
+ message: string;
160
+ suggestions: string[];
161
+ documentation: string;
162
+ };
163
+ [ERROR_CODES.AUTH_TOKEN_EXPIRED]: {
164
+ message: string;
165
+ suggestions: string[];
166
+ documentation: string;
167
+ };
168
+ [ERROR_CODES.UNKNOWN_ERROR]: {
169
+ message: string;
170
+ suggestions: string[];
171
+ documentation: string;
172
+ };
173
+ };
174
+ /**
175
+ * Error carrying a code, the context used to build its message, suggested
176
+ * fixes and a documentation path, all taken from ERROR_TEMPLATES.
177
+ */
178
+ export class EnhancedError extends Error {
179
+ /**
180
+ * @param {string} code - Error code, normally one of ERROR_CODES
181
+ * @param {Object<string, *>} [context={}] - Values for the template placeholders
182
+ * @param {Error|null} [originalError=null] - Underlying error, if any
183
+ */
184
+ constructor(code: string, context?: {
185
+ [x: string]: any;
186
+ }, originalError?: Error | null);
187
+ code: string;
188
+ context: {
189
+ [x: string]: any;
190
+ };
191
+ suggestions: string[];
192
+ documentation: string;
193
+ originalError: Error;
194
+ timestamp: string;
195
+ /**
196
+ * Format this error for display with formatErrorMessage's default options.
197
+ * @returns {string} Multi-line message with code, suggestions and documentation link
198
+ */
199
+ getFormattedMessage(): string;
200
+ /**
201
+ * Plain object for JSON logging, including the stack and the original
202
+ * error's message and stack (or null).
203
+ * @returns {{name: string, code: string, message: string, suggestions: string[], documentation: string, context: Object<string, *>, timestamp: string, stack: string, originalError: ({message: string, stack: string}|null)}}
204
+ */
205
+ toJSON(): {
206
+ name: string;
207
+ code: string;
208
+ message: string;
209
+ suggestions: string[];
210
+ documentation: string;
211
+ context: {
212
+ [x: string]: any;
213
+ };
214
+ timestamp: string;
215
+ stack: string;
216
+ originalError: ({
217
+ message: string;
218
+ stack: string;
219
+ } | null);
220
+ };
221
+ }
222
+ /**
223
+ * Create an EnhancedError. Same as calling its constructor.
224
+ *
225
+ * @param {string} code - Error code
226
+ * @param {Object<string, *>} [context={}] - Placeholder values
227
+ * @param {Error|null} [originalError=null] - Underlying error, if any
228
+ * @returns {EnhancedError} Enhanced error instance
229
+ */
230
+ export function createError(code: string, context?: {
231
+ [x: string]: any;
232
+ }, originalError?: Error | null): EnhancedError;
233
+ /**
234
+ * Wrap an existing error in an EnhancedError. The original message is added
235
+ * to the context as originalMessage and the error is kept as originalError.
236
+ *
237
+ * @param {Error} error - Original error
238
+ * @param {string} code - Error code
239
+ * @param {Object<string, *>} [context={}] - Additional placeholder values
240
+ * @returns {EnhancedError} Enhanced error
241
+ */
242
+ export function wrapError(error: Error, code: string, context?: {
243
+ [x: string]: any;
244
+ }): EnhancedError;
245
+ /**
246
+ * Fill the template for an error code with context values. Unknown codes use
247
+ * the UNKNOWN_ERROR template. Each placeholder is replaced at its first
248
+ * occurrence only; placeholders with no context value stay as written.
249
+ *
250
+ * @param {string} code - Error code
251
+ * @param {Object<string, *>} [context={}] - Placeholder values, converted with String()
252
+ * @returns {{code: string, message: string, suggestions: string[], documentation: string, context: Object<string, *>}} Formatted error details
253
+ */
254
+ export function formatError(code: string, context?: {
255
+ [x: string]: any;
256
+ }): {
257
+ code: string;
258
+ message: string;
259
+ suggestions: string[];
260
+ documentation: string;
261
+ context: {
262
+ [x: string]: any;
263
+ };
264
+ };
265
+ /**
266
+ * Build a multi-line display string for an error. Fields the error lacks
267
+ * (code, suggestions, documentation) are skipped.
268
+ *
269
+ * @param {Error|EnhancedError} error - Error to format
270
+ * @param {Object} [options] - Formatting options
271
+ * @param {boolean} [options.includeCode=true] - Prefix the message with the error code
272
+ * @param {boolean} [options.includeSuggestions=true] - List the suggestions
273
+ * @param {boolean} [options.includeDocumentation=true] - Add the documentation URL
274
+ * @param {boolean} [options.includeContext=false] - List the context values as JSON
275
+ * @param {boolean} [options.includeStack=false] - Append the stack trace
276
+ * @param {boolean} [options.color=false] - Accepted but unused
277
+ * @returns {string} Formatted error message
278
+ */
279
+ export function formatErrorMessage(error: Error | EnhancedError, options?: {
280
+ includeCode?: boolean;
281
+ includeSuggestions?: boolean;
282
+ includeDocumentation?: boolean;
283
+ includeContext?: boolean;
284
+ includeStack?: boolean;
285
+ color?: boolean;
286
+ }): string;
287
+ /**
288
+ * Check whether a value is an EnhancedError instance.
289
+ *
290
+ * @param {*} error - Value to check
291
+ * @returns {boolean} True if enhanced error
292
+ */
293
+ export function isEnhancedError(error: any): boolean;
294
+ /**
295
+ * Severity for an error code: 'critical' for PIPELINE_CREATION_FAILED,
296
+ * CONFIG_NOT_FOUND, FS_PERMISSION_DENIED and AUTH_INVALID_TOKEN, 'warning'
297
+ * for the HOT_RELOAD_FAILED and HOT_RELOAD_STATE_ERROR codes, 'error' otherwise.
298
+ *
299
+ * @param {string} code - Error code
300
+ * @returns {'critical'|'warning'|'error'} Severity level
301
+ */
302
+ export function getErrorSeverity(code: string): "critical" | "warning" | "error";
303
+ /**
304
+ * Format an error and write it to a logger. Warnings go to logger.warn,
305
+ * everything else to logger.error; critical errors include the stack trace.
306
+ * Errors that are not EnhancedError are treated as 'error'.
307
+ *
308
+ * @param {Error|EnhancedError} error - Error to log
309
+ * @param {Pick<Console, 'error'|'warn'>} [logger=console] - Logger to write to
310
+ * @returns {void}
311
+ */
312
+ export function logError(error: Error | EnhancedError, logger?: Pick<Console, "error" | "warn">): void;
@@ -1,3 +1,4 @@
1
+ /** The shared structured logger (pino), exported as `logger.logger`. */
1
2
  export const logger: StructuredLogger;
2
3
  /**
3
4
  * Create a logger with correlation ID for request tracing
@@ -31,10 +32,30 @@ export class StructuredLogger {
31
32
  * Log with automatic correlation ID and context injection
32
33
  */
33
34
  _log(level: any, message: any, meta?: {}): void;
34
- info(message: any, meta: any): void;
35
- error(message: any, meta: any): void;
36
- warn(message: any, meta: any): void;
37
- debug(message: any, meta: any): void;
35
+ /**
36
+ * Logs at info level.
37
+ * @param {string} message
38
+ * @param {*} [meta] - An object, Error or value to attach
39
+ */
40
+ info(message: string, meta?: any): void;
41
+ /**
42
+ * Logs at error level.
43
+ * @param {string} message
44
+ * @param {*} [meta] - An object, Error or value to attach
45
+ */
46
+ error(message: string, meta?: any): void;
47
+ /**
48
+ * Logs at warn level.
49
+ * @param {string} message
50
+ * @param {*} [meta] - An object, Error or value to attach
51
+ */
52
+ warn(message: string, meta?: any): void;
53
+ /**
54
+ * Logs at debug level.
55
+ * @param {string} message
56
+ * @param {*} [meta] - An object, Error or value to attach
57
+ */
58
+ debug(message: string, meta?: any): void;
38
59
  /**
39
60
  * Security audit logging with enhanced metadata
40
61
  */
@@ -48,3 +69,7 @@ export class StructuredLogger {
48
69
  */
49
70
  tenant(tenantId: any, message: any, meta?: {}): void;
50
71
  }
72
+ export declare function info(message: string, meta?: any): void;
73
+ export declare function warn(message: string, meta?: any): void;
74
+ export declare function error(message: string, meta?: any): void;
75
+ export declare function debug(message: string, meta?: any): void;
@@ -1,18 +1,23 @@
1
1
  export = RateLimiter;
2
+ /**
3
+ * Sliding-window limiter: allows up to `maxAttempts` requests per identifier
4
+ * in `windowMs`, then blocks that identifier for `blockDurationMs`. Check a
5
+ * request with allowRequest(identifier).
6
+ */
2
7
  declare class RateLimiter {
3
8
  /**
4
9
  * Create a rate limiter instance
5
10
  * @param {Object} options - Configuration options
6
- * @param {number} options.maxAttempts - Maximum attempts allowed (default: 5)
7
- * @param {number} options.windowMs - Time window in milliseconds (default: 15 minutes)
8
- * @param {number} options.blockDurationMs - How long to block after exceeding limit (default: 1 hour)
9
- * @param {number} options.cleanupIntervalMs - How often to cleanup expired entries (default: 5 minutes)
11
+ * @param {number} [options.maxAttempts=5] - Maximum attempts allowed (default: 5)
12
+ * @param {number} [options.windowMs=900000] - Time window in milliseconds (default: 15 minutes)
13
+ * @param {number} [options.blockDurationMs=3600000] - How long to block after exceeding limit (default: 1 hour)
14
+ * @param {number} [options.cleanupIntervalMs=300000] - How often to cleanup expired entries (default: 5 minutes)
10
15
  */
11
16
  constructor(options?: {
12
- maxAttempts: number;
13
- windowMs: number;
14
- blockDurationMs: number;
15
- cleanupIntervalMs: number;
17
+ maxAttempts?: number;
18
+ windowMs?: number;
19
+ blockDurationMs?: number;
20
+ cleanupIntervalMs?: number;
16
21
  });
17
22
  maxAttempts: number;
18
23
  windowMs: number;
@@ -19,6 +19,16 @@ export function retry(fn: Function, opts?: {
19
19
  sleep?: Function;
20
20
  onDelay?: Function;
21
21
  }): Promise<any>;
22
+ /**
23
+ * Older wrapper around retry(): retries `fn` with exponential backoff from
24
+ * `initialDelay` ms and logs each retry and the final failure under `label`.
25
+ * @param {function(number): *} fn - The operation; called with the attempt index
26
+ * @param {Object} [options]
27
+ * @param {number} [options.retries=3] - Retries after the first attempt
28
+ * @param {number} [options.initialDelay=300] - First backoff delay in ms
29
+ * @param {string} [options.label='operation'] - Name used in log messages
30
+ * @returns {Promise<*>} The operation's result
31
+ */
22
32
  export function withRetry(_fn: any, { retries, initialDelay, label }?: {
23
33
  retries?: number;
24
34
  initialDelay?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devilsdev/rag-pipeline-utils",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "Composable retrieval-augmented generation (RAG) for Node.js: grounded citations, evaluation, guardrails, hybrid retrieval and a CLI.",
5
5
  "main": "dist/index.cjs",
6
6
  "type": "commonjs",
@@ -65,7 +65,7 @@
65
65
  "lint:diagnostic:ts": "eslint . --ext .js,.jsx,.ts,.tsx --format=stylish",
66
66
  "lint:errors-only:ts": "eslint . --ext .js,.jsx,.ts,.tsx --quiet",
67
67
  "lint-staged": "lint-staged",
68
- "postinstall": "husky install",
68
+ "prepare": "husky install",
69
69
  "verify:mocks": "node scripts/verify-fixture-mocks.js",
70
70
  "build:docs": "npm run docs:build",
71
71
  "ci": "node scripts/ci-runner.js",
@@ -84,10 +84,12 @@
84
84
  "security:audit:critical": "npm audit --audit-level=critical --production || echo 'Development vulnerabilities found but acceptable'",
85
85
  "security:audit:json": "npm audit --json > security-audit-report.json || true",
86
86
  "docs:generate": "node scripts/generate-docs.js --verbose",
87
- "docs:api": "node scripts/generate-docs.js --formats=html,markdown,json",
87
+ "docs:api": "npm run build && typedoc && prettier --write docs-site/docs/api --log-level warn",
88
88
  "benchmark": "node scripts/benchmark.js --iterations=100 --verbose",
89
89
  "benchmark:quick": "node scripts/benchmark.js --iterations=20",
90
90
  "benchmark:full": "node scripts/benchmark.js --iterations=500 --verbose",
91
+ "bench:speed": "node benchmarks/speed.js",
92
+ "bench:quality": "node benchmarks/quality.js",
91
93
  "dev": "node scripts/dev-tools.js menu",
92
94
  "dev:health": "node scripts/dev-tools.js health",
93
95
  "dev:setup": "node scripts/dev-tools.js setup",
@@ -214,18 +216,22 @@
214
216
  "eslint-plugin-storybook": "^0.12.0",
215
217
  "framer-motion": "^12.7.4",
216
218
  "husky": "^8.0.3",
219
+ "hyparquet": "1.32.0",
217
220
  "isolated-vm": "5.0.4",
218
221
  "jest": "^29.7.0",
219
222
  "jest-junit": "^16.0.0",
220
223
  "jest-transform-stub": "^2.0.0",
221
224
  "lint-staged": "^15.5.1",
222
225
  "octokit": "^4.1.3",
226
+ "pg": "8.23.1",
223
227
  "plaiceholder": "^3.0.0",
224
228
  "prettier": "^3.5.3",
225
229
  "prop-types": "^15.8.1",
226
230
  "rimraf": "^6.0.1",
227
231
  "sharp": "^0.34.1",
228
232
  "ts-jest": "^29.3.2",
233
+ "typedoc": "0.28.20",
234
+ "typedoc-plugin-markdown": "4.14.1",
229
235
  "typescript": "5.8.3",
230
236
  "why-is-node-running": "^3.2.2"
231
237
  },
@@ -249,12 +255,16 @@
249
255
  },
250
256
  "peerDependencies": {
251
257
  "isolated-vm": ">=5.0.0",
258
+ "pg": ">=8.11.0",
252
259
  "pino-pretty": ">=10.0.0"
253
260
  },
254
261
  "peerDependenciesMeta": {
255
262
  "isolated-vm": {
256
263
  "optional": true
257
264
  },
265
+ "pg": {
266
+ "optional": true
267
+ },
258
268
  "pino-pretty": {
259
269
  "optional": true
260
270
  }
@@ -68,7 +68,10 @@ class MemoryCache {
68
68
  }
69
69
  }
70
70
 
71
- /** @param {string} key */
71
+ /**
72
+ * Removes a key.
73
+ * @param {string} key
74
+ */
72
75
  async delete(key) {
73
76
  this._entries.delete(key);
74
77
  }
@@ -67,7 +67,10 @@ class RedisCache {
67
67
  }
68
68
  }
69
69
 
70
- /** @param {string} key */
70
+ /**
71
+ * Removes a key.
72
+ * @param {string} key
73
+ */
71
74
  async delete(key) {
72
75
  const del = this.client.del || this.client.unlink;
73
76
  if (typeof del === 'function')
@@ -12,6 +12,8 @@ const { logger } = require("../utils/logger.js");
12
12
  // Shared utilities
13
13
  const {
14
14
  parsePositiveInteger,
15
+ collectMin,
16
+ parseTolerance,
15
17
  generateCompletionScript,
16
18
  getVersion,
17
19
  getExtendedHelp,
@@ -28,6 +30,8 @@ const handlers = {
28
30
  config: () => require("./handlers/config-handler.js"),
29
31
  ingest: () => require("./handlers/ingest-handler.js"),
30
32
  query: () => require("./handlers/query-handler.js"),
33
+ eval: () => require("./handlers/eval-handler.js"),
34
+ generate: () => require("./handlers/generate-handler.js"),
31
35
  };
32
36
  /* eslint-enable global-require */
33
37
 
@@ -154,6 +158,81 @@ class EnhancedCLI {
154
158
  .handleQuery(this.program.opts(), question, options);
155
159
  });
156
160
 
161
+ this.program
162
+ .command("eval")
163
+ .description(
164
+ "Run an evaluation dataset; exit 1 on failures, low scores or regressions",
165
+ )
166
+ .argument("<dataset>", "Test set: .json (array or { cases }) or .jsonl")
167
+ .option("--baseline <report>", "Compare with a report saved by --output")
168
+ .option("--output <report>", "Save this run's report as JSON")
169
+ .option(
170
+ "--report <file>",
171
+ "Write a summary: .html for a page, .md for Markdown",
172
+ )
173
+ .option(
174
+ "--min <metric=value>",
175
+ "Minimum mean for a metric, e.g. faithfulness=0.8 (repeatable)",
176
+ collectMin,
177
+ )
178
+ .option(
179
+ "--tolerance <n>",
180
+ "Allowed drop in a metric's mean from the baseline (default 0.02)",
181
+ parseTolerance,
182
+ )
183
+ .option(
184
+ "--case-tolerance <n>",
185
+ "Drop in one case's score that lists it (default 0.1)",
186
+ parseTolerance,
187
+ )
188
+ .option(
189
+ "--concurrency <n>",
190
+ "Cases run at the same time",
191
+ parsePositiveInteger,
192
+ 1,
193
+ )
194
+ .option(
195
+ "--timeout <ms>",
196
+ "Abort each case after this many milliseconds",
197
+ parsePositiveInteger,
198
+ )
199
+ .option("--top-k <n>", "Passages to retrieve", parsePositiveInteger, 3)
200
+ .option("--json", "Print the verdict, summary and comparison as JSON")
201
+ .action(async (dataset, options) => {
202
+ await handlers.eval().handleEval(this.program.opts(), dataset, options);
203
+ });
204
+
205
+ this.program
206
+ .command("generate-dataset")
207
+ .description(
208
+ "Write a test set from the index: the llm writes questions about each chunk",
209
+ )
210
+ .requiredOption("--output <file>", "Where to write the test set (.json)")
211
+ .option("--count <n>", "Cases to write at most", parsePositiveInteger, 20)
212
+ .option(
213
+ "--per-document <n>",
214
+ "Questions to ask per chunk",
215
+ parsePositiveInteger,
216
+ 1,
217
+ )
218
+ .option(
219
+ "--min-faithfulness <n>",
220
+ "Share of an answer's sentences its chunk must support (default 1)",
221
+ parseTolerance,
222
+ )
223
+ .option(
224
+ "--concurrency <n>",
225
+ "Chunks processed at the same time",
226
+ parsePositiveInteger,
227
+ 1,
228
+ )
229
+ .option("--force", "Replace --output if it exists")
230
+ .action(async (options) => {
231
+ await handlers
232
+ .generate()
233
+ .handleGenerateDataset(this.program.opts(), options);
234
+ });
235
+
157
236
  this.program
158
237
  .command("validate")
159
238
  .description("Validate the configuration and resolve its components")
@@ -162,7 +162,7 @@ async function handleFeedback(globalOptions, question, docId, options = {}) {
162
162
  `Retriever "${components.names.retriever}" cannot list documents. Use "file-store".`,
163
163
  );
164
164
  }
165
- if (!retriever.list().some((doc) => doc.id === docId)) {
165
+ if (!(await retriever.list()).some((doc) => doc.id === docId)) {
166
166
  throw new Error(
167
167
  `No indexed chunk with id "${docId}". Ids are shown by \`rag-pipeline ai search\`.`,
168
168
  );
@@ -197,7 +197,7 @@ async function handleSearch(globalOptions, question, options = {}) {
197
197
  const topK = options.topK || 3;
198
198
  const components = loadComponents({ config, configDir });
199
199
  const { retriever } = components;
200
- const size = retriever.size ? retriever.size() : topK;
200
+ const size = retriever.size ? await retriever.size() : topK;
201
201
  // Re-score a larger pool so a boosted chunk can move into the top-k.
202
202
  const pool = Math.min(size, Math.max(topK * 4, topK + 10));
203
203
  const results = await retrieve({ components, question, topK: pool });