@unrdf/hooks 5.0.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/package.json +70 -0
  4. package/src/hooks/builtin-hooks.mjs +296 -0
  5. package/src/hooks/condition-cache.mjs +109 -0
  6. package/src/hooks/condition-evaluator.mjs +722 -0
  7. package/src/hooks/define-hook.mjs +211 -0
  8. package/src/hooks/effect-sandbox-worker.mjs +170 -0
  9. package/src/hooks/effect-sandbox.mjs +517 -0
  10. package/src/hooks/file-resolver.mjs +387 -0
  11. package/src/hooks/hook-chain-compiler.mjs +236 -0
  12. package/src/hooks/hook-executor-batching.mjs +277 -0
  13. package/src/hooks/hook-executor.mjs +465 -0
  14. package/src/hooks/hook-management.mjs +202 -0
  15. package/src/hooks/hook-scheduler.mjs +413 -0
  16. package/src/hooks/knowledge-hook-engine.mjs +358 -0
  17. package/src/hooks/knowledge-hook-manager.mjs +269 -0
  18. package/src/hooks/observability.mjs +531 -0
  19. package/src/hooks/policy-pack.mjs +572 -0
  20. package/src/hooks/quad-pool.mjs +249 -0
  21. package/src/hooks/quality-metrics.mjs +544 -0
  22. package/src/hooks/security/error-sanitizer.mjs +257 -0
  23. package/src/hooks/security/path-validator.mjs +194 -0
  24. package/src/hooks/security/sandbox-restrictions.mjs +331 -0
  25. package/src/hooks/telemetry.mjs +167 -0
  26. package/src/index.mjs +101 -0
  27. package/src/security/sandbox/browser-executor.mjs +220 -0
  28. package/src/security/sandbox/detector.mjs +342 -0
  29. package/src/security/sandbox/isolated-vm-executor.mjs +373 -0
  30. package/src/security/sandbox/vm2-executor.mjs +217 -0
  31. package/src/security/sandbox/worker-executor-runtime.mjs +74 -0
  32. package/src/security/sandbox/worker-executor.mjs +212 -0
  33. package/src/security/sandbox-adapter.mjs +141 -0
