tiny-essentials 1.25.5 → 1.26.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/TinyFork.mjs ADDED
@@ -0,0 +1,608 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs/promises';
4
+ import { existsSync } from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import parser from '@babel/parser';
8
+ import _traverse from '@babel/traverse';
9
+ import _generate from '@babel/generator';
10
+ import * as t from '@babel/types';
11
+
12
+ // Workaround for Babel ESM compatibility
13
+ const traverse = _traverse.default || _traverse;
14
+ const generate = _generate.default || _generate;
15
+
16
+ const LATEST_VERSION = 1;
17
+
18
+ /**
19
+ * Defines the current execution paths.
20
+ * @typedef {Object} AppPaths
21
+ * @property {string} cliRoot - The root directory of tiny-essentials.
22
+ * @property {string} userProjectRoot - The root directory of the user's project invoking the CLI.
23
+ */
24
+ const PATHS = {
25
+ cliRoot: path.dirname(fileURLToPath(import.meta.url)),
26
+ userProjectRoot: process.cwd(),
27
+ /**
28
+ * Resolves the output directory.
29
+ * @param {string|null} customPath - Optional custom path provided by the user.
30
+ * @returns {string} The absolute path for the vendor directory.
31
+ */
32
+ getVendorDir(customPath) {
33
+ return customPath
34
+ ? path.resolve(this.userProjectRoot, customPath)
35
+ : path.join(this.userProjectRoot, 'vendor', 'tiny-essentials');
36
+ },
37
+ };
38
+
39
+ /**
40
+ * Parses a single user input target into resolving segments.
41
+ *
42
+ * @param {string} rawTarget - The raw string input from CLI.
43
+ * @returns {Object} Target descriptors.
44
+ * @property {number|null} targetVersion - The explicit version if provided.
45
+ * @property {string} basePath - The relative path requested.
46
+ * @property {string[]} functionsToExtract - The specific functions to pull via Babel.
47
+ */
48
+ function parseTarget(rawTarget) {
49
+ let targetVersion = null;
50
+ let cleanTarget = rawTarget;
51
+
52
+ const versionMatch = rawTarget.match(/^v(\d+)\//);
53
+ if (versionMatch) {
54
+ targetVersion = parseInt(versionMatch[1], 10);
55
+ cleanTarget = rawTarget.replace(versionMatch[0], '');
56
+ }
57
+
58
+ const hasFunctions = cleanTarget.includes(',');
59
+ let functionsToExtract = [];
60
+ let basePath = cleanTarget;
61
+
62
+ if (hasFunctions) {
63
+ const parts = cleanTarget.split('/');
64
+ const lastPart = parts.pop();
65
+ if (lastPart) {
66
+ functionsToExtract = lastPart.split(',').map((f) => f.trim());
67
+ basePath = parts.join('/');
68
+ }
69
+ }
70
+
71
+ return { targetVersion, basePath, functionsToExtract };
72
+ }
73
+
74
+ /**
75
+ * Searches for the requested module file across versions.
76
+ *
77
+ * @param {number|null} targetVersion - The optional hardcoded version.
78
+ * @param {string} basePath - The module path.
79
+ * @returns {Promise<{ filePath: string, ext: string, versionDir: string, interceptedFunction?: string }>} Resolved file info.
80
+ */
81
+ async function resolveSourceFile(targetVersion, basePath) {
82
+ if (basePath.includes('/build/')) {
83
+ throw new Error('Forbidden extraction: Cannot export files from the build directory.');
84
+ }
85
+
86
+ const extensions = ['.mjs', '.js'];
87
+ const startVersion = targetVersion || LATEST_VERSION;
88
+ const endVersion = targetVersion ? targetVersion : 1;
89
+
90
+ for (let v = startVersion; v >= endVersion; v--) {
91
+ const versionDir = path.join(PATHS.cliRoot, 'dist', `v${v}`);
92
+
93
+ // If it's a direct file path without functions (which means basePath might be the full path)
94
+ for (const ext of extensions) {
95
+ // First attempt: Treating basePath as the exact file (minus extension)
96
+ const exactPath = path.join(versionDir, `${basePath}${ext}`);
97
+ if (existsSync(exactPath)) return { filePath: exactPath, ext, versionDir };
98
+
99
+ // Second attempt: Checking if the last folder segment in basePath was meant to be functions,
100
+ // but didn't have commas. (e.g., folder/module/func1)
101
+ const parts = basePath.split('/');
102
+ if (parts.length > 1) {
103
+ const funcNameCandidate = parts.pop();
104
+ const modulePath = parts.join('/');
105
+ const nestedPath = path.join(versionDir, `${modulePath}${ext}`);
106
+
107
+ if (existsSync(nestedPath)) {
108
+ // Returns the path, but caller needs to know it intercepted a single function
109
+ return { filePath: nestedPath, ext, versionDir, interceptedFunction: funcNameCandidate };
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ throw new Error(`Module not found: ${basePath}`);
116
+ }
117
+
118
+ /**
119
+ * Advanced Multi-File Dependency Extractor.
120
+ * Parses files, traces dependencies, and extracts everything mirroring the original structure.
121
+ */
122
+ class MultiFileExtractor {
123
+ /**
124
+ * @param {string} outDir - The base output directory.
125
+ */
126
+ constructor(outDir) {
127
+ this.outDir = outDir;
128
+ this.fileRequests = new Map();
129
+ this.processedFiles = new Map();
130
+ }
131
+
132
+ /**
133
+ * Registers a file and the specific functions needed from it into the extraction queue.
134
+ *
135
+ * @param {string} filePath - Absolute path to the file.
136
+ * @param {string[]} names - Specific exports to keep (empty array means full file).
137
+ * @param {string} versionDir - The base version directory for structural mirroring.
138
+ */
139
+ requestExtraction(filePath, names, versionDir) {
140
+ if (!this.fileRequests.has(filePath)) {
141
+ this.fileRequests.set(filePath, { names: new Set(), versionDir, full: false });
142
+ }
143
+ const req = this.fileRequests.get(filePath);
144
+
145
+ if (names.length === 0) {
146
+ req.full = true;
147
+ } else {
148
+ names.forEach((n) => req.names.add(n));
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Resolves local imports relative to the current file.
154
+ *
155
+ * @param {string} currentFilePath - The file importing the module.
156
+ * @param {string} importStr - The import string.
157
+ * @returns {string|null} Resolved absolute path or null if it's an external package.
158
+ */
159
+ resolveImportPath(currentFilePath, importStr) {
160
+ if (!importStr.startsWith('.')) return null; // Ignore external/node_modules
161
+
162
+ const dir = path.dirname(currentFilePath);
163
+ let resolved = path.resolve(dir, importStr);
164
+
165
+ if (!path.extname(resolved)) {
166
+ for (const ext of ['.mjs', '.js']) {
167
+ if (existsSync(resolved + ext)) return resolved + ext;
168
+ }
169
+ }
170
+ return resolved;
171
+ }
172
+
173
+ async processQueue() {
174
+ let processedCount = 0;
175
+
176
+ // Use a while loop because tracing dependencies will dynamically add new files to the Map
177
+ while (processedCount < this.fileRequests.size) {
178
+ const entries = Array.from(this.fileRequests.entries());
179
+
180
+ for (const [filePath, req] of entries) {
181
+ if (this.processedFiles.has(filePath)) continue;
182
+
183
+ const finalCode = await this.parseAndPrune(filePath, req);
184
+ this.processedFiles.set(filePath, { code: finalCode, versionDir: req.versionDir });
185
+ processedCount++;
186
+ }
187
+ }
188
+
189
+ await this.writeFiles();
190
+ }
191
+
192
+ /**
193
+ * Core AST manipulation: traces required imports and removes unused code.
194
+ *
195
+ * @param {string} filePath - Target file path.
196
+ * @param {Object} req - Request object containing required export names.
197
+ * @returns {Promise<string>} The minified and formatted generated source code.
198
+ */
199
+ async parseAndPrune(filePath, req) {
200
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
201
+ const ast = parser.parse(sourceCode, { sourceType: 'module', plugins: ['classProperties'] });
202
+
203
+ const localKeepNames = new Set();
204
+ const requiredImports = new Map();
205
+
206
+ // Phase 1: Deep traceability
207
+ traverse(ast, {
208
+ Program: (astPath) => {
209
+ const bindings = astPath.scope.bindings;
210
+
211
+ if (req.full) {
212
+ Object.keys(bindings).forEach((name) => localKeepNames.add(name));
213
+ }
214
+
215
+ const queue = [];
216
+ const addToKeep = (name) => {
217
+ if (!localKeepNames.has(name)) {
218
+ localKeepNames.add(name);
219
+ queue.push(name);
220
+ }
221
+ };
222
+
223
+ astPath.traverse({
224
+ // Tracing Static Methods on any Class
225
+ ClassDeclaration(classPath) {
226
+ if (req.full) return;
227
+ classPath.node.body.body.forEach((item) => {
228
+ const keyName = t.isIdentifier(item.key) ? item.key.name : null;
229
+ if (
230
+ keyName &&
231
+ item.static &&
232
+ (t.isClassMethod(item) || t.isClassProperty(item)) &&
233
+ req.names.has(keyName)
234
+ ) {
235
+ traverse(
236
+ item,
237
+ {
238
+ Identifier(idPath) {
239
+ if (idPath.isReferencedIdentifier() && bindings[idPath.node.name]) {
240
+ addToKeep(idPath.node.name);
241
+ }
242
+ },
243
+ },
244
+ astPath.scope,
245
+ astPath,
246
+ );
247
+ }
248
+ });
249
+ },
250
+ CallExpression(callPath) {
251
+ if (callPath.node.callee.type === 'Import') {
252
+ const arg = callPath.node.arguments[0];
253
+ if (t.isStringLiteral(arg)) {
254
+ const source = arg.value;
255
+ if (!requiredImports.has(source)) requiredImports.set(source, new Set());
256
+ requiredImports.get(source).add({ local: '*', imported: '*' });
257
+ }
258
+ }
259
+ },
260
+ ImportDeclaration(expPath) {
261
+ const source = expPath.node.source.value;
262
+ if (!requiredImports.has(source)) requiredImports.set(source, new Set());
263
+
264
+ if (req.full) {
265
+ if (expPath.node.specifiers.length === 0) {
266
+ requiredImports.get(source).add({ local: null, imported: null });
267
+ } else {
268
+ expPath.node.specifiers.forEach((spec) => {
269
+ const importedName = t.isImportDefaultSpecifier(spec)
270
+ ? 'default'
271
+ : t.isImportNamespaceSpecifier(spec)
272
+ ? '*'
273
+ : spec.imported.name;
274
+ requiredImports
275
+ .get(source)
276
+ .add({ local: spec.local.name, imported: importedName });
277
+ });
278
+ }
279
+ }
280
+ },
281
+ // Catch re-exports (export * from './module')
282
+ ExportAllDeclaration(expPath) {
283
+ if (expPath.node.source) {
284
+ const source = expPath.node.source.value;
285
+ if (!requiredImports.has(source)) requiredImports.set(source, new Set());
286
+ // Always request '*' to trace export everything if needed
287
+ requiredImports.get(source).add({ local: '*', imported: '*', isExportAll: true });
288
+ }
289
+ },
290
+ ExportNamedDeclaration(expPath) {
291
+ if (expPath.node.source) {
292
+ // Tracing specific re-exports: export { target } from './module'
293
+ const source = expPath.node.source.value;
294
+ if (!requiredImports.has(source)) requiredImports.set(source, new Set());
295
+
296
+ expPath.node.specifiers.forEach((spec) => {
297
+ const exportedName = spec.exported.name;
298
+ if (req.full || req.names.has(exportedName)) {
299
+ const importedName = spec.local ? spec.local.name : exportedName;
300
+ requiredImports
301
+ .get(source)
302
+ .add({ local: null, imported: importedName, isReExport: true, exportedName });
303
+ }
304
+ });
305
+ } else {
306
+ const dec = expPath.node.declaration;
307
+ if (dec) {
308
+ if (t.isFunctionDeclaration(dec) && (req.full || req.names.has(dec.id.name))) {
309
+ addToKeep(dec.id.name);
310
+ } else if (t.isVariableDeclaration(dec)) {
311
+ dec.declarations.forEach((d) => {
312
+ if (t.isIdentifier(d.id) && (req.full || req.names.has(d.id.name)))
313
+ addToKeep(d.id.name);
314
+ });
315
+ }
316
+ } else if (expPath.node.specifiers) {
317
+ expPath.node.specifiers.forEach((spec) => {
318
+ if (req.full || req.names.has(spec.exported.name)) addToKeep(spec.local.name);
319
+ });
320
+ }
321
+ }
322
+ },
323
+ ExportDefaultDeclaration(expPath) {
324
+ const dec = expPath.node.declaration;
325
+ if (req.full || req.names.has('default')) {
326
+ if (t.isIdentifier(dec)) addToKeep(dec.name);
327
+ else if (dec.id) addToKeep(dec.id.name);
328
+ }
329
+ },
330
+ });
331
+
332
+ // Trace local variables and imports inside the queue
333
+ let i = 0;
334
+ while (i < queue.length) {
335
+ const currentName = queue[i++];
336
+ const binding = bindings[currentName];
337
+
338
+ if (binding) {
339
+ // Namespace Specifier Fix Included!
340
+ if (
341
+ binding.path.isImportSpecifier() ||
342
+ binding.path.isImportDefaultSpecifier() ||
343
+ binding.path.isImportNamespaceSpecifier()
344
+ ) {
345
+ const importDecl = binding.path.parentPath.node;
346
+ const source = importDecl.source.value;
347
+ if (!requiredImports.has(source)) requiredImports.set(source, new Set());
348
+
349
+ const importedName = binding.path.isImportDefaultSpecifier()
350
+ ? 'default'
351
+ : binding.path.isImportNamespaceSpecifier()
352
+ ? '*'
353
+ : binding.path.node.imported.name;
354
+ requiredImports.get(source).add({ local: currentName, imported: importedName });
355
+ } else {
356
+ binding.path.traverse({
357
+ Identifier(idPath) {
358
+ if (idPath.isReferencedIdentifier() && bindings[idPath.node.name]) {
359
+ addToKeep(idPath.node.name);
360
+ }
361
+ },
362
+ });
363
+ }
364
+ }
365
+ }
366
+ },
367
+ });
368
+
369
+ // Trigger extraction of all found deep dependencies
370
+ for (const [source, specs] of requiredImports.entries()) {
371
+ const resolvedPath = this.resolveImportPath(filePath, source);
372
+ if (resolvedPath) {
373
+ // If it's a full export dependency (like side-effect imports or export *), request full extraction
374
+ const isFullDependency = Array.from(specs).some(
375
+ (s) => s.imported === null || s.imported === '*',
376
+ );
377
+ const importedNamesToRequest = isFullDependency
378
+ ? []
379
+ : Array.from(specs).map((s) => s.imported);
380
+
381
+ this.requestExtraction(resolvedPath, importedNamesToRequest, req.versionDir);
382
+ }
383
+ }
384
+
385
+ // Phase 2: Surgical Pruning
386
+ if (!req.full) {
387
+ // Helper function to extract Class Methods AND Class Properties natively
388
+ const extractStaticMethods = (classNode) => {
389
+ const newNodes = [];
390
+ classNode.body.body.forEach((item) => {
391
+ const keyName = t.isIdentifier(item.key) ? item.key.name : null;
392
+ if (
393
+ keyName &&
394
+ item.static &&
395
+ (t.isClassMethod(item) || t.isClassProperty(item)) &&
396
+ req.names.has(keyName)
397
+ ) {
398
+ if (t.isClassMethod(item)) {
399
+ const func = t.functionDeclaration(
400
+ t.identifier(keyName),
401
+ item.params,
402
+ item.body,
403
+ item.generator,
404
+ item.async,
405
+ );
406
+ if (item.leadingComments) func.leadingComments = item.leadingComments;
407
+ newNodes.push(t.exportNamedDeclaration(func));
408
+ } else if (t.isClassProperty(item)) {
409
+ const variableDec = t.variableDeclaration('const', [
410
+ t.variableDeclarator(t.identifier(keyName), item.value),
411
+ ]);
412
+ if (item.leadingComments) variableDec.leadingComments = item.leadingComments;
413
+ newNodes.push(t.exportNamedDeclaration(variableDec));
414
+ }
415
+ }
416
+ });
417
+ return newNodes;
418
+ };
419
+
420
+ traverse(ast, {
421
+ Program(astPath) {
422
+ const body = astPath.get('body');
423
+ for (const statement of body) {
424
+ let keep = false;
425
+
426
+ if (statement.isImportDeclaration()) {
427
+ const source = statement.node.source.value;
428
+ if (requiredImports.has(source)) {
429
+ const neededSpecs = requiredImports.get(source);
430
+ const neededLocalNames = new Set(
431
+ Array.from(neededSpecs)
432
+ .map((s) => s.local)
433
+ .filter(Boolean),
434
+ );
435
+
436
+ if (statement.node.specifiers.length > 0) {
437
+ statement.node.specifiers = statement.node.specifiers.filter((spec) =>
438
+ neededLocalNames.has(spec.local.name),
439
+ );
440
+ if (statement.node.specifiers.length > 0) keep = true;
441
+ }
442
+ }
443
+ } else if (statement.isExportNamedDeclaration()) {
444
+ if (statement.node.source) {
445
+ const source = statement.node.source.value;
446
+ if (requiredImports.has(source)) {
447
+ const neededSpecs = requiredImports.get(source);
448
+ const neededExportedNames = new Set(
449
+ Array.from(neededSpecs)
450
+ .filter((s) => s.isReExport)
451
+ .map((s) => s.exportedName),
452
+ );
453
+
454
+ statement.node.specifiers = statement.node.specifiers.filter((spec) =>
455
+ neededExportedNames.has(spec.exported.name),
456
+ );
457
+ if (statement.node.specifiers.length > 0) keep = true;
458
+ }
459
+ } else {
460
+ const dec = statement.get('declaration');
461
+ if (
462
+ dec &&
463
+ dec.isFunctionDeclaration() &&
464
+ (req.names.has(dec.node.id.name) || localKeepNames.has(dec.node.id.name))
465
+ ) {
466
+ keep = true;
467
+ } else if (dec && dec.isVariableDeclaration()) {
468
+ const keepers = dec.node.declarations.filter(
469
+ (d) =>
470
+ t.isIdentifier(d.id) &&
471
+ (req.names.has(d.id.name) || localKeepNames.has(d.id.name)),
472
+ );
473
+ if (keepers.length > 0) {
474
+ dec.node.declarations = keepers;
475
+ keep = true;
476
+ }
477
+ } else if (dec && dec.isClassDeclaration()) {
478
+ if (req.names.has(dec.node.id.name) || localKeepNames.has(dec.node.id.name)) {
479
+ keep = true;
480
+ } else {
481
+ const extractedNodes = extractStaticMethods(dec.node);
482
+ if (extractedNodes.length > 0) {
483
+ statement.replaceWithMultiple(extractedNodes);
484
+ keep = true;
485
+ continue;
486
+ }
487
+ }
488
+ } else if (statement.node.specifiers && statement.node.specifiers.length > 0) {
489
+ statement.node.specifiers = statement.node.specifiers.filter(
490
+ (spec) =>
491
+ req.names.has(spec.exported.name) || localKeepNames.has(spec.local.name),
492
+ );
493
+ if (statement.node.specifiers.length > 0) keep = true;
494
+ }
495
+ }
496
+ } else if (statement.isExportAllDeclaration()) {
497
+ const source = statement.node.source.value;
498
+ if (requiredImports.has(source)) {
499
+ const neededSpecs = requiredImports.get(source);
500
+ if (Array.from(neededSpecs).some((s) => s.isExportAll)) keep = true;
501
+ }
502
+ } else if (statement.isExportDefaultDeclaration()) {
503
+ if (req.names.has('default')) {
504
+ keep = true;
505
+ } else if (t.isClassDeclaration(statement.node.declaration)) {
506
+ const extractedNodes = extractStaticMethods(statement.node.declaration);
507
+ if (extractedNodes.length > 0) {
508
+ statement.replaceWithMultiple(extractedNodes);
509
+ keep = true;
510
+ continue;
511
+ }
512
+ }
513
+ } else if (statement.isClassDeclaration()) {
514
+ if (localKeepNames.has(statement.node.id.name)) {
515
+ keep = true;
516
+ } else {
517
+ const extractedNodes = extractStaticMethods(statement.node);
518
+ if (extractedNodes.length > 0) {
519
+ statement.replaceWithMultiple(extractedNodes);
520
+ keep = true;
521
+ continue;
522
+ }
523
+ }
524
+ } else if (
525
+ statement.isFunctionDeclaration() &&
526
+ localKeepNames.has(statement.node.id.name)
527
+ ) {
528
+ keep = true;
529
+ } else if (statement.isVariableDeclaration()) {
530
+ const keepers = statement.node.declarations.filter(
531
+ (d) => t.isIdentifier(d.id) && localKeepNames.has(d.id.name),
532
+ );
533
+ if (keepers.length > 0) {
534
+ statement.node.declarations = keepers;
535
+ keep = true;
536
+ }
537
+ }
538
+
539
+ if (!keep) statement.remove();
540
+ }
541
+ },
542
+ });
543
+ }
544
+
545
+ return generate(ast).code;
546
+ }
547
+
548
+ /**
549
+ * Flushes processed files mapping onto the filesystem maintaining origin structure.
550
+ */
551
+ async writeFiles() {
552
+ let filesSaved = 0;
553
+ for (const [filePath, data] of this.processedFiles.entries()) {
554
+ const relativeToVersion = path.relative(data.versionDir, filePath);
555
+ const destPath = path.join(this.outDir, relativeToVersion);
556
+
557
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
558
+ await fs.writeFile(destPath, data.code, 'utf-8');
559
+ console.log(`> Created: ${relativeToVersion}`);
560
+ filesSaved++;
561
+ }
562
+ console.log(`\nSuccessfully orchestrated and bundled ${filesSaved} isolated files!`);
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Main execution function for the CLI tool.
568
+ */
569
+ async function runCLI() {
570
+ const args = process.argv.slice(2);
571
+
572
+ const outDirArg = args.find((a) => a.startsWith('--out-dir='));
573
+ const customOutDir = outDirArg ? outDirArg.split('=')[1] : null;
574
+
575
+ // Filter out flags to get the raw target strings
576
+ const rawTargets = args.filter((a) => !a.startsWith('--'));
577
+
578
+ if (rawTargets.length === 0) {
579
+ console.error('Usage: npx tiny-essentials-fork [--out-dir=custom/path] <target1> [target2] ...');
580
+ console.error('Example: npx tiny-essentials-fork v1/basics/example/func1 v1/lib/TinyHtml');
581
+ process.exit(1);
582
+ }
583
+
584
+ try {
585
+ const finalVendorDir = PATHS.getVendorDir(customOutDir);
586
+ const extractor = new MultiFileExtractor(finalVendorDir);
587
+
588
+ for (const rawTarget of rawTargets) {
589
+ let { targetVersion, basePath, functionsToExtract } = parseTarget(rawTarget);
590
+ const resolved = await resolveSourceFile(targetVersion, basePath);
591
+
592
+ if (resolved.interceptedFunction && functionsToExtract.length === 0) {
593
+ functionsToExtract.push(resolved.interceptedFunction);
594
+ }
595
+
596
+ extractor.requestExtraction(resolved.filePath, functionsToExtract, resolved.versionDir);
597
+ }
598
+
599
+ console.log(`Starting extraction to: ${finalVendorDir}\n`);
600
+ await extractor.processQueue();
601
+ } catch (error) {
602
+ console.error('\nError extracting modules:');
603
+ console.error(error.message);
604
+ process.exit(1);
605
+ }
606
+ }
607
+
608
+ runCLI();
@@ -0,0 +1,5 @@
1
+ ## ✨ New Features
2
+
3
+ * **🧱 Mamdani Architecture:** Fully decoupled Inference Engine using `Map` structures to dynamically handle an infinite number of linguistic variables and fuzzy sets.
4
+
5
+ **Full Changelog**: https://github.com/Tiny-Essentials/Tiny-Essentials/compare/1.25.4...1.25.5
@@ -0,0 +1,7 @@
1
+ ### ✨ Key Changes
2
+ * **Early-Exit Optimization:** Added the `optimize` flag to the global `trapezoid` utility and the `FuzzySet` class.
3
+ * **How it works:** When enabled (`true`), the engine uses short-circuit logic. It immediately returns `0` or `1` if the input value falls completely outside the outer bounds or entirely within the top plateau, safely skipping unnecessary slope calculations. The parameter defaults to `false`.
4
+
5
+ To take advantage of this performance boost, simply update your `FuzzySet` instantiations by passing `true` as the final argument: `new FuzzySet("Name", a, b, c, d, true)`.
6
+
7
+ **Full Changelog**: https://github.com/Tiny-Essentials/Tiny-Essentials/compare/1.25.4...1.25.6
@@ -0,0 +1,5 @@
1
+ # 🚀 Introducing TinyFork CLI: The Smart Extractor for Tiny-Essentials! 🍴✨
2
+
3
+ If you've ever wanted to use just a single function or class from `tiny-essentials` without bringing the entire library into your bundle, this release is for you! TinyFork is a custom, AST-powered tool that deeply scans our JavaScript modules and extracts *exactly* what you ask for.
4
+
5
+ It automatically traces and pulls in any required local dependencies, maintains the original `jsDoc` comments, and perfectly mirrors the original folder structure in your project.