@cldmv/slothlet 3.1.0 → 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.
- package/README.md +12 -7
- package/dist/lib/builders/api_builder.mjs +101 -4
- package/dist/lib/handlers/api-manager.mjs +179 -14
- package/dist/lib/handlers/metadata.mjs +15 -0
- package/dist/lib/handlers/unified-wrapper.mjs +22 -11
- package/dist/lib/handlers/version-manager.mjs +773 -0
- package/dist/lib/helpers/config.mjs +20 -5
- package/dist/lib/i18n/languages/de-de.json +15 -1
- package/dist/lib/i18n/languages/en-gb.json +15 -1
- package/dist/lib/i18n/languages/en-us.json +15 -1
- package/dist/lib/i18n/languages/es-mx.json +15 -1
- package/dist/lib/i18n/languages/fr-fr.json +15 -1
- package/dist/lib/i18n/languages/hi-in.json +15 -1
- package/dist/lib/i18n/languages/ja-jp.json +15 -1
- package/dist/lib/i18n/languages/ko-kr.json +15 -1
- package/dist/lib/i18n/languages/pt-br.json +15 -1
- package/dist/lib/i18n/languages/ru-ru.json +15 -1
- package/dist/lib/i18n/languages/zh-cn.json +15 -1
- package/dist/slothlet.mjs +70 -1
- package/package.json +5 -2
- package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
- package/types/dist/lib/handlers/api-manager.d.mts +28 -0
- package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/metadata.d.mts +15 -0
- package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
- package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
- package/types/dist/lib/handlers/version-manager.d.mts +234 -0
- package/types/dist/lib/handlers/version-manager.d.mts.map +1 -0
- package/types/dist/lib/helpers/config.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +15 -0
- 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,20 +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.
|
|
58
|
+
### Latest: v3.2.0 (April 2026)
|
|
59
59
|
|
|
60
|
-
- **
|
|
61
|
-
- **`
|
|
62
|
-
-
|
|
63
|
-
-
|
|
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)
|
|
64
66
|
|
|
65
67
|
### Recent Releases
|
|
66
68
|
|
|
69
|
+
- **v3.1.0** (March 2026) — Frozen `api.slothlet.env` snapshot; `env.include` allowlist; reload immunity ([Changelog](./docs/changelog/v3/v3.1.0.md))
|
|
67
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))
|
|
68
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))
|
|
69
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))
|
|
70
73
|
- **v2.10.0** — Function metadata tagging and introspection capabilities ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.10.md))
|
|
71
|
-
- **v2.9** — Per-Request Context Isolation ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md))
|
|
72
74
|
|
|
73
75
|
📚 **For complete version history and detailed release notes, see [docs/changelog/](./docs/changelog/) folder.**
|
|
74
76
|
|
|
@@ -327,6 +329,7 @@ await api.slothlet.api.reload("database.*");
|
|
|
327
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 |
|
|
328
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()` |
|
|
329
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"` |
|
|
330
333
|
| `i18n` | `object` | `{}` | Internationalization settings: `{ language: "en" }` - supported: `en`, `es`, `fr`, `de`, `pt`, `it`, `ja`, `zh`, `ko` |
|
|
331
334
|
|
|
332
335
|
---
|
|
@@ -1035,6 +1038,8 @@ To my wife and children - thank you for your patience, your encouragement, and t
|
|
|
1035
1038
|
[github_license_url]: https://github.com/CLDMV/slothlet/blob/HEAD/LICENSE
|
|
1036
1039
|
[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fslothlet.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
1037
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
|
|
1038
1043
|
[contributors]: https://img.shields.io/github/contributors/CLDMV/slothlet.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
1039
1044
|
[contributors_url]: https://github.com/CLDMV/slothlet/graphs/contributors
|
|
1040
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
|
-
|
|
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
|
|
|
@@ -639,7 +682,61 @@ export class ApiBuilder extends ComponentBase {
|
|
|
639
682
|
})(),
|
|
640
683
|
|
|
641
684
|
|
|
642
|
-
env: slothlet.envSnapshot
|
|
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
|
+
}
|
|
643
740
|
};
|
|
644
741
|
|
|
645
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
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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();
|