@cldmv/slothlet 1.0.1 → 2.0.1

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 (40) hide show
  1. package/README.md +862 -73
  2. package/dist/lib/engine/README.md +21 -0
  3. package/dist/lib/engine/slothlet_child.mjs +58 -0
  4. package/dist/lib/engine/slothlet_engine.mjs +371 -0
  5. package/dist/lib/engine/slothlet_esm.mjs +229 -0
  6. package/dist/lib/engine/slothlet_helpers.mjs +454 -0
  7. package/dist/lib/engine/slothlet_worker.mjs +148 -0
  8. package/dist/lib/helpers/resolve-from-caller.mjs +141 -0
  9. package/dist/lib/helpers/sanitize.mjs +78 -0
  10. package/dist/lib/modes/slothlet_eager.mjs +80 -0
  11. package/dist/lib/modes/slothlet_lazy.mjs +342 -0
  12. package/dist/lib/runtime/runtime.mjs +249 -0
  13. package/dist/slothlet.mjs +1092 -0
  14. package/index.cjs +81 -0
  15. package/index.mjs +76 -0
  16. package/package.json +136 -14
  17. package/types/dist/lib/engine/slothlet_child.d.mts +2 -0
  18. package/types/dist/lib/engine/slothlet_child.d.mts.map +1 -0
  19. package/types/dist/lib/engine/slothlet_engine.d.mts +31 -0
  20. package/types/dist/lib/engine/slothlet_engine.d.mts.map +1 -0
  21. package/types/dist/lib/engine/slothlet_esm.d.mts +19 -0
  22. package/types/dist/lib/engine/slothlet_esm.d.mts.map +1 -0
  23. package/types/dist/lib/engine/slothlet_helpers.d.mts +24 -0
  24. package/types/dist/lib/engine/slothlet_helpers.d.mts.map +1 -0
  25. package/types/dist/lib/engine/slothlet_worker.d.mts +2 -0
  26. package/types/dist/lib/engine/slothlet_worker.d.mts.map +1 -0
  27. package/types/dist/lib/helpers/resolve-from-caller.d.mts +149 -0
  28. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -0
  29. package/types/dist/lib/helpers/sanitize.d.mts +138 -0
  30. package/types/dist/lib/helpers/sanitize.d.mts.map +1 -0
  31. package/types/dist/lib/modes/slothlet_eager.d.mts +66 -0
  32. package/types/dist/lib/modes/slothlet_eager.d.mts.map +1 -0
  33. package/types/dist/lib/modes/slothlet_lazy.d.mts +32 -0
  34. package/types/dist/lib/modes/slothlet_lazy.d.mts.map +1 -0
  35. package/types/dist/lib/runtime/runtime.d.mts +49 -0
  36. package/types/dist/lib/runtime/runtime.d.mts.map +1 -0
  37. package/types/dist/slothlet.d.mts +110 -0
  38. package/types/dist/slothlet.d.mts.map +1 -0
  39. package/types/index.d.mts +23 -0
  40. package/slothlet.mjs +0 -1218
