@cldmv/slothlet 2.11.0 → 3.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 (189) hide show
  1. package/AGENT-USAGE.md +355 -325
  2. package/README.md +554 -238
  3. package/dist/lib/builders/api-assignment.mjs +605 -0
  4. package/dist/lib/builders/api_builder.mjs +1073 -0
  5. package/dist/lib/builders/builder.mjs +94 -0
  6. package/dist/lib/builders/modes-processor.mjs +1816 -0
  7. package/dist/lib/errors.mjs +227 -0
  8. package/dist/lib/factories/component-base.mjs +96 -0
  9. package/dist/lib/factories/context.mjs +38 -0
  10. package/dist/lib/handlers/api-cache-manager.mjs +216 -0
  11. package/dist/lib/handlers/api-manager.mjs +2364 -0
  12. package/dist/lib/handlers/context-async.mjs +184 -0
  13. package/dist/lib/handlers/context-live.mjs +184 -0
  14. package/dist/lib/handlers/hook-manager.mjs +789 -0
  15. package/dist/lib/handlers/lifecycle-token.mjs +44 -0
  16. package/dist/lib/handlers/lifecycle.mjs +131 -0
  17. package/dist/lib/handlers/materialize-manager.mjs +64 -0
  18. package/dist/lib/handlers/metadata.mjs +500 -0
  19. package/dist/lib/handlers/ownership.mjs +338 -0
  20. package/dist/lib/handlers/unified-wrapper.mjs +3031 -0
  21. package/dist/lib/helpers/class-instance-wrapper.mjs +125 -0
  22. package/dist/lib/helpers/config.mjs +343 -0
  23. package/dist/lib/helpers/eventemitter-context.mjs +365 -0
  24. package/dist/lib/helpers/hint-detector.mjs +63 -0
  25. package/dist/lib/helpers/modes-utils.mjs +53 -0
  26. package/dist/lib/helpers/resolve-from-caller.mjs +123 -117
  27. package/dist/lib/helpers/sanitize.mjs +247 -168
  28. package/dist/lib/helpers/utilities.mjs +46 -81
  29. package/dist/lib/i18n/languages/de-de.json +377 -0
  30. package/dist/lib/i18n/languages/en-gb.json +377 -0
  31. package/dist/lib/i18n/languages/en-us.json +377 -0
  32. package/dist/lib/i18n/languages/es-mx.json +377 -0
  33. package/dist/lib/i18n/languages/fr-fr.json +377 -0
  34. package/dist/lib/i18n/languages/hi-in.json +377 -0
  35. package/dist/lib/i18n/languages/ja-jp.json +377 -0
  36. package/dist/lib/i18n/languages/ko-kr.json +377 -0
  37. package/dist/lib/i18n/languages/pt-br.json +377 -0
  38. package/dist/lib/i18n/languages/ru-ru.json +377 -0
  39. package/dist/lib/i18n/languages/zh-cn.json +377 -0
  40. package/dist/lib/i18n/translations.mjs +140 -0
  41. package/dist/lib/modes/eager.mjs +75 -0
  42. package/dist/lib/modes/lazy.mjs +97 -0
  43. package/dist/lib/processors/flatten.mjs +453 -0
  44. package/dist/lib/processors/loader.mjs +355 -0
  45. package/dist/lib/processors/type-generator.mjs +291 -0
  46. package/dist/lib/processors/typescript.mjs +188 -0
  47. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +80 -522
  48. package/dist/lib/runtime/runtime-livebindings.mjs +45 -390
  49. package/dist/lib/runtime/runtime.mjs +39 -159
  50. package/dist/slothlet.mjs +525 -744
  51. package/docs/API-RULES.md +338 -486
  52. package/index.cjs +4 -4
  53. package/index.mjs +82 -45
  54. package/package.json +143 -30
  55. package/types/dist/lib/builders/api-assignment.d.mts +97 -0
  56. package/types/dist/lib/builders/api-assignment.d.mts.map +1 -0
  57. package/types/dist/lib/builders/api_builder.d.mts +96 -0
  58. package/types/dist/lib/builders/api_builder.d.mts.map +1 -0
  59. package/types/dist/lib/builders/builder.d.mts +60 -0
  60. package/types/dist/lib/builders/builder.d.mts.map +1 -0
  61. package/types/dist/lib/builders/modes-processor.d.mts +32 -0
  62. package/types/dist/lib/builders/modes-processor.d.mts.map +1 -0
  63. package/types/dist/lib/errors.d.mts +118 -0
  64. package/types/dist/lib/errors.d.mts.map +1 -0
  65. package/types/dist/lib/factories/component-base.d.mts +182 -0
  66. package/types/dist/lib/factories/component-base.d.mts.map +1 -0
  67. package/types/dist/lib/factories/context.d.mts +26 -0
  68. package/types/dist/lib/factories/context.d.mts.map +1 -0
  69. package/types/dist/lib/handlers/api-cache-manager.d.mts +208 -0
  70. package/types/dist/lib/handlers/api-cache-manager.d.mts.map +1 -0
  71. package/types/dist/lib/handlers/api-manager.d.mts +392 -0
  72. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -0
  73. package/types/dist/lib/handlers/context-async.d.mts +66 -0
  74. package/types/dist/lib/handlers/context-async.d.mts.map +1 -0
  75. package/types/dist/lib/handlers/context-live.d.mts +65 -0
  76. package/types/dist/lib/handlers/context-live.d.mts.map +1 -0
  77. package/types/dist/lib/handlers/hook-manager.d.mts +199 -0
  78. package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -0
  79. package/types/dist/lib/handlers/lifecycle-token.d.mts +49 -0
  80. package/types/dist/lib/handlers/lifecycle-token.d.mts.map +1 -0
  81. package/types/dist/lib/handlers/lifecycle.d.mts +90 -0
  82. package/types/dist/lib/handlers/lifecycle.d.mts.map +1 -0
  83. package/types/dist/lib/handlers/materialize-manager.d.mts +75 -0
  84. package/types/dist/lib/handlers/materialize-manager.d.mts.map +1 -0
  85. package/types/dist/lib/handlers/metadata.d.mts +215 -0
  86. package/types/dist/lib/handlers/metadata.d.mts.map +1 -0
  87. package/types/dist/lib/handlers/ownership.d.mts +170 -0
  88. package/types/dist/lib/handlers/ownership.d.mts.map +1 -0
  89. package/types/dist/lib/handlers/unified-wrapper.d.mts +250 -0
  90. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -0
  91. package/types/dist/lib/helpers/class-instance-wrapper.d.mts +54 -0
  92. package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -0
  93. package/types/dist/lib/helpers/config.d.mts +96 -0
  94. package/types/dist/lib/helpers/config.d.mts.map +1 -0
  95. package/types/dist/lib/helpers/eventemitter-context.d.mts +31 -0
  96. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -0
  97. package/types/dist/lib/helpers/hint-detector.d.mts +20 -0
  98. package/types/dist/lib/helpers/hint-detector.d.mts.map +1 -0
  99. package/types/dist/lib/helpers/modes-utils.d.mts +35 -0
  100. package/types/dist/lib/helpers/modes-utils.d.mts.map +1 -0
  101. package/types/dist/lib/helpers/resolve-from-caller.d.mts +29 -145
  102. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  103. package/types/dist/lib/helpers/sanitize.d.mts +95 -94
  104. package/types/dist/lib/helpers/sanitize.d.mts.map +1 -1
  105. package/types/dist/lib/helpers/utilities.d.mts +53 -116
  106. package/types/dist/lib/helpers/utilities.d.mts.map +1 -1
  107. package/types/dist/lib/i18n/translations.d.mts +39 -0
  108. package/types/dist/lib/i18n/translations.d.mts.map +1 -0
  109. package/types/dist/lib/modes/eager.d.mts +36 -0
  110. package/types/dist/lib/modes/eager.d.mts.map +1 -0
  111. package/types/dist/lib/modes/lazy.d.mts +49 -0
  112. package/types/dist/lib/modes/lazy.d.mts.map +1 -0
  113. package/types/dist/lib/processors/flatten.d.mts +114 -0
  114. package/types/dist/lib/processors/flatten.d.mts.map +1 -0
  115. package/types/dist/lib/processors/loader.d.mts +47 -0
  116. package/types/dist/lib/processors/loader.d.mts.map +1 -0
  117. package/types/dist/lib/processors/type-generator.d.mts +19 -0
  118. package/types/dist/lib/processors/type-generator.d.mts.map +1 -0
  119. package/types/dist/lib/processors/typescript.d.mts +55 -0
  120. package/types/dist/lib/processors/typescript.d.mts.map +1 -0
  121. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +47 -42
  122. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  123. package/types/dist/lib/runtime/runtime-livebindings.d.mts +34 -65
  124. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  125. package/types/dist/lib/runtime/runtime.d.mts +39 -9
  126. package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
  127. package/types/dist/slothlet.d.mts +184 -111
  128. package/types/dist/slothlet.d.mts.map +1 -1
  129. package/types/index.d.mts +1 -3
  130. package/dist/lib/engine/README.md +0 -21
  131. package/dist/lib/engine/slothlet_child.mjs +0 -59
  132. package/dist/lib/engine/slothlet_engine.mjs +0 -372
  133. package/dist/lib/engine/slothlet_esm.mjs +0 -230
  134. package/dist/lib/engine/slothlet_helpers.mjs +0 -455
  135. package/dist/lib/engine/slothlet_worker.mjs +0 -149
  136. package/dist/lib/helpers/als-eventemitter.mjs +0 -256
  137. package/dist/lib/helpers/api_builder/add_api.mjs +0 -553
  138. package/dist/lib/helpers/api_builder/analysis.mjs +0 -532
  139. package/dist/lib/helpers/api_builder/construction.mjs +0 -495
  140. package/dist/lib/helpers/api_builder/decisions.mjs +0 -748
  141. package/dist/lib/helpers/api_builder/metadata.mjs +0 -248
  142. package/dist/lib/helpers/api_builder.mjs +0 -41
  143. package/dist/lib/helpers/auto-wrap.mjs +0 -62
  144. package/dist/lib/helpers/hooks.mjs +0 -389
  145. package/dist/lib/helpers/instance-manager.mjs +0 -111
  146. package/dist/lib/helpers/metadata-api.mjs +0 -201
  147. package/dist/lib/helpers/multidefault.mjs +0 -216
  148. package/dist/lib/modes/slothlet_eager.mjs +0 -154
  149. package/dist/lib/modes/slothlet_lazy.mjs +0 -594
  150. package/docs/API-RULES-CONDITIONS.md +0 -712
  151. package/types/dist/lib/engine/slothlet_child.d.mts +0 -2
  152. package/types/dist/lib/engine/slothlet_child.d.mts.map +0 -1
  153. package/types/dist/lib/engine/slothlet_engine.d.mts +0 -31
  154. package/types/dist/lib/engine/slothlet_engine.d.mts.map +0 -1
  155. package/types/dist/lib/engine/slothlet_esm.d.mts +0 -19
  156. package/types/dist/lib/engine/slothlet_esm.d.mts.map +0 -1
  157. package/types/dist/lib/engine/slothlet_helpers.d.mts +0 -25
  158. package/types/dist/lib/engine/slothlet_helpers.d.mts.map +0 -1
  159. package/types/dist/lib/engine/slothlet_worker.d.mts +0 -2
  160. package/types/dist/lib/engine/slothlet_worker.d.mts.map +0 -1
  161. package/types/dist/lib/helpers/als-eventemitter.d.mts +0 -56
  162. package/types/dist/lib/helpers/als-eventemitter.d.mts.map +0 -1
  163. package/types/dist/lib/helpers/api_builder/add_api.d.mts +0 -102
  164. package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +0 -1
  165. package/types/dist/lib/helpers/api_builder/analysis.d.mts +0 -189
  166. package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +0 -1
  167. package/types/dist/lib/helpers/api_builder/construction.d.mts +0 -107
  168. package/types/dist/lib/helpers/api_builder/construction.d.mts.map +0 -1
  169. package/types/dist/lib/helpers/api_builder/decisions.d.mts +0 -213
  170. package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +0 -1
  171. package/types/dist/lib/helpers/api_builder/metadata.d.mts +0 -99
  172. package/types/dist/lib/helpers/api_builder/metadata.d.mts.map +0 -1
  173. package/types/dist/lib/helpers/api_builder.d.mts +0 -6
  174. package/types/dist/lib/helpers/api_builder.d.mts.map +0 -1
  175. package/types/dist/lib/helpers/auto-wrap.d.mts +0 -49
  176. package/types/dist/lib/helpers/auto-wrap.d.mts.map +0 -1
  177. package/types/dist/lib/helpers/hooks.d.mts +0 -342
  178. package/types/dist/lib/helpers/hooks.d.mts.map +0 -1
  179. package/types/dist/lib/helpers/instance-manager.d.mts +0 -41
  180. package/types/dist/lib/helpers/instance-manager.d.mts.map +0 -1
  181. package/types/dist/lib/helpers/metadata-api.d.mts +0 -132
  182. package/types/dist/lib/helpers/metadata-api.d.mts.map +0 -1
  183. package/types/dist/lib/helpers/multidefault.d.mts +0 -90
  184. package/types/dist/lib/helpers/multidefault.d.mts.map +0 -1
  185. package/types/dist/lib/modes/slothlet_eager.d.mts +0 -65
  186. package/types/dist/lib/modes/slothlet_eager.d.mts.map +0 -1
  187. package/types/dist/lib/modes/slothlet_lazy.d.mts +0 -31
  188. package/types/dist/lib/modes/slothlet_lazy.d.mts.map +0 -1
  189. package/types/index.d.mts.map +0 -1
