@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
@@ -0,0 +1,773 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+
18
+
19
+
20
+
21
+ import { inspect } from "node:util";
22
+ import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
23
+
24
+
25
+
26
+
27
+ function stripPrefix(tag) {
28
+
29
+ return tag.replace(/^[^0-9]+/, "");
30
+ }
31
+
32
+
33
+ function stripSuffix(s) {
34
+
35
+ return s.replace(/[-+].*$/, "");
36
+ }
37
+
38
+
39
+ function normaliseVersionTag(tag) {
40
+ const bare = stripSuffix(stripPrefix(tag));
41
+ const parts = bare.split(".").map((p) => {
42
+ const n = parseInt(p, 10);
43
+
44
+
45
+ return isNaN(n) ? 0 : n;
46
+ });
47
+
48
+
49
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
50
+ }
51
+
52
+
53
+ function compareTuples(a, b) {
54
+ for (let i = 0; i < 3; i++) {
55
+ if (a[i] !== b[i]) return b[i] - a[i];
56
+ }
57
+
58
+
59
+
60
+
61
+
62
+ return 0;
63
+ }
64
+
65
+
66
+
67
+ const FORCE_VERSION_SYMBOL = Symbol.for("slothlet.versioning.force");
68
+
69
+
70
+
71
+
72
+ export class VersionManager extends ComponentBase {
73
+ static slothletProperty = "versionManager";
74
+
75
+
76
+ #registry = new Map();
77
+
78
+
79
+ #versionMetadataByModule = new Map();
80
+
81
+
82
+ #moduleToVersionKey = new Map();
83
+
84
+
85
+ #dispatchers = new Map();
86
+
87
+
88
+
89
+
90
+ registerVersion(logicalPath, versionTag, moduleID, versionMeta, isDefault) {
91
+ if (!this.#registry.has(logicalPath)) {
92
+ this.#registry.set(logicalPath, { versions: new Map() });
93
+ }
94
+
95
+ const entry = this.#registry.get(logicalPath);
96
+
97
+
98
+ if (entry.versions.has(versionTag)) {
99
+ throw new this.SlothletError("VERSION_REGISTER_DUPLICATE", {
100
+ version: versionTag,
101
+ apiPath: logicalPath
102
+ });
103
+ }
104
+
105
+
106
+ const versionEntry = {
107
+ moduleID,
108
+ versionTag,
109
+ versionedPath: `${versionTag}.${logicalPath}`,
110
+
111
+
112
+
113
+ versionedParts: [versionTag, ...logicalPath.split(".")],
114
+
115
+
116
+ isDefault: isDefault ?? false,
117
+
118
+ versionMeta: versionMeta ?? {},
119
+ registeredAt: Date.now()
120
+ };
121
+
122
+ entry.versions.set(versionTag, versionEntry);
123
+ this.#moduleToVersionKey.set(moduleID, { logicalPath, versionTag });
124
+
125
+
126
+ this.#versionMetadataByModule.set(moduleID, {
127
+
128
+
129
+ ...(versionMeta ?? {}),
130
+ version: versionTag,
131
+ logicalPath
132
+ });
133
+
134
+ this.slothlet.debug("versioning", {
135
+ key: "DEBUG_VERSION_REGISTERED",
136
+ version: versionTag,
137
+ logicalPath,
138
+ moduleID
139
+ });
140
+
141
+
142
+ this.updateDispatcher(logicalPath);
143
+ }
144
+
145
+
146
+ unregisterVersion(logicalPath, versionTag) {
147
+ const entry = this.#registry.get(logicalPath);
148
+
149
+
150
+
151
+ if (!entry) return false;
152
+
153
+ const versionEntry = entry.versions.get(versionTag);
154
+
155
+
156
+ if (!versionEntry) return false;
157
+
158
+
159
+ this.#moduleToVersionKey.delete(versionEntry.moduleID);
160
+ this.#versionMetadataByModule.delete(versionEntry.moduleID);
161
+ entry.versions.delete(versionTag);
162
+
163
+ this.slothlet.debug("versioning", {
164
+ key: "DEBUG_VERSION_UNREGISTERED",
165
+ version: versionTag,
166
+ logicalPath
167
+ });
168
+
169
+ if (entry.versions.size === 0) {
170
+
171
+ this.#registry.delete(logicalPath);
172
+ this.teardownDispatcher(logicalPath);
173
+ } else {
174
+
175
+ this.updateDispatcher(logicalPath);
176
+ }
177
+
178
+ return true;
179
+ }
180
+
181
+
182
+ getVersionKeyForModule(moduleID) {
183
+ return this.#moduleToVersionKey.get(moduleID);
184
+ }
185
+
186
+
187
+ hasDispatcher(logicalPath) {
188
+ return this.#dispatchers.has(logicalPath);
189
+ }
190
+
191
+
192
+ getVersionMetadata(moduleID) {
193
+ return this.#versionMetadataByModule.get(moduleID);
194
+ }
195
+
196
+
197
+ getVersionMetadataByPath(logicalPath, versionTag) {
198
+ const entry = this.#registry.get(logicalPath);
199
+ if (!entry) return undefined;
200
+ const ve = entry.versions.get(versionTag);
201
+ if (!ve) return undefined;
202
+ return this.#versionMetadataByModule.get(ve.moduleID);
203
+ }
204
+
205
+
206
+ setVersionMetadataByPath(logicalPath, versionTag, patch) {
207
+ const entry = this.#registry.get(logicalPath);
208
+ if (!entry || !entry.versions.has(versionTag)) {
209
+ throw new this.SlothletError("VERSION_NOT_FOUND", {
210
+ version: versionTag,
211
+ apiPath: logicalPath
212
+ });
213
+ }
214
+ const ve = entry.versions.get(versionTag);
215
+ const existing = this.#versionMetadataByModule.get(ve.moduleID) ?? {};
216
+
217
+ this.#versionMetadataByModule.set(ve.moduleID, {
218
+ ...existing,
219
+ ...(patch && typeof patch === "object" ? patch : {}),
220
+ version: ve.versionTag,
221
+ logicalPath
222
+ });
223
+ }
224
+
225
+
226
+ list(logicalPath) {
227
+ const entry = this.#registry.get(logicalPath);
228
+ if (!entry) return undefined;
229
+
230
+ const versions = {};
231
+ for (const [tag, ve] of entry.versions) {
232
+ versions[tag] = { ...ve };
233
+ }
234
+ return { versions, default: this.getDefaultVersion(logicalPath) };
235
+ }
236
+
237
+
238
+ setDefault(logicalPath, versionTag) {
239
+ const entry = this.#registry.get(logicalPath);
240
+ if (!entry) {
241
+ throw new this.SlothletError("VERSION_NOT_FOUND", {
242
+ version: versionTag,
243
+ apiPath: logicalPath
244
+ });
245
+ }
246
+ if (!entry.versions.has(versionTag)) {
247
+ throw new this.SlothletError("VERSION_NOT_FOUND", {
248
+ version: versionTag,
249
+ apiPath: logicalPath
250
+ });
251
+ }
252
+
253
+ for (const ve of entry.versions.values()) {
254
+ ve.isDefault = false;
255
+ }
256
+ entry.versions.get(versionTag).isDefault = true;
257
+ }
258
+
259
+
260
+
261
+
262
+ getDefaultVersion(logicalPath) {
263
+ const entry = this.#registry.get(logicalPath);
264
+
265
+
266
+
267
+ if (!entry || entry.versions.size === 0) return null;
268
+
269
+
270
+ for (const [tag, ve] of entry.versions) {
271
+ if (ve.isDefault) return tag;
272
+ }
273
+
274
+
275
+ const tags = Array.from(entry.versions.keys());
276
+ if (tags.length === 1) return tags[0];
277
+
278
+
279
+ const sorted = tags
280
+ .map((tag) => ({ tag, tuple: normaliseVersionTag(tag) }))
281
+ .sort((a, b) => {
282
+ const cmp = compareTuples(a.tuple, b.tuple);
283
+ if (cmp !== 0) return cmp;
284
+
285
+
286
+
287
+ const aSuffix = a.tag.match(/[-+]/) ? 1 : 0;
288
+
289
+ const bSuffix = b.tag.match(/[-+]/) ? 1 : 0;
290
+ return aSuffix - bSuffix;
291
+ });
292
+
293
+ return sorted[0].tag;
294
+ }
295
+
296
+
297
+
298
+
299
+ resolveForPath(logicalPath, allVersions, caller) {
300
+ const discriminator = this.slothlet.config?.versionDispatcher ?? "version";
301
+
302
+ let resolvedTag = null;
303
+
304
+ if (typeof discriminator === "string") {
305
+
306
+ resolvedTag = caller.versionMetadata?.[discriminator] ?? null;
307
+ }
308
+
309
+
310
+
311
+ if (typeof discriminator === "function") {
312
+ try {
313
+ resolvedTag = discriminator(allVersions, caller);
314
+ } catch {
315
+ resolvedTag = null;
316
+ }
317
+ }
318
+
319
+ if (resolvedTag == null) return null;
320
+
321
+
322
+ const entry = this.#registry.get(logicalPath);
323
+ if (!entry || !entry.versions.has(resolvedTag)) {
324
+ this.slothlet.debug("versioning", {
325
+ key: "DEBUG_VERSION_RESOLVED",
326
+ version: null,
327
+ apiPath: logicalPath,
328
+
329
+
330
+ callerModule: caller?.metadata?.moduleID ?? null
331
+ });
332
+ return null;
333
+ }
334
+
335
+ this.slothlet.debug("versioning", {
336
+ key: "DEBUG_VERSION_RESOLVED",
337
+ version: resolvedTag,
338
+ apiPath: logicalPath,
339
+
340
+
341
+ callerModule: caller?.metadata?.moduleID ?? null
342
+ });
343
+
344
+ return resolvedTag;
345
+ }
346
+
347
+
348
+
349
+
350
+ buildAllVersionsArg(logicalPath) {
351
+ const entry = this.#registry.get(logicalPath);
352
+
353
+
354
+
355
+ if (!entry) return {};
356
+
357
+ const defaultTag = this.getDefaultVersion(logicalPath);
358
+ const result = {};
359
+
360
+ for (const [tag, ve] of entry.versions) {
361
+
362
+
363
+
364
+ const mountedWrapper = this.#walkApiPath(ve.versionedParts);
365
+
366
+
367
+ const regularMetadata = this.slothlet.handlers.metadata?.getMetadata?.(mountedWrapper) ?? {};
368
+
369
+ const versionMetadata = this.#versionMetadataByModule.get(ve.moduleID) ?? {};
370
+
371
+ result[tag] = {
372
+ version: tag,
373
+ default: tag === defaultTag,
374
+ metadata: regularMetadata,
375
+ versionMetadata
376
+ };
377
+ }
378
+
379
+ return result;
380
+ }
381
+
382
+
383
+ buildCallerArg(callerWrapper) {
384
+
385
+
386
+ const callerModuleID = callerWrapper?.____slothletInternal?.moduleID ?? callerWrapper?.__moduleID;
387
+ const callerVersionEntry = callerModuleID ? this.#findVersionEntryForModule(callerModuleID) : null;
388
+
389
+
390
+ const regularMetadata = this.slothlet.handlers.metadata?.getMetadata?.(callerWrapper) ?? {};
391
+
392
+ if (!callerVersionEntry) {
393
+ return {
394
+ version: null,
395
+ default: null,
396
+ metadata: regularMetadata,
397
+ versionMetadata: null
398
+ };
399
+ }
400
+
401
+ const defaultTag = this.getDefaultVersion(callerVersionEntry.logicalPath);
402
+
403
+
404
+ const versionMetadata = this.#versionMetadataByModule.get(callerModuleID) ?? {};
405
+
406
+ return {
407
+ version: callerVersionEntry.versionTag,
408
+ default: callerVersionEntry.versionTag === defaultTag,
409
+ metadata: regularMetadata,
410
+ versionMetadata
411
+ };
412
+ }
413
+
414
+
415
+ #findVersionEntryForModule(moduleID) {
416
+ const key = this.#moduleToVersionKey.get(moduleID);
417
+
418
+
419
+ if (!key) return null;
420
+ const entry = this.#registry.get(key.logicalPath);
421
+
422
+
423
+
424
+ if (!entry) return null;
425
+
426
+
427
+ return entry.versions.get(key.versionTag) ?? null;
428
+ }
429
+
430
+
431
+ #walkApiPath(apiPath) {
432
+
433
+
434
+ if (!apiPath) return undefined;
435
+ let node = this.slothlet.api;
436
+
437
+
438
+ const segments = Array.isArray(apiPath) ? apiPath : apiPath.split(".");
439
+ for (const segment of segments) {
440
+
441
+
442
+ if (node == null) return undefined;
443
+ node = node[segment];
444
+ }
445
+ return node;
446
+ }
447
+
448
+
449
+
450
+
451
+ createDispatcher(logicalPath) {
452
+ const manager = this;
453
+ const target = { __isVersionDispatcher: true, __logicalPath: logicalPath };
454
+ const displayName = logicalPath.split(".").pop();
455
+
456
+
457
+ const resolveVersion = () => {
458
+
459
+
460
+
461
+ const ctx = manager.slothlet.contextManager?.tryGetContext?.();
462
+ const forcedVersion = ctx?.context?.[FORCE_VERSION_SYMBOL];
463
+ if (forcedVersion) {
464
+ const entry = manager.#registry.get(logicalPath);
465
+ if (entry?.versions.has(forcedVersion)) return forcedVersion;
466
+ }
467
+
468
+
469
+
470
+ const callerWrapper = ctx?.currentWrapper ?? null;
471
+ const allVersions = manager.buildAllVersionsArg(logicalPath);
472
+ const caller = manager.buildCallerArg(callerWrapper);
473
+
474
+
475
+ let tag = manager.resolveForPath(logicalPath, allVersions, caller);
476
+
477
+ if (tag == null) {
478
+
479
+ tag = manager.getDefaultVersion(logicalPath);
480
+
481
+
482
+
483
+ if (tag != null) {
484
+ manager.slothlet.debug("versioning", {
485
+ key: "DEBUG_VERSION_DEFAULT_USED",
486
+ apiPath: logicalPath,
487
+ version: tag
488
+ });
489
+ }
490
+ }
491
+
492
+ return tag;
493
+ };
494
+
495
+
496
+ const resolveVersionedWrapper = () => {
497
+ const versionTag = resolveVersion();
498
+
499
+
500
+
501
+ if (!versionTag) return null;
502
+
503
+
504
+ return manager.#walkApiPath([versionTag, ...logicalPath.split(".")]);
505
+ };
506
+
507
+ const handlers = {
508
+
509
+ get(t, prop) {
510
+
511
+ if (typeof prop === "string") {
512
+ if (prop === "____slothletInternal" || prop === "_impl" || prop === "__impl" || prop === "__state" || prop === "__invalid") {
513
+ return undefined;
514
+ }
515
+ }
516
+
517
+
518
+ if (prop === "__isVersionDispatcher") return true;
519
+
520
+
521
+ if (prop === "__mode") return "eager";
522
+ if (prop === "__apiPath") return logicalPath;
523
+
524
+
525
+ if (prop === "__slothletPath") return logicalPath;
526
+ if (prop === "__isCallable") return false;
527
+
528
+
529
+
530
+
531
+ if (prop === "__materializeOnCreate") return false;
532
+ if (prop === "__materialized") return true;
533
+ if (prop === "__inFlight") return false;
534
+ if (prop === "__displayName") return displayName;
535
+ if (prop === "__moduleID") return `versionDispatcher:${logicalPath}`;
536
+ if (prop === "_materialize") return () => {};
537
+ if (prop === "length") return 0;
538
+ if (prop === "name") return displayName;
539
+
540
+
541
+
542
+
543
+
544
+ if (prop === "then") return undefined;
545
+
546
+
547
+
548
+
549
+ if (prop === "constructor") return Object.prototype.constructor;
550
+
551
+
552
+ if (prop === Symbol.toStringTag) {
553
+ const vw = resolveVersionedWrapper();
554
+
555
+
556
+ if (!vw) return "Object";
557
+ return vw[Symbol.toStringTag];
558
+ }
559
+
560
+
561
+
562
+
563
+
564
+
565
+ if (typeof prop === "symbol" && prop.toString() === "Symbol(nodejs.util.inspect.custom)") {
566
+ return () => {
567
+ const vw = resolveVersionedWrapper();
568
+ if (vw) {
569
+ try {
570
+ return inspect(vw);
571
+ } catch {
572
+
573
+ void 0;
574
+ }
575
+ }
576
+
577
+ const entry = manager.#registry.get(logicalPath);
578
+ const versions = entry ? Array.from(entry.versions.keys()) : [];
579
+ return { __versionDispatcher: logicalPath, versions };
580
+ };
581
+ }
582
+
583
+
584
+
585
+ if (prop === "toString") return () => `[VersionDispatcher: ${logicalPath}]`;
586
+
587
+
588
+ if (prop === "valueOf") return () => dispatcherProxy;
589
+
590
+
591
+ if (prop === "toJSON") return () => undefined;
592
+
593
+
594
+ if (typeof prop === "symbol") return undefined;
595
+
596
+
597
+ if (prop === "__metadata" || prop === "__filePath" || prop === "__sourceFolder" || prop === "__type") {
598
+ const vw = resolveVersionedWrapper();
599
+
600
+
601
+
602
+ if (!vw) return undefined;
603
+ return vw[prop];
604
+ }
605
+
606
+
607
+
608
+ const versionTag = resolveVersion();
609
+
610
+
611
+
612
+ if (!versionTag) {
613
+ throw new manager.SlothletError("VERSION_NO_DEFAULT", {
614
+ apiPath: logicalPath
615
+ });
616
+ }
617
+
618
+
619
+ const versionedWrapper = manager.#walkApiPath([versionTag, ...logicalPath.split(".")]);
620
+
621
+
622
+
623
+ if (!versionedWrapper) return undefined;
624
+
625
+ return versionedWrapper[prop];
626
+ },
627
+
628
+
629
+
630
+ apply() {
631
+ throw new manager.SlothletError("VERSION_DISPATCH_NOT_CALLABLE", {
632
+ apiPath: logicalPath
633
+ });
634
+ },
635
+
636
+
637
+ has(t, key) {
638
+
639
+
640
+
641
+ if (Reflect.has(t, key)) return true;
642
+ const entry = manager.#registry.get(logicalPath);
643
+
644
+
645
+ if (!entry) return false;
646
+ for (const ve of entry.versions.values()) {
647
+ const vw = manager.#walkApiPath(ve.versionedParts);
648
+
649
+
650
+
651
+
652
+
653
+ if (vw && key in vw) return true;
654
+ }
655
+ return false;
656
+ },
657
+
658
+
659
+ ownKeys(t) {
660
+ const keySet = new Set(Reflect.ownKeys(t));
661
+ const entry = manager.#registry.get(logicalPath);
662
+
663
+
664
+ if (entry) {
665
+ for (const ve of entry.versions.values()) {
666
+ const vw = manager.#walkApiPath(ve.versionedParts);
667
+
668
+
669
+ if (vw) {
670
+ for (const k of Reflect.ownKeys(Object(vw))) {
671
+ keySet.add(k);
672
+ }
673
+ }
674
+ }
675
+ }
676
+ return Array.from(keySet);
677
+ },
678
+
679
+
680
+ getOwnPropertyDescriptor(t, prop) {
681
+
682
+ const targetDesc = Reflect.getOwnPropertyDescriptor(t, prop);
683
+ if (targetDesc) return { configurable: true, enumerable: true, writable: false, value: t[prop] };
684
+
685
+
686
+
687
+
688
+ return { configurable: true, enumerable: true, writable: false, value: undefined };
689
+ }
690
+ };
691
+
692
+ let dispatcherProxy;
693
+ dispatcherProxy = new Proxy(target, handlers);
694
+ return dispatcherProxy;
695
+ }
696
+
697
+
698
+ updateDispatcher(logicalPath) {
699
+
700
+
701
+ if (this.#dispatchers.has(logicalPath)) {
702
+ return;
703
+ }
704
+
705
+ const dispatcher = this.createDispatcher(logicalPath);
706
+ this.#dispatchers.set(logicalPath, dispatcher);
707
+
708
+ const parts = logicalPath.split(".");
709
+ const mountOptions = {
710
+ collisionMode: "replace",
711
+ moduleID: `versionDispatcher:${logicalPath}`,
712
+ allowOverwrite: true,
713
+ mutateExisting: false
714
+ };
715
+
716
+
717
+
718
+
719
+ if (this.slothlet.api) {
720
+ this.slothlet.handlers.apiManager.setValueAtPath(this.slothlet.api, parts, dispatcher, mountOptions);
721
+ }
722
+
723
+
724
+ if (this.slothlet.boundApi) {
725
+ this.slothlet.handlers.apiManager.setValueAtPath(this.slothlet.boundApi, parts, dispatcher, mountOptions);
726
+ }
727
+ }
728
+
729
+
730
+ teardownDispatcher(logicalPath) {
731
+ this.#dispatchers.delete(logicalPath);
732
+
733
+ const parts = logicalPath.split(".");
734
+
735
+
736
+
737
+ if (this.slothlet.api) {
738
+ this.slothlet.handlers.apiManager.deletePath(this.slothlet.api, parts).catch(() => {});
739
+ }
740
+ if (this.slothlet.boundApi) {
741
+ this.slothlet.handlers.apiManager.deletePath(this.slothlet.boundApi, parts).catch(() => {});
742
+ }
743
+
744
+ }
745
+
746
+
747
+
748
+
749
+ onVersionedModuleReload(moduleID) {
750
+ const key = this.#moduleToVersionKey.get(moduleID);
751
+ if (!key) return;
752
+
753
+ const { logicalPath } = key;
754
+
755
+
756
+ this.updateDispatcher(logicalPath);
757
+
758
+ this.slothlet.debug("versioning", {
759
+ key: "DEBUG_VERSION_REGISTERED",
760
+ version: key.versionTag,
761
+ logicalPath,
762
+ moduleID
763
+ });
764
+ }
765
+
766
+
767
+ shutdown() {
768
+ this.#registry.clear();
769
+ this.#versionMetadataByModule.clear();
770
+ this.#moduleToVersionKey.clear();
771
+ this.#dispatchers.clear();
772
+ }
773
+ }