tiny-essentials 1.26.3 → 1.26.4

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.
@@ -70,40 +70,111 @@ class TinyClassManager {
70
70
  }
71
71
 
72
72
  /**
73
- * Applies a plugin to the class chain if all conditions are met.
73
+ * Internal helper to validate dependencies, check conflicts, and transition state.
74
74
  * @template {new (...args: any[]) => any} R
75
- * @param {PluginDefinition<T, R>} plugin - The plugin module to be integrated.
76
- * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
77
- * @throws {Error} Throws if instance is already consumed, plugin is duplicate, or dependencies are missing.
75
+ * @param {string} name - The plugin name.
76
+ * @param {string[]} deps - The plugin dependencies.
77
+ * @param {R} ExtendedClass - The newly extended class returned by the apply function.
78
+ * @returns {TinyClassManager<R>} A new manager instance.
78
79
  */
79
- use(plugin) {
80
+ #validateAndTransition(name, deps, ExtendedClass) {
81
+ // Verifies if the current manager has already been consumed to prevent branching.
80
82
  if (this.#used) throw new Error(`[TinyClassManager] Cannot reuse a consumed manager instance.`);
81
- if (this.#appliedPlugins.has(plugin.name))
82
- throw new Error(`[TinyClassManager] Plugin conflict: "${plugin.name}" is already installed.`);
83
83
 
84
- /**
85
- * @type {string[]}
86
- * Extracted dependencies array with fallback for undefined properties.
87
- */
88
- const deps = plugin.dependencies || [];
84
+ // Checks if the plugin being inserted is already in the applied list.
85
+ if (this.#appliedPlugins.has(name))
86
+ throw new Error(`[TinyClassManager] Plugin conflict: "${name}" is already installed.`);
89
87
 
90
88
  for (const dep of deps) {
91
89
  if (!this.#appliedPlugins.has(dep)) {
92
90
  throw new Error(
93
- `[TinyClassManager] Missing Dependency: "${plugin.name}" requires "${dep}" to be installed first.`,
91
+ `[TinyClassManager] Missing Dependency: "${name}" requires "${dep}" to be installed first.`,
94
92
  );
95
93
  }
96
94
  }
97
95
 
98
- const newClassManager = new TinyClassManager(plugin.apply(this.#currentClass));
99
- this.#appliedPlugins.forEach((name) => newClassManager.#appliedPlugins.add(name));
100
- newClassManager.#appliedPlugins.add(plugin.name);
96
+ /**
97
+ * @type {TinyClassManager<R>}
98
+ * The next manager instance wrapping the newly extended class chain.
99
+ */
100
+ const newClassManager = new TinyClassManager(ExtendedClass);
101
+
102
+ this.#appliedPlugins.forEach((appliedName) => newClassManager.#appliedPlugins.add(appliedName));
103
+ newClassManager.#appliedPlugins.add(name);
101
104
 
102
105
  this.#used = true;
103
106
  this.#appliedPlugins.clear();
107
+
104
108
  return newClassManager;
105
109
  }
106
110
 
111
+ /**
112
+ * Applies a plugin to the class chain using a definition object.
113
+ * @deprecated Use {@link insert} instead.
114
+ * @template {new (...args: any[]) => any} R
115
+ * @param {PluginDefinition<T, R>} plugin - The plugin module to be integrated.
116
+ * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
117
+ * @throws {Error} Throws if instance is already consumed, plugin is duplicate, or dependencies are missing.
118
+ */
119
+ use(plugin) {
120
+ /**
121
+ * @type {string[]}
122
+ * Extracted dependencies array with fallback for undefined properties.
123
+ */
124
+ const deps = plugin.dependencies || [];
125
+
126
+ /**
127
+ * @type {R}
128
+ * The new class generated by the old object-style apply wrapper.
129
+ */
130
+ const ExtendedClass = plugin.apply(this.#currentClass);
131
+
132
+ return this.#validateAndTransition(plugin.name, deps, ExtendedClass);
133
+ }
134
+
135
+ /**
136
+ * Inserts a plugin directly by passing its apply function.
137
+ * It reads `_tinyDepName` and `_tinyDeps` statically from the returned class.
138
+ * @template {new (...args: any[]) => any} R
139
+ * @param {function(T): R} applyFn - Function that receives the base class and returns the extended class.
140
+ * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
141
+ * @throws {Error} Throws if static properties are missing, instance is consumed, or validation fails.
142
+ */
143
+ insert(applyFn) {
144
+ /** * @type {R}
145
+ * The extended class returned directly from the provided function.
146
+ */
147
+ const ExtendedClass = applyFn(this.#currentClass);
148
+
149
+ /**
150
+ * @type {string}
151
+ * The plugin name extracted from the static property.
152
+ */
153
+ // @ts-ignore
154
+ const pluginName = ExtendedClass._tinyDepName;
155
+
156
+ if (!pluginName) {
157
+ throw new Error(
158
+ `[TinyClassManager] Plugin class must define a static '_tinyDepName' property.`,
159
+ );
160
+ }
161
+
162
+ /**
163
+ * @type {string[]}
164
+ * The dependencies extracted from the static property, defaulting to an empty array.
165
+ */
166
+ // @ts-ignore
167
+ const deps = ExtendedClass._tinyDeps ?? [];
168
+
169
+ if (!Array.isArray(deps) || !deps.every((name) => typeof name === 'string')) {
170
+ throw new Error(
171
+ `[TinyClassManager] Plugin class must define a static array of strings in '_tinyDepName' property.`,
172
+ );
173
+ }
174
+
175
+ return this.#validateAndTransition(pluginName, deps, ExtendedClass);
176
+ }
177
+
107
178
  /**
108
179
  * Finalizes the composition and returns the fully built class.
109
180
  * @returns {T} The final class representing the last extended version in the chain.
@@ -50,13 +50,23 @@ declare class TinyClassManager<T extends new (...args: any[]) => any> {
50
50
  */
51
51
  get used(): boolean;
52
52
  /**
53
- * Applies a plugin to the class chain if all conditions are met.
53
+ * Applies a plugin to the class chain using a definition object.
54
+ * @deprecated Use {@link insert} instead.
54
55
  * @template {new (...args: any[]) => any} R
55
56
  * @param {PluginDefinition<T, R>} plugin - The plugin module to be integrated.
56
57
  * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
57
58
  * @throws {Error} Throws if instance is already consumed, plugin is duplicate, or dependencies are missing.
58
59
  */
59
60
  use<R extends new (...args: any[]) => any>(plugin: PluginDefinition<T, R>): TinyClassManager<R>;
61
+ /**
62
+ * Inserts a plugin directly by passing its apply function.
63
+ * It reads `_tinyDepName` and `_tinyDeps` statically from the returned class.
64
+ * @template {new (...args: any[]) => any} R
65
+ * @param {function(T): R} applyFn - Function that receives the base class and returns the extended class.
66
+ * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
67
+ * @throws {Error} Throws if static properties are missing, instance is consumed, or validation fails.
68
+ */
69
+ insert<R extends new (...args: any[]) => any>(applyFn: (arg0: T) => R): TinyClassManager<R>;
60
70
  /**
61
71
  * Finalizes the composition and returns the fully built class.
62
72
  * @returns {T} The final class representing the last extended version in the chain.
@@ -59,33 +59,89 @@ class TinyClassManager {
59
59
  this.#currentClass = coreClass;
60
60
  }
61
61
  /**
62
- * Applies a plugin to the class chain if all conditions are met.
62
+ * Internal helper to validate dependencies, check conflicts, and transition state.
63
+ * @template {new (...args: any[]) => any} R
64
+ * @param {string} name - The plugin name.
65
+ * @param {string[]} deps - The plugin dependencies.
66
+ * @param {R} ExtendedClass - The newly extended class returned by the apply function.
67
+ * @returns {TinyClassManager<R>} A new manager instance.
68
+ */
69
+ #validateAndTransition(name, deps, ExtendedClass) {
70
+ // Verifies if the current manager has already been consumed to prevent branching.
71
+ if (this.#used)
72
+ throw new Error(`[TinyClassManager] Cannot reuse a consumed manager instance.`);
73
+ // Checks if the plugin being inserted is already in the applied list.
74
+ if (this.#appliedPlugins.has(name))
75
+ throw new Error(`[TinyClassManager] Plugin conflict: "${name}" is already installed.`);
76
+ for (const dep of deps) {
77
+ if (!this.#appliedPlugins.has(dep)) {
78
+ throw new Error(`[TinyClassManager] Missing Dependency: "${name}" requires "${dep}" to be installed first.`);
79
+ }
80
+ }
81
+ /**
82
+ * @type {TinyClassManager<R>}
83
+ * The next manager instance wrapping the newly extended class chain.
84
+ */
85
+ const newClassManager = new TinyClassManager(ExtendedClass);
86
+ this.#appliedPlugins.forEach((appliedName) => newClassManager.#appliedPlugins.add(appliedName));
87
+ newClassManager.#appliedPlugins.add(name);
88
+ this.#used = true;
89
+ this.#appliedPlugins.clear();
90
+ return newClassManager;
91
+ }
92
+ /**
93
+ * Applies a plugin to the class chain using a definition object.
94
+ * @deprecated Use {@link insert} instead.
63
95
  * @template {new (...args: any[]) => any} R
64
96
  * @param {PluginDefinition<T, R>} plugin - The plugin module to be integrated.
65
97
  * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
66
98
  * @throws {Error} Throws if instance is already consumed, plugin is duplicate, or dependencies are missing.
67
99
  */
68
100
  use(plugin) {
69
- if (this.#used)
70
- throw new Error(`[TinyClassManager] Cannot reuse a consumed manager instance.`);
71
- if (this.#appliedPlugins.has(plugin.name))
72
- throw new Error(`[TinyClassManager] Plugin conflict: "${plugin.name}" is already installed.`);
73
101
  /**
74
102
  * @type {string[]}
75
103
  * Extracted dependencies array with fallback for undefined properties.
76
104
  */
77
105
  const deps = plugin.dependencies || [];
78
- for (const dep of deps) {
79
- if (!this.#appliedPlugins.has(dep)) {
80
- throw new Error(`[TinyClassManager] Missing Dependency: "${plugin.name}" requires "${dep}" to be installed first.`);
81
- }
106
+ /**
107
+ * @type {R}
108
+ * The new class generated by the old object-style apply wrapper.
109
+ */
110
+ const ExtendedClass = plugin.apply(this.#currentClass);
111
+ return this.#validateAndTransition(plugin.name, deps, ExtendedClass);
112
+ }
113
+ /**
114
+ * Inserts a plugin directly by passing its apply function.
115
+ * It reads `_tinyDepName` and `_tinyDeps` statically from the returned class.
116
+ * @template {new (...args: any[]) => any} R
117
+ * @param {function(T): R} applyFn - Function that receives the base class and returns the extended class.
118
+ * @returns {TinyClassManager<R>} A new manager instance holding the extended class chain.
119
+ * @throws {Error} Throws if static properties are missing, instance is consumed, or validation fails.
120
+ */
121
+ insert(applyFn) {
122
+ /** * @type {R}
123
+ * The extended class returned directly from the provided function.
124
+ */
125
+ const ExtendedClass = applyFn(this.#currentClass);
126
+ /**
127
+ * @type {string}
128
+ * The plugin name extracted from the static property.
129
+ */
130
+ // @ts-ignore
131
+ const pluginName = ExtendedClass._tinyDepName;
132
+ if (!pluginName) {
133
+ throw new Error(`[TinyClassManager] Plugin class must define a static '_tinyDepName' property.`);
82
134
  }
83
- const newClassManager = new TinyClassManager(plugin.apply(this.#currentClass));
84
- this.#appliedPlugins.forEach((name) => newClassManager.#appliedPlugins.add(name));
85
- newClassManager.#appliedPlugins.add(plugin.name);
86
- this.#used = true;
87
- this.#appliedPlugins.clear();
88
- return newClassManager;
135
+ /**
136
+ * @type {string[]}
137
+ * The dependencies extracted from the static property, defaulting to an empty array.
138
+ */
139
+ // @ts-ignore
140
+ const deps = ExtendedClass._tinyDeps ?? [];
141
+ if (!Array.isArray(deps) || !deps.every((name) => typeof name === 'string')) {
142
+ throw new Error(`[TinyClassManager] Plugin class must define a static array of strings in '_tinyDepName' property.`);
143
+ }
144
+ return this.#validateAndTransition(pluginName, deps, ExtendedClass);
89
145
  }
90
146
  /**
91
147
  * Finalizes the composition and returns the fully built class.
@@ -264,6 +264,13 @@ class TinyEvents {
264
264
  this.#listeners.clear();
265
265
  }
266
266
 
267
+ /**
268
+ * Removes all event listeners of all types from the element.
269
+ */
270
+ removeAllListeners() {
271
+ return this.offAllTypes();
272
+ }
273
+
267
274
  /////////////////////////////////////////////
268
275
 
269
276
  /**
@@ -101,6 +101,10 @@ declare class TinyEvents {
101
101
  * Removes all event listeners of all types from the element.
102
102
  */
103
103
  offAllTypes(): void;
104
+ /**
105
+ * Removes all event listeners of all types from the element.
106
+ */
107
+ removeAllListeners(): void;
104
108
  /**
105
109
  * Returns the number of listeners for a given event.
106
110
  *
@@ -244,6 +244,12 @@ class TinyEvents {
244
244
  offAllTypes() {
245
245
  this.#listeners.clear();
246
246
  }
247
+ /**
248
+ * Removes all event listeners of all types from the element.
249
+ */
250
+ removeAllListeners() {
251
+ return this.offAllTypes();
252
+ }
247
253
  /////////////////////////////////////////////
248
254
  /**
249
255
  * Returns the number of listeners for a given event.
@@ -15,57 +15,59 @@ class EntityCore {
15
15
  }
16
16
 
17
17
  // 2. Creating Dependent DLCs
18
- const Health = {
19
- name: 'Health',
20
- dependencies: [], // No dependencies, works just with the Core
21
- /** @param {typeof EntityCore} Base */
22
- apply: (Base) =>
23
- class HealthModule extends Base {
24
- /** @param {string} id */
25
- constructor(id) {
26
- super(id);
27
- this.hp = 100;
28
- }
18
+ /** @param {typeof EntityCore} Base */
19
+ const applyHealth = (Base) =>
20
+ class HealthModule extends Base {
21
+ static _tinyDepName = 'Health';
22
+ /** @type {string[]} */
23
+ static _tinyDeps = []; // No dependencies, works just with the Core
29
24
 
30
- /**
31
- * Deducts health from the entity.
32
- * @param {number} amount - The amount of raw damage to deal.
33
- */
34
- takeDamage(amount) {
35
- this.hp -= amount;
36
- console.log(`Took ${amount} damage. HP left: ${this.hp}`);
37
- }
38
- },
39
- };
25
+ /** @param {string} id */
26
+ constructor(id) {
27
+ super(id);
28
+ this.hp = 100;
29
+ }
30
+
31
+ /**
32
+ * Deducts health from the entity.
33
+ * @param {number} amount - The amount of raw damage to deal.
34
+ */
35
+ takeDamage(amount) {
36
+ this.hp -= amount;
37
+ console.log(`Took ${amount} damage. HP left: ${this.hp}`);
38
+ }
39
+ };
40
40
 
41
41
  // 3. Creating Independent DLCs
42
- const Armor = {
43
- name: 'Armor',
44
- dependencies: ['Health'], // Strictly requires the Health module to be installed first
45
- /** @param {ReturnType<typeof Health.apply>} Base */
46
- apply: (Base) =>
47
- class ArmorModule extends Base {
48
- /** @param {string} id */
49
- constructor(id) {
50
- super(id);
51
- this.armor = 50;
52
- }
42
+ /** @param {ReturnType<typeof applyHealth>} Base */
43
+ const applyArmor = (Base) =>
44
+ class ArmorModule extends Base {
45
+ static _tinyDepName = 'Armor';
46
+ static _tinyDeps = ['Health']; // Strictly requires the Health module to be installed first
47
+
48
+ /** @param {string} id */
49
+ constructor(id) {
50
+ super(id);
51
+ this.armor = 50;
52
+ }
53
53
 
54
+ /**
55
+ * Deducts health, factoring in damage mitigation provided by armor.
56
+ * @param {number} amount - The initial damage before armor reduction.
57
+ */
58
+ takeDamage(amount) {
54
59
  /**
55
- * Deducts health, factoring in damage mitigation provided by armor.
56
- * @param {number} amount - The initial damage before armor reduction.
60
+ * Calculated damage after armor mitigation is applied, ensuring it never drops below 0.
57
61
  */
58
- takeDamage(amount) {
59
- const reducedDamage = Math.max(0, amount - this.armor * 0.1);
60
- super.takeDamage(reducedDamage);
61
- }
62
- },
63
- };
62
+ const reducedDamage = Math.max(0, amount - this.armor * 0.1);
63
+ super.takeDamage(reducedDamage);
64
+ }
65
+ };
64
66
 
65
67
  // 4. Building the final class
66
68
  try {
67
69
  const CoreTest = new TinyClassManager(EntityCore);
68
- const FullyArmoredEntity = CoreTest.use(Health).use(Armor).build();
70
+ const FullyArmoredEntity = CoreTest.insert(applyHealth).insert(applyArmor).build();
69
71
 
70
72
  const myEntity = new FullyArmoredEntity('Player_1');
71
73
  myEntity.takeDamage(30);
@@ -10,51 +10,51 @@ class EntityCore {
10
10
  }
11
11
  }
12
12
  // 2. Creating Dependent DLCs
13
- const Health = {
14
- name: 'Health',
15
- dependencies: [], // No dependencies, works just with the Core
16
- /** @param {typeof EntityCore} Base */
17
- apply: (Base) => class HealthModule extends Base {
18
- /** @param {string} id */
19
- constructor(id) {
20
- super(id);
21
- this.hp = 100;
22
- }
23
- /**
24
- * Deducts health from the entity.
25
- * @param {number} amount - The amount of raw damage to deal.
26
- */
27
- takeDamage(amount) {
28
- this.hp -= amount;
29
- console.log(`Took ${amount} damage. HP left: ${this.hp}`);
30
- }
31
- },
13
+ /** @param {typeof EntityCore} Base */
14
+ const applyHealth = (Base) => class HealthModule extends Base {
15
+ static _tinyDepName = 'Health';
16
+ /** @type {string[]} */
17
+ static _tinyDeps = []; // No dependencies, works just with the Core
18
+ /** @param {string} id */
19
+ constructor(id) {
20
+ super(id);
21
+ this.hp = 100;
22
+ }
23
+ /**
24
+ * Deducts health from the entity.
25
+ * @param {number} amount - The amount of raw damage to deal.
26
+ */
27
+ takeDamage(amount) {
28
+ this.hp -= amount;
29
+ console.log(`Took ${amount} damage. HP left: ${this.hp}`);
30
+ }
32
31
  };
33
32
  // 3. Creating Independent DLCs
34
- const Armor = {
35
- name: 'Armor',
36
- dependencies: ['Health'], // Strictly requires the Health module to be installed first
37
- /** @param {ReturnType<typeof Health.apply>} Base */
38
- apply: (Base) => class ArmorModule extends Base {
39
- /** @param {string} id */
40
- constructor(id) {
41
- super(id);
42
- this.armor = 50;
43
- }
33
+ /** @param {ReturnType<typeof applyHealth>} Base */
34
+ const applyArmor = (Base) => class ArmorModule extends Base {
35
+ static _tinyDepName = 'Armor';
36
+ static _tinyDeps = ['Health']; // Strictly requires the Health module to be installed first
37
+ /** @param {string} id */
38
+ constructor(id) {
39
+ super(id);
40
+ this.armor = 50;
41
+ }
42
+ /**
43
+ * Deducts health, factoring in damage mitigation provided by armor.
44
+ * @param {number} amount - The initial damage before armor reduction.
45
+ */
46
+ takeDamage(amount) {
44
47
  /**
45
- * Deducts health, factoring in damage mitigation provided by armor.
46
- * @param {number} amount - The initial damage before armor reduction.
48
+ * Calculated damage after armor mitigation is applied, ensuring it never drops below 0.
47
49
  */
48
- takeDamage(amount) {
49
- const reducedDamage = Math.max(0, amount - this.armor * 0.1);
50
- super.takeDamage(reducedDamage);
51
- }
52
- },
50
+ const reducedDamage = Math.max(0, amount - this.armor * 0.1);
51
+ super.takeDamage(reducedDamage);
52
+ }
53
53
  };
54
54
  // 4. Building the final class
55
55
  try {
56
56
  const CoreTest = new TinyClassManager(EntityCore);
57
- const FullyArmoredEntity = CoreTest.use(Health).use(Armor).build();
57
+ const FullyArmoredEntity = CoreTest.insert(applyHealth).insert(applyArmor).build();
58
58
  const myEntity = new FullyArmoredEntity('Player_1');
59
59
  myEntity.takeDamage(30);
60
60
  console.log(`Armor value: ${myEntity.armor}`);