@cldmv/slothlet 3.2.3 → 3.3.2

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 (138) hide show
  1. package/README.md +22 -9
  2. package/REFERENCE.md +23 -0
  3. package/dist/lib/builders/api-assignment.mjs +1 -589
  4. package/dist/lib/builders/api_builder.mjs +1 -1155
  5. package/dist/lib/builders/builder.mjs +1 -78
  6. package/dist/lib/builders/modes-processor.mjs +1 -1800
  7. package/dist/lib/errors.mjs +9 -211
  8. package/dist/lib/factories/component-base.mjs +1 -80
  9. package/dist/lib/factories/context.mjs +1 -22
  10. package/dist/lib/handlers/api-cache-manager.mjs +1 -200
  11. package/dist/lib/handlers/api-manager.mjs +1 -2513
  12. package/dist/lib/handlers/context-async.mjs +1 -168
  13. package/dist/lib/handlers/context-live.mjs +1 -168
  14. package/dist/lib/handlers/hook-manager.mjs +1 -773
  15. package/dist/lib/handlers/lifecycle-token.mjs +1 -28
  16. package/dist/lib/handlers/lifecycle.mjs +1 -115
  17. package/dist/lib/handlers/materialize-manager.mjs +1 -48
  18. package/dist/lib/handlers/metadata.mjs +1 -501
  19. package/dist/lib/handlers/ownership.mjs +1 -322
  20. package/dist/lib/handlers/permission-manager.mjs +17 -0
  21. package/dist/lib/handlers/unified-wrapper.mjs +1 -3042
  22. package/dist/lib/handlers/version-manager.mjs +1 -885
  23. package/dist/lib/helpers/class-instance-wrapper.mjs +1 -109
  24. package/dist/lib/helpers/config.mjs +1 -355
  25. package/dist/lib/helpers/eventemitter-context.mjs +1 -349
  26. package/dist/lib/helpers/hint-detector.mjs +1 -47
  27. package/dist/lib/helpers/modes-utils.mjs +1 -37
  28. package/dist/lib/helpers/pattern-matcher.mjs +17 -0
  29. package/dist/lib/helpers/resolve-from-caller.mjs +1 -169
  30. package/dist/lib/helpers/sanitize.mjs +1 -340
  31. package/dist/lib/helpers/utilities.mjs +1 -70
  32. package/dist/lib/i18n/languages/de-de.json +21 -1
  33. package/dist/lib/i18n/languages/en-gb.json +21 -1
  34. package/dist/lib/i18n/languages/en-us.json +21 -1
  35. package/dist/lib/i18n/languages/es-mx.json +21 -1
  36. package/dist/lib/i18n/languages/fr-fr.json +21 -1
  37. package/dist/lib/i18n/languages/hi-in.json +21 -1
  38. package/dist/lib/i18n/languages/ja-jp.json +21 -1
  39. package/dist/lib/i18n/languages/ko-kr.json +21 -1
  40. package/dist/lib/i18n/languages/pt-br.json +21 -1
  41. package/dist/lib/i18n/languages/ru-ru.json +21 -1
  42. package/dist/lib/i18n/languages/zh-cn.json +21 -1
  43. package/dist/lib/i18n/translations.mjs +1 -126
  44. package/dist/lib/modes/eager.mjs +1 -59
  45. package/dist/lib/modes/lazy.mjs +1 -81
  46. package/dist/lib/processors/flatten.mjs +1 -437
  47. package/dist/lib/processors/loader.mjs +1 -339
  48. package/dist/lib/processors/type-generator.mjs +1 -275
  49. package/dist/lib/processors/typescript.mjs +1 -172
  50. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +1 -113
  51. package/dist/lib/runtime/runtime-livebindings.mjs +1 -78
  52. package/dist/lib/runtime/runtime.mjs +1 -102
  53. package/dist/slothlet.mjs +1 -808
  54. package/package.json +37 -31
  55. package/types/dist/lib/builders/api-assignment.d.mts +3 -92
  56. package/types/dist/lib/builders/api-assignment.d.mts.map +1 -1
  57. package/types/dist/lib/builders/api_builder.d.mts +102 -91
  58. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  59. package/types/dist/lib/builders/builder.d.mts +1 -55
  60. package/types/dist/lib/builders/builder.d.mts.map +1 -1
  61. package/types/dist/lib/builders/modes-processor.d.mts +3 -27
  62. package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
  63. package/types/dist/lib/errors.d.mts +19 -109
  64. package/types/dist/lib/errors.d.mts.map +1 -1
  65. package/types/dist/lib/factories/component-base.d.mts +7 -177
  66. package/types/dist/lib/factories/component-base.d.mts.map +1 -1
  67. package/types/dist/lib/factories/context.d.mts +4 -22
  68. package/types/dist/lib/factories/context.d.mts.map +1 -1
  69. package/types/dist/lib/handlers/api-cache-manager.d.mts +20 -203
  70. package/types/dist/lib/handlers/api-cache-manager.d.mts.map +1 -1
  71. package/types/dist/lib/handlers/api-manager.d.mts +33 -408
  72. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
  73. package/types/dist/lib/handlers/context-async.d.mts +23 -61
  74. package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
  75. package/types/dist/lib/handlers/context-live.d.mts +22 -59
  76. package/types/dist/lib/handlers/context-live.d.mts.map +1 -1
  77. package/types/dist/lib/handlers/hook-manager.d.mts +46 -185
  78. package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -1
  79. package/types/dist/lib/handlers/lifecycle-token.d.mts +3 -48
  80. package/types/dist/lib/handlers/lifecycle-token.d.mts.map +1 -1
  81. package/types/dist/lib/handlers/lifecycle.d.mts +5 -82
  82. package/types/dist/lib/handlers/lifecycle.d.mts.map +1 -1
  83. package/types/dist/lib/handlers/materialize-manager.d.mts +8 -70
  84. package/types/dist/lib/handlers/materialize-manager.d.mts.map +1 -1
  85. package/types/dist/lib/handlers/metadata.d.mts +17 -221
  86. package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
  87. package/types/dist/lib/handlers/ownership.d.mts +44 -160
  88. package/types/dist/lib/handlers/ownership.d.mts.map +1 -1
  89. package/types/dist/lib/handlers/permission-manager.d.mts +47 -0
  90. package/types/dist/lib/handlers/permission-manager.d.mts.map +1 -0
  91. package/types/dist/lib/handlers/unified-wrapper.d.mts +26 -239
  92. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
  93. package/types/dist/lib/handlers/version-manager.d.mts +28 -225
  94. package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
  95. package/types/dist/lib/helpers/class-instance-wrapper.d.mts +2 -52
  96. package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -1
  97. package/types/dist/lib/helpers/config.d.mts +125 -123
  98. package/types/dist/lib/helpers/config.d.mts.map +1 -1
  99. package/types/dist/lib/helpers/eventemitter-context.d.mts +3 -29
  100. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
  101. package/types/dist/lib/helpers/hint-detector.d.mts +2 -15
  102. package/types/dist/lib/helpers/hint-detector.d.mts.map +1 -1
  103. package/types/dist/lib/helpers/modes-utils.d.mts +3 -30
  104. package/types/dist/lib/helpers/modes-utils.d.mts.map +1 -1
  105. package/types/dist/lib/helpers/pattern-matcher.d.mts +4 -0
  106. package/types/dist/lib/helpers/pattern-matcher.d.mts.map +1 -0
  107. package/types/dist/lib/helpers/resolve-from-caller.d.mts +3 -27
  108. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  109. package/types/dist/lib/helpers/sanitize.d.mts +4 -92
  110. package/types/dist/lib/helpers/sanitize.d.mts.map +1 -1
  111. package/types/dist/lib/helpers/utilities.d.mts +4 -52
  112. package/types/dist/lib/helpers/utilities.d.mts.map +1 -1
  113. package/types/dist/lib/i18n/translations.d.mts +4 -37
  114. package/types/dist/lib/i18n/translations.d.mts.map +1 -1
  115. package/types/dist/lib/modes/eager.d.mts +8 -30
  116. package/types/dist/lib/modes/eager.d.mts.map +1 -1
  117. package/types/dist/lib/modes/lazy.d.mts +10 -43
  118. package/types/dist/lib/modes/lazy.d.mts.map +1 -1
  119. package/types/dist/lib/processors/flatten.d.mts +56 -107
  120. package/types/dist/lib/processors/flatten.d.mts.map +1 -1
  121. package/types/dist/lib/processors/loader.d.mts +6 -41
  122. package/types/dist/lib/processors/loader.d.mts.map +1 -1
  123. package/types/dist/lib/processors/type-generator.d.mts +2 -16
  124. package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
  125. package/types/dist/lib/processors/typescript.d.mts +6 -53
  126. package/types/dist/lib/processors/typescript.d.mts.map +1 -1
  127. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +3 -71
  128. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  129. package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -37
  130. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  131. package/types/dist/lib/runtime/runtime.d.mts +3 -39
  132. package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
  133. package/types/dist/slothlet.d.mts +3 -249
  134. package/types/dist/slothlet.d.mts.map +1 -1
  135. package/types/index.d.mts +36 -16
  136. package/types/index.d.mts.map +1 -0
  137. package/AGENT-USAGE.md +0 -736
  138. package/docs/API-RULES.md +0 -712
