@devilsdev/rag-pipeline-utils 2.2.1 → 2.3.1

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 (40) hide show
  1. package/README.md +260 -19
  2. package/contracts/contract-schema.json +189 -0
  3. package/dist/index.cjs +19 -0
  4. package/dist/index.mjs +15 -1
  5. package/package.json +3 -2
  6. package/src/cli/commands/wizard.js +627 -0
  7. package/src/cli/input-sanitization-middleware.js +356 -0
  8. package/src/cli/templates/development.template.json +64 -0
  9. package/src/cli/templates/minimal.template.json +33 -0
  10. package/src/cli/templates/production.template.json +104 -0
  11. package/src/cli/templates/testing.template.json +45 -0
  12. package/src/config/connection-pool-config.json +259 -0
  13. package/src/config/memory-optimization.js +414 -0
  14. package/src/config/redaction-patterns.json +150 -0
  15. package/src/config/sanitization-rules.json +323 -0
  16. package/src/core/plugin-registry.js +266 -20
  17. package/src/dag/core/error-context.js +391 -0
  18. package/src/dag/core/execution-scheduler.js +350 -0
  19. package/src/dag/core/topology-validation.js +415 -0
  20. package/src/dag/dag-engine.js +26 -424
  21. package/src/dev/README.md +126 -0
  22. package/src/dev/dev-server.js +356 -0
  23. package/src/dev/examples/hot-reload-usage.js +355 -0
  24. package/src/dev/hot-reload.js +807 -0
  25. package/src/enterprise/sso-integration.js +67 -25
  26. package/src/index.js +19 -0
  27. package/src/observability/event-logger.js +62 -2
  28. package/src/plugins/optimized-openai-embedder.js +169 -0
  29. package/src/security/jwt-config.js +431 -0
  30. package/src/security/jwt-validator.js +701 -0
  31. package/src/utils/api-client-pool-integration.js +436 -0
  32. package/src/utils/batch-processor.js +710 -0
  33. package/src/utils/connection-pool.js +605 -0
  34. package/src/utils/contract-validator.js +460 -0
  35. package/src/utils/error-formatter.js +603 -0
  36. package/src/utils/examples/error-formatter-usage.js +351 -0
  37. package/src/utils/input-sanitizer.js +967 -0
  38. package/src/utils/memory-monitor.js +800 -0
  39. package/src/utils/secure-logger-config.js +352 -0
  40. package/src/utils/secure-logger.js +576 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * @fileoverview Contract Validation Utilities
