@cldmv/slothlet 2.10.0 → 3.0.0

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