package/dist/slothlet.mjs CHANGED
@@ -17,924 +17,705 @@
17
17
 
18
18
 
19
19
 
20
- import fs from "node:fs/promises";
21
- import path from "node:path";
20
+ import { readdirSync } from "node:fs";
21
+ import { dirname, join } from "node:path";
22
22
  import { fileURLToPath, pathToFileURL } from "node:url";
23
-
24
- import { resolvePathFromCaller } from "@cldmv/slothlet/helpers/resolve-from-caller";
23
+ import { getContextManager } from "@cldmv/slothlet/factories/context";
24
+ import { SlothletError, SlothletWarning, SlothletDebug } from "@cldmv/slothlet/errors";
25
+ import { registerInstance } from "@cldmv/slothlet/handlers/lifecycle-token";
26
+ import { initI18n } from "@cldmv/slothlet/i18n";
25
27
  import {
26
- analyzeModule,
27
- processModuleFromAnalysis,
28
- buildCategoryStructure,
29
- toapiPathKey,
30
- shouldIncludeFile,
31
- safeDefine,
32
- deepMerge,
33
- mutateLiveBindingFunction,
34
- addApiFromFolder
35
- } from "@cldmv/slothlet/helpers/api_builder";
36
- import { updateInstanceData, cleanupInstance } from "@cldmv/slothlet/helpers/instance-manager";
37
- import { disableAlsForEventEmitters, cleanupAllSlothletListeners } from "@cldmv/slothlet/helpers/als-eventemitter";
38
- import { HookManager } from "@cldmv/slothlet/helpers/hooks";
39
-
40
-
41
-
42
-
43
- function normalizeRuntimeType(runtime) {
44
- if (!runtime || typeof runtime !== "string") {
45
- return "async";
46
- }
28
+ enableEventEmitterPatching,
29
+ disableEventEmitterPatching,
30
+ cleanupEventEmitterResources,
31
+ setApiContextChecker
32
+ } from "@cldmv/slothlet/helpers/eventemitter-context";
47
33
 
48
- const normalized = runtime.toLowerCase().trim();
49
34
 
