@cldmv/slothlet 3.0.1 → 3.2.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 (34) hide show
  1. package/README.md +16 -14
  2. package/dist/lib/builders/api_builder.mjs +104 -4
  3. package/dist/lib/handlers/api-manager.mjs +179 -14
  4. package/dist/lib/handlers/metadata.mjs +15 -0
  5. package/dist/lib/handlers/unified-wrapper.mjs +22 -11
  6. package/dist/lib/handlers/version-manager.mjs +773 -0
  7. package/dist/lib/helpers/config.mjs +33 -5
  8. package/dist/lib/i18n/languages/de-de.json +15 -1
  9. package/dist/lib/i18n/languages/en-gb.json +15 -1
  10. package/dist/lib/i18n/languages/en-us.json +15 -1
  11. package/dist/lib/i18n/languages/es-mx.json +15 -1
  12. package/dist/lib/i18n/languages/fr-fr.json +15 -1
  13. package/dist/lib/i18n/languages/hi-in.json +15 -1
  14. package/dist/lib/i18n/languages/ja-jp.json +15 -1
  15. package/dist/lib/i18n/languages/ko-kr.json +15 -1
  16. package/dist/lib/i18n/languages/pt-br.json +15 -1
  17. package/dist/lib/i18n/languages/ru-ru.json +15 -1
  18. package/dist/lib/i18n/languages/zh-cn.json +15 -1
  19. package/dist/lib/i18n/translations.mjs +2 -0
  20. package/dist/slothlet.mjs +98 -1
  21. package/package.json +5 -2
  22. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  23. package/types/dist/lib/handlers/api-manager.d.mts +28 -0
  24. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
  25. package/types/dist/lib/handlers/metadata.d.mts +15 -0
  26. package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
  27. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
  28. package/types/dist/lib/handlers/version-manager.d.mts +234 -0
  29. package/types/dist/lib/handlers/version-manager.d.mts.map +1 -0
  30. package/types/dist/lib/helpers/config.d.mts +33 -0
  31. package/types/dist/lib/helpers/config.d.mts.map +1 -1
  32. package/types/dist/lib/i18n/translations.d.mts.map +1 -1
  33. package/types/dist/slothlet.d.mts +25 -0
  34. package/types/dist/slothlet.d.mts.map +1 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@ The name might suggest we're taking it easy, but don't be fooled. **Slothlet del
16
16
 
17
17
  > _Where sophisticated architecture meets blazing performance - slothlet is anything but slow._
18
18
 
