@devilsdev/rag-pipeline-utils 1.0.0 → 2.0.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 (72) hide show
  1. package/.codecov.yml +21 -0
  2. package/.dockerignore +14 -14
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.json +21 -0
  5. package/.github/workflows/ci.yml +64 -42
  6. package/.husky/pre-commit +5 -0
  7. package/CHANGELOG.md +46 -0
  8. package/Dockerfile +22 -22
  9. package/LICENSE +674 -674
  10. package/README.md +117 -116
  11. package/__tests__/fixtures/.ragrc.json +8 -0
  12. package/__tests__/fixtures/invalid-ragrc.json +4 -0
  13. package/__tests__/fixtures/sample-eval-dataset.json +14 -14
  14. package/__tests__/fixtures/sample.pdf +1 -0
  15. package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
  16. package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
  17. package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
  18. package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
  19. package/__tests__/fixtures/valid-ragrc.json +16 -0
  20. package/__tests__/integration/cli/config-flow.test.js +98 -44
  21. package/__tests__/integration/config/load-config.test.js +67 -52
  22. package/__tests__/unit/config/validate-schema.test.js +121 -0
  23. package/__tests__/unit/core/plugin-registry.test.js +55 -55
  24. package/__tests__/unit/reranker/llm-reranker.test.js +18 -42
  25. package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
  26. package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
  27. package/__tests__/unit/reranker/reranker.snapshot.test.js +15 -17
  28. package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
  29. package/bin/cli.js +62 -168
  30. package/docs-site/docs/Architecture.md +76 -76
  31. package/docs-site/docs/CLI.md +69 -69
  32. package/docs-site/docs/Evaluation.md +61 -61
  33. package/docs-site/docs/FAQ.md +50 -50
  34. package/docs-site/docs/Plugins.md +61 -61
  35. package/docs-site/docs/Usage.md +72 -72
  36. package/jest.config.js +20 -13
  37. package/package.json +20 -3
  38. package/plugin.config.json +14 -0
  39. package/public/src/App.jsx +57 -57
  40. package/release.config.js +36 -23
  41. package/scripts/banner-injector.js +89 -0
  42. package/scripts/ci-runner.js +51 -0
  43. package/scripts/repair-fixtures.js +30 -0
  44. package/scripts/setup.js +31 -0
  45. package/scripts/test-global-setup.js +18 -0
  46. package/scripts/verify-fixture-mocks.js +45 -0
  47. package/scripts/verify-fixtures.js +36 -0
  48. package/server.js +31 -31
  49. package/src/config/load-config.js +33 -35
  50. package/src/config/load-plugin-config.js +56 -0
  51. package/src/config/validate-plugin-schema.js +28 -0
  52. package/src/config/validate-schema.js +107 -49
  53. package/src/core/create-pipeline.js +96 -82
  54. package/src/core/plugin-contracts.js +21 -0
  55. package/src/core/plugin-registry.js +48 -61
  56. package/src/dag/dag-engine.js +69 -69
  57. package/src/evaluate/evaluator.js +44 -44
  58. package/src/evaluate/scoring.js +73 -73
  59. package/src/loader/csv-loader.js +30 -30
  60. package/src/loader/directory-loader.js +37 -37
  61. package/src/loader/html-loader.js +57 -57
  62. package/src/loader/markdown-loader.js +58 -58
  63. package/src/mocks/openai-embedder.js +30 -0
  64. package/src/mocks/openai-llm.js +18 -0
  65. package/src/mocks/pdf-loader.js +17 -0
  66. package/src/mocks/pinecone-retriever.js +25 -0
  67. package/src/reranker/llm-reranker.js +34 -49
  68. package/src/utils/ci/diagnostic-reporter.js +36 -0
  69. package/src/utils/logger.js +29 -29
  70. package/src/utils/retry.js +42 -42
  71. package/src/utils/validate-plugin-contract.js +36 -0
  72. package/.github/workflows/release.yml +0 -21
