@cldmv/slothlet 3.8.0 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +6 -7
  2. package/dist/lib/builders/api_builder.mjs +1 -1
  3. package/dist/lib/builders/modes-processor.mjs +1 -1
  4. package/dist/lib/handlers/api-manager.mjs +1 -1
  5. package/dist/lib/handlers/context-async.mjs +1 -1
  6. package/dist/lib/handlers/metadata.mjs +1 -1
  7. package/dist/lib/handlers/module-manager.mjs +1 -1
  8. package/dist/lib/handlers/unified-wrapper.mjs +1 -1
  9. package/dist/lib/handlers/version-manager.mjs +1 -1
  10. package/dist/lib/helpers/class-instance-wrapper.mjs +1 -1
  11. package/dist/lib/helpers/config.mjs +1 -1
  12. package/dist/lib/helpers/eventemitter-context.mjs +1 -1
  13. package/dist/lib/helpers/generate-manifest.mjs +17 -0
  14. package/dist/lib/helpers/manifest-resolver.mjs +17 -0
  15. package/dist/lib/helpers/module-discovery.mjs +1 -1
  16. package/dist/lib/helpers/module-manifest-validator.mjs +1 -1
  17. package/dist/lib/helpers/platform.mjs +17 -0
  18. package/dist/lib/helpers/resolve-from-caller.mjs +1 -1
  19. package/dist/lib/i18n/languages/de-de.json +53 -46
  20. package/dist/lib/i18n/languages/en-gb.json +53 -46
  21. package/dist/lib/i18n/languages/en-us.json +53 -46
  22. package/dist/lib/i18n/languages/es-es.json +53 -46
  23. package/dist/lib/i18n/languages/es-mx.json +53 -46
  24. package/dist/lib/i18n/languages/fr-fr.json +7 -0
  25. package/dist/lib/i18n/languages/hi-in.json +49 -42
  26. package/dist/lib/i18n/languages/ja-jp.json +9 -2
  27. package/dist/lib/i18n/languages/ko-kr.json +9 -2
  28. package/dist/lib/i18n/languages/pt-br.json +53 -46
  29. package/dist/lib/i18n/languages/ru-ru.json +45 -38
  30. package/dist/lib/i18n/languages/zh-cn.json +14 -7
  31. package/dist/lib/i18n/translations.mjs +1 -1
  32. package/dist/lib/processors/loader.mjs +1 -1
  33. package/dist/lib/processors/type-generator.mjs +1 -1
  34. package/dist/lib/processors/typescript.mjs +1 -1
  35. package/dist/lib/typegen/typegen.mjs +1 -1
  36. package/dist/slothlet.mjs +1 -1
  37. package/index.cjs +4 -14
  38. package/index.mjs +19 -22
  39. package/package.json +8 -2
  40. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  41. package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
  42. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
  43. package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
  44. package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
  45. package/types/dist/lib/handlers/module-manager.d.mts.map +1 -1
  46. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
  47. package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
  48. package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -1
  49. package/types/dist/lib/helpers/config.d.mts +7 -0
  50. package/types/dist/lib/helpers/config.d.mts.map +1 -1
  51. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
  52. package/types/dist/lib/helpers/generate-manifest.d.mts +2 -0
  53. package/types/dist/lib/helpers/generate-manifest.d.mts.map +1 -0
  54. package/types/dist/lib/helpers/manifest-resolver.d.mts +2 -0
  55. package/types/dist/lib/helpers/manifest-resolver.d.mts.map +1 -0
  56. package/types/dist/lib/helpers/module-discovery.d.mts.map +1 -1
  57. package/types/dist/lib/helpers/module-manifest-validator.d.mts.map +1 -1
  58. package/types/dist/lib/helpers/platform.d.mts +12 -0
  59. package/types/dist/lib/helpers/platform.d.mts.map +1 -0
  60. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  61. package/types/dist/lib/i18n/translations.d.mts +1 -0
  62. package/types/dist/lib/i18n/translations.d.mts.map +1 -1
  63. package/types/dist/lib/processors/loader.d.mts.map +1 -1
  64. package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
  65. package/types/dist/lib/processors/typescript.d.mts.map +1 -1
  66. package/types/dist/lib/typegen/typegen.d.mts.map +1 -1
  67. package/types/dist/slothlet.d.mts.map +1 -1
  68. package/types/index.d.mts +3 -24
  69. package/types/index.d.mts.map +1 -1