package/AGENT-USAGE.md DELETED
@@ -1,736 +0,0 @@
1
- # AGENT-USAGE.md: Building Slothlet API Folders
2
-
3
- > **Critical**: This guide prevents AI agents from making architectural mistakes when building Slothlet API modules.
4
-
5
- ## 📋 Related Documentation
6
-
7
- - **[`docs/API-RULES.md`](docs/API-RULES.md)** - All 13 API transformation rules with verified test examples
8
- - **[`README.md`](README.md)** - Complete project overview and usage examples
9
- - **[`api_tests/*/README.md`](api_tests/)** - Live examples demonstrating each pattern below
10
-
11
- ---
12
-
13
- ## 🚫 NEVER DO: Cross-Module Imports
14
-
15
- **The #1 mistake AI agents make with Slothlet**: Importing API files from each other.
16
-
17
- ```js
18
- // ❌ WRONG - Do NOT import API modules from each other
19
- import { math } from "./math/math.mjs"; // BREAKS SLOTHLET
20
- import { config } from "../config.mjs"; // BREAKS SLOTHLET
21
- import { util } from "./util/util.mjs"; // BREAKS SLOTHLET
22
- ```
23
-
24
- **Why this breaks Slothlet**:
25
-
26
- - Slothlet builds API structure dynamically at runtime
27
- - Cross-imports create circular dependencies
28
- - Breaks lazy loading and context isolation
29
- - Defeats the purpose of the module loading framework
30
-
31
- ## ✅ CORRECT: Use Slothlet's Live-Binding System
32
-
33
- ```js
34
- // ✅ CORRECT - Import from Slothlet runtime for cross-module access
35
- import { self, context } from "@cldmv/slothlet/runtime";
36
-
37
- export const myModule = {
38
- async processData(input) {
39
- // Access other API modules via `self` (live binding - always current)
40
- const mathResult = self.math.add(2, 3);
41
- const configValue = self.config.get("setting");
42
- // context holds the current request/call context
43
- console.log(`Caller: ${context.userId}`);
44
- return `Processed: ${input}, Math: ${mathResult}`;
45
- }
46
- };
47
- ```
48
-
49
- ---
50
-
51
- ## 🏗️ API Module Patterns
52
-
53
- ### Pattern 1: Simple Object Export (Most Common)
54
-
55
- **File**: `math/math.mjs` → **API**: `api.math.add()`, `api.math.multiply()`
56
-
57
- ```js
58
- export const math = {
59
- add(a, b) { return a + b; },
60
- multiply(a, b) { return a * b; }
61
- };
62
- ```
63
-
64
- **Result**: Filename matches folder (`math/math.mjs`) → Auto-flattening → `api.math.add()` (not `api.math.math.add()`)
65
-
66
- > 📖 See [API-RULES.md Rule 1](docs/API-RULES.md) for flattening details.
67
-
68
- ### Pattern 2: Multiple Files in Folder
69
-
70
- **Files**: `multi/alpha.mjs`, `multi/beta.mjs` → **API**: `api.multi.alpha.hello()`, `api.multi.beta.world()`
71
-
72
- ```js
73
- // File: multi/alpha.mjs
74
- export const alpha = { hello() { return "alpha hello"; } };
75
-
76
- // File: multi/beta.mjs
77
- export const beta = { world() { return "beta world"; } };
78
- ```
79
-
80
- **Result**: Different filenames from folder → No flattening → Nested structure preserved.
81
-
82
- ### Pattern 3: Default Function Export
83
-
84
- **File**: `funcmod/funcmod.mjs` → **API**: `api.funcmod(name)`
85
-
86
- ```js
87
- export default function funcmod(name) {
88
- return `Hello, ${name}!`;
89
- }
90
- ```
91
-
92
- **Result**: Filename matches folder + default export → Function flattened to `api.funcmod()`.
93
-
94
- ### Pattern 4: Root-Level API Functions
95
-
96
- **File**: `root-function.mjs` → **API**: `api(name)` + `api.rootFunctionShout()`
97
-
98
- ```js
99
- export default function greet(name) {
100
- return `Hello, ${name}!`;
101
- }
102
-
103
- export function rootFunctionShout(message) {
104
- return message.toUpperCase();
105
- }
106
- ```
107
-
108
- **Result**: Root file with default export → `api()` callable + named exports as top-level `api.methodName()`.
109
-
110
- ### Pattern 5: AddApi Special File Pattern (Rule 11)
111
-
112
- Files named `addapi.mjs` always flatten regardless of `autoFlatten` setting:
113
-
114
- ```js
115
- // File: plugins/addapi.mjs
116
- export function initializePlugin() { return "Plugin initialized"; }
117
- export function cleanup() { return "Plugin cleaned up"; }
118
- ```
119
-
120
- ```js
121
- await api.slothlet.api.add("plugins", "./plugins-folder");
122
- api.plugins.initializePlugin(); // ✅ Direct extension - no intermediate namespace
123
- ```
124
-
125
- > 📖 See [API-RULES.md Rule 11](docs/API-RULES.md) for addApi flattening details.
126
-
127
- ---
128
-
129
- ## 🔄 Operating Modes
130
-
131
- Slothlet supports two loading modes set via `mode:` in the config:
132
-
133
- ### Eager Mode (default)
134
-
135
- All modules are loaded synchronously at `await slothlet(...)`. The API is fully populated before `slothlet()` resolves.
136
-
137
- ```js
138
- const api = await slothlet({ dir: "./api" }); // mode: "eager" is default
139
- // All api.* properties are immediately available
140
- ```
141
-
142
- ### Lazy Mode
143
-
144
- Modules are loaded on first access via transparent proxy. `slothlet()` resolves immediately without loading any files.
145
-
146
- ```js
147
- const api = await slothlet({
148
- dir: "./api",
149
- mode: "lazy"
150
- });
151
- // api.math is a proxy - file not loaded yet
152
- const result = api.math.add(2, 3); // First access triggers load
153
- ```
154
-
155
- #### Background Materialization
156
-
157
- Enable `backgroundMaterialize: true` to pre-load all modules in the background immediately after init (still non-blocking):
158
-
159
- ```js
160
- const api = await slothlet({
161
- dir: "./api",
162
- mode: "lazy",
163
- backgroundMaterialize: true
164
- });
165
-
166
- // Subscribe to completion event
167
- api.slothlet.lifecycle.on("materialized:complete", (data) => {
168
- console.log(`${data.total} modules materialized`);
169
- });
170
-
171
- // Or await all modules to be ready
172
- await api.slothlet.materialize.wait();
173
-
174
- // Or check current progress
175
- const stats = api.slothlet.materialize.get();
176
- // { total, materialized, remaining, percentage }
177
- ```
178
-
179
- **Important**: Lazy mode hot reload intentionally restores modules to an unmaterialized state on reload (references are not preserved). Eager mode preserves existing references by merging into the live wrapper.
180
-
181
- ---
182
-
183
- ## 🎣 Hook System
184
-
185
- Hooks intercept API function calls. They work across all modes. See [`docs/HOOKS.md`](docs/HOOKS.md) for the full reference.
186
-
187
- ### Hook Configuration
188
-
189
- ```js
190
- // Simple enable (default pattern "**")
191
- const api = await slothlet({ dir: "./api", hook: true });
192
-
193
- // Enable with default pattern filter
194
- const api = await slothlet({ dir: "./api", hook: "database.*" });
195
-
196
- // Full configuration
197
- const api = await slothlet({
198
- dir: "./api",
199
- hook: {
200
- enabled: true,
201
- pattern: "**",
202
- suppressErrors: false // true = errors suppressed (returns undefined instead of throwing)
203
- }
204
- });
205
- ```
206
-
207
- ### Hook Types
208
-
209
- - **`before`** - Executes before the function. Can modify arguments or short-circuit. **Must be synchronous.**
210
- - **`after`** - Executes after successful completion. Can transform the return value.
211
- - **`always`** - Read-only observer. Always executes (even on short-circuit). Return value ignored.
212
- - **`error`** - Executes only when an error occurs. Receives error with source tracking.
213
-
214
- ### Basic Hook Usage
215
-
216
- The `hook.on(typePattern, handler, options)` signature uses `"type:pattern"` as the first argument:
217
-
218
- ```js
219
- // Before hook - modify arguments
220
- api.slothlet.hook.on(
221
- "before:math.add",
222
- ({ path, args, ctx }) => {
223
- return [args[0] * 2, args[1] * 2]; // Return array to replace arguments
224
- // Return any non-array non-undefined value to short-circuit (skip function)
225
- // Return undefined to continue with original args
226
- },
227
- { id: "double-args", priority: 100 }
228
- );
229
-
230
- // After hook - transform result
231
- api.slothlet.hook.on(
232
- "after:math.*",
233
- ({ path, args, result, ctx }) => {
234
- return result * 10; // Return value to replace result; undefined = no change
235
- },
236
- { id: "scale-result" }
237
- );
238
-
239
- // Always hook - observe (read-only)
240
- api.slothlet.hook.on(
241
- "always:**",
242
- ({ path, result, hasError, errors }) => {
243
- if (hasError) console.error(`${path} failed:`, errors);
244
- else console.log(`${path} returned:`, result);
245
- // Return value is ignored
246
- },
247
- { id: "logger" }
248
- );
249
-
250
- // Error hook - monitor failures
251
- api.slothlet.hook.on(
252
- "error:**",
253
- ({ path, error, source }) => {
254
- // source.type: "before" | "after" | "always" | "function"
255
- console.error(`Error in ${path} (from ${source.type}):`, error.message);
256
- },
257
- { id: "error-monitor" }
258
- );
259
- ```
260
-
261
- ### Pattern Matching
262
-
263
- | Syntax | Description | Example |
264
- |---|---|---|
265
- | `exact.path` | Exact match | `"before:math.add"` |
266
- | `namespace.*` | All functions in namespace | `"after:math.*"` |
267
- | `*.funcName` | Function name across namespaces | `"always:*.add"` |
268
- | `**` | All functions | `"error:**"` |
269
- | `{a,b}` | Brace expansion | `"before:{math,utils}.*"` |
270
- | `!pattern` | Negation | `"before:!internal.*"` |
271
-
272
- ### Hook Subsets
273
-
274
- Each hook type has three ordered execution phases:
275
-
276
- | Subset | Order | Typical use |
277
- |---|---|---|
278
- | `"before"` | First | Auth checks, security validation |
279
- | `"primary"` | Middle (default) | Main hook logic |
280
- | `"after"` | Last | Audit trails, cleanup |
281
-
282
- ```js
283
- api.slothlet.hook.on(
284
- "before:protected.*",
285
- ({ ctx }) => { if (!ctx.user) throw new Error("Unauthorized"); },
286
- { id: "auth", subset: "before", priority: 2000 }
287
- );
288
- ```
289
-
290
- ### Hook Management
291
-
292
- ```js
293
- // Remove by ID
294
- api.slothlet.hook.remove({ id: "my-hook" });
295
- api.slothlet.hook.off("my-hook"); // alias for remove
296
-
297
- // Remove by filter
298
- api.slothlet.hook.remove({ type: "before", pattern: "math.*" });
299
-
300
- // Remove all
301
- api.slothlet.hook.clear();
302
-
303
- // List hooks
304
- const all = api.slothlet.hook.list();
305
- const active = api.slothlet.hook.list({ enabled: true });
306
-
307
- // Enable / disable without unregistering
308
- api.slothlet.hook.disable(); // disable all
309
- api.slothlet.hook.disable({ pattern: "math.*" });
310
- api.slothlet.hook.enable(); // re-enable all
311
- api.slothlet.hook.enable({ type: "before" });
312
- ```
313
-
314
- ---
315
-
316
- ## 🔄 Per-Request Context
317
-
318
- Execute functions with temporary merged context using `api.slothlet.context`:
319
-
320
- ```js
321
- const api = await slothlet({
322
- dir: "./api",
323
- context: { appName: "MyApp", version: "3.0" }
324
- });
325
-
326
- // run() - execute a function inside a scoped context
327
- await api.slothlet.context.run({ userId: "alice", role: "admin" }, async () => {
328
- // Inside this scope: context = { appName, version, userId, role }
329
- await api.database.query();
330
- await api.audit.log();
331
- });
332
-
333
- // scope() - return a new API object with merged context
334
- const scopedApi = api.slothlet.context.scope({ userId: "bob" });
335
- await scopedApi.database.query(); // context includes userId: "bob"
336
- ```
337
-
338
- ### Deep Merge Strategy
339
-
340
- ```js
341
- // Default: shallow merge (top-level properties replaced)
342
- await api.slothlet.context.run({ newProp: "value" }, handler);
343
-
344
- // Deep merge: nested objects recursively merged
345
- await api.slothlet.context.run(
346
- { nested: { prop: "value" } },
347
- handler,
348
- { mergeStrategy: "deep" }
349
- );
350
- ```
351
-
352
- ### Automatic EventEmitter Context Propagation
353
-
354
- Context propagates automatically through EventEmitter callbacks:
355
-
356
- ```js
357
- import net from "net";
358
- import { context } from "@cldmv/slothlet/runtime";
359
-
360
- export const server = {
361
- async start() {
362
- const tcpServer = net.createServer((socket) => {
363
- // Context automatically available in connection handler
364
- console.log(`User ${context.userId} connected`);
365
-
366
- socket.on("data", (data) => {
367
- // Context preserved in nested event callbacks
368
- console.log(`Data from ${context.userId}: ${data}`);
369
- });
370
- });
371
- tcpServer.listen(3000);
372
- }
373
- };
374
- ```
375
-
376
- Works with: TCP servers, HTTP servers, custom EventEmitters, unlimited nested callbacks.
377
-
378
- > 📖 See [`docs/CONTEXT-PROPAGATION.md`](docs/CONTEXT-PROPAGATION.md) for full documentation.
379
-
380
- ---
381
-
382
- ## 🏷️ Metadata System
383
-
384
- Tag API paths with metadata for authorization, auditing, and security. See [`docs/METADATA.md`](docs/METADATA.md) for the full reference.
385
-
386
- ```js
387
- // Set metadata when loading (via api.slothlet.api.add)
388
- await api.slothlet.api.add("plugins/trusted", "./trusted-dir", {
389
- metadata: { trusted: true, securityLevel: "high" }
390
- });
391
-
392
- // Set metadata at runtime
393
- api.slothlet.metadata.set("plugins.trusted.someFunc", { version: 2 });
394
- api.slothlet.metadata.setGlobal({ environment: "production" });
395
- api.slothlet.metadata.setFor("plugins/trusted", { owner: "core-team" });
396
-
397
- // Read metadata inside a module
398
- import { self } from "@cldmv/slothlet/runtime";
399
-
400
- export const secureOperation = {
401
- async execute() {
402
- // Access metadata via api.slothlet.metadata from within a module via self
403
- // Or read it externally:
404
- // const meta = api.slothlet.metadata.get("plugins.trusted.execute");
405
- return "Authorized execution";
406
- }
407
- };
408
- ```
409
-
410
- ---
411
-
412
- ## 🌍 Environment Snapshot (v3.1+)
413
-
414
- Slothlet captures a **frozen snapshot of `process.env` at init time** and exposes it at `api.slothlet.env`. The snapshot is deeply read-only — mutating `process.env` after init does not affect `api.slothlet.env`.
415
-
416
- ```js
417
- const api = await slothlet({
418
- dir: "./api",
419
- env: true,
420
- // env: { include: ["NODE_ENV", "DATABASE_URL", "PORT"] } // allowlist
421
- });
422
-
423
- // Access inside any module:
424
- import { self } from "@cldmv/slothlet/runtime";
425
-
426
- export const getConfig = () => ({
427
- mode: self.slothlet.env.NODE_ENV,
428
- dbUrl: self.slothlet.env.DATABASE_URL
429
- });
430
- ```
431
-
432
- **Key behaviors:**
433
- - `env: true` → all `process.env` variables are captured
434
- - `env: { include: [...] }` → only the listed keys are captured (recommended for security)
435
- - `api.slothlet.env` is a frozen object — writes throw in strict mode
436
- - Snapshot is taken at init time — late `process.env` mutations are NOT reflected
437
-
438
- ---
439
-
440
- ## 🔁 Hot Reload / Dynamic API Management
441
-
442
- ```js
443
- // Add new modules at runtime
444
- await api.slothlet.api.add("newModule", "./new-module-path");
445
- await api.slothlet.api.add("plugins", "./plugins", { collision: "merge" });
446
-
447
- // Remove modules by path
448
- await api.slothlet.api.remove("oldModule");
449
-
450
- // Reload all modules
451
- await api.slothlet.reload();
452
-
453
- // Reload specific API path
454
- await api.slothlet.api.reload("database.*");
455
- await api.slothlet.api.reload("plugins.auth");
456
- ```
457
-
458
- > **Lazy mode reload behavior**: In lazy mode, reload restores modules to an unmaterialized proxy state - existing references to lazy wrappers are intentionally not preserved. Eager mode merges new module exports into the existing live wrapper, preserving references.
459
-
460
- > 📖 See [`docs/RELOAD.md`](docs/RELOAD.md) for reload system documentation.
461
-
462
- ---
463
-
464
- ## ⚡ Lifecycle Events
465
-
466
- Subscribe to internal API events via `api.slothlet.lifecycle`:
467
-
468
- ```js
469
- // Available events
470
- api.slothlet.lifecycle.on("materialized:complete", (data) => {
471
- console.log(`${data.total} modules materialized`);
472
- });
473
-
474
- api.slothlet.lifecycle.on("impl:changed", (data) => {
475
- console.log(`Module at ${data.apiPath} was reloaded`);
476
- });
477
-
478
- // Unsubscribe
479
- const handler = (data) => console.log(data);
480
- api.slothlet.lifecycle.on("materialized:complete", handler);
481
- api.slothlet.lifecycle.off("materialized:complete", handler);
482
- ```
483
-
484
- **Available events**: `"materialized:complete"`, `"impl:created"`, `"impl:changed"`, `"impl:removed"`
485
-
486
- ---
487
-
488
- ## 🔀 API Path Versioning (v3.2+)
489
-
490
- Mount multiple versions of the same logical path and dispatch to the correct version automatically based on the caller's version metadata.
491
-
492
- ### Setup
493
-
494
- ```js
495
- const api = await slothlet({
496
- dir: "./api",
497
- versionDispatcher: "version" // use caller's versionMetadata.version field
498
- // versionDispatcher: (allVersions, caller) => { ... } // custom function
499
- });
500
-
501
- // Register versioned modules — 4th argument is versionConfig
502
- await api.slothlet.api.add("auth", "./api/v1", {}, { version: "v1", default: true });
503
- await api.slothlet.api.add("auth", "./api/v2", {}, { version: "v2" });
504
- ```
505
-
506
- ### Access Patterns
507
-
508
- ```js
509
- // Dispatcher — routes via discriminator (use this in most cases)
510
- api.auth.login(user, pass) // → routes to v1 or v2 based on caller metadata
511
-
512
- // Direct versioned access — bypasses dispatcher entirely
513
- api.v1.auth.login(user, pass) // → always v1
514
- api.v2.auth.login(user, pass) // → always v2
515
- ```
516
-
517
- ### Attaching Version Metadata to a Caller
518
-
519
- ```js
520
- // Register a module WITH a version tag so the discriminator can use it
521
- await api.slothlet.api.add("services/payments", "./payments", {}, {
522
- version: "v2",
523
- metadata: { stable: true } // versionConfig.metadata — stored in VersionManager only
524
- });
525
-
526
- // options.metadata (3rd arg) → regular Metadata system (metadata.caller() etc.)
527
- // versionConfig.metadata (4th arg) → version-system only, used by the discriminator
528
- ```
529
-
530
- ### Runtime Version Management
531
-
532
- ```js
533
- // List registered versions for a path
534
- api.slothlet.versioning.list("auth");
535
- // → { versions: { v1: { ... }, v2: { ... } }, default: "v2" }
536
-
537
- // Change the default dispatcher fallback
538
- api.slothlet.versioning.setDefault("auth", "v1");
539
-
540
- // Unregister a version (removes api.v1.auth; dispatcher updates automatically)
541
- await api.slothlet.versioning.unregister("auth", "v1");
542
-
543
- // Read version metadata stored at registration
544
- api.slothlet.versioning.getVersionMetadata(moduleID);
545
- ```
546
-
547
- > 📖 See [`docs/VERSIONING.md`](docs/VERSIONING.md) for full documentation.
548
-
549
- ---
550
-
551
- ## 📁 File Organization Best Practices
552
-
553
- ### ✅ Clean Folder Structure
554
-
555
- ```text
556
- api/
557
- ├── config.mjs → api.config.*
558
- ├── math/
559
- │ └── math.mjs → api.math.* (flattened - filename matches folder)
560
- ├── util/
561
- │ ├── util.mjs → api.util.* (flattened methods)
562
- │ ├── extract.mjs → api.util.extract.*
563
- │ └── controller.mjs → api.util.controller.*
564
- ├── nested/
565
- │ └── date/
566
- │ └── date.mjs → api.nested.date.*
567
- └── multi/
568
- ├── alpha.mjs → api.multi.alpha.*
569
- └── beta.mjs → api.multi.beta.*
570
- ```
571
-
572
- ### ✅ Naming Conventions
573
-
574
- - **Filename matches folder** → Auto-flattening (`math/math.mjs` → `api.math.*`)
575
- - **Different filename** → Nested structure preserved
576
- - **Dash-separated names** → camelCase API (`auto-ip.mjs` → `api.autoIP`)
577
- - **Function name preferred** → Original capitalization kept over sanitized form
578
- (see [Rule 9](docs/API-RULES.md))
579
-
580
- ---
581
-
582
- ## 🚨 Common AI Agent Mistakes
583
-
584
- ### ❌ Mistake 1: Cross-Module Imports
585
-
586
- ```js
587
- // ❌ WRONG
588
- import { config } from "./config.mjs";
589
- // ✅ CORRECT
590
- import { self } from "@cldmv/slothlet/runtime";
591
- // then: self.config.get(...)
592
- ```
593
-
594
- ### ❌ Mistake 2: Using V2 API Surface
595
-
596
- ```js
597
- // ❌ WRONG (v2 API - does not exist in v3)
598
- await api.addApi("plugins", "./dir");
599
- await api.reloadApi("math.*");
600
- api.hooks.on("validate", "before", handler, { pattern: "math.*" });
601
- await api.run({ userId: "alice" }, fn);
602
-
603
- // ✅ CORRECT (v3 API)
604
- await api.slothlet.api.add("plugins", "./dir");
605
- await api.slothlet.api.reload("math.*");
606
- api.slothlet.hook.on("before:math.*", handler);
607
- await api.slothlet.context.run({ userId: "alice" }, fn);
608
- ```
609
-
610
- ### ❌ Mistake 3: Wrong Hook Config Key
611
-
612
- ```js
613
- // ❌ WRONG
614
- const api = await slothlet({ dir: "./api", hooks: true }); // "hooks" plural
615
-
616
- // ✅ CORRECT
617
- const api = await slothlet({ dir: "./api", hook: true }); // "hook" singular
618
- ```
619
-
620
- ### ❌ Mistake 4: Breaking Auto-Flattening
621
-
622
- ```js
623
- // File: math/calculator.mjs (different name from folder)
624
- export const math = { /* methods */ };
625
- // Result: api.math.calculator.math.* ← extra nesting, not flattened
626
-
627
- // ✅ CORRECT: File math/math.mjs
628
- export const math = { /* methods */ };
629
- // Result: api.math.* ← flattened
630
- ```
631
-
632
- ### ❌ Mistake 5: Using lifecycle.subscribe / lifecycle.emit
633
-
634
- ```js
635
- // ❌ WRONG - subscribe/emit are internal
636
- api.slothlet.lifecycle.subscribe("materialized:complete", handler);
637
- api.slothlet.lifecycle.emit("impl:changed", data);
638
-
639
- // ✅ CORRECT - public surface is on/off only
640
- api.slothlet.lifecycle.on("materialized:complete", handler);
641
- api.slothlet.lifecycle.off("materialized:complete", handler);
642
- ```
643
-
644
- ### ❌ Mistake 6: Assuming `api.v1.auth` Goes Through the Dispatcher
645
-
646
- ```js
647
- // ❌ WRONG — direct versioned path bypasses the discriminator entirely
648
- api.v1.auth.login(user, pass); // always v1, no routing logic
649
-
650
- // ✅ CORRECT — use the logical dispatcher path for dynamic routing
651
- api.auth.login(user, pass); // routes based on caller version metadata
652
- ```
653
-
654
- ### ❌ Mistake 7: Wrong Config Key for Versioning
655
-
656
- ```js
657
- // ❌ WRONG
658
- const api = await slothlet({ dir: "./api", versionResolver: "version" });
659
-
660
- // ✅ CORRECT
661
- const api = await slothlet({ dir: "./api", versionDispatcher: "version" });
662
- ```
663
-
664
- ### ❌ Mistake 8: Conflating versionConfig.metadata with options.metadata
665
-
666
- ```js
667
- // ❌ WRONG — puts version tag in the regular Metadata system instead of VersionManager
668
- await api.slothlet.api.add("auth", "./v2", { metadata: { version: "v2" } });
669
-
670
- // ✅ CORRECT
671
- // options.metadata (3rd arg) → regular Metadata system (metadata.caller() etc.)
672
- // versionConfig.metadata (4th arg) → VersionManager only, used by the discriminator
673
- await api.slothlet.api.add("auth", "./v2",
674
- { metadata: { role: "core" } },
675
- { version: "v2", metadata: { stable: true } }
676
- );
677
- ```
678
-
679
- ---
680
-
681
- ## ✅ AI Agent Checklist
682
-
683
- - [ ] **No cross-module imports** - use `self` from `@cldmv/slothlet/runtime` instead
684
- - [ ] **Match filename to folder** for cleaner APIs (auto-flattening)
685
- - [ ] **Hook config key is `hook:` (singular)**, not `hooks:`
686
- - [ ] **Hook API** is `api.slothlet.hook.*`, not `api.hooks.*`
687
- - [ ] **Context API** is `api.slothlet.context.run/scope()`, not `api.run/scope()`
688
- - [ ] **Reload/add/remove** is `api.slothlet.api.add/remove/reload()`, not `api.addApi()` etc.
689
- - [ ] **Lifecycle** uses `api.slothlet.lifecycle.on/off()` only
690
- - [ ] **Lazy mode**: if using background materialization, use `api.slothlet.materialize.wait()` before accessing the API
691
- - [ ] **Hook subsets**: auth/security → `subset: "before"`, main logic → `"primary"`, audit → `"after"`
692
- - [ ] **Versioning config key is `versionDispatcher:`**, not `versionResolver:` or `versionDiscriminator:`
693
- - [ ] **Use `api.auth` (dispatcher path) for dynamic routing**, not `api.v1.auth` (direct path bypasses discriminator)
694
- - [ ] **`versionConfig.metadata` (4th arg)** and **`options.metadata` (3rd arg)** are separate systems — don't conflate them
695
- - [ ] **Double quotes everywhere** - follow Slothlet coding standards
696
-
697
- ---
698
-
699
- ## 📚 Reference Examples
700
-
701
- - **Auto-flattening**: `api_tests/api_test/math/math.mjs`
702
- - **Multi-file folders**: `api_tests/api_test/multi/`
703
- - **Cross-module calls**: `api_tests/api_test_mixed/`
704
- - **Root-level APIs**: `api_tests/api_test/root-function.mjs`
705
- - **Nested structures**: `api_tests/api_test/nested/date/`
706
-
707
- ---
708
-
709
- ## 📖 Essential Documentation
710
-
711
- ### Core Architecture
712
-
713
- - **[`docs/API-RULES.md`](docs/API-RULES.md)** - All 13 API transformation rules
714
- - **[`docs/API-RULES/API-RULES-CONDITIONS.md`](docs/API-RULES/API-RULES-CONDITIONS.md)** - All C01–C34 conditional logic
715
- - **[`docs/API-RULES/API-FLATTENING.md`](docs/API-RULES/API-FLATTENING.md)** - Flattening rules F01–F08
716
-
717
- ### Configuration & Features
718
-
719
- - **[`docs/CONFIGURATION.md`](docs/CONFIGURATION.md)** - All config options
720
- - **[`docs/HOOKS.md`](docs/HOOKS.md)** - Hook system (types, subsets, patterns, management)
721
- - **[`docs/METADATA.md`](docs/METADATA.md)** - Metadata system
722
- - **[`docs/CONTEXT-PROPAGATION.md`](docs/CONTEXT-PROPAGATION.md)** - Per-request context and EventEmitter propagation
723
- - **[`docs/RELOAD.md`](docs/RELOAD.md)** - Hot reload and dynamic API management
724
- - **[`docs/LIFECYCLE.md`](docs/LIFECYCLE.md)** - Lazy mode, materialization, and lifecycle events
725
- - **[`docs/SANITIZATION.md`](docs/SANITIZATION.md)** - Property name sanitization rules
726
- - **[`docs/I18N.md`](docs/I18N.md)** - Internationalization and language support
727
- - **[`docs/PERFORMANCE.md`](docs/PERFORMANCE.md)** - Performance characteristics and benchmarks
728
-
729
- ### Critical Reading Order for AI Agents
730
-
731
- 1. **This file** - Prevents architectural mistakes
732
- 2. **[`README.md`](README.md)** - Project overview and quickstart
733
- 3. **[`docs/API-RULES.md`](docs/API-RULES.md)** - API transformation rules
734
- 4. **[`docs/HOOKS.md`](docs/HOOKS.md)** - Hook system (if needed)
735
- 5. **[`docs/METADATA.md`](docs/METADATA.md)** - Metadata system (if needed)
736
- 6. **[`api_tests/api_test/README.md`](api_tests/api_test/README.md)** - Live examples