3
+ *
4
+ * Provides utilities for validating plugin implementations against their
5
+ * contract specifications. Ensures plugins conform to required interfaces,
6
+ * method signatures, and metadata requirements.
7
+ *
8
+ * @module utils/contract-validator
9
+ * @author Ali Kahwaji
10
+ * @since 2.2.4
11
+ * @version 1.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ /**
20
+ * ContractValidator class for validating plugin implementations
21
+ *
22
+ * Provides comprehensive validation of:
23
+ * - Plugin metadata (name, version, type)
24
+ * - Required methods existence
25
+ * - Method signatures and parameter counts
26
+ * - Method return types
27
+ * - Contract compliance reporting
28
+ *
29
+ * @class
30
+ * @since 2.2.4
31
+ *
32
+ * @example
33
+ * const validator = new ContractValidator();
34
+ * const contract = validator.loadContract('loader');
35
+ * const result = validator.validatePlugin(myPlugin, contract);
36
+ * if (!result.valid) {
37
+ * console.error('Validation errors:', result.errors);
38
+ * }
39
+ */
40
+ class ContractValidator {
41
+ /**
42
+ * Create a ContractValidator instance
43
+ *
44
+ * @param {Object} [options={}] - Validator configuration
45
+ * @param {string} [options.contractsDir='./contracts'] - Contracts directory path
46
+ */
47
+ constructor(options = {}) {
48
+ this.contractsDir =
49
+ options.contractsDir || path.join(process.cwd(), 'contracts');
50
+ }
51
+
52
+ /**
53
+ * Load a contract from the contracts directory
54
+ *
55
+ * @param {string} type - Plugin type (loader, embedder, retriever, llm, reranker)
56
+ * @returns {Object} Contract specification
57
+ * @throws {Error} If contract file not found or invalid
58
+ *
59
+ * @example
60
+ * const contract = validator.loadContract('loader');
61
+ * console.log(contract.type); // 'loader'
62
+ * console.log(contract.version); // '1.0.0'
63
+ *
64
+ * @since 2.2.4
65
+ */
66
+ loadContract(type) {
67
+ const contractPath = path.join(this.contractsDir, `${type}-contract.json`);
68
+
69
+ if (!fs.existsSync(contractPath)) {
70
+ throw new Error(
71
+ `Contract not found for type: ${type} at ${contractPath}`,
72
+ );
73
+ }
74
+
75
+ try {
76
+ const contractData = fs.readFileSync(contractPath, 'utf8');
77
+ return JSON.parse(contractData);
78
+ } catch (error) {
79
+ throw new Error(`Failed to load contract for ${type}: ${error.message}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Load all contracts from the contracts directory
85
+ *
86
+ * @returns {Object} Map of plugin type to contract
87
+ *
88
+ * @example
89
+ * const contracts = validator.loadAllContracts();
90
+ * // { loader: {...}, embedder: {...}, ... }
91
+ *
92
+ * @since 2.2.4
93
+ */
94
+ loadAllContracts() {
95
+ const contracts = {};
96
+
97
+ if (!fs.existsSync(this.contractsDir)) {
98
+ return contracts;
99
+ }
100
+
101
+ const files = fs.readdirSync(this.contractsDir);
102
+
103
+ for (const file of files) {
104
+ if (file.endsWith('-contract.json')) {
105
+ const type = file.replace('-contract.json', '');
106
+ try {
107
+ contracts[type] = this.loadContract(type);
108
+ } catch (error) {
109
+ // Skip invalid contracts
110
+ console.warn(`Skipping invalid contract: ${file}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ return contracts;
116
+ }
117
+
118
+ /**
119
+ * Validate plugin metadata structure
120
+ *
121
+ * Validates that plugin has required metadata properties:
122
+ * - name (string)
123
+ * - version (string)
124
+ * - type (string matching contract type)
125
+ *
126
+ * @param {Object} plugin - Plugin instance to validate
127
+ * @param {Object} contract - Contract specification
128
+ * @returns {Object} Validation result { valid: boolean, errors: string[] }
129
+ *
130
+ * @example
131
+ * const result = validator.validateMetadata(myPlugin, contract);
132
+ * if (!result.valid) {
133
+ * console.error('Metadata errors:', result.errors);
134
+ * }
135
+ *
136
+ * @since 2.2.4
137
+ */
138
+ validateMetadata(plugin, contract) {
139
+ const errors = [];
140
+
141
+ if (!plugin) {
142
+ errors.push('Plugin is null or undefined');
143
+ return { valid: false, errors };
144
+ }
145
+
146
+ // Check name
147
+ if (typeof plugin.name !== 'string' || plugin.name.trim() === '') {
148
+ errors.push(
149
+ 'Plugin must have a valid "name" property (non-empty string)',
150
+ );
151
+ }
152
+
153
+ // Check version
154
+ if (typeof plugin.version !== 'string' || plugin.version.trim() === '') {
155
+ errors.push(
156
+ 'Plugin must have a valid "version" property (non-empty string)',
157
+ );
158
+ }
159
+
160
+ // Check type matches contract
161
+ if (plugin.type !== contract.type) {
162
+ errors.push(
163
+ `Plugin type "${plugin.type}" does not match contract type "${contract.type}"`,
164
+ );
165
+ }
166
+
167
+ return {
168
+ valid: errors.length === 0,
169
+ errors,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Validate required methods exist and are functions
175
+ *
176
+ * Ensures all methods listed in contract.required are present
177
+ * on the plugin and are callable functions.
178
+ *
179
+ * @param {Object} plugin - Plugin instance to validate
180
+ * @param {Object} contract - Contract specification
181
+ * @returns {Object} Validation result { valid: boolean, errors: string[] }
182
+ *
183
+ * @example
184
+ * const result = validator.validateRequiredMethods(myPlugin, contract);
185
+ * // Check all required methods exist
186
+ *
187
+ * @since 2.2.4
188
+ */
189
+ validateRequiredMethods(plugin, contract) {
190
+ const errors = [];
191
+
192
+ if (!contract.required || !Array.isArray(contract.required)) {
193
+ return { valid: true, errors: [] };
194
+ }
195
+
196
+ for (const methodName of contract.required) {
197
+ if (!(methodName in plugin)) {
198
+ errors.push(`Required method "${methodName}" is missing`);
199
+ continue;
200
+ }
201
+
202
+ if (typeof plugin[methodName] !== 'function') {
203
+ errors.push(`Required property "${methodName}" must be a function`);
204
+ }
205
+ }
206
+
207
+ return {
208
+ valid: errors.length === 0,
209
+ errors,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Validate method signatures match contract specifications
215
+ *
216
+ * Checks that method parameter counts align with contract specifications.
217
+ * Note: JavaScript does not enforce parameter types, so this validates
218
+ * parameter count and naming conventions.
219
+ *
220
+ * @param {Object} plugin - Plugin instance to validate
221
+ * @param {Object} contract - Contract specification
222
+ * @returns {Object} Validation result { valid: boolean, errors: string[], warnings: string[] }
223
+ *
224
+ * @example
225
+ * const result = validator.validateMethodSignatures(myPlugin, contract);
226
+ * if (result.warnings.length) {
227
+ * console.warn('Signature warnings:', result.warnings);
228
+ * }
229
+ *
230
+ * @since 2.2.4
231
+ */
232
+ validateMethodSignatures(plugin, contract) {
233
+ const errors = [];
234
+ const warnings = [];
235
+
236
+ if (!contract.methods || !Array.isArray(contract.methods)) {
237
+ return { valid: true, errors: [], warnings: [] };
238
+ }
239
+
240
+ for (const methodSpec of contract.methods) {
241
+ const methodName = methodSpec.name;
242
+
243
+ if (!(methodName in plugin)) {
244
+ continue; // Already caught by validateRequiredMethods
245
+ }
246
+
247
+ const method = plugin[methodName];
248
+ if (typeof method !== 'function') {
249
+ continue; // Already caught by validateRequiredMethods
250
+ }
251
+
252
+ // Check parameter count
253
+ const expectedParams = methodSpec.parameters || [];
254
+ const methodLength = method.length;
255
+
256
+ // JavaScript functions can accept more parameters than defined,
257
+ // but we check if they accept at least the required ones
258
+ if (methodLength < expectedParams.length) {
259
+ warnings.push(
260
+ `Method "${methodName}" expects ${expectedParams.length} parameters ` +
261
+ `but function signature shows ${methodLength}`,
262
+ );
263
+ }
264
+
265
+ // Extract parameter names from function signature
266
+ const paramNames = this._extractParameterNames(method);
267
+
268
+ // Check if parameter names match (case-insensitive suggestion)
269
+ for (let i = 0; i < expectedParams.length && i < paramNames.length; i++) {
270
+ if (paramNames[i] !== expectedParams[i]) {
271
+ warnings.push(
272
+ `Method "${methodName}" parameter ${i + 1} is named "${paramNames[i]}" ` +
273
+ `but contract suggests "${expectedParams[i]}"`,
274
+ );
275
+ }
276
+ }
277
+ }
278
+
279
+ return {
280
+ valid: errors.length === 0,
281
+ errors,
282
+ warnings,
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Extract parameter names from a function
288
+ *
289
+ * Uses Function.toString() to parse parameter names. This is a best-effort
290
+ * approach and may not work for all function types (arrow functions, etc.).
291
+ *
292
+ * @private
293
+ * @param {Function} func - Function to extract parameters from
294
+ * @returns {string[]} Array of parameter names
295
+ *
296
+ * @since 2.2.4
297
+ */
298
+ _extractParameterNames(func) {
299
+ try {
300
+ const funcStr = func.toString();
301
+
302
+ // Match function parameters
303
+ let match = funcStr.match(/\(([^)]*)\)/);
304
+ if (!match) return [];
305
+
306
+ const params = match[1]
307
+ .split(',')
308
+ .map((p) => p.trim())
309
+ .filter((p) => p && p !== '');
310
+
311
+ // Extract just the parameter name (before '=' for defaults)
312
+ return params.map((p) => p.split('=')[0].trim());
313
+ } catch (error) {
314
+ return [];
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Validate complete plugin implementation against contract
320
+ *
321
+ * Runs all validation checks:
322
+ * - Metadata validation
323
+ * - Required methods validation
324
+ * - Method signature validation
325
+ *
326
+ * Returns comprehensive validation result with all errors and warnings.
327
+ *
328
+ * @param {Object} plugin - Plugin instance to validate
329
+ * @param {Object} contract - Contract specification
330
+ * @returns {Object} Validation result
331
+ * @returns {boolean} result.valid - Overall validation status
332
+ * @returns {string[]} result.errors - Critical errors
333
+ * @returns {string[]} result.warnings - Non-critical warnings
334
+ * @returns {Object} result.details - Detailed validation results
335
+ *
336
+ * @example
337
+ * const result = validator.validatePlugin(myPlugin, contract);
338
+ * if (!result.valid) {
339
+ * console.error('Validation failed:');
340
+ * result.errors.forEach(err => console.error(` - ${err}`));
341
+ * }
342
+ * if (result.warnings.length) {
343
+ * console.warn('Warnings:');
344
+ * result.warnings.forEach(warn => console.warn(` - ${warn}`));
345
+ * }
346
+ *
347
+ * @since 2.2.4
348
+ */
349
+ validatePlugin(plugin, contract) {
350
+ const metadataResult = this.validateMetadata(plugin, contract);
351
+ const methodsResult = this.validateRequiredMethods(plugin, contract);
352
+ const signaturesResult = this.validateMethodSignatures(plugin, contract);
353
+
354
+ const errors = [
355
+ ...metadataResult.errors,
356
+ ...methodsResult.errors,
357
+ ...signaturesResult.errors,
358
+ ];
359
+
360
+ const warnings = [...(signaturesResult.warnings || [])];
361
+
362
+ return {
363
+ valid: errors.length === 0,
364
+ errors,
365
+ warnings,
366
+ details: {
367
+ metadata: metadataResult,
368
+ requiredMethods: methodsResult,
369
+ signatures: signaturesResult,
370
+ },
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Format validation result for console output
376
+ *
377
+ * Creates a human-readable report of validation results.
378
+ *
379
+ * @param {Object} result - Validation result from validatePlugin
380
+ * @param {string} pluginName - Name of plugin being validated
381
+ * @returns {string} Formatted validation report
382
+ *
383
+ * @example
384
+ * const result = validator.validatePlugin(plugin, contract);
385
+ * const report = validator.formatValidationReport(result, 'MyPlugin');
386
+ * console.log(report);
387
+ *
388
+ * @since 2.2.4
389
+ */
390
+ formatValidationReport(result, pluginName = 'Plugin') {
391
+ const lines = [];
392
+
393
+ lines.push(`Validation Report for: ${pluginName}`);
394
+ lines.push('='.repeat(50));
395
+ lines.push(`Status: ${result.valid ? '✓ PASS' : '✗ FAIL'}`);
396
+ lines.push('');
397
+
398
+ if (result.errors.length > 0) {
399
+ lines.push('Errors:');
400
+ result.errors.forEach((err) => lines.push(` ✗ ${err}`));
401
+ lines.push('');
402
+ }
403
+
404
+ if (result.warnings.length > 0) {
405
+ lines.push('Warnings:');
406
+ result.warnings.forEach((warn) => lines.push(` ⚠ ${warn}`));
407
+ lines.push('');
408
+ }
409
+
410
+ if (result.valid && result.warnings.length === 0) {
411
+ lines.push('All validation checks passed!');
412
+ }
413
+
414
+ return lines.join('\n');
415
+ }
416
+
417
+ /**
418
+ * Validate multiple plugins against their contracts
419
+ *
420
+ * Batch validation of multiple plugins, useful for testing entire
421
+ * plugin ecosystems.
422
+ *
423
+ * @param {Object[]} plugins - Array of plugin instances
424
+ * @param {Object} contracts - Map of contract types to contracts
425
+ * @returns {Object[]} Array of validation results
426
+ *
427
+ * @example
428
+ * const plugins = [loaderPlugin, embedderPlugin, retrieverPlugin];
429
+ * const contracts = validator.loadAllContracts();
430
+ * const results = validator.validatePlugins(plugins, contracts);
431
+ * const failedPlugins = results.filter(r => !r.valid);
432
+ *
433
+ * @since 2.2.4
434
+ */
435
+ validatePlugins(plugins, contracts) {
436
+ return plugins.map((plugin) => {
437
+ const contract = contracts[plugin.type];
438
+ if (!contract) {
439
+ return {
440
+ plugin: plugin.name || 'Unknown',
441
+ valid: false,
442
+ errors: [`No contract found for plugin type: ${plugin.type}`],
443
+ warnings: [],
444
+ };
445
+ }
446
+
447
+ const result = this.validatePlugin(plugin, contract);
448
+ return {
449
+ plugin: plugin.name || 'Unknown',
450
+ type: plugin.type,
451
+ ...result,
452
+ };
453
+ });
454
+ }
455
+ }
456
+
457
+ module.exports = ContractValidator;
458
+
459
+ // Default export for convenience
460
+ module.exports.default = ContractValidator;