@@ -2,6 +2,9 @@
2
2
  "translations": {
3
3
  "INVALID_CONFIG": "Неверная конфигурация: {option} имеет значение {value}, ожидалось {expected}. {hint}",
4
4
  "INVALID_CONFIG_DIR_MISSING": "Ошибка конфигурации: параметр 'dir' обязателен. Укажите путь к директории для загрузки API.",
5
+ "INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST": "Ошибка конфигурации: 'manifest' обязателен, когда env равно 'browser'. Предоставьте заранее сгенерированный объект манифеста с массивами 'files' и 'directories'.",
6
+ "INVALID_CONFIG_BROWSER_MANIFEST_INVALID": "Ошибка конфигурации: 'manifest' должен быть простым объектом с массивами 'files' и 'directories'. Получено: {received}",
7
+ "INVALID_CONFIG_BROWSER_RESOLVE_SPECIFIER_INVALID": "Ошибка конфигурации: 'resolveModuleSpecifier' должен быть функцией, когда env равно 'browser'. Получено: {received}",
5
8
  "INVALID_CONFIG_DIR_INVALID": "Ошибка конфигурации: 'dir' должен быть непустой строкой-путем. Получено: {dir}",
6
9
  "INVALID_CONFIG_MODE_INVALID": "Ошибка конфигурации: 'mode' должен быть либо 'eager', либо 'lazy'. Получено: {value}",
7
10
  "INVALID_CONFIG_NOT_LOADED": "Невозможно выполнить операцию '{operation}' - экземпляр не загружен. Сначала вызовите load().",
@@ -28,7 +31,7 @@
28
31
  "CONTEXT_ALREADY_EXISTS": "Контекст для экземпляра '{instanceID}' уже существует. Невозможно инициализировать дважды.",
29
32
  "CONTEXT_NOT_FOUND": "Контекст для экземпляра '{instanceID}' не найден. Экземпляр мог быть завершён. Доступные экземпляры: {availableInstances}",
30
33
  "CONTEXT_EXECUTION_FAILED": "Не удалось выполнить в контексте для экземпляра '{instanceID}': {error}",
31
- "NO_ACTIVE_CONTEXT_LIVE": "Нет активного контекста — в режиме live bindings нет активного экземпляра.",
34
+ "NO_ACTIVE_CONTEXT_LIVE": "Нет активного контекста — в режиме живых привязок нет активного экземпляра.",
32
35
  "NO_ACTIVE_CONTEXT_ASYNC": "Нет активного контекста — функцию нужно вызывать внутри slothlet API-контекста.",
33
36
  "RUNTIME_NO_ACTIVE_CONTEXT_SELF": "Нет активного контекста — доступ к 'self' невозможен. Вызывайте внутри Slothlet API-функции.",
34
37
  "RUNTIME_NO_ACTIVE_CONTEXT_CONTEXT": "Нет активного контекста — 'context' можно менять только внутри Slothlet API-вызовов.",
@@ -54,7 +57,10 @@
54
57
  "HINT_WARNING_COLLISION_TRIGGER_MATERIALIZE_ERROR": "Это некритическая фоноваая ошибка при обработке конфликтов. Lazy-папка по этому пути не смогла материализоваться. Проверьте модуль по указанному пути.",
55
58
  "WARNING_LIFECYCLE_HANDLER_ERROR": "Обработчик lifecycle-события выбросил ошибку для события '{event}'. Другие обработчики продолжили выполнение.",
56
59
  "HINT_WARNING_LIFECYCLE_HANDLER_ERROR": "Один из обработчиков lifecycle для события '{event}' выдал ошибку. Проверьте lifecycle.on('{event}', ...) на наличие багов. Другие обработчики unaffected.",
57
- "WARNING_MULTIPLE_ROOT_CONTRIBUTORS": "Обнаружено несколько root-level default экспортов: {rootContributors}. Каждый был помещён в namespace по имени файла (например api.{firstContributor}()). Рассмотрите вариант объединения.",
60
+ "WARNING_MULTIPLE_ROOT_CONTRIBUTORS": "Обнаружено несколько default экспортов корневого уровня: {rootContributors}. Каждый был помещён в namespace по имени файла (например api.{firstContributor}()). Рассмотрите вариант объединения.",
61
+ "WARN_SUPPRESS_FIX_ACTIVE": "Исправление правила '{rule}' подавлено через 'suppressFixes'. Подробности см. по адресу {url}. Это временное переопределение, которое будет удалено в v4. Исправленное поведение '{rule}' будет применено постоянно.",
62
+ "HINT_WARN_SUPPRESS_FIX_ACTIVE": "Удалите '{rule}' из 'suppressFixes' и обновите структуру директорий API для поддержки исправленного поведения перед обновлением до v4.",
63
+ "V3_CONFIG_DEPRECATED": "Параметр конфигурации '{option}' устарел и будет удалён в v4. Используйте вместо него '{replacement}'.",
58
64
  "V2_CONFIG_UNSUPPORTED": "Опция конфигурации '{option}' из v2 не поддерживается в v3. {hint} Используйте '{replacement}'.",
59
65
  "DEBUG_MODE_ROOT_CONTRIBUTOR": "[{mode}] Обнаружен root-contributor: {functionName}",
60
66
  "DEBUG_MODE_ROOT_CONTRIBUTOR_APPLIED": "[{mode}] Применён шаблон root-contributor — API-функция с {properties} дополнительными свойствами",
@@ -78,12 +84,12 @@
78
84
  "DEBUG_MODE_COLLISION_REPLACE_VERIFY": "СТОЛКНОВЕНИЕ-ЗАМЕНА: проверка",
79
85
  "DEBUG_MODE_COLLISION_COPY_CHILD_KEYS": "СТОЛКНОВЕНИЕ-КОПИРОВАНИЕ: копирование дочерних ключей",
80
86
  "DEBUG_MODE_COLLISION_COPY_INDIVIDUAL_KEY": "СТОЛКНОВЕНИЕ-КОПИРОВАНИЕ: копирование отдельного ключа",
81
- "DEBUG_MODE_COLLISION_TRIGGER_EARLY_MAT": "СТОЛКНОВЕНИЕ-ЗАПУСК-ИНСТАНЦИРОВАНИЯ: ранняя материализация (fire-and-forget)",
87
+ "DEBUG_MODE_COLLISION_TRIGGER_EARLY_MAT": "СТОЛКНОВЕНИЕ-ЗАПУСК-ИНСТАНЦИРОВАНИЯ: ранняя материализация (запустил-и-забыл)",
82
88
  "DEBUG_MODE_MERGE_API_OBJECTS_ENTRY": "mergeApiObjects: вход",
83
89
  "DEBUG_MODE_MERGE_API_OBJECTS_SOURCE_KEYS": "mergeApiObjects: ключи sourceApi",
84
90
  "DEBUG_MODE_MERGE_API_OBJECTS_EXIT_INVALID_SOURCE": "mergeApiObjects: выход — sourceApi не объект/функция",
85
91
  "DEBUG_MODE_MERGE_API_OBJECTS_PROCESSING_KEY": "mergeApiObjects: обработка ключа",
86
- "DEBUG_MODE_MERGE_API_OBJECTS_RECURSING": "mergeApiObjects — оба plain object, рекурсивно",
92
+ "DEBUG_MODE_MERGE_API_OBJECTS_RECURSING": "mergeApiObjects — оба простые object, рекурсивно",
87
93
  "DEBUG_MODE_MERGE_API_OBJECTS_CALLING_ASSIGN": "mergeApiObjects: вызов assignToApiPath",
88
94
  "DEBUG_MODE_BUILD_FINAL_API_CALLED": "Вызван buildFinalAPI",
89
95
  "DEBUG_MODE_SLOTHLET_NAMESPACE_CREATED": "Создано пространство имён Slothlet",
@@ -139,7 +145,7 @@
139
145
  "DEBUG_MODE_MUTATE_API_VALUE_SYNC_WRAPPERS": "mutateApiValue — оба являются обёртками, вызывается syncWrapper",
140
146
  "DEBUG_MODE_MUTATE_API_VALUE_MERGE_INTO_WRAPPER": "mutateApiValue — слияние свойств в существующую обёртку",
141
147
  "DEBUG_MODE_MUTATE_API_VALUE_MERGE_KEYS": "mutateApiValue: ключи для слияния",
142
- "DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK": "mutateApiValue — использование fallback ___setImpl",
148
+ "DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK": "mutateApiValue — использование резервного ___setImpl",
143
149
  "DEBUG_MODE_SET_VALUE_AT_PATH": "установить-значение-на-пути",
144
150
  "DEBUG_MODE_SET_VALUE_AT_PATH_SKIP_COLLISION": "setValueAtPath — пропуск столкновения",
145
151
  "DEBUG_MODE_SET_VALUE_AT_PATH_REPLACE_MERGE": "setValueAtPath — замена слиянием (сохраняет обёртку)",
@@ -168,7 +174,7 @@
168
174
  "DEBUG_MODE_WRAPPER_CONSTRUCTOR_IMPL_KEYS": "Конструктор UnifiedWrapper — ключи реализации",
169
175
  "DEBUG_MODE_WRAPPER_CONSTRUCTOR_AFTER_ADOPT": "Конструктор UnifiedWrapper — после adopt",
170
176
  "DEBUG_MODE_BACKGROUND_MATERIALIZE_ERROR": "Фоновая ошибка материализации",
171
- "DEBUG_MODE_APPLY_IMPL_UPDATE_PATH": "APPLY-IMPL-UPDATE-PATH: обновлён filePath из null",
177
+ "DEBUG_MODE_APPLY_IMPL_UPDATE_PATH": "[APPLY-IMPL-UPDATE-PATH]: обновлён filePath из null",
172
178
  "DEBUG_MODE_SETIMPL_CALLED": "___setImpl вызван",
173
179
  "DEBUG_MODE_RESETLAZY_CALLED": "___resetLazy вызван",
174
180
  "DEBUG_MODE_RESETLAZY_COMPLETE": "___resetLazy завершён — обёртка теперь не материализована",
@@ -177,47 +183,47 @@
177
183
  "DEBUG_MODE_MATERIALIZE_CALLING_FUNC": "_materialize: вызов materializeFunc",
178
184
  "DEBUG_MODE_MATERIALIZE_COMPLETE": "_materialize: завершено",
179
185
  "DEBUG_MODE_MATERIALIZE_ERROR": "_materialize: ошибка",
180
- "DEBUG_MODE_ADOPT_START": "ADOPT: начало",
181
- "DEBUG_MODE_ADOPT_REPLACE_CLEARING": "ADOPT: режим REPLACE — очистка существующих свойств",
182
- "DEBUG_MODE_ADOPT_PROCESS": "ADOPT: процесс",
183
- "DEBUG_MODE_ADOPT_CHECK": "ADOPT: проверка",
184
- "DEBUG_MODE_ADOPT_SKIP_COLLISION_MERGED": "ADOPT: пропуск — столкновение объединено, сохраняется версия из файла",
185
- "DEBUG_MODE_ADOPT_SKIP_NON_CONFIGURABLE": "ADOPT: пропуск — свойство неконфигурируемо (унаследовано)",
186
- "DEBUG_MODE_ADOPT_SKIP_SAME_WRAPPER": "ADOPT: пропуск — свойство уже существует с той же обёрткой",
187
- "DEBUG_MODE_ADOPT_ALLOW_NOT_COLLISION_MERGED": "ADOPT: разрешено — не было объединения",
188
- "DEBUG_MODE_ADOPT_REUSE_CHILD_WRAPPER": "ADOPT: повторное использование дочерней обёртки",
189
- "DEBUG_MODE_ADOPT_WRAP": "ADOPT: обёртывание",
190
- "DEBUG_MODE_ADOPT_DEFINE": "ADOPT: определение на обёртке",
191
- "DEBUG_MODE_ADOPT_DEFINED": "ADOPT: успешно определено на обёртке",
192
- "DEBUG_MODE_WRAP_CHILD_PATH_CHECK": "WRAP-CHILD-PATH: проверка пути дочернего файла",
193
- "DEBUG_MODE_WRAP_CHILD_PATH_AVAILABLE": "WRAP-CHILD-PATH: __childFilePaths доступны",
194
- "DEBUG_MODE_WRAP_CHILD_PATH_USING": "WRAP-CHILD-PATH: использование __childFilePaths",
195
- "DEBUG_MODE_WRAP_CHILD_PATH_PRE_MAT": "WRAP-CHILD-PATH: использование __childFilePathsPreMaterialize",
196
- "DEBUG_MODE_WRAP_CHILD_PATH_FALLBACK": "WRAP-CHILD-PATH: использование запасного parentMetadata?.filePath",
186
+ "DEBUG_MODE_ADOPT_START": "[ADOPT]: начало",
187
+ "DEBUG_MODE_ADOPT_REPLACE_CLEARING": "[ADOPT]: режим REPLACE — очистка существующих свойств",
188
+ "DEBUG_MODE_ADOPT_PROCESS": "[ADOPT]: процесс",
189
+ "DEBUG_MODE_ADOPT_CHECK": "[ADOPT]: проверка",
190
+ "DEBUG_MODE_ADOPT_SKIP_COLLISION_MERGED": "[ADOPT]: пропуск — столкновение объединено, сохраняется версия из файла",
191
+ "DEBUG_MODE_ADOPT_SKIP_NON_CONFIGURABLE": "[ADOPT]: пропуск — свойство неконфигурируемо (унаследовано)",
192
+ "DEBUG_MODE_ADOPT_SKIP_SAME_WRAPPER": "[ADOPT]: пропуск — свойство уже существует с той же обёрткой",
193
+ "DEBUG_MODE_ADOPT_ALLOW_NOT_COLLISION_MERGED": "[ADOPT]: разрешено — не было объединения",
194
+ "DEBUG_MODE_ADOPT_REUSE_CHILD_WRAPPER": "[ADOPT]: повторное использование дочерней обёртки",
195
+ "DEBUG_MODE_ADOPT_WRAP": "[ADOPT]: обёртывание",
196
+ "DEBUG_MODE_ADOPT_DEFINE": "[ADOPT]: определение на обёртке",
197
+ "DEBUG_MODE_ADOPT_DEFINED": "[ADOPT]: успешно определено на обёртке",
198
+ "DEBUG_MODE_WRAP_CHILD_PATH_CHECK": "[WRAP-CHILD-PATH]: проверка пути дочернего файла",
199
+ "DEBUG_MODE_WRAP_CHILD_PATH_AVAILABLE": "[WRAP-CHILD-PATH]: __childFilePaths доступны",
200
+ "DEBUG_MODE_WRAP_CHILD_PATH_USING": "[WRAP-CHILD-PATH]: использование __childFilePaths",
201
+ "DEBUG_MODE_WRAP_CHILD_PATH_PRE_MAT": "[WRAP-CHILD-PATH]: использование __childFilePathsPreMaterialize",
202
+ "DEBUG_MODE_WRAP_CHILD_PATH_FALLBACK": "[WRAP-CHILD-PATH]: использование запасного parentMetadata?.filePath",
197
203
  "DEBUG_MODE_WAITING_TYPE": "ОЖИДАЕТ-ТИП",
198
- "DEBUG_MODE_WAITING_TYPE_WALK_WRAPPER": "WAITING-TYPE-WALK: найдено в обёртке",
199
- "DEBUG_MODE_WAITING_TYPE_WALK_IMPL": "WAITING-TYPE-WALK: найдено в _impl",
200
- "DEBUG_MODE_WAITING_TYPE_WALK_DIRECT": "WAITING-TYPE-WALK: доступ напрямую",
204
+ "DEBUG_MODE_WAITING_TYPE_WALK_WRAPPER": "[WAITING-TYPE-WALK]: найдено в обёртке",
205
+ "DEBUG_MODE_WAITING_TYPE_WALK_IMPL": "[WAITING-TYPE-WALK]: найдено в _impl",
206
+ "DEBUG_MODE_WAITING_TYPE_WALK_DIRECT": "[WAITING-TYPE-WALK]: доступ напрямую",
201
207
  "DEBUG_MODE_WAITING_TYPE_RESOLVED": "ОЖИДАЕТ-ТИП: разрешено",
202
- "DEBUG_MODE_WAITING_TYPE_INFLIGHT": "WAITING-TYPE-INFLIGHT: возвращает IN_FLIGHT",
203
- "DEBUG_MODE_WAITING_GET_PREMATURE": "WAITING-GET-PREMATURE: найдено до материализации",
204
- "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT": "WAITING-GET-IMMEDIATE-MAT: инициирована немедленная материализация для collision-merged папки",
205
- "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT_ERROR": "WAITING-GET-IMMEDIATE-MAT-ERROR: материализация не удалась",
206
- "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT_SUCCESS": "WAITING-GET-IMMEDIATE-MAT-SUCCESS: теперь доступно в обёртке",
208
+ "DEBUG_MODE_WAITING_TYPE_INFLIGHT": "[WAITING-TYPE-INFLIGHT]: возвращает IN_FLIGHT",
209
+ "DEBUG_MODE_WAITING_GET_PREMATURE": "[WAITING-GET-PREMATURE]: найдено до материализации",
210
+ "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT": "[WAITING-GET-IMMEDIATE-MAT]: инициирована немедленная материализация для объединённой при коллизии папки",
211
+ "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT_ERROR": "[WAITING-GET-IMMEDIATE-MAT-ERROR]: материализация не удалась",
212
+ "DEBUG_MODE_WAITING_GET_IMMEDIATE_MAT_SUCCESS": "[WAITING-GET-IMMEDIATE-MAT-SUCCESS]: теперь доступно в обёртке",
207
213
  "DEBUG_MODE_WAITING_APPLY_ENTRY": "ОЖИДАЕТ-АНДЕР-ЗАПИСЬ",
208
- "DEBUG_MODE_WAITING_APPLY_MATERIALIZE": "WAITING-APPLY-MATERIALIZE: запуск материализации",
209
- "DEBUG_MODE_WAITING_APPLY_MATERIALIZED": "WAITING-APPLY-MATERIALIZED: материализация завершена",
210
- "DEBUG_MODE_WAITING_APPLY_START_WALK": "WAITING-APPLY-START-WALK: начало обхода propChain",
214
+ "DEBUG_MODE_WAITING_APPLY_MATERIALIZE": "[WAITING-APPLY-MATERIALIZE]: запуск материализации",
215
+ "DEBUG_MODE_WAITING_APPLY_MATERIALIZED": "[WAITING-APPLY-MATERIALIZED]: материализация завершена",
216
+ "DEBUG_MODE_WAITING_APPLY_START_WALK": "[WAITING-APPLY-START-WALK]: начало обхода propChain",
211
217
  "DEBUG_MODE_WAITING_APPLY_WALK": "ОЖИДАЕТ-ОБХОД-АНДЕР",
212
218
  "DEBUG_MODE_WAITING_APPLY": "ОЖИДАЕТ-АНДЕР",
213
- "DEBUG_MODE_GET_START": "GET: начало",
219
+ "DEBUG_MODE_GET_START": "[GET]: начало",
214
220
  "DEBUG_MODE_INSPECT_LAZY_UNMATERIALIZED": "util.inspect.custom: lazy не материализовано",
215
221
  "DEBUG_MODE_GET_CACHED_REPLACE": "ПОЛУЧИТЬ-ИЗ-КЭША-ЗАМЕНИТЬ",
216
222
  "DEBUG_MODE_GET_CACHED": "ПОЛУЧЕНО-ИЗ-КЭША",
217
223
  "DEBUG_MODE_ADOPT": "ПРИНЯТЬ",
218
- "DEBUG_MODE_GET_PROXYGET_ACCESSING": "GET-PROXYGET: доступ",
219
- "DEBUG_MODE_GET_PROXYGET_FOUND": "GET-PROXYGET: найдено в обёртке",
220
- "DEBUG_MODE_LAZY_GET_CREATE_WAITING_PROXY": "LAZY-GET: будет создан ожид. прокси",
224
+ "DEBUG_MODE_GET_PROXYGET_ACCESSING": "[GET-PROXYGET]: доступ",
225
+ "DEBUG_MODE_GET_PROXYGET_FOUND": "[GET-PROXYGET]: найдено в обёртке",
226
+ "DEBUG_MODE_LAZY_GET_CREATE_WAITING_PROXY": "[LAZY-GET]: будет создан ожид. прокси",
221
227
  "DEBUG_MODE_BUILD_LAZY_API_CALLED": "buildLazyAPI вызван",
222
228
  "DEBUG_MODE_COMPONENT_INITIALIZED": "Компонент инициализирован: {component} → this.{category}.{propertyName}",
223
229
  "DEBUG_MODE_LAZY_WRAPPER_REGISTERED": "Lazy-обёртка зарегистрирована",
@@ -261,6 +267,7 @@
261
267
  "HINT_WARNING_HOT_RELOAD_MERGE_PRIMITIVES": "Нельзя объединить примитивы — используйте 'replace' для перезаписи или оставьте 'warn' для сохранения существующего.",
262
268
  "HINT_WARNING_OWNERSHIP_CONFLICT": "Путь принадлежит другому модулю — используйте forceOverwrite: true или вызовите api.slothlet.api.remove().",
263
269
  "HINT_V2_CONFIG_UNSUPPORTED": "Опция v2 не поддерживается в v3 — обновите конфигурацию.",
270
+ "HINT_V3_CONFIG_DEPRECATED": "Этот параметр конфигурации был переименован для ясности. Обновите код на новое имя параметра для совместимости с v4.",
264
271
  "HINT_MODULE_LOAD_FAILED": "Проверьте файл модуля на ошибки синтаксиса и экспорты.",
265
272
  "HINT_MODULE_IMPORT_FAILED": "Проверьте существование файла и корректность импорта. Проверьте зависимости.",
266
273
  "HINT_MODULE_NO_EXPORTS": "Модуль должен экспортировать хотя бы одну функцию или объект.",
@@ -2,6 +2,9 @@
2
2
  "translations": {
3
3
  "INVALID_CONFIG": "配置错误:{option} 为 {value},预期为 {expected}。{hint}",
4
4
  "INVALID_CONFIG_DIR_MISSING": "配置错误:必须提供 'dir' 选项。请指定加载 API 的目录路径。",
5
+ "INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST": "配置错误:当 env 为 'browser' 时,必须提供 'manifest'。请提供包含 'files' 和 'directories' 数组的预生成 manifest 对象。",
6
+ "INVALID_CONFIG_BROWSER_MANIFEST_INVALID": "配置错误:'manifest' 必须是包含 'files' 和 'directories' 数组的普通对象。收到:{received}",
7
+ "INVALID_CONFIG_BROWSER_RESOLVE_SPECIFIER_INVALID": "配置错误:当 env 为 'browser' 时,'resolveModuleSpecifier' 必须是一个函数。收到:{received}",
5
8
  "INVALID_CONFIG_DIR_INVALID": "配置错误:'dir' 必须是非空字符串路径。接收到:{dir}",
6
9
  "INVALID_CONFIG_MODE_INVALID": "配置错误:'mode' 必须是 'eager' 或 'lazy'。接收到:{value}",
7
10
  "INVALID_CONFIG_NOT_LOADED": "无法执行操作 '{operation}' - 实例未加载。请先调用 load()。",
@@ -14,7 +17,7 @@
14
17
  "METADATA_LIFECYCLE_BYPASS": "tagSystemMetadata() 必须通过生命周期系统调用。请使用 lifecycle.emit('impl:created') 或 lifecycle.emit('impl:changed'),而不是直接调用。",
15
18
  "INVALID_METADATA_KEY": "无效的元数据键:预期为 {expected},键 '{key}' 接收到类型 {type}。元数据键必须格式正确。",
16
19
  "WARNING_METADATA_MISMATCH": "安全警告:'{apiPath}' 的元数据文件路径不匹配。堆栈跟踪显示为 '{stackFile}',但元数据记录为 '{metadataFile}'。这可能表明存在热重载或篡改。",
17
- "COLLISION_ERROR": "冲突错误 at '{key}' (context: {collisionContext}, mode: {collisionMode})。请将冲突模式设置为 'merge'、'replace'、'warn' 或 'skip' 以处理冲突。",
20
+ "COLLISION_ERROR": "冲突错误位于 '{key}' (context: {collisionContext}, mode: {collisionMode})。请将冲突模式设置为 'merge'、'replace'、'warn' 或 'skip' 以处理冲突。",
18
21
  "COLLISION_DEFAULT_EXPORT_ERROR": "检测到冲突:属性 '{key}' 已存在于 '{apiPath}' 的默认导出中。当前冲突模式为 'error'。",
19
22
  "INVALID_ARGUMENT": "无效参数 '{argument}':预期为 {expected},接收到 {received}。",
20
23
  "RUNTIME_NO_ACTIVE_CONTEXT": "未找到活动上下文。此操作需要在 slothlet API 函数内部调用。",
@@ -39,7 +42,7 @@
39
42
  "LOOSE_SET_RESERVED_KEY": "无法在 apiPath '{apiPath}' 中分配给保留键段 '{segment}'。__proto__、prototype 和 constructor 等名称已被阻止,以防止 API 树受到原型污染。",
40
43
  "INVALID_DIRECTORY": "无效目录 '{dir}':{error}。请确保目录存在且可访问。",
41
44
  "WARN_DIRECTORY_EMPTY": "目录 '{dir}'(解析路径:'{resolvedPath}')为空或不包含可加载模块(.mjs, .cjs, .js 文件)。这在 add-api 工作流程中是有效的,但可能表明路径配置有误。",
42
- "INTERNAL_HOOK_STATE_CORRUPT": "[内部错误] HookManager 状态损坏:钩子 \"{hookId}\" ({type}/{subset}/{pattern}) — {detail}。这是一个 Slothlet 的 bug — 请报告它。",
45
+ "INTERNAL_HOOK_STATE_CORRUPT": "[内部错误] HookManager 状态损坏:钩子 \"{hookId}\" ({type}/{subset}/{pattern}) — {detail}。这是一个 Slothlet 的缺陷 — 请报告它。",
43
46
  "WARNING_RESERVED_PROPERTY_CONFLICT": "用户 API 与保留属性冲突:{properties}",
44
47
  "WARNING_LANGUAGE_LOAD_FAILED": "加载语言 '{lang}' 失败,正在回退到英语。",
45
48
  "WARNING_LANGUAGE_UNAVAILABLE": "语言 '{lang}' 不可用,正在使用英语。",
@@ -55,6 +58,9 @@
55
58
  "WARNING_LIFECYCLE_HANDLER_ERROR": "生命周期事件处理程序为事件 '{event}' 抛出错误。该事件的其他处理程序继续执行。",
56
59
  "HINT_WARNING_LIFECYCLE_HANDLER_ERROR": "为 '{event}' 事件注册的生命周期处理程序抛出错误。请检查您的 lifecycle.on('{event}', ...) 处理程序是否存在错误。同一事件中的其他处理程序不受影响。",
57
60
  "WARNING_MULTIPLE_ROOT_CONTRIBUTORS": "检测到多个根级默认函数导出:{rootContributors}。每个导出已通过文件名(例如 api.{firstContributor}())进行命名空间划分。请考虑使用单个根级默认导出或将文件移动到子目录中。",
61
+ "WARN_SUPPRESS_FIX_ACTIVE": "规则修复 '{rule}' 已通过 'suppressFixes' 被禁用。详情请参阅 {url}。这是一个临时覆盖,将在 v4 中移除。'{rule}' 的修正行为将被永久执行。",
62
+ "HINT_WARN_SUPPRESS_FIX_ACTIVE": "在升级到 v4 之前,请从 'suppressFixes' 中删除 '{rule}',并更新您的 API 目录结构以适应修正后的行为。",
63
+ "V3_CONFIG_DEPRECATED": "配置选项 '{option}' 已弃用,将在 v4 中移除。请改用 '{replacement}'。",
58
64
  "V2_CONFIG_UNSUPPORTED": "v2 中的配置选项 '{option}' 在 v3 中不受支持。{hint} 请改为使用 '{replacement}'。",
59
65
  "DEBUG_MODE_ROOT_CONTRIBUTOR": "[{mode}] 检测到根贡献者:{functionName}",
60
66
  "DEBUG_MODE_ROOT_CONTRIBUTOR_APPLIED": "[{mode}] 应用了根贡献者模式 - 具有 {properties} 个附加属性的函数 API",
@@ -168,7 +174,7 @@
168
174
  "DEBUG_MODE_WRAPPER_CONSTRUCTOR_IMPL_KEYS": "UnifiedWrapper 构造函数 - 实现(impl)键",
169
175
  "DEBUG_MODE_WRAPPER_CONSTRUCTOR_AFTER_ADOPT": "UnifiedWrapper 构造函数 - 采纳(adopt)后",
170
176
  "DEBUG_MODE_BACKGROUND_MATERIALIZE_ERROR": "后台实例化错误",
171
- "DEBUG_MODE_APPLY_IMPL_UPDATE_PATH": "APPLY-IMPL-UPDATE-PATH:从 null 更新了 filePath",
177
+ "DEBUG_MODE_APPLY_IMPL_UPDATE_PATH": "[APPLY-IMPL-UPDATE-PATH]:从 null 更新了 filePath",
172
178
  "DEBUG_MODE_SETIMPL_CALLED": "___setImpl 已调用",
173
179
  "DEBUG_MODE_RESETLAZY_CALLED": "___resetLazy 已调用",
174
180
  "DEBUG_MODE_RESETLAZY_COMPLETE": "___resetLazy 完成 - 包装器现在处于未实例化(un-materialized)状态",
@@ -235,7 +241,7 @@
235
241
  "HINT_OWNERSHIP_INVALID_MODULE_ID": "模块 ID 无效。请确保模块加载正确且 ID 准确。",
236
242
  "HINT_OWNERSHIP_INVALID_API_PATH": "API 路径无效。请确保路径格式正确。",
237
243
  "HINT_OWNERSHIP_CONFLICT": "路径已被另一个模块拥有。选项:(1) 在 api.add() 选项中使用 forceOverwrite: true;(2) 将冲突配置设置为 'merge' 或 'replace';(3) 先调用 api.slothlet.api.remove() 清除路径。",
238
- "HINT_INVALID_CONFIG": "请检查配置值,并确保其符合预期的类型 and 格式。",
244
+ "HINT_INVALID_CONFIG": "请检查配置值,并确保其符合预期的类型和格式。",
239
245
  "HINT_INVALID_CONFIG_DIR_INVALID": "请确保目录路径是有效的字符串,并指向一个现有的目录。",
240
246
  "HINT_INVALID_CONFIG_FORCE_OVERWRITE_REQUIRES_MODULE_ID": "使用 forceOverwrite 或 allowOverwrite 时,必须提供唯一的 moduleID 以跟踪被覆盖路径的所有权。",
241
247
  "HINT_COLLISION_ERROR": "冲突模式设置为 'error',会阻止任何覆盖。请改用 'merge'(合并属性)、'replace'(完全覆盖)、'warn'(警告但保留现有)或 'skip'(默默忽略)。",
@@ -261,14 +267,15 @@
261
267
  "HINT_WARNING_HOT_RELOAD_MERGE_PRIMITIVES": "无法合并原始值(字符串、数字、布尔值)。若要替换该值,请使用冲突模式 'replace';若要保留现有值,请继续使用 'warn' 模式。",
262
268
  "HINT_WARNING_OWNERSHIP_CONFLICT": "路径已被另一个模块拥有。若要强制覆盖,请在 api.add() 选项中使用 forceOverwrite: true,或先调用 api.slothlet.api.remove() 清除路径。",
263
269
  "HINT_V2_CONFIG_UNSUPPORTED": "v2 中的此配置选项在 v3 中不受支持。请更新您的配置以使用 v3 的对应项,从而获得更好的控制权和清晰度。",
270
+ "HINT_V3_CONFIG_DEPRECATED": "此配置选项已重命名以提高清晰度。请更新代码以使用新选项名称,以确保与 v4 的向前兼容性。",
264
271
  "HINT_MODULE_LOAD_FAILED": "请检查模块文件是否存在错误。确保其具有有效的 JavaScript 语法和导出。",
265
272
  "HINT_MODULE_IMPORT_FAILED": "请确保模块文件存在且可导入。检查语法错误或缺失的依赖项。",
266
273
  "HINT_MODULE_NO_EXPORTS": "模块必须导出一个或多个函数或对象。",
267
274
  "HINT_INVALID_DIRECTORY": "请确保目录路径正确,且目录存在且可访问。",
268
275
  "HINT_WARN_DIRECTORY_EMPTY": "请检查目录路径是否正确。在模块动态加载的 add-api 工作流中,空目录是合法的。",
269
276
  "HINT_INVALID_CONFIG_LAZY_NOT_READY": "在延迟(lazy)模式下,请在调用函数前通过访问属性来触发实例化。",
270
- "HINT_INTERNAL_INVALID_STATE": "发生内部状态错误。这很可能是一个 bug — 请提供详细信息以便报告。",
271
- "HINT_INTERNAL_HOOK_STATE_CORRUPT": "HookManager 内部状态不一致。这在正常使用中不应发生 — 请提交 bug 报告,并说明您是如何调用 hook.on() 和 hook.remove() 的。",
277
+ "HINT_INTERNAL_INVALID_STATE": "发生内部状态错误。这很可能是一个缺陷 — 请提供详细信息以便报告。",
278
+ "HINT_INTERNAL_HOOK_STATE_CORRUPT": "HookManager 内部状态不一致。这在正常使用中不应发生 — 请提交缺陷报告,并说明您是如何调用 hook.on() 和 hook.remove() 的。",
272
279
  "HINT_INVALID_CONFIG_LAZY_ASYNC_REQUIRED": "延迟模式需要异步操作。请使用立即(eager)模式或实现适当的异步模式。",
273
280
  "HINT_RUNTIME_NO_ACTIVE_CONTEXT_SELF": "无法在 slothlet API 函数上下文之外访问 'self'。",
274
281
  "HINT_RUNTIME_NO_ACTIVE_CONTEXT_CONTEXT": "无法在 slothlet API 函数外部访问 'context'。上下文只能在 slothlet API 调用内部修改。",
@@ -294,7 +301,7 @@
294
301
  "HOOK_BRACE_EXPANSION_MAX_DEPTH": "钩子模式中的大括号扩展超过了最大嵌套深度 {maxDepth}。",
295
302
  "HINT_HOOK_BRACE_EXPANSION_MAX_DEPTH": "请简化钩子路径模式以减少大括号嵌套。最大允许深度为 {maxDepth} 层。",
296
303
  "SCOPE_DISABLED": "每请求上下文隔离已禁用。请在 slothlet 配置中设置 'scope: {}' 以启用该功能。",
297
- "HINT_SCOPE_DISABLED": "请在您的 slothlet 选项中添加 'scope: {}'(或一个 scope 配置对象),以通过 api.slothlet.run() and api.slothlet.scope() 启用每请求上下文隔离。",
304
+ "HINT_SCOPE_DISABLED": "请在您的 slothlet 选项中添加 'scope: {}'(或一个 scope 配置对象),以通过 api.slothlet.run() 和 api.slothlet.scope() 启用每请求上下文隔离。",
298
305
  "SCOPE_INVALID_CONTEXT": "上下文 data 必须是一个普通对象。接收到:{received}。",
299
306
  "HINT_SCOPE_INVALID_CONTEXT": "请将一个普通对象作为第一个参数传递给 run(),例如 api.slothlet.run({ userId: 1 }, callback)。",
300
307
  "SCOPE_INVALID_CALLBACK": "回调必须是一个函数。接收到:{received}。",
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{existsSync,readFileSync}from"fs";import{fileURLToPath}from"url";import{dirname,join}from"path";const translations_dirname=dirname(fileURLToPath(import.meta.url));const defaultTranslations=JSON.parse(readFileSync(join(translations_dirname,"languages","en-us.json"),"utf-8"));let currentTranslations=defaultTranslations.translations;let currentLanguage="en-us";const i18n_languageFallbacks={en:"en-us",es:"es-mx",de:"de-de",fr:"fr-fr",hi:"hi-in",ja:"ja-jp",ko:"ko-kr",pt:"pt-br",ru:"ru-ru",zh:"zh-cn"};function i18n_languageFileExists(lang){const langFilePath=join(translations_dirname,"languages",`${lang}.json`);return existsSync(langFilePath)}function i18n_normalizeEnvLanguage(envLang){const normalized=String(envLang).split(".")[0].split("@")[0].replace(/_/g,"-").toLowerCase();if(normalized==="c"||normalized==="posix")return"en-us";if(i18n_languageFileExists(normalized))return normalized;const base=normalized.split("-")[0];if(!base)return"en-us";if(i18n_languageFileExists(base))return base;return i18n_languageFallbacks[base]||base}function i18n_detectLanguage(){const envLang=process.env.LANG||process.env.LANGUAGE||process.env.LC_ALL;if(envLang){return i18n_normalizeEnvLanguage(envLang)}return"en-us"}function i18n_loadLanguageSync(lang){try{const langFilePath=join(translations_dirname,"languages",`${lang}.json`);const langData=JSON.parse(readFileSync(langFilePath,"utf-8"));return langData.translations}catch(___error){return null}}function setLanguage(lang){currentTranslations={...defaultTranslations.translations};if(lang!=="en-us"){const langTranslations=i18n_loadLanguageSync(lang);if(langTranslations){currentTranslations={...currentTranslations,...langTranslations};currentLanguage=lang}else{console.warn(`Failed to load language '${lang}', falling back to English.`);currentLanguage="en-us"}}else{currentLanguage="en-us"}}function getLanguage(){return currentLanguage}function translate(errorCode,params={}){let template=currentTranslations[errorCode];if(!template&&errorCode.startsWith("INVALID_CONFIG_")){template=currentTranslations.INVALID_CONFIG_generic}let message=template||`Error: ${errorCode}`;for(const[key,value]of Object.entries(params)){message=message.replace(new RegExp(`\\{${key}\\}`,"g"),value!==void 0?String(value):"")}message=message.replace(/\{\w+\}/g,"").replace(/\s+/g," ").trim();return message}function initI18n(options={}){try{if(options.language){setLanguage(options.language)}else{const detected=i18n_detectLanguage();setLanguage(detected)}}catch(___error){console.warn("i18n initialization failed, using English:",___error.message);currentLanguage="en-us";currentTranslations=defaultTranslations.translations}}initI18n();const t=translate;export{getLanguage,initI18n,setLanguage,t,translate};
17
+ import defaultTranslations from"./languages/en-us.json"with{type:"json"};import{isNode,fs,path,url,loadJson}from"@cldmv/slothlet/helpers/platform";const translations_dirname=isNode?path.dirname(url.fileURLToPath(import.meta.url)):null;let currentTranslations=defaultTranslations.translations;let currentLanguage="en-us";const i18n_languageFallbacks={en:"en-us",es:"es-mx",de:"de-de",fr:"fr-fr",hi:"hi-in",ja:"ja-jp",ko:"ko-kr",pt:"pt-br",ru:"ru-ru",zh:"zh-cn"};const KNOWN_LOCALES=new Set(["de-de","en-gb","en-us","es-es","es-mx","fr-fr","hi-in","ja-jp","ko-kr","pt-br","ru-ru","zh-cn"]);function i18n_localeRef(lang){return isNode?path.join(translations_dirname,"languages",`${lang}.json`):`@cldmv/slothlet/i18n/language/${lang}.json`}function i18n_languageFileExists(lang){if(!isNode)return KNOWN_LOCALES.has(lang);const langFilePath=path.join(translations_dirname,"languages",`${lang}.json`);return fs.existsSync(langFilePath)}function i18n_normalizeEnvLanguage(envLang){const normalized=String(envLang).split(".")[0].split("@")[0].replace(/_/g,"-").toLowerCase();if(normalized==="c"||normalized==="posix")return"en-us";if(i18n_languageFileExists(normalized))return normalized;const base=normalized.split("-")[0];if(!base)return"en-us";if(i18n_languageFileExists(base))return base;return i18n_languageFallbacks[base]||base}function i18n_detectLanguage(){if(!isNode){const navLang=typeof navigator!=="undefined"?navigator.languages?.[0]||navigator.language:null;return navLang?i18n_normalizeEnvLanguage(navLang):"en-us"}const envLang=process.env.LANG||process.env.LANGUAGE||process.env.LC_ALL;if(envLang){return i18n_normalizeEnvLanguage(envLang)}return"en-us"}function i18n_loadLanguageSync(lang){if(!isNode)return null;const langData=loadJson(i18n_localeRef(lang));return langData?langData.translations:null}function setLanguage(lang){currentTranslations={...defaultTranslations.translations};if(lang==="en-us"){currentLanguage="en-us";return}if(!isNode){void setLanguageAsync(lang);return}const langTranslations=i18n_loadLanguageSync(lang);if(langTranslations){currentTranslations={...currentTranslations,...langTranslations};currentLanguage=lang}else{console.warn(`Failed to load language '${lang}', falling back to English.`);currentLanguage="en-us"}}async function setLanguageAsync(lang){currentTranslations={...defaultTranslations.translations};if(lang==="en-us"){currentLanguage="en-us";return}currentLanguage="en-us";const langData=await loadJson(i18n_localeRef(lang));if(langData?.translations){currentTranslations={...currentTranslations,...langData.translations};currentLanguage=lang}else{console.warn(`Failed to load language '${lang}', falling back to English.`);currentLanguage="en-us"}}function getLanguage(){return currentLanguage}function translate(errorCode,params={}){let template=currentTranslations[errorCode];if(!template&&errorCode.startsWith("INVALID_CONFIG_")){template=currentTranslations.INVALID_CONFIG_generic}let message=template||`Error: ${errorCode}`;for(const[key,value]of Object.entries(params)){message=message.replace(new RegExp(`\\{${key}\\}`,"g"),value!==void 0?String(value):"")}message=message.replace(/\{\w+\}/g,"").replace(/\s+/g," ").trim();return message}function initI18n(options={}){try{const lang=options.language||i18n_detectLanguage();if(!isNode){currentLanguage="en-us";if(lang&&lang!=="en-us")void setLanguageAsync(lang);return}setLanguage(lang)}catch(___error){console.warn("i18n initialization failed, using English:",___error.message);currentLanguage="en-us";currentTranslations=defaultTranslations.translations}}initI18n();const t=translate;export{getLanguage,initI18n,setLanguage,setLanguageAsync,t,translate};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{readdir,stat}from"node:fs/promises";import{join,extname,basename,resolve}from"node:path";import{pathToFileURL}from"node:url";import{createRequire}from"node:module";import{ComponentBase}from"@cldmv/slothlet/factories/component-base";class Loader extends ComponentBase{static slothletProperty="loader";constructor(slothlet){super(slothlet)}async loadModule(filePath,instanceID,moduleID,cacheBust=null){try{if(filePath.endsWith(".cjs")){return this.#loadCJSIsolated(filePath)}const isTypeScript=filePath.endsWith(".ts")||filePath.endsWith(".mts");const typescriptConfig=this.slothlet.config?.typescript;let moduleUrl;if(isTypeScript&&typescriptConfig?.enabled){const mode=typescriptConfig.mode;if(mode==="strict"){if(!typescriptConfig.types?.output){throw new this.SlothletError("TS_STRICT_REQUIRES_OUTPUT",{},null,{validationError:true})}if(!typescriptConfig.types?.interfaceName){throw new this.SlothletError("TS_STRICT_REQUIRES_INTERFACE_NAME",{},null,{validationError:true})}if(!this.slothlet._typesGenerated){const{fork}=await import("child_process");const path=await import("path");const{fileURLToPath}=await import("url");const __dirname=path.dirname(fileURLToPath(import.meta.url));const scriptPath=path.resolve(__dirname,"../../../tools/build/generate-types-worker.mjs");const childConfig=JSON.stringify({dir:this.slothlet.config.root||this.slothlet.config.dir,mode:"eager",typescript:{enabled:true,mode:"fast"},types:typescriptConfig.types});await new Promise((resolve2,reject)=>{const child=fork(scriptPath,[],{stdio:["pipe","pipe","pipe","ipc"],env:{...process.env,SLOTHLET_CONFIG:childConfig}});let errorOutput="";child.stderr?.on("data",data=>{errorOutput+=data.toString()});child.on("message",msg=>{if(msg.type==="success"){this.slothlet._typesGenerated=true;resolve2()}else if(msg.type==="error"){reject(new this.SlothletError("TS_TYPE_GENERATION_FAILED",{},{message:msg.error}))}});child.on("error",error=>{reject(new this.SlothletError("TS_TYPE_GENERATION_FORK_FAILED",{},error))});child.on("exit",code=>{if(code!==0&&!this.slothlet._typesGenerated){reject(new this.SlothletError("TS_TYPE_GENERATION_PROCESS_EXITED",{code,output:errorOutput},null,{validationError:true}))}})})}const{transformTypeScriptStrict,writeTransformedToCache,formatDiagnostics}=await import("@cldmv/slothlet/processors/typescript");const strictTransform=async tsPath=>{const result=await transformTypeScriptStrict(tsPath,{target:typescriptConfig.target,module:typescriptConfig.module,strict:typescriptConfig.strict,typeDefinitionPath:typescriptConfig.types.output,compilerOptions:typescriptConfig.compilerOptions});if(result.diagnostics&&result.diagnostics.length>0){const ts=await import("typescript");const errors=formatDiagnostics(result.diagnostics,ts.default);const error=new this.SlothletError("TS_TYPE_CHECK_ERRORS",{filePath:tsPath,errors:errors.join("\n")},null,{validationError:true});error.diagnostics=result.diagnostics;throw error}return result.code};const entryCode=await strictTransform(filePath);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,entryCode,instanceID,moduleID,cacheBust,strictTransform)}else{const{transformTypeScript,writeTransformedToCache}=await import("@cldmv/slothlet/processors/typescript");const transformOptions={target:typescriptConfig.target,sourcemap:typescriptConfig.sourcemap};const transformedCode=await transformTypeScript(filePath,transformOptions);const transform=tsPath=>transformTypeScript(tsPath,transformOptions);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,transformedCode,instanceID,moduleID,cacheBust,transform)}}else{const fileUrl=pathToFileURL(filePath).href;moduleUrl=`${fileUrl}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}}const module=await import(moduleUrl);return module}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}#loadCJSIsolated(filePath){const requireFn=createRequire(filePath);const resolved=requireFn.resolve(filePath);delete requireFn.cache[resolved];const exports=requireFn(resolved);delete requireFn.cache[resolved];const namespace={default:exports};if(exports!==null&&typeof exports==="object"){for(const key of Object.keys(exports)){if(key!=="default"){namespace[key]=exports[key]}}}return namespace}async#buildTypescriptModuleUrl(writeTransformedToCache,filePath,code,instanceID,moduleID,cacheBust,transform){const{url,cacheDir}=await writeTransformedToCache(filePath,code,instanceID,transform);(this.slothlet._typescriptCacheDirs??=new Set).add(cacheDir);let moduleUrl=`${url}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}return moduleUrl}async scanDirectory(dir,options={}){const typescriptConfig=this.slothlet.config?.typescript;const defaultExtensions=[".mjs",".cjs",".js"];const typescriptExtensions=typescriptConfig?.enabled?[".ts",".mts"]:[];const allExtensions=[...defaultExtensions,...typescriptExtensions];const{recursive=true,extensions=allExtensions,isRootScan=true,currentDepth=0,maxDepth=Infinity,fileFilter=null}=options;try{await stat(dir)}catch(error){throw new this.SlothletError("INVALID_DIRECTORY",{dir},error)}const structure={files:[],directories:[]};const entries=await readdir(dir,{withFileTypes:true});for(const entry of entries){const fullPath=join(dir,entry.name);if(entry.isDirectory()){if(fileFilter){continue}if(recursive&&currentDepth<maxDepth){const subStructure=await this.scanDirectory(fullPath,{...options,isRootScan:false,currentDepth:currentDepth+1});structure.directories.push({path:fullPath,name:entry.name,children:subStructure})}}else if(entry.isFile()){const ext=extname(entry.name);if(extensions.includes(ext)){if(entry.name.startsWith("__")){continue}if(fileFilter&&!fileFilter(entry.name)){continue}const nameWithoutExt=basename(entry.name,ext);structure.files.push({path:fullPath,name:nameWithoutExt,fullName:entry.name,moduleID:this.slothlet.helpers.sanitize.getModuleId(fullPath,dir)})}}}if(isRootScan&&structure.files.length===0&&structure.directories.length===0){new this.SlothletWarning("WARN_DIRECTORY_EMPTY",{dir,resolvedPath:resolve(dir)})}return structure}extractExports(module){const exports={};if(module.default!==void 0){exports.default=module.default}for(const key of Object.keys(module)){if(key!=="default"&&key!=="module.exports"&&typeof key==="string"){exports[key]=module[key]}}if(exports.default&&typeof exports.default==="object"&&exports.default!==null&&"default"in exports.default){const rootNamedKeys=Object.keys(exports).filter(k=>k!=="default"&&k!=="module.exports");const defaultKeys=Object.keys(exports.default).filter(k=>k!=="default");const isCJSPattern=rootNamedKeys.every(k=>k in exports.default);if(isCJSPattern&&defaultKeys.length>0){exports.default=exports.default.default}}return exports}}export{Loader};
17
+ import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";class Loader extends ComponentBase{static slothletProperty="loader";constructor(slothlet){super(slothlet)}async loadModule(filePath,instanceID,moduleID,cacheBust=null){try{if(this.slothlet.envTarget==="browser"){return this.#loadModuleBrowser(filePath)}if(filePath.endsWith(".cjs")){return this.#loadCJSIsolated(filePath)}const isTypeScript=filePath.endsWith(".ts")||filePath.endsWith(".mts");const typescriptConfig=this.slothlet.config?.typescript;let moduleUrl;if(isTypeScript&&typescriptConfig?.enabled){const mode=typescriptConfig.mode;if(mode==="strict"){if(!typescriptConfig.types?.output){throw new this.SlothletError("TS_STRICT_REQUIRES_OUTPUT",{},null,{validationError:true})}if(!typescriptConfig.types?.interfaceName){throw new this.SlothletError("TS_STRICT_REQUIRES_INTERFACE_NAME",{},null,{validationError:true})}if(!this.slothlet._typesGenerated){const{fork}=await import("child_process");const path2=await import("path");const{fileURLToPath}=await import("url");const __dirname=path2.dirname(fileURLToPath(import.meta.url));const scriptPath=path2.resolve(__dirname,"../../../tools/build/generate-types-worker.mjs");const childConfig=JSON.stringify({dir:this.slothlet.config.root||this.slothlet.config.dir,mode:"eager",typescript:{enabled:true,mode:"fast"},types:typescriptConfig.types});await new Promise((resolve,reject)=>{const child=fork(scriptPath,[],{stdio:["pipe","pipe","pipe","ipc"],env:{...process.env,SLOTHLET_CONFIG:childConfig}});let errorOutput="";child.stderr?.on("data",data=>{errorOutput+=data.toString()});child.on("message",msg=>{if(msg.type==="success"){this.slothlet._typesGenerated=true;resolve()}else if(msg.type==="error"){reject(new this.SlothletError("TS_TYPE_GENERATION_FAILED",{},{message:msg.error}))}});child.on("error",error=>{reject(new this.SlothletError("TS_TYPE_GENERATION_FORK_FAILED",{},error))});child.on("exit",code=>{if(code!==0&&!this.slothlet._typesGenerated){reject(new this.SlothletError("TS_TYPE_GENERATION_PROCESS_EXITED",{code,output:errorOutput},null,{validationError:true}))}})})}const{transformTypeScriptStrict,writeTransformedToCache,formatDiagnostics}=await import("@cldmv/slothlet/processors/typescript");const strictTransform=async tsPath=>{const result=await transformTypeScriptStrict(tsPath,{target:typescriptConfig.target,module:typescriptConfig.module,strict:typescriptConfig.strict,typeDefinitionPath:typescriptConfig.types.output,compilerOptions:typescriptConfig.compilerOptions});if(result.diagnostics&&result.diagnostics.length>0){const ts=await import("typescript");const errors=formatDiagnostics(result.diagnostics,ts.default);const error=new this.SlothletError("TS_TYPE_CHECK_ERRORS",{filePath:tsPath,errors:errors.join("\n")},null,{validationError:true});error.diagnostics=result.diagnostics;throw error}return result.code};const entryCode=await strictTransform(filePath);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,entryCode,instanceID,moduleID,cacheBust,strictTransform)}else{const{transformTypeScript,writeTransformedToCache}=await import("@cldmv/slothlet/processors/typescript");const transformOptions={target:typescriptConfig.target,sourcemap:typescriptConfig.sourcemap};const transformedCode=await transformTypeScript(filePath,transformOptions);const transform=tsPath=>transformTypeScript(tsPath,transformOptions);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,transformedCode,instanceID,moduleID,cacheBust,transform)}}else{const fileUrl=url.pathToFileURL(filePath).href;moduleUrl=`${fileUrl}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}}const module=await import(moduleUrl);return module}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}#loadCJSIsolated(filePath){const requireFn=createRequire(filePath);const resolved=requireFn.resolve(filePath);delete requireFn.cache[resolved];const exports=requireFn(resolved);delete requireFn.cache[resolved];const namespace={default:exports};if(exports!==null&&typeof exports==="object"){for(const key of Object.keys(exports)){if(key!=="default"){namespace[key]=exports[key]}}}return namespace}async#buildTypescriptModuleUrl(writeTransformedToCache,filePath,code,instanceID,moduleID,cacheBust,transform){const{url:url2,cacheDir}=await writeTransformedToCache(filePath,code,instanceID,transform);(this.slothlet._typescriptCacheDirs??=new Set).add(cacheDir);let moduleUrl=`${url2}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}return moduleUrl}async scanDirectory(dir,options={}){if(this.slothlet.envTarget==="browser"){return this.#scanDirectoryBrowser(dir,options)}const typescriptConfig=this.slothlet.config?.typescript;const defaultExtensions=[".mjs",".cjs",".js"];const typescriptExtensions=typescriptConfig?.enabled?[".ts",".mts"]:[];const allExtensions=[...defaultExtensions,...typescriptExtensions];const{recursive=true,extensions=allExtensions,isRootScan=true,currentDepth=0,maxDepth=Infinity,fileFilter=null}=options;try{await fsp.stat(dir)}catch(error){throw new this.SlothletError("INVALID_DIRECTORY",{dir},error)}const structure={files:[],directories:[]};const entries=await fsp.readdir(dir,{withFileTypes:true});for(const entry of entries){const fullPath=path.join(dir,entry.name);if(entry.isDirectory()){if(fileFilter){continue}if(recursive&&currentDepth<maxDepth){const subStructure=await this.scanDirectory(fullPath,{...options,isRootScan:false,currentDepth:currentDepth+1});structure.directories.push({path:fullPath,name:entry.name,children:subStructure})}}else if(entry.isFile()){const ext=path.extname(entry.name);if(extensions.includes(ext)){if(entry.name.startsWith("__")){continue}if(fileFilter&&!fileFilter(entry.name)){continue}const nameWithoutExt=path.basename(entry.name,ext);structure.files.push({path:fullPath,name:nameWithoutExt,fullName:entry.name,moduleID:this.slothlet.helpers.sanitize.getModuleId(fullPath,dir)})}}}if(isRootScan&&structure.files.length===0&&structure.directories.length===0){new this.SlothletWarning("WARN_DIRECTORY_EMPTY",{dir,resolvedPath:path.resolve(dir)})}return structure}#scanDirectoryBrowser(dir,options={}){const manifest=this.slothlet.config?.manifest;if(!manifest){throw new this.SlothletError("INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST",{},null,{validationError:true})}const configBase=(this.slothlet.config?.dir||"").replace(/\/$/,"");let relativePath=(dir||"").replace(/\/$/,"");if(configBase&&relativePath.startsWith(configBase)){relativePath=relativePath.slice(configBase.length).replace(/^\/|\/$/g,"")}const isRoot=!relativePath||relativePath==="/"||relativePath===".";const node=isRoot?manifest:this.#findManifestNode(manifest,relativePath);if(!node){throw new this.SlothletError("INVALID_DIRECTORY",{dir},null)}return this.#manifestNodeToStructure(node,dir,options)}#findManifestNode(node,targetPath){const normalised=targetPath.replace(/\\/g,"/").replace(/^\/|\/$/g,"");for(const dir of node.directories||[]){const dirPath=(dir.path||dir.name||"").replace(/\\/g,"/").replace(/^\/|\/$/g,"");if(dirPath===normalised)return dir.children||dir;const found=this.#findManifestNode(dir.children||dir,normalised);if(found)return found}return null}#manifestNodeToStructure(node,rootPath,options={}){const{fileFilter=null}=options;const ALLOWED_EXTS=[".mjs",".cjs",".js"];const structure={files:[],directories:[]};for(const file of node.files||[]){const filePath=file.path||file.relativePath||"";const fullName=file.fullName||filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const ext=lastDot>=0?fullName.slice(lastDot):"";if(!ALLOWED_EXTS.includes(ext))continue;if(fullName.startsWith("__"))continue;if(fileFilter&&!fileFilter(fullName))continue;const name=file.name||(lastDot>=0?fullName.slice(0,lastDot):fullName);structure.files.push({path:filePath,name,fullName,moduleID:this.slothlet.helpers.sanitize.getModuleId(filePath,rootPath)})}if(!fileFilter){for(const dir of node.directories||[]){const dirPath=dir.path||dir.name||"";structure.directories.push({path:dirPath,name:dir.name||dirPath.split("/").pop(),children:this.#manifestNodeToStructure(dir.children||dir,dirPath,options)})}}return structure}async#loadModuleBrowser(filePath){const resolveModuleSpecifier=this.slothlet.config?.resolveModuleSpecifier??(({path:p})=>{const base=this.slothlet.config?.base??this.slothlet.config?.dir??"";const leadingSlash=base.startsWith("/")?"":"/";const baseUrl=/^[a-zA-Z][\w+\-.]*:\/\//.test(base)?base.endsWith("/")?base:base+"/":"file://"+leadingSlash+base.replace(/\/?$/,"/");return new URL(p,baseUrl).href});const fullName=filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const name=lastDot>=0?fullName.slice(0,lastDot):fullName;const specifier=resolveModuleSpecifier({path:filePath,name,fullName});const module=await import(specifier);return module}extractExports(module){const exports={};if(module.default!==void 0){exports.default=module.default}for(const key of Object.keys(module)){if(key!=="default"&&key!=="module.exports"&&typeof key==="string"){exports[key]=module[key]}}if(exports.default&&typeof exports.default==="object"&&exports.default!==null&&"default"in exports.default){const rootNamedKeys=Object.keys(exports).filter(k=>k!=="default"&&k!=="module.exports");const defaultKeys=Object.keys(exports.default).filter(k=>k!=="default");const isCJSPattern=rootNamedKeys.every(k=>k in exports.default);if(isCJSPattern&&defaultKeys.length>0){exports.default=exports.default.default}}return exports}}export{Loader};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import fs from"fs";import path from"path";import{SlothletError}from"@cldmv/slothlet/errors";let typescriptInstance=null;async function getTypeScript(){if(!typescriptInstance){try{typescriptInstance=await import("typescript")}catch(error){throw new SlothletError("TYPESCRIPT_NOT_INSTALLED",{feature:"type-generation"},error)}}return typescriptInstance}async function generateTypes(api,options){const ts=await getTypeScript();if(!options.output){throw new SlothletError("INVALID_CONFIG",{option:"types.output",expected:"a string output path",value:options.output,hint:"Provide a string output path for the generated .d.ts file, e.g. './types/api.d.ts'.",validationError:true})}if(!options.interfaceName){throw new SlothletError("INVALID_CONFIG",{option:"types.interfaceName",expected:"a string interface name",value:options.interfaceName,hint:"Provide a string interface name for the generated TypeScript interface, e.g. 'SlothletAPI'.",validationError:true})}const nodes=traverseAPI(api);for(const node of nodes){if(node.metadata?.filePath){node.typeInfo=await extractTypesFromFile(node.metadata.filePath,ts)}}const declaration=generateDeclaration(nodes,options);const outputPath=path.resolve(options.output);const outputDir=path.dirname(outputPath);if(!fs.existsSync(outputDir)){fs.mkdirSync(outputDir,{recursive:true})}fs.writeFileSync(outputPath,declaration,"utf8");return{output:declaration,filePath:outputPath}}function traverseAPI(api,currentPath=[],visited=new Set){const nodes=[];if(!api||typeof api!=="object"){return nodes}if(visited.has(api)){return nodes}visited.add(api);for(const[key,value]of Object.entries(api)){if(key.startsWith("_")||key.startsWith("__")||key==="slothlet"||key==="shutdown"||key==="destroy"){continue}const nodePath=[...currentPath,key];const metadata=value?.__metadata;if(typeof value==="function"){nodes.push({type:"function",path:nodePath,value,metadata})}else if(typeof value==="object"&&value!==null){nodes.push({type:"object",path:nodePath,value,metadata});const childNodes=traverseAPI(value,nodePath,visited);nodes.push(...childNodes)}}return nodes}async function extractTypesFromFile(filePath,ts){try{let visit=function(node){if(ts.isFunctionDeclaration(node)&&node.name){const hasExport=node.modifiers?.some(m=>m.kind===ts.SyntaxKind.ExportKeyword);if(hasExport){exports.push({name:node.name.text,type:"function",signature:extractFunctionSignature(node,source,ts)})}}if(ts.isVariableStatement(node)){const hasExport=node.modifiers?.some(m=>m.kind===ts.SyntaxKind.ExportKeyword);if(hasExport){for(const decl of node.declarationList.declarations){if(!decl.name||!ts.isIdentifier(decl.name))continue;const init=decl.initializer;if(!init)continue;if(ts.isArrowFunction(init)||ts.isFunctionExpression(init)){exports.push({name:decl.name.text,type:"function",signature:extractFunctionSignature(init,source,ts)})}}}}ts.forEachChild(node,visit)};const source=fs.readFileSync(filePath,"utf8");const sourceFile=ts.createSourceFile(filePath,source,ts.ScriptTarget.Latest,true);const exports=[];visit(sourceFile);return{exports,sourceFile}}catch(____error){return{exports:[]}}}function extractFunctionSignature(node,____source,____ts){const params=node.parameters.map(p=>{const name=p.name.getText(node.getSourceFile());const type=p.type?p.type.getText(node.getSourceFile()):"any";return`${name}: ${type}`}).join(", ");const returnType=node.type?node.type.getText(node.getSourceFile()):"any";return`(${params}): ${returnType}`}function generateDeclaration(nodes,options){const interfaceName=options.interfaceName;const lines=[];lines.push("/**");lines.push(` * Generated TypeScript declarations for Slothlet API`);lines.push(` * @generated ${new Date().toISOString()}`);lines.push(" */");lines.push("");const structure={};for(const node of nodes){if(node.type==="function"){const fnName=node.path[node.path.length-1];const exportInfo=node.typeInfo?.exports?.find(e=>e.name===fnName);const signature=exportInfo?.signature??"(...args: any[]): any";setNestedProperty(structure,node.path,{type:"function",signature})}}lines.push(`export interface ${interfaceName} {`);generateInterfaceContent(structure,lines,1);lines.push("}");lines.push("");lines.push(`declare const self: ${interfaceName};`);lines.push("");return lines.join("\n")}function setNestedProperty(obj,path2,value){let current=obj;for(let i=0;i<path2.length-1;i++){const key=path2[i];if(!current[key]){current[key]={}}current=current[key]}const lastKey=path2[path2.length-1];current[lastKey]=value}function generateInterfaceContent(structure,lines,indent){const indentation=" ".repeat(indent);for(const[key,value]of Object.entries(structure)){if(value.type==="function"){lines.push(`${indentation}${key}${value.signature};`)}else if(typeof value==="object"&&value!==null){lines.push(`${indentation}${key}: {`);generateInterfaceContent(value,lines,indent+1);lines.push(`${indentation}};`)}else{}}}export{generateTypes};
17
+ import fs from"fs";import path from"path";import{SlothletError}from"@cldmv/slothlet/errors";let typescriptInstance=null;async function getTypeScript(){if(!typescriptInstance){try{typescriptInstance=await import("typescript")}catch(error){throw new SlothletError("TYPESCRIPT_NOT_INSTALLED",{feature:"type-generation"},error)}}return typescriptInstance}async function generateTypes(api,options){const ts=await getTypeScript();if(!options.output){throw new SlothletError("INVALID_CONFIG",{option:"types.output",expected:"a string output path",value:options.output,hint:"Provide a string output path for the generated .d.ts file, e.g. './types/api.d.ts'.",validationError:true})}if(!options.interfaceName){throw new SlothletError("INVALID_CONFIG",{option:"types.interfaceName",expected:"a string interface name",value:options.interfaceName,hint:"Provide a string interface name for the generated TypeScript interface, e.g. 'SlothletAPI'.",validationError:true})}const nodes=traverseAPI(api);for(const node of nodes){if(node.metadata?.filePath){node.typeInfo=await extractTypesFromFile(node.metadata.filePath,ts)}}const declaration=generateDeclaration(nodes,options);const outputPath=path.resolve(options.output);const outputDir=path.dirname(outputPath);fs.mkdirSync(outputDir,{recursive:true});fs.writeFileSync(outputPath,declaration,"utf8");return{output:declaration,filePath:outputPath}}function traverseAPI(api,currentPath=[],visited=new Set){const nodes=[];if(!api||typeof api!=="object"){return nodes}if(visited.has(api)){return nodes}visited.add(api);for(const[key,value]of Object.entries(api)){if(key.startsWith("_")||key.startsWith("__")||key==="slothlet"||key==="shutdown"||key==="destroy"){continue}const nodePath=[...currentPath,key];const metadata=value?.__metadata;if(typeof value==="function"){nodes.push({type:"function",path:nodePath,value,metadata})}else if(typeof value==="object"&&value!==null){nodes.push({type:"object",path:nodePath,value,metadata});const childNodes=traverseAPI(value,nodePath,visited);nodes.push(...childNodes)}}return nodes}async function extractTypesFromFile(filePath,ts){try{let visit=function(node){if(ts.isFunctionDeclaration(node)&&node.name){const hasExport=node.modifiers?.some(m=>m.kind===ts.SyntaxKind.ExportKeyword);if(hasExport){exports.push({name:node.name.text,type:"function",signature:extractFunctionSignature(node,source,ts)})}}if(ts.isVariableStatement(node)){const hasExport=node.modifiers?.some(m=>m.kind===ts.SyntaxKind.ExportKeyword);if(hasExport){for(const decl of node.declarationList.declarations){if(!decl.name||!ts.isIdentifier(decl.name))continue;const init=decl.initializer;if(!init)continue;if(ts.isArrowFunction(init)||ts.isFunctionExpression(init)){exports.push({name:decl.name.text,type:"function",signature:extractFunctionSignature(init,source,ts)})}}}}ts.forEachChild(node,visit)};const source=fs.readFileSync(filePath,"utf8");const sourceFile=ts.createSourceFile(filePath,source,ts.ScriptTarget.Latest,true);const exports=[];visit(sourceFile);return{exports,sourceFile}}catch(____error){return{exports:[]}}}function extractFunctionSignature(node,____source,____ts){const params=node.parameters.map(p=>{const name=p.name.getText(node.getSourceFile());const type=p.type?p.type.getText(node.getSourceFile()):"any";return`${name}: ${type}`}).join(", ");const returnType=node.type?node.type.getText(node.getSourceFile()):"any";return`(${params}): ${returnType}`}function generateDeclaration(nodes,options){const interfaceName=options.interfaceName;const lines=[];lines.push("/**");lines.push(` * Generated TypeScript declarations for Slothlet API`);lines.push(` * @generated ${new Date().toISOString()}`);lines.push(" */");lines.push("");const structure={};for(const node of nodes){if(node.type==="function"){const fnName=node.path[node.path.length-1];const exportInfo=node.typeInfo?.exports?.find(e=>e.name===fnName);const signature=exportInfo?.signature??"(...args: any[]): any";setNestedProperty(structure,node.path,{type:"function",signature})}}lines.push(`export interface ${interfaceName} {`);generateInterfaceContent(structure,lines,1);lines.push("}");lines.push("");lines.push(`declare const self: ${interfaceName};`);lines.push("");return lines.join("\n")}function setNestedProperty(obj,path2,value){let current=obj;for(let i=0;i<path2.length-1;i++){const key=path2[i];if(!current[key]){current[key]={}}current=current[key]}const lastKey=path2[path2.length-1];current[lastKey]=value}function generateInterfaceContent(structure,lines,indent){const indentation=" ".repeat(indent);for(const[key,value]of Object.entries(structure)){if(value.type==="function"){lines.push(`${indentation}${key}${value.signature};`)}else if(typeof value==="object"&&value!==null){lines.push(`${indentation}${key}: {`);generateInterfaceContent(value,lines,indent+1);lines.push(`${indentation}};`)}else{}}}export{generateTypes};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import fs from"fs";import path from"path";import{writeFile,mkdir,readdir,rm}from"node:fs/promises";import{createHash}from"node:crypto";import{tmpdir}from"node:os";import{pathToFileURL}from"node:url";import{SlothletError}from"@cldmv/slothlet/errors";let esbuildInstance=null;let typescriptInstance=null;async function getEsbuild(){if(!esbuildInstance){try{esbuildInstance=await import("esbuild")}catch(error){throw new SlothletError("TYPESCRIPT_ESBUILD_NOT_INSTALLED",{mode:"fast"},error)}}return esbuildInstance}async function getTypeScript(){if(!typescriptInstance){try{typescriptInstance=await import("typescript")}catch(error){throw new SlothletError("TYPESCRIPT_TSC_NOT_INSTALLED",{mode:"strict"},error)}}return typescriptInstance}async function transformTypeScript(filePath,options={}){const esbuild=await getEsbuild();const code=fs.readFileSync(filePath,"utf8");const result=await esbuild.transform(code,{loader:"ts",format:options.format||"esm",target:options.target||"es2020",sourcemap:options.sourcemap||false,...options});return result.code}function createDataUrl(code){const encoded=encodeURIComponent(code);const timestamp=Date.now();return`data:text/javascript;charset=utf-8,${encoded}#t=${timestamp}`}function findPackageRoot(startPath){let dir;try{const resolved=path.resolve(startPath);dir=fs.statSync(resolved).isFile()?path.dirname(resolved):resolved}catch{return null}while(true){if(fs.existsSync(path.join(dir,"package.json")))return dir;const parent=path.dirname(dir);if(parent===dir)return null;dir=parent}}function isProcessAlive(pid){try{process.kill(pid,0);return true}catch(err){return err.code!=="ESRCH"}}const sweepPromises=new Map;function sweepStaleSlothletCache(projectRoot){if(sweepPromises.has(projectRoot))return sweepPromises.get(projectRoot);const promise=(async()=>{const cacheRoot=path.join(projectRoot,".slothlet-cache");let entries;try{entries=await readdir(cacheRoot,{withFileTypes:true})}catch{return}const currentPid=process.pid;await Promise.allSettled(entries.map(async entry=>{if(!entry.isDirectory())return;const match=entry.name.match(/^(\d+)-/);if(!match)return;const pid=Number(match[1]);if(pid===currentPid||isProcessAlive(pid))return;await rm(path.join(cacheRoot,entry.name),{recursive:true,force:true})}))})();sweepPromises.set(projectRoot,promise);return promise}const TS_SOURCE_EXT=/\.m?ts$/;function resolveModuleFile(absoluteTarget){const isFile=candidate=>{try{return fs.statSync(candidate).isFile()}catch{return false}};if(isFile(absoluteTarget)){return{path:absoluteTarget,isTS:TS_SOURCE_EXT.test(absoluteTarget)}}const ext=path.extname(absoluteTarget);const base=ext?absoluteTarget.slice(0,-ext.length):absoluteTarget;const candidates=ext===".mjs"?[".mts"]:ext===".js"?[".ts"]:ext===""?[".mts",".ts"]:[];for(const candidate of candidates){if(isFile(base+candidate)){return{path:base+candidate,isTS:true}}}return{path:absoluteTarget,isTS:false}}const REGEX_PRECEDING_KEYWORDS=new Set(["return","typeof","instanceof","in","of","new","delete","void","throw","else","yield","await","case","do"]);function isIdentifierChar(ch){const cc=ch.charCodeAt(0);return cc>=97&&cc<=122||cc>=65&&cc<=90||cc>=48&&cc<=57||ch==="_"||ch==="$"||cc>127}function maskStringsAndComments(code){const n=code.length;const mask=new Uint8Array(n);let regexAllowed=true;let i=0;while(i<n){const c=code[i];const c2=code[i+1];if(c==="/"&&c2==="/"){while(i<n&&code[i]!=="\n")mask[i++]=1}else if(c==="/"&&c2==="*"){mask[i++]=1;mask[i++]=1;while(i<n&&!(code[i]==="*"&&code[i+1]==="/"))mask[i++]=1;if(i<n){mask[i++]=1;mask[i++]=1}}else if(c==="/"&&regexAllowed){mask[i++]=1;let inClass=false;while(i<n&&code[i]!=="\n"){const ch=code[i];if(ch==="\\"&&i+1<n){mask[i++]=1;mask[i++]=1;continue}if(ch==="[")inClass=true;else if(ch==="]")inClass=false;mask[i++]=1;if(ch==="/"&&!inClass)break}regexAllowed=false}else if(c==='"'||c==="'"||c==="`"){mask[i++]=1;while(i<n&&code[i]!==c){if(code[i]==="\\"&&i+1<n)mask[i++]=1;mask[i++]=1}if(i<n)mask[i++]=1;regexAllowed=false}else if(c===" "||c===" "||c==="\n"||c==="\r"||c==="\f"||c==="\v"){i++}else if(isIdentifierChar(c)&&!(c>="0"&&c<="9")){const start=i;while(i<n&&isIdentifierChar(code[i]))i++;regexAllowed=REGEX_PRECEDING_KEYWORDS.has(code.slice(start,i))}else{regexAllowed=c!==")"&&c!=="]"&&!(c>="0"&&c<="9");i++}}return mask}const TOKEN_GAP="(?:\\s|/\\*[\\s\\S]*?\\*/|//[^\\n]*)*";const STATIC_FROM_RE=new RegExp(`^([ \\t]*(?:import|export)\\b[^"'\`;]*?\\bfrom${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2`,"gm");const BARE_IMPORT_RE=new RegExp(`^([ \\t]*import${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2`,"gm");const DYNAMIC_IMPORT_RE=new RegExp(`(?<![.\\w$])import(${TOKEN_GAP}\\(${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2(${TOKEN_GAP}\\))`,"g");function rewriteRelativeSpecifiers(code,sourcePath,resolve){const sourceDir=path.dirname(sourcePath);const resolveSpecifier=resolve??((absoluteTarget,suffix)=>pathToFileURL(absoluteTarget).href+suffix);const handle=specifier=>{const suffixIdx=specifier.search(/[?#]/);const pathPart=suffixIdx===-1?specifier:specifier.slice(0,suffixIdx);const suffix=suffixIdx===-1?"":specifier.slice(suffixIdx);return resolveSpecifier(path.resolve(sourceDir,pathPart),suffix,specifier)};const rewriteWith=(text,regex,replacer)=>{const mask=maskStringsAndComments(text);return text.replace(regex,(...args)=>{const offset=args[args.length-2];return mask[offset]?args[0]:replacer(...args)})};let out=code;out=rewriteWith(out,STATIC_FROM_RE,(_m,pre,q,spec)=>`${pre}${q}${handle(spec)}${q}`);out=rewriteWith(out,BARE_IMPORT_RE,(_m,pre,q,spec)=>`${pre}${q}${handle(spec)}${q}`);out=rewriteWith(out,DYNAMIC_IMPORT_RE,(_m,pre,q,spec,post)=>`import${pre}${q}${handle(spec)}${q}${post}`);return out}async function writeTransformedToCache(originalPath,code,instanceID,transform){const absolutePath=path.resolve(originalPath);const projectRoot=findPackageRoot(absolutePath)??tmpdir();await sweepStaleSlothletCache(projectRoot);const cacheDir=path.join(projectRoot,".slothlet-cache",`${process.pid}-${instanceID}`);const transformed=new Map([[absolutePath,code]]);const deps=new Map;if(transform){const pending=[absolutePath];while(pending.length>0){const file=pending.pop();const fileDeps=new Set;deps.set(file,fileDeps);rewriteRelativeSpecifiers(transformed.get(file),file,(absoluteTarget,_suffix,specifier)=>{const resolved=resolveModuleFile(absoluteTarget);if(resolved.isTS)fileDeps.add(resolved.path);return specifier});for(const dep of fileDeps){if(!transformed.has(dep)){transformed.set(dep,await transform(dep));pending.push(dep)}}}}const closureCache=new Map;const closureOf=file=>{if(closureCache.has(file))return closureCache.get(file);const seen=new Set;const walk=node=>{if(seen.has(node))return;seen.add(node);for(const next of deps.get(node)??[])walk(next)};walk(file);closureCache.set(file,seen);return seen};const cacheName=file=>{const hash=createHash("sha256");for(const member of[...closureOf(file)].sort()){hash.update(member).update("\0").update(transformed.get(member)).update("\0")}hash.update("entry\0").update(file);return`${hash.digest("hex").slice(0,16)}.mjs`};await mkdir(cacheDir,{recursive:true});for(const[file,fileCode]of transformed){const rewritten=rewriteRelativeSpecifiers(fileCode,file,(absoluteTarget,suffix)=>{const resolved=resolveModuleFile(absoluteTarget);if(resolved.isTS&&transformed.has(resolved.path)){return pathToFileURL(path.join(cacheDir,cacheName(resolved.path))).href+suffix}return pathToFileURL(resolved.path).href+suffix});const cachePath=path.join(cacheDir,cacheName(file));if(!fs.existsSync(cachePath)){await writeFile(cachePath,rewritten,"utf8")}}return{url:pathToFileURL(path.join(cacheDir,cacheName(absolutePath))).href,cacheDir}}async function transformTypeScriptStrict(filePath,options={}){const ts=await getTypeScript();const code=fs.readFileSync(filePath,"utf8");const targetMap={es3:ts.ScriptTarget.ES3,es5:ts.ScriptTarget.ES5,es6:ts.ScriptTarget.ES2015,es2015:ts.ScriptTarget.ES2015,es2016:ts.ScriptTarget.ES2016,es2017:ts.ScriptTarget.ES2017,es2018:ts.ScriptTarget.ES2018,es2019:ts.ScriptTarget.ES2019,es2020:ts.ScriptTarget.ES2020,es2021:ts.ScriptTarget.ES2021,es2022:ts.ScriptTarget.ES2022,esnext:ts.ScriptTarget.ESNext,latest:ts.ScriptTarget.Latest};const moduleMap={none:ts.ModuleKind.None,commonjs:ts.ModuleKind.CommonJS,amd:ts.ModuleKind.AMD,system:ts.ModuleKind.System,umd:ts.ModuleKind.UMD,es6:ts.ModuleKind.ES2015,es2015:ts.ModuleKind.ES2015,es2020:ts.ModuleKind.ES2020,es2022:ts.ModuleKind.ES2022,esnext:ts.ModuleKind.ESNext,node16:ts.ModuleKind.Node16,nodenext:ts.ModuleKind.NodeNext};const targetKey=(options.target||"es2020").toLowerCase();const moduleKey=(options.module||"esnext").toLowerCase();const compilerOptions={target:targetMap[targetKey]||ts.ScriptTarget.ES2020,module:moduleMap[moduleKey]||ts.ModuleKind.ESNext,strict:options.strict!==false,esModuleInterop:true,skipLibCheck:true,noEmit:false,...options.typeDefinitionPath&&{typeRoots:[path.dirname(options.typeDefinitionPath)],types:[path.basename(options.typeDefinitionPath,".d.ts")]},...options.compilerOptions};let diagnostics=[];if(!options.skipTypeCheck){const host=ts.createCompilerHost(compilerOptions);const originalReadFile=host.readFile;host.readFile=fileName=>{if(fileName===filePath){return code}return originalReadFile.call(host,fileName)};const program=ts.createProgram([filePath],compilerOptions,host);const allDiagnostics=[...program.getSemanticDiagnostics(),...program.getSyntacticDiagnostics()];diagnostics=allDiagnostics.filter(d=>d.file&&d.file.fileName===filePath)}const result=ts.transpileModule(code,{compilerOptions,fileName:filePath});return{code:result.outputText,diagnostics}}function formatDiagnostics(diagnostics,ts){return diagnostics.map(diagnostic=>{const message=ts.flattenDiagnosticMessageText(diagnostic.messageText,"\n");if(diagnostic.file&&diagnostic.start!==void 0){const{line,character}=diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);const fileName=diagnostic.file.fileName;return`${fileName}:${line+1}:${character+1} - ${message}`}return message})}export{createDataUrl,formatDiagnostics,maskStringsAndComments,resolveModuleFile,rewriteRelativeSpecifiers,transformTypeScript,transformTypeScriptStrict,writeTransformedToCache};
17
+ import fs from"fs";import path from"path";import{writeFile,mkdir,readdir,rm}from"node:fs/promises";import{createHash}from"node:crypto";import{tmpdir}from"node:os";import{pathToFileURL}from"node:url";import{SlothletError}from"@cldmv/slothlet/errors";let esbuildInstance=null;let typescriptInstance=null;async function getEsbuild(){if(!esbuildInstance){try{esbuildInstance=await import("esbuild")}catch(error){throw new SlothletError("TYPESCRIPT_ESBUILD_NOT_INSTALLED",{mode:"fast"},error)}}return esbuildInstance}async function getTypeScript(){if(!typescriptInstance){try{typescriptInstance=await import("typescript")}catch(error){throw new SlothletError("TYPESCRIPT_TSC_NOT_INSTALLED",{mode:"strict"},error)}}return typescriptInstance}async function transformTypeScript(filePath,options={}){const esbuild=await getEsbuild();const code=fs.readFileSync(filePath,"utf8");const result=await esbuild.transform(code,{loader:"ts",format:options.format||"esm",target:options.target||"es2020",sourcemap:options.sourcemap||false,...options});return result.code}function createDataUrl(code){const encoded=encodeURIComponent(code);const timestamp=Date.now();return`data:text/javascript;charset=utf-8,${encoded}#t=${timestamp}`}function findPackageRoot(startPath){let dir;try{const resolved=path.resolve(startPath);dir=fs.statSync(resolved).isFile()?path.dirname(resolved):resolved}catch{return null}while(true){if(fs.existsSync(path.join(dir,"package.json")))return dir;const parent=path.dirname(dir);if(parent===dir)return null;dir=parent}}function isProcessAlive(pid){try{process.kill(pid,0);return true}catch(err){return err.code!=="ESRCH"}}const sweepPromises=new Map;let secureFallbackRoot;function getSecureFallbackRoot(){return secureFallbackRoot??=fs.mkdtempSync(path.join(tmpdir(),"slothlet-"))}function sweepStaleSlothletCache(projectRoot){if(sweepPromises.has(projectRoot))return sweepPromises.get(projectRoot);const promise=(async()=>{const cacheRoot=path.join(projectRoot,".slothlet-cache");let entries;try{entries=await readdir(cacheRoot,{withFileTypes:true})}catch{return}const currentPid=process.pid;await Promise.allSettled(entries.map(async entry=>{if(!entry.isDirectory())return;const match=entry.name.match(/^(\d+)-/);if(!match)return;const pid=Number(match[1]);if(pid===currentPid||isProcessAlive(pid))return;await rm(path.join(cacheRoot,entry.name),{recursive:true,force:true})}))})();sweepPromises.set(projectRoot,promise);return promise}const TS_SOURCE_EXT=/\.m?ts$/;function resolveModuleFile(absoluteTarget){const isFile=candidate=>{try{return fs.statSync(candidate).isFile()}catch{return false}};if(isFile(absoluteTarget)){return{path:absoluteTarget,isTS:TS_SOURCE_EXT.test(absoluteTarget)}}const ext=path.extname(absoluteTarget);const base=ext?absoluteTarget.slice(0,-ext.length):absoluteTarget;const candidates=ext===".mjs"?[".mts"]:ext===".js"?[".ts"]:ext===""?[".mts",".ts"]:[];for(const candidate of candidates){if(isFile(base+candidate)){return{path:base+candidate,isTS:true}}}return{path:absoluteTarget,isTS:false}}const REGEX_PRECEDING_KEYWORDS=new Set(["return","typeof","instanceof","in","of","new","delete","void","throw","else","yield","await","case","do"]);function isIdentifierChar(ch){const cc=ch.charCodeAt(0);return cc>=97&&cc<=122||cc>=65&&cc<=90||cc>=48&&cc<=57||ch==="_"||ch==="$"||cc>127}function maskStringsAndComments(code){const n=code.length;const mask=new Uint8Array(n);let regexAllowed=true;let i=0;while(i<n){const c=code[i];const c2=code[i+1];if(c==="/"&&c2==="/"){while(i<n&&code[i]!=="\n")mask[i++]=1}else if(c==="/"&&c2==="*"){mask[i++]=1;mask[i++]=1;while(i<n&&!(code[i]==="*"&&code[i+1]==="/"))mask[i++]=1;if(i<n){mask[i++]=1;mask[i++]=1}}else if(c==="/"&&regexAllowed){mask[i++]=1;let inClass=false;while(i<n&&code[i]!=="\n"){const ch=code[i];if(ch==="\\"&&i+1<n){mask[i++]=1;mask[i++]=1;continue}if(ch==="[")inClass=true;else if(ch==="]")inClass=false;mask[i++]=1;if(ch==="/"&&!inClass)break}regexAllowed=false}else if(c==='"'||c==="'"||c==="`"){mask[i++]=1;while(i<n&&code[i]!==c){if(code[i]==="\\"&&i+1<n)mask[i++]=1;mask[i++]=1}if(i<n)mask[i++]=1;regexAllowed=false}else if(c===" "||c===" "||c==="\n"||c==="\r"||c==="\f"||c==="\v"){i++}else if(isIdentifierChar(c)&&!(c>="0"&&c<="9")){const start=i;while(i<n&&isIdentifierChar(code[i]))i++;regexAllowed=REGEX_PRECEDING_KEYWORDS.has(code.slice(start,i))}else{regexAllowed=c!==")"&&c!=="]"&&!(c>="0"&&c<="9");i++}}return mask}const TOKEN_GAP="(?:\\s|/\\*[\\s\\S]*?\\*/|//[^\\n]*)*";const STATIC_FROM_RE=new RegExp(`^([ \\t]*(?:import|export)\\b[^"'\`;]*?\\bfrom${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2`,"gm");const BARE_IMPORT_RE=new RegExp(`^([ \\t]*import${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2`,"gm");const DYNAMIC_IMPORT_RE=new RegExp(`(?<![.\\w$])import(${TOKEN_GAP}\\(${TOKEN_GAP})(["'])(\\.\\.?/[^"']*)\\2(${TOKEN_GAP}\\))`,"g");function rewriteRelativeSpecifiers(code,sourcePath,resolve){const sourceDir=path.dirname(sourcePath);const resolveSpecifier=resolve??((absoluteTarget,suffix)=>pathToFileURL(absoluteTarget).href+suffix);const handle=specifier=>{const suffixIdx=specifier.search(/[?#]/);const pathPart=suffixIdx===-1?specifier:specifier.slice(0,suffixIdx);const suffix=suffixIdx===-1?"":specifier.slice(suffixIdx);return resolveSpecifier(path.resolve(sourceDir,pathPart),suffix,specifier)};const rewriteWith=(text,regex,replacer)=>{const mask=maskStringsAndComments(text);return text.replace(regex,(...args)=>{const offset=args[args.length-2];return mask[offset]?args[0]:replacer(...args)})};let out=code;out=rewriteWith(out,STATIC_FROM_RE,(_m,pre,q,spec)=>`${pre}${q}${handle(spec)}${q}`);out=rewriteWith(out,BARE_IMPORT_RE,(_m,pre,q,spec)=>`${pre}${q}${handle(spec)}${q}`);out=rewriteWith(out,DYNAMIC_IMPORT_RE,(_m,pre,q,spec,post)=>`import${pre}${q}${handle(spec)}${q}${post}`);return out}async function writeTransformedToCache(originalPath,code,instanceID,transform){const absolutePath=path.resolve(originalPath);const projectRoot=findPackageRoot(absolutePath)??getSecureFallbackRoot();await sweepStaleSlothletCache(projectRoot);const cacheDir=path.join(projectRoot,".slothlet-cache",`${process.pid}-${instanceID}`);const transformed=new Map([[absolutePath,code]]);const deps=new Map;if(transform){const pending=[absolutePath];while(pending.length>0){const file=pending.pop();const fileDeps=new Set;deps.set(file,fileDeps);rewriteRelativeSpecifiers(transformed.get(file),file,(absoluteTarget,_suffix,specifier)=>{const resolved=resolveModuleFile(absoluteTarget);if(resolved.isTS)fileDeps.add(resolved.path);return specifier});for(const dep of fileDeps){if(!transformed.has(dep)){transformed.set(dep,await transform(dep));pending.push(dep)}}}}const closureCache=new Map;const closureOf=file=>{if(closureCache.has(file))return closureCache.get(file);const seen=new Set;const walk=node=>{if(seen.has(node))return;seen.add(node);for(const next of deps.get(node)??[])walk(next)};walk(file);closureCache.set(file,seen);return seen};const cacheName=file=>{const hash=createHash("sha256");for(const member of[...closureOf(file)].sort()){hash.update(member).update("\0").update(transformed.get(member)).update("\0")}hash.update("entry\0").update(file);return`${hash.digest("hex").slice(0,16)}.mjs`};await mkdir(cacheDir,{recursive:true});for(const[file,fileCode]of transformed){const rewritten=rewriteRelativeSpecifiers(fileCode,file,(absoluteTarget,suffix)=>{const resolved=resolveModuleFile(absoluteTarget);if(resolved.isTS&&transformed.has(resolved.path)){return pathToFileURL(path.join(cacheDir,cacheName(resolved.path))).href+suffix}return pathToFileURL(resolved.path).href+suffix});const cachePath=path.join(cacheDir,cacheName(file));try{await writeFile(cachePath,rewritten,{encoding:"utf8",flag:"wx"})}catch(err){if(err.code!=="EEXIST")throw err}}return{url:pathToFileURL(path.join(cacheDir,cacheName(absolutePath))).href,cacheDir}}async function transformTypeScriptStrict(filePath,options={}){const ts=await getTypeScript();const code=fs.readFileSync(filePath,"utf8");const targetMap={es3:ts.ScriptTarget.ES3,es5:ts.ScriptTarget.ES5,es6:ts.ScriptTarget.ES2015,es2015:ts.ScriptTarget.ES2015,es2016:ts.ScriptTarget.ES2016,es2017:ts.ScriptTarget.ES2017,es2018:ts.ScriptTarget.ES2018,es2019:ts.ScriptTarget.ES2019,es2020:ts.ScriptTarget.ES2020,es2021:ts.ScriptTarget.ES2021,es2022:ts.ScriptTarget.ES2022,esnext:ts.ScriptTarget.ESNext,latest:ts.ScriptTarget.Latest};const moduleMap={none:ts.ModuleKind.None,commonjs:ts.ModuleKind.CommonJS,amd:ts.ModuleKind.AMD,system:ts.ModuleKind.System,umd:ts.ModuleKind.UMD,es6:ts.ModuleKind.ES2015,es2015:ts.ModuleKind.ES2015,es2020:ts.ModuleKind.ES2020,es2022:ts.ModuleKind.ES2022,esnext:ts.ModuleKind.ESNext,node16:ts.ModuleKind.Node16,nodenext:ts.ModuleKind.NodeNext};const targetKey=(options.target||"es2020").toLowerCase();const moduleKey=(options.module||"esnext").toLowerCase();const compilerOptions={target:targetMap[targetKey]||ts.ScriptTarget.ES2020,module:moduleMap[moduleKey]||ts.ModuleKind.ESNext,strict:options.strict!==false,esModuleInterop:true,skipLibCheck:true,noEmit:false,...options.typeDefinitionPath&&{typeRoots:[path.dirname(options.typeDefinitionPath)],types:[path.basename(options.typeDefinitionPath,".d.ts")]},...options.compilerOptions};let diagnostics=[];if(!options.skipTypeCheck){const host=ts.createCompilerHost(compilerOptions);const originalReadFile=host.readFile;host.readFile=fileName=>{if(fileName===filePath){return code}return originalReadFile.call(host,fileName)};const program=ts.createProgram([filePath],compilerOptions,host);const allDiagnostics=[...program.getSemanticDiagnostics(),...program.getSyntacticDiagnostics()];diagnostics=allDiagnostics.filter(d=>d.file&&d.file.fileName===filePath)}const result=ts.transpileModule(code,{compilerOptions,fileName:filePath});return{code:result.outputText,diagnostics}}function formatDiagnostics(diagnostics,ts){return diagnostics.map(diagnostic=>{const message=ts.flattenDiagnosticMessageText(diagnostic.messageText,"\n");if(diagnostic.file&&diagnostic.start!==void 0){const{line,character}=diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);const fileName=diagnostic.file.fileName;return`${fileName}:${line+1}:${character+1} - ${message}`}return message})}export{createDataUrl,formatDiagnostics,maskStringsAndComments,resolveModuleFile,rewriteRelativeSpecifiers,transformTypeScript,transformTypeScriptStrict,writeTransformedToCache};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import path from"node:path";import slothlet from"@cldmv/slothlet";import{generateTypes as writeDeclarationFile}from"@cldmv/slothlet/processors/type-generator";import{SlothletError}from"@cldmv/slothlet/errors";async function generateTypes(options={}){if(options===null||typeof options!=="object"||Array.isArray(options)){throw new SlothletError("INVALID_CONFIG",{option:"typegen.options",expected:"plain object",value:options,hint:"Call generateTypes({ dir, output, interfaceName }) with an options object.",validationError:true},null,{validationError:true})}assertOption(options.dir,"dir");assertOption(options.output,"output");assertOption(options.interfaceName,"interfaceName");const api=await slothlet({dir:path.resolve(options.dir),mode:"eager",typescript:options.typescript??{mode:"fast"}});try{const result=await writeDeclarationFile(api,{output:path.resolve(options.output),interfaceName:options.interfaceName,includeDocumentation:options.includeDocumentation??true});return{filePath:result.filePath,content:result.output}}finally{await api.slothlet.shutdown()}}function assertOption(value,name){if(typeof value!=="string"||value.length===0){throw new SlothletError("INVALID_CONFIG",{option:`typegen.${name}`,expected:"non-empty string",value,hint:`Provide options.${name} as a non-empty string when calling generateTypes(). For the CLI, use --${camelToKebab(name)} or the matching positional argument.`,validationError:true},null,{validationError:true})}}function camelToKebab(s){return s.replace(/[A-Z]/g,m=>`-${m.toLowerCase()}`)}export{generateTypes};
17
+ import path from"node:path";import slothlet from"@cldmv/slothlet";import{generateTypes as writeDeclarationFile}from"@cldmv/slothlet/processors/type-generator";import{SlothletError}from"@cldmv/slothlet/errors";async function generateTypes(options={}){if(options===null||typeof options!=="object"||Array.isArray(options)){throw new SlothletError("INVALID_CONFIG",{option:"typegen.options",expected:"plain object",value:options,hint:"Call generateTypes({ dir, output, interfaceName }) with an options object.",validationError:true},null,{validationError:true})}assertOption(options.dir,"dir");assertOption(options.output,"output");assertOption(options.interfaceName,"interfaceName");const api=await slothlet({base:path.resolve(options.dir),mode:"eager",typescript:options.typescript??{mode:"fast"}});try{const result=await writeDeclarationFile(api,{output:path.resolve(options.output),interfaceName:options.interfaceName,includeDocumentation:options.includeDocumentation??true});return{filePath:result.filePath,content:result.output}}finally{await api.slothlet.shutdown()}}function assertOption(value,name){if(typeof value!=="string"||value.length===0){throw new SlothletError("INVALID_CONFIG",{option:`typegen.${name}`,expected:"non-empty string",value,hint:`Provide options.${name} as a non-empty string when calling generateTypes(). For the CLI, use --${camelToKebab(name)} or the matching positional argument.`,validationError:true},null,{validationError:true})}}function camelToKebab(s){return s.replace(/[A-Z]/g,m=>`-${m.toLowerCase()}`)}export{generateTypes};
package/dist/slothlet.mjs CHANGED
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{readdirSync}from"node:fs";import{dirname,join}from"node:path";import{fileURLToPath,pathToFileURL}from"node:url";import{getContextManager}from"@cldmv/slothlet/factories/context";import{SlothletError,SlothletWarning,SlothletDebug}from"@cldmv/slothlet/errors";import{registerInstance}from"@cldmv/slothlet/handlers/lifecycle-token";import{resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";import{initI18n}from"@cldmv/slothlet/i18n";import{enableEventEmitterPatching,disableEventEmitterPatching,cleanupEventEmitterResources,setApiContextChecker}from"@cldmv/slothlet/helpers/eventemitter-context";class Slothlet{static RESERVED_ROOT_KEYS=["slothlet","shutdown","destroy"];static SKIP_PROPS=["__metadata","__type","_materialize","_impl","____slothletInternal"];constructor(){this.SlothletError=SlothletError;this.SlothletWarning=SlothletWarning;this.debugLogger=null;this.instanceID=null;this.config=null;this.api=null;this.boundApi=null;this.contextManager=null;this.isLoaded=false;this.reference=null;this.context=null;this.envSnapshot=null;this._totalLazyCount=0;this._unmaterializedLazyCount=0;this._materializationComplete=false;this._materializationWaiters=[];this._materializationCompleteEmitted=false;this.componentCategories=["helpers","handlers","builders","processors","modes"];for(const category of this.componentCategories){this[category]={}}}async _initializeComponents(){const baseDir=join(dirname(fileURLToPath(import.meta.url)),"lib");for(const category of this.componentCategories){const categoryDir=join(baseDir,category);const files=readdirSync(categoryDir).filter(f=>f.endsWith(".mjs"));for(const file of files){const filePath=join(categoryDir,file);try{const module=await import(pathToFileURL(filePath).href);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this);if(this.config?.debug?.initialization){this.debug("initialization",{key:"DEBUG_MODE_COMPONENT_INITIALIZED",component:ClassExport.name,category,propertyName:propName})}}}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}}}_setupLifecycleSubscribers(){if(!this.handlers.lifecycle){return}if(this.handlers.metadata){this.handlers.lifecycle.subscribe("impl:created",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:changed",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:removed",data=>{if(data.apiPath){const rootSegment=data.apiPath.split(".")[0];this.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}})}if(this.handlers.ownership){this.handlers.lifecycle.subscribe("impl:created",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})});this.handlers.lifecycle.subscribe("impl:changed",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;const currentOwner=this.handlers.ownership.getCurrentOwner(data.apiPath);if(currentOwner?.moduleID!==data.moduleID){this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})}})}}_registerLazyWrapper(){this._totalLazyCount++;this._unmaterializedLazyCount++;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_REGISTERED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount})}}_onWrapperMaterialized(){this._unmaterializedLazyCount--;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_MATERIALIZED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount,percentage:this._totalLazyCount>0?(this._totalLazyCount-this._unmaterializedLazyCount)/this._totalLazyCount*100:100})}if(this._unmaterializedLazyCount===0&&!this._materializationComplete){this._materializationComplete=true;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_ALL_LAZY_WRAPPERS_MATERIALIZED",total:this._totalLazyCount})}const waiters=this._materializationWaiters.splice(0);for(const resolve of waiters){resolve()}if(this.config?.tracking?.materialization&&!this._materializationCompleteEmitted){this._materializationCompleteEmitted=true;if(this.handlers?.lifecycle){this.handlers.lifecycle.emit("materialized:complete",{total:this._totalLazyCount,timestamp:Date.now()})}}}}_captureEnvSnapshot(envConfig){const rawInclude=envConfig?.include;const include=Array.isArray(rawInclude)?rawInclude.filter(key=>typeof key==="string"):null;const useAllowlist=include!==null&&include.length>0;const raw=useAllowlist?include.reduce((acc,key)=>{if(Object.prototype.hasOwnProperty.call(process.env,key)){acc[key]=process.env[key]}return acc},Object.create(null)):{...process.env};return Object.freeze(raw)}async load(config={},preservedInstanceID=null){this.config=config;if(!this.envSnapshot){this.envSnapshot=this._captureEnvSnapshot(config.env)}this.debugLogger=new SlothletDebug(config);await this._initializeComponents();registerInstance(this);this._setupLifecycleSubscribers();this.config=this.helpers.config.transformConfig(config);if(this.config?.i18n?.language){initI18n({language:this.config.i18n.language})}this.debugLogger=new SlothletDebug(this.config);this.instanceID=preservedInstanceID||this.helpers.utilities.generateId();this.reference=this.config.reference;this.context=this.config.context;this.contextManager=getContextManager(this.config.runtime);setApiContextChecker(()=>{const ctx=this.contextManager.tryGetContext();return!!(ctx&&ctx.self)});let store;if(preservedInstanceID&&this.contextManager.instances.has(preservedInstanceID)){this.contextManager.cleanup(preservedInstanceID);store=this.contextManager.initialize(this.instanceID,this.config)}else{store=this.contextManager.initialize(this.instanceID,this.config)}enableEventEmitterPatching();if(typeof this.contextManager.registerEventEmitterContextChecker==="function"){this.contextManager.registerEventEmitterContextChecker()}const baseModuleId=`base_${this.helpers.utilities.generateId().substring(0,8)}`;const baseApi=await this.builders.builder.buildAPI({dir:this.config.dir,mode:this.config.mode,moduleID:baseModuleId});this.api=baseApi;const apiWithBuiltins=await this.buildFinalAPI(this.api);if(this.handlers.apiCacheManager){this.handlers.apiCacheManager.set(baseModuleId,{endpoint:".",moduleID:baseModuleId,api:this.api,folderPath:this.config.dir,mode:this.config.mode,sanitizeOptions:this.config.sanitize||{},collisionMode:this.config.collision?.api||"merge",config:{...this.config},timestamp:Date.now()})}this.injectRuntimeMetadataFunctions(apiWithBuiltins);if(this.config.metadata&&typeof this.config.metadata==="object"){for(const[key,value]of Object.entries(this.config.metadata)){this.handlers.metadata.setGlobalMetadata(key,value)}this.handlers.metadata.registerUserMetadata(baseModuleId,this.config.metadata)}if(this.handlers.ownership){this.handlers.ownership.registerSubtree(apiWithBuiltins,baseModuleId,"");this.handlers.ownership.setModuleEndpoint(baseModuleId,".")}if(!this.boundApi){const isCallable=typeof this.api==="function"||this.api&&typeof this.api.default==="function";const proxyTarget=isCallable?function(){}:{};this.boundApi=new Proxy(proxyTarget,{get:(target,prop)=>this.api?this.api[prop]:void 0,set:(target,prop,value)=>{if(this.api){this.api[prop]=value}return true},has:(target,prop)=>this.api?prop in this.api:false,ownKeys:____target=>this.api?Reflect.ownKeys(this.api):[],deleteProperty:(target,prop)=>this.api?delete this.api[prop]:true,apply:(target,thisArg,args)=>this.api?Reflect.apply(this.api,thisArg,args):void 0,construct:(target,args)=>this.api?Reflect.construct(this.api,args):{},getOwnPropertyDescriptor:(target,prop)=>{if(isCallable&&prop==="prototype"){return Object.getOwnPropertyDescriptor(target,prop)}if(this.api&&prop in this.api){const desc=Object.getOwnPropertyDescriptor(this.api,prop);if(desc){return{...desc,configurable:true}}}return void 0}})}store.self=this.boundApi;store.context=this.context||{};store.slothlet=this;if(this.reference&&typeof this.reference==="object"){Object.assign(this.boundApi,this.reference)}this.isLoaded=true;return this.boundApi}async reload(options={}){const{keepInstanceID=false}=options;if(!this.config?.dir){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reload",validationError:true})}const operationHistory=this.handlers.apiManager?.state?.operationHistory?[...this.handlers.apiManager.state.operationHistory]:[];await this._clearModuleCaches();const oldInstanceID=this.instanceID;if(!keepInstanceID){this.instanceID=`${oldInstanceID}_reload_${Date.now()}`;if(this._typescriptCacheDirs?.size){const{rm}=await import("node:fs/promises");await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}}const savedMetadataState=this.handlers.metadata?.exportUserState?.();const savedHooks=this.handlers.hookManager?.exportHooks?.();await this.load(this.config,this.instanceID);if(savedMetadataState&&this.handlers.metadata){this.handlers.metadata.importUserState(savedMetadataState)}if(savedHooks?.length&&this.handlers.hookManager){this.handlers.hookManager.importHooks(savedHooks)}for(const[,store]of this.contextManager.instances){if(store.parentInstanceID===oldInstanceID){store.parentInstanceID=this.instanceID}}if(oldInstanceID&&oldInstanceID!==this.instanceID&&this.contextManager.instances?.has(oldInstanceID)){this.contextManager.cleanup(oldInstanceID)}for(const operation of operationHistory){if(operation.type==="add"){await this.handlers.apiManager.addApiComponent({apiPath:operation.apiPath,folderPath:operation.folderPath,options:{...operation.options||{},recordHistory:false},moduleID:`replay_${this.helpers.utilities.generateId().substring(0,8)}`,versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){await this.handlers.apiManager.removeApiComponent(operation.apiPath,{recordHistory:false})}else if(operation.type==="addPermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.addRule(operation.rule,operation.ownerModuleID,operation.ruleId)}}else if(operation.type==="removePermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.removeRule(operation.ruleId,operation.callerModuleID)}}}return this.boundApi}async _clearModuleCaches(){const targetDir=this.config.dir;const{resolve}=await import("node:path");const{createRequire}=await import("node:module");const require2=createRequire(import.meta.url);const absoluteTargetDir=resolve(targetDir);for(const key of Object.keys(require2.cache)){if(key.startsWith(absoluteTargetDir)){delete require2.cache[key]}}}injectRuntimeMetadataFunctions(api){if(!api.slothlet?.metadata){return}const metadataHandler=this.handlers.metadata;api.slothlet.metadata.get=async function slothlet_metadata_get_runtime(path){return metadataHandler.get(path)};api.slothlet.metadata.self=function slothlet_metadata_self_runtime(){return metadataHandler.self()};api.slothlet.metadata.caller=function slothlet_metadata_caller_runtime(){return metadataHandler.caller()}}async _drainInFlightLoads(){if(!this.api)return;const pending=[];const seen=new Set;const collect=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){const mat=wrapper.____slothletInternal.materializationPromise;if(mat)pending.push(mat);for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){collect(this.api[key])}if(pending.length>0){await Promise.allSettled(pending)}}async shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();disableEventEmitterPatching();cleanupEventEmitterResources();if(this.instanceID&&this.contextManager){this.contextManager.cleanup(this.instanceID)}if(this.handlers.ownership){this.handlers.ownership.clear()}this.handlers.versionManager?.shutdown();await this.handlers.permissionManager?.shutdown();if(this._typescriptCacheDirs?.size){const{rm}=await import("node:fs/promises");await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}this.isLoaded=false}debug(code,context={}){if(this.debugLogger){this.debugLogger.log(code,context)}}getAPI(){if(!this.isLoaded){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"getAPI"},null,{validationError:true})}return this.boundApi}getDiagnostics(){return{instanceID:this.instanceID,isLoaded:this.isLoaded,config:this.config,context:this.contextManager?.getDiagnostics()||null,ownership:this.handlers.ownership?.getDiagnostics()||null}}getOwnership(){if(!this.handlers.ownership){return null}return this.handlers.ownership.getDiagnostics()}buildFinalAPI(userApi){return this.builders.apiBuilder.buildFinalAPI(userApi)}}async function slothlet(config){const instance=new Slothlet;const api=await instance.load(config);return api}var stdin_default=slothlet;export{stdin_default as default,slothlet};
17
+ import{isNode,fs,fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";import{getContextManager}from"@cldmv/slothlet/factories/context";import{SlothletError,SlothletWarning,SlothletDebug}from"@cldmv/slothlet/errors";import{registerInstance}from"@cldmv/slothlet/handlers/lifecycle-token";import{resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";import{initI18n}from"@cldmv/slothlet/i18n";import{enableEventEmitterPatching,disableEventEmitterPatching,cleanupEventEmitterResources,setApiContextChecker}from"@cldmv/slothlet/helpers/eventemitter-context";class Slothlet{static RESERVED_ROOT_KEYS=["slothlet","shutdown","destroy"];static SKIP_PROPS=["__metadata","__type","_materialize","_impl","____slothletInternal"];constructor(){this.SlothletError=SlothletError;this.SlothletWarning=SlothletWarning;this.debugLogger=null;this.instanceID=null;this.config=null;this.envTarget="node";this.api=null;this.boundApi=null;this.contextManager=null;this.isLoaded=false;this.reference=null;this.context=null;this.envSnapshot=null;this._totalLazyCount=0;this._unmaterializedLazyCount=0;this._materializationComplete=false;this._materializationWaiters=[];this._materializationCompleteEmitted=false;this.componentCategories=["helpers","handlers","builders","processors","modes"];for(const category of this.componentCategories){this[category]={}}}async _initializeComponents(){if(this.envTarget==="browser"){await this._initializeComponentsBrowser();return}const baseDir=path.join(path.dirname(url.fileURLToPath(import.meta.url)),"lib");for(const category of this.componentCategories){const categoryDir=path.join(baseDir,category);const files=fs.readdirSync(categoryDir).filter(f=>f.endsWith(".mjs"));for(const file of files){const filePath=path.join(categoryDir,file);try{const module=await import(url.pathToFileURL(filePath).href);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this);if(this.config?.debug?.initialization){this.debug("initialization",{key:"DEBUG_MODE_COMPONENT_INITIALIZED",component:ClassExport.name,category,propertyName:propName})}}}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}}}async _initializeComponentsBrowser(){const BROWSER_COMPONENT_SPECIFIERS=["@cldmv/slothlet/builders/api-assignment","@cldmv/slothlet/builders/api_builder","@cldmv/slothlet/builders/builder","@cldmv/slothlet/builders/modes-processor","@cldmv/slothlet/handlers/api-cache-manager","@cldmv/slothlet/handlers/api-manager","@cldmv/slothlet/handlers/hook-manager","@cldmv/slothlet/handlers/lifecycle","@cldmv/slothlet/handlers/materialize-manager","@cldmv/slothlet/handlers/metadata","@cldmv/slothlet/handlers/module-manager","@cldmv/slothlet/handlers/ownership","@cldmv/slothlet/handlers/permission-manager","@cldmv/slothlet/handlers/version-manager","@cldmv/slothlet/helpers/config","@cldmv/slothlet/helpers/hint-detector","@cldmv/slothlet/helpers/modes-utils","@cldmv/slothlet/helpers/resolve-from-caller","@cldmv/slothlet/helpers/sanitize","@cldmv/slothlet/helpers/utilities","@cldmv/slothlet/modes/eager","@cldmv/slothlet/modes/lazy","@cldmv/slothlet/processors/flatten","@cldmv/slothlet/processors/loader"];for(const specifier of BROWSER_COMPONENT_SPECIFIERS){const parts=specifier.split("/");const category=parts[2];const module=await import(specifier);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this)}}}_setupLifecycleSubscribers(){if(!this.handlers.lifecycle){return}if(this.handlers.metadata){this.handlers.lifecycle.subscribe("impl:created",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:changed",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:removed",data=>{if(data.apiPath){const rootSegment=data.apiPath.split(".")[0];this.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}})}if(this.handlers.ownership){this.handlers.lifecycle.subscribe("impl:created",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})});this.handlers.lifecycle.subscribe("impl:changed",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;const currentOwner=this.handlers.ownership.getCurrentOwner(data.apiPath);if(currentOwner?.moduleID!==data.moduleID){this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})}})}}_registerLazyWrapper(){this._totalLazyCount++;this._unmaterializedLazyCount++;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_REGISTERED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount})}}_onWrapperMaterialized(){this._unmaterializedLazyCount--;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_MATERIALIZED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount,percentage:this._totalLazyCount>0?(this._totalLazyCount-this._unmaterializedLazyCount)/this._totalLazyCount*100:100})}if(this._unmaterializedLazyCount===0&&!this._materializationComplete){this._materializationComplete=true;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_ALL_LAZY_WRAPPERS_MATERIALIZED",total:this._totalLazyCount})}const waiters=this._materializationWaiters.splice(0);for(const resolve of waiters){resolve()}if(this.config?.tracking?.materialization&&!this._materializationCompleteEmitted){this._materializationCompleteEmitted=true;if(this.handlers?.lifecycle){this.handlers.lifecycle.emit("materialized:complete",{total:this._totalLazyCount,timestamp:Date.now()})}}}}_captureEnvSnapshot(envConfig){const rawInclude=envConfig?.include;const include=Array.isArray(rawInclude)?rawInclude.filter(key=>typeof key==="string"):null;const useAllowlist=include!==null&&include.length>0;const raw=useAllowlist?include.reduce((acc,key)=>{if(Object.prototype.hasOwnProperty.call(process.env,key)){acc[key]=process.env[key]}return acc},Object.create(null)):{...process.env};return Object.freeze(raw)}async load(config={},preservedInstanceID=null){this.config=config;this.envTarget=config.platform==="browser"||config.platform!=="node"&&config.manifest!=null?"browser":"node";if(!this.envSnapshot){this.envSnapshot=this.envTarget==="browser"?Object.freeze(Object.create(null)):this._captureEnvSnapshot(config.env)}this.debugLogger=new SlothletDebug(config);await this._initializeComponents();registerInstance(this);this._setupLifecycleSubscribers();this.config=this.helpers.config.transformConfig(config);if(this.config?.i18n?.language){initI18n({language:this.config.i18n.language})}this.debugLogger=new SlothletDebug(this.config);this.instanceID=preservedInstanceID||this.helpers.utilities.generateId();this.reference=this.config.reference;this.context=this.config.context;this.contextManager=getContextManager(this.envTarget==="browser"?"live":this.config.runtime);setApiContextChecker(()=>{const ctx=this.contextManager.tryGetContext();return!!(ctx&&ctx.self)});let store;if(preservedInstanceID&&this.contextManager.instances.has(preservedInstanceID)){this.contextManager.cleanup(preservedInstanceID);store=this.contextManager.initialize(this.instanceID,this.config)}else{store=this.contextManager.initialize(this.instanceID,this.config)}enableEventEmitterPatching();if(typeof this.contextManager.registerEventEmitterContextChecker==="function"){this.contextManager.registerEventEmitterContextChecker()}const baseModuleId=`base_${this.helpers.utilities.generateId().substring(0,8)}`;const baseApi=await this.builders.builder.buildAPI({dir:this.config.dir,mode:this.config.mode,moduleID:baseModuleId});this.api=baseApi;const apiWithBuiltins=await this.buildFinalAPI(this.api);if(this.handlers.apiCacheManager){this.handlers.apiCacheManager.set(baseModuleId,{endpoint:".",moduleID:baseModuleId,api:this.api,folderPath:this.config.dir,mode:this.config.mode,sanitizeOptions:this.config.sanitize||{},collisionMode:this.config.collision?.api||"merge",config:{...this.config},timestamp:Date.now()})}this.injectRuntimeMetadataFunctions(apiWithBuiltins);if(this.config.metadata&&typeof this.config.metadata==="object"){for(const[key,value]of Object.entries(this.config.metadata)){this.handlers.metadata.setGlobalMetadata(key,value)}this.handlers.metadata.registerUserMetadata(baseModuleId,this.config.metadata)}if(this.handlers.ownership){this.handlers.ownership.registerSubtree(apiWithBuiltins,baseModuleId,"");this.handlers.ownership.setModuleEndpoint(baseModuleId,".")}if(!this.boundApi){const isCallable=typeof this.api==="function"||this.api&&typeof this.api.default==="function";const proxyTarget=isCallable?function(){}:{};this.boundApi=new Proxy(proxyTarget,{get:(target,prop)=>this.api?this.api[prop]:void 0,set:(target,prop,value)=>{if(this.api){this.api[prop]=value}return true},has:(target,prop)=>this.api?prop in this.api:false,ownKeys:____target=>this.api?Reflect.ownKeys(this.api):[],deleteProperty:(target,prop)=>this.api?delete this.api[prop]:true,apply:(target,thisArg,args)=>this.api?Reflect.apply(this.api,thisArg,args):void 0,construct:(target,args)=>this.api?Reflect.construct(this.api,args):{},getOwnPropertyDescriptor:(target,prop)=>{if(isCallable&&prop==="prototype"){return Object.getOwnPropertyDescriptor(target,prop)}if(this.api&&prop in this.api){const desc=Object.getOwnPropertyDescriptor(this.api,prop);if(desc){return{...desc,configurable:true}}}return void 0}})}store.self=this.boundApi;store.context=this.context||{};store.slothlet=this;if(this.reference&&typeof this.reference==="object"){Object.assign(this.boundApi,this.reference)}this.isLoaded=true;return this.boundApi}async reload(options={}){const{keepInstanceID=false}=options;if(!this.config?.dir){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reload",validationError:true})}const operationHistory=this.handlers.apiManager?.state?.operationHistory?[...this.handlers.apiManager.state.operationHistory]:[];await this._clearModuleCaches();const oldInstanceID=this.instanceID;if(!keepInstanceID){this.instanceID=`${oldInstanceID}_reload_${Date.now()}`;if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}}const savedMetadataState=this.handlers.metadata?.exportUserState?.();const savedHooks=this.handlers.hookManager?.exportHooks?.();await this.load(this.config,this.instanceID);if(savedMetadataState&&this.handlers.metadata){this.handlers.metadata.importUserState(savedMetadataState)}if(savedHooks?.length&&this.handlers.hookManager){this.handlers.hookManager.importHooks(savedHooks)}for(const[,store]of this.contextManager.instances){if(store.parentInstanceID===oldInstanceID){store.parentInstanceID=this.instanceID}}if(oldInstanceID&&oldInstanceID!==this.instanceID&&this.contextManager.instances?.has(oldInstanceID)){this.contextManager.cleanup(oldInstanceID)}for(const operation of operationHistory){if(operation.type==="add"){await this.handlers.apiManager.addApiComponent({apiPath:operation.apiPath,folderPath:operation.folderPath,options:{...operation.options||{},recordHistory:false},moduleID:`replay_${this.helpers.utilities.generateId().substring(0,8)}`,versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){await this.handlers.apiManager.removeApiComponent(operation.apiPath,{recordHistory:false})}else if(operation.type==="addPermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.addRule(operation.rule,operation.ownerModuleID,operation.ruleId)}}else if(operation.type==="removePermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.removeRule(operation.ruleId,operation.callerModuleID)}}}return this.boundApi}async _clearModuleCaches(){if(!isNode)return;const targetDir=this.config.dir;const require2=createRequire(import.meta.url);const absoluteTargetDir=path.resolve(targetDir);for(const key of Object.keys(require2.cache)){if(key.startsWith(absoluteTargetDir)){delete require2.cache[key]}}}injectRuntimeMetadataFunctions(api){if(!api.slothlet?.metadata){return}const metadataHandler=this.handlers.metadata;api.slothlet.metadata.get=async function slothlet_metadata_get_runtime(path2){return metadataHandler.get(path2)};api.slothlet.metadata.self=function slothlet_metadata_self_runtime(){return metadataHandler.self()};api.slothlet.metadata.caller=function slothlet_metadata_caller_runtime(){return metadataHandler.caller()}}async _drainInFlightLoads(){if(!this.api)return;const pending=[];const seen=new Set;const collect=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){const mat=wrapper.____slothletInternal.materializationPromise;if(mat)pending.push(mat);for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){collect(this.api[key])}if(pending.length>0){await Promise.allSettled(pending)}}async shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();disableEventEmitterPatching();cleanupEventEmitterResources();if(this.instanceID&&this.contextManager){this.contextManager.cleanup(this.instanceID)}if(this.handlers.ownership){this.handlers.ownership.clear()}this.handlers.versionManager?.shutdown();await this.handlers.permissionManager?.shutdown();if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}this.isLoaded=false}debug(code,context={}){if(this.debugLogger){this.debugLogger.log(code,context)}}getAPI(){if(!this.isLoaded){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"getAPI"},null,{validationError:true})}return this.boundApi}getDiagnostics(){return{instanceID:this.instanceID,isLoaded:this.isLoaded,config:this.config,context:this.contextManager?.getDiagnostics()||null,ownership:this.handlers.ownership?.getDiagnostics()||null}}getOwnership(){if(!this.handlers.ownership){return null}return this.handlers.ownership.getDiagnostics()}buildFinalAPI(userApi){return this.builders.apiBuilder.buildFinalAPI(userApi)}}async function slothlet(config){const instance=new Slothlet;const api=await instance.load(config);return api}var stdin_default=slothlet;export{stdin_default as default,slothlet};
package/index.cjs CHANGED
@@ -22,31 +22,21 @@
22
22
  * Eliminates code duplication between entry points and ensures consistent behavior.