19
- [![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] <!-- [![GitHub release]][github_release_url] -->[![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] <!-- [![Release date]][release_date_url] -->[![npm last update]][npm_last_update_url]
19
+ [![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] <!-- [![GitHub release]][github_release_url] -->[![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] <!-- [![Release date]][release_date_url] -->[![npm last update]][npm_last_update_url] [![coverage]][coverage_url]
20
20
 
21
21
  > [!NOTE]
22
22
  > **🚀 Production Ready Modes:**
@@ -55,23 +55,22 @@ Every feature has been hardened with a comprehensive test suite - over **5,300 t
55
55
 
56
56
  ## ✨ What's New
57
57
 
58
- ### Latest: v3.0.0 (February 2026)
58
+ ### Latest: v3.2.0 (April 2026)
59
59
 
60
- - **Unified Wrapper Architecture** - consistent proxy surface for all modes and operations
61
- - **Hook System Redesigned** - `hook:` config key, `api.slothlet.hook.*`, three-phase subset ordering
62
- - **Full i18n Support** - 9 languages for all error and debug messages
63
- - **Background Materialization** - pre-load lazy modules without blocking startup
64
- - **Lifecycle Event System** - `api.slothlet.lifecycle.on/off()` for module lifecycle events
65
- - **Collision Modes** - fine-grained control over API namespace conflicts
66
- - **Mutation Controls** - per-operation access restrictions for API mutations
67
- - [View full v3.0 Changelog](./docs/changelog/v3.0.md)
60
+ - **API Path Versioning** register the same logical path (e.g. `auth`) under multiple version tags (`v1`, `v2`); a configurable discriminator routes each caller to the correct version at dispatch time
61
+ - **`versionDispatcher` config** `"version"` (string key lookup), custom function `(allVersions, caller) => tag`, or omit to use the `"version"` default
62
+ - **`api.slothlet.versioning.*`** runtime management: `list()`, `setDefault()`, `unregister()`, `getVersionMetadata()`
63
+ - **Separate version metadata** `versionConfig.metadata` stored in VersionManager; never merged with `options.metadata` (regular Metadata system)
64
+ - **Shutdown race fix** — `shutdown()` now drains in-flight lazy-mode ESM `import()` calls before teardown, preventing unhandled rejections on worker exit
65
+ - [View full v3.2.0 Changelog](./docs/changelog/v3/v3.2.0.md)
68
66
 
69
67
  ### Recent Releases
70
68
 
71
- - **v2.11.0** - AddApi Special File Pattern (Rule 11), smart flattening enhancements ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.11.md))
72
- - **v2.10.0** - Function metadata tagging and introspection capabilities ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.10.md))
73
- - **v2.9** - Per-Request Context Isolation ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md))
74
- - **v2.7** - Hook system introduced ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.7.md))
69
+ - **v3.1.0** (March 2026) Frozen `api.slothlet.env` snapshot; `env.include` allowlist; reload immunity ([Changelog](./docs/changelog/v3/v3.1.0.md))
70
+ - **v3.0.1** (March 2026) — Resolver fix for user `index.mjs` mis-classified as internal; CI `slothlet-dev` stripping hardening; respawn race fix; resilient `build:dist` script ([Changelog](./docs/changelog/v3/v3.0.1.md))
71
+ - **v3.0.0** (February 2026) Unified Wrapper architecture, redesigned hook system, full i18n, background materialization, lifecycle events, collision modes, mutation controls ([Changelog](./docs/changelog/v3.0.md))
72
+ - **v2.11.0** AddApi Special File Pattern (Rule 11), smart flattening enhancements ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.11.md))
73
+ - **v2.10.0** — Function metadata tagging and introspection capabilities ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.10.md))
75
74
 
76
75
  📚 **For complete version history and detailed release notes, see [docs/changelog/](./docs/changelog/) folder.**
77
76
 
@@ -330,6 +329,7 @@ await api.slothlet.api.reload("database.*");
330
329
  | `backgroundMaterialize` | `boolean` | `false` | In lazy mode: start background pre-loading of all modules immediately after init; automatically enables materialization tracking and the `materialized:complete` lifecycle event |
331
330
  | `api.collision` | `mixed` | `"merge"` | Collision mode for API namespace conflicts: `"merge"`, `"skip"`, `"overwrite"`, `"throw"` - or `{ initial: "merge", api: "skip" }` to set independently for load vs runtime `add()` |
332
331
  | `api.mutations` | `object` | all `true` | Per-operation mutation controls: `{ add: true, remove: true, reload: true }` - set any to `false` to disable |
332
+ | `versionDispatcher` | `mixed` | `undefined` | Version routing discriminator: `"version"` (or any string key) looks up that key in the caller's version metadata; a function receives `(allVersions, caller)` and returns a tag or `null`; `undefined` behaves like `"version"` |
333
333
  | `i18n` | `object` | `{}` | Internationalization settings: `{ language: "en" }` - supported: `en`, `es`, `fr`, `de`, `pt`, `it`, `ja`, `zh`, `ko` |
334
334
 
335
335
  ---
@@ -1038,6 +1038,8 @@ To my wife and children - thank you for your patience, your encouragement, and t
1038
1038
  [github_license_url]: https://github.com/CLDMV/slothlet/blob/HEAD/LICENSE
1039
1039
  [npm license]: https://img.shields.io/npm/l/%40cldmv%2Fslothlet.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
1040
1040
  [npm_license_url]: https://www.npmjs.com/package/@cldmv/slothlet
