@cldmv/slothlet 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +9 -10
  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 -1385
  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 -2536
  12. package/dist/lib/handlers/context-async.mjs +1 -172
  13. package/dist/lib/handlers/context-live.mjs +1 -173
  14. package/dist/lib/handlers/hook-manager.mjs +1 -667
  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 +1 -392
  21. package/dist/lib/handlers/unified-wrapper.mjs +1 -3110
  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 -439
  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 +1 -125
  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 +1 -0
  33. package/dist/lib/i18n/languages/en-gb.json +1 -0
  34. package/dist/lib/i18n/languages/en-us.json +1 -0
  35. package/dist/lib/i18n/languages/es-es.json +412 -0
  36. package/dist/lib/i18n/languages/es-mx.json +1 -0
  37. package/dist/lib/i18n/languages/fr-fr.json +1 -0
  38. package/dist/lib/i18n/languages/hi-in.json +2 -1
  39. package/dist/lib/i18n/languages/ja-jp.json +1 -0
  40. package/dist/lib/i18n/languages/ko-kr.json +1 -0
  41. package/dist/lib/i18n/languages/pt-br.json +21 -20
  42. package/dist/lib/i18n/languages/ru-ru.json +2 -1
  43. package/dist/lib/i18n/languages/zh-cn.json +6 -5
  44. package/dist/lib/i18n/translations.mjs +1 -126
  45. package/dist/lib/modes/eager.mjs +1 -59
  46. package/dist/lib/modes/lazy.mjs +1 -81
  47. package/dist/lib/processors/flatten.mjs +1 -437
  48. package/dist/lib/processors/loader.mjs +1 -339
  49. package/dist/lib/processors/type-generator.mjs +1 -275
  50. package/dist/lib/processors/typescript.mjs +1 -172
  51. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +1 -113
  52. package/dist/lib/runtime/runtime-livebindings.mjs +1 -78
  53. package/dist/lib/runtime/runtime.mjs +1 -102
  54. package/dist/slothlet.mjs +1 -817
  55. package/package.json +35 -31
  56. package/types/dist/lib/builders/api-assignment.d.mts +3 -92
  57. package/types/dist/lib/builders/api-assignment.d.mts.map +1 -1
  58. package/types/dist/lib/builders/api_builder.d.mts +102 -91
  59. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  60. package/types/dist/lib/builders/builder.d.mts +1 -55
  61. package/types/dist/lib/builders/builder.d.mts.map +1 -1
  62. package/types/dist/lib/builders/modes-processor.d.mts +3 -27
  63. package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
  64. package/types/dist/lib/errors.d.mts +19 -109
  65. package/types/dist/lib/errors.d.mts.map +1 -1
  66. package/types/dist/lib/factories/component-base.d.mts +7 -177
  67. package/types/dist/lib/factories/component-base.d.mts.map +1 -1
  68. package/types/dist/lib/factories/context.d.mts +4 -22
  69. package/types/dist/lib/factories/context.d.mts.map +1 -1
  70. package/types/dist/lib/handlers/api-cache-manager.d.mts +20 -203
  71. package/types/dist/lib/handlers/api-cache-manager.d.mts.map +1 -1
  72. package/types/dist/lib/handlers/api-manager.d.mts +34 -408
  73. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
  74. package/types/dist/lib/handlers/context-async.d.mts +23 -61
  75. package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
  76. package/types/dist/lib/handlers/context-live.d.mts +22 -59
  77. package/types/dist/lib/handlers/context-live.d.mts.map +1 -1
  78. package/types/dist/lib/handlers/hook-manager.d.mts +46 -185
  79. package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -1
  80. package/types/dist/lib/handlers/lifecycle-token.d.mts +3 -48
  81. package/types/dist/lib/handlers/lifecycle-token.d.mts.map +1 -1
  82. package/types/dist/lib/handlers/lifecycle.d.mts +5 -82
  83. package/types/dist/lib/handlers/lifecycle.d.mts.map +1 -1
  84. package/types/dist/lib/handlers/materialize-manager.d.mts +8 -70
  85. package/types/dist/lib/handlers/materialize-manager.d.mts.map +1 -1
  86. package/types/dist/lib/handlers/metadata.d.mts +17 -221
  87. package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
  88. package/types/dist/lib/handlers/ownership.d.mts +44 -160
  89. package/types/dist/lib/handlers/ownership.d.mts.map +1 -1
  90. package/types/dist/lib/handlers/permission-manager.d.mts +40 -141
  91. package/types/dist/lib/handlers/permission-manager.d.mts.map +1 -1
  92. package/types/dist/lib/handlers/unified-wrapper.d.mts +26 -239
  93. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
  94. package/types/dist/lib/handlers/version-manager.d.mts +28 -225
  95. package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
  96. package/types/dist/lib/helpers/class-instance-wrapper.d.mts +2 -52
  97. package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -1
  98. package/types/dist/lib/helpers/config.d.mts +125 -139
  99. package/types/dist/lib/helpers/config.d.mts.map +1 -1
  100. package/types/dist/lib/helpers/eventemitter-context.d.mts +3 -29
  101. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
  102. package/types/dist/lib/helpers/hint-detector.d.mts +2 -15
  103. package/types/dist/lib/helpers/hint-detector.d.mts.map +1 -1
  104. package/types/dist/lib/helpers/modes-utils.d.mts +3 -30
  105. package/types/dist/lib/helpers/modes-utils.d.mts.map +1 -1
  106. package/types/dist/lib/helpers/pattern-matcher.d.mts +3 -43
  107. package/types/dist/lib/helpers/pattern-matcher.d.mts.map +1 -1
  108. package/types/dist/lib/helpers/resolve-from-caller.d.mts +3 -27
  109. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  110. package/types/dist/lib/helpers/sanitize.d.mts +4 -92
  111. package/types/dist/lib/helpers/sanitize.d.mts.map +1 -1
  112. package/types/dist/lib/helpers/utilities.d.mts +4 -52
  113. package/types/dist/lib/helpers/utilities.d.mts.map +1 -1
  114. package/types/dist/lib/i18n/translations.d.mts +4 -37
  115. package/types/dist/lib/i18n/translations.d.mts.map +1 -1
  116. package/types/dist/lib/modes/eager.d.mts +8 -30
  117. package/types/dist/lib/modes/eager.d.mts.map +1 -1
  118. package/types/dist/lib/modes/lazy.d.mts +10 -43
  119. package/types/dist/lib/modes/lazy.d.mts.map +1 -1
  120. package/types/dist/lib/processors/flatten.d.mts +56 -107
  121. package/types/dist/lib/processors/flatten.d.mts.map +1 -1
  122. package/types/dist/lib/processors/loader.d.mts +6 -41
  123. package/types/dist/lib/processors/loader.d.mts.map +1 -1
  124. package/types/dist/lib/processors/type-generator.d.mts +2 -16
  125. package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
  126. package/types/dist/lib/processors/typescript.d.mts +6 -53
  127. package/types/dist/lib/processors/typescript.d.mts.map +1 -1
  128. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +3 -71
  129. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  130. package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -37
  131. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  132. package/types/dist/lib/runtime/runtime.d.mts +3 -39
  133. package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
  134. package/types/dist/slothlet.d.mts +3 -249
  135. package/types/dist/slothlet.d.mts.map +1 -1
  136. package/types/index.d.mts +36 -16
  137. package/types/index.d.mts.map +1 -0
  138. package/AGENT-USAGE.md +0 -736
  139. package/docs/API-RULES.md +0 -712
