architex-js 1.2.0 → 1.4.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.
- package/package.json +3 -2
- package/src/index.js +2 -1
- package/src/microkernel/Microkernel.js +60 -0
- package/src/microkernel/index.js +1 -0
- package/src/pipeline/Pipeline.js +71 -0
- package/src/pipeline/index.js +1 -0
- package/test/pipeline.test.js +43 -0
- package/test/toolbox.test.js +0 -39
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "architex-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.js",
|
|
7
7
|
"./abc": "./src/abc/index.js",
|
|
8
8
|
"./mixins": "./src/mixins/index.js",
|
|
9
9
|
"./microkernel": "./src/microkernel/index.js",
|
|
10
|
-
"./exceptions": "./src/exceptions/index.js"
|
|
10
|
+
"./exceptions": "./src/exceptions/index.js",
|
|
11
|
+
"./pipeline": "./src/pipeline/index.js"
|
|
11
12
|
},
|
|
12
13
|
"description": "Architectural Toolbox for JavaScript - Providing high-level building blocks for robust systems.",
|
|
13
14
|
"author": {
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Microkernel class to manage plugins and core functionality.
|
|
6
|
+
*/
|
|
7
|
+
class Microkernel {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.plugins = new Map();
|
|
10
|
+
this.registry = {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Boot the kernel and load plugins from the specified directory.
|
|
15
|
+
* @param {string} pluginsPath - The path to the plugins directory.
|
|
16
|
+
*/
|
|
17
|
+
async boot(pluginsPath) {
|
|
18
|
+
const dir = path.resolve(pluginsPath);
|
|
19
|
+
const files = fs.readdirSync(dir);
|
|
20
|
+
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
if (file.endsWith('.js')) {
|
|
23
|
+
const PluginClass = require(path.join(dir, file));
|
|
24
|
+
const instance = new PluginClass();
|
|
25
|
+
|
|
26
|
+
// VALIDATION: Is it a valid plugin from our suite?
|
|
27
|
+
if (!(instance instanceof BasePlugin)) {
|
|
28
|
+
console.error(`[Kernel] Ignored: ${file} does not extend BasePlugin.`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const name = instance.name || file.replace('.js', '');
|
|
33
|
+
this.plugins.set(name, instance);
|
|
34
|
+
|
|
35
|
+
// Execute the mandatory contract
|
|
36
|
+
await instance.setup(this);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Allows a plugin to offer a functionality to the rest of the system
|
|
43
|
+
* @param {string} key
|
|
44
|
+
* @param {any} service
|
|
45
|
+
*/
|
|
46
|
+
provideService(key, service) {
|
|
47
|
+
this.registry[key] = service;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Allows a plugin to consume a functionality of another
|
|
52
|
+
* @param {string} key
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
getService(key) {
|
|
56
|
+
return this.registry[key];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Microkernel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Microkernel.js";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single step in a pipeline.
|
|
3
|
+
* @template T
|
|
4
|
+
* @callback PipelineStep
|
|
5
|
+
* @param {T} payload - The data passing through the pipeline.
|
|
6
|
+
* @returns {T | Promise<T>} The transformed data.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A sequential data-processing pipeline (Chain of Responsibility).
|
|
11
|
+
* Each step receives the output of the previous step.
|
|
12
|
+
*
|
|
13
|
+
* @template T
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const result = await Pipeline.create()
|
|
17
|
+
* .pipe(validate)
|
|
18
|
+
* .pipe(transform)
|
|
19
|
+
* .pipe(save)
|
|
20
|
+
* .run(payload);
|
|
21
|
+
*/
|
|
22
|
+
class Pipeline {
|
|
23
|
+
constructor() {
|
|
24
|
+
/** @type {Array<PipelineStep>} */
|
|
25
|
+
this._steps = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new empty Pipeline instance.
|
|
30
|
+
* @returns {Pipeline}
|
|
31
|
+
*/
|
|
32
|
+
static create() {
|
|
33
|
+
return new Pipeline();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Adds a processing step to the pipeline.
|
|
38
|
+
* @param {PipelineStep} step - A function (sync or async) that receives and returns the payload.
|
|
39
|
+
* @returns {Pipeline} The same pipeline instance (fluent API).
|
|
40
|
+
*/
|
|
41
|
+
pipe(step) {
|
|
42
|
+
if (typeof step !== 'function') {
|
|
43
|
+
throw new TypeError(`Pipeline.pipe() expects a function, got: ${typeof step}`);
|
|
44
|
+
}
|
|
45
|
+
this._steps.push(step);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Executes all pipeline steps sequentially.
|
|
51
|
+
* @param {T} initialPayload - The initial data to pass through the pipeline.
|
|
52
|
+
* @returns {Promise<T>} The final transformed payload.
|
|
53
|
+
*/
|
|
54
|
+
async run(initialPayload) {
|
|
55
|
+
let payload = initialPayload;
|
|
56
|
+
for (const step of this._steps) {
|
|
57
|
+
payload = await step(payload);
|
|
58
|
+
}
|
|
59
|
+
return payload;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the number of steps registered in the pipeline.
|
|
64
|
+
* @returns {number}
|
|
65
|
+
*/
|
|
66
|
+
get size() {
|
|
67
|
+
return this._steps.length;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { Pipeline };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Pipeline.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Pipeline } from '../src/pipeline/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Pipeline', () => {
|
|
5
|
+
it('should run steps sequentially and return the final result', async () => {
|
|
6
|
+
const result = await Pipeline.create()
|
|
7
|
+
.pipe(x => x + 1)
|
|
8
|
+
.pipe(x => x * 2)
|
|
9
|
+
.pipe(x => x - 3)
|
|
10
|
+
.run(5);
|
|
11
|
+
|
|
12
|
+
// (5 + 1) * 2 - 3 = 9
|
|
13
|
+
expect(result).toBe(9);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should support async steps', async () => {
|
|
17
|
+
const result = await Pipeline.create()
|
|
18
|
+
.pipe(async x => x + 10)
|
|
19
|
+
.pipe(async x => x * 2)
|
|
20
|
+
.run(5);
|
|
21
|
+
|
|
22
|
+
// (5 + 10) * 2 = 30
|
|
23
|
+
expect(result).toBe(30);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should report the correct number of steps', () => {
|
|
27
|
+
const pipeline = Pipeline.create()
|
|
28
|
+
.pipe(x => x)
|
|
29
|
+
.pipe(x => x)
|
|
30
|
+
.pipe(x => x);
|
|
31
|
+
|
|
32
|
+
expect(pipeline.size).toBe(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw if a non-function is passed to pipe()', () => {
|
|
36
|
+
expect(() => Pipeline.create().pipe(42)).toThrow(TypeError);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return the initial payload if no steps are added', async () => {
|
|
40
|
+
const result = await Pipeline.create().run({ id: 1 });
|
|
41
|
+
expect(result).toEqual({ id: 1 });
|
|
42
|
+
});
|
|
43
|
+
});
|
package/test/toolbox.test.js
CHANGED
|
@@ -2,8 +2,6 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import {
|
|
3
3
|
MixinBuilder,
|
|
4
4
|
ABC,
|
|
5
|
-
EventBus,
|
|
6
|
-
Microkernel,
|
|
7
5
|
AbstractMethodNotImplementedException,
|
|
8
6
|
InstantiatedAbstractClassException
|
|
9
7
|
} from '../src/index.js';
|
|
@@ -40,41 +38,4 @@ describe('Architecture Toolbox', () => {
|
|
|
40
38
|
expect(instance.greet()).toBe('hello');
|
|
41
39
|
});
|
|
42
40
|
});
|
|
43
|
-
|
|
44
|
-
describe('EventBus', () => {
|
|
45
|
-
it('should emit and receive events', () => {
|
|
46
|
-
const bus = new EventBus();
|
|
47
|
-
let received = null;
|
|
48
|
-
bus.on('test', (data) => { received = data; });
|
|
49
|
-
bus.emit('test', 'payload');
|
|
50
|
-
expect(received).toBe('payload');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should handle "once" subscriptions', () => {
|
|
54
|
-
const bus = new EventBus();
|
|
55
|
-
let count = 0;
|
|
56
|
-
bus.once('test', () => { count++; });
|
|
57
|
-
bus.emit('test');
|
|
58
|
-
bus.emit('test');
|
|
59
|
-
expect(count).toBe(1);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('Microkernel', () => {
|
|
64
|
-
it('should register and start plugins', async () => {
|
|
65
|
-
const kernel = new Microkernel();
|
|
66
|
-
let started = false;
|
|
67
|
-
|
|
68
|
-
const plugin = {
|
|
69
|
-
install: (k) => k.set('service', { ok: true }),
|
|
70
|
-
start: async () => { started = true; }
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
kernel.register('test-plugin', plugin);
|
|
74
|
-
expect(kernel.get('service').ok).toBe(true);
|
|
75
|
-
|
|
76
|
-
await kernel.start();
|
|
77
|
-
expect(started).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
41
|
});
|