@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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/.ragrc.schema.json +218 -0
- package/README.md +178 -550
- package/bin/cli.js +8 -14
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +18 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +620 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +294 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +1030 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +111 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/rate-limiter.js +244 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +388 -0
- package/src/utils/validate-plugin-contract.js +42 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Path: /src/utils/plugin-scaffolder.js
|
|
4
|
+
* Description: Plugin scaffolding utility for creating new plugins with best practices
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
// eslint-disable-line global-require
|
|
10
|
+
const path = require('path');
|
|
11
|
+
// eslint-disable-line global-require
|
|
12
|
+
const { pluginContracts } = require('../core/plugin-contracts.js');
|
|
13
|
+
// eslint-disable-line global-require
|
|
14
|
+
|
|
15
|
+
class PluginScaffolder {
|
|
16
|
+
constructor(_options = {}) {
|
|
17
|
+
this.outputDir = _options.outputDir || process.cwd();
|
|
18
|
+
this.author = _options.author || 'Your Name';
|
|
19
|
+
this.namespace = _options.namespace || 'your-namespace';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Scaffold a new plugin with all necessary files
|
|
24
|
+
* @param {string} pluginType - Type of plugin (loader, embedder, retriever, llm, reranker)
|
|
25
|
+
* @param {string} pluginName - Name of the plugin
|
|
26
|
+
* @param {object} _options - Additional _options
|
|
27
|
+
*/
|
|
28
|
+
async scaffoldPlugin(pluginType, pluginName, _options = {}) {
|
|
29
|
+
if (!pluginContracts[pluginType]) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unknown plugin _type: ${pluginType}. Available types: ${Object.keys(pluginContracts).join(', ')}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pluginDir = path.join(this.outputDir, `${pluginName}-${pluginType}`);
|
|
36
|
+
|
|
37
|
+
// Create plugin directory
|
|
38
|
+
if (!fs.existsSync(pluginDir)) {
|
|
39
|
+
fs.mkdirSync(pluginDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Generate all plugin files
|
|
43
|
+
await this.generatePluginFile(pluginDir, pluginType, pluginName, _options);
|
|
44
|
+
await this.generateTestFile(pluginDir, pluginType, pluginName);
|
|
45
|
+
await this.generateMockFile(pluginDir, pluginType, pluginName);
|
|
46
|
+
await this.generateConfigExample(pluginDir, pluginType, pluginName);
|
|
47
|
+
await this.generateReadme(pluginDir, pluginType, pluginName, _options);
|
|
48
|
+
await this.generatePackageJson(pluginDir, pluginType, pluginName, _options);
|
|
49
|
+
|
|
50
|
+
console.log(`✅ Plugin scaffolded successfully at: ${pluginDir}`);
|
|
51
|
+
// eslint-disable-line no-console
|
|
52
|
+
console.log('📁 Generated files:');
|
|
53
|
+
// eslint-disable-line no-console
|
|
54
|
+
console.log(` - src/${pluginName}-${pluginType}.js (main plugin)`);
|
|
55
|
+
// eslint-disable-line no-console
|
|
56
|
+
console.log(` - __tests__/${pluginName}-${pluginType}.test.js (tests)`);
|
|
57
|
+
// eslint-disable-line no-console
|
|
58
|
+
console.log(
|
|
59
|
+
` - mocks/${pluginName}-${pluginType}-mock.js (mock implementation)`,
|
|
60
|
+
);
|
|
61
|
+
// eslint-disable-line no-console
|
|
62
|
+
console.log(' - .ragrc.example.json (configuration example)');
|
|
63
|
+
// eslint-disable-line no-console
|
|
64
|
+
console.log(' - README.md (documentation)');
|
|
65
|
+
// eslint-disable-line no-console
|
|
66
|
+
console.log(' - package.json (npm package)');
|
|
67
|
+
// eslint-disable-line no-console
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate the main plugin implementation file
|
|
72
|
+
*/
|
|
73
|
+
async generatePluginFile(pluginDir, pluginType, pluginName, _options) {
|
|
74
|
+
const contract = pluginContracts[pluginType];
|
|
75
|
+
const className = this.toPascalCase(`${pluginName}_${pluginType}`);
|
|
76
|
+
const srcDir = path.join(pluginDir, 'src');
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(srcDir)) {
|
|
79
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const methods = contract.requiredMethods
|
|
83
|
+
.map((methodName) => {
|
|
84
|
+
const signature = contract.methodSignatures?.[methodName];
|
|
85
|
+
return this.generateMethodStub(methodName, signature);
|
|
86
|
+
})
|
|
87
|
+
.join('\n\n');
|
|
88
|
+
|
|
89
|
+
const optionalMethods =
|
|
90
|
+
contract.optionalMethods
|
|
91
|
+
?.map((methodName) => {
|
|
92
|
+
const signature = contract.methodSignatures?.[methodName];
|
|
93
|
+
return this.generateMethodStub(methodName, signature, true);
|
|
94
|
+
})
|
|
95
|
+
.join('\n\n') || '';
|
|
96
|
+
|
|
97
|
+
const kebabName = pluginName
|
|
98
|
+
.replace(/([A-Z])/g, '-$1')
|
|
99
|
+
.toLowerCase()
|
|
100
|
+
.replace(/^-/, '');
|
|
101
|
+
|
|
102
|
+
const content = `/**
|
|
103
|
+
* ${className} - ${pluginType} plugin implementation
|
|
104
|
+
* Generated by RAG Pipeline Utils Plugin Scaffolder
|
|
105
|
+
* Author: ${this.author}
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
class ${className} {
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
this.name = '${kebabName}';
|
|
111
|
+
this.options = options;
|
|
112
|
+
|
|
113
|
+
// Add your initialization logic here
|
|
114
|
+
// Example: this.apiKey = options.apiKey;
|
|
115
|
+
// Example: this.endpoint = options.endpoint || 'https://api.example.com';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
${methods}${optionalMethods ? '\n\n // Optional methods\n' + optionalMethods : ''}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = ${className};
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const _filePath = path.join(srcDir, `${pluginName}-${pluginType}.js`);
|
|
125
|
+
fs.writeFileSync(_filePath, content);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Generate method stub with proper signature
|
|
130
|
+
*/
|
|
131
|
+
generateMethodStub(methodName, signature, isOptional = false) {
|
|
132
|
+
const params = signature?.params || ['input'];
|
|
133
|
+
const paramTypes = signature?.paramTypes || ['any'];
|
|
134
|
+
const returnType = signature?.returnType || 'Promise<any>';
|
|
135
|
+
const description =
|
|
136
|
+
signature?.description ||
|
|
137
|
+
`${isOptional ? 'Optional: ' : ''}Implement ${methodName} functionality`;
|
|
138
|
+
|
|
139
|
+
const paramList = params.map((param, _i) => `${param}`).join(', ');
|
|
140
|
+
const paramDocs = params
|
|
141
|
+
.map((param, _i) => ` * @param {${paramTypes[_i]}} ${param}`)
|
|
142
|
+
.join('\n');
|
|
143
|
+
|
|
144
|
+
return ` /**
|
|
145
|
+
* ${description}
|
|
146
|
+
${paramDocs}
|
|
147
|
+
* @returns {${returnType}}
|
|
148
|
+
*/
|
|
149
|
+
async ${methodName}(${paramList}) {
|
|
150
|
+
// TODO: Implement ${methodName} logic
|
|
151
|
+
throw new Error('${methodName} not yet implemented');
|
|
152
|
+
}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate test file
|
|
157
|
+
*/
|
|
158
|
+
async generateTestFile(pluginDir, pluginType, pluginName) {
|
|
159
|
+
const className = this.toPascalCase(`${pluginName}_${pluginType}`);
|
|
160
|
+
const testDir = path.join(pluginDir, '__tests__');
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(testDir)) {
|
|
163
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const contract = pluginContracts[pluginType];
|
|
167
|
+
const testCases = contract.requiredMethods
|
|
168
|
+
.map((method) => {
|
|
169
|
+
return ` describe('${method}', () => {
|
|
170
|
+
it('should implement ${method} method', () => {
|
|
171
|
+
expect(typeof plugin.${method}).toBe('function');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should handle ${method} functionality', async () => {
|
|
175
|
+
// TODO: Add specific test cases for ${method}
|
|
176
|
+
// Example: const result = await plugin.${method}(testInput);
|
|
177
|
+
// Example: expect(result).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
});`;
|
|
180
|
+
})
|
|
181
|
+
.join('\n\n');
|
|
182
|
+
|
|
183
|
+
const content = `/**
|
|
184
|
+
* Tests for ${className}
|
|
185
|
+
* Generated by RAG Pipeline Utils Plugin Scaffolder
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
const { ${className} } = require('../src/${pluginName}-${pluginType}.js');
|
|
189
|
+
|
|
190
|
+
describe('${className}', () => {
|
|
191
|
+
let plugin;
|
|
192
|
+
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
plugin = new ${className}({
|
|
195
|
+
// Add test configuration _options here
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('constructor', () => {
|
|
200
|
+
it('should create _instance with default _options', () => {
|
|
201
|
+
const _instance = new ${className}();
|
|
202
|
+
expect(_instance).toBeInstanceOf(${className});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should accept configuration _options', () => {
|
|
206
|
+
const _options = { testOption: 'value' };
|
|
207
|
+
const _instance = new ${className}(_options);
|
|
208
|
+
expect(_instance._options).toEqual(_options);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
${testCases}
|
|
213
|
+
|
|
214
|
+
describe('error handling', () => {
|
|
215
|
+
it('should handle invalid inputs gracefully', async () => {
|
|
216
|
+
// TODO: Add error handling tests
|
|
217
|
+
// Example: await expect(plugin.someMethod(null)).rejects.toThrow();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const _filePath = path.join(testDir, `${pluginName}-${pluginType}.test.js`);
|
|
224
|
+
fs.writeFileSync(_filePath, content);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Generate mock implementation file
|
|
229
|
+
*/
|
|
230
|
+
async generateMockFile(pluginDir, pluginType, pluginName) {
|
|
231
|
+
const className = this.toPascalCase(`${pluginName}_${pluginType}`);
|
|
232
|
+
const mockDir = path.join(pluginDir, 'mocks');
|
|
233
|
+
|
|
234
|
+
if (!fs.existsSync(mockDir)) {
|
|
235
|
+
fs.mkdirSync(mockDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const contract = pluginContracts[pluginType];
|
|
239
|
+
const mockMethods = contract.requiredMethods
|
|
240
|
+
.map((methodName) => {
|
|
241
|
+
return ` async ${methodName}(...args) {
|
|
242
|
+
// Mock implementation for ${methodName}
|
|
243
|
+
console.log(\`Mock ${className}.${methodName} called with:\`, args);
|
|
244
|
+
// eslint-disable-line no-console
|
|
245
|
+
return \`Mock result from ${methodName}\`;
|
|
246
|
+
}`;
|
|
247
|
+
})
|
|
248
|
+
.join('\n\n');
|
|
249
|
+
|
|
250
|
+
const content = `/**
|
|
251
|
+
* Mock implementation of ${className}
|
|
252
|
+
* Generated by RAG Pipeline Utils Plugin Scaffolder
|
|
253
|
+
* Use this for testing and development
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
class Mock${className} {
|
|
257
|
+
constructor(_options = {}) {
|
|
258
|
+
this._options = _options;
|
|
259
|
+
this.mockMode = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
${mockMethods}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Export default instance for easy use
|
|
266
|
+
Mock${className}();
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
const _filePath = path.join(mockDir, `${pluginName}-${pluginType}-mock.js`);
|
|
270
|
+
fs.writeFileSync(_filePath, content);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Generate configuration example
|
|
275
|
+
*/
|
|
276
|
+
async generateConfigExample(pluginDir, pluginType, pluginName) {
|
|
277
|
+
const _config = {
|
|
278
|
+
[pluginType]: {
|
|
279
|
+
[pluginName]: `./src/${pluginName}-${pluginType}.js`,
|
|
280
|
+
},
|
|
281
|
+
namespace: `${pluginName}-namespace`,
|
|
282
|
+
pipeline: ['loader', 'embedder', 'retriever'],
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const content = JSON.stringify(_config, null, 2);
|
|
286
|
+
const _filePath = path.join(pluginDir, '.ragrc.example.json');
|
|
287
|
+
fs.writeFileSync(_filePath, content);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Generate README documentation
|
|
292
|
+
*/
|
|
293
|
+
async generateReadme(pluginDir, pluginType, pluginName, _options) {
|
|
294
|
+
const className = this.toPascalCase(`${pluginName}_${pluginType}`);
|
|
295
|
+
const contract = pluginContracts[pluginType];
|
|
296
|
+
|
|
297
|
+
const methodDocs = contract.requiredMethods
|
|
298
|
+
.map((method) => {
|
|
299
|
+
const signature = contract.methodSignatures?.[method];
|
|
300
|
+
return `### \`${method}(${signature?.params?.join(', ') || '...args'})\`
|
|
301
|
+
|
|
302
|
+
${signature?.description || `Implements ${method} functionality`}
|
|
303
|
+
|
|
304
|
+
**Parameters:**
|
|
305
|
+
${signature?.params?.map((param, _i) => `- \`${param}\` (${signature.paramTypes?.[_i] || 'any'})`).join('\n') || '- TBD'}
|
|
306
|
+
|
|
307
|
+
**Returns:** \`${signature?.returnType || 'Promise<any>'}\``;
|
|
308
|
+
})
|
|
309
|
+
.join('\n\n');
|
|
310
|
+
|
|
311
|
+
const content = `# ${className}
|
|
312
|
+
|
|
313
|
+
A ${pluginType} plugin for RAG Pipeline Utils.
|
|
314
|
+
|
|
315
|
+
## Description
|
|
316
|
+
|
|
317
|
+
${_options.description || `This plugin provides ${pluginType} functionality for RAG (Retrieval-Augmented Generation) pipelines.`}
|
|
318
|
+
|
|
319
|
+
## Installation
|
|
320
|
+
|
|
321
|
+
\`\`\`bash
|
|
322
|
+
npm install ${this.namespace}/${pluginName}-${pluginType}
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
## Usage
|
|
326
|
+
|
|
327
|
+
### Basic Usage
|
|
328
|
+
|
|
329
|
+
\`\`\`javascript
|
|
330
|
+
const { ${className} } = require('${this.namespace}/${pluginName}-${pluginType}');
|
|
331
|
+
|
|
332
|
+
const plugin = new ${className}({
|
|
333
|
+
// Configuration _options
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Use the plugin methods
|
|
337
|
+
// const result = await plugin.someMethod(input);
|
|
338
|
+
\`\`\`
|
|
339
|
+
|
|
340
|
+
### With RAG Pipeline Utils
|
|
341
|
+
|
|
342
|
+
\`\`\`json
|
|
343
|
+
{
|
|
344
|
+
"${pluginType}": {
|
|
345
|
+
"${pluginName}": "./src/${pluginName}-${pluginType}.js"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
## API Reference
|
|
351
|
+
|
|
352
|
+
${methodDocs}
|
|
353
|
+
|
|
354
|
+
## Configuration Options
|
|
355
|
+
|
|
356
|
+
| Option | Type | Default | Description |
|
|
357
|
+
|--------|------|---------|-------------|
|
|
358
|
+
| \`option1\` | \`string\` | \`"default"\` | Description of option1 |
|
|
359
|
+
| \`option2\` | \`number\` | \`100\` | Description of option2 |
|
|
360
|
+
|
|
361
|
+
## Testing
|
|
362
|
+
|
|
363
|
+
\`\`\`bash
|
|
364
|
+
npm test
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
1. Clone the repository
|
|
370
|
+
2. Install dependencies: \`npm install\`
|
|
371
|
+
3. Run tests: \`npm test\`
|
|
372
|
+
4. Build: \`npm run build\`
|
|
373
|
+
|
|
374
|
+
## Contributing
|
|
375
|
+
|
|
376
|
+
1. Fork the repository
|
|
377
|
+
2. Create a feature branch
|
|
378
|
+
3. Make your changes
|
|
379
|
+
4. Add tests
|
|
380
|
+
5. Submit a pull request
|
|
381
|
+
|
|
382
|
+
## License
|
|
383
|
+
|
|
384
|
+
MIT
|
|
385
|
+
|
|
386
|
+
## Author
|
|
387
|
+
|
|
388
|
+
${this.author}
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
const _filePath = path.join(pluginDir, 'README.md');
|
|
392
|
+
fs.writeFileSync(_filePath, content);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Generate package.json
|
|
397
|
+
*/
|
|
398
|
+
async generatePackageJson(pluginDir, pluginType, pluginName, _options) {
|
|
399
|
+
const packageName = `${this.namespace}/${pluginName}-${pluginType}`;
|
|
400
|
+
|
|
401
|
+
const packageJson = {
|
|
402
|
+
name: packageName,
|
|
403
|
+
version: '1.0.0',
|
|
404
|
+
description:
|
|
405
|
+
_options.description || `A ${pluginType} plugin for RAG Pipeline Utils`,
|
|
406
|
+
main: `src/${pluginName}-${pluginType}.js`,
|
|
407
|
+
_type: 'module',
|
|
408
|
+
scripts: {
|
|
409
|
+
test: 'jest',
|
|
410
|
+
'test:watch': 'jest --watch',
|
|
411
|
+
lint: 'eslint src/ __tests__/',
|
|
412
|
+
'lint:fix': 'eslint src/ __tests__/ --fix',
|
|
413
|
+
},
|
|
414
|
+
keywords: ['rag', 'pipeline', 'plugin', pluginType, 'ai', 'ml'],
|
|
415
|
+
author: this.author,
|
|
416
|
+
license: 'MIT',
|
|
417
|
+
peerDependencies: {
|
|
418
|
+
'@devilsdev/rag-pipeline-utils': '^2.0.0',
|
|
419
|
+
},
|
|
420
|
+
devDependencies: {
|
|
421
|
+
jest: '^29.7.0',
|
|
422
|
+
eslint: '^8.57.1',
|
|
423
|
+
},
|
|
424
|
+
files: ['src/', 'mocks/', 'README.md', '.ragrc.example.json'],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const content = JSON.stringify(packageJson, null, 2);
|
|
428
|
+
const _filePath = path.join(pluginDir, 'package.json');
|
|
429
|
+
fs.writeFileSync(_filePath, content);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Convert string to PascalCase
|
|
434
|
+
*/
|
|
435
|
+
toPascalCase(str) {
|
|
436
|
+
return str
|
|
437
|
+
.split(/[-_\s]/)
|
|
438
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
439
|
+
.join('');
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Default export
|
|
444
|
+
|
|
445
|
+
module.exports = {
|
|
446
|
+
PluginScaffolder,
|
|
447
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Utility
|
|
3
|
+
* Protects against brute force attacks and abuse
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Sliding window rate limiting
|
|
7
|
+
* - Per-identifier tracking (IP, user, tenant)
|
|
8
|
+
* - Automatic cleanup of expired entries
|
|
9
|
+
* - Configurable thresholds and windows
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
class RateLimiter {
|
|
15
|
+
/**
|
|
16
|
+
* Create a rate limiter instance
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
* @param {number} options.maxAttempts - Maximum attempts allowed (default: 5)
|
|
19
|
+
* @param {number} options.windowMs - Time window in milliseconds (default: 15 minutes)
|
|
20
|
+
* @param {number} options.blockDurationMs - How long to block after exceeding limit (default: 1 hour)
|
|
21
|
+
* @param {number} options.cleanupIntervalMs - How often to cleanup expired entries (default: 5 minutes)
|
|
22
|
+
*/
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.maxAttempts = options.maxAttempts || 5;
|
|
25
|
+
this.windowMs = options.windowMs || 15 * 60 * 1000; // 15 minutes
|
|
26
|
+
this.blockDurationMs = options.blockDurationMs || 60 * 60 * 1000; // 1 hour
|
|
27
|
+
this.cleanupIntervalMs = options.cleanupIntervalMs || 5 * 60 * 1000; // 5 minutes
|
|
28
|
+
|
|
29
|
+
// Map of identifier -> { attempts: [], blockedUntil: timestamp }
|
|
30
|
+
this.store = new Map();
|
|
31
|
+
|
|
32
|
+
// Start automatic cleanup
|
|
33
|
+
this._startCleanup();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a request should be allowed
|
|
38
|
+
* @param {string} identifier - Unique identifier (IP, userId, tenantId, etc.)
|
|
39
|
+
* @returns {Object} { allowed: boolean, remaining: number, resetAt: Date, blockedUntil: Date|null }
|
|
40
|
+
*/
|
|
41
|
+
allowRequest(identifier) {
|
|
42
|
+
const key = this._hashIdentifier(identifier);
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
|
|
45
|
+
// Get or create record
|
|
46
|
+
let record = this.store.get(key);
|
|
47
|
+
if (!record) {
|
|
48
|
+
record = { attempts: [], blockedUntil: null };
|
|
49
|
+
this.store.set(key, record);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if currently blocked
|
|
53
|
+
if (record.blockedUntil && record.blockedUntil > now) {
|
|
54
|
+
return {
|
|
55
|
+
allowed: false,
|
|
56
|
+
remaining: 0,
|
|
57
|
+
resetAt: new Date(record.blockedUntil),
|
|
58
|
+
blockedUntil: new Date(record.blockedUntil),
|
|
59
|
+
retryAfter: Math.ceil((record.blockedUntil - now) / 1000), // seconds
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Clear expired block
|
|
64
|
+
if (record.blockedUntil && record.blockedUntil <= now) {
|
|
65
|
+
record.blockedUntil = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Remove expired attempts
|
|
69
|
+
const windowStart = now - this.windowMs;
|
|
70
|
+
record.attempts = record.attempts.filter(
|
|
71
|
+
(timestamp) => timestamp > windowStart,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Check if exceeded limit
|
|
75
|
+
if (record.attempts.length >= this.maxAttempts) {
|
|
76
|
+
// Block the identifier
|
|
77
|
+
record.blockedUntil = now + this.blockDurationMs;
|
|
78
|
+
return {
|
|
79
|
+
allowed: false,
|
|
80
|
+
remaining: 0,
|
|
81
|
+
resetAt: new Date(record.blockedUntil),
|
|
82
|
+
blockedUntil: new Date(record.blockedUntil),
|
|
83
|
+
retryAfter: Math.ceil(this.blockDurationMs / 1000), // seconds
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Allow request and record attempt
|
|
88
|
+
record.attempts.push(now);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
allowed: true,
|
|
92
|
+
remaining: this.maxAttempts - record.attempts.length,
|
|
93
|
+
resetAt: new Date(now + this.windowMs),
|
|
94
|
+
blockedUntil: null,
|
|
95
|
+
retryAfter: 0,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Manually reset rate limit for an identifier
|
|
101
|
+
* @param {string} identifier - Identifier to reset
|
|
102
|
+
*/
|
|
103
|
+
reset(identifier) {
|
|
104
|
+
const key = this._hashIdentifier(identifier);
|
|
105
|
+
this.store.delete(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get current status for an identifier
|
|
110
|
+
* @param {string} identifier - Identifier to check
|
|
111
|
+
* @returns {Object} Current rate limit status
|
|
112
|
+
*/
|
|
113
|
+
getStatus(identifier) {
|
|
114
|
+
const key = this._hashIdentifier(identifier);
|
|
115
|
+
const record = this.store.get(key);
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
|
|
118
|
+
if (!record) {
|
|
119
|
+
return {
|
|
120
|
+
attempts: 0,
|
|
121
|
+
remaining: this.maxAttempts,
|
|
122
|
+
blocked: false,
|
|
123
|
+
blockedUntil: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Remove expired attempts
|
|
128
|
+
const windowStart = now - this.windowMs;
|
|
129
|
+
const validAttempts = record.attempts.filter(
|
|
130
|
+
(timestamp) => timestamp > windowStart,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const blocked = !!(record.blockedUntil && record.blockedUntil > now);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
attempts: validAttempts.length,
|
|
137
|
+
remaining: Math.max(0, this.maxAttempts - validAttempts.length),
|
|
138
|
+
blocked,
|
|
139
|
+
blockedUntil: blocked ? new Date(record.blockedUntil) : null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clean up expired entries
|
|
145
|
+
*/
|
|
146
|
+
cleanup() {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const expiredKeys = [];
|
|
149
|
+
|
|
150
|
+
for (const [key, record] of this.store.entries()) {
|
|
151
|
+
// Check if block has expired and no recent attempts
|
|
152
|
+
const blockExpired = !record.blockedUntil || record.blockedUntil < now;
|
|
153
|
+
const windowStart = now - this.windowMs;
|
|
154
|
+
const hasRecentAttempts = record.attempts.some(
|
|
155
|
+
(timestamp) => timestamp > windowStart,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (blockExpired && !hasRecentAttempts) {
|
|
159
|
+
expiredKeys.push(key);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Delete expired entries
|
|
164
|
+
expiredKeys.forEach((key) => this.store.delete(key));
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
cleaned: expiredKeys.length,
|
|
168
|
+
remaining: this.store.size,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get statistics about rate limiter usage
|
|
174
|
+
* @returns {Object} Statistics
|
|
175
|
+
*/
|
|
176
|
+
getStats() {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
let totalAttempts = 0;
|
|
179
|
+
let blockedIdentifiers = 0;
|
|
180
|
+
let activeIdentifiers = 0;
|
|
181
|
+
|
|
182
|
+
for (const record of this.store.values()) {
|
|
183
|
+
const windowStart = now - this.windowMs;
|
|
184
|
+
const validAttempts = record.attempts.filter(
|
|
185
|
+
(timestamp) => timestamp > windowStart,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (validAttempts.length > 0) {
|
|
189
|
+
activeIdentifiers++;
|
|
190
|
+
totalAttempts += validAttempts.length;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (record.blockedUntil && record.blockedUntil > now) {
|
|
194
|
+
blockedIdentifiers++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
totalIdentifiers: this.store.size,
|
|
200
|
+
activeIdentifiers,
|
|
201
|
+
blockedIdentifiers,
|
|
202
|
+
totalAttempts,
|
|
203
|
+
averageAttemptsPerIdentifier:
|
|
204
|
+
activeIdentifiers > 0 ? totalAttempts / activeIdentifiers : 0,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Stop the rate limiter and cleanup
|
|
210
|
+
*/
|
|
211
|
+
destroy() {
|
|
212
|
+
if (this._cleanupInterval) {
|
|
213
|
+
clearInterval(this._cleanupInterval);
|
|
214
|
+
this._cleanupInterval = null;
|
|
215
|
+
}
|
|
216
|
+
this.store.clear();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hash identifier for storage (privacy)
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
_hashIdentifier(identifier) {
|
|
224
|
+
return crypto.createHash('sha256').update(identifier).digest('hex');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Start automatic cleanup timer
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
_startCleanup() {
|
|
232
|
+
this._cleanupInterval = setInterval(() => {
|
|
233
|
+
this.cleanup();
|
|
234
|
+
}, this.cleanupIntervalMs);
|
|
235
|
+
|
|
236
|
+
// Don't prevent process exit
|
|
237
|
+
if (this._cleanupInterval.unref) {
|
|
238
|
+
this._cleanupInterval.unref();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = RateLimiter;
|
|
244
|
+
module.exports.default = module.exports;
|