@@ -0,0 +1,465 @@
1
+ /**
2
+ * @file Hook execution utilities for UNRDF Knowledge Hooks.
3
+ * @module hooks/hook-executor
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { HookSchema, hasValidation, hasTransformation } from './define-hook.mjs';
8
+
9
+ /**
10
+ * @typedef {import('./define-hook.mjs').Hook} Hook
11
+ * @typedef {import('n3').Quad} Quad
12
+ */
13
+
14
+ /**
15
+ * Hook execution result.
16
+ * @typedef {Object} HookResult
17
+ * @property {boolean} valid - Whether validation passed
18
+ * @property {Quad} [quad] - Transformed quad (if transformation applied)
19
+ * @property {string} [error] - Error message if validation failed
20
+ * @property {string} hookName - Name of hook that executed
21
+ */
22
+
23
+ /**
24
+ * Hook chain execution result.
25
+ * @typedef {Object} ChainResult
26
+ * @property {boolean} valid - Whether all validations passed
27
+ * @property {Quad} quad - Final transformed quad
28
+ * @property {HookResult[]} results - Individual hook results
29
+ * @property {string} [error] - Error message if any validation failed
30
+ */
31
+
32
+ /* ========================================================================= */
33
+ /* Zod Schemas */
34
+ /* ========================================================================= */
35
+
36
+ export const HookResultSchema = z.object({
37
+ valid: z.boolean(),
38
+ quad: z.any().optional(),
39
+ error: z.string().optional(),
40
+ hookName: z.string(),
41
+ });
42
+
43
+ export const ChainResultSchema = z.object({
44
+ valid: z.boolean(),
45
+ quad: z.any(),
46
+ results: z.array(HookResultSchema),
47
+ error: z.string().optional(),
48
+ });
49
+
50
+ /* ========================================================================= */
51
+ /* Public API */
52
+ /* ========================================================================= */
53
+
54
+ /**
55
+ * Execute a single hook on a quad.
56
+ *
57
+ * @param {Hook} hook - Hook to execute
58
+ * @param {Quad} quad - Quad to process
59
+ * @returns {HookResult} - Execution result
60
+ *
61
+ * @example
62
+ * const result = executeHook(iriValidator, quad);
63
+ * if (!result.valid) {
64
+ * console.error(result.error);
65
+ * }
66
+ */
67
+ export function executeHook(hook, quad, options = {}) {
68
+ // Fast path: skip Zod if hook was created via defineHook (_validated flag)
69
+ const validatedHook = hook._validated ? hook : HookSchema.parse(hook);
70
+
71
+ /** @type {HookResult} */
72
+ const result = {
73
+ valid: true,
74
+ quad: quad,
75
+ hookName: validatedHook.name,
76
+ };
77
+
78
+ try {
79
+ // Execute validation if present
80
+ if (hasValidation(validatedHook)) {
81
+ const validationResult = validatedHook.validate(quad);
82
+
83
+ // POKA-YOKE: Non-boolean validation return guard (RPN 280 → 28)
84
+ if (typeof validationResult !== 'boolean') {
85
+ console.warn(
86
+ `[POKA-YOKE] Hook "${validatedHook.name}": validate() returned ${typeof validationResult}, expected boolean. Coercing to boolean.`
87
+ );
88
+ result.warning = `Non-boolean validation return (${typeof validationResult}) coerced to boolean`;
89
+ }
90
+
91
+ if (!validationResult) {
92
+ result.valid = false;
93
+ result.error = `Validation failed for hook: ${validatedHook.name}`;
94
+ return result;
95
+ }
96
+ }
97
+
98
+ // Execute transformation if present
99
+ if (hasTransformation(validatedHook)) {
100
+ const transformed = validatedHook.transform(quad);
101
+
102
+ // POKA-YOKE: Transform return type validation (RPN 280 → 28)
103
+ if (!transformed || typeof transformed !== 'object') {
104
+ throw new TypeError(
105
+ `Hook "${validatedHook.name}": transform() must return a Quad object, got ${typeof transformed}`
106
+ );
107
+ }
108
+
109
+ // POKA-YOKE: Check for required Quad properties
110
+ if (!transformed.subject || !transformed.predicate || !transformed.object) {
111
+ throw new TypeError(
112
+ `Hook "${validatedHook.name}": transform() returned object missing subject/predicate/object`
113
+ );
114
+ }
115
+
116
+ // POKA-YOKE: Pooled quad leak detection (warn if returning pooled quad)
117
+ if (transformed._pooled && options.warnPooledQuads !== false) {
118
+ console.warn(
119
+ `[POKA-YOKE] Hook "${validatedHook.name}": returned pooled quad. Clone before storing to prevent memory issues.`
120
+ );
121
+ result.warning = 'Pooled quad returned - consider cloning';
122
+ }
123
+
124
+ result.quad = transformed;
125
+ }
126
+
127
+ return result;
128
+ } catch (error) {
129
+ result.valid = false;
130
+ result.error = error instanceof Error ? error.message : String(error);
131
+
132
+ // POKA-YOKE: Stack trace preservation (RPN 504 → 50)
133
+ result.errorDetails = {
134
+ hookName: validatedHook.name,
135
+ hookTrigger: validatedHook.trigger,
136
+ stack: error instanceof Error ? error.stack : undefined,
137
+ originalError: error instanceof Error ? error : undefined,
138
+ rawError: !(error instanceof Error) ? error : undefined,
139
+ };
140
+
141
+ return result;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Execute multiple hooks in sequence on a quad.
147
+ * Stops at first validation failure.
148
+ * Transformations are chained (output of one becomes input to next).
149
+ *
150
+ * @param {Hook[]} hooks - Array of hooks to execute
151
+ * @param {Quad} quad - Initial quad to process
152
+ * @returns {ChainResult} - Chain execution result
153
+ *
154
+ * @example
155
+ * const result = executeHookChain([validator, transformer], quad);
156
+ * if (result.valid) {
157
+ * store.add(result.quad);
158
+ * }
159
+ */
160
+ export function executeHookChain(hooks, quad) {
161
+ // Fast path: trust pre-validated hooks (skip Zod array parse)
162
+
163
+ /** @type {HookResult[]} */
164
+ const results = [];
165
+ let currentQuad = quad;
166
+ let chainValid = true;
167
+ let chainError = undefined;
168
+
169
+ for (const hook of hooks) {
170
+ const result = executeHook(hook, currentQuad);
171
+ results.push(result);
172
+
173
+ if (!result.valid) {
174
+ chainValid = false;
175
+ chainError = result.error;
176
+ break;
177
+ }
178
+
179
+ if (result.quad) {
180
+ currentQuad = result.quad;
181
+ }
182
+ }
183
+
184
+ // Fast path: return plain object (skip ChainResultSchema.parse)
185
+ return {
186
+ valid: chainValid,
187
+ quad: currentQuad,
188
+ results,
189
+ error: chainError,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Execute hooks for a specific trigger type.
195
+ *
196
+ * @param {Hook[]} hooks - All registered hooks
197
+ * @param {import('./define-hook.mjs').HookTrigger} trigger - Trigger type to execute
198
+ * @param {Quad} quad - Quad to process
199
+ * @returns {ChainResult} - Execution result
200
+ *
201
+ * @example
202
+ * const result = executeHooksByTrigger(allHooks, 'before-add', quad);
203
+ */
204
+ export function executeHooksByTrigger(hooks, trigger, quad) {
205
+ // Fast path: trust pre-validated hooks (skip Zod array parse)
206
+ const matchingHooks = hooks.filter(h => h.trigger === trigger);
207
+ return executeHookChain(matchingHooks, quad);
208
+ }
209
+
210
+ /**
211
+ * Check if hooks would pass for a quad (dry-run validation).
212
+ *
213
+ * @param {Hook[]} hooks - Hooks to check
214
+ * @param {Quad} quad - Quad to validate
215
+ * @returns {boolean} - True if all validations would pass
216
+ */
217
+ export function wouldPassHooks(hooks, quad) {
218
+ const result = executeHookChain(hooks, quad);
219
+ return result.valid;
220
+ }
221
+
222
+ /* ========================================================================= */
223
+ /* Batch API (High-Performance Bulk Operations) */
224
+ /* Sub-1μs per operation via Zod-free hot path */
225
+ /* ========================================================================= */
226
+
227
+ /**
228
+ * Execute validation only (skip transforms) for faster validation-only checks.
229
+ * Zod-free hot path for sub-1μs execution.
230
+ *
231
+ * @param {Hook[]} hooks - Hooks to execute (must be pre-validated via defineHook)
232
+ * @param {Quad} quad - Quad to validate
233
+ * @returns {HookResult} - Validation result
234
+ */
235
+ export function validateOnly(hooks, quad) {
236
+ // Skip Zod in hot path - trust pre-validated hooks
237
+ for (const hook of hooks) {
238
+ if (hasValidation(hook)) {
239
+ try {
240
+ if (!hook.validate(quad)) {
241
+ return {
242
+ valid: false,
243
+ quad,
244
+ error: `Validation failed for hook: ${hook.name}`,
245
+ hookName: hook.name,
246
+ };
247
+ }
248
+ } catch (error) {
249
+ return {
250
+ valid: false,
251
+ quad,
252
+ error: error instanceof Error ? error.message : String(error),
253
+ hookName: hook.name,
254
+ };
255
+ }
256
+ }
257
+ }
258
+
259
+ return { valid: true, quad, hookName: 'validateOnly' };
260
+ }
261
+
262
+ /**
263
+ * Execute hooks in batch for multiple quads.
264
+ * Optimized for bulk operations - Zod-free hot path.
265
+ *
266
+ * @param {Hook[]} hooks - Hooks to execute (must be pre-validated via defineHook)
267
+ * @param {Quad[]} quads - Array of quads to process
268
+ * @param {Object} [options] - Batch options
269
+ * @param {boolean} [options.stopOnError=false] - Stop on first error
270
+ * @returns {{ results: ChainResult[], validCount: number, invalidCount: number }}
271
+ */
272
+ export function executeBatch(hooks, quads, options = {}) {
273
+ const { stopOnError = false } = options;
274
+
275
+ /** @type {ChainResult[]} */
276
+ const results = [];
277
+ let validCount = 0;
278
+ let invalidCount = 0;
279
+
280
+ // Zod-free hot path - hooks already validated by defineHook
281
+ for (let i = 0; i < quads.length; i++) {
282
+ const quad = quads[i];
283
+ let currentQuad = quad;
284
+ let isValid = true;
285
+ let error;
286
+
287
+ for (const hook of hooks) {
288
+ // Validation check
289
+ if (hasValidation(hook)) {
290
+ try {
291
+ if (!hook.validate(currentQuad)) {
292
+ isValid = false;
293
+ error = `Validation failed: ${hook.name}`;
294
+ break;
295
+ }
296
+ } catch (e) {
297
+ isValid = false;
298
+ error = e instanceof Error ? e.message : String(e);
299
+ break;
300
+ }
301
+ }
302
+
303
+ // Transform if valid
304
+ if (isValid && hasTransformation(hook)) {
305
+ try {
306
+ currentQuad = hook.transform(currentQuad);
307
+ } catch (e) {
308
+ isValid = false;
309
+ error = e instanceof Error ? e.message : String(e);
310
+ break;
311
+ }
312
+ }
313
+ }
314
+
315
+ results.push({ valid: isValid, quad: currentQuad, error, results: [] });
316
+
317
+ if (isValid) {
318
+ validCount++;
319
+ } else {
320
+ invalidCount++;
321
+ if (stopOnError) break;
322
+ }
323
+ }
324
+
325
+ return { results, validCount, invalidCount };
326
+ }
327
+
328
+ /**
329
+ * Validate batch of quads, returning bitmap of valid quads.
330
+ * Hyper-speed: Zod-free hot path, returns Uint8Array directly.
331
+ *
332
+ * @param {Hook[]} hooks - Hooks to execute (must be pre-validated via defineHook)
333
+ * @param {Quad[]} quads - Array of quads to validate
334
+ * @returns {Uint8Array} - Bitmap where 1 = valid, 0 = invalid
335
+ */
336
+ export function validateBatch(hooks, quads) {
337
+ // Filter validation hooks once (no Zod)
338
+ const validationHooks = hooks.filter(hasValidation);
339
+
340
+ // Use Uint8Array for compact boolean storage - returned directly
341
+ const bitmap = new Uint8Array(quads.length);
342
+
343
+ for (let i = 0; i < quads.length; i++) {
344
+ const quad = quads[i];
345
+ let isValid = true;
346
+
347
+ for (const hook of validationHooks) {
348
+ try {
349
+ if (!hook.validate(quad)) {
350
+ isValid = false;
351
+ break;
352
+ }
353
+ } catch {
354
+ isValid = false;
355
+ break;
356
+ }
357
+ }
358
+
359
+ bitmap[i] = isValid ? 1 : 0;
360
+ }
361
+
362
+ return bitmap;
363
+ }
364
+
365
+ /**
366
+ * Transform batch of quads.
367
+ * Applies transformation hooks to all quads - Zod-free hot path.
368
+ *
369
+ * @param {Hook[]} hooks - Hooks to execute (must be pre-validated via defineHook)
370
+ * @param {Quad[]} quads - Array of quads to transform
371
+ * @param {Object} [options] - Transform options
372
+ * @param {boolean} [options.validateFirst=true] - Validate before transform
373
+ * @returns {{ transformed: Quad[], errors: Array<{index: number, error: string}> }}
374
+ */
375
+ export function transformBatch(hooks, quads, options = {}) {
376
+ const { validateFirst = true } = options;
377
+
378
+ /** @type {Quad[]} */
379
+ const transformed = [];
380
+ /** @type {Array<{index: number, error: string}>} */
381
+ const errors = [];
382
+
383
+ for (let i = 0; i < quads.length; i++) {
384
+ let currentQuad = quads[i];
385
+ let hasError = false;
386
+
387
+ for (const hook of hooks) {
388
+ try {
389
+ // Validate first if required
390
+ if (validateFirst && hasValidation(hook)) {
391
+ if (!hook.validate(currentQuad)) {
392
+ errors.push({ index: i, error: `Validation failed: ${hook.name}` });
393
+ hasError = true;
394
+ break;
395
+ }
396
+ }
397
+
398
+ // Apply transformation
399
+ if (hasTransformation(hook)) {
400
+ currentQuad = hook.transform(currentQuad);
401
+ }
402
+ } catch (error) {
403
+ errors.push({
404
+ index: i,
405
+ error: error instanceof Error ? error.message : String(error),
406
+ });
407
+ hasError = true;
408
+ break;
409
+ }
410
+ }
411
+
412
+ if (!hasError) {
413
+ transformed.push(currentQuad);
414
+ }
415
+ }
416
+
417
+ return { transformed, errors };
418
+ }
419
+
420
+ /* ========================================================================= */
421
+ /* Cache Management */
422
+ /* ========================================================================= */
423
+
424
+ /**
425
+ * Hook execution cache for pre-validated hooks.
426
+ * @type {WeakMap<object, boolean>}
427
+ */
428
+ const hookValidationCache = new WeakMap();
429
+
430
+ /**
431
+ * Clear all hook caches (validation and compiled chains).
432
+ * Call this when hooks are modified or for testing.
433
+ */
434
+ export function clearHookCaches() {
435
+ // WeakMap auto-clears, but we can signal intent
436
+ // The compiled chain cache is in hook-chain-compiler.mjs
437
+ // This is a no-op for WeakMap but provides consistent API
438
+ }
439
+
440
+ /**
441
+ * Pre-warm hook cache by pre-validating hooks.
442
+ * Call this at startup to avoid first-execution overhead.
443
+ *
444
+ * @param {Hook[]} hooks - Hooks to pre-warm
445
+ * @returns {{ prewarmed: number, errors: string[] }}
446
+ */
447
+ export function prewarmHookCache(hooks) {
448
+ const errors = [];
449
+ let prewarmed = 0;
450
+
451
+ for (const hook of hooks) {
452
+ try {
453
+ // Validate hook structure
454
+ HookSchema.parse(hook);
455
+ hookValidationCache.set(hook, true);
456
+ prewarmed++;
457
+ } catch (error) {
458
+ errors.push(
459
+ `Hook "${hook?.name || 'unknown'}": ${error instanceof Error ? error.message : String(error)}`
460
+ );
461
+ }
462
+ }
463
+
464
+ return { prewarmed, errors };
465
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @file Hook registry and management utilities for UNRDF Knowledge Hooks.
3
+ * @module hooks/hook-management
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { HookSchema } from './define-hook.mjs';
8
+
9
+ /**
10
+ * @typedef {import('./define-hook.mjs').Hook} Hook
11
+ * @typedef {import('./define-hook.mjs').HookTrigger} HookTrigger
12
+ */
13
+
14
+ /**
15
+ * Hook registry for managing registered hooks.
16
+ * @typedef {Object} HookRegistry
17
+ * @property {Map<string, Hook>} hooks - Map of hook name to hook
18
+ * @property {Map<HookTrigger, Set<string>>} triggerIndex - Index of trigger to hook names
19
+ */
20
+
21
+ /* ========================================================================= */
22
+ /* Zod Schemas */
23
+ /* ========================================================================= */
24
+
25
+ export const HookRegistrySchema = z.object({
26
+ hooks: z.instanceof(Map),
27
+ triggerIndex: z.instanceof(Map),
28
+ });
29
+
30
+ /* ========================================================================= */
31
+ /* Public API */
32
+ /* ========================================================================= */
33
+
34
+ /**
35
+ * Create a new hook registry.
36
+ *
37
+ * @returns {HookRegistry} - New empty registry
38
+ *
39
+ * @example
40
+ * const registry = createHookRegistry();
41
+ * registerHook(registry, myHook);
42
+ */
43
+ export function createHookRegistry() {
44
+ return {
45
+ hooks: new Map(),
46
+ triggerIndex: new Map(),
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Register a hook in the registry.
52
+ *
53
+ * @param {HookRegistry} registry - Hook registry
54
+ * @param {Hook} hook - Hook to register
55
+ * @throws {Error} - If hook with same name already exists
56
+ *
57
+ * @example
58
+ * registerHook(registry, defineHook({
59
+ * name: 'validate-iri',
60
+ * trigger: 'before-add',
61
+ * validate: (quad) => quad.subject.termType === 'NamedNode'
62
+ * }));
63
+ */
64
+ export function registerHook(registry, hook) {
65
+ const validatedRegistry = HookRegistrySchema.parse(registry);
66
+ const validatedHook = HookSchema.parse(hook);
67
+
68
+ if (validatedRegistry.hooks.has(validatedHook.name)) {
69
+ throw new Error(`Hook already registered: ${validatedHook.name}`);
70
+ }
71
+
72
+ validatedRegistry.hooks.set(validatedHook.name, validatedHook);
73
+
74
+ if (!validatedRegistry.triggerIndex.has(validatedHook.trigger)) {
75
+ validatedRegistry.triggerIndex.set(validatedHook.trigger, new Set());
76
+ }
77
+ validatedRegistry.triggerIndex.get(validatedHook.trigger).add(validatedHook.name);
78
+ }
79
+
80
+ /**
81
+ * Unregister a hook from the registry.
82
+ *
83
+ * @param {HookRegistry} registry - Hook registry
84
+ * @param {string} name - Hook name to remove
85
+ * @returns {boolean} - True if hook was removed, false if not found
86
+ *
87
+ * @example
88
+ * unregisterHook(registry, 'validate-iri');
89
+ */
90
+ export function unregisterHook(registry, name) {
91
+ const validatedRegistry = HookRegistrySchema.parse(registry);
92
+
93
+ const hook = validatedRegistry.hooks.get(name);
94
+ if (!hook) {
95
+ return false;
96
+ }
97
+
98
+ validatedRegistry.hooks.delete(name);
99
+
100
+ const triggerSet = validatedRegistry.triggerIndex.get(hook.trigger);
101
+ if (triggerSet) {
102
+ triggerSet.delete(name);
103
+ if (triggerSet.size === 0) {
104
+ validatedRegistry.triggerIndex.delete(hook.trigger);
105
+ }
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Get a hook by name.
113
+ *
114
+ * @param {HookRegistry} registry - Hook registry
115
+ * @param {string} name - Hook name
116
+ * @returns {Hook | undefined} - Hook if found, undefined otherwise
117
+ */
118
+ export function getHook(registry, name) {
119
+ const validatedRegistry = HookRegistrySchema.parse(registry);
120
+ return validatedRegistry.hooks.get(name);
121
+ }
122
+
123
+ /**
124
+ * List all registered hooks.
125
+ *
126
+ * @param {HookRegistry} registry - Hook registry
127
+ * @returns {Hook[]} - Array of all registered hooks
128
+ */
129
+ export function listHooks(registry) {
130
+ const validatedRegistry = HookRegistrySchema.parse(registry);
131
+ return Array.from(validatedRegistry.hooks.values());
132
+ }
133
+
134
+ /**
135
+ * Get hooks by trigger type.
136
+ *
137
+ * @param {HookRegistry} registry - Hook registry
138
+ * @param {HookTrigger} trigger - Trigger type
139
+ * @returns {Hook[]} - Array of hooks for this trigger
140
+ */
141
+ export function getHooksByTrigger(registry, trigger) {
142
+ const validatedRegistry = HookRegistrySchema.parse(registry);
143
+
144
+ const hookNames = validatedRegistry.triggerIndex.get(trigger);
145
+ if (!hookNames) {
146
+ return [];
147
+ }
148
+
149
+ const hooks = [];
150
+ for (const name of hookNames) {
151
+ const hook = validatedRegistry.hooks.get(name);
152
+ if (hook) {
153
+ hooks.push(hook);
154
+ }
155
+ }
156
+ return hooks;
157
+ }
158
+
159
+ /**
160
+ * Check if a hook is registered.
161
+ *
162
+ * @param {HookRegistry} registry - Hook registry
163
+ * @param {string} name - Hook name
164
+ * @returns {boolean} - True if hook is registered
165
+ */
166
+ export function hasHook(registry, name) {
167
+ const validatedRegistry = HookRegistrySchema.parse(registry);
168
+ return validatedRegistry.hooks.has(name);
169
+ }
170
+
171
+ /**
172
+ * Clear all hooks from registry.
173
+ *
174
+ * @param {HookRegistry} registry - Hook registry
175
+ */
176
+ export function clearHooks(registry) {
177
+ const validatedRegistry = HookRegistrySchema.parse(registry);
178
+ validatedRegistry.hooks.clear();
179
+ validatedRegistry.triggerIndex.clear();
180
+ }
181
+
182
+ /**
183
+ * Get registry statistics.
184
+ *
185
+ * @param {HookRegistry} registry - Hook registry
186
+ * @returns {Object} - Registry statistics
187
+ * @property {number} totalHooks - Total number of registered hooks
188
+ * @property {Record<string, number>} byTrigger - Count of hooks by trigger type
189
+ */
190
+ export function getRegistryStats(registry) {
191
+ const validatedRegistry = HookRegistrySchema.parse(registry);
192
+
193
+ const byTrigger = {};
194
+ for (const [trigger, names] of validatedRegistry.triggerIndex) {
195
+ byTrigger[trigger] = names.size;
196
+ }
197
+
198
+ return {
199
+ totalHooks: validatedRegistry.hooks.size,
200
+ byTrigger,
201
+ };
202
+ }