@@ -14,670 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
-
18
-
19
-
20
-
21
- import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
22
- import { compilePattern } from "@cldmv/slothlet/helpers/pattern-matcher";
23
-
24
-
25
-
26
-
27
- const ERROR_HOOK_PROCESSED = Symbol.for("@cldmv/slothlet/hook-error-processed");
28
-
29
-
30
- export class HookManager extends ComponentBase {
31
-
32
- static slothletProperty = "hookManager";
33
-
34
-
35
- #hooks = {
36
- before: { before: {}, primary: {}, after: {} },
37
- after: { before: {}, primary: {}, after: {} },
38
- always: { before: {}, primary: {}, after: {} },
39
- error: { before: {}, primary: {}, after: {} }
40
- };
41
-
42
-
43
- #byId = new Map();
44
-
45
-
46
- #idCounter = 0;
47
-
48
-
49
- #validTypes = new Set(["before", "after", "always", "error"]);
50
-
51
-
52
- #validSubsets = new Set(["before", "primary", "after"]);
53
-
54
-
55
- constructor(slothlet) {
56
- super(slothlet);
57
-
58
-
59
- const hookConfig = slothlet.config?.hook || { enabled: false, pattern: "**", suppressErrors: false };
60
-
61
- this.enabled = hookConfig.enabled;
62
- this.defaultPattern = hookConfig.pattern || "**";
63
- this.suppressErrors = hookConfig.suppressErrors || false;
64
- this.enabledPatterns = new Set();
65
- this.patternFilterActive = false;
66
-
67
- this.hooks = new Map();
68
- this.registrationOrder = 0;
69
- this.reportedErrors = new WeakSet();
70
- }
71
-
72
-
73
- on(typePattern, handler, options = {}) {
74
-
75
- let { type, pattern } = this.#parseTypePattern(typePattern);
76
-
77
-
78
- if (options.pattern !== undefined) {
79
- pattern = options.pattern;
80
- }
81
-
82
-
83
- if (!this.#validTypes.has(type)) {
84
- throw new this.slothlet.SlothletError("INVALID_HOOK_TYPE", {
85
- type,
86
- validTypes: Array.from(this.#validTypes)
87
- });
88
- }
89
-
90
-
91
- if (typeof handler !== "function") {
92
- throw new this.slothlet.SlothletError("INVALID_HOOK_HANDLER", {
93
- receivedType: typeof handler,
94
- validationError: true
95
- });
96
- }
97
-
98
-
99
- const id = options.id || this.#generateId();
100
-
101
-
102
- if (this.#byId.has(id)) {
103
- throw new this.slothlet.SlothletError("DUPLICATE_HOOK_ID", { id, validationError: true });
104
- }
105
-
106
-
107
- const subset = options.subset || "primary";
108
- if (!this.#validSubsets.has(subset)) {
109
- throw new this.slothlet.SlothletError("INVALID_HOOK_SUBSET", {
110
- subset,
111
- validSubsets: Array.from(this.#validSubsets)
112
- });
113
- }
114
-
115
-
116
- this.#compilePattern(pattern);
117
-
118
-
119
- const hook = {
120
- id,
121
- type,
122
- pattern,
123
- handler,
124
- priority: options.priority || 0,
125
- subset,
126
- enabled: true,
127
- _compiled: null
128
- };
129
-
130
-
131
- const typeIndex = this.#hooks[type];
132
- const subsetIndex = typeIndex[subset];
133
-
134
- if (!subsetIndex[pattern]) {
135
- subsetIndex[pattern] = [];
136
- }
137
- subsetIndex[pattern].push(hook);
138
-
139
-
140
- this.#byId.set(id, hook);
141
-
142
- return id;
143
- }
144
-
145
-
146
- remove(filter = {}) {
147
- let removed = 0;
148
-
149
-
150
- if (filter.id) {
151
- const hook = this.#byId.get(filter.id);
152
- if (hook) {
153
- this.#removeHook(hook);
154
- removed = 1;
155
- }
156
- return removed;
157
- }
158
-
159
-
160
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
161
-
162
- for (const type of types) {
163
- const typeIndex = this.#hooks[type];
164
-
165
- for (const subset of ["before", "primary", "after"]) {
166
- const subsetIndex = typeIndex[subset];
167
-
168
-
169
- if (filter.pattern) {
170
- const patternHooks = subsetIndex[filter.pattern];
171
- if (patternHooks) {
172
- removed += patternHooks.length;
173
- patternHooks.forEach((hook) => this.#byId.delete(hook.id));
174
- delete subsetIndex[filter.pattern];
175
- }
176
- } else {
177
-
178
- for (const pattern in subsetIndex) {
179
- const patternHooks = subsetIndex[pattern];
180
- removed += patternHooks.length;
181
- patternHooks.forEach((hook) => this.#byId.delete(hook.id));
182
- delete subsetIndex[pattern];
183
- }
184
- }
185
- }
186
- }
187
-
188
- return removed;
189
- }
190
-
191
-
192
- enable(filter = {}) {
193
-
194
- if (typeof filter === "string") {
195
- filter = { pattern: filter };
196
- }
197
-
198
-
199
- this.enabled = true;
200
-
201
- return this.#setEnabledState(filter, true);
202
- }
203
-
204
-
205
- disable(filter = {}) {
206
-
207
- if (typeof filter === "string") {
208
- filter = { pattern: filter };
209
- }
210
-
211
-
212
- if (Object.keys(filter).length === 0) {
213
- this.enabled = false;
214
- }
215
- return this.#setEnabledState(filter, false);
216
- }
217
-
218
-
219
- list(filter = {}) {
220
-
221
- if (typeof filter === "string") {
222
-
223
- if (this.#validTypes.has(filter)) {
224
- filter = { type: filter };
225
- } else {
226
-
227
- filter = { pattern: filter };
228
- }
229
- }
230
-
231
- const hooks = [];
232
-
233
-
234
- if (filter.id) {
235
- const hook = this.#byId.get(filter.id);
236
- if (hook && (filter.enabled === undefined || hook.enabled === filter.enabled)) {
237
- hooks.push(this.#serializeHook(hook));
238
- }
239
- return { registeredHooks: hooks };
240
- }
241
-
242
-
243
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
244
-
245
-
246
- let patternMatcher = null;
247
- if (filter.pattern) {
248
- patternMatcher = this.#compilePattern(filter.pattern);
249
- }
250
-
251
- for (const type of types) {
252
- const typeIndex = this.#hooks[type];
253
-
254
- for (const subset of ["before", "primary", "after"]) {
255
- const subsetIndex = typeIndex[subset];
256
-
257
-
258
- for (const pattern in subsetIndex) {
259
- const patternHooks = subsetIndex[pattern];
260
- for (const hook of patternHooks) {
261
-
262
- if (filter.enabled !== undefined && hook.enabled !== filter.enabled) {
263
- continue;
264
- }
265
-
266
-
267
- if (patternMatcher && !patternMatcher(hook.pattern)) {
268
- continue;
269
- }
270
-
271
- hooks.push(this.#serializeHook(hook));
272
- }
273
- }
274
- }
275
- }
276
-
277
- return { registeredHooks: hooks };
278
- }
279
-
280
-
281
- getHooksForPath(type, apiPath) {
282
-
283
- if (this.enabled === false) {
284
- return [];
285
- }
286
-
287
- const typeIndex = this.#hooks[type];
288
- if (!typeIndex) {
289
- return [];
290
- }
291
-
292
- const hooks = [];
293
-
294
-
295
- for (const subset of ["before", "primary", "after"]) {
296
- const subsetIndex = typeIndex[subset];
297
- const subsetHooks = [];
298
-
299
-
300
- for (const pattern in subsetIndex) {
301
- const patternHooks = subsetIndex[pattern];
302
-
303
-
304
- if (pattern === apiPath) {
305
- subsetHooks.push(...patternHooks.filter((h) => h.enabled));
306
- continue;
307
- }
308
-
309
-
310
- for (const hook of patternHooks) {
311
- if (!hook.enabled) continue;
312
-
313
- if (!hook._compiled) {
314
- hook._compiled = this.#compilePattern(hook.pattern);
315
- }
316
-
317
- if (hook._compiled(apiPath)) {
318
- subsetHooks.push(hook);
319
- }
320
- }
321
- }
322
-
323
-
324
- subsetHooks.sort((a, b) => b.priority - a.priority);
325
-
326
-
327
- hooks.push(...subsetHooks);
328
- }
329
-
330
- return hooks;
331
- }
332
-
333
-
334
- executeBeforeHooks(path, args, api, ctx) {
335
- const hooks = this.getHooksForPath("before", path);
336
-
337
- for (const hook of hooks) {
338
- try {
339
- const result = hook.handler({ path, args, api, ctx });
340
-
341
-
342
- if (result && typeof result === "object" && typeof result.then === "function") {
343
- throw new this.SlothletError("HOOK_BEFORE_RETURNED_PROMISE", { id: hook.id, path }, null, { validationError: true });
344
- }
345
-
346
-
347
- if (result !== undefined && !Array.isArray(result)) {
348
- return { args, shortCircuit: true, value: result };
349
- }
350
-
351
-
352
- if (Array.isArray(result)) {
353
- args = result;
354
- }
355
- } catch (error) {
356
-
357
- const sourceInfo = {
358
- type: "before",
359
- subset: hook.subset,
360
- hookTag: hook.id,
361
- hookId: hook.id,
362
- timestamp: Date.now(),
363
- stack: error.stack
364
- };
365
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
366
-
367
- if (!this.suppressErrors) {
368
- throw error;
369
- }
370
-
371
- return { args, shortCircuit: true, value: undefined };
372
- }
373
- }
374
-
375
- return { args, shortCircuit: false };
376
- }
377
-
378
-
379
- executeAfterHooks(path, result, args, api, ctx) {
380
- const hooks = this.getHooksForPath("after", path);
381
- const originalResult = result;
382
- let currentResult = result;
383
-
384
- for (const hook of hooks) {
385
- try {
386
- const hookContext = {
387
- path,
388
- args,
389
- result: currentResult,
390
- api,
391
- ctx
392
- };
393
- const transformed = hook.handler(hookContext);
394
-
395
-
396
- if (transformed !== undefined) {
397
- currentResult = transformed;
398
- }
399
- } catch (error) {
400
-
401
- const sourceInfo = {
402
- type: "after",
403
- subset: hook.subset,
404
- hookTag: hook.id,
405
- hookId: hook.id,
406
- timestamp: Date.now(),
407
- stack: error.stack
408
- };
409
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
410
-
411
- if (!this.suppressErrors) {
412
- throw error;
413
- }
414
- }
415
- }
416
-
417
-
418
- if (currentResult === originalResult) {
419
- return { modified: false };
420
- } else {
421
- return { modified: true, result: currentResult };
422
- }
423
- }
424
-
425
-
426
- executeAlwaysHooks(path, args, resultOrError, hasError = false, errors = [], api, ctx) {
427
- const hooks = this.getHooksForPath("always", path);
428
-
429
- for (const hook of hooks) {
430
- try {
431
- hook.handler({
432
- path,
433
- args,
434
- result: hasError ? undefined : resultOrError,
435
- hasError,
436
- errors: errors,
437
- api,
438
- ctx
439
- });
440
- } catch (error) {
441
-
442
- const sourceInfo = {
443
- type: "always",
444
- subset: hook.subset,
445
- hookTag: hook.id,
446
- hookId: hook.id,
447
- timestamp: Date.now(),
448
- stack: error.stack
449
- };
450
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
451
- }
452
- }
453
- }
454
-
455
-
456
- executeErrorHooks(path, error, source, args, api, ctx) {
457
-
458
- if (error && typeof error === "object") {
459
- error[ERROR_HOOK_PROCESSED] = true;
460
- }
461
-
462
- const hooks = this.getHooksForPath("error", path);
463
-
464
- for (const hook of hooks) {
465
- try {
466
- hook.handler({
467
- path,
468
- args,
469
- error,
470
- errorType: error?.constructor?.name || "Error",
471
- source,
472
- timestamp: new Date(),
473
- api,
474
- ctx
475
- });
476
- } catch (hookError) {
477
-
478
- this.slothlet.debug("hooks", `Error hook failed for ${path}:`, hookError);
479
- }
480
- }
481
- }
482
-
483
-
484
- #parseTypePattern(typePattern) {
485
- if (typeof typePattern !== "string") {
486
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
487
- typePattern,
488
- expected: "string in format 'type:pattern'"
489
- });
490
- }
491
-
492
- const colonIndex = typePattern.indexOf(":");
493
- if (colonIndex === -1) {
494
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
495
- typePattern,
496
- expected: "string in format 'type:pattern' with at least one colon"
497
- });
498
- }
499
-
500
- const type = typePattern.substring(0, colonIndex);
501
- const pattern = typePattern.substring(colonIndex + 1);
502
-
503
- if (!type || !pattern) {
504
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
505
- typePattern,
506
- expected: "non-empty type and pattern"
507
- });
508
- }
509
-
510
- return { type, pattern };
511
- }
512
-
513
-
514
- #compilePattern(pattern) {
515
- return compilePattern(pattern, {
516
- onMaxDepth: (maxDepth) => {
517
- throw new this.SlothletError("HOOK_BRACE_EXPANSION_MAX_DEPTH", { maxDepth }, null, { validationError: true });
518
- }
519
- });
520
- }
521
-
522
-
523
- getCompilePatternForDiagnostics() {
524
- return this.#compilePattern.bind(this);
525
- }
526
-
527
-
528
- #generateId() {
529
- return `hook-${++this.#idCounter}`;
530
- }
531
-
532
-
533
- #removeHook(hook) {
534
- const typeIndex = this.#hooks[hook.type];
535
- const subsetIndex = typeIndex[hook.subset];
536
- const patternHooks = subsetIndex[hook.pattern];
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
- if (!patternHooks) {
546
- throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT", {
547
- hookId: hook.id,
548
- type: hook.type,
549
- subset: hook.subset,
550
- pattern: hook.pattern,
551
- detail: "patternHooks array missing from subsetIndex — #byId and #hooks are desynced"
552
- });
553
- }
554
-
555
- const index = patternHooks.indexOf(hook);
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
- if (index === -1) {
564
- throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT", {
565
- hookId: hook.id,
566
- type: hook.type,
567
- subset: hook.subset,
568
- pattern: hook.pattern,
569
- detail: "hook object not found in patternHooks array — #byId and #hooks are desynced"
570
- });
571
- }
572
-
573
- patternHooks.splice(index, 1);
574
-
575
-
576
- if (patternHooks.length === 0) {
577
- delete subsetIndex[hook.pattern];
578
- }
579
-
580
- this.#byId.delete(hook.id);
581
- }
582
-
583
-
584
- #setEnabledState(filter, enabled) {
585
- let affected = 0;
586
-
587
-
588
- if (filter.id) {
589
- const hook = this.#byId.get(filter.id);
590
- if (hook) {
591
- hook.enabled = enabled;
592
- affected = 1;
593
- }
594
- return affected;
595
- }
596
-
597
-
598
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
599
-
600
- for (const type of types) {
601
- const typeIndex = this.#hooks[type];
602
-
603
- for (const subset of ["before", "primary", "after"]) {
604
- const subsetIndex = typeIndex[subset];
605
-
606
-
607
- if (filter.pattern) {
608
- const patternHooks = subsetIndex[filter.pattern] || [];
609
- for (const hook of patternHooks) {
610
- hook.enabled = enabled;
611
- affected++;
612
- }
613
- } else {
614
-
615
- for (const pattern in subsetIndex) {
616
- const patternHooks = subsetIndex[pattern];
617
- for (const hook of patternHooks) {
618
- hook.enabled = enabled;
619
- affected++;
620
- }
621
- }
622
- }
623
- }
624
- }
625
-
626
- return affected;
627
- }
628
-
629
-
630
- #serializeHook(hook) {
631
- return {
632
- id: hook.id,
633
- type: hook.type,
634
- pattern: hook.pattern,
635
- priority: hook.priority,
636
- subset: hook.subset,
637
- enabled: hook.enabled
638
-
639
- };
640
- }
641
-
642
-
643
- exportHooks() {
644
- const registrations = [];
645
- for (const hook of this.#byId.values()) {
646
- registrations.push({
647
- typePattern: `${hook.type}:${hook.pattern}`,
648
- handler: hook.handler,
649
- options: {
650
- id: hook.id,
651
- priority: hook.priority,
652
- subset: hook.subset
653
- },
654
- enabled: hook.enabled
655
- });
656
- }
657
- return registrations;
658
- }
659
-
660
-
661
- importHooks(registrations) {
662
- if (!Array.isArray(registrations)) return;
663
- for (const reg of registrations) {
664
- this.on(reg.typePattern, reg.handler, reg.options);
665
- if (!reg.enabled) {
666
- this.disable({ id: reg.options.id });
667
- }
668
- }
669
- }
670
-
671
-
672
- async shutdown() {
673
-
674
- this.#hooks = {
675
- before: { before: {}, primary: {}, after: {} },
676
- after: { before: {}, primary: {}, after: {} },
677
- always: { before: {}, primary: {}, after: {} },
678
- error: { before: {}, primary: {}, after: {} }
679
- };
680
- this.#byId.clear();
681
- this.#idCounter = 0;
682
- }
683
- }
17
+ import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";const ERROR_HOOK_PROCESSED=Symbol.for("@cldmv/slothlet/hook-error-processed");class HookManager extends ComponentBase{static slothletProperty="hookManager";#hooks={before:{before:{},primary:{},after:{}},after:{before:{},primary:{},after:{}},always:{before:{},primary:{},after:{}},error:{before:{},primary:{},after:{}}};#byId=new Map;#idCounter=0;#validTypes=new Set(["before","after","always","error"]);#validSubsets=new Set(["before","primary","after"]);constructor(slothlet){super(slothlet);const hookConfig=slothlet.config?.hook||{enabled:false,pattern:"**",suppressErrors:false};this.enabled=hookConfig.enabled;this.defaultPattern=hookConfig.pattern||"**";this.suppressErrors=hookConfig.suppressErrors||false;this.enabledPatterns=new Set;this.patternFilterActive=false;this.hooks=new Map;this.registrationOrder=0;this.reportedErrors=new WeakSet}on(typePattern,handler,options={}){let{type,pattern}=this.#parseTypePattern(typePattern);if(options.pattern!==void 0){pattern=options.pattern}if(!this.#validTypes.has(type)){throw new this.slothlet.SlothletError("INVALID_HOOK_TYPE",{type,validTypes:Array.from(this.#validTypes)})}if(typeof handler!=="function"){throw new this.slothlet.SlothletError("INVALID_HOOK_HANDLER",{receivedType:typeof handler,validationError:true})}const id=options.id||this.#generateId();if(this.#byId.has(id)){throw new this.slothlet.SlothletError("DUPLICATE_HOOK_ID",{id,validationError:true})}const subset=options.subset||"primary";if(!this.#validSubsets.has(subset)){throw new this.slothlet.SlothletError("INVALID_HOOK_SUBSET",{subset,validSubsets:Array.from(this.#validSubsets)})}this.#compilePattern(pattern);const hook={id,type,pattern,handler,priority:options.priority||0,subset,enabled:true,_compiled:null};const typeIndex=this.#hooks[type];const subsetIndex=typeIndex[subset];if(!subsetIndex[pattern]){subsetIndex[pattern]=[]}subsetIndex[pattern].push(hook);this.#byId.set(id,hook);return id}remove(filter={}){let removed=0;if(filter.id){const hook=this.#byId.get(filter.id);if(hook){this.#removeHook(hook);removed=1}return removed}const types=filter.type?[filter.type]:Array.from(this.#validTypes);for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];if(filter.pattern){const patternHooks=subsetIndex[filter.pattern];if(patternHooks){removed+=patternHooks.length;patternHooks.forEach(hook=>this.#byId.delete(hook.id));delete subsetIndex[filter.pattern]}}else{for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];removed+=patternHooks.length;patternHooks.forEach(hook=>this.#byId.delete(hook.id));delete subsetIndex[pattern]}}}}return removed}enable(filter={}){if(typeof filter==="string"){filter={pattern:filter}}this.enabled=true;return this.#setEnabledState(filter,true)}disable(filter={}){if(typeof filter==="string"){filter={pattern:filter}}if(Object.keys(filter).length===0){this.enabled=false}return this.#setEnabledState(filter,false)}list(filter={}){if(typeof filter==="string"){if(this.#validTypes.has(filter)){filter={type:filter}}else{filter={pattern:filter}}}const hooks=[];if(filter.id){const hook=this.#byId.get(filter.id);if(hook&&(filter.enabled===void 0||hook.enabled===filter.enabled)){hooks.push(this.#serializeHook(hook))}return{registeredHooks:hooks}}const types=filter.type?[filter.type]:Array.from(this.#validTypes);let patternMatcher=null;if(filter.pattern){patternMatcher=this.#compilePattern(filter.pattern)}for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];for(const hook of patternHooks){if(filter.enabled!==void 0&&hook.enabled!==filter.enabled){continue}if(patternMatcher&&!patternMatcher(hook.pattern)){continue}hooks.push(this.#serializeHook(hook))}}}}return{registeredHooks:hooks}}getHooksForPath(type,apiPath){if(this.enabled===false){return[]}const typeIndex=this.#hooks[type];if(!typeIndex){return[]}const hooks=[];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];const subsetHooks=[];for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];if(pattern===apiPath){subsetHooks.push(...patternHooks.filter(h=>h.enabled));continue}for(const hook of patternHooks){if(!hook.enabled)continue;if(!hook._compiled){hook._compiled=this.#compilePattern(hook.pattern)}if(hook._compiled(apiPath)){subsetHooks.push(hook)}}}subsetHooks.sort((a,b)=>b.priority-a.priority);hooks.push(...subsetHooks)}return hooks}executeBeforeHooks(path,args,api,ctx){const hooks=this.getHooksForPath("before",path);for(const hook of hooks){try{const result=hook.handler({path,args,api,ctx});if(result&&typeof result==="object"&&typeof result.then==="function"){throw new this.SlothletError("HOOK_BEFORE_RETURNED_PROMISE",{id:hook.id,path},null,{validationError:true})}if(result!==void 0&&!Array.isArray(result)){return{args,shortCircuit:true,value:result}}if(Array.isArray(result)){args=result}}catch(error){const sourceInfo={type:"before",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx);if(!this.suppressErrors){throw error}return{args,shortCircuit:true,value:void 0}}}return{args,shortCircuit:false}}executeAfterHooks(path,result,args,api,ctx){const hooks=this.getHooksForPath("after",path);const originalResult=result;let currentResult=result;for(const hook of hooks){try{const hookContext={path,args,result:currentResult,api,ctx};const transformed=hook.handler(hookContext);if(transformed!==void 0){currentResult=transformed}}catch(error){const sourceInfo={type:"after",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx);if(!this.suppressErrors){throw error}}}if(currentResult===originalResult){return{modified:false}}else{return{modified:true,result:currentResult}}}executeAlwaysHooks(path,args,resultOrError,hasError=false,errors=[],api,ctx){const hooks=this.getHooksForPath("always",path);for(const hook of hooks){try{hook.handler({path,args,result:hasError?void 0:resultOrError,hasError,errors,api,ctx})}catch(error){const sourceInfo={type:"always",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx)}}}executeErrorHooks(path,error,source,args,api,ctx){if(error&&typeof error==="object"){error[ERROR_HOOK_PROCESSED]=true}const hooks=this.getHooksForPath("error",path);for(const hook of hooks){try{hook.handler({path,args,error,errorType:error?.constructor?.name||"Error",source,timestamp:new Date,api,ctx})}catch(hookError){this.slothlet.debug("hooks",`Error hook failed for ${path}:`,hookError)}}}#parseTypePattern(typePattern){if(typeof typePattern!=="string"){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"string in format 'type:pattern'"})}const colonIndex=typePattern.indexOf(":");if(colonIndex===-1){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"string in format 'type:pattern' with at least one colon"})}const type=typePattern.substring(0,colonIndex);const pattern=typePattern.substring(colonIndex+1);if(!type||!pattern){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"non-empty type and pattern"})}return{type,pattern}}#compilePattern(pattern){return compilePattern(pattern,{onMaxDepth:maxDepth=>{throw new this.SlothletError("HOOK_BRACE_EXPANSION_MAX_DEPTH",{maxDepth},null,{validationError:true})}})}getCompilePatternForDiagnostics(){return this.#compilePattern.bind(this)}#generateId(){return`hook-${++this.#idCounter}`}#removeHook(hook){const typeIndex=this.#hooks[hook.type];const subsetIndex=typeIndex[hook.subset];const patternHooks=subsetIndex[hook.pattern];if(!patternHooks){throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT",{hookId:hook.id,type:hook.type,subset:hook.subset,pattern:hook.pattern,detail:"patternHooks array missing from subsetIndex \u2014 #byId and #hooks are desynced"})}const index=patternHooks.indexOf(hook);if(index===-1){throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT",{hookId:hook.id,type:hook.type,subset:hook.subset,pattern:hook.pattern,detail:"hook object not found in patternHooks array \u2014 #byId and #hooks are desynced"})}patternHooks.splice(index,1);if(patternHooks.length===0){delete subsetIndex[hook.pattern]}this.#byId.delete(hook.id)}#setEnabledState(filter,enabled){let affected=0;if(filter.id){const hook=this.#byId.get(filter.id);if(hook){hook.enabled=enabled;affected=1}return affected}const types=filter.type?[filter.type]:Array.from(this.#validTypes);for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];if(filter.pattern){const patternHooks=subsetIndex[filter.pattern]||[];for(const hook of patternHooks){hook.enabled=enabled;affected++}}else{for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];for(const hook of patternHooks){hook.enabled=enabled;affected++}}}}}return affected}#serializeHook(hook){return{id:hook.id,type:hook.type,pattern:hook.pattern,priority:hook.priority,subset:hook.subset,enabled:hook.enabled}}exportHooks(){const registrations=[];for(const hook of this.#byId.values()){registrations.push({typePattern:`${hook.type}:${hook.pattern}`,handler:hook.handler,options:{id:hook.id,priority:hook.priority,subset:hook.subset},enabled:hook.enabled})}return registrations}importHooks(registrations){if(!Array.isArray(registrations))return;for(const reg of registrations){this.on(reg.typePattern,reg.handler,reg.options);if(!reg.enabled){this.disable({id:reg.options.id})}}}async shutdown(){this.#hooks={before:{before:{},primary:{},after:{}},after:{before:{},primary:{},after:{}},always:{before:{},primary:{},after:{}},error:{before:{},primary:{},after:{}}};this.#byId.clear();this.#idCounter=0}}export{HookManager};
@@ -14,31 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
-
18
-
19
-
20
-
21
-
22
- const instanceTokens = new WeakMap();
23
-
24
-
25
- function registerInstance(slothlet) {
26
- if (instanceTokens.has(slothlet)) {
27
-
28
-
29
- return;
30
- }
31
- instanceTokens.set(slothlet, Symbol("@cldmv/slothlet/lifecycle.tagToken"));
32
- }
33
-
34
-
35
- function getInstanceToken(slothlet) {
36
- return instanceTokens.get(slothlet);
37
- }
38
-
39
-
40
- function verifyToken(slothlet, token) {
41
- return token === instanceTokens.get(slothlet);
42
- }
43
-
44
- export { registerInstance, getInstanceToken, verifyToken };
17
+ const instanceTokens=new WeakMap;function registerInstance(slothlet){if(instanceTokens.has(slothlet)){return}instanceTokens.set(slothlet,Symbol("@cldmv/slothlet/lifecycle.tagToken"))}function getInstanceToken(slothlet){return instanceTokens.get(slothlet)}function verifyToken(slothlet,token){return token===instanceTokens.get(slothlet)}export{getInstanceToken,registerInstance,verifyToken};