35
+ class Slothlet {
50
36
 
51
- if (normalized === "async" || normalized === "asynclocal" || normalized === "asynclocalstorage") {
52
- return "async";
53
- }
54
-
55
37
 
56
- if (normalized === "live" || normalized === "livebindings" || normalized === "experimental") {
57
- return "live";
58
- }
38
+ static RESERVED_ROOT_KEYS = ["slothlet", "shutdown", "destroy"];
59
39
 
60
40
 
61
- return "async";
62
- }
63
-
64
- const __filename = fileURLToPath(import.meta.url);
65
- const __dirname = path.dirname(__filename);
66
-
67
-
68
-
69
-
70
-
71
- let DEBUG = process.argv.includes("--slothletdebug")
72
- ? true
73
- : process.env.SLOTHLET_DEBUG === "1" || process.env.SLOTHLET_DEBUG === "true"
74
- ? true
75
- : false;
76
-
41
+ static SKIP_PROPS = ["__metadata", "__type", "_materialize", "_impl", "____slothletInternal"];
77
42
 
78
- if (DEBUG && !process.env.SLOTHLET_DEBUG) {
79
- process.env.SLOTHLET_DEBUG = "1";
80
- }
81
-
82
-
83
- export const self = {};
84
-
85
-
86
- export const context = {};
87
-
88
-
89
- export const reference = {};
90
-
91
-
92
- async function slothlet(options = {}) {
93
-
94
- const instance = createFreshInstance();
95
-
96
-
97
- for (const key of Object.keys(slothlet)) {
98
- if (key.startsWith("_")) continue;
43
+ constructor() {
99
44
 
100
- if (key === "name" || key === "length") continue;
101
- try {
102
- delete slothlet[key];
103
- } catch {
104
-
105
- }
106
- }
45
+ this.SlothletError = SlothletError;
46
+ this.SlothletWarning = SlothletWarning;
47
+
48
+ this.debugLogger = null;
107
49
 
108
-
109
- for (const [k, v] of Object.entries(instance)) {
110
- if (typeof v === "function") {
111
-
112
- Object.defineProperty(slothlet, k, { value: v.bind(instance), writable: false, enumerable: true, configurable: true });
113
- } else {
114
-
115
- Object.defineProperty(slothlet, k, { value: instance[k], writable: true, enumerable: true, configurable: true });
116
- }
117
- }
118
- Object.defineProperty(slothlet, "_instance", { value: instance, writable: false, enumerable: false, configurable: true });
50
+
51
+ this.instanceID = null;
52
+ this.config = null;
53
+ this.api = null;
54
+ this.boundApi = null;
55
+ this.contextManager = null;
56
+ this.isLoaded = false;
57
+ this.reference = null;
58
+ this.context = null;
119
59
 
120
-
121
- return await instance.create(options);
122
- }
60
+
61
+ this._totalLazyCount = 0;
62
+ this._unmaterializedLazyCount = 0;
63
+ this._materializationComplete = false;
64
+ this._materializationWaiters = [];
65
+ this._materializationCompleteEmitted = false;
123
66
 
67
+
68
+ this.componentCategories = ["helpers", "handlers", "builders", "processors", "modes"];
124
69
 
125
- function createFreshInstance() {
126
- const instance = {};
127
- for (const [k, v] of Object.entries(slothletObject)) {
128
- if (typeof v === "function") {
129
-
130
- instance[k] = v;
131
- continue;
132
- }
133
70
 
134
- if (k === "config") instance[k] = { ...v };
135
- else if (k === "boundapi" || k === "context" || k === "reference") instance[k] = {};
136
- else instance[k] = v;
71
+ for (const category of this.componentCategories) {
72
+ this[category] = {};
73
+ }
137
74
  }
138
- return instance;
139
- }
140
-
141
-
142
- const slothletObject = {
143
-
144
- api: null,
145
- self: {},
146
- boundapi: {},
147
- context: {},
148
- reference: {},
149
- mode: "singleton",
150
- loaded: false,
151
- config: {
152
- lazy: false,
153
- apiDepth: Infinity,
154
- debug: DEBUG,
155
- dir: null,
156
- sanitize: null,
157
- allowApiOverwrite: true,
158
- enableModuleOwnership: false
159
- },
160
-
161
- _moduleOwnership: new Map(),
162
- _dispose: null,
163
- _boundAPIShutdown: null,
164
- instanceId: null,
165
75
 
166
76
 
167
- async create(options = {}) {
168
- if (this.loaded) {
169
- console.warn("[slothlet] create: API already loaded, returning existing instance.");
170
- return this.boundapi;
171
- }
172
-
77
+ async _initializeComponents() {
173
78
 
174
79
 
175
80
 
176
- const { entry = import.meta.url, engine = "singleton", mode, api_mode = "auto" } = options ?? {};
177
-
178
81
 
179
- const executionEngine = mode && ["singleton", "vm", "worker", "fork"].includes(mode) ? mode : engine;
180
-
181
82
 
182
- let isLazyMode;
183
- if (mode && ["lazy", "eager"].includes(mode)) {
184
- isLazyMode = mode === "lazy";
185
- } else {
186
- isLazyMode = options.lazy !== undefined ? options.lazy : this.config.lazy;
187
- }
188
83
 
189
-
190
- const runtimeType = normalizeRuntimeType(options.runtime || "async");
191
- const loadingModeStr = isLazyMode ? "lazy" : "eager";
192
- this.instanceId = `slothlet_${runtimeType}_${loadingModeStr}_${Date.now()}_${Math.random().toString(36).slice(2, 11).padEnd(9, "0")}`;
84
+ const baseDir = join(dirname(fileURLToPath(import.meta.url)), "lib");
193
85
 
194
-
195
- if (!this.modes) {
196
- this.modes = {};
197
- const modesDir = path.join(__dirname, "lib", "modes");
86
+ for (const category of this.componentCategories) {
87
+ const categoryDir = join(baseDir, category);
88
+ const files = readdirSync(categoryDir).filter((f) => f.endsWith(".mjs"));
198
89
 
199
- const dirents = await fs.readdir(modesDir, { withFileTypes: true });
200
- const modeFiles = dirents.filter((d) => d.isFile()).map((d) => path.join(modesDir, d.name));
90
+ for (const file of files) {
91
+ const filePath = join(categoryDir, file);
201
92
 
202
- for (const file of modeFiles) {
203
- const modePath = file;
204
-
205
- const modeName = path.parse(file).name.replace(/^slothlet_/, "");
206
- if (!modeName || modeName.includes(" ")) continue;
207
93
  try {
94
+ const module = await import(pathToFileURL(filePath).href);
95
+
208
96
 
209
- const modeUrl = pathToFileURL(modePath).href;
97
+ const classExports = Object.values(module).filter((exp) => typeof exp === "function" && exp.slothletProperty);
210
98
 
211
- const imported = await import(modeUrl);
212
- if (imported && typeof imported === "object") {
213
- this.modes[modeName] = imported.default || imported;
99
+ for (const ClassExport of classExports) {
100
+ const propName = ClassExport.slothletProperty;
101
+
102
+ this[category][propName] = new ClassExport(this);
103
+
104
+ if (this.config?.debug?.initialization) {
105
+ this.debug("initialization", {
106
+ key: "DEBUG_MODE_COMPONENT_INITIALIZED",
107
+ component: ClassExport.name,
108
+ category,
109
+ propertyName: propName
110
+ });
111
+ }
214
112
  }
215
- } catch (err) {
216
- console.error(`[slothlet] Could not import mode '${modeName}':`, err);
113
+ } catch (error) {
114
+
115
+
116
+
117
+
118
+ throw new this.SlothletError(
119
+ "MODULE_IMPORT_FAILED",
120
+ {
121
+ modulePath: filePath
122
+ },
123
+ error
124
+ );
217
125
  }
218
126
  }
219
127
  }
128
+ }
220
129
 
221
- this.mode = executionEngine;
222
- this.api_mode = api_mode;
223
- let api;
224
- let dispose;
225
- if (executionEngine === "singleton") {
226
-
227
-
228
- const { context = null, reference = null, sanitize = null, hooks = false, scope, engine, mode, ...loadConfig } = options;
229
- this.context = context;
230
- this.reference = reference;
130
+
131
+ _setupLifecycleSubscribers() {
132
+
133
+
134
+ if (!this.handlers.lifecycle) {
135
+ return;
136
+ }
137
+
231
138
 
232
-
233
- let hooksEnabled = false;
234
- let hooksPattern = null;
235
- let hooksSuppressErrors = false;
139
+
140
+
141
+
142
+ if (this.handlers.metadata) {
143
+ this.handlers.lifecycle.subscribe("impl:created", (data, token) => {
144
+ this.handlers.metadata.tagSystemMetadata(
145
+ data.impl,
146
+ {
147
+ filePath: data.filePath,
148
+ apiPath: data.apiPath,
149
+ moduleID: data.moduleID,
150
+ sourceFolder: data.sourceFolder
151
+ },
152
+ token
153
+ );
154
+ });
155
+
156
+ this.handlers.lifecycle.subscribe("impl:changed", (data, token) => {
157
+ this.handlers.metadata.tagSystemMetadata(
158
+ data.impl,
159
+ {
160
+ filePath: data.filePath,
161
+ apiPath: data.apiPath,
162
+ moduleID: data.moduleID,
163
+ sourceFolder: data.sourceFolder
164
+ },
165
+ token
166
+ );
167
+ });
236
168
 
237
- if (hooks === true || hooks === false) {
169
+ this.handlers.lifecycle.subscribe("impl:removed", (data) => {
238
170
 
239
- hooksEnabled = hooks;
240
- hooksPattern = hooks ? "**" : null;
241
- } else if (typeof hooks === "string") {
242
171
 
243
- hooksEnabled = true;
244
- hooksPattern = hooks;
245
- } else if (hooks && typeof hooks === "object") {
246
172
 
247
- hooksEnabled = hooks.enabled !== false;
248
- hooksPattern = hooks.pattern || "**";
249
- hooksSuppressErrors = hooks.suppressErrors || false;
250
- }
251
-
252
-
253
- this.hookManager = new HookManager(hooksEnabled, hooksPattern, { suppressErrors: hooksSuppressErrors });
254
-
255
-
256
- if (scope && typeof scope === "object") {
257
- const mergeStrategy = scope.merge || "shallow";
258
- if (mergeStrategy !== "shallow" && mergeStrategy !== "deep") {
259
- throw new TypeError(`Invalid scope.merge value: "${mergeStrategy}". Must be "shallow" or "deep".`);
173
+ if (data.apiPath) {
174
+ const rootSegment = data.apiPath.split(".")[0];
175
+ this.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
260
176
  }
261
- this.config.scope = { merge: mergeStrategy };
262
- } else if (scope === false) {
263
- this.config.scope = { enabled: false };
264
- } else {
265
-
266
- this.config.scope = { merge: "shallow" };
267
- }
177
+ });
178
+ }
268
179
 
180
+
181
+
182
+
183
+ if (this.handlers.ownership) {
269
184
 
270
- if (sanitize !== null) {
271
- this.config.sanitize = sanitize;
272
- }
273
-
274
185
 
275
- loadConfig.lazy = isLazyMode;
186
+ this.handlers.lifecycle.subscribe("impl:created", (data) => {
187
+
188
+
189
+
190
+ const collisionMode = this.config?.collision?.api || "merge";
191
+
192
+ const implValue = data.wrapper?.__impl ?? data.impl;
193
+ this.handlers.ownership.register({
194
+ moduleID: data.moduleID,
195
+ apiPath: data.apiPath,
196
+ value: implValue,
197
+ source: data.source,
198
+ filePath: data.filePath,
199
+ collisionMode: collisionMode
200
+ });
201
+ });
276
202
 
277
203
 
278
204
 
279
- if (this.api_mode === "function") {
280
- this.boundapi = function (...args) {
281
- if (typeof this.boundapi._impl === "function") {
282
- return this.boundapi._impl(...args);
283
- }
284
- }.bind(this);
285
- this.boundapi._impl = () => {};
286
- } else if (this.api_mode === "auto") {
205
+ this.handlers.lifecycle.subscribe("impl:changed", (data) => {
287
206
 
288
- this.boundapi = {};
289
- } else {
290
- this.boundapi = {};
291
- }
292
-
293
- await this.load(loadConfig, { context, reference });
207
+
208
+
209
+ const collisionMode = this.config?.collision?.api || "merge";
210
+
211
+
212
+
213
+ const implValue = data.wrapper?.__impl ?? data.impl;
294
214
 
295
-
296
- return this.boundapi;
297
- } else {
298
- const { createEngine } = await import("./lib/engine/slothlet_engine.mjs");
299
-
300
- ({ api, dispose } = await createEngine({ entry, mode: executionEngine, ...options }));
301
-
302
-
303
- if (typeof dispose === "function") {
304
- Object.defineProperty(api, "__dispose__", {
305
- value: dispose,
306
- writable: false,
307
- enumerable: false,
308
- configurable: false
309
- });
310
- }
311
- this._dispose = dispose;
312
- this.loaded = true;
313
- return api;
215
+
216
+
217
+ const currentOwner = this.handlers.ownership.getCurrentOwner(data.apiPath);
218
+ if (currentOwner?.moduleID !== data.moduleID) {
219
+ this.handlers.ownership.register({
220
+ moduleID: data.moduleID,
221
+ apiPath: data.apiPath,
222
+ value: implValue,
223
+ source: data.source,
224
+ filePath: data.filePath,
225
+ collisionMode: collisionMode
226
+ });
227
+ }
228
+ });
314
229
  }
315
- },
230
+ }
316
231
 
317
232
 
318
- async load(config = {}, ctxRef = { context: null, reference: null }) {
319
- this.config = { ...this.config, ...config };
233
+ _registerLazyWrapper() {
234
+ this._totalLazyCount++;
235
+ this._unmaterializedLazyCount++;
236
+
237
+ if (this.config?.debug?.materialize) {
238
+ this.debug("materialize", {
239
+ key: "DEBUG_MODE_LAZY_WRAPPER_REGISTERED",
240
+ total: this._totalLazyCount,
241
+ unmaterialized: this._unmaterializedLazyCount
242
+ });
243
+ }
244
+ }
320
245
 
321
-
322
- this.config.runtime = normalizeRuntimeType(this.config.runtime);
323
-
324
- if (!this.runtime) {
325
- if (this.config.runtime === "live") {
326
- this.runtime = await import("@cldmv/slothlet/runtime/live");
327
- } else {
246
+
247
+ _onWrapperMaterialized() {
248
+ this._unmaterializedLazyCount--;
249
+
250
+ if (this.config?.debug?.materialize) {
251
+ this.debug("materialize", {
252
+ key: "DEBUG_MODE_LAZY_WRAPPER_MATERIALIZED",
253
+ total: this._totalLazyCount,
254
+ unmaterialized: this._unmaterializedLazyCount,
328
255
 
329
- this.runtime = await import("@cldmv/slothlet/runtime/async");
330
- }
256
+
257
+ percentage: this._totalLazyCount > 0 ? ((this._totalLazyCount - this._unmaterializedLazyCount) / this._totalLazyCount) * 100 : 100
258
+ });
331
259
  }
332
260
 
333
- let apiDir = this.config.dir || "api";
334
261
 
335
- if (apiDir && !path.isAbsolute(apiDir)) {
336
- apiDir = resolvePathFromCaller(apiDir);
337
- }
338
-
339
- if (this.loaded) return this.api;
340
- if (this.config.lazy) {
341
- this.api = await this.modes.lazy.create.call(this, apiDir, this.config.apiDepth || Infinity, 0);
342
- } else {
343
- this.api = await this.modes.eager.create.call(this, apiDir, this.config.apiDepth || Infinity, 0);
344
- }
262
+ if (this._unmaterializedLazyCount === 0 && !this._materializationComplete) {
263
+ this._materializationComplete = true;
345
264
 
346
-
347
- if (this.hookManager) {
348
- const hooksApi = {
349
- on: (name, type, handler, options) => this.hookManager.on(name, type, handler, options),
350
- off: (idOrPattern) => this.hookManager.off(idOrPattern),
351
- enable: (pattern) => this.hookManager.enable(pattern),
352
- disable: () => this.hookManager.disable(),
353
- clear: (type) => this.hookManager.clear(type),
354
- list: (type) => this.hookManager.list(type)
355
- };
265
+ if (this.config?.debug?.materialize) {
266
+ this.debug("materialize", {
267
+ key: "DEBUG_MODE_ALL_LAZY_WRAPPERS_MATERIALIZED",
268
+ total: this._totalLazyCount
269
+ });
270
+ }
356
271
 
357
272
 
358
- Object.defineProperty(this.api, "hooks", {
359
- value: hooksApi,
360
- writable: false,
361
- enumerable: true,
362
- configurable: true
363
- });
364
- }
365
-
366
- if (this.config.debug) console.log(this.api);
367
-
368
-
369
- if (this.api_mode === "auto") {
370
- this.api_mode = typeof this.api === "function" ? "function" : "object";
371
- if (this.config.debug) {
372
- console.log(`[DEBUG] Auto-detected api_mode: ${this.api_mode} (API is ${typeof this.api})`);
273
+ const waiters = this._materializationWaiters.splice(0);
274
+ for (const resolve of waiters) {
275
+ resolve();
373
276
  }
374
277
 
375
278
 
376
- if (this.api_mode === "function" && typeof this.boundapi !== "function") {
377
- this.boundapi = function (...args) {
378
-
379
- if (typeof this.boundapi._impl === "function") {
380
- return this.boundapi._impl(...args);
381
- }
382
- }.bind(this);
383
- this.boundapi._impl = () => {};
384
- } else if (this.api_mode === "object" && typeof this.boundapi === "function") {
385
- this.boundapi = {};
279
+ if (this.config?.tracking?.materialization && !this._materializationCompleteEmitted) {
280
+ this._materializationCompleteEmitted = true;
281
+
282
+
283
+ if (this.handlers?.lifecycle) {
284
+ this.handlers.lifecycle.emit("materialized:complete", {
285
+ total: this._totalLazyCount,
286
+ timestamp: Date.now()
287
+ });
288
+ }
386
289
  }
387
290
  }
388
-
389
- const l_ctxRef = { ...{ context: null, reference: null }, ...ctxRef };
390
-
391
- const _boundapi = this.createBoundApi(l_ctxRef.reference);
392
-
393
- mutateLiveBindingFunction(this.boundapi, _boundapi);
394
-
395
- this.updateBindings(this.context, this.reference, this.boundapi);
396
-
397
- this.loaded = true;
398
-
399
- return this.boundapi;
400
- },
291
+ }
401
292
 
402
293
 
403
- _toapiPathKey(name) {
404
- return toapiPathKey(name, this.config.sanitize || {});
405
- },
294
+ async load(config = {}, preservedInstanceID = null) {
295
+
296
+ this.config = config;
406
297
 
407
-
408
- async _buildCategory(categoryPath, options = {}) {
409
- const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler, existingApi } = options;
298
+
299
+
300
+
301
+ this.debugLogger = new SlothletDebug(config);
410
302
 
411
303
 
412
- return buildCategoryStructure(categoryPath, {
413
- currentDepth,
414
- maxDepth,
415
- mode,
416
- subdirHandler,
417
- instance: this,
418
- existingApi
419
- });
420
- },
304
+
305
+ await this._initializeComponents();
421
306
 
422
-
423
- async _loadCategory(categoryPath, currentDepth = 0, maxDepth = Infinity) {
424
- return this._buildCategory(categoryPath, { currentDepth, maxDepth, mode: "eager" });
425
- },
307
+
308
+ registerInstance(this);
426
309
 
427
-
428
- async _loadSingleModule(modulePath, returnAnalysis = false) {
429
310
 
430
- const analysis = await analyzeModule(modulePath, {
431
- debug: this.config.debug,
432
- instance: this
433
- });
434
- const processedModule = processModuleFromAnalysis(analysis, {
435
- debug: this.config.debug,
436
- instance: this
437
- });
311
+ this._setupLifecycleSubscribers();
312
+
313
+
314
+ this.config = this.helpers.config.transformConfig(config);
438
315
 
439
316
 
440
- if (returnAnalysis) {
441
- return { mod: processedModule, analysis };
317
+ if (this.config?.i18n?.language) {
318
+ initI18n({ language: this.config.i18n.language });
442
319
  }
443
320
 
444
321
 
445
- return processedModule;
446
- },
322
+
323
+ this.debugLogger = new SlothletDebug(this.config);
447
324
 
448
-
449
- _shouldIncludeFile(entry) {
450
- return shouldIncludeFile(entry);
451
- },
325
+
326
+ this.instanceID = preservedInstanceID || this.helpers.utilities.generateId();
452
327
 
453
-
454
- updateBoundApiProperty(key, materializedValue) {
455
- if (this.boundapi && key) {
456
- try {
457
-
458
- Object.defineProperty(this.boundapi, key, {
459
- value: materializedValue,
460
- writable: true,
461
- enumerable: true,
462
- configurable: true
463
- });
328
+
329
+ this.reference = this.config.reference;
330
+ this.context = this.config.context;
464
331
 
465
-
466
- if (self && typeof self === "object") {
467
- Object.defineProperty(self, key, {
468
- value: materializedValue,
469
- writable: true,
470
- enumerable: true,
471
- configurable: true
472
- });
473
- }
332
+
333
+ this.contextManager = getContextManager(this.config.runtime);
474
334
 
475
-
476
-
477
- const currentApi = this.api;
478
- if (currentApi && currentApi[key] === materializedValue) {
479
- mutateLiveBindingFunction(this.boundapi, currentApi);
480
- }
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+ setApiContextChecker(() => {
344
+
345
+ const ctx = this.contextManager.tryGetContext();
346
+ return !!(ctx && ctx.self);
347
+
348
+ });
349
+
481
350
 
482
- if (this.config.debug) {
483
- console.log(`[DEBUG] Updated boundapi.${key} with materialized value`);
484
- console.log(`[DEBUG] boundapi.${key} is now:`, typeof this.boundapi[key]);
485
- console.log(`[DEBUG] boundapi.${key} keys:`, Object.keys(this.boundapi[key] || {}));
486
- }
487
- } catch (error) {
488
- console.warn(`[slothlet] Failed to update boundapi.${key}:`, error.message);
489
- }
351
+
352
+ let store;
353
+ if (preservedInstanceID && this.contextManager.instances.has(preservedInstanceID)) {
354
+
355
+ this.contextManager.cleanup(preservedInstanceID);
356
+ store = this.contextManager.initialize(this.instanceID, this.config);
357
+ } else {
358
+
359
+ store = this.contextManager.initialize(this.instanceID, this.config);
490
360
  }
491
- },
492
361
 
493
-
494
- updateBindings(newContext = null, newReference = null, newSelf = null) {
495
- if (newContext === null || (typeof newContext === "object" && Object.keys(newContext).length === 0)) newContext = { ...context };
496
- if (newReference === null || (typeof newReference === "object" && Object.keys(newReference).length === 0))
497
- newReference = { ...reference };
498
- if (newSelf === null || (typeof newSelf === "object" && Object.keys(newSelf).length === 0)) newSelf = this.boundapi;
499
-
500
362
 
501
363
 
364
+ enableEventEmitterPatching();
502
365
 
503
- mutateLiveBindingFunction(self, newSelf);
366
+
367
+
368
+
369
+ if (typeof this.contextManager.registerEventEmitterContextChecker === "function") {
370
+ this.contextManager.registerEventEmitterContextChecker();
371
+ }
504
372
 
505
373
 
506
- const contextWithRuntime = {
507
- ...newContext,
508
- runtimeType: this.config.runtime
509
- };
510
- Object.assign(context, contextWithRuntime || {});
511
- Object.assign(reference, newReference || {});
512
374
 
513
375
 
514
- if (this.instanceId) {
515
- updateInstanceData(this.instanceId, "self", newSelf);
516
- updateInstanceData(this.instanceId, "context", contextWithRuntime);
517
- updateInstanceData(this.instanceId, "reference", newReference);
518
- updateInstanceData(this.instanceId, "config", this.config);
519
- }
376
+ const baseModuleId = `base_${this.helpers.utilities.generateId().substring(0, 8)}`;
520
377
 
521
- this.safeDefine(this.boundapi, "__ctx", {
522
- self: this.boundapi,
523
- context: this.context,
524
- reference: this.reference,
525
- hookManager: this.hookManager
378
+
379
+
380
+ const baseApi = await this.builders.builder.buildAPI({
381
+ dir: this.config.dir,
382
+ mode: this.config.mode,
383
+ moduleID: baseModuleId
526
384
  });
527
- },
528
385
 
529
-
530
- createBoundApi(ref = null) {
531
- if (!this.api) throw new Error("BindleApi modules not loaded. Call load() first.");
532
-
533
- let boundApi;
386
+
387
+
388
+ this.api = baseApi;
534
389
 
535
- boundApi = this.api;
390
+
391
+ const apiWithBuiltins = await this.buildFinalAPI(this.api);
536
392
 
537
393
 
538
- if (ref && typeof ref === "object") {
539
- for (const [key, value] of Object.entries(ref)) {
540
- if (!(key in boundApi)) {
541
- try {
542
- boundApi[key] = value;
543
- } catch {
544
-
545
- }
546
- }
547
- }
394
+
395
+
396
+
397
+ if (this.handlers.apiCacheManager) {
398
+ this.handlers.apiCacheManager.set(baseModuleId, {
399
+ endpoint: ".",
400
+ moduleID: baseModuleId,
401
+ api: this.api,
402
+ folderPath: this.config.dir,
403
+ mode: this.config.mode,
404
+ sanitizeOptions: this.config.sanitize || {},
405
+
406
+
407
+ collisionMode: this.config.collision?.api || "merge",
408
+ config: { ...this.config },
409
+ timestamp: Date.now()
410
+ });
548
411
  }
549
412
 
550
413
 
551
- const instance = this;
552
- this.safeDefine(boundApi, "describe", function (showAll = false) {
414
+ this.injectRuntimeMetadataFunctions(apiWithBuiltins);
415
+
416
+
417
+ if (this.config.metadata && typeof this.config.metadata === "object") {
553
418
 
554
- if (instance.config && instance.config.lazy) {
555
- if (!showAll) {
556
- return Reflect.ownKeys(boundApi);
557
- }
558
-
559
- async function resolveAll(obj) {
560
- const keys = Reflect.ownKeys(obj);
561
- const entries = await Promise.all(
562
- keys.map(async (key) => {
563
- const value = obj[key];
564
-
565
- if (typeof value === "function" && value.constructor.name === "Proxy") {
566
- let resolved;
567
- try {
568
- resolved = await value();
569
- } catch {
570
- resolved = value;
571
- }
572
-
573
- if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
574
- return [key, await resolveAll(resolved)];
575
- }
576
- return [key, resolved];
577
- } else if (value && typeof value === "object" && !Array.isArray(value)) {
578
-
579
- return [key, await resolveAll(value)];
580
- } else {
581
- return [key, value];
582
- }
583
- })
584
- );
585
- const apiObj = {};
586
- for (const [key, val] of entries) {
587
- apiObj[key] = val;
588
- }
589
- return apiObj;
590
- }
591
- return resolveAll(boundApi);
419
+ for (const [key, value] of Object.entries(this.config.metadata)) {
420
+ this.handlers.metadata.setGlobalMetadata(key, value);
592
421
  }
422
+
593
423
 
594
- return { ...boundApi };
595
- });
424
+ this.handlers.metadata.registerUserMetadata(baseModuleId, this.config.metadata);
425
+ }
596
426
 
597
427
 
598
- if (
599
- typeof boundApi.shutdown === "function" &&
600
- boundApi.shutdown !== this.shutdown &&
601
- boundApi.shutdown.name !== "bound get" &&
602
- boundApi.shutdown.name !== "bound shutdown" &&
603
- boundApi.shutdown.toString().indexOf("[native code]") === -1 &&
604
- boundApi.shutdown.toString() !== this.shutdown.bind(this).toString()
605
- ) {
606
- this._boundAPIShutdown = boundApi.shutdown;
607
- } else {
608
- this._boundAPIShutdown = null;
428
+
429
+
430
+
431
+ if (this.handlers.ownership) {
432
+ this.handlers.ownership.registerSubtree(apiWithBuiltins, baseModuleId, "");
609
433
  }
610
434
 
611
435
 
612
436
 
613
-
614
- const hasUserDefinedShutdown = this._boundAPIShutdown !== null;
615
437
 
616
- const shutdownDesc = Object.getOwnPropertyDescriptor(boundApi, "shutdown");
617
- if (!shutdownDesc || shutdownDesc.configurable) {
618
- Object.defineProperty(boundApi, "shutdown", {
619
- value: this.shutdown.bind(this),
620
- writable: true,
621
- configurable: true,
622
- enumerable: hasUserDefinedShutdown
438
+
439
+ if (!this.boundApi) {
440
+
441
+
442
+
443
+ const isCallable = typeof this.api === "function" || (this.api && typeof this.api.default === "function");
444
+ const proxyTarget = isCallable ? function () {} : {};
445
+
446
+ this.boundApi = new Proxy(proxyTarget, {
447
+ get: (target, prop) => (this.api ? this.api[prop] : undefined),
448
+ set: (target, prop, value) => {
449
+ if (this.api) {
450
+ this.api[prop] = value;
451
+ }
452
+ return true;
453
+ },
454
+ has: (target, prop) => (this.api ? prop in this.api : false),
455
+ ownKeys: (____target) => (this.api ? Reflect.ownKeys(this.api) : []),
456
+ deleteProperty: (target, prop) => (this.api ? delete this.api[prop] : true),
457
+ apply: (target, thisArg, args) => (this.api ? Reflect.apply(this.api, thisArg, args) : undefined),
458
+ construct: (target, args) => (this.api ? Reflect.construct(this.api, args) : {}),
459
+ getOwnPropertyDescriptor: (target, prop) => {
460
+
461
+ if (isCallable && prop === "prototype") {
462
+ return Object.getOwnPropertyDescriptor(target, prop);
463
+ }
464
+ if (this.api && prop in this.api) {
465
+ const desc = Object.getOwnPropertyDescriptor(this.api, prop);
466
+ if (desc) {
467
+ return { ...desc, configurable: true };
468
+ }
469
+ }
470
+ return undefined;
471
+ }
623
472
  });
624
- } else if (this.config && this.config.debug) {
625
- console.warn("Could not redefine boundApi.shutdown: not configurable");
473
+
626
474
  }
627
475
 
628
476
 
629
- const addApiDesc = Object.getOwnPropertyDescriptor(boundApi, "addApi");
630
- if (!addApiDesc || addApiDesc.configurable) {
631
- Object.defineProperty(boundApi, "addApi", {
632
- value: this.addApi.bind(this),
633
- writable: true,
634
- configurable: true,
635
- enumerable: false
636
- });
637
- } else if (this.config && this.config.debug) {
638
- console.warn("Could not redefine boundApi.addApi: not configurable");
639
- }
477
+ store.self = this.boundApi;
478
+ store.context = this.context || {};
640
479
 
641
480
 
642
- const runDesc = Object.getOwnPropertyDescriptor(boundApi, "run");
643
- if (!runDesc || runDesc.configurable) {
644
- Object.defineProperty(boundApi, "run", {
645
- value: this.run.bind(this),
646
- writable: true,
647
- configurable: true,
648
- enumerable: false
649
- });
650
- } else if (this.config && this.config.debug) {
651
- console.warn("Could not redefine boundApi.run: not configurable");
481
+
482
+ if (this.reference && typeof this.reference === "object") {
483
+ Object.assign(this.boundApi, this.reference);
652
484
  }
653
485
 
486
+ this.isLoaded = true;
487
+
488
+ return this.boundApi;
489
+ }
490
+
491
+
492
+ async reload(options = {}) {
493
+ const { keepInstanceID = false } = options;
494
+
495
+
654
496
 
655
- const instanceIdDesc = Object.getOwnPropertyDescriptor(boundApi, "instanceId");
656
- if (!instanceIdDesc || instanceIdDesc.configurable) {
657
- Object.defineProperty(boundApi, "instanceId", {
658
- value: this.instanceId,
659
- writable: false,
660
- configurable: true,
661
- enumerable: false
497
+ if (!this.config?.dir) {
498
+ throw new SlothletError("INVALID_CONFIG_NOT_LOADED", {
499
+ operation: "reload",
500
+ validationError: true
662
501
  });
663
502
  }
664
503
 
665
504
 
666
- const scopeDesc = Object.getOwnPropertyDescriptor(boundApi, "scope");
667
- if (!scopeDesc || scopeDesc.configurable) {
668
- Object.defineProperty(boundApi, "scope", {
669
- value: this.scope.bind(this),
670
- writable: true,
671
- configurable: true,
672
- enumerable: false
673
- });
674
- } else if (this.config && this.config.debug) {
675
- console.warn("Could not redefine boundApi.scope: not configurable");
676
- }
677
505
 
506
+
507
+ const operationHistory = this.handlers.apiManager?.state?.operationHistory ? [...this.handlers.apiManager.state.operationHistory] : [];
678
508
 
679
509
 
510
+ await this._clearModuleCaches();
680
511
 
681
512
 
682
513
 
514
+
515
+
516
+ const oldInstanceID = this.instanceID;
517
+ if (!keepInstanceID) {
518
+ this.instanceID = `${oldInstanceID}_reload_${Date.now()}`;
519
+ }
683
520
 
684
- return boundApi;
685
- },
686
-
687
-
688
- safeDefine(obj, key, value, enumerable = false) {
689
- return safeDefine(obj, key, value, enumerable, this.config);
690
- },
691
-
692
-
693
- isLoaded() {
694
- return this.loaded;
695
- },
521
+
522
+
523
+ const savedMetadataState = this.handlers.metadata?.exportUserState?.();
696
524
 
697
-
698
- getApi() {
699
- return this.api;
700
- },
525
+
526
+
527
+ const savedHooks = this.handlers.hookManager?.exportHooks?.();
701
528
 
702
-
703
- getBoundApi() {
704
- return this.boundapi;
705
- },
529
+
530
+ await this.load(this.config, this.instanceID);
706
531
 
707
-
708
- _registerApiOwnership(apiPath, moduleId) {
709
- if (!this.config.enableModuleOwnership) return;
710
- this._moduleOwnership.set(apiPath, moduleId);
711
- if (this.config.debug) {
712
- console.log(`[DEBUG] Registered ownership: ${apiPath} -> ${moduleId}`);
532
+
533
+
534
+
535
+
536
+ if (savedMetadataState && this.handlers.metadata) {
537
+ this.handlers.metadata.importUserState(savedMetadataState);
713
538
  }
714
- },
715
-
716
-
717
- _getApiOwnership(apiPath) {
718
- if (!this.config.enableModuleOwnership) return null;
719
- return this._moduleOwnership.get(apiPath) || null;
720
- },
721
-
722
-
723
- _validateModuleOwnership(apiPath, moduleId, forceOverwrite) {
724
- if (!this.config.enableModuleOwnership || !forceOverwrite) return false;
725
539
 
726
- const existingOwner = this._getApiOwnership(apiPath);
727
- if (!existingOwner) {
728
-
729
- return true;
540
+
541
+
542
+
543
+ if (savedHooks?.length && this.handlers.hookManager) {
544
+ this.handlers.hookManager.importHooks(savedHooks);
730
545
  }
731
546
 
732
547
 
733
- if (existingOwner === moduleId) {
734
- return true;
548
+
549
+
550
+ for (const [, store] of this.contextManager.instances) {
551
+ if (store.parentInstanceID === oldInstanceID) {
552
+ store.parentInstanceID = this.instanceID;
553
+ }
735
554
  }
736
555
 
737
556
 
738
- if (this.config.debug) {
739
- console.log(`[DEBUG] Ownership conflict: ${apiPath} owned by ${existingOwner}, attempted by ${moduleId}`);
557
+
558
+ if (oldInstanceID && oldInstanceID !== this.instanceID && this.contextManager.instances?.has(oldInstanceID)) {
559
+ this.contextManager.cleanup(oldInstanceID);
740
560
  }
741
- return false;
742
- },
743
561
 
744
-
745
- async addApi(apiPath, folderPath, metadata = {}, options = {}) {
746
- return addApiFromFolder({ apiPath, folderPath, instance: this, metadata, options });
747
- },
562
+
563
+ for (const operation of operationHistory) {
564
+ if (operation.type === "add") {
565
+ await this.handlers.apiManager.addApiComponent({
566
+ apiPath: operation.apiPath,
567
+ folderPath: operation.folderPath,
568
+
569
+
570
+ options: { ...(operation.options || {}), recordHistory: false },
571
+ moduleID: `replay_${this.helpers.utilities.generateId().substring(0, 8)}`
572
+ });
573
+
574
+
575
+ } else if (operation.type === "remove") {
576
+
577
+
578
+ const { parts } = this.handlers.apiManager.normalizeApiPath(operation.apiPath);
579
+ this.handlers.apiManager.deletePath(this.api, parts);
580
+
581
+
582
+
583
+ if (this.handlers.metadata) {
584
+ const rootSegment = operation.apiPath.split(".")[0];
585
+ this.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
586
+ }
587
+ }
588
+ }
748
589
 
749
-
590
+ return this.boundApi;
591
+ }
750
592
 
751
593
 
752
- run(contextData, callback, ...args) {
753
- if (this.config.scope?.enabled === false) {
754
- throw new Error("Per-request context (scope) is disabled for this instance.");
755
- }
594
+ async _clearModuleCaches() {
595
+
596
+
597
+ const targetDir = this.config.dir;
598
+ const { resolve } = await import("node:path");
599
+ const { createRequire } = await import("node:module");
600
+ const require = createRequire(import.meta.url);
601
+ const absoluteTargetDir = resolve(targetDir);
756
602
 
757
- if (typeof callback !== "function") {
758
- throw new TypeError("Callback must be a function.");
603
+
604
+ for (const key of Object.keys(require.cache)) {
605
+ if (key.startsWith(absoluteTargetDir)) {
606
+ delete require.cache[key];
607
+ }
759
608
  }
760
609
 
761
- const runtimeType = this.config.runtime || "async";
762
- let requestALS;
763
- if (runtimeType === "async") {
764
- return import("@cldmv/slothlet/runtime/async").then((asyncRuntime) => {
765
- requestALS = asyncRuntime.requestALS;
766
- const parentContext = requestALS.getStore() || {};
767
-
768
- let mergedContext;
769
- if (this.config.scope?.merge === "deep") {
770
- const instanceContext = this.context || {};
771
- let temp = this._deepMerge({}, instanceContext);
772
- temp = this._deepMerge(temp, parentContext);
773
- mergedContext = this._deepMerge(temp, contextData);
774
- } else {
775
- mergedContext = { ...parentContext, ...contextData };
776
- }
777
-
778
- return requestALS.run(mergedContext, () => callback(...args));
779
- });
780
- } else {
781
- return import("@cldmv/slothlet/runtime/live").then((liveRuntime) => {
782
- requestALS = liveRuntime.requestALS;
783
- const parentContext = requestALS.getStore() || {};
784
-
785
- let mergedContext;
786
- if (this.config.scope?.merge === "deep") {
787
- const instanceContext = this.context || {};
788
- let temp = this._deepMerge({}, instanceContext);
789
- temp = this._deepMerge(temp, parentContext);
790
- mergedContext = this._deepMerge(temp, contextData);
791
- } else {
792
- mergedContext = { ...parentContext, ...contextData };
793
- }
794
-
795
- return requestALS.run(mergedContext, () => callback(...args));
796
- });
797
- }
798
- },
610
+
611
+
612
+ }
799
613
 
800
614
 
801
- scope({ context, fn, args }) {
802
- if (this.config.scope?.enabled === false) {
803
- throw new Error("Per-request context (scope) is disabled for this instance.");
615
+ injectRuntimeMetadataFunctions(api) {
616
+ if (!api.slothlet?.metadata) {
617
+ return;
804
618
  }
805
619
 
806
- if (!context || typeof context !== "object") {
807
- throw new TypeError("context must be an object.");
808
- }
620
+ const metadataHandler = this.handlers.metadata;
809
621
 
810
- if (typeof fn !== "function") {
811
- throw new TypeError("fn must be a function.");
812
- }
622
+
623
+ api.slothlet.metadata.get = async function slothlet_metadata_get_runtime(path) {
624
+ return metadataHandler.get(path);
625
+ };
813
626
 
814
- const runtimeType = this.config.runtime || "async";
815
- let requestALS;
816
- if (runtimeType === "async") {
817
- return import("./lib/runtime/runtime-asynclocalstorage.mjs").then((asyncRuntime) => {
818
- requestALS = asyncRuntime.requestALS;
819
- const parentContext = requestALS.getStore() || {};
820
-
821
- let mergedContext;
822
- if (this.config.scope?.merge === "deep") {
823
- const instanceContext = this.context || {};
824
- let temp = this._deepMerge({}, instanceContext);
825
- temp = this._deepMerge(temp, parentContext);
826
- mergedContext = this._deepMerge(temp, context);
827
- } else {
828
- mergedContext = { ...parentContext, ...context };
829
- }
627
+ api.slothlet.metadata.self = function slothlet_metadata_self_runtime() {
628
+ return metadataHandler.self();
629
+ };
830
630
 
831
- const argsArray = args || [];
832
- return requestALS.run(mergedContext, () => fn(...argsArray));
833
- });
834
- } else {
835
- return import("./lib/runtime/runtime-livebindings.mjs").then((liveRuntime) => {
836
- requestALS = liveRuntime.requestALS;
837
- const parentContext = requestALS.getStore() || {};
838
-
839
- let mergedContext;
840
- if (this.config.scope?.merge === "deep") {
841
- const instanceContext = this.context || {};
842
- let temp = this._deepMerge({}, instanceContext);
843
- temp = this._deepMerge(temp, parentContext);
844
- mergedContext = this._deepMerge(temp, context);
845
- } else {
846
- mergedContext = { ...parentContext, ...context };
847
- }
631
+ api.slothlet.metadata.caller = function slothlet_metadata_caller_runtime() {
632
+ return metadataHandler.caller();
633
+ };
634
+ }
848
635
 
849
- const argsArray = args || [];
850
- return requestALS.run(mergedContext, () => fn(...argsArray));
851
- });
636
+
637
+ async shutdown() {
638
+ if (!this.isLoaded) {
639
+ return;
852
640
  }
853
- },
854
641
 
855
-
856
- _deepMerge(target, source) {
857
- return deepMerge(target, source);
858
- },
642
+
643
+ disableEventEmitterPatching();
644
+ cleanupEventEmitterResources();
645
+
646
+
647
+ if (this.instanceID && this.contextManager) {
648
+ this.contextManager.cleanup(this.instanceID);
649
+ }
859
650
 
860
- async shutdown() {
861
651
 
862
652
 
863
- if (this._shutdownInProgress) {
864
- if (!this.loaded) return;
865
- console.warn("[slothlet] shutdown already in progress – ignoring nested call.");
866
- return;
653
+
654
+ if (this.handlers.ownership) {
655
+ this.handlers.ownership.clear();
867
656
  }
868
- this._shutdownInProgress = true;
869
- try {
870
-
871
- if (this.loaded) {
872
- const TIMEOUT_MS = 5000;
873
- let apiError, internalError;
874
- if (typeof this._boundAPIShutdown === "function") {
875
- try {
876
- await Promise.race([
877
- this._boundAPIShutdown.call(this.boundapi),
878
- new Promise((_, reject) => setTimeout(() => reject(new Error("API shutdown timeout")), TIMEOUT_MS))
879
- ]);
880
- } catch (err) {
881
- apiError = err;
882
- }
883
- }
884
-
885
- const disposeFn = this.boundapi && typeof this.boundapi.__dispose__ === "function" ? this.boundapi.__dispose__ : this._dispose;
886
- if (typeof disposeFn === "function") {
887
- try {
888
- await disposeFn();
889
- } catch (err) {
890
- internalError = err;
891
- }
892
- }
893
- this.loaded = false;
894
- this.api = null;
895
- this.boundapi = {};
896
- this.context = {};
897
- this.reference = {};
898
- this._dispose = null;
899
- this._boundAPIShutdown = null;
900
657
 
901
-
902
- if (this.hookManager) {
903
- this.hookManager.cleanup();
904
- this.hookManager = null;
905
- }
658
+
659
+
660
+ this.isLoaded = false;
661
+ }
906
662
 
907
-
663
+
664
+ debug(code, context = {}) {
665
+ if (this.debugLogger) {
666
+ this.debugLogger.log(code, context);
667
+ }
668
+ }
908
669
 
909
-
910
- if (this.instanceId) {
911
- await cleanupInstance(this.instanceId);
912
- }
670
+
671
+ getAPI() {
672
+ if (!this.isLoaded) {
673
+ throw new SlothletError(
674
+ "INVALID_CONFIG_NOT_LOADED",
675
+ {
676
+ operation: "getAPI"
677
+ },
678
+ null,
679
+ { validationError: true }
680
+ );
681
+ }
682
+ return this.boundApi;
683
+ }
913
684
 
914
-
915
-
916
- try {
917
-
918
- cleanupAllSlothletListeners();
919
-
920
- disableAlsForEventEmitters();
921
- } catch (cleanupError) {
922
-
923
- console.warn("[slothlet] Warning: EventEmitter cleanup failed:", cleanupError.message);
924
- }
685
+
686
+ getDiagnostics() {
687
+ return {
688
+ instanceID: this.instanceID,
689
+ isLoaded: this.isLoaded,
690
+ config: this.config,
691
+ context: this.contextManager?.getDiagnostics() || null,
692
+ ownership: this.handlers.ownership?.getDiagnostics() || null
693
+ };
694
+ }
925
695
 
926
- if (apiError || internalError) throw apiError || internalError;
927
- }
928
- } finally {
929
-
930
- this._shutdownInProgress = false;
696
+
697
+ getOwnership() {
698
+ if (!this.handlers.ownership) {
699
+ return null;
931
700
  }
701
+ return this.handlers.ownership.getDiagnostics();
702
+ }
703
+
704
+
705
+ buildFinalAPI(userApi) {
706
+ return this.builders.apiBuilder.buildFinalAPI(userApi);
932
707
  }
933
- };
708
+ }
934
709
 
935
710
 
711
+ export async function slothlet(config) {
712
+ const instance = new Slothlet();
713
+ const api = await instance.load(config);
714
+
715
+
716
+ return api;
717
+ }
936
718
 
937
- export { slothlet };
938
719
  export default slothlet;
939
720
 
940
721