23
23
  * @public
24
24
  * @async
25
- * @param {object} [options={}] - Configuration options for the slothlet instance
26
- * @param {string} [options.dir="api"] - Directory to load API modules from
27
- * @param {boolean} [options.lazy=false] - Use lazy loading (true) or eager loading (false)
28
- * @param {number} [options.apiDepth=Infinity] - Maximum directory depth to scan
29
- * @param {boolean} [options.debug=false] - Enable debug logging
30
- * @param {string} [options.mode="singleton"] - Execution mode (singleton, vm, worker, fork)
31
- * @param {string} [options.api_mode="auto"] - API structure mode (auto, function, object)
32
- * @param {string} [options.runtime] - Runtime type ("async", "asynclocalstorage", "live", "livebindings", "experimental")
33
- * @param {boolean} [options.allowApiOverwrite=true] - Allow addApi to overwrite existing API endpoints
34
- * @param {object} [options.context={}] - Context data for live bindings
35
- * @param {object} [options.reference={}] - Reference objects to merge into API root
25
+ * @param {import("./src/slothlet.mjs").SlothletOptions} [options={}] - Configuration options for the slothlet instance. See {@link SlothletOptions} for the full set.
36
26
  * @returns {Promise<import("./src/slothlet.mjs").SlothletAPI>} The bound API object with management methods
37
27
  *
38
28
  * @example // CJS usage
39
29
  * const slothlet = require("@cldmv/slothlet");
40
- * const api = await slothlet({ dir: "./api", context: { user: "alice" } });
30
+ * const api = await slothlet({ base: "./api", context: { user: "alice" } });
41
31
  * console.log(api.config.username); // Access configuration
42
32
  *
43
33
  * @example // CJS usage with runtime selection
44
34
  * const slothlet = require("@cldmv/slothlet");
45
- * const api = await slothlet({ dir: "./api", runtime: "live" });
35
+ * const api = await slothlet({ base: "./api", runtime: "live" });
46
36
  *
47
37
  * @example // CJS named destructuring
48
38
  * const { slothlet } = require("@cldmv/slothlet");
49
- * const api = await slothlet({ dir: "./api" });
39
+ * const api = await slothlet({ base: "./api" });
50
40
  */
51
41
  async function slothlet(options = {}) {
52
42
  // Dynamic import of ESM entry point - single source of truth