@@ -1,61 +1,48 @@
1
- /**
2
- * Version: 1.0.0
3
- * Path: /src/core/plugin-registry.js
4
- * Description: Registry for managing pluggable components (loader, embedder, retriever, llm)
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- export class PluginRegistry {
9
- #registry = {
10
- loader: new Map(),
11
- embedder: new Map(),
12
- retriever: new Map(),
13
- llm: new Map(),
14
- reranker: new Map(),
15
- };
16
-
17
- /**
18
- * Registers a plugin instance under a specific type and name.
19
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin type category.
20
- * @param {string} name - Unique name/key for this plugin.
21
- * @param {object} plugin - Plugin implementation (e.g., instance or factory).
22
- */
23
- register(type, name, plugin) {
24
- const group = this.#registry[type];
25
- if (!group) {
26
- throw new Error(`Unknown plugin type: '${type}'`);
27
- }
28
- group.set(name, plugin);
29
- }
30
-
31
- /**
32
- * Retrieves a plugin by type and name.
33
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin type.
34
- * @param {string} name - Plugin name/key.
35
- * @returns {object} - The plugin instance.
36
- */
37
- get(type, name) {
38
- const group = this.#registry[type];
39
- if (!group) {
40
- throw new Error(`Unknown plugin type: '${type}'`);
41
- }
42
- const plugin = group.get(name);
43
- if (!plugin) {
44
- throw new Error(`Plugin not found: [${type}:${name}]`);
45
- }
46
- return plugin;
47
- }
48
-
49
- /**
50
- * Lists all registered plugin names for a type.
51
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin category.
52
- * @returns {string[]} - Array of registered plugin names.
53
- */
54
- list(type) {
55
- const group = this.#registry[type];
56
- if (!group) {
57
- throw new Error(`Unknown plugin type: '${type}'`);
58
- }
59
- return [...group.keys()];
60
- }
61
- }
1
+ /**
2
+ * Version: 1.0.0
3
+ * Path: /src/core/plugin-registry.js
4
+ * Description: Registry for managing pluggable components (loader, embedder, retriever, llm)
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ export class PluginRegistry {
9
+ #registry = {
10
+ loader: new Map(),
11
+ embedder: new Map(),
12
+ retriever: new Map(),
13
+ llm: new Map(),
14
+ reranker: new Map(),
15
+ };
16
+
17
+ register(type, name, plugin) {
18
+ const group = this.#registry[type];
19
+ if (!group) {
20
+ throw new Error(`Unknown plugin type: '${type}'`);
21
+ }
22
+ group.set(name, plugin);
23
+ }
24
+
25
+ get(type, name) {
26
+ const group = this.#registry[type];
27
+ if (!group) {
28
+ throw new Error(`Unknown plugin type: '${type}'`);
29
+ }
30
+ const plugin = group.get(name);
31
+ if (!plugin) {
32
+ throw new Error(`Plugin not found: [${type}:${name}]`);
33
+ }
34
+ return plugin;
35
+ }
36
+
37
+ list(type) {
38
+ const group = this.#registry[type];
39
+ if (!group) {
40
+ throw new Error(`Unknown plugin type: '${type}'`);
41
+ }
42
+ return [...group.keys()];
43
+ }
44
+ }
45
+
46
+ const registry = new PluginRegistry();
47
+
48
+ export default registry;
@@ -1,70 +1,70 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/dag/dag-engine.js
4
- * Description: DAG executor for chained RAG pipelines
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- export class DAGNode {
9
- constructor(id, run) {
10
- this.id = id;
11
- this.run = run; // async (input) => output
12
- this.inputs = [];
13
- this.outputs = [];
14
- }
15
- }
16
-
17
- export class DAG {
18
- constructor() {
19
- this.nodes = new Map();
20
- }
21
-
22
- addNode(id, fn) {
23
- const node = new DAGNode(id, fn);
24
- this.nodes.set(id, node);
25
- return node;
26
- }
27
-
28
- connect(fromId, toId) {
29
- const from = this.nodes.get(fromId);
30
- const to = this.nodes.get(toId);
31
- if (!from || !to) throw new Error(`Invalid edge: ${fromId} → ${toId}`);
32
- from.outputs.push(to);
33
- to.inputs.push(from);
34
- }
35
-
36
- /**
37
- * Executes DAG from source nodes to sinks using topological order
38
- * @param {any} seed - Initial input to the graph
39
- * @returns {Promise<any>} - Final output from sink(s)
40
- */
41
- async execute(seed) {
42
- const visited = new Set();
43
- const order = this.topoSort();
44
- const results = new Map();
45
-
46
- for (const node of order) {
47
- const inputValues = node.inputs.map(n => results.get(n.id));
48
- const input = inputValues.length === 1 ? inputValues[0] : inputValues;
49
- const output = await node.run(input ?? seed);
50
- results.set(node.id, output);
51
- }
52
-
53
- const last = order.at(-1);
54
- return results.get(last.id);
55
- }
56
-
57
- topoSort() {
58
- const order = [];
59
- const visited = new Set();
60
- const visit = (node) => {
61
- if (visited.has(node.id)) return;
62
- visited.add(node.id);
63
- node.inputs.forEach(visit);
64
- order.push(node);
65
- };
66
- for (const node of this.nodes.values()) visit(node);
67
- return order;
68
- }
69
- }
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/dag/dag-engine.js
4
+ * Description: DAG executor for chained RAG pipelines
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ export class DAGNode {
9
+ constructor(id, run) {
10
+ this.id = id;
11
+ this.run = run; // async (input) => output
12
+ this.inputs = [];
13
+ this.outputs = [];
14
+ }
15
+ }
16
+
17
+ export class DAG {
18
+ constructor() {
19
+ this.nodes = new Map();
20
+ }
21
+
22
+ addNode(id, fn) {
23
+ const node = new DAGNode(id, fn);
24
+ this.nodes.set(id, node);
25
+ return node;
26
+ }
27
+
28
+ connect(fromId, toId) {
29
+ const from = this.nodes.get(fromId);
30
+ const to = this.nodes.get(toId);
31
+ if (!from || !to) throw new Error(`Invalid edge: ${fromId} → ${toId}`);
32
+ from.outputs.push(to);
33
+ to.inputs.push(from);
34
+ }
35
+
36
+ /**
37
+ * Executes DAG from source nodes to sinks using topological order
38
+ * @param {any} seed - Initial input to the graph
39
+ * @returns {Promise<any>} - Final output from sink(s)
40
+ */
41
+ async execute(seed) {
42
+ // const visited = new Set();
43
+ const order = this.topoSort();
44
+ const results = new Map();
45
+
46
+ for (const node of order) {
47
+ const inputValues = node.inputs.map(n => results.get(n.id));
48
+ const input = inputValues.length === 1 ? inputValues[0] : inputValues;
49
+ const output = await node.run(input ?? seed);
50
+ results.set(node.id, output);
51
+ }
52
+
53
+ const last = order.at(-1);
54
+ return results.get(last.id);
55
+ }
56
+
57
+ topoSort() {
58
+ const order = [];
59
+ const visited = new Set();
60
+ const visit = (node) => {
61
+ if (visited.has(node.id)) return;
62
+ visited.add(node.id);
63
+ node.inputs.forEach(visit);
64
+ order.push(node);
65
+ };
66
+ for (const node of this.nodes.values()) visit(node);
67
+ return order;
68
+ }
69
+ }
70
70
 