1041
+ [coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fslothlet%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white
1042
+ [coverage_url]: https://github.com/CLDMV/slothlet/blob/badges/coverage.json
1041
1043
  [contributors]: https://img.shields.io/github/contributors/CLDMV/slothlet.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
1042
1044
  [contributors_url]: https://github.com/CLDMV/slothlet/graphs/contributors
1043
1045
  [sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor
@@ -30,7 +30,14 @@ function _resolvePathOrModuleId(slothlet, pathOrModuleId) {
30
30
 
31
31
 
32
32
  if (history) {
33
- const match = history.findLast((entry) => entry.moduleID === pathOrModuleId);
33
+ let match = null;
34
+ for (let i = history.length - 1; i >= 0; i--) {
35
+ const entry = history[i];
36
+ if (entry?.moduleID === pathOrModuleId) {
37
+ match = entry;
38
+ break;
39
+ }
40
+ }
34
41
  if (match) return match.apiPath;
35
42
  }
36
43
  return pathOrModuleId;
@@ -186,7 +193,7 @@ export class ApiBuilder extends ComponentBase {
186
193
 
187
194
  api: {
188
195
 
189
- add: async function slothlet_api_add(apiPath, folderPath, options = {}) {
196
+ add: async function slothlet_api_add(apiPath, folderPath, options = {}, versionConfig = null) {
190
197
 
191
198
  if (!config.api?.mutations?.add) {
192
199
  throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED", {
@@ -208,7 +215,8 @@ export class ApiBuilder extends ComponentBase {
208
215
  return slothlet.handlers.apiManager.addApiComponent({
209
216
  apiPath,
210
217
  folderPath,
211
- options: filteredOptions
218
+ options: filteredOptions,
219
+ versionConfig: versionConfig || null
212
220
  });
213
221
  },
214
222
 
@@ -568,6 +576,41 @@ export class ApiBuilder extends ComponentBase {
568
576
  if (!slothlet.handlers?.metadata) return;
569
577
  const resolvedPath = _resolvePathOrModuleId(slothlet, pathOrModuleId);
570
578
  return slothlet.handlers.metadata.removePathMetadata(resolvedPath, key);
579
+ },
580
+
581
+
582
+ setForVersion: function slothlet_metadata_setForVersion(logicalPath, versionTag, keyOrObj, value) {
583
+ if (!slothlet.handlers?.metadata) {
584
+ throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE", {
585
+ handlersKeys: slothlet.handlers
586
+ ? Object.keys(slothlet.handlers).join(", ")
587
+ :
588
+
589
+ "undefined",
590
+ validationError: true
591
+ });
592
+ }
593
+ const info = slothlet.handlers?.versionManager?.list(logicalPath);
594
+ if (!info || !info.versions?.[versionTag]) {
595
+ throw new slothlet.SlothletError("VERSION_NOT_FOUND", {
596
+ version: versionTag,
597
+ apiPath: logicalPath
598
+ });
599
+ }
600
+ const { moduleID } = info.versions[versionTag];
601
+ const resolvedPath = _resolvePathOrModuleId(slothlet, moduleID);
602
+ return slothlet.handlers.metadata.setPathMetadata(resolvedPath, keyOrObj, value);
603
+ },
604
+
605
+
606
+ getForVersion: function slothlet_metadata_getForVersion(logicalPath, versionTag) {
607
+
608
+ if (!slothlet.handlers?.metadata) return {};
609
+ const info = slothlet.handlers?.versionManager?.list(logicalPath);
610
+ if (!info || !info.versions?.[versionTag]) return {};
611
+ const { moduleID } = info.versions[versionTag];
612
+ const resolvedPath = _resolvePathOrModuleId(slothlet, moduleID);
613
+ return slothlet.handlers.metadata.getPathMetadata(resolvedPath);
571
614
  }
572
615
  },
573
616
 
@@ -636,7 +679,64 @@ export class ApiBuilder extends ComponentBase {
636
679
  on: handler.on.bind(handler),
637
680
  off: handler.off.bind(handler)
638
681
  };
639
- })()
682
+ })(),
683
+
684
+
685
+ env: slothlet.envSnapshot,
686
+
687
+
688
+ versioning: {
689
+
690
+ list: function slothlet_version_list(logicalPath) {
691
+
692
+
693
+ if (!slothlet.handlers?.versionManager) return undefined;
694
+ return slothlet.handlers.versionManager.list(logicalPath);
695
+ },
696
+
697
+
698
+ setDefault: function slothlet_version_setDefault(logicalPath, versionTag) {
699
+
700
+
701
+ if (!slothlet.handlers?.versionManager) return;
702
+ return slothlet.handlers.versionManager.setDefault(logicalPath, versionTag);
703
+ },
704
+
705
+
706
+ unregister: async function slothlet_version_unregister(logicalPath, versionTag) {
707
+
708
+
709
+ if (!slothlet.handlers?.versionManager) return false;
710
+
711
+ const info = slothlet.handlers.versionManager.list(logicalPath);
712
+ if (!info || !info.versions?.[versionTag]) return false;
713
+
714
+
715
+
716
+ const { moduleID: versionedModuleID } = info.versions[versionTag];
717
+
718
+
719
+
720
+ await slothlet.handlers.apiManager.removeApiComponent(versionedModuleID);
721
+ return true;
722
+ },
723
+
724
+
725
+ getVersionMetadata: function slothlet_version_getVersionMetadata(logicalPath, versionTag) {
726
+
727
+
728
+ if (!slothlet.handlers?.versionManager) return undefined;
729
+ return slothlet.handlers.versionManager.getVersionMetadataByPath(logicalPath, versionTag);
730
+ },
731
+
732
+
733
+ setVersionMetadata: function slothlet_version_setVersionMetadata(logicalPath, versionTag, patch) {
734
+
735
+
736
+ if (!slothlet.handlers?.versionManager) return;
737
+ return slothlet.handlers.versionManager.setVersionMetadataByPath(logicalPath, versionTag, patch);
738
+ }
739
+ }
640
740
  };
