@cldmv/slothlet 2.7.1 → 2.9.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.
Files changed (39) hide show
  1. package/AGENT-USAGE.md +1 -1
  2. package/README.md +253 -1475
  3. package/dist/lib/helpers/als-eventemitter.mjs +4 -5
  4. package/dist/lib/helpers/api_builder/add_api.mjs +237 -0
  5. package/dist/lib/helpers/api_builder/analysis.mjs +522 -0
  6. package/dist/lib/helpers/api_builder/construction.mjs +457 -0
  7. package/dist/lib/helpers/api_builder/decisions.mjs +737 -0
  8. package/dist/lib/helpers/api_builder.mjs +16 -1567
  9. package/dist/lib/helpers/utilities.mjs +121 -0
  10. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +44 -17
  11. package/dist/lib/runtime/runtime-livebindings.mjs +18 -3
  12. package/dist/lib/runtime/runtime.mjs +3 -3
  13. package/dist/slothlet.mjs +197 -547
  14. package/docs/API-RULES-CONDITIONS.md +508 -0
  15. package/{API-RULES.md → docs/API-RULES.md} +127 -72
  16. package/index.cjs +2 -1
  17. package/index.mjs +2 -1
  18. package/package.json +11 -9
  19. package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
  20. package/types/dist/lib/helpers/api_builder/add_api.d.mts +60 -0
  21. package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +1 -0
  22. package/types/dist/lib/helpers/api_builder/analysis.d.mts +189 -0
  23. package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +1 -0
  24. package/types/dist/lib/helpers/api_builder/construction.d.mts +107 -0
  25. package/types/dist/lib/helpers/api_builder/construction.d.mts.map +1 -0
  26. package/types/dist/lib/helpers/api_builder/decisions.d.mts +213 -0
  27. package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +1 -0
  28. package/types/dist/lib/helpers/api_builder.d.mts +5 -448
  29. package/types/dist/lib/helpers/api_builder.d.mts.map +1 -1
  30. package/types/dist/lib/helpers/utilities.d.mts +120 -0
  31. package/types/dist/lib/helpers/utilities.d.mts.map +1 -0
  32. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +7 -0
  33. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  34. package/types/dist/lib/runtime/runtime-livebindings.d.mts +8 -0
  35. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  36. package/types/dist/slothlet.d.mts +23 -13
  37. package/types/dist/slothlet.d.mts.map +1 -1
  38. package/types/index.d.mts +0 -1
  39. package/API-RULES-CONDITIONS.md +0 -367
@@ -18,1575 +18,24 @@
18
18
 
19
19
 
20
20
 
21
- import fs from "node:fs/promises";
22
- import path from "node:path";
23
- import { types as utilTypes } from "node:util";
24
- import { pathToFileURL } from "node:url";
25
- import { multidefault_analyzeModules } from "@cldmv/slothlet/helpers/multidefault";
26
- import { setActiveInstance } from "@cldmv/slothlet/helpers/instance-manager";
27
21
 
22
+ export {
23
+ isLikelySerializable,
24
+ analyzeModule,
25
+ processModuleFromAnalysis,
26
+ analyzeDirectoryStructure,
27
+ getCategoryBuildingDecisions
28
+ } from "@cldmv/slothlet/helpers/api_builder/analysis";
28
29
 
30
+ export {
31
+ getFlatteningDecision,
32
+ applyFunctionNamePreference,
33
+ processModuleForAPI,
34
+ buildCategoryDecisions
35
+ } from "@cldmv/slothlet/helpers/api_builder/decisions";
29
36
 
37
+ export { buildCategoryStructure, buildRootAPI, toapiPathKey, shouldIncludeFile } from "@cldmv/slothlet/helpers/api_builder/construction";
30
38
 
39
+ export { safeDefine, deepMerge, mutateLiveBindingFunction } from "@cldmv/slothlet/helpers/utilities";
31
40
 
32
-
33
- function isLikelySerializable(val) {
34
- const type = typeof val;
35
-
36
-
37
- if (type !== "object" || val === null) {
38
- return type === "string" || type === "number" || type === "boolean" || type === "undefined";
39
- }
40
-
41
-
42
- return (
43
- Array.isArray(val) || val instanceof Date || val instanceof RegExp || val?.constructor === Object || typeof val.toJSON === "function"
44
- );
45
- }
46
-
47
-
48
-
49
-
50
-
51
-
52
- export async function analyzeModule(modulePath, options = {}) {
53
- const { debug = false, instance = null } = options;
54
-
55
- const moduleUrl = pathToFileURL(modulePath).href;
56
-
57
-
58
- let importUrl = moduleUrl;
59
- if (instance && instance.instanceId) {
60
- const runtimeType = instance.config?.runtime || "async";
61
-
62
-
63
- if (runtimeType === "live") {
64
- const separator = moduleUrl.includes("?") ? "&" : "?";
65
- importUrl = `${moduleUrl}${separator}slothlet_instance=${instance.instanceId}`;
66
- importUrl = `${importUrl}&slothlet_runtime=${runtimeType}`;
67
-
68
-
69
- setActiveInstance(instance.instanceId);
70
- }
71
-
72
-
73
- }
74
-
75
- const rawModule = await import(importUrl);
76
-
77
-
78
- let processedModule = rawModule;
79
- const isCjs = modulePath.endsWith(".cjs") && "default" in rawModule;
80
-
81
- if (isCjs) {
82
- processedModule = rawModule.default;
83
- }
84
-
85
- const hasDefault = !!processedModule.default;
86
- const isFunction = typeof processedModule.default === "function";
87
- const exports = Object.entries(processedModule);
88
- const namedExports = Object.entries(processedModule).filter(([k]) => k !== "default");
89
-
90
-
91
- let defaultExportType = null;
92
- if (hasDefault) {
93
- defaultExportType = typeof processedModule.default === "function" ? "function" : "object";
94
- }
95
-
96
-
97
- let shouldWrapAsCallable = false;
98
-
99
-
100
- if (
101
- hasDefault &&
102
- typeof processedModule.default === "object" &&
103
- processedModule.default !== null &&
104
- typeof processedModule.default.default === "function"
105
- ) {
106
- shouldWrapAsCallable = true;
107
- }
108
-
109
-
110
- if (!shouldWrapAsCallable) {
111
- for (const [_, exportValue] of namedExports) {
112
- if (typeof exportValue === "object" && exportValue !== null && typeof exportValue.default === "function") {
113
- shouldWrapAsCallable = true;
114
- break;
115
- }
116
- }
117
- }
118
-
119
- if (debug) {
120
- console.log(`[DEBUG] analyzeModule(${path.basename(modulePath)}):`, {
121
- isCjs,
122
- hasDefault,
123
- isFunction,
124
- defaultExportType,
125
- shouldWrapAsCallable,
126
- namedExportsCount: namedExports.length
127
- });
128
- }
129
-
130
- return {
131
- rawModule,
132
- processedModule,
133
- isFunction,
134
- hasDefault,
135
- isCjs,
136
- exports,
137
- defaultExportType,
138
- shouldWrapAsCallable,
139
- namedExports,
140
- metadata: { modulePath }
141
- };
142
- }
143
-
144
-
145
- export function processModuleFromAnalysis(analysis, options = {}) {
146
- const { instance, debug = false } = options;
147
- const { processedModule, isFunction, hasDefault, shouldWrapAsCallable, namedExports } = analysis;
148
-
149
- if (!instance) {
150
- throw new Error("processModuleFromAnalysis requires instance parameter for _toapiPathKey access");
151
- }
152
-
153
-
154
- if (isFunction) {
155
- let fn = processedModule.default;
156
-
157
-
158
- if (hasDefault) {
159
- try {
160
- Object.defineProperty(fn, "__slothletDefault", {
161
- value: true,
162
- writable: false,
163
- enumerable: false,
164
- configurable: true
165
- });
166
- } catch {
167
-
168
- }
169
- }
170
-
171
-
172
- for (const [exportName, exportValue] of Object.entries(processedModule)) {
173
- if (exportName !== "default") {
174
- fn[instance._toapiPathKey(exportName)] = exportValue;
175
- }
176
- }
177
- return fn;
178
- }
179
-
180
-
181
- if (shouldWrapAsCallable) {
182
- let callableObject = null;
183
- let objectName = "callable";
184
-
185
-
186
- if (
187
- hasDefault &&
188
- typeof processedModule.default === "object" &&
189
- processedModule.default !== null &&
190
- typeof processedModule.default.default === "function"
191
- ) {
192
- callableObject = processedModule.default;
193
- objectName = processedModule.default.name || (namedExports[0] && namedExports[0][0] !== "default" ? namedExports[0][0] : "callable");
194
- } else {
195
-
196
- for (const [exportName, exportValue] of namedExports) {
197
- if (typeof exportValue === "object" && exportValue !== null && typeof exportValue.default === "function") {
198
- callableObject = exportValue;
199
- objectName = exportName;
200
- break;
201
- }
202
- }
203
- }
204
-
205
- if (callableObject) {
206
- const callableApi = {
207
- [objectName]: function (...args) {
208
- return callableObject.default.apply(callableObject, args);
209
- }
210
- }[objectName];
211
-
212
-
213
- for (const [methodName, method] of Object.entries(callableObject)) {
214
- if (methodName === "default") continue;
215
- callableApi[methodName] = method;
216
- }
217
-
218
- if (debug) {
219
- console.log(`[DEBUG] Created callable wrapper for ${objectName}`);
220
- }
221
-
222
- return callableApi;
223
- }
224
- }
225
-
226
-
227
- if (hasDefault && typeof processedModule.default === "object") {
228
- const obj = processedModule.default;
229
-
230
-
231
- const namedExportsToAdd = Object.entries(processedModule).filter(
232
- ([exportName, exportValue]) => exportName !== "default" && exportValue !== obj
233
- );
234
-
235
- if (namedExportsToAdd.length > 0) {
236
-
237
-
238
- const isCustomProxy = utilTypes?.isProxy?.(obj) ?? false;
239
-
240
- if (isCustomProxy) {
241
-
242
-
243
- for (const [exportName, exportValue] of namedExportsToAdd) {
244
- const apiKey = instance._toapiPathKey(exportName);
245
- obj[apiKey] = exportValue;
246
- }
247
-
248
-
249
-
250
- const proxyWithStructure = obj;
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
- proxyWithStructure.default = obj;
272
-
273
-
274
- if (!proxyWithStructure.toJSON) {
275
- Object.defineProperty(proxyWithStructure, "toJSON", {
276
- value: function () {
277
-
278
- const serializable = {};
279
-
280
-
281
- for (const key of Reflect.ownKeys(this)) {
282
-
283
- if (typeof key !== "string") continue;
284
-
285
- const descriptor = Reflect.getOwnPropertyDescriptor(this, key);
286
- if (!descriptor || !descriptor.enumerable) continue;
287
-
288
- if (key === "default") {
289
-
290
- continue;
291
- }
292
-
293
- const value = this[key];
294
- if (typeof value === "function") {
295
- serializable[key] = "[Function]";
296
- } else if (isLikelySerializable(value)) {
297
-
298
- serializable[key] = value;
299
- } else {
300
-
301
- try {
302
- JSON.stringify(value);
303
- serializable[key] = value;
304
- } catch {
305
- serializable[key] = "[Non-serializable value]";
306
- }
307
- }
308
- }
309
-
310
-
311
- serializable._slothlet_proxy_info = {
312
- type: "proxy",
313
- circular_reference: "Property .default points to this object (excluded from serialization)",
314
- warning: "This is a slothlet API proxy with circular .default reference"
315
- };
316
- return serializable;
317
- },
318
- writable: false,
319
- enumerable: false,
320
- configurable: true
321
- });
322
- }
323
-
324
-
325
- for (const [exportName, exportValue] of namedExportsToAdd) {
326
- const apiKey = instance._toapiPathKey(exportName);
327
- if (!(apiKey in proxyWithStructure)) {
328
- proxyWithStructure[apiKey] = exportValue;
329
- }
330
- }
331
-
332
- return proxyWithStructure;
333
- } else {
334
-
335
- for (const [exportName, exportValue] of namedExportsToAdd) {
336
- obj[instance._toapiPathKey(exportName)] = exportValue;
337
- }
338
-
339
- return obj;
340
- }
341
- }
342
-
343
- return obj;
344
- }
345
-
346
-
347
- if (namedExports.length > 0) {
348
- const apiExport = {};
349
- for (const [exportName, exportValue] of namedExports) {
350
- apiExport[instance._toapiPathKey(exportName)] = exportValue;
351
- }
352
- return apiExport;
353
- }
354
-
355
-
356
- throw new Error(`No valid exports found in processed module`);
357
- }
358
-
359
-
360
- export async function analyzeDirectoryStructure(categoryPath, options = {}) {
361
- const { instance, currentDepth = 0, debug = false } = options;
362
-
363
- if (!instance || typeof instance._toapiPathKey !== "function") {
364
- throw new Error("analyzeDirectoryStructure requires a valid slothlet instance");
365
- }
366
-
367
- const files = await fs.readdir(categoryPath, { withFileTypes: true });
368
- const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
369
- const categoryName = instance._toapiPathKey(path.basename(categoryPath));
370
- const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
371
-
372
-
373
- let processingStrategy;
374
- if (moduleFiles.length === 0) {
375
- processingStrategy = "empty";
376
- } else if (moduleFiles.length === 1 && subDirs.length === 0) {
377
- processingStrategy = "single-file";
378
- } else {
379
- processingStrategy = "multi-file";
380
- }
381
-
382
-
383
- let multiDefaultAnalysis = null;
384
- if (processingStrategy === "multi-file") {
385
- multiDefaultAnalysis = await multidefault_analyzeModules(moduleFiles, categoryPath, { debug, instance });
386
- }
387
-
388
-
389
- const flatteningHints = {
390
- shouldFlattenSingleFile: processingStrategy === "single-file",
391
- shouldFlattenToParent: currentDepth > 0 && processingStrategy === "single-file",
392
- hasMultipleDefaults: multiDefaultAnalysis?.hasMultipleDefaultExports || false,
393
- selfReferentialFiles: multiDefaultAnalysis?.selfReferentialFiles || new Set()
394
- };
395
-
396
- if (debug) {
397
- console.log(`[DEBUG] analyzeDirectoryStructure(${categoryName}):`, {
398
- processingStrategy,
399
- moduleCount: moduleFiles.length,
400
- subDirCount: subDirs.length,
401
- hasMultipleDefaults: flatteningHints.hasMultipleDefaults
402
- });
403
- }
404
-
405
- return {
406
- isSingleFile: processingStrategy === "single-file",
407
- shouldAutoFlatten: flatteningHints.shouldFlattenSingleFile,
408
- categoryName,
409
- moduleFiles,
410
- subDirs,
411
- multiDefaultAnalysis,
412
- processingStrategy,
413
- flatteningHints
414
- };
415
- }
416
-
417
-
418
- export async function getCategoryBuildingDecisions(categoryPath, options = {}) {
419
- const { instance, currentDepth = 0, maxDepth = Infinity, debug = false } = options;
420
-
421
- if (!instance) {
422
- throw new Error("getCategoryBuildingDecisions requires a valid slothlet instance");
423
- }
424
-
425
- const analysis = await analyzeDirectoryStructure(categoryPath, { instance, currentDepth, maxDepth, debug });
426
- const { processingStrategy, categoryName, moduleFiles, subDirs, multiDefaultAnalysis } = analysis;
427
-
428
- const processedModules = [];
429
- const subDirectories = [];
430
-
431
-
432
- if (processingStrategy !== "empty") {
433
- for (const file of moduleFiles) {
434
- const moduleExt = path.extname(file.name);
435
- const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
436
- const modulePath = path.join(categoryPath, file.name);
437
-
438
-
439
- const analysis = await analyzeModule(modulePath, {
440
- debug,
441
- instance
442
- });
443
- const processedModule = processModuleFromAnalysis(analysis, {
444
- debug,
445
- instance
446
- });
447
-
448
-
449
- const flatteningInfo = {
450
- shouldFlatten: false,
451
- apiPathKey: moduleName,
452
- reason: "default"
453
- };
454
-
455
-
456
- if (processingStrategy === "single-file") {
457
-
458
- const functionNameMatchesFolder =
459
- typeof processedModule === "function" &&
460
- processedModule.name &&
461
- processedModule.name.toLowerCase() === categoryName.toLowerCase();
462
-
463
- const moduleNameMatchesCategory = moduleName === categoryName && typeof processedModule === "function" && currentDepth > 0;
464
-
465
- if (functionNameMatchesFolder && currentDepth > 0) {
466
- flatteningInfo.shouldFlatten = true;
467
- flatteningInfo.apiPathKey = processedModule.name;
468
- flatteningInfo.reason = "function name matches folder";
469
- } else if (moduleNameMatchesCategory) {
470
- flatteningInfo.shouldFlatten = true;
471
- flatteningInfo.apiPathKey = categoryName;
472
- flatteningInfo.reason = "module name matches category";
473
- }
474
- }
475
-
476
- processedModules.push({
477
- file,
478
- moduleName,
479
- processedModule,
480
- flattening: flatteningInfo
481
- });
482
- }
483
- }
484
-
485
-
486
- for (const subDirEntry of subDirs) {
487
- if (currentDepth < maxDepth) {
488
- const apiPathKey = instance._toapiPathKey(subDirEntry.name);
489
- subDirectories.push({ dirEntry: subDirEntry, apiPathKey });
490
- }
491
- }
492
-
493
-
494
- const upwardFlatteningCandidate = { shouldFlatten: false, apiPathKey: null };
495
- if (processedModules.length === 1 && subDirectories.length === 0) {
496
- const single = processedModules[0];
497
- if (single.moduleName === categoryName) {
498
- upwardFlatteningCandidate.shouldFlatten = true;
499
- upwardFlatteningCandidate.apiPathKey = single.moduleName;
500
- }
501
- }
502
-
503
- if (debug) {
504
- console.log(`[DEBUG] getCategoryBuildingDecisions(${categoryName}):`, {
505
- processingStrategy,
506
- moduleCount: processedModules.length,
507
- subDirCount: subDirectories.length,
508
- upwardFlattening: upwardFlatteningCandidate.shouldFlatten
509
- });
510
- }
511
-
512
- return {
513
- processingStrategy,
514
- categoryName,
515
- shouldFlattenSingle: processingStrategy === "single-file",
516
- processedModules,
517
- subDirectories,
518
- multiDefaultAnalysis,
519
- flatteningDecisions: analysis.flatteningHints,
520
- upwardFlatteningCandidate
521
- };
522
- }
523
-
524
-
525
- export function getFlatteningDecision(options) {
526
- const {
527
- mod,
528
- fileName,
529
- apiPathKey,
530
- hasMultipleDefaultExports,
531
- isSelfReferential,
532
-
533
-
534
- moduleHasDefault = !!mod.default,
535
- categoryName,
536
-
537
- totalModules = 1
538
- } = options;
539
-
540
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
541
-
542
-
543
- if (isSelfReferential) {
544
- return {
545
- shouldFlatten: false,
546
- flattenToRoot: false,
547
- flattenToCategory: false,
548
- preserveAsNamespace: true,
549
- useAutoFlattening: false,
550
- reason: "self-referential export"
551
- };
552
- }
553
-
554
-
555
- if (hasMultipleDefaultExports) {
556
- if (moduleHasDefault) {
557
-
558
- return {
559
- shouldFlatten: false,
560
- flattenToRoot: false,
561
- flattenToCategory: false,
562
- preserveAsNamespace: true,
563
- useAutoFlattening: false,
564
- reason: "multi-default context with default export"
565
- };
566
- } else {
567
-
568
- return {
569
- shouldFlatten: true,
570
- flattenToRoot: true,
571
- flattenToCategory: true,
572
- preserveAsNamespace: false,
573
- useAutoFlattening: false,
574
- reason: "multi-default context without default export"
575
- };
576
- }
577
- }
578
-
579
-
580
- if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
581
- return {
582
- shouldFlatten: true,
583
- flattenToRoot: false,
584
- flattenToCategory: false,
585
- preserveAsNamespace: false,
586
- useAutoFlattening: true,
587
- reason: "auto-flatten single named export matching filename"
588
- };
589
- }
590
-
591
-
592
- if (categoryName && fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0) {
593
- return {
594
- shouldFlatten: true,
595
- flattenToRoot: false,
596
- flattenToCategory: true,
597
- preserveAsNamespace: false,
598
- useAutoFlattening: false,
599
- reason: "filename matches container, flatten to category"
600
- };
601
- }
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
- return {
619
- shouldFlatten: false,
620
- flattenToRoot: false,
621
- flattenToCategory: false,
622
- preserveAsNamespace: true,
623
- useAutoFlattening: false,
624
- reason: "traditional namespace preservation"
625
- };
626
- }
627
-
628
-
629
- export function processModuleForAPI(options) {
630
- const {
631
- mod,
632
- fileName,
633
- apiPathKey,
634
- hasMultipleDefaultExports,
635
- isSelfReferential,
636
- api,
637
- getRootDefault,
638
- setRootDefault,
639
- context = {},
640
- originalAnalysis = null
641
- } = options;
642
-
643
- const { debug = false, mode = "unknown", categoryName, totalModules = 1 } = context;
644
-
645
- let processed = false;
646
- let rootDefaultSet = false;
647
- let flattened = false;
648
- let namespaced = false;
649
- const apiAssignments = {};
650
-
651
-
652
-
653
-
654
- const hasDefaultFunction = (mod && typeof mod.default === "function") || (mod && typeof mod === "function" && !mod.default);
655
-
656
-
657
- const defaultFunction = mod?.default || (typeof mod === "function" ? mod : null);
658
-
659
- if (hasDefaultFunction) {
660
- processed = true;
661
-
662
- if (hasMultipleDefaultExports && !isSelfReferential) {
663
-
664
- apiAssignments[apiPathKey] = mod;
665
- namespaced = true;
666
-
667
-
668
-
669
-
670
- if (debug) {
671
- console.log(
672
- `[DEBUG] ${mode}: Multi-default function - using filename '${apiPathKey}' for default export, mod type: ${typeof mod}, function name: ${defaultFunction?.name}`
673
- );
674
- }
675
- } else if (isSelfReferential) {
676
-
677
- apiAssignments[apiPathKey] = mod;
678
- namespaced = true;
679
-
680
- if (debug) {
681
- console.log(`[DEBUG] ${mode}: Self-referential function - preserving ${fileName} as namespace`);
682
- }
683
- } else {
684
-
685
- if (debug) {
686
- console.log(
687
- `[DEBUG] ${mode}: Processing traditional default function: hasMultipleDefaultExports=${hasMultipleDefaultExports}, rootDefaultFunction=${!!(getRootDefault && getRootDefault())}`
688
- );
689
- }
690
-
691
-
692
- if (mode === "root" && getRootDefault && setRootDefault && !hasMultipleDefaultExports && !getRootDefault()) {
693
- setRootDefault(defaultFunction);
694
- rootDefaultSet = true;
695
-
696
- if (debug) {
697
- console.log(`[DEBUG] ${mode}: Set rootDefaultFunction to:`, defaultFunction.name);
698
- }
699
-
700
-
701
-
702
- } else {
703
-
704
- apiAssignments[apiPathKey] = mod;
705
- namespaced = true;
706
-
707
-
708
-
709
- }
710
- }
711
- } else {
712
-
713
- processed = true;
714
-
715
- if (debug) {
716
- console.log(`[DEBUG] ${mode}: Processing non-function or named-only exports for ${fileName}`);
717
- }
718
-
719
-
720
- const decision = getFlatteningDecision({
721
- mod,
722
- fileName,
723
- apiPathKey,
724
- hasMultipleDefaultExports,
725
- isSelfReferential,
726
-
727
-
728
-
729
- moduleHasDefault: originalAnalysis ? originalAnalysis.hasDefault : !!mod.default,
730
- categoryName,
731
- totalModules,
732
- debug
733
- });
734
-
735
- if (debug) {
736
- console.log(`[DEBUG] ${mode}: Flattening decision for ${fileName}: ${decision.reason}`);
737
- }
738
-
739
- if (decision.useAutoFlattening) {
740
-
741
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
742
- apiAssignments[apiPathKey] = mod[moduleKeys[0]];
743
- flattened = true;
744
- } else if (decision.flattenToRoot || decision.flattenToCategory) {
745
-
746
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
747
- for (const key of moduleKeys) {
748
- apiAssignments[key] = mod[key];
749
- if (debug) {
750
- console.log(`[DEBUG] ${mode}: Flattened ${fileName}.${key} to ${decision.flattenToRoot ? "root" : "category"}.${key}`);
751
- }
752
- }
753
- flattened = true;
754
- } else if (isSelfReferential) {
755
-
756
- apiAssignments[apiPathKey] = mod[apiPathKey] || mod;
757
- namespaced = true;
758
- } else {
759
-
760
- apiAssignments[apiPathKey] = mod;
761
- namespaced = true;
762
- }
763
- }
764
-
765
-
766
- for (const [key, value] of Object.entries(apiAssignments)) {
767
- if (debug && key && typeof value === "function" && value.name) {
768
- console.log(`[DEBUG] ${mode}: Assigning key '${key}' to function '${value.name}'`);
769
- }
770
- api[key] = value;
771
- }
772
-
773
- return {
774
- processed,
775
- rootDefaultSet,
776
- flattened,
777
- namespaced,
778
- apiAssignments
779
- };
780
- }
781
-
782
-
783
- export function applyFunctionNamePreference(options) {
784
- const { mod, fileName, apiPathKey, categoryModules, toapiPathKey, debug = false } = options;
785
-
786
- let hasPreferredName = false;
787
- let preferredKey = apiPathKey;
788
-
789
-
790
- for (const [, exportValue] of Object.entries(mod)) {
791
- if (typeof exportValue === "function" && exportValue.name) {
792
- const functionNameLower = exportValue.name.toLowerCase();
793
- const filenameLower = fileName.toLowerCase();
794
-
795
-
796
- if (functionNameLower === filenameLower && exportValue.name !== apiPathKey) {
797
-
798
- preferredKey = exportValue.name;
799
- hasPreferredName = true;
800
-
801
- if (debug) {
802
- console.log(`[DEBUG] Using function name preference: ${exportValue.name} instead of ${apiPathKey} for ${fileName}`);
803
- }
804
- break;
805
- }
806
-
807
-
808
- const sanitizedFunctionName = toapiPathKey(exportValue.name);
809
- if (sanitizedFunctionName.toLowerCase() === apiPathKey.toLowerCase() && exportValue.name !== apiPathKey) {
810
- preferredKey = exportValue.name;
811
- hasPreferredName = true;
812
-
813
- if (debug) {
814
- console.log(`[DEBUG] Using function name preference: ${exportValue.name} instead of ${apiPathKey} for ${fileName}`);
815
- }
816
- break;
817
- }
818
- }
819
- }
820
-
821
- if (hasPreferredName) {
822
-
823
- categoryModules[preferredKey] = mod;
824
- }
825
-
826
- return { hasPreferredName, preferredKey };
827
- }
828
-
829
-
830
- export async function buildCategoryStructure(categoryPath, options = {}) {
831
- const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler, instance } = options;
832
-
833
- if (!instance || typeof instance._toapiPathKey !== "function" || typeof instance._shouldIncludeFile !== "function") {
834
- throw new Error("buildCategoryStructure requires a valid slothlet instance");
835
- }
836
-
837
- const debug = instance.config?.debug || false;
838
-
839
- if (debug) {
840
- console.log(`[DEBUG] buildCategoryStructure called with path: ${categoryPath}, mode: ${mode}, depth: ${currentDepth}`);
841
- }
842
-
843
- const files = await fs.readdir(categoryPath, { withFileTypes: true });
844
- const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
845
- const categoryName = instance._toapiPathKey(path.basename(categoryPath));
846
- const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
847
-
848
-
849
- if (moduleFiles.length === 1 && subDirs.length === 0) {
850
- const moduleExt = path.extname(moduleFiles[0].name);
851
- const moduleName = instance._toapiPathKey(path.basename(moduleFiles[0].name, moduleExt));
852
-
853
-
854
- const analysis = await analyzeModule(path.join(categoryPath, moduleFiles[0].name), {
855
- debug,
856
- instance
857
- });
858
-
859
-
860
- const mod = processModuleFromAnalysis(analysis, { instance, debug });
861
-
862
-
863
- const functionNameMatchesFolder = typeof mod === "function" && mod.name && mod.name.toLowerCase() === categoryName.toLowerCase();
864
-
865
- const functionNameMatchesFilename =
866
- typeof mod === "function" &&
867
- mod.name &&
868
- instance._toapiPathKey(mod.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
869
- mod.name !== instance._toapiPathKey(moduleName);
870
-
871
-
872
-
873
-
874
-
875
-
876
-
877
- if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
878
- try {
879
- Object.defineProperty(mod, "name", { value: categoryName, configurable: true });
880
- } catch {
881
-
882
- }
883
- return mod;
884
- }
885
-
886
-
887
- if (moduleName === categoryName && mod && typeof mod === "object" && !Array.isArray(mod) && currentDepth > 0) {
888
- if (debug) {
889
- console.log(`[DEBUG] Single-file auto-flattening: ${categoryName}/${moduleFiles[0].name} -> flatten object contents`);
890
- }
891
-
892
-
893
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
894
- if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
895
- return mod[moduleName];
896
- }
897
-
898
-
899
-
900
-
901
-
902
-
903
-
904
- if (moduleKeys.length > 1) {
905
- if (debug) {
906
- console.log(`[DEBUG] Default export flattening: ${categoryName}/${moduleFiles[0].name} -> flatten default object contents`);
907
- }
908
- return mod;
909
- }
910
- return mod;
911
- }
912
-
913
-
914
- if (moduleFiles.length === 1 && currentDepth > 0 && mod && typeof mod === "object" && !Array.isArray(mod)) {
915
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
916
- const fileName = moduleFiles[0].name.replace(/\.(mjs|cjs|js)$/, "");
917
-
918
-
919
- const isGenericFilename = ["singlefile", "index", "main", "default"].includes(fileName.toLowerCase());
920
-
921
-
922
- if (moduleKeys.length === 1 && isGenericFilename) {
923
- if (debug) {
924
- console.log(
925
- `[DEBUG] Single-file parent-level auto-flattening: ${categoryName}/${moduleFiles[0].name} -> flatten to parent level`
926
- );
927
- }
928
- const exportValue = mod[moduleKeys[0]];
929
- return { [moduleKeys[0]]: exportValue };
930
- }
931
- }
932
-
933
-
934
- if (functionNameMatchesFolder && currentDepth > 0) {
935
- try {
936
- Object.defineProperty(mod, "name", { value: mod.name, configurable: true });
937
- } catch {
938
-
939
- }
940
- return mod;
941
- }
942
-
943
-
944
- if (functionNameMatchesFilename) {
945
- return { [mod.name]: mod };
946
- }
947
-
948
-
949
- if (typeof mod === "function" && (!mod.name || mod.name === "default" || mod.__slothletDefault === true) && currentDepth > 0) {
950
- try {
951
- Object.defineProperty(mod, "name", { value: categoryName, configurable: true });
952
- } catch {
953
-
954
- }
955
- return mod;
956
- }
957
-
958
-
959
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
960
- if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
961
- return mod[moduleName];
962
- }
963
-
964
-
965
- return { [moduleName]: mod };
966
- }
967
-
968
-
969
- const categoryModules = {};
970
-
971
-
972
- const analysis = await multidefault_analyzeModules(moduleFiles, categoryPath, { debug, instance });
973
- const { totalDefaultExports, hasMultipleDefaultExports, selfReferentialFiles, defaultExportFiles: analysisDefaults } = analysis;
974
-
975
-
976
- const defaultExportFiles = [];
977
- for (const { fileName } of analysisDefaults) {
978
- const file = moduleFiles.find((f) => path.basename(f.name, path.extname(f.name)) === fileName);
979
- if (file) {
980
- const analysis = await analyzeModule(path.join(categoryPath, file.name), {
981
- debug,
982
- instance
983
- });
984
- const processedMod = processModuleFromAnalysis(analysis, {
985
- debug,
986
- instance
987
- });
988
- defaultExportFiles.push({ file, moduleName: instance._toapiPathKey(fileName), mod: processedMod, analysis });
989
- }
990
- }
991
-
992
- if (debug) {
993
- console.log(`[DEBUG] buildCategoryStructure: Multi-default analysis results`);
994
- console.log(`[DEBUG] - totalDefaultExports: ${totalDefaultExports}`);
995
- console.log(`[DEBUG] - hasMultipleDefaultExports: ${hasMultipleDefaultExports}`);
996
- console.log(`[DEBUG] - selfReferentialFiles: ${Array.from(selfReferentialFiles)}`);
997
- }
998
-
999
-
1000
- for (const file of moduleFiles) {
1001
- const moduleExt = path.extname(file.name);
1002
- const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
1003
- const fileName = path.basename(file.name, moduleExt);
1004
- const apiPathKey = instance._toapiPathKey(fileName);
1005
-
1006
-
1007
- let mod = null;
1008
- let analysis = null;
1009
- const existingDefault = defaultExportFiles.find((def) => def.moduleName === moduleName);
1010
- if (existingDefault) {
1011
- mod = existingDefault.mod;
1012
- analysis = existingDefault.analysis;
1013
- } else {
1014
- analysis = await analyzeModule(path.join(categoryPath, file.name), {
1015
- debug,
1016
- instance
1017
- });
1018
- mod = processModuleFromAnalysis(analysis, {
1019
- debug,
1020
- instance
1021
- });
1022
- }
1023
-
1024
-
1025
- processModuleForAPI({
1026
- mod,
1027
- fileName,
1028
- apiPathKey,
1029
- hasMultipleDefaultExports,
1030
- isSelfReferential: selfReferentialFiles.has(moduleName),
1031
- api: categoryModules,
1032
- getRootDefault: () => null,
1033
- setRootDefault: () => {},
1034
- context: {
1035
- debug,
1036
- mode: "category",
1037
- categoryName,
1038
- totalModules: moduleFiles.length
1039
- },
1040
- originalAnalysis: analysis
1041
- });
1042
- }
1043
-
1044
-
1045
- for (const subDirEntry of subDirs) {
1046
- if (currentDepth < maxDepth) {
1047
- const key = instance._toapiPathKey(subDirEntry.name);
1048
- const subDirPath = path.join(categoryPath, subDirEntry.name);
1049
- let subModule;
1050
-
1051
- if (mode === "lazy" && typeof subdirHandler === "function") {
1052
-
1053
- subModule = subdirHandler({
1054
- subDirEntry,
1055
- subDirPath,
1056
- key,
1057
- categoryModules,
1058
- currentDepth,
1059
- maxDepth
1060
- });
1061
- } else {
1062
-
1063
- subModule = await buildCategoryStructure(subDirPath, {
1064
- currentDepth: currentDepth + 1,
1065
- maxDepth,
1066
- mode: "eager",
1067
- instance
1068
- });
1069
- }
1070
-
1071
-
1072
-
1073
- if (
1074
- typeof subModule === "function" &&
1075
- subModule.name &&
1076
- subModule.name.toLowerCase() === key.toLowerCase() &&
1077
- subModule.name !== key
1078
- ) {
1079
- categoryModules[subModule.name] = subModule;
1080
- } else {
1081
- categoryModules[key] = subModule;
1082
- }
1083
- }
1084
- }
1085
-
1086
- return categoryModules;
1087
- }
1088
-
1089
-
1090
- export async function buildRootAPI(dir, options = {}) {
1091
- const { lazy = false, maxDepth = Infinity, instance } = options;
1092
-
1093
- if (!instance || typeof instance._shouldIncludeFile !== "function" || typeof instance._loadCategory !== "function") {
1094
- throw new Error("buildRootAPI requires a valid slothlet instance");
1095
- }
1096
-
1097
- const debug = instance.config?.debug || false;
1098
-
1099
- if (debug) {
1100
- console.log(`[DEBUG] buildRootAPI called with dir: ${dir}, lazy: ${lazy}, maxDepth: ${maxDepth}`);
1101
- }
1102
-
1103
- const entries = await fs.readdir(dir, { withFileTypes: true });
1104
- const api = {};
1105
- let rootDefaultFunction = null;
1106
-
1107
-
1108
- const moduleFiles = entries.filter((e) => instance._shouldIncludeFile(e));
1109
-
1110
- if (moduleFiles.length > 0) {
1111
-
1112
- const analysis = await multidefault_analyzeModules(moduleFiles, dir, { debug, instance });
1113
- const { hasMultipleDefaultExports, selfReferentialFiles } = analysis;
1114
-
1115
-
1116
- for (const entry of moduleFiles) {
1117
- const ext = path.extname(entry.name);
1118
- const fileName = path.basename(entry.name, ext);
1119
- const apiPathKey = instance._toapiPathKey(fileName);
1120
-
1121
- const analysis = await analyzeModule(path.join(dir, entry.name), {
1122
- debug,
1123
- instance
1124
- });
1125
- const mod = processModuleFromAnalysis(analysis, {
1126
- debug,
1127
- instance
1128
- });
1129
-
1130
-
1131
- processModuleForAPI({
1132
- mod,
1133
- fileName,
1134
- apiPathKey,
1135
- hasMultipleDefaultExports,
1136
- isSelfReferential: selfReferentialFiles.has(fileName),
1137
- api,
1138
- getRootDefault: () => rootDefaultFunction,
1139
- setRootDefault: (fn) => {
1140
- rootDefaultFunction = fn;
1141
- },
1142
- context: {
1143
- debug,
1144
- mode: "root",
1145
- totalModules: moduleFiles.length
1146
- }
1147
- });
1148
- }
1149
- }
1150
-
1151
-
1152
- for (const entry of entries) {
1153
- if (entry.isDirectory() && !entry.name.startsWith(".")) {
1154
- const categoryPath = path.join(dir, entry.name);
1155
-
1156
- if (lazy) {
1157
-
1158
- api[instance._toapiPathKey(entry.name)] = await instance._loadCategory(categoryPath, 0, maxDepth);
1159
- } else {
1160
-
1161
- api[instance._toapiPathKey(entry.name)] = await buildCategoryStructure(categoryPath, {
1162
- currentDepth: 1,
1163
- maxDepth,
1164
- mode: "eager",
1165
- instance
1166
- });
1167
- }
1168
- }
1169
- }
1170
-
1171
-
1172
- let finalApi;
1173
- if (debug) {
1174
- console.log(`[DEBUG] Final assembly: rootDefaultFunction=${!!rootDefaultFunction}`);
1175
- console.log(`[DEBUG] API object keys before final assembly:`, Object.keys(api));
1176
- }
1177
-
1178
- if (rootDefaultFunction) {
1179
-
1180
- Object.assign(rootDefaultFunction, api);
1181
- finalApi = rootDefaultFunction;
1182
-
1183
- if (debug) {
1184
- console.log(`[DEBUG] Applied root contributor pattern - final API is function`);
1185
- }
1186
- } else {
1187
-
1188
- finalApi = api;
1189
-
1190
- if (debug) {
1191
- console.log(`[DEBUG] No root function - final API is object`);
1192
- }
1193
- }
1194
-
1195
- return finalApi;
1196
- }
1197
-
1198
-
1199
- export async function buildCategoryDecisions(categoryPath, options = {}) {
1200
- const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler } = options;
1201
- const { instance } = options;
1202
-
1203
- if (!instance || typeof instance._toapiPathKey !== "function") {
1204
- throw new Error("buildCategoryDecisions requires instance parameter with _toapiPathKey method");
1205
- }
1206
-
1207
- const debug = instance.config?.debug || false;
1208
-
1209
-
1210
- if (debug) {
1211
- console.log(`[DEBUG] buildCategoryDecisions called with path: ${categoryPath}, mode: ${mode}`);
1212
- }
1213
-
1214
- const files = await fs.readdir(categoryPath, { withFileTypes: true });
1215
- const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
1216
- const categoryName = instance._toapiPathKey(path.basename(categoryPath));
1217
- const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
1218
-
1219
- const decisions = {
1220
- type: null,
1221
- categoryName,
1222
- moduleFiles,
1223
- subDirs,
1224
- currentDepth,
1225
- maxDepth,
1226
- mode,
1227
- subdirHandler,
1228
-
1229
- singleFile: null,
1230
- shouldFlatten: false,
1231
- flattenType: null,
1232
- preferredName: null,
1233
-
1234
- multifileAnalysis: null,
1235
- processedModules: [],
1236
- categoryModules: {},
1237
-
1238
- subdirectoryDecisions: []
1239
- };
1240
-
1241
-
1242
- if (moduleFiles.length === 1 && subDirs.length === 0) {
1243
- decisions.type = "single-file";
1244
- const moduleFile = moduleFiles[0];
1245
- const moduleExt = path.extname(moduleFile.name);
1246
- const moduleName = instance._toapiPathKey(path.basename(moduleFile.name, moduleExt));
1247
-
1248
- decisions.singleFile = {
1249
- file: moduleFile,
1250
- moduleName,
1251
- moduleExt
1252
- };
1253
-
1254
-
1255
- const analysis = await analyzeModule(path.join(categoryPath, moduleFile.name), {
1256
- debug,
1257
- instance
1258
- });
1259
- const mod = processModuleFromAnalysis(analysis, {
1260
- debug,
1261
- instance
1262
- });
1263
-
1264
- decisions.singleFile.mod = mod;
1265
-
1266
-
1267
- const functionNameMatchesFolder = typeof mod === "function" && mod.name && mod.name.toLowerCase() === categoryName.toLowerCase();
1268
-
1269
-
1270
- const functionNameMatchesFilename =
1271
- typeof mod === "function" &&
1272
- mod.name &&
1273
- instance._toapiPathKey(mod.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
1274
- mod.name !== instance._toapiPathKey(moduleName);
1275
-
1276
-
1277
-
1278
- if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
1279
- decisions.shouldFlatten = true;
1280
- decisions.flattenType = "function-folder-match";
1281
- decisions.preferredName = categoryName;
1282
- return decisions;
1283
- }
1284
-
1285
-
1286
-
1287
- if (analysis.hasDefault && analysis.defaultExportType === "object" && moduleName === categoryName && currentDepth > 0) {
1288
- if (debug) {
1289
- console.log(`[DEBUG] Default export flattening: ${categoryName}/${moduleFile.name} -> flatten default object contents`);
1290
- }
1291
- decisions.shouldFlatten = true;
1292
- decisions.flattenType = "default-export-flatten";
1293
- return decisions;
1294
- }
1295
-
1296
-
1297
-
1298
- if (moduleName === categoryName && mod && typeof mod === "object" && !Array.isArray(mod) && currentDepth > 0) {
1299
-
1300
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1301
- if (debug) {
1302
- console.log(
1303
- `[DEBUG] Auto-flatten check: moduleName="${moduleName}" categoryName="${categoryName}" moduleKeys=[${moduleKeys}] match=${moduleKeys.length === 1 && moduleKeys[0] === moduleName}`
1304
- );
1305
- }
1306
- if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
1307
- if (debug) {
1308
- console.log(`[DEBUG] Single-file auto-flattening: ${categoryName}/${moduleFile.name} -> flatten object contents`);
1309
- }
1310
- decisions.shouldFlatten = true;
1311
- decisions.flattenType = "object-auto-flatten";
1312
- decisions.preferredName = moduleKeys[0];
1313
- return decisions;
1314
- }
1315
-
1316
-
1317
-
1318
- const fileBaseName = moduleFile.name.replace(/\.(mjs|cjs|js)$/, "");
1319
- if (fileBaseName === categoryName && moduleKeys.length > 0) {
1320
- if (debug) {
1321
- console.log(
1322
- `[DEBUG] Single-file filename-folder exact match flattening: ${categoryName}/${moduleFile.name} -> avoid double nesting`
1323
- );
1324
- }
1325
- decisions.shouldFlatten = true;
1326
- decisions.flattenType = "filename-folder-match-flatten";
1327
- return decisions;
1328
- }
1329
- }
1330
-
1331
-
1332
-
1333
-
1334
-
1335
- if (moduleFiles.length === 1 && currentDepth > 0 && mod && typeof mod === "object" && !Array.isArray(mod)) {
1336
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1337
- const fileName = moduleFile.name.replace(/\.(mjs|cjs|js)$/, "");
1338
-
1339
-
1340
-
1341
- const isGenericFilename = ["singlefile", "index", "main", "default"].includes(fileName.toLowerCase());
1342
-
1343
-
1344
- if (moduleKeys.length === 1 && isGenericFilename) {
1345
- if (debug) {
1346
- console.log(`[DEBUG] Single-file parent-level auto-flattening: ${categoryName}/${moduleFile.name} -> flatten to parent level`);
1347
- }
1348
- decisions.shouldFlatten = true;
1349
- decisions.flattenType = "parent-level-flatten";
1350
- decisions.preferredName = moduleKeys[0];
1351
- return decisions;
1352
- }
1353
- }
1354
-
1355
-
1356
-
1357
- if (functionNameMatchesFolder && currentDepth > 0) {
1358
- decisions.shouldFlatten = true;
1359
- decisions.flattenType = "function-folder-match";
1360
- decisions.preferredName = mod.name;
1361
- return decisions;
1362
- }
1363
-
1364
-
1365
- if (functionNameMatchesFilename) {
1366
- decisions.shouldFlatten = false;
1367
- decisions.preferredName = mod.name;
1368
- return decisions;
1369
- }
1370
-
1371
-
1372
-
1373
-
1374
- if (
1375
- typeof mod === "function" &&
1376
- (!mod.name || mod.name === "default" || mod.__slothletDefault === true) &&
1377
- currentDepth > 0
1378
- ) {
1379
- decisions.shouldFlatten = true;
1380
- decisions.flattenType = "default-function";
1381
- decisions.preferredName = categoryName;
1382
- return decisions;
1383
- }
1384
-
1385
-
1386
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1387
- if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
1388
-
1389
- decisions.shouldFlatten = true;
1390
- decisions.flattenType = "object-auto-flatten";
1391
- decisions.preferredName = moduleName;
1392
- return decisions;
1393
- }
1394
-
1395
-
1396
- decisions.shouldFlatten = false;
1397
- decisions.preferredName = moduleName;
1398
- return decisions;
1399
- }
1400
-
1401
-
1402
- decisions.type = "multi-file";
1403
- if (debug) {
1404
- console.log(`[DEBUG] buildCategoryDecisions: Processing multi-file case for ${categoryPath}`);
1405
- }
1406
-
1407
-
1408
- const analysis = await multidefault_analyzeModules(moduleFiles, categoryPath, { debug, instance });
1409
-
1410
- decisions.multifileAnalysis = analysis;
1411
-
1412
- const { totalDefaultExports, hasMultipleDefaultExports, selfReferentialFiles, defaultExportFiles: analysisDefaults } = analysis;
1413
-
1414
-
1415
- const defaultExportFiles = [];
1416
- for (const { fileName } of analysisDefaults) {
1417
- const file = moduleFiles.find((f) => path.basename(f.name, path.extname(f.name)) === fileName);
1418
- if (file) {
1419
- const analysis = await analyzeModule(path.join(categoryPath, file.name), {
1420
- debug,
1421
- instance
1422
- });
1423
- const processedMod = processModuleFromAnalysis(analysis, {
1424
- debug,
1425
- instance
1426
- });
1427
- defaultExportFiles.push({ file, moduleName: instance._toapiPathKey(fileName), mod: processedMod, analysis });
1428
- }
1429
- }
1430
-
1431
- if (debug) {
1432
- console.log(`[DEBUG] buildCategoryDecisions: Using shared multidefault utility results`);
1433
- console.log(`[DEBUG] - totalDefaultExports: ${totalDefaultExports}`);
1434
- console.log(`[DEBUG] - hasMultipleDefaultExports: ${hasMultipleDefaultExports}`);
1435
- console.log(`[DEBUG] - selfReferentialFiles: ${Array.from(selfReferentialFiles)}`);
1436
- }
1437
-
1438
-
1439
- for (const file of moduleFiles) {
1440
- const moduleExt = path.extname(file.name);
1441
- const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
1442
-
1443
-
1444
- let mod = null;
1445
- let analysis = null;
1446
- const existingDefault = defaultExportFiles.find((def) => def.moduleName === moduleName);
1447
- if (existingDefault) {
1448
- mod = existingDefault.mod;
1449
-
1450
- analysis = existingDefault.analysis;
1451
- } else {
1452
-
1453
- analysis = await analyzeModule(path.join(categoryPath, file.name), {
1454
- debug,
1455
- instance
1456
- });
1457
- mod = processModuleFromAnalysis(analysis, {
1458
- debug,
1459
- instance
1460
- });
1461
- }
1462
-
1463
- const moduleDecision = {
1464
- file,
1465
- moduleName,
1466
- mod,
1467
- type: null,
1468
- apiPathKey: null,
1469
- shouldFlatten: false,
1470
- flattenType: null,
1471
- specialHandling: null
1472
- };
1473
-
1474
- if (moduleName === categoryName && mod && typeof mod === "object") {
1475
- moduleDecision.type = "category-match-object";
1476
- moduleDecision.specialHandling = "category-merge";
1477
- } else if (typeof mod === "function") {
1478
- moduleDecision.type = "function";
1479
-
1480
-
1481
- const isSelfReferential = selfReferentialFiles.has(moduleName);
1482
-
1483
- if (hasMultipleDefaultExports && mod.__slothletDefault === true && !isSelfReferential) {
1484
-
1485
- moduleDecision.apiPathKey = moduleName;
1486
- moduleDecision.specialHandling = "multi-default-filename";
1487
- if (debug) {
1488
- console.log(
1489
- `[DEBUG] Multi-default function case: ${moduleName} => ${moduleDecision.apiPathKey} (hasMultiple=${hasMultipleDefaultExports}, __slothletDefault=${mod.__slothletDefault}, isSelfRef=${isSelfReferential})`
1490
- );
1491
- }
1492
- } else if (selfReferentialFiles.has(moduleName)) {
1493
-
1494
- moduleDecision.type = "self-referential";
1495
- moduleDecision.specialHandling = "self-referential-namespace";
1496
- } else {
1497
-
1498
- const fnName = mod.name && mod.name !== "default" ? mod.name : moduleName;
1499
- if (debug) {
1500
- console.log(
1501
- `[DEBUG] Standard function case: ${moduleName}, fnName=${fnName}, mod.__slothletDefault=${mod.__slothletDefault}, hasMultiple=${hasMultipleDefaultExports}`
1502
- );
1503
- }
1504
-
1505
-
1506
-
1507
- if (fnName && fnName.toLowerCase() === moduleName.toLowerCase() && fnName !== moduleName) {
1508
-
1509
- moduleDecision.apiPathKey = fnName;
1510
- moduleDecision.specialHandling = "prefer-function-name";
1511
- } else {
1512
-
1513
- moduleDecision.apiPathKey = instance._toapiPathKey(fnName);
1514
- }
1515
- }
1516
- } else {
1517
- moduleDecision.type = "object";
1518
-
1519
-
1520
- let hasPreferredName = false;
1521
- const modWithPreferredNames = {};
1522
-
1523
- for (const [exportName, exportValue] of Object.entries(mod)) {
1524
- if (
1525
- typeof exportValue === "function" &&
1526
- exportValue.name &&
1527
- instance._toapiPathKey(exportValue.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
1528
- exportValue.name !== instance._toapiPathKey(moduleName)
1529
- ) {
1530
-
1531
- modWithPreferredNames[exportValue.name] = exportValue;
1532
- hasPreferredName = true;
1533
- } else {
1534
- modWithPreferredNames[instance._toapiPathKey(exportName)] = exportValue;
1535
- }
1536
- }
1537
-
1538
- if (hasPreferredName) {
1539
- moduleDecision.specialHandling = "preferred-export-names";
1540
- moduleDecision.processedExports = modWithPreferredNames;
1541
- } else if (selfReferentialFiles.has(moduleName)) {
1542
-
1543
- moduleDecision.type = "self-referential";
1544
- moduleDecision.specialHandling = "self-referential-namespace";
1545
- } else {
1546
-
1547
- const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1548
- const apiPathKey = instance._toapiPathKey(moduleName);
1549
-
1550
-
1551
-
1552
- if (!hasMultipleDefaultExports && analysis.hasDefault && analysis.defaultExportType === "object") {
1553
- moduleDecision.shouldFlatten = true;
1554
- moduleDecision.flattenType = "single-default-object";
1555
- moduleDecision.apiPathKey = apiPathKey;
1556
- } else if (hasMultipleDefaultExports && !analysis.hasDefault && moduleKeys.length > 0) {
1557
-
1558
- moduleDecision.shouldFlatten = true;
1559
- moduleDecision.flattenType = "multi-default-no-default";
1560
- } else if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
1561
-
1562
- moduleDecision.shouldFlatten = true;
1563
- moduleDecision.flattenType = "single-named-export-match";
1564
- moduleDecision.apiPathKey = apiPathKey;
1565
- } else if (!analysis.hasDefault && moduleKeys.length > 0 && moduleName === categoryName) {
1566
-
1567
- moduleDecision.shouldFlatten = true;
1568
- moduleDecision.flattenType = "category-name-match-flatten";
1569
- } else {
1570
-
1571
- moduleDecision.apiPathKey = apiPathKey;
1572
- }
1573
- }
1574
- }
1575
-
1576
- decisions.processedModules.push(moduleDecision);
1577
- }
1578
-
1579
-
1580
- for (const subDir of subDirs) {
1581
- const subDirPath = path.join(categoryPath, subDir.name);
1582
- const subDirDecision = {
1583
- name: subDir.name,
1584
- path: subDirPath,
1585
- apiPathKey: instance._toapiPathKey(subDir.name),
1586
- shouldRecurse: currentDepth < maxDepth
1587
- };
1588
- decisions.subdirectoryDecisions.push(subDirDecision);
1589
- }
1590
-
1591
- return decisions;
1592
- }
41
+ export { addApiFromFolder } from "@cldmv/slothlet/helpers/api_builder/add_api";