@@ -1,44 +1,44 @@
1
- /**
2
- * Version: 0.1.1
3
- * Path: /src/evaluate/evaluator.js
4
- * Description: Evaluation runner with scoring metrics for batch RAG QA
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import { createRagPipeline } from '../core/create-pipeline.js';
9
- import { logger } from '../utils/logger.js';
10
- import fs from 'fs/promises';
11
- import path from 'path';
12
- import { scoreAnswer } from './scoring.js';
13
-
14
- /**
15
- * Evaluate a list of prompt/answer pairs using the RAG pipeline and compute scores
16
- * @param {string} datasetPath - Path to JSON file with { prompt, expected }[]
17
- * @param {object} config - RAG plugin config (loader optional)
18
- * @returns {Promise<Array<{ prompt: string, expected: string, actual: string, success: boolean, scores: object }>>}
19
- */
20
- export async function evaluateRagDataset(datasetPath, config) {
21
- const file = await fs.readFile(path.resolve(datasetPath), 'utf-8');
22
- const cases = JSON.parse(file);
23
- const pipeline = createRagPipeline(config);
24
-
25
- const results = [];
26
- for (const { prompt, expected } of cases) {
27
- try {
28
- logger.info({ prompt }, 'Evaluating case');
29
- const actual = await pipeline.query(prompt);
30
- const success = normalizeText(actual) === normalizeText(expected);
31
- const scores = scoreAnswer(actual, expected);
32
- results.push({ prompt, expected, actual, success, scores });
33
- } catch (err) {
34
- logger.error({ prompt, error: err.message }, 'Evaluation failed');
35
- results.push({ prompt, expected, actual: '', success: false, scores: { bleu: 0, rouge: 0 } });
36
- }
37
- }
38
- return results;
39
- }
40
-
41
- function normalizeText(txt) {
42
- return txt.trim().toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\s+/g, ' ');
43
- }
44
-
1
+ /**
2
+ * Version: 0.1.1
3
+ * Path: /src/evaluate/evaluator.js
4
+ * Description: Evaluation runner with scoring metrics for batch RAG QA
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import { createRagPipeline } from '../core/create-pipeline.js';
9
+ import { logger } from '../utils/logger.js';
10
+ import fs from 'fs/promises';
11
+ import path from 'path';
12
+ import { scoreAnswer } from './scoring.js';
13
+
14
+ /**
15
+ * Evaluate a list of prompt/answer pairs using the RAG pipeline and compute scores
16
+ * @param {string} datasetPath - Path to JSON file with { prompt, expected }[]
17
+ * @param {object} config - RAG plugin config (loader optional)
18
+ * @returns {Promise<Array<{ prompt: string, expected: string, actual: string, success: boolean, scores: object }>>}
19
+ */
20
+ export async function evaluateRagDataset(datasetPath, config) {
21
+ const file = await fs.readFile(path.resolve(datasetPath), 'utf-8');
22
+ const cases = JSON.parse(file);
23
+ const pipeline = createRagPipeline(config);
24
+
25
+ const results = [];
26
+ for (const { prompt, expected } of cases) {
27
+ try {
28
+ logger.info({ prompt }, 'Evaluating case');
29
+ const actual = await pipeline.query(prompt);
30
+ const success = normalizeText(actual) === normalizeText(expected);
31
+ const scores = scoreAnswer(actual, expected);
32
+ results.push({ prompt, expected, actual, success, scores });
33
+ } catch (err) {
34
+ logger.error({ prompt, error: err.message }, 'Evaluation failed');
35
+ results.push({ prompt, expected, actual: '', success: false, scores: { bleu: 0, rouge: 0 } });
36
+ }
37
+ }
38
+ return results;
39
+ }
40
+
41
+ function normalizeText(txt) {
42
+ return txt.trim().toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\s+/g, ' ');
43
+ }
44
+
@@ -1,74 +1,74 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/evaluate/scoring.js
4
- * Description: Basic scoring metrics (BLEU, ROUGE-L) for RAG output evaluation
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- /**
9
- * Tokenize a string by words
10
- * @param {string} text
11
- * @returns {string[]}
12
- */
13
- function tokenize(text) {
14
- return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').split(/\s+/).filter(Boolean);
15
- }
16
-
17
- /**
18
- * Compute BLEU-1 score: unigram precision
19
- * @param {string[]} candidate - Generated tokens
20
- * @param {string[]} reference - Reference tokens
21
- * @returns {number}
22
- */
23
- export function computeBLEU(candidate, reference) {
24
- const candCounts = candidate.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
25
- const refCounts = reference.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
26
-
27
- let match = 0;
28
- for (const token of Object.keys(candCounts)) {
29
- match += Math.min(candCounts[token], refCounts[token] || 0);
30
- }
31
- return candidate.length ? match / candidate.length : 0;
32
- }
33
-
34
- /**
35
- * Compute ROUGE-L (Longest Common Subsequence) recall
36
- * @param {string[]} candidate
37
- * @param {string[]} reference
38
- * @returns {number}
39
- */
40
- export function computeROUGE(candidate, reference) {
41
- const m = candidate.length;
42
- const n = reference.length;
43
- const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
44
-
45
- for (let i = 1; i <= m; i++) {
46
- for (let j = 1; j <= n; j++) {
47
- if (candidate[i - 1] === reference[j - 1]) {
48
- dp[i][j] = dp[i - 1][j - 1] + 1;
49
- } else {
50
- dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
51
- }
52
- }
53
- }
54
-
55
- const lcs = dp[m][n];
56
- return reference.length ? lcs / reference.length : 0;
57
- }
58
-
59
- /**
60
- * Score a prompt-answer pair using multiple metrics
61
- * @param {string} actual - LLM response
62
- * @param {string} expected - Reference answer
63
- * @returns {{ bleu: number, rouge: number }}
64
- */
65
- export function scoreAnswer(actual, expected) {
66
- const candTokens = tokenize(actual);
67
- const refTokens = tokenize(expected);
68
- return {
69
- bleu: computeBLEU(candTokens, refTokens),
70
- rouge: computeROUGE(candTokens, refTokens)
71
- };
72
- }
73
-
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/evaluate/scoring.js
4
+ * Description: Basic scoring metrics (BLEU, ROUGE-L) for RAG output evaluation
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ /**
9
+ * Tokenize a string by words
10
+ * @param {string} text
11
+ * @returns {string[]}
12
+ */
13
+ function tokenize(text) {
14
+ return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').split(/\s+/).filter(Boolean);
15
+ }
16
+
17
+ /**
18
+ * Compute BLEU-1 score: unigram precision
19
+ * @param {string[]} candidate - Generated tokens
20
+ * @param {string[]} reference - Reference tokens
21
+ * @returns {number}
22
+ */
23
+ export function computeBLEU(candidate, reference) {
24
+ const candCounts = candidate.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
25
+ const refCounts = reference.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
26
+
27
+ let match = 0;
28
+ for (const token of Object.keys(candCounts)) {
29
+ match += Math.min(candCounts[token], refCounts[token] || 0);
30
+ }
31
+ return candidate.length ? match / candidate.length : 0;
32
+ }
33
+
34
+ /**
35
+ * Compute ROUGE-L (Longest Common Subsequence) recall
36
+ * @param {string[]} candidate
37
+ * @param {string[]} reference
38
+ * @returns {number}
39
+ */
40
+ export function computeROUGE(candidate, reference) {
41
+ const m = candidate.length;
42
+ const n = reference.length;
43
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
44
+
45
+ for (let i = 1; i <= m; i++) {
46
+ for (let j = 1; j <= n; j++) {
47
+ if (candidate[i - 1] === reference[j - 1]) {
48
+ dp[i][j] = dp[i - 1][j - 1] + 1;
49
+ } else {
50
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
51
+ }
52
+ }
53
+ }
54
+
55
+ const lcs = dp[m][n];
56
+ return reference.length ? lcs / reference.length : 0;
57
+ }
58
+
59
+ /**
60
+ * Score a prompt-answer pair using multiple metrics
61
+ * @param {string} actual - LLM response
62
+ * @param {string} expected - Reference answer
63
+ * @returns {{ bleu: number, rouge: number }}
64
+ */
65
+ export function scoreAnswer(actual, expected) {
66
+ const candTokens = tokenize(actual);
67
+ const refTokens = tokenize(expected);
68
+ return {
69
+ bleu: computeBLEU(candTokens, refTokens),
70
+ rouge: computeROUGE(candTokens, refTokens)
71
+ };
72
+ }
73
+
74
74
 
@@ -1,30 +1,30 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/loader/csv-loader.js
4
- * Description: CSV document loader with row-wise chunking
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import fs from 'fs/promises';
9
- import path from 'path';
10
- import { parse } from 'csv-parse/sync';
11
-
12
- export class CSVLoader {
13
- /**
14
- * Load and parse a CSV file into chunks
15
- * @param {string} filePath
16
- * @returns {Promise<Array<{ chunk(): string[] }>>}
17
- */
18
- async load(filePath) {
19
- const absPath = path.resolve(filePath);
20
- const raw = await fs.readFile(absPath, 'utf-8');
21
- const records = parse(raw, { columns: true });
22
-
23
- return [
24
- {
25
- chunk: () => records.map(r => Object.values(r).join(' '))
26
- }
27
- ];
28
- }
29
- }
30
-
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/loader/csv-loader.js
4
+ * Description: CSV document loader with row-wise chunking
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { parse } from 'csv-parse/sync';
11
+
12
+ export class CSVLoader {
13
+ /**
14
+ * Load and parse a CSV file into chunks
15
+ * @param {string} filePath
16
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
17
+ */
18
+ async load(filePath) {
19
+ const absPath = path.resolve(filePath);
20
+ const raw = await fs.readFile(absPath, 'utf-8');
21
+ const records = parse(raw, { columns: true });
22
+
23
+ return [
24
+ {
25
+ chunk: () => records.map(r => Object.values(r).join(' '))
26
+ }
27
+ ];
28
+ }
29
+ }
30
+
@@ -1,38 +1,38 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/loader/directory-loader.js
4
- * Description: Recursive directory loader with file type routing
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import fg from 'fast-glob';
9
- import { MarkdownLoader } from './markdown-loader.js';
10
- import { HTMLLoader } from './html-loader.js';
11
- import { CSVLoader } from './csv-loader.js';
12
-
13
- const loaders = {
14
- '.md': new MarkdownLoader(),
15
- '.html': new HTMLLoader(),
16
- '.csv': new CSVLoader()
17
- };
18
-
19
- export class DirectoryLoader {
20
- /**
21
- * Load and route supported files from a directory
22
- * @param {string} dirPath
23
- * @returns {Promise<Array<{ chunk(): string[] }>>}
24
- */
25
- async load(dirPath) {
26
- const matches = await fg([`${dirPath}/**/*.{md,html,csv}`]);
27
- const all = [];
28
- for (const file of matches) {
29
- const ext = file.slice(file.lastIndexOf('.'));
30
- const loader = loaders[ext];
31
- if (loader) {
32
- const docs = await loader.load(file);
33
- all.push(...docs);
34
- }
35
- }
36
- return all;
37
- }
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/loader/directory-loader.js
4
+ * Description: Recursive directory loader with file type routing
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import fg from 'fast-glob';
9
+ import { MarkdownLoader } from './markdown-loader.js';
10
+ import { HTMLLoader } from './html-loader.js';
11
+ import { CSVLoader } from './csv-loader.js';
12
+
13
+ const loaders = {
14
+ '.md': new MarkdownLoader(),
15
+ '.html': new HTMLLoader(),
16
+ '.csv': new CSVLoader()
17
+ };
18
+
19
+ export class DirectoryLoader {
20
+ /**
21
+ * Load and route supported files from a directory
22
+ * @param {string} dirPath
23
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
24
+ */
25
+ async load(dirPath) {
26
+ const matches = await fg([`${dirPath}/**/*.{md,html,csv}`]);
27
+ const all = [];
28
+ for (const file of matches) {
29
+ const ext = file.slice(file.lastIndexOf('.'));
30
+ const loader = loaders[ext];
31
+ if (loader) {
32
+ const docs = await loader.load(file);
33
+ all.push(...docs);
34
+ }
35
+ }
36
+ return all;
37
+ }
38
38
  }