641
741
 
642
742
 
@@ -863,7 +863,7 @@ export class ApiManager extends ComponentBase {
863
863
  async addApiComponent(params) {
864
864
 
865
865
 
866
- const { apiPath, folderPath, options = {} } = params || {};
866
+ const { apiPath, folderPath, options = {}, versionConfig = null } = params || {};
867
867
 
868
868
 
869
869
  if (Array.isArray(folderPath)) {
@@ -872,7 +872,8 @@ export class ApiManager extends ComponentBase {
872
872
  const moduleID = await this.addApiComponent({
873
873
  apiPath,
874
874
  folderPath: singlePath,
875
- options
875
+ options,
876
+ versionConfig
876
877
  });
877
878
  moduleIDs.push(moduleID);
878
879
  }
@@ -890,6 +891,37 @@ export class ApiManager extends ComponentBase {
890
891
  const { apiPath: normalizedPath, parts } = this.normalizeApiPath(apiPath);
891
892
 
892
893
 
894
+ let effectivePath = normalizedPath;
895
+ let effectiveParts = parts;
896
+ if (versionConfig?.version !== undefined && versionConfig?.version !== null) {
897
+
898
+
899
+
900
+ if (normalizedPath === "") {
901
+ throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
902
+ apiPath,
903
+ reason: translate("API_PATH_REASON_VERSIONED_ROOT"),
904
+ index: undefined,
905
+ segment: undefined,
906
+ validationError: true
907
+ });
908
+ }
909
+ if (typeof versionConfig.version !== "string" || !String(versionConfig.version).trim()) {
910
+ throw new this.SlothletError("INVALID_CONFIG_VERSION_TAG", {
911
+ received: versionConfig.version,
912
+ validationError: true
913
+ });
914
+ }
915
+ const versionTag = String(versionConfig.version).trim();
916
+
917
+
918
+
919
+
920
+ effectiveParts = [versionTag, ...parts];
921
+ effectivePath = effectiveParts.join(".");
922
+ }
923
+
924
+
893
925
  const { resolvedPath, isDirectory, isFile } = await this.resolvePath(folderPath);
894
926
 
895
927
 
@@ -959,7 +991,8 @@ export class ApiManager extends ComponentBase {
959
991
 
960
992
 
961
993
 
962
- apiPathPrefix: normalizedPath,
994
+
995
+ apiPathPrefix: effectivePath,
963
996
  collisionContext: "addApi",
964
997
  moduleID: moduleID,
965
998
 
@@ -973,7 +1006,7 @@ export class ApiManager extends ComponentBase {
973
1006
 
974
1007
  if (this.slothlet.handlers.apiCacheManager) {
975
1008
  this.slothlet.handlers.apiCacheManager.set(moduleID, {
976
- endpoint: normalizedPath,
1009
+ endpoint: effectivePath,
977
1010
  moduleID: moduleID,
978
1011
  api: newApi,
979
1012
  folderPath: resolvedFolderPath,
@@ -1141,7 +1174,7 @@ export class ApiManager extends ComponentBase {
1141
1174
  const isCallableNamespace = typeof apiToMerge === "function";
1142
1175
 
1143
1176
  const containerWrapper = new UnifiedWrapper(this.slothlet, {
1144
- apiPath: normalizedPath,
1177
+ apiPath: effectivePath,
1145
1178
  mode: this.____config.mode,
1146
1179
  isCallable: isCallableNamespace,
1147
1180
  moduleID: moduleID,
@@ -1154,14 +1187,14 @@ export class ApiManager extends ComponentBase {
1154
1187
  apiToMerge = containerWrapper.createProxy();
1155
1188
  }
1156
1189
 
1157
- const result1 = await this.setValueAtPath(this.slothlet.api, parts, apiToMerge, {
1190
+ const result1 = await this.setValueAtPath(this.slothlet.api, effectiveParts, apiToMerge, {
1158
1191
  mutateExisting,
1159
1192
  collisionMode,
1160
1193
  moduleID,
1161
1194
  sourceFolder: resolvedFolderPath
1162
1195
  });
1163
1196
 
1164
- const result2 = await this.setValueAtPath(this.slothlet.boundApi, parts, apiToMerge, {
1197
+ const result2 = await this.setValueAtPath(this.slothlet.boundApi, effectiveParts, apiToMerge, {
1165
1198
  mutateExisting,
1166
1199
  collisionMode,
1167
1200
  moduleID,
@@ -1187,6 +1220,11 @@ export class ApiManager extends ComponentBase {
1187
1220
 
1188
1221
  const collectPendingMaterializations = (obj, depth = 0) => {
1189
1222
  if (!obj || typeof obj !== "object" || depth > 10) return;
1223
+
1224
+
1225
+
1226
+
1227
+ if (obj.__isVersionDispatcher === true) return;
1190
1228
 
1191
1229
  const wrapper = resolveWrapper(obj);
1192
1230
  if (wrapper) {
@@ -1220,7 +1258,9 @@ export class ApiManager extends ComponentBase {
1220
1258
 
1221
1259
 
1222
1260
 
1223
- if (parts.length === 0) {
1261
+
1262
+
1263
+ if (effectiveParts.length === 0) {
1224
1264
 
1225
1265
  for (const key of Object.keys(newApi)) {
1226
1266
 
@@ -1232,7 +1272,7 @@ export class ApiManager extends ComponentBase {
1232
1272
  } else {
1233
1273
 
1234
1274
  let current = this.slothlet.api;
1235
- for (const part of parts) {
1275
+ for (const part of effectiveParts) {
1236
1276
 
1237
1277
 
1238
1278
  if (current && current[part]) {
@@ -1277,7 +1317,11 @@ export class ApiManager extends ComponentBase {
1277
1317
  this.slothlet.handlers.metadata.registerUserMetadata(key, metadata);
1278
1318
  }
1279
1319
  } else {
1280
- const rootSegment = normalizedPath.split(".")[0];
1320
+
1321
+
1322
+
1323
+
1324
+ const rootSegment = effectiveParts[0];
1281
1325
  this.slothlet.handlers.metadata.registerUserMetadata(rootSegment, metadata);
1282
1326
  }
1283
1327
  }
@@ -1285,8 +1329,10 @@ export class ApiManager extends ComponentBase {
1285
1329
 
1286
1330
 
1287
1331
 
1332
+
1333
+
1288
1334
  if (this.slothlet.handlers.ownership && moduleID) {
1289
- this.slothlet.handlers.ownership.registerSubtree(apiToMerge, moduleID, normalizedPath);
1335
+ this.slothlet.handlers.ownership.registerSubtree(apiToMerge, moduleID, effectivePath);
1290
1336
  }
1291
1337
 
1292
1338
 
@@ -1297,24 +1343,83 @@ export class ApiManager extends ComponentBase {
1297
1343
  apiPath: normalizedPath,
1298
1344
  folderPath: resolvedFolderPath,
1299
1345
  options: { ...restOptions, metadata, moduleID },
1300
- moduleID
1346
+ moduleID,
1347
+ versionConfig: versionConfig || null
1301
1348
  });
1302
1349
 
1303
1350
 
1351
+
1304
1352
  this.state.operationHistory.push({
1305
1353
  type: "add",
1306
1354
  apiPath: normalizedPath,
1307
1355
  folderPath: resolvedFolderPath,
1308
1356
  options: { ...restOptions, metadata, moduleID },
1309
- moduleID
1357
+ moduleID,
1358
+ versionConfig: versionConfig || null
1310
1359
  });
1311
1360
  }
1312
1361
  }
1313
1362
 
1363
+
1364
+
1365
+
1366
+
1367
+ if (versionConfig?.version && this.slothlet.handlers.versionManager) {
1368
+ const versionTag = String(versionConfig.version).trim();
1369
+ try {
1370
+ this.slothlet.handlers.versionManager.registerVersion(
1371
+ normalizedPath,
1372
+ versionTag,
1373
+ moduleID,
1374
+ versionConfig.metadata ?? {},
1375
+ versionConfig.default ?? false
1376
+ );
1377
+ } catch (error) {
1378
+ await this._rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath });
1379
+ throw error;
1380
+ }
1381
+ }
1382
+
1314
1383
  return moduleID;
1315
1384
  }
1316
1385
 
1317
1386
 
1387
+ async _rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath }) {
1388
+
1389
+
1390
+
1391
+
1392
+
1393
+ let addIndex = -1;
1394
+ for (let i = this.state.operationHistory.length - 1; i >= 0; i--) {
1395
+ const entry = this.state.operationHistory[i];
1396
+ if (entry?.type === "add" && entry?.apiPath === normalizedPath && entry?.moduleID === moduleID) {
1397
+ addIndex = i;
1398
+ break;
1399
+ }
1400
+ }
1401
+ if (addIndex !== -1) {
1402
+ this.state.operationHistory.splice(addIndex, 1);
1403
+ }
1404
+
1405
+
1406
+
1407
+
1408
+
1409
+ this.state.addHistory = this.state.addHistory.filter((entry) => entry?.moduleID !== moduleID);
1410
+
1411
+
1412
+
1413
+
1414
+ try {
1415
+ await this.removeApiComponent(moduleID || effectivePath, { recordHistory: false });
1416
+ } catch {
1417
+
1418
+
1419
+ }
1420
+ }
1421
+
1422
+
1318
1423
  async removeApiComponent(pathOrModuleId, options = {}) {
1319
1424
  const recordHistory = options.recordHistory !== false;
1320
1425
  if (typeof pathOrModuleId !== "string" || !pathOrModuleId) {
@@ -1340,7 +1445,14 @@ export class ApiManager extends ComponentBase {
1340
1445
 
1341
1446
 
1342
1447
  const registeredModules = Array.from(this.slothlet.handlers.ownership.moduleToPath.keys());
1343
- const matchingModule = registeredModules.findLast((m) => m === candidateModuleID || m.startsWith(`${candidateModuleID}_`));
1448
+ let matchingModule = null;
1449
+ for (let i = registeredModules.length - 1; i >= 0; i--) {
1450
+ const candidate = registeredModules[i];
1451
+ if (candidate === candidateModuleID || candidate.startsWith(`${candidateModuleID}_`)) {
1452
+ matchingModule = candidate;
1453
+ break;
1454
+ }
1455
+ }
1344
1456
 
1345
1457
  if (matchingModule) {
1346
1458
 
@@ -1395,6 +1507,19 @@ export class ApiManager extends ComponentBase {
1395
1507
  this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
1396
1508
  }
1397
1509
 
1510
+ if (this.slothlet.handlers.versionManager) {
1511
+ const versionKey = this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);
1512
+ if (versionKey) {
1513
+ this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath, versionKey.versionTag);
1514
+ }
1515
+
1516
+
1517
+
1518
+ if (this.slothlet.handlers.versionManager.hasDispatcher(normalizedPath)) {
1519
+ this.slothlet.handlers.versionManager.teardownDispatcher(normalizedPath);
1520
+ }
1521
+ }
1522
+
1398
1523
  this.state.operationHistory.push({
1399
1524
  type: "remove",
1400
1525
  apiPath: normalizedPath
@@ -1451,6 +1576,16 @@ export class ApiManager extends ComponentBase {
1451
1576
  const result = this.slothlet.handlers.ownership?.unregister?.(moduleIDKey) || { removed: [], rolledBack: [] };
1452
1577
 
1453
1578
 
1579
+
1580
+
1581
+ if (this.slothlet.handlers.versionManager) {
1582
+ const versionKey = this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);
1583
+ if (versionKey) {
1584
+ this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath, versionKey.versionTag);
1585
+ }
1586
+ }
1587
+
1588
+
1454
1589
  const allPaths = [...result.removed, ...result.rolledBack.map((r) => r.apiPath)];
1455
1590
 
1456
1591
 
@@ -1780,6 +1915,11 @@ export class ApiManager extends ComponentBase {
1780
1915
  key: "DEBUG_MODE_MODULE_RELOAD_COMPLETE",
1781
1916
  moduleID
1782
1917
  });
1918
+
1919
+
1920
+ if (this.slothlet.handlers.versionManager) {
1921
+ this.slothlet.handlers.versionManager.onVersionedModuleReload(moduleID);
1922
+ }
1783
1923
  }
1784
1924
 
1785
1925
 
@@ -2268,6 +2408,31 @@ export class ApiManager extends ComponentBase {
2268
2408
 
2269
2409
 
2270
2410
 
2411
+ if (parts.length > 0 && implForReload && typeof implForReload === "object") {
2412
+ const lastEndpointPart = parts[parts.length - 1];
2413
+ if (lastEndpointPart && Object.prototype.hasOwnProperty.call(implForReload, lastEndpointPart)) {
2414
+ const dupValue = implForReload[lastEndpointPart];
2415
+ const dupWrapperForDedup = resolveWrapper(dupValue);
2416
+ if (dupWrapperForDedup) {
2417
+ const hoisted = {};
2418
+ for (const k of Object.keys(implForReload)) {
2419
+ if (k !== lastEndpointPart) hoisted[k] = implForReload[k];
2420
+ }
2421
+ for (const k of Object.keys(dupWrapperForDedup).filter((k) => !k.startsWith("_") && !k.startsWith("__"))) {
2422
+ hoisted[k] = dupWrapperForDedup[k];
2423
+ }
2424
+ implForReload = hoisted;
2425
+ }
2426
+ }
2427
+ }
2428
+
2429
+
2430
+
2431
+
2432
+
2433
+
2434
+
2435
+
2271
2436
 
2272
2437
  if (implForReload && typeof implForReload === "object") {
2273
2438
  for (const key of Object.keys(implForReload)) {
@@ -371,6 +371,21 @@ export class Metadata extends ComponentBase {
371
371
  }
372
372
 
373
373
 
374
+ getPathMetadata(apiPath) {
375
+ if (!apiPath || typeof apiPath !== "string") return {};
376
+ const parts = apiPath.split(".");
377
+ const collected = {};
378
+ for (let i = 1; i <= parts.length; i++) {
379
+ const parentPath = parts.slice(0, i).join(".");
380
+ const parentMeta = this.#userMetadataStore.get(parentPath);
381
+ if (parentMeta?.metadata) {
382
+ Object.assign(collected, parentMeta.metadata);
383
+ }
384
+ }
385
+ return { ...this.#globalUserMetadata, ...collected };
386
+ }
387
+
388
+
374
389
  removePathMetadata(apiPath, key) {
375
390
  if (!apiPath || typeof apiPath !== "string") return;
376
391
 
@@ -660,6 +660,14 @@ export class UnifiedWrapper extends ComponentBase {
660
660
  return;
661
661
  }
662
662
 
663
+
664
+
665
+
666
+
667
+
668
+
669
+ if (util.types.isProxy(this.____slothletInternal.impl)) return;
670
+
663
671
  const ownKeys = Reflect.ownKeys(this.____slothletInternal.impl);
664
672
 
665
673
  const internalKeys = new Set([
@@ -1331,7 +1339,10 @@ export class UnifiedWrapper extends ComponentBase {
1331
1339
  !wrapper.____slothletInternal.state.materialized &&
1332
1340
  !wrapper.____slothletInternal.state.inFlight
1333
1341
  ) {
1334
- wrapper._materialize();
1342
+
1343
+
1344
+
1345
+ wrapper._materialize().catch(() => {});
1335
1346
  }
1336
1347
 
1337
1348
  if (prop === util.inspect.custom) {
@@ -1889,7 +1900,7 @@ export class UnifiedWrapper extends ComponentBase {
1889
1900
  !wrapper.____slothletInternal.state.materialized &&
1890
1901
  wrapper.____slothletInternal.materializeFunc
1891
1902
  ) {
1892
- wrapper._materialize();
1903
+ wrapper._materialize().catch(() => {});
1893
1904
  }
1894
1905
 
1895
1906
 
@@ -2043,7 +2054,7 @@ export class UnifiedWrapper extends ComponentBase {
2043
2054
  !wrapper.____slothletInternal.state.materialized &&
2044
2055
  !wrapper.____slothletInternal.state.inFlight
2045
2056
  ) {
2046
- wrapper._materialize();
2057
+ wrapper._materialize().catch(() => {});
2047
2058
  }
2048
2059
 
2049
2060
 
@@ -2240,7 +2251,7 @@ export class UnifiedWrapper extends ComponentBase {
2240
2251
  !wrapper.____slothletInternal.state.materialized &&
2241
2252
  !wrapper.____slothletInternal.state.inFlight
2242
2253
  ) {
2243
- wrapper._materialize();
2254
+ wrapper._materialize().catch(() => {});
2244
2255
  }
2245
2256
 
2246
2257
 
@@ -2294,7 +2305,7 @@ export class UnifiedWrapper extends ComponentBase {
2294
2305
  !cachedWrapper.____slothletInternal.state.materialized &&
2295
2306
  !cachedWrapper.____slothletInternal.state.inFlight
2296
2307
  ) {
2297
- cachedWrapper._materialize();
2308
+ cachedWrapper._materialize().catch(() => {});
2298
2309
  }
2299
2310
  }
2300
2311
 
@@ -2342,7 +2353,7 @@ export class UnifiedWrapper extends ComponentBase {
2342
2353
  !cachedWrapper.____slothletInternal.state.materialized &&
2343
2354
  !cachedWrapper.____slothletInternal.state.inFlight
2344
2355
  ) {
2345
- cachedWrapper._materialize();
2356
+ cachedWrapper._materialize().catch(() => {});
2346
2357
  }
2347
2358
  }
2348
2359
  return cached;
@@ -2375,7 +2386,7 @@ export class UnifiedWrapper extends ComponentBase {
2375
2386
 
2376
2387
 
2377
2388
  if (!wrapper.____slothletInternal.state.materialized && !wrapper.____slothletInternal.state.inFlight) {
2378
- wrapper._materialize();
2389
+ wrapper._materialize().catch(() => {});
2379
2390
  }
2380
2391
 
2381
2392
  this.slothlet.debug("wrapper", {
@@ -2554,7 +2565,7 @@ export class UnifiedWrapper extends ComponentBase {
2554
2565
  !wrapper.____slothletInternal.state.materialized &&
2555
2566
  !wrapper.____slothletInternal.state.inFlight
2556
2567
  ) {
2557
- wrapper._materialize();
2568
+ wrapper._materialize().catch(() => {});
2558
2569
  }
2559
2570
 
2560
2571
 
@@ -2761,7 +2772,7 @@ export class UnifiedWrapper extends ComponentBase {
2761
2772
  !wrapper.____slothletInternal.state.materialized &&
2762
2773
  !wrapper.____slothletInternal.state.inFlight
2763
2774
  ) {
2764
- wrapper._materialize();
2775
+ wrapper._materialize().catch(() => {});
2765
2776
  }
2766
2777
 
2767
2778
 
@@ -2788,7 +2799,7 @@ export class UnifiedWrapper extends ComponentBase {
2788
2799
  !wrapper.____slothletInternal.state.materialized &&
2789
2800
  !wrapper.____slothletInternal.state.inFlight
2790
2801
  ) {
2791
- wrapper._materialize();
2802
+ wrapper._materialize().catch(() => {});
2792
2803
  }
2793
2804
 
2794
2805
  if (prop === "____slothletInternal") return undefined;
@@ -2837,7 +2848,7 @@ export class UnifiedWrapper extends ComponentBase {
2837
2848
  !wrapper.____slothletInternal.state.materialized &&
2838
2849
  !wrapper.____slothletInternal.state.inFlight
2839
2850
  ) {
2840
- wrapper._materialize();
2851
+ wrapper._materialize().catch(() => {});
2841
2852
  }
2842
2853
 
2843
2854
  const keys = new Set();