@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.
- package/README.md +260 -19
- package/contracts/contract-schema.json +189 -0
- package/dist/index.cjs +19 -0
- package/dist/index.mjs +15 -1
- package/package.json +3 -2
- package/src/cli/commands/wizard.js +627 -0
- package/src/cli/input-sanitization-middleware.js +356 -0
- package/src/cli/templates/development.template.json +64 -0
- package/src/cli/templates/minimal.template.json +33 -0
- package/src/cli/templates/production.template.json +104 -0
- package/src/cli/templates/testing.template.json +45 -0
- package/src/config/connection-pool-config.json +259 -0
- package/src/config/memory-optimization.js +414 -0
- package/src/config/redaction-patterns.json +150 -0
- package/src/config/sanitization-rules.json +323 -0
- package/src/core/plugin-registry.js +266 -20
- package/src/dag/core/error-context.js +391 -0
- package/src/dag/core/execution-scheduler.js +350 -0
- package/src/dag/core/topology-validation.js +415 -0
- package/src/dag/dag-engine.js +26 -424
- package/src/dev/README.md +126 -0
- package/src/dev/dev-server.js +356 -0
- package/src/dev/examples/hot-reload-usage.js +355 -0
- package/src/dev/hot-reload.js +807 -0
- package/src/enterprise/sso-integration.js +67 -25
- package/src/index.js +19 -0
- package/src/observability/event-logger.js +62 -2
- package/src/plugins/optimized-openai-embedder.js +169 -0
- package/src/security/jwt-config.js +431 -0
- package/src/security/jwt-validator.js +701 -0
- package/src/utils/api-client-pool-integration.js +436 -0
- package/src/utils/batch-processor.js +710 -0
- package/src/utils/connection-pool.js +605 -0
- package/src/utils/contract-validator.js +460 -0
- package/src/utils/error-formatter.js +603 -0
- package/src/utils/examples/error-formatter-usage.js +351 -0
- package/src/utils/input-sanitizer.js +967 -0
- package/src/utils/memory-monitor.js +800 -0
- package/src/utils/secure-logger-config.js +352 -0
- 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;
|