architex-js 1.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.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # mixin-support
2
+
3
+ **Library for JavaScript mixin support, provides an easy and useful interface for the implementation of this powerful concept.**
4
+
5
+ ## Description
6
+
7
+ `mixin-support` is a library designed to simplify the implementation of mixins in JavaScript. By using this library, developers can easily create classes that inherit from multiple mixins, enhancing code reuse and modularity.
8
+
9
+ ## Installation
10
+
11
+ To install the library, you can use npm:
12
+
13
+ ```bash
14
+ npm install mixin-support
15
+ ```
16
+
17
+ Or if you are using yarn:
18
+
19
+ ```bash
20
+ yarn add mixin-support
21
+ ```
22
+
23
+
24
+ ## Usage
25
+
26
+ The core of the library is the `MixinBuilder` class, which provides a static method `with` to create a class that combines multiple mixins.
27
+
28
+ ### Example
29
+
30
+ Here's a basic example demonstrating how to use `mixin-support`:
31
+
32
+ ```js
33
+ import { MixinBuilder } from 'mixin-support';
34
+
35
+ // Define some mixins
36
+ class MixinA {
37
+ constructor() {
38
+ this.name = 'MixinA';
39
+ }
40
+ greet() {
41
+ console.log(`Hello from ${this.name}`);
42
+ }
43
+ }
44
+
45
+ class MixinB {
46
+ constructor() {
47
+ this.type = 'MixinB';
48
+ }
49
+ describe() {
50
+ console.log(`This is ${this.type}`);
51
+ }
52
+ }
53
+
54
+ // Create a new class that combines the mixins
55
+ class CombinedClass extends MixinBuilder.with(MixinA, MixinB) {
56
+ constructor() {
57
+ super();
58
+ }
59
+ }
60
+
61
+ const instance = new CombinedClass();
62
+ instance.greet(); // Output: Hello from MixinA
63
+ instance.describe(); // Output: This is MixinB
64
+
65
+ ```
66
+
67
+ ### Advanced Example
68
+
69
+ For more advanced usage, you can include mixins that add static methods or properties to the final class:
70
+
71
+ ```js
72
+ class StaticMixin {
73
+ static staticMethod() {
74
+ console.log('This is a static method');
75
+ }
76
+ }
77
+
78
+ // Create a new class that combines the mixins
79
+ class AdvancedClass extends MixinBuilder.with(MixinA, MixinB, StaticMixin) {
80
+ constructor() {
81
+ super();
82
+ }
83
+ }
84
+
85
+ AdvancedClass.staticMethod(); // Output: This is a static method
86
+
87
+ const advancedInstance = new AdvancedClass();
88
+ advancedInstance.greet(); // Output: Hello from MixinA
89
+ advancedInstance.describe(); // Output: This is MixinB
90
+
91
+ ```
92
+
93
+ ## API
94
+
95
+ ### `MixinBuilder`
96
+
97
+ #### `static with(...mixins)`
98
+
99
+ This static method takes any number of mixins and returns a new class that incorporates the properties and methods of each mixin. Mixins can be either classes or functions that return classes.
100
+
101
+ * **Parameters** :
102
+ * `...mixins`: The mixins to include in the final class.
103
+ * **Returns** : The final class that includes all the mixins.
104
+
105
+ ## License
106
+
107
+ This project is licensed under the MIT License - see the LICENSE file for details.
108
+
109
+ ## Contributing
110
+
111
+ Contributions are welcome! Please open an issue or submit a pull request for any bugs or feature requests.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "architex-js",
3
+ "version": "1.0.0",
4
+ "main": "src/index.js",
5
+ "exports": {
6
+ ".": "./src/index.js",
7
+ "./abc": "./src/abc/index.js",
8
+ "./mixins": "./src/mixins/index.js",
9
+ "./microkernel": "./src/microkernel/index.js",
10
+ "./exceptions": "./src/exceptions/index.js"
11
+ },
12
+ "description": "Architectural Toolbox for JavaScript - Providing high-level building blocks for robust systems.",
13
+ "author": {
14
+ "name": "devjuanda",
15
+ "email": "penalozahernandezjuandavid03@gmail.com",
16
+ "url": "https://www.youtube.com/@codeend_01"
17
+ },
18
+ "license": "MIT",
19
+ "keywords": [
20
+ "architecture",
21
+ "toolbox",
22
+ "meta-framework",
23
+ "mixins",
24
+ "event-bus",
25
+ "microkernel",
26
+ "JavaScript",
27
+ "Node.js",
28
+ "Clean Code"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/dev-juanda01/architex-js"
33
+ },
34
+ "homepage": "https://github.com/dev-juanda01/architex-js",
35
+ "type": "module",
36
+ "scripts": {
37
+ "test": "vitest run"
38
+ },
39
+ "devDependencies": {
40
+ "vitest": "^3.0.6"
41
+ }
42
+ }
package/src/abc/ABC.js ADDED
@@ -0,0 +1,48 @@
1
+ import {
2
+ InstantiatedAbstractClassException,
3
+ AbstractMethodNotImplementedException,
4
+ } from "../exceptions/index.js";
5
+
6
+ /**
7
+ * Provides functionality for defining abstract base classes.
8
+ */
9
+ class ABC {
10
+ /**
11
+ * Creates an abstract base class that enforces method implementation in subclasses.
12
+ * @returns {Class} An abstract base class.
13
+ */
14
+ static abstract() {
15
+ return class AbstractBase {
16
+ constructor() {
17
+ // Prevent direct instantiation of the abstract class itself
18
+ if (new.target === AbstractBase || new.target.name === 'AbstractBase') {
19
+ throw new InstantiatedAbstractClassException(
20
+ `Cannot instantiate abstract class "${this.constructor.name}" directly.`
21
+ );
22
+ }
23
+
24
+ // Get all methods defined in the immediate prototype of the class being instantiated
25
+ const implementedMethods = Object.getOwnPropertyNames(new.target.prototype)
26
+ .filter(prop => typeof new.target.prototype[prop] === 'function' && prop !== 'constructor');
27
+
28
+ // Get the prototype of the abstract base (the one created by ABC.abstract)
29
+ const abstractPrototype = Object.getPrototypeOf(this);
30
+ const parentPrototype = Object.getPrototypeOf(abstractPrototype);
31
+
32
+ if (parentPrototype && parentPrototype !== Object.prototype) {
33
+ Object.getOwnPropertyNames(parentPrototype)
34
+ .filter(prop => prop !== 'constructor' && typeof parentPrototype[prop] === 'function')
35
+ .forEach(methodName => {
36
+ if (!implementedMethods.includes(methodName)) {
37
+ throw new AbstractMethodNotImplementedException(
38
+ `Class "${new.target.name}" must implement abstract method: "${methodName}"`
39
+ );
40
+ }
41
+ });
42
+ }
43
+ }
44
+ };
45
+ }
46
+ }
47
+
48
+ export { ABC };
@@ -0,0 +1 @@
1
+ export * from "./ABC.js";
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./abc/index.js";
2
+ export * from "./mixins/index.js";
3
+ export * from "./microkernel/index.js";
4
+ export * from "./exceptions/index.js";
@@ -0,0 +1,19 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ABC } from "../src/index.js";
3
+
4
+ describe('Legacy Abstract Class Support', () => {
5
+ it('should allow instantiation of a class that implements abstract methods', () => {
6
+ class A extends ABC.abstract() {
7
+ prepare() { }
8
+ }
9
+
10
+ class B extends A {
11
+ prepare() {
12
+ return "B prepared";
13
+ }
14
+ }
15
+
16
+ const b = new B();
17
+ expect(b.prepare()).toBe("B prepared");
18
+ });
19
+ });
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ MixinBuilder,
4
+ ABC,
5
+ EventBus,
6
+ Microkernel,
7
+ AbstractMethodNotImplementedException,
8
+ InstantiatedAbstractClassException
9
+ } from '../src/index.js';
10
+
11
+ describe('Architecture Toolbox', () => {
12
+
13
+ describe('MixinBuilder & Abstract Classes', () => {
14
+ it('should throw InstantiatedAbstractClassException when instantiating abstract class directly', () => {
15
+ const AbstractBase = ABC.abstract();
16
+ expect(() => new AbstractBase()).toThrow(InstantiatedAbstractClassException);
17
+ });
18
+
19
+ it('should throw AbstractMethodNotImplementedException when subclass misses required methods', () => {
20
+ class Base extends ABC.abstract() {
21
+ mustImplement() { }
22
+ }
23
+
24
+ class Sub extends Base { }
25
+
26
+ expect(() => new Sub()).toThrow(AbstractMethodNotImplementedException);
27
+ expect(() => new Sub()).toThrow(/must implement abstract method: "mustImplement"/);
28
+ });
29
+
30
+ it('should work correctly when abstract methods are implemented', () => {
31
+ class Base extends ABC.abstract() {
32
+ greet() { }
33
+ }
34
+
35
+ class Sub extends Base {
36
+ greet() { return 'hello'; }
37
+ }
38
+
39
+ const instance = new Sub();
40
+ expect(instance.greet()).toBe('hello');
41
+ });
42
+ });
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
+ });