tiny-essentials 1.28.16 โ†’ 1.28.18

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.
@@ -4,6 +4,7 @@ var events = require('events');
4
4
  var mediaContent = require('../../basics/mediaContent.cjs');
5
5
  var tools = require('../utils/tools.cjs');
6
6
  var objChecker = require('../../basics/objChecker.cjs');
7
+ var TinyAdvancedRaffle = require('../math/TinyAdvancedRaffle.cjs');
7
8
 
8
9
  const checkDestroy = tools.createCheckDestroyed('TinyRadioFm');
9
10
 
@@ -85,12 +86,13 @@ const checkDestroy = tools.createCheckDestroyed('TinyRadioFm');
85
86
  * @property {number} silenceDuration - Gap in ms between tracks.
86
87
  * @property {number} queryLimit - Safety lock for max items processed.
87
88
  * @property {boolean} voiceAfterMusic - Whether to play voice messages after music tracks.
89
+ * @property {number} voiceWeight - Weight used to calculate the probability of a voice block occurring.
88
90
  * @property {number} voiceMin - Minimum amount of voice messages to play if voiceAfterMusic is true.
89
91
  * @property {number} voiceMax - Maximum amount of voice messages to play.
90
92
  * @property {number} musicMaxConsecutive - Max times a music track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
91
93
  * @property {number} musicMinConsecutive - Min times a music track must repeat consecutively (1 = no mandatory repetition).
92
94
  * @property {number} voiceMaxConsecutive - Max times a voice track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
93
- * @property {number} voiceMinConsecutive - Min times a voice track must repeat consecutively (1 = no mandatory repetition).
95
+ * @property {number} voiceMinConsecutive - Min times a voice track can repeat consecutively (1 = no mandatory repetition).
94
96
  */
95
97
 
96
98
  //////////////////////////////////////////////////////////////////
@@ -358,6 +360,7 @@ class TinyRadioFm extends events.EventEmitter {
358
360
  voiceAfterMusic: true,
359
361
  voiceMin: 0,
360
362
  voiceMax: 1,
363
+ voiceWeight: 1,
361
364
  musicMaxConsecutive: 0,
362
365
  musicMinConsecutive: 0,
363
366
  voiceMaxConsecutive: 0,
@@ -435,6 +438,7 @@ class TinyRadioFm extends events.EventEmitter {
435
438
  }
436
439
  };
437
440
 
441
+ checkRange(config.voiceWeight, 'voiceWeight');
438
442
  checkRange(config.voiceMin, 'voiceMin');
439
443
  checkRange(config.voiceMax, 'voiceMax');
440
444
  checkRange(config.musicMinConsecutive, 'musicMinConsecutive');
@@ -992,11 +996,6 @@ class TinyRadioFm extends events.EventEmitter {
992
996
  };
993
997
  }
994
998
 
995
- /**
996
- * Pool of available items for weighted selection.
997
- * @type {Array<MediaContent & { weight: number }>}
998
- */
999
- const pool = list.map((item) => ({ ...item, weight: item.weight ?? 1 }));
1000
999
  const random = this._prng(currentSeed);
1001
1000
 
1002
1001
  /**
@@ -1009,6 +1008,30 @@ class TinyRadioFm extends events.EventEmitter {
1009
1008
  let lastId = initialLastId;
1010
1009
  let consecutiveCount = initialConsecutiveCount;
1011
1010
 
1011
+ // Initializes TinyAdvancedRaffle specifically for this sequence generation
1012
+ const raffle = new TinyAdvancedRaffle({
1013
+ rng: random,
1014
+ normalization: 'relative',
1015
+ });
1016
+
1017
+ // We keep the pool as an array to respect the original structure and support possible
1018
+ // duplicate IDs, if they occur (even though it is rare).
1019
+
1020
+ /** @type {MediaContent[]} */
1021
+ const pool = [...list];
1022
+
1023
+ // Registers all items in the Raffle system
1024
+ pool.forEach((item) => {
1025
+ const itemWeight = item.weight ?? 1;
1026
+ const existing = raffle.getItem(item.id);
1027
+ if (!existing) {
1028
+ raffle.addItem(item.id, { weight: itemWeight });
1029
+ } else {
1030
+ // If the ID already exists, we add its weight to maintain the correct proportion
1031
+ raffle.setBaseWeight(item.id, existing.baseWeight + itemWeight);
1032
+ }
1033
+ });
1034
+
1012
1035
  while (pool.length > 0) {
1013
1036
  /** @type {MediaContent|null} */
1014
1037
  let selectedItem = null;
@@ -1026,35 +1049,32 @@ class TinyRadioFm extends events.EventEmitter {
1026
1049
  }
1027
1050
  }
1028
1051
 
1029
- // 2. Maximum repetition logic and random draw
1052
+ // 2. Maximum repetition logic and drawing using the Raffle
1030
1053
  if (!selectedItem) {
1031
- let validPool = pool;
1054
+ let excludedId = null;
1032
1055
 
1033
1056
  if (lastId !== null && maxConsecutive !== -1 && consecutiveCount >= maxConsecutive) {
1034
- validPool = pool.filter((item) => item.id !== lastId);
1035
- }
1036
-
1037
- if (validPool.length === 0) {
1038
- validPool = pool;
1057
+ // Checks if there are other different items in the pool before preventing the last one
1058
+ const hasOtherItems = pool.some((item) => item.id !== lastId);
1059
+ if (hasOtherItems) {
1060
+ raffle.excludeItem(lastId);
1061
+ excludedId = lastId;
1062
+ }
1039
1063
  }
1040
1064
 
1041
- const totalWeight = validPool.reduce((sum, item) => sum + item.weight, 0);
1065
+ const drawResult = raffle.drawOne();
1042
1066
 
1043
- if (totalWeight <= 0) {
1044
- selectedItem = validPool[0];
1045
- selectedIndex = pool.findIndex((p) => p.id === selectedItem?.id);
1046
- } else {
1047
- const r = random() * totalWeight;
1048
- let sum = 0;
1049
- for (let i = 0; i < validPool.length; i++) {
1050
- sum += validPool[i].weight;
1051
- if (r <= sum) {
1052
- selectedItem = validPool[i];
1053
- selectedIndex = pool.findIndex((p) => p.id === selectedItem?.id);
1054
- break;
1055
- }
1067
+ if (drawResult) {
1068
+ selectedIndex = pool.findIndex((item) => item.id === drawResult.id);
1069
+ if (selectedIndex !== -1) {
1070
+ selectedItem = pool[selectedIndex];
1056
1071
  }
1057
1072
  }
1073
+
1074
+ // Removes the exclusion to avoid breaking future draws
1075
+ if (excludedId) {
1076
+ raffle.includeItem(excludedId);
1077
+ }
1058
1078
  }
1059
1079
 
1060
1080
  // Safety fallback
@@ -1065,8 +1085,23 @@ class TinyRadioFm extends events.EventEmitter {
1065
1085
 
1066
1086
  sequence.push(selectedItem);
1067
1087
 
1088
+ // Updates the pool and the Raffle after the item has been drawn and used
1068
1089
  if (selectedIndex !== -1) {
1069
1090
  pool.splice(selectedIndex, 1);
1091
+
1092
+ // If this item is no longer in the pool permanently, remove it from the Raffle
1093
+ const stillInPool = pool.some((item) => item.id === selectedItem.id);
1094
+ if (!stillInPool) {
1095
+ raffle.removeItem(selectedItem.id);
1096
+ } else {
1097
+ // If it still exists, we reduce the weight proportionally to continue the draw fairly
1098
+ const existing = raffle.getItem(selectedItem.id);
1099
+ if (existing) {
1100
+ const newWeight = Math.max(0, existing.baseWeight - (selectedItem.weight ?? 1));
1101
+ if (newWeight <= 0) raffle.removeItem(selectedItem.id);
1102
+ else raffle.setBaseWeight(selectedItem.id, newWeight);
1103
+ }
1104
+ }
1070
1105
  }
1071
1106
 
1072
1107
  if (selectedItem.id === lastId) {
@@ -1077,6 +1112,8 @@ class TinyRadioFm extends events.EventEmitter {
1077
1112
  }
1078
1113
  }
1079
1114
 
1115
+ raffle.destroy(); // Ensures memory is cleared at the end of each cycle
1116
+
1080
1117
  return { items: sequence, lastId, consecutiveCount };
1081
1118
  }
1082
1119
 
@@ -1146,19 +1183,25 @@ class TinyRadioFm extends events.EventEmitter {
1146
1183
  cycleDuration += music.duration + this.#config.silenceDuration;
1147
1184
 
1148
1185
  if (this.#config.voiceAfterMusic && voiceSeq.length > 0) {
1149
- const range = this.#config.voiceMax - this.#config.voiceMin + 1;
1150
- const voiceAmount = Math.floor(mixRandom() * range) + this.#config.voiceMin;
1151
-
1152
- for (let v = 0; v < voiceAmount; v++) {
1153
- const voice = voiceSeq[voiceCursor % voiceSeq.length];
1154
- voiceCursor++;
1155
-
1156
- block.push({
1157
- ...voice,
1158
- cycleStart: cycleDuration,
1159
- cycleEnd: cycleDuration + voice.duration,
1160
- });
1161
- cycleDuration += voice.duration + this.#config.silenceDuration;
1186
+ // Calculate chance based on weight: P = W / (W + 1)
1187
+ const voiceChance = this.#config.voiceWeight / (this.#config.voiceWeight + 1);
1188
+
1189
+ // Only proceed if the random roll is less than the calculated chance
1190
+ if (mixRandom() < voiceChance) {
1191
+ const range = this.#config.voiceMax - this.#config.voiceMin + 1;
1192
+ const voiceAmount = Math.floor(mixRandom() * range) + this.#config.voiceMin;
1193
+
1194
+ for (let v = 0; v < voiceAmount; v++) {
1195
+ const voice = voiceSeq[voiceCursor % voiceSeq.length];
1196
+ voiceCursor++;
1197
+
1198
+ block.push({
1199
+ ...voice,
1200
+ cycleStart: cycleDuration,
1201
+ cycleEnd: cycleDuration + voice.duration,
1202
+ });
1203
+ cycleDuration += voice.duration + this.#config.silenceDuration;
1204
+ }
1162
1205
  }
1163
1206
  }
1164
1207
  }
@@ -121,6 +121,10 @@ export type RadioConfig = {
121
121
  * - Whether to play voice messages after music tracks.
122
122
  */
123
123
  voiceAfterMusic: boolean;
124
+ /**
125
+ * - Weight used to calculate the probability of a voice block occurring.
126
+ */
127
+ voiceWeight: number;
124
128
  /**
125
129
  * - Minimum amount of voice messages to play if voiceAfterMusic is true.
126
130
  */
@@ -142,7 +146,7 @@ export type RadioConfig = {
142
146
  */
143
147
  voiceMaxConsecutive: number;
144
148
  /**
145
- * - Min times a voice track must repeat consecutively (1 = no mandatory repetition).
149
+ * - Min times a voice track can repeat consecutively (1 = no mandatory repetition).
146
150
  */
147
151
  voiceMinConsecutive: number;
148
152
  };
@@ -285,12 +289,13 @@ export type TinyRadioFmImport = {
285
289
  * @property {number} silenceDuration - Gap in ms between tracks.
286
290
  * @property {number} queryLimit - Safety lock for max items processed.
287
291
  * @property {boolean} voiceAfterMusic - Whether to play voice messages after music tracks.
292
+ * @property {number} voiceWeight - Weight used to calculate the probability of a voice block occurring.
288
293
  * @property {number} voiceMin - Minimum amount of voice messages to play if voiceAfterMusic is true.
289
294
  * @property {number} voiceMax - Maximum amount of voice messages to play.
290
295
  * @property {number} musicMaxConsecutive - Max times a music track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
291
296
  * @property {number} musicMinConsecutive - Min times a music track must repeat consecutively (1 = no mandatory repetition).
292
297
  * @property {number} voiceMaxConsecutive - Max times a voice track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
293
- * @property {number} voiceMinConsecutive - Min times a voice track must repeat consecutively (1 = no mandatory repetition).
298
+ * @property {number} voiceMinConsecutive - Min times a voice track can repeat consecutively (1 = no mandatory repetition).
294
299
  */
295
300
  /**
296
301
  * An extension of MediaContent that includes temporal boundaries within a generated cycle.
@@ -1 +1 @@
1
- {"version":3,"file":"TinyRadioFm.d.mts","sourceRoot":"","sources":["../../../../src/v1/libs/media/TinyRadioFm.mjs"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TinyRadioFm.d.mts","sourceRoot":"","sources":["../../../../src/v1/libs/media/TinyRadioFm.mjs"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { EventEmitter } from 'events';
2
2
  import { blobUrlToBase64, convertToBlobUrl, parseMediaMetadata, revokeContentUrls, valMediaContentMetadata, } from '../../basics/mediaContent.mjs';
3
3
  import { createCheckDestroyed } from '../utils/tools.mjs';
4
4
  import { isJsonObject } from '../../basics/objChecker.mjs';
5
+ import TinyAdvancedRaffle from '../math/TinyAdvancedRaffle.mjs';
5
6
  const checkDestroy = createCheckDestroyed('TinyRadioFm');
6
7
  /**
7
8
  * @typedef {import('../../basics/mediaContent.mjs').PictureDataType} PictureDataType
@@ -71,12 +72,13 @@ const checkDestroy = createCheckDestroyed('TinyRadioFm');
71
72
  * @property {number} silenceDuration - Gap in ms between tracks.
72
73
  * @property {number} queryLimit - Safety lock for max items processed.
73
74
  * @property {boolean} voiceAfterMusic - Whether to play voice messages after music tracks.
75
+ * @property {number} voiceWeight - Weight used to calculate the probability of a voice block occurring.
74
76
  * @property {number} voiceMin - Minimum amount of voice messages to play if voiceAfterMusic is true.
75
77
  * @property {number} voiceMax - Maximum amount of voice messages to play.
76
78
  * @property {number} musicMaxConsecutive - Max times a music track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
77
79
  * @property {number} musicMinConsecutive - Min times a music track must repeat consecutively (1 = no mandatory repetition).
78
80
  * @property {number} voiceMaxConsecutive - Max times a voice track can repeat consecutively (-1 = unlimited, 0 = strictly no repetition).
79
- * @property {number} voiceMinConsecutive - Min times a voice track must repeat consecutively (1 = no mandatory repetition).
81
+ * @property {number} voiceMinConsecutive - Min times a voice track can repeat consecutively (1 = no mandatory repetition).
80
82
  */
81
83
  //////////////////////////////////////////////////////////////////
82
84
  /**
@@ -302,6 +304,7 @@ class TinyRadioFm extends EventEmitter {
302
304
  voiceAfterMusic: true,
303
305
  voiceMin: 0,
304
306
  voiceMax: 1,
307
+ voiceWeight: 1,
305
308
  musicMaxConsecutive: 0,
306
309
  musicMinConsecutive: 0,
307
310
  voiceMaxConsecutive: 0,
@@ -363,6 +366,7 @@ class TinyRadioFm extends EventEmitter {
363
366
  throw new TypeError(`${minKey} must be a non-negative number.`);
364
367
  }
365
368
  };
369
+ checkRange(config.voiceWeight, 'voiceWeight');
366
370
  checkRange(config.voiceMin, 'voiceMin');
367
371
  checkRange(config.voiceMax, 'voiceMax');
368
372
  checkRange(config.musicMinConsecutive, 'musicMinConsecutive');
@@ -838,11 +842,6 @@ class TinyRadioFm extends EventEmitter {
838
842
  consecutiveCount: initialLastId === lastItem.id ? initialConsecutiveCount + 1 : 1,
839
843
  };
840
844
  }
841
- /**
842
- * Pool of available items for weighted selection.
843
- * @type {Array<MediaContent & { weight: number }>}
844
- */
845
- const pool = list.map((item) => ({ ...item, weight: item.weight ?? 1 }));
846
845
  const random = this._prng(currentSeed);
847
846
  /**
848
847
  * The finalized deterministic sequence.
@@ -852,6 +851,27 @@ class TinyRadioFm extends EventEmitter {
852
851
  /** @type {string|null} */
853
852
  let lastId = initialLastId;
854
853
  let consecutiveCount = initialConsecutiveCount;
854
+ // Initializes TinyAdvancedRaffle specifically for this sequence generation
855
+ const raffle = new TinyAdvancedRaffle({
856
+ rng: random,
857
+ normalization: 'relative',
858
+ });
859
+ // We keep the pool as an array to respect the original structure and support possible
860
+ // duplicate IDs, if they occur (even though it is rare).
861
+ /** @type {MediaContent[]} */
862
+ const pool = [...list];
863
+ // Registers all items in the Raffle system
864
+ pool.forEach((item) => {
865
+ const itemWeight = item.weight ?? 1;
866
+ const existing = raffle.getItem(item.id);
867
+ if (!existing) {
868
+ raffle.addItem(item.id, { weight: itemWeight });
869
+ }
870
+ else {
871
+ // If the ID already exists, we add its weight to maintain the correct proportion
872
+ raffle.setBaseWeight(item.id, existing.baseWeight + itemWeight);
873
+ }
874
+ });
855
875
  while (pool.length > 0) {
856
876
  /** @type {MediaContent|null} */
857
877
  let selectedItem = null;
@@ -867,32 +887,28 @@ class TinyRadioFm extends EventEmitter {
867
887
  selectedItem = sequence[sequence.length - 1];
868
888
  }
869
889
  }
870
- // 2. Maximum repetition logic and random draw
890
+ // 2. Maximum repetition logic and drawing using the Raffle
871
891
  if (!selectedItem) {
872
- let validPool = pool;
892
+ let excludedId = null;
873
893
  if (lastId !== null && maxConsecutive !== -1 && consecutiveCount >= maxConsecutive) {
874
- validPool = pool.filter((item) => item.id !== lastId);
875
- }
876
- if (validPool.length === 0) {
877
- validPool = pool;
878
- }
879
- const totalWeight = validPool.reduce((sum, item) => sum + item.weight, 0);
880
- if (totalWeight <= 0) {
881
- selectedItem = validPool[0];
882
- selectedIndex = pool.findIndex((p) => p.id === selectedItem?.id);
894
+ // Checks if there are other different items in the pool before preventing the last one
895
+ const hasOtherItems = pool.some((item) => item.id !== lastId);
896
+ if (hasOtherItems) {
897
+ raffle.excludeItem(lastId);
898
+ excludedId = lastId;
899
+ }
883
900
  }
884
- else {
885
- const r = random() * totalWeight;
886
- let sum = 0;
887
- for (let i = 0; i < validPool.length; i++) {
888
- sum += validPool[i].weight;
889
- if (r <= sum) {
890
- selectedItem = validPool[i];
891
- selectedIndex = pool.findIndex((p) => p.id === selectedItem?.id);
892
- break;
893
- }
901
+ const drawResult = raffle.drawOne();
902
+ if (drawResult) {
903
+ selectedIndex = pool.findIndex((item) => item.id === drawResult.id);
904
+ if (selectedIndex !== -1) {
905
+ selectedItem = pool[selectedIndex];
894
906
  }
895
907
  }
908
+ // Removes the exclusion to avoid breaking future draws
909
+ if (excludedId) {
910
+ raffle.includeItem(excludedId);
911
+ }
896
912
  }
897
913
  // Safety fallback
898
914
  if (!selectedItem) {
@@ -900,8 +916,25 @@ class TinyRadioFm extends EventEmitter {
900
916
  selectedIndex = 0;
901
917
  }
902
918
  sequence.push(selectedItem);
919
+ // Updates the pool and the Raffle after the item has been drawn and used
903
920
  if (selectedIndex !== -1) {
904
921
  pool.splice(selectedIndex, 1);
922
+ // If this item is no longer in the pool permanently, remove it from the Raffle
923
+ const stillInPool = pool.some((item) => item.id === selectedItem.id);
924
+ if (!stillInPool) {
925
+ raffle.removeItem(selectedItem.id);
926
+ }
927
+ else {
928
+ // If it still exists, we reduce the weight proportionally to continue the draw fairly
929
+ const existing = raffle.getItem(selectedItem.id);
930
+ if (existing) {
931
+ const newWeight = Math.max(0, existing.baseWeight - (selectedItem.weight ?? 1));
932
+ if (newWeight <= 0)
933
+ raffle.removeItem(selectedItem.id);
934
+ else
935
+ raffle.setBaseWeight(selectedItem.id, newWeight);
936
+ }
937
+ }
905
938
  }
906
939
  if (selectedItem.id === lastId) {
907
940
  consecutiveCount++;
@@ -911,6 +944,7 @@ class TinyRadioFm extends EventEmitter {
911
944
  consecutiveCount = 1;
912
945
  }
913
946
  }
947
+ raffle.destroy(); // Ensures memory is cleared at the end of each cycle
914
948
  return { items: sequence, lastId, consecutiveCount };
915
949
  }
916
950
  /**
@@ -956,17 +990,22 @@ class TinyRadioFm extends EventEmitter {
956
990
  });
957
991
  cycleDuration += music.duration + this.#config.silenceDuration;
958
992
  if (this.#config.voiceAfterMusic && voiceSeq.length > 0) {
959
- const range = this.#config.voiceMax - this.#config.voiceMin + 1;
960
- const voiceAmount = Math.floor(mixRandom() * range) + this.#config.voiceMin;
961
- for (let v = 0; v < voiceAmount; v++) {
962
- const voice = voiceSeq[voiceCursor % voiceSeq.length];
963
- voiceCursor++;
964
- block.push({
965
- ...voice,
966
- cycleStart: cycleDuration,
967
- cycleEnd: cycleDuration + voice.duration,
968
- });
969
- cycleDuration += voice.duration + this.#config.silenceDuration;
993
+ // Calculate chance based on weight: P = W / (W + 1)
994
+ const voiceChance = this.#config.voiceWeight / (this.#config.voiceWeight + 1);
995
+ // Only proceed if the random roll is less than the calculated chance
996
+ if (mixRandom() < voiceChance) {
997
+ const range = this.#config.voiceMax - this.#config.voiceMin + 1;
998
+ const voiceAmount = Math.floor(mixRandom() * range) + this.#config.voiceMin;
999
+ for (let v = 0; v < voiceAmount; v++) {
1000
+ const voice = voiceSeq[voiceCursor % voiceSeq.length];
1001
+ voiceCursor++;
1002
+ block.push({
1003
+ ...voice,
1004
+ cycleStart: cycleDuration,
1005
+ cycleEnd: cycleDuration + voice.duration,
1006
+ });
1007
+ cycleDuration += voice.duration + this.#config.silenceDuration;
1008
+ }
970
1009
  }
971
1010
  }
972
1011
  }
@@ -60,6 +60,7 @@ Direct access to smaller utilities:
60
60
  * `basics/text`
61
61
  * `basics/promiseUtils`
62
62
  * `basics/browserDetector`
63
+ * `basics/jsonFilter`
63
64
 
64
65
  Direct access to file utilities:
65
66
 
@@ -229,11 +230,11 @@ Each library can be imported separately:
229
230
  **ESM (modern projects)**
230
231
 
231
232
  ```js
232
- import { TinyTextarea } from "tiny-essentials/libs/text/TinyTextarea";
233
+ import TinyTextarea from "tiny-essentials/libs/text/TinyTextarea";
233
234
  ```
234
235
 
235
236
  **CommonJS (Node.js)**
236
237
 
237
238
  ```js
238
- const { TinyTextarea } = require("tiny-essentials/libs/text/TinyTextarea");
239
+ const { shuffleArray } = require("tiny-essentials/basics/array");
239
240
  ```
package/docs/v1/README.md CHANGED
@@ -13,6 +13,7 @@ Here, you can navigate through different sections of the project. Below are the
13
13
  - ๐Ÿ“ฆ **[Array](./basics/array.md)** โ€” A tiny utility for shuffling arrays using the Fisherโ€“Yates algorithm.
14
14
  - โฐ **[Clock](./basics/clock.md)** โ€” A versatile time utility module for calculating and formatting time durations.
15
15
  - ๐Ÿง  **[ObjFilter](./basics/objFilter.md)** โ€” Type detection, extension, and analysis made easy with simple and extensible type validation.
16
+ - ๐Ÿ” **[jsonFilter](./basics/jsonFilter.md)** โ€” High-precision utilities for filtering objects, arrays, and complex data structures like Maps and Sets.
16
17
  * ๐Ÿงฎ **[objChecker](./basics/objChecker.md)** โ€” Utilities for counting keys in objects or arrays and for safely detecting plain JSON-compatible objects.
17
18
  - ๐Ÿ”ข **[SimpleMath](./basics/simpleMath.md)** โ€” A collection of simple math utilities for calculations like the Rule of Three and percentages.
18
19
  - โœ๏ธ **[Text](./basics/text.md)** โ€” A utility for transforming text into title case formats, with multiple options for capitalization.
@@ -178,4 +179,4 @@ If you're curious about how I integrate AI into my development workflow โ€” incl
178
179
 
179
180
  โœจ **Visit the Tiny-Essentials documentation hub here:**
180
181
 
181
- ๐Ÿ‘‰ [https://github.com/Tiny-Essentials/.github/tree/main/docs](https://github.com/Tiny-Essentials/.github/tree/main/docs)
182
+ ๐Ÿ‘‰ [https://github.com/Tiny-Essentials/Tiny-AI-Workflow](https://github.com/Tiny-Essentials/Tiny-AI-Workflow)