package/slothlet.mjs DELETED
@@ -1,1218 +0,0 @@
1
- /**
2
- * slothlet - Lazy loading version of bindleApiLoader
3
- * Supports both lazy and eager loading via config.
4
- *
5
- * Usage:
6
- * import { slothlet } from './loaderv2.mjs';
7
- * await slothlet.load({ lazy: true });
8
- * const api = slothlet.createBoundApi(context);
9
- *
10
- * Config:
11
- * lazy: true // enables lazy loading (default: true)
12
- * lazyDepth: 2 // how deep to lazy load (default: Infinity)
13
- */
14
- import fs from "fs/promises";
15
- import path from "path";
16
- import { fileURLToPath, pathToFileURL } from "url";
17
-
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = path.dirname(__filename);
20
- /**
21
- * DEBUG mode: configurable via command line (--slothletdebug), environment variable (SLOTHLET_DEBUG), or defaults to false.
22
- */
23
- let DEBUG = process.argv.includes("--slothletdebug")
24
- ? true
25
- : process.env.SLOTHLET_DEBUG === "1" || process.env.SLOTHLET_DEBUG === "true"
26
- ? true
27
- : false;
28
-
29
- /**
30
- * Live-binding references for API object (self) and context.
31
- * These are updated whenever a new API instance is created.
32
- * Dynamically imported modules can access these at runtime.
33
- * @type {object}
34
- */
35
- export let self = null;
36
- /**
37
- * Live-binding reference for contextual data.
38
- * @type {object}
39
- */
40
- export let context = null;
41
- /**
42
- * Live-binding reference for ref data.
43
- * @type {object}
44
- */
45
- export let reference = null;
46
-
47
- /**
48
- * Updates the live-binding references for self, context, and reference.
49
- * Call this whenever a new API instance is created.
50
- * Ensures submodules can import and use `self`, `context`, and `reference` directly.
51
- *
52
- * @param {object} newContext - The current context object to bind as `context`.
53
- * @param {object} newReference - The current reference object to bind as `reference`.
54
- * @param {object} newSelf - The current API object instance to bind as `self`.
55
- *
56
- * @example
57
- * // Update live bindings after creating a new API instance
58
- * updateBindings(api, { user: 'alice' }, { custom: 123 });
59
- * // Submodules can now use imported self, context, reference
60
- * self.math.add(1, 2);
61
- * context.user; // 'alice'
62
- * reference.custom; // 123
63
- */
64
- export function updateBindings(newContext, newReference, newSelf = null) {
65
- self = newSelf;
66
- context = newContext;
67
- reference = newReference;
68
- }
69
-
70
- export const slothlet = {
71
- api: null,
72
- boundapi: null,
73
- mode: "singleton",
74
- loaded: false,
75
- config: { lazy: true, lazyDepth: Infinity, debug: DEBUG, dir: null },
76
- _dispose: null,
77
- _boundAPIShutdown: null,
78
-
79
- async create(options = {}) {
80
- if (this.loaded) {
81
- console.warn("[slothlet] create: API already loaded, returning existing instance.");
82
- return this.boundapi;
83
- }
84
- // Default entry is THIS module, which re-exports `slothlet`
85
- // const { entry = import.meta.url, mode = "vm" } = options ?? {};
86
- const { entry = import.meta.url, mode = "singleton" } = options ?? {};
87
- this.mode = mode;
88
- let api;
89
- let dispose;
90
- if (mode === "singleton") {
91
- const { context = null, reference = null, ...loadConfig } = options;
92
- await slothlet.load(loadConfig, { context, reference });
93
- // console.log("this.boundapi", this.boundapi);
94
- // process.exit(0);
95
- return this.boundapi;
96
- } else {
97
- const { createEngine } = await import("./src/lib/slothlet_engine.mjs");
98
- ({ api, dispose } = await createEngine({ entry, ...options }));
99
- // setShutdown(dispose); // stash the disposer so shutdown() can call it
100
- // Attach __dispose__ as a non-enumerable property to the API object
101
- if (typeof dispose === "function") {
102
- Object.defineProperty(api, "__dispose__", {
103
- value: dispose,
104
- writable: false,
105
- enumerable: false,
106
- configurable: false
107
- });
108
- }
109
- this._dispose = dispose;
110
- this.loaded = true;
111
- return api;
112
- }
113
- },
114
-
115
- /**
116
- * Returns the loaded API object (Proxy or plain).
117
- * @returns {object}
118
- */
119
- getApi() {
120
- return this.api;
121
- },
122
-
123
- /**
124
- * Returns the loaded API object (Proxy or plain).
125
- * @returns {object}
126
- */
127
- getBoundApi() {
128
- return this.boundapi;
129
- },
130
-
131
- /**
132
- * Shuts down both the bound API and internal resources, with timeout and error handling.
133
- * Prevents infinite recursion if called from Proxy.
134
- * @returns {Promise<void>}
135
- */
136
- async shutdown() {
137
- if (this._shutdownInProgress) {
138
- console.log("this._boundAPIShutdown", this._boundAPIShutdown);
139
- throw new Error("slothlet.shutdown: Recursive shutdown detected");
140
- } else {
141
- this._shutdownInProgress = true;
142
- /**
143
- * Shuts down both the bound API and internal resources, with timeout and error handling.
144
- * @returns {Promise<void>}
145
- */
146
- // console.debug("[slothlet] shutdown: Starting shutdown process...");
147
- // console.log(this);
148
- if (this.loaded) {
149
- const TIMEOUT_MS = 5000;
150
- let apiError, internalError;
151
- if (typeof this._boundAPIShutdown === "function") {
152
- // console.debug("[slothlet] shutdown: Starting bound API shutdown...");
153
- try {
154
- await Promise.race([
155
- this._boundAPIShutdown.call(this.boundapi),
156
- new Promise((_, reject) => setTimeout(() => reject(new Error("API shutdown timeout")), TIMEOUT_MS))
157
- ]);
158
- // console.debug("[slothlet] shutdown: Bound API shutdown complete.");
159
- } catch (err) {
160
- apiError = err;
161
- // console.error("[slothlet] shutdown: Bound API shutdown error:", err);
162
- }
163
- }
164
- // Prefer boundApi.__dispose__ if available
165
- const disposeFn = this.boundapi && typeof this.boundapi.__dispose__ === "function" ? this.boundapi.__dispose__ : this._dispose;
166
- if (typeof disposeFn === "function") {
167
- // console.debug("[slothlet] shutdown: About to call dispose...");
168
- try {
169
- await disposeFn();
170
- // console.debug("[slothlet] shutdown: dispose() completed.");
171
- } catch (err) {
172
- internalError = err;
173
- // console.error("[slothlet] shutdown: Internal dispose error:", err);
174
- }
175
- }
176
- this.loaded = false;
177
- this.api = null;
178
- this.boundapi = null;
179
- this._dispose = null;
180
- this._boundAPIShutdown = null;
181
-
182
- this.updateBindings(null, null, null); // Clear live bindings
183
- if (apiError || internalError) {
184
- throw apiError || internalError;
185
- }
186
- }
187
- }
188
- },
189
-
190
- /**
191
- * Loads the bindleApi modules, either lazily or eagerly.
192
- *
193
- * @param {object} [config] - Loader configuration options.
194
- * @param {boolean} [config.lazy=true] - If true, enables lazy loading (API modules loaded on demand).
195
- * @param {number} [config.lazyDepth=Infinity] - How deep to lazy load (subdirectory depth; use Infinity for full lazy loading).
196
- * @param {string} [config.dir] - Directory to load API modules from. Defaults to the loader's directory (__dirname).
197
- * @returns {Promise<object>} The API object. If lazy loading is enabled, returns a Proxy that loads modules on access; otherwise, returns a fully loaded API object.
198
- *
199
- * @example
200
- * // Lazy load from default directory
201
- * await slothlet.load({ lazy: true });
202
- *
203
- * // Eager load from a custom directory
204
- * await slothlet.load({ lazy: false, dir: '/custom/path/to/api' });
205
- *
206
- * // Access API endpoints
207
- * const api = slothlet.createBoundApi(ctx);
208
- * const result = await api.fs.ensureDir('/some/path');
209
- */
210
- async load(config = {}, ctxRef = { context: null, reference: null }) {
211
- this.config = { ...this.config, ...config };
212
- // console.log("this.config", this.config);
213
- // process.exit(0);
214
- const apiDir = this.config.dir || __dirname;
215
- if (this.loaded) return this.api;
216
- if (this.config.lazy) {
217
- this.api = await this._createLazyApiProxy(apiDir, 0);
218
- } else {
219
- this.api = await this._eagerLoadApi(apiDir);
220
- }
221
- if (this.config.debug) console.log(this.api);
222
-
223
- const l_ctxRef = { ...{ context: null, reference: null }, ...ctxRef };
224
-
225
- this.boundapi = this.createBoundApi(l_ctxRef.context, l_ctxRef.reference);
226
- // process.exit(0);
227
- this.loaded = true;
228
- return this.boundapi;
229
- },
230
-
231
- /**
232
- * Eagerly loads all API modules (same as original loader).
233
- * @param {string} dir - Directory to load
234
- * @returns {Promise<object>} API object
235
- * @private
236
- */
237
- async _eagerLoadApi(dir, rootLevel = true) {
238
- const entries = await fs.readdir(dir, { withFileTypes: true });
239
- const api = {};
240
- const rootFunctions = [];
241
- const rootNamedExports = {};
242
- let rootFunctionKey = null;
243
- let rootDefaultFunction = null;
244
-
245
- if (rootLevel) {
246
- // Load root-level .mjs files
247
- for (const entry of entries) {
248
- if (entry.isFile() && entry.name.endsWith(".mjs") && !entry.name.startsWith(".")) {
249
- const fileName = path.basename(entry.name, ".mjs");
250
- const apiKey = this._toApiKey(fileName);
251
- const mod = await this._loadSingleModule(path.join(dir, entry.name), true);
252
- if (mod && typeof mod.default === "function") {
253
- if (!rootDefaultFunction) rootDefaultFunction = mod.default;
254
- for (const [key, value] of Object.entries(mod)) {
255
- if (key !== "default") api[key] = value;
256
- }
257
- } else {
258
- api[apiKey] = mod;
259
- for (const [key, value] of Object.entries(mod)) {
260
- rootNamedExports[key] = value;
261
- }
262
- }
263
- }
264
- }
265
- }
266
-
267
- // Load directories (categories)
268
- for (const entry of entries) {
269
- if (entry.isDirectory() && !entry.name.startsWith(".")) {
270
- const categoryPath = path.join(dir, entry.name);
271
- const subEntries = await fs.readdir(categoryPath, { withFileTypes: true });
272
- const hasSubDirs = subEntries.some((e) => e.isDirectory());
273
- if (hasSubDirs) {
274
- api[this._toApiKey(entry.name)] = await this._eagerLoadApi(categoryPath, false);
275
- } else {
276
- api[this._toApiKey(entry.name)] = await this._eagerLoadCategory(categoryPath);
277
- }
278
- }
279
- }
280
-
281
- // If a root-level default export function exists, make API callable
282
- if (rootDefaultFunction) {
283
- if (this.config.debug) console.log("rootDefaultFunction found");
284
- // process.exit(0);
285
- Object.assign(rootDefaultFunction, api);
286
- return rootDefaultFunction;
287
- } else {
288
- return api;
289
- }
290
- },
291
-
292
- /**
293
- * Converts a filename or folder name to camelCase for API property.
294
- * @param {string} name
295
- * @returns {string}
296
- * @example
297
- * toApiKey('root-math') // 'rootMath'
298
- */
299
- _toApiKey(name) {
300
- return name.replace(/-([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
301
- },
302
-
303
- /**
304
- * Eagerly loads a category (same flattening logic as original).
305
- * @param {string} categoryPath
306
- * @returns {Promise<object>}
307
- * @private
308
- */
309
- async _eagerLoadCategory(categoryPath) {
310
- const files = await fs.readdir(categoryPath);
311
- const mjsFiles = files.filter((f) => f.endsWith(".mjs") && !f.startsWith("."));
312
- if (mjsFiles.length === 1) {
313
- const categoryName = path.basename(categoryPath);
314
- const moduleName = path.basename(mjsFiles[0], ".mjs");
315
- if (moduleName === categoryName) {
316
- const mod = await this._loadSingleModule(path.join(categoryPath, mjsFiles[0]));
317
- // If the module is an object with only named exports, flatten them
318
- if (mod && typeof mod === "object" && !mod.default) {
319
- return { ...mod };
320
- }
321
- return mod;
322
- }
323
- }
324
- const categoryModules = {};
325
- for (const file of mjsFiles) {
326
- const moduleName = path.basename(file, ".mjs");
327
- const mod = await this._loadSingleModule(path.join(categoryPath, file));
328
- // For multi-file categories, always assign to property (do not flatten)
329
- categoryModules[this._toApiKey(moduleName)] = mod;
330
- }
331
- return categoryModules;
332
- },
333
-
334
- /**
335
- * Loads a single module file and returns its exports (flattened if needed).
336
- * @param {string} modulePath
337
- * @returns {Promise<object>}
338
- * @private
339
- */
340
- async _loadSingleModule(modulePath, rootLevel = false) {
341
- const moduleUrl = pathToFileURL(modulePath).href;
342
- const module = await import(moduleUrl);
343
- if (this.config.debug) console.log("module: ", module);
344
- // If default export is a function, expose as callable and attach named exports as properties
345
- if (typeof module.default === "function") {
346
- let fn;
347
- if (rootLevel) {
348
- fn = module;
349
- } else {
350
- fn = module.default;
351
- for (const [exportName, exportValue] of Object.entries(module)) {
352
- if (exportName !== "default") {
353
- fn[exportName] = exportValue;
354
- }
355
- }
356
- if (this.config.debug) console.log("fn: ", fn);
357
- }
358
- return fn;
359
- }
360
-
361
- const moduleExports = Object.entries(module);
362
- if (this.config.debug) console.log("moduleExports: ", moduleExports);
363
-
364
- // Handle both module.default and moduleExports[0][1] for callable and object default exports
365
- const defaultExportObj =
366
- typeof module.default === "object" && module.default !== null
367
- ? module.default
368
- : typeof moduleExports[0][1] === "object" && typeof moduleExports[0][1].default === "function" && moduleExports[0][1] !== null
369
- ? moduleExports[0][1]
370
- : null;
371
- let objectName = null;
372
- if (typeof module.default === "function" && module.default.name) {
373
- objectName = module.default.name;
374
- } else if (moduleExports[0] && moduleExports[0][0] !== "default") {
375
- objectName = moduleExports[0][0];
376
- }
377
- const defaultExportFn =
378
- typeof module.default === "function" ? module.default : typeof moduleExports[0][1] === "function" ? moduleExports[0][1] : null;
379
-
380
- if (this.config.debug) console.log("defaultExportObj: ", defaultExportObj);
381
- if (this.config.debug) console.log("objectName: ", objectName);
382
-
383
- if (defaultExportObj && typeof defaultExportObj.default === "function") {
384
- if (this.config.debug) console.log("DEFAULT FUNCTION FOUND FOR: ", module);
385
- /**
386
- * Wraps an object with a callable default property as a function.
387
- * @param {...any} args - Arguments to pass to the default function.
388
- * @returns {any}
389
- * @example
390
- * api(...args); // calls default
391
- */
392
- // const callableApi = defaultExportObj.default;
393
- // callableApi.displayName = moduleExports[0][0];
394
- // const objectName = moduleExports[0][0]; // dynamically set as needed
395
- const callableApi = {
396
- [objectName]: function (...args) {
397
- return defaultExportObj.default.apply(defaultExportObj, args);
398
- }
399
- }[objectName];
400
- for (const [methodName, method] of Object.entries(defaultExportObj)) {
401
- if (methodName === "default") continue;
402
- callableApi[methodName] = method;
403
- }
404
- // for (const [exportName, exportValue] of Object.entries(module)) {
405
- // if (exportName !== "default" && exportValue !== callableApi) {
406
- // callableApi[exportName] = exportValue;
407
- // }
408
- // }
409
- if (this.config.debug) console.log("callableApi", callableApi);
410
- return callableApi;
411
- } else if (defaultExportObj) {
412
- if (this.config.debug) console.log("DEFAULT FOUND FOR: ", module);
413
- /**
414
- * Flattens object default exports and attaches named exports.
415
- * @returns {object}
416
- * @example
417
- * api.method();
418
- */
419
- const obj = { ...defaultExportObj };
420
- for (const [exportName, exportValue] of Object.entries(module)) {
421
- if (exportName !== "default" && exportValue !== obj) {
422
- obj[exportName] = exportValue;
423
- }
424
- }
425
- return obj;
426
- }
427
- // If only named exports and no default, expose the export directly
428
- const namedExports = Object.entries(module).filter(([k]) => k !== "default");
429
- if (this.config.debug) console.log("namedExports: ", namedExports);
430
- if (namedExports.length === 1 && !module.default) {
431
- if (typeof namedExports[0][1] === "object") {
432
- // Flatten single object export
433
- if (this.config.debug) console.log("namedExports[0][1] === object: ", namedExports[0][1]);
434
- return { ...namedExports[0][1] };
435
- }
436
- if (typeof namedExports[0][1] === "function") {
437
- if (this.config.debug) console.log("namedExports[0][1] === function: ", namedExports[0][1]);
438
- // Return single function export directly
439
- return namedExports[0][1];
440
- }
441
- }
442
- const apiExport = {};
443
- for (const [exportName, exportValue] of namedExports) {
444
- apiExport[exportName] = exportValue;
445
- }
446
- return apiExport;
447
- },
448
-
449
- /**
450
- * Creates a lazy API proxy for a directory.
451
- * @param {string} dir - Directory path.
452
- * @param {number} [depth=0] - Recursion depth.
453
- * @returns {Proxy} Proxy object for lazy API loading.
454
- * @private
455
- */
456
- async _createLazyApiProxy(dir, depth = 0, rootLevel = true) {
457
- const self = this;
458
- const entries = await fs.readdir(dir, { withFileTypes: true });
459
-
460
- // ───────────────────────── helpers ─────────────────────────
461
- const SEP = "\x1f";
462
- const pathKeyOf = (arr) => arr.join(SEP);
463
- const cacheMap = new Map(); // full-path → proxy/value
464
- const rootExportCache = new Map(); // exportName → value (functions or values)
465
- const rootLoaders = []; // loaders for root files (for api() and root exports)
466
-
467
- // loader thunk that memoizes its import() and keeps resolved namespace
468
- function createLoader(loadModule) {
469
- const loader = async () => {
470
- if (!loader.__promise) {
471
- loader.__promise = loadModule().then((ns) => {
472
- loader.__ns = ns;
473
- return ns;
474
- });
475
- }
476
- return loader.__promise;
477
- };
478
- loader.__isLoader = true;
479
- loader.__ns = null;
480
- loader.__promise = null;
481
- return loader;
482
- }
483
-
484
- // build a callable proxy that represents a member (possibly nested) *inside* a module namespace
485
- function buildModuleMemberProxy(loader, insideKeys, fullPath) {
486
- // Heuristic to resolve a callable from a value (ESM/CJS friendly)
487
- function resolveCallable(value) {
488
- // direct function
489
- if (typeof value === "function") return value;
490
-
491
- // common wrapper shapes
492
- if (value && typeof value.default === "function") return value.default; // { default: fn }
493
- if (value && value.__esModule && typeof value.default === "function") return value.default;
494
-
495
- // sometimes bundlers double-wrap: { default: { default: fn } }
496
- if (value && value.default && typeof value.default.default === "function") return value.default.default;
497
-
498
- // last resort: some libraries hang callables on .handler/.fn
499
- if (value && typeof value.handler === "function") return value.handler;
500
- if (value && typeof value.fn === "function") return value.fn;
501
-
502
- return null;
503
- }
504
-
505
- // Resolve the target value from the module namespace using insideKeys
506
- function resolveFromNamespace(ns) {
507
- // module root call: prefer module-as-function, then default
508
- if (insideKeys.length === 0) {
509
- // CJS: module.exports = fn
510
- const cand0 = resolveCallable(ns);
511
- if (cand0) return cand0;
512
-
513
- // ESM: export default fn
514
- const cand1 = resolveCallable(ns && ns.default);
515
- if (cand1) return cand1;
516
-
517
- return null;
518
- }
519
-
520
- // Nested member call: walk ns[...] then resolve callable on that leaf
521
- let cur = ns;
522
- for (const k of insideKeys) {
523
- cur = cur?.[k];
524
- if (cur == null) break;
525
- }
526
- return resolveCallable(cur);
527
- }
528
-
529
- const targetFn = function (...args) {
530
- return (async () => {
531
- const ns = loader.__ns || (await loader());
532
- const fn = resolveFromNamespace(ns);
533
-
534
- const shownPath = insideKeys.length ? fullPath.concat(insideKeys).join(".") : `${fullPath.join(".")}.default`;
535
-
536
- if (typeof fn !== "function") {
537
- throw new Error(`slothlet: ${shownPath} is not a function.`);
538
- }
539
- return fn(...args);
540
- })();
541
- };
542
-
543
- return new Proxy(targetFn, {
544
- // Build deeper paths synchronously; import happens only when called
545
- get(_fn, prop, receiver) {
546
- if (typeof prop === "symbol" || Reflect.has(targetFn, prop)) {
547
- return Reflect.get(targetFn, prop, receiver);
548
- }
549
- if (typeof prop !== "string") return undefined;
550
-
551
- const seg = self._toApiKey(prop);
552
- const nextInside = insideKeys.concat(seg);
553
- const nextFull = fullPath.concat(seg);
554
- return buildModuleMemberProxy(loader, nextInside, nextFull);
555
- },
556
-
557
- // Always delegate calls through the unified resolver
558
- apply(_fn, _this, args) {
559
- return targetFn(...args);
560
- },
561
-
562
- // Keep inspector happy without forcing import
563
- ownKeys() {
564
- return ["default"];
565
- },
566
- getOwnPropertyDescriptor() {
567
- return { enumerable: true, configurable: true };
568
- }
569
- });
570
- }
571
-
572
- // callable, path-aware object proxy (for objects that hold loaders or nested objects)
573
- function makeObjProxy(currentObj, pathArr = []) {
574
- const targetFn = function (...args) {
575
- if (pathArr.length === 0) return rootApply(args); // api(...)
576
- throw new Error("slothlet: cannot call this path directly.");
577
- };
578
-
579
- return new Proxy(targetFn, {
580
- get(_, prop, receiver) {
581
- // pass-through function built-ins/symbols
582
- if (typeof prop === "symbol" || Reflect.has(targetFn, prop)) {
583
- return Reflect.get(targetFn, prop, receiver);
584
- }
585
- // guards
586
- if (typeof prop !== "string" || prop === "then" || prop === "default" || prop.startsWith("_") || /^[0-9]+$/.test(prop))
587
- return undefined;
588
-
589
- const seg = self._toApiKey(prop);
590
- const newPath = pathArr.concat(seg);
591
- const pkey = pathKeyOf(newPath);
592
-
593
- if (cacheMap.has(pkey)) return cacheMap.get(pkey);
594
- if (self.config.debug) console.log("path:", newPath.join("."));
595
-
596
- let next;
597
-
598
- // 1) real member on currentObj?
599
- if (currentObj && Object.prototype.hasOwnProperty.call(currentObj, seg)) {
600
- const value = currentObj[seg];
601
-
602
- if (value && value.__isLoader) {
603
- // lazy module at this node
604
- next = buildModuleMemberProxy(value, [], newPath);
605
- } else if (value && typeof value === "object") {
606
- // plain nested object (e.g., subfolder object)
607
- next = makeObjProxy(value, newPath);
608
- } else {
609
- // primitive or function already materialized (rare at build time)
610
- next = value;
611
- }
612
- }
613
- // 2) at root, treat unknown as potential root named export (lazy resolver)
614
- else if (pathArr.length === 0) {
615
- next = buildRootExportResolver(seg);
616
- }
617
- // 3) otherwise keep walking with a virtual node
618
- else {
619
- next = makeObjProxy({}, newPath);
620
- }
621
-
622
- cacheMap.set(pkey, next);
623
- return next;
624
- },
625
-
626
- apply(_fn, _thisArg, args) {
627
- if (pathArr.length === 0) return rootApply(args);
628
- throw new Error("slothlet: only the API root is callable.");
629
- },
630
-
631
- // invariants: union of function target keys + object keys
632
- ownKeys() {
633
- const s = new Set(Reflect.ownKeys(targetFn));
634
- for (const k of Reflect.ownKeys(currentObj || {})) s.add(k);
635
- return [...s];
636
- },
637
- getOwnPropertyDescriptor(_t, p) {
638
- const onTarget = Reflect.getOwnPropertyDescriptor(targetFn, p);
639
- if (onTarget) return onTarget;
640
- if (currentObj) {
641
- const onObj = Object.getOwnPropertyDescriptor(currentObj, p);
642
- if (onObj) return onObj;
643
- }
644
- return { enumerable: true, configurable: true };
645
- }
646
- });
647
- }
648
-
649
- // lazy resolver for root-level named exports: api.someExport()
650
- function buildRootExportResolver(exportKey) {
651
- const targetFn = function (...args) {
652
- return (async () => {
653
- // cached?
654
- if (rootExportCache.has(exportKey)) {
655
- const v = rootExportCache.get(exportKey);
656
- if (typeof v !== "function") throw new Error(`slothlet: ${exportKey} is not a function.`);
657
- return v(...args);
658
- }
659
- // scan each root module lazily until found
660
- for (const loader of rootLoaders) {
661
- const ns = loader.__ns || (await loader());
662
- if (Object.prototype.hasOwnProperty.call(ns, exportKey)) {
663
- const v = ns[exportKey];
664
- rootExportCache.set(exportKey, v);
665
- if (typeof v !== "function") throw new Error(`slothlet: ${exportKey} is not a function.`);
666
- return v(...args);
667
- }
668
- }
669
- throw new Error(`slothlet: root export '${exportKey}' not found.`);
670
- })();
671
- };
672
-
673
- return new Proxy(targetFn, {
674
- get(_fn, prop, receiver) {
675
- if (typeof prop === "symbol" || Reflect.has(targetFn, prop)) {
676
- return Reflect.get(targetFn, prop, receiver);
677
- }
678
- return undefined; // don’t allow chaining off a root export name
679
- },
680
- apply(_fn, _this, args) {
681
- return targetFn(...args);
682
- },
683
- ownKeys() {
684
- return [];
685
- },
686
- getOwnPropertyDescriptor() {
687
- return { enumerable: true, configurable: true };
688
- }
689
- });
690
- }
691
-
692
- // root apply: find first root module with callable default (lazy, memoized)
693
- let cachedRootDefault = null;
694
- async function rootApply(args) {
695
- if (cachedRootDefault) return cachedRootDefault(...args);
696
- for (const loader of rootLoaders) {
697
- const ns = loader.__ns || (await loader());
698
- if (typeof ns?.default === "function") {
699
- cachedRootDefault = ns.default;
700
- return cachedRootDefault(...args);
701
- }
702
- }
703
- throw new Error("slothlet: no root default function is available.");
704
- }
705
-
706
- // ───────────────────── build the LAZY api tree ─────────────────────
707
- const api = {};
708
-
709
- // Root files → expose as lazy modules under api[fileKey]
710
- for (const entry of entries) {
711
- if (entry.isFile() && !entry.name.startsWith(".") && (entry.name.endsWith(".mjs") || entry.name.endsWith(".js"))) {
712
- const filePath = path.join(dir, entry.name);
713
- const fileKey = self._toApiKey(path.basename(entry.name, path.extname(entry.name)));
714
- const loader = createLoader(() => self._loadSingleModule(filePath, true));
715
-
716
- rootLoaders.push(loader);
717
- api[fileKey] = loader; // store loader (not proxy) – object proxy will wrap it on access
718
- }
719
- }
720
-
721
- // Directories → flatten or build nested objects of loaders
722
- for (const entry of entries) {
723
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
724
-
725
- const categoryPath = path.join(dir, entry.name);
726
- const subEntries = await fs.readdir(categoryPath, { withFileTypes: true });
727
- const mjsFiles = subEntries.filter((e) => e.isFile() && e.name.endsWith(".mjs") && !e.name.startsWith("."));
728
- const subDirs = subEntries.filter((e) => e.isDirectory() && !e.name.startsWith("."));
729
- const catKey = self._toApiKey(entry.name);
730
- const categoryName = path.basename(categoryPath);
731
-
732
- // Flatten: ./nested/date/date.mjs -> api.nested.date.*
733
- if (mjsFiles.length === 1 && path.basename(mjsFiles[0].name, ".mjs") === categoryName && subDirs.length === 0) {
734
- const modPath = path.join(categoryPath, mjsFiles[0].name);
735
- const loader = createLoader(() => self._loadSingleModule(modPath));
736
- api[catKey] = loader; // object proxy will wrap this loader lazily
737
- continue;
738
- }
739
-
740
- // Nested: build an object of loaders and nested proxies
741
- const categoryObj = {};
742
- for (const fileEntry of mjsFiles) {
743
- const moduleName = path.basename(fileEntry.name, ".mjs");
744
- const modKey = self._toApiKey(moduleName);
745
- const loader = createLoader(() => self._loadSingleModule(path.join(categoryPath, fileEntry.name)));
746
- categoryObj[modKey] = loader;
747
- }
748
- for (const subDirEntry of subDirs) {
749
- categoryObj[self._toApiKey(subDirEntry.name)] = await self._createLazyApiProxy(
750
- path.join(categoryPath, subDirEntry.name),
751
- depth + 1,
752
- false
753
- );
754
- }
755
- api[catKey] = categoryObj;
756
- }
757
-
758
- // Return the callable, path-aware proxy rooted at `api`
759
- return makeObjProxy(api, []);
760
- },
761
-
762
- async _createLazyApiProxy2(dir, depth = 0, rootLevel = true) {
763
- const self = this;
764
- const entries = await fs.readdir(dir, { withFileTypes: true });
765
- let cache = {};
766
- const dirName = path.basename(dir);
767
- const mjsFiles = entries.filter((e) => e.isFile() && (e.name.endsWith(".mjs") || e.name.endsWith(".js")) && !e.name.startsWith("."));
768
- const api = {};
769
- let rootDefaultFunction = null;
770
-
771
- // if (rootLevel) {
772
- // Load root-level .mjs files
773
- for (const entry of mjsFiles) {
774
- const fileName = path.basename(entry.name, path.extname(entry.name));
775
- const apiKey = self._toApiKey(fileName);
776
- const modPromise = await self._loadSingleModule(path.join(dir, entry.name), true);
777
- cache[apiKey] = modPromise;
778
- if (this.config.debug) console.log(`modPromise (${entry.name})(${apiKey}): `, modPromise);
779
- /*
780
- modPromise.then((mod) => {
781
- if (this.config.debug) console.log(`mod (${entry.name})(${apiKey}): `, mod);
782
- if (mod && typeof mod.default === "function" && rootLevel) {
783
- if (!rootDefaultFunction) rootDefaultFunction = mod.default;
784
- for (const [key, value] of Object.entries(mod)) {
785
- if (key !== "default") api[key] = value;
786
- }
787
- } else {
788
- api[apiKey] = mod;
789
- // if (typeof mod === "function") {
790
- // for (const [key, value] of Object.entries(mod)) {
791
- // if (key !== "default") api[key] = value;
792
- // }
793
- // } else {
794
- for (const [key, value] of Object.entries(mod)) {
795
- api[key] = value;
796
- }
797
- // }
798
- }
799
- // cache = api;
800
- if (this.config.debug) console.log(`api[apiKey] (${entry.name})(${apiKey}): `, api[apiKey]);
801
- }); */
802
- }
803
- // }
804
-
805
- // Load directories (categories)
806
- for (const entry of entries) {
807
- if (entry.isDirectory() && !entry.name.startsWith(".")) {
808
- const categoryPath = path.join(dir, entry.name);
809
- const subEntries = await fs.readdir(categoryPath, { withFileTypes: true });
810
- const mjsFiles = subEntries.filter((e) => e.isFile() && e.name.endsWith(".mjs") && !e.name.startsWith("."));
811
- const subDirs = subEntries.filter((e) => e.isDirectory() && !e.name.startsWith("."));
812
- const categoryName = path.basename(categoryPath);
813
- // Only flatten if single file matches category name and no subdirs
814
- if (mjsFiles.length === 1 && path.basename(mjsFiles[0].name, ".mjs") === categoryName && subDirs.length === 0) {
815
- const mod = await self._loadSingleModule(path.join(categoryPath, mjsFiles[0].name));
816
- api[self._toApiKey(entry.name)] = mod;
817
- // api[self._toApiKey(entry.name)] = await mod;
818
- if (this.config.debug) console.log(`mod mjsFiles (${entry.name} :: ${mjsFiles[0].name}): `, mod);
819
- } else {
820
- // Multi-file or nested: assign each file and subdir as a property
821
- const categoryObj = {};
822
- for (const fileEntry of mjsFiles) {
823
- const moduleName = path.basename(fileEntry.name, ".mjs");
824
- if (this.config.debug) console.log(`mod categoryObj ${moduleName} === ${categoryName} (${entry.name} :: ${fileEntry.name}): `);
825
- if (moduleName === categoryName) {
826
- // Merge exports directly into parent object
827
- const mod = await self._loadSingleModule(path.join(categoryPath, fileEntry.name));
828
- if (this.config.debug) console.log(`mod categoryObj (${entry.name} :: ${fileEntry.name}): `, mod);
829
- if (mod && typeof mod === "object") {
830
- Object.assign(categoryObj, mod);
831
- } else {
832
- categoryObj[moduleName] = mod;
833
- }
834
- } else {
835
- categoryObj[self._toApiKey(moduleName)] = await self._loadSingleModule(path.join(categoryPath, fileEntry.name));
836
- }
837
- }
838
- for (const subDirEntry of subDirs) {
839
- const mod = await self._createLazyApiProxy(path.join(categoryPath, subDirEntry.name), depth + 1, false);
840
- categoryObj[self._toApiKey(subDirEntry.name)] = mod;
841
- if (this.config.debug) console.log(`subDirEntry (${entry.name} :: ${subDirEntry.name}): `, mod);
842
- }
843
- if (this.config.debug) console.log(`categoryObj (${entry.name}): `, categoryObj);
844
- api[self._toApiKey(entry.name)] = categoryObj;
845
- }
846
- }
847
- }
848
- /*
849
- // Await all module promises and build API object
850
- const apiKeys = Object.keys(cache);
851
- const resolvedModules = await Promise.all(apiKeys.map((k) => cache[k]));
852
- for (let i = 0; i < apiKeys.length; i++) {
853
- const key = apiKeys[i];
854
- const mod = resolvedModules[i];
855
- if (typeof mod === "function" && rootLevel) {
856
- // Module itself is a function, assign directly
857
- // api[key] = mod;
858
- if (!rootDefaultFunction) rootDefaultFunction = mod;
859
- } else if (mod && typeof mod.default === "function" && rootLevel) {
860
- // Module is an object with a callable default property, assign the whole object
861
- // api[key] = mod;
862
- if (!rootDefaultFunction) rootDefaultFunction = mod;
863
- } else if (mod && typeof mod === "object" && mod !== null) {
864
- // } else if (mod && typeof mod === "object" && mod !== null && typeof mod !== "function") {
865
- api[key] = { ...mod };
866
- } else {
867
- api[key] = mod;
868
- }
869
- }
870
- */
871
- // If a root-level default export function exists, make API callable
872
- if (rootDefaultFunction) {
873
- // Use the original function reference for callability and attach named exports
874
- let fn = rootDefaultFunction;
875
- for (const [key, value] of Object.entries(api)) {
876
- // Prevent circular reference and skip 'default' property
877
- if (key === "default" || value === fn) continue;
878
- try {
879
- fn[key] = value;
880
- } catch (err) {
881
- // Ignore assignment errors for read-only properties
882
- }
883
- }
884
- return fn;
885
- } else {
886
- const SEP = "\x1f";
887
- const PATH = Symbol("path");
888
-
889
- // Turn ['foo','bar'] into 'foo␟bar'
890
- const pathKeyOf = (path) => path.join(SEP);
891
-
892
- // Use a Map so keys aren't accidentally coerced
893
- const cache = new Map();
894
-
895
- function wrap(value, path) {
896
- if (value == null) return makeProxy({}, path);
897
-
898
- if (typeof value === "function") {
899
- return new Proxy(value, {
900
- get(fn, p, r) {
901
- if (p === PATH) return path.slice();
902
- return Reflect.get(fn, p, r);
903
- },
904
- apply(fn, thisArg, args) {
905
- console.log("call:", path.join("."), "args:", args);
906
- return Reflect.apply(fn, thisArg, args);
907
- }
908
- });
909
- }
910
-
911
- if (typeof value === "object") return makeProxy(value, path);
912
-
913
- // primitives end the chain
914
- return value;
915
- }
916
-
917
- function makeProxy(api, path = []) {
918
- return new Proxy(api, {
919
- get(target, prop, receiver) {
920
- if (
921
- typeof prop !== "string" ||
922
- prop === "then" ||
923
- prop === "default" ||
924
- prop.startsWith("_") ||
925
- typeof prop === "symbol" ||
926
- /^[0-9]+$/.test(prop)
927
- )
928
- return undefined;
929
-
930
- const seg = self._toApiKey(prop); // sanitized segment
931
- const newPath = path.concat(seg);
932
- const key = pathKeyOf(newPath);
933
-
934
- // full-path cache lookup
935
- if (cache.has(key)) return cache.get(key);
936
-
937
- console.log("path:", newPath.join(".")); // full chain log
938
-
939
- // prefer real member; otherwise keep walking
940
- const real = Reflect.get(target, seg, receiver);
941
- const next = real !== undefined ? wrap(real, newPath) : makeProxy({}, newPath);
942
-
943
- // cache the wrapped/proxied value for this *full path*
944
- cache.set(key, next);
945
- return next;
946
- },
947
-
948
- ownKeys(t) {
949
- return Reflect.ownKeys(t);
950
- },
951
- getOwnPropertyDescriptor(t, p) {
952
- return Object.getOwnPropertyDescriptor(t, p) || { enumerable: true, configurable: true };
953
- }
954
- });
955
- }
956
- return makeProxy(api);
957
- }
958
- },
959
-
960
- /**
961
- * Updates the live-binding references for self and context.
962
- * Call this whenever a new API instance is created.
963
- * @param {object} newContext - The current context object to bind as `context`.
964
- * @param {object} newReference - The current reference object to bind as `reference`.
965
- * @param {object} newSelf - The current API object instance to bind as `self`.
966
- */
967
- updateBindings(newContext, newReference, newSelf = null) {
968
- if (newSelf === null) newSelf = this.boundapi;
969
- updateBindings(newContext, newReference, newSelf);
970
- },
971
-
972
- /**
973
- * Creates a bound API object with live-bound self, context, and reference.
974
- * Ensures submodules can access `self`, `context`, and `reference` directly.
975
- * Works for both eager and lazy loading modes.
976
- *
977
- * @param {object} [ctx=null] - Context object to be spread into the API and live-bound.
978
- * @param {object|object[]} [ref=null] - Reference object(s) to extend the API/self with additional properties.
979
- * @returns {object} Bound API object (Proxy or plain) with live-bound self, context, and reference.
980
- *
981
- * @example
982
- * // Create API with context and reference
983
- * const api = slothlet.createBoundApi({ user: 'alice' }, { custom: 123 });
984
- *
985
- * // Access API endpoints
986
- * api.math.add(2, 3); // 5
987
- *
988
- * // Access live-bound self and context
989
- * api.self.math.add(1, 2); // 3
990
- * api.context.user; // 'alice'
991
- * api.reference.custom; // 123
992
- *
993
- * // Submodules can import { self, context, reference } from the loader
994
- * // and use them directly: self.math.add(...)
995
- */
996
- createBoundApi(ctx = null, ref = null) {
997
- if (!this.api) throw new Error("BindleApi modules not loaded. Call load() first.");
998
- let boundApi;
999
- if (this.config.lazy) {
1000
- boundApi = this._createBoundLazyApi(this.api);
1001
- } else {
1002
- boundApi = this._buildCompleteApi(this.api);
1003
- }
1004
-
1005
- // Allow ref to extend boundApi
1006
- // if (ref && typeof ref === "object") {
1007
- // for (const [key, value] of Object.entries(ref)) {
1008
- // if (!(key in boundApi)) {
1009
- // try {
1010
- // boundApi[key] = value;
1011
- // } catch {}
1012
- // }
1013
- // }
1014
- // }
1015
-
1016
- const safeDefine = (obj, key, value) => {
1017
- const desc = Object.getOwnPropertyDescriptor(obj, key);
1018
- if (!desc) {
1019
- Object.defineProperty(obj, key, {
1020
- value,
1021
- writable: true,
1022
- configurable: true,
1023
- enumerable: true
1024
- });
1025
- } else if (desc.configurable) {
1026
- Object.defineProperty(obj, key, {
1027
- value,
1028
- writable: true,
1029
- configurable: true,
1030
- enumerable: true
1031
- });
1032
- } else if (this.config && this.config.debug) {
1033
- console.warn(`Could not redefine boundApi.${key}: not configurable`);
1034
- }
1035
- };
1036
-
1037
- // Live-bind self and context for submodules
1038
- // this.updateBindings(ctx, ref, boundApi);
1039
-
1040
- safeDefine(boundApi, "self", self);
1041
- safeDefine(boundApi, "context", context);
1042
- safeDefine(boundApi, "reference", reference);
1043
- safeDefine(boundApi, "describe", function () {
1044
- // For lazy mode, only show top-level keys, do not resolve modules
1045
- if (this.config && this.config.lazy) {
1046
- return Reflect.ownKeys(boundApi);
1047
- }
1048
- // For eager mode, show full API
1049
- return { ...boundApi };
1050
- });
1051
-
1052
- // Only set _boundAPIShutdown if it's a user-defined function, not a bound version of slothlet.shutdown
1053
- if (
1054
- typeof boundApi.shutdown === "function" &&
1055
- boundApi.shutdown !== this.shutdown &&
1056
- boundApi.shutdown.name !== "bound get" &&
1057
- boundApi.shutdown.name !== "bound shutdown" &&
1058
- boundApi.shutdown.toString().indexOf("[native code]") === -1 &&
1059
- boundApi.shutdown.toString() !== this.shutdown.bind(this).toString()
1060
- ) {
1061
- this._boundAPIShutdown = boundApi.shutdown; // Save original
1062
- } else {
1063
- this._boundAPIShutdown = null;
1064
- }
1065
- const shutdownDesc = Object.getOwnPropertyDescriptor(boundApi, "shutdown");
1066
- if (!shutdownDesc || shutdownDesc.configurable) {
1067
- Object.defineProperty(boundApi, "shutdown", {
1068
- value: this.shutdown.bind(this),
1069
- writable: true,
1070
- configurable: true,
1071
- enumerable: true
1072
- });
1073
- } else if (this.config && this.config.debug) {
1074
- console.warn("Could not redefine boundApi.shutdown: not configurable");
1075
- }
1076
- // console.debug("[slothlet] createBoundApi: boundApi.shutdown is now slothlet.shutdown.");
1077
-
1078
- // Live-bind self and context for submodules
1079
- this.updateBindings(ctx, ref, boundApi);
1080
-
1081
- return boundApi;
1082
- },
1083
-
1084
- /**
1085
- * Recursively builds a bound API from an eagerly loaded API object.
1086
- * @param {object} apiModules
1087
- * @returns {object}
1088
- * @private
1089
- */
1090
- _buildCompleteApi(apiModules) {
1091
- // Improved build logic: preserve functions, handle callable objects, recurse only into objects
1092
- const buildModule = (module) => {
1093
- if (!module) return module;
1094
- if (typeof module === "function") {
1095
- // Return function as-is (preserve callability)
1096
- return module;
1097
- }
1098
- if (typeof module === "object" && module !== null) {
1099
- if (typeof module.default === "function") {
1100
- // Make object callable via default, attach named methods as direct function references
1101
- const callableApi = function (...args) {
1102
- return module.default.apply(module, args);
1103
- };
1104
- for (const [methodName, method] of Object.entries(module)) {
1105
- if (methodName === "default") continue;
1106
- callableApi[methodName] = typeof method === "function" ? method : buildModule(method);
1107
- }
1108
- return callableApi;
1109
- }
1110
- // For plain objects, assign function references directly, recurse only into objects
1111
- const builtModule = {};
1112
- for (const [methodName, method] of Object.entries(module)) {
1113
- builtModule[methodName] = typeof method === "function" ? method : buildModule(method);
1114
- }
1115
- return builtModule;
1116
- }
1117
- return module;
1118
- };
1119
- let completeApi = {};
1120
- if (typeof apiModules === "function") {
1121
- completeApi = apiModules;
1122
- }
1123
- for (const [moduleName, module] of Object.entries(apiModules)) {
1124
- completeApi[moduleName] = buildModule(module);
1125
- }
1126
- return completeApi;
1127
- },
1128
-
1129
- /**
1130
- * Wraps the lazy API proxy so that modules are loaded and built with context on access.
1131
- * @param {Proxy} proxyApi
1132
- * @returns {Proxy}
1133
- * @private
1134
- */
1135
- _createBoundLazyApi(proxyApi) {
1136
- const slothletSelf = this;
1137
- function wrap(value, prop) {
1138
- if (value instanceof Promise) {
1139
- return new Proxy(function () {}, {
1140
- apply: async (target, thisArg, args) => {
1141
- const loaded = await value;
1142
- const built = slothletSelf._buildCompleteApi({ [prop]: loaded })[prop];
1143
- if (typeof built === "function") {
1144
- return built.apply(thisArg, args);
1145
- } else if (built && typeof built.default === "function") {
1146
- return built.default.apply(built, args);
1147
- }
1148
- return built;
1149
- },
1150
- get: (target, subProp) => {
1151
- return wrap(
1152
- value.then((loaded) => loaded[subProp]),
1153
- subProp
1154
- );
1155
- }
1156
- });
1157
- }
1158
- if (value && typeof value === "object" && typeof value.default === "function") {
1159
- const callableApi = function (...args) {
1160
- return value.default.apply(value, args);
1161
- };
1162
- for (const [methodName, method] of Object.entries(value)) {
1163
- if (methodName === "default") continue;
1164
- callableApi[methodName] = method;
1165
- }
1166
- return callableApi;
1167
- }
1168
- if (value && typeof value === "object") {
1169
- return slothletSelf._createBoundLazyApi(value);
1170
- }
1171
- return value;
1172
- }
1173
- return new Proxy(proxyApi, {
1174
- get(target, prop) {
1175
- // Only run RegExp.test if prop is a primitive string (not a Proxy object)
1176
- if (
1177
- typeof prop !== "string" ||
1178
- prop === "then" ||
1179
- prop === "default" ||
1180
- prop.startsWith("_") ||
1181
- typeof prop === "symbol" ||
1182
- /^[0-9]+$/.test(String(prop))
1183
- )
1184
- return undefined;
1185
- if (prop === "shutdown") {
1186
- // Always return the main slothlet shutdown, bound to slothletSelf, to avoid recursion
1187
- return slothletSelf.shutdown.bind(slothletSelf);
1188
- }
1189
- const apiKey = slothletSelf._toApiKey(prop);
1190
- return wrap(target[apiKey], prop);
1191
- },
1192
- ownKeys(target) {
1193
- return Reflect.ownKeys(target);
1194
- },
1195
- getOwnPropertyDescriptor(target, prop) {
1196
- // For special properties, delegate to Reflect
1197
- if (prop === "prototype" || prop === "constructor") {
1198
- return Reflect.getOwnPropertyDescriptor(target, prop);
1199
- }
1200
- // If property exists, delegate to Reflect
1201
- const desc = Reflect.getOwnPropertyDescriptor(target, prop);
1202
- if (desc) return desc;
1203
- // For non-existent properties, return a configurable descriptor
1204
- return { configurable: true, enumerable: true, writable: true, value: undefined };
1205
- }
1206
- });
1207
- },
1208
-
1209
- /**
1210
- * Checks if the API has been loaded.
1211
- * @returns {boolean}
1212
- */
1213
- isLoaded() {
1214
- return this.loaded;
1215
- }
1216
- };
1217
-
1218
- export default slothlet;