@sparkleideas/plugins 3.0.0-alpha.10

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 (80) hide show
  1. package/README.md +401 -0
  2. package/__tests__/collection-manager.test.ts +332 -0
  3. package/__tests__/dependency-graph.test.ts +434 -0
  4. package/__tests__/enhanced-plugin-registry.test.ts +488 -0
  5. package/__tests__/plugin-registry.test.ts +368 -0
  6. package/__tests__/ruvector-bridge.test.ts +2429 -0
  7. package/__tests__/ruvector-integration.test.ts +1602 -0
  8. package/__tests__/ruvector-migrations.test.ts +1099 -0
  9. package/__tests__/ruvector-quantization.test.ts +846 -0
  10. package/__tests__/ruvector-streaming.test.ts +1088 -0
  11. package/__tests__/sdk.test.ts +325 -0
  12. package/__tests__/security.test.ts +348 -0
  13. package/__tests__/utils/ruvector-test-utils.ts +860 -0
  14. package/examples/plugin-creator/index.ts +636 -0
  15. package/examples/plugin-creator/plugin-creator.test.ts +312 -0
  16. package/examples/ruvector/README.md +288 -0
  17. package/examples/ruvector/attention-patterns.ts +394 -0
  18. package/examples/ruvector/basic-usage.ts +288 -0
  19. package/examples/ruvector/docker-compose.yml +75 -0
  20. package/examples/ruvector/gnn-analysis.ts +501 -0
  21. package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
  22. package/examples/ruvector/init-db.sql +119 -0
  23. package/examples/ruvector/quantization.ts +680 -0
  24. package/examples/ruvector/self-learning.ts +447 -0
  25. package/examples/ruvector/semantic-search.ts +576 -0
  26. package/examples/ruvector/streaming-large-data.ts +507 -0
  27. package/examples/ruvector/transactions.ts +594 -0
  28. package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
  29. package/examples/ruvector-plugins/index.ts +79 -0
  30. package/examples/ruvector-plugins/intent-router.ts +354 -0
  31. package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
  32. package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
  33. package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
  34. package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
  35. package/examples/ruvector-plugins/shared/index.ts +20 -0
  36. package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
  37. package/examples/ruvector-plugins/sona-learning.ts +445 -0
  38. package/package.json +97 -0
  39. package/src/collections/collection-manager.ts +661 -0
  40. package/src/collections/index.ts +56 -0
  41. package/src/collections/official/index.ts +1040 -0
  42. package/src/core/base-plugin.ts +416 -0
  43. package/src/core/plugin-interface.ts +215 -0
  44. package/src/hooks/index.ts +685 -0
  45. package/src/index.ts +378 -0
  46. package/src/integrations/agentic-flow.ts +743 -0
  47. package/src/integrations/index.ts +88 -0
  48. package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
  49. package/src/integrations/ruvector/attention-advanced.ts +1040 -0
  50. package/src/integrations/ruvector/attention-executor.ts +782 -0
  51. package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
  52. package/src/integrations/ruvector/attention.ts +1063 -0
  53. package/src/integrations/ruvector/gnn.ts +3050 -0
  54. package/src/integrations/ruvector/hyperbolic.ts +1948 -0
  55. package/src/integrations/ruvector/index.ts +394 -0
  56. package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
  57. package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
  58. package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
  59. package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
  60. package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
  61. package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
  62. package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
  63. package/src/integrations/ruvector/migrations/index.ts +35 -0
  64. package/src/integrations/ruvector/migrations/migrations.ts +647 -0
  65. package/src/integrations/ruvector/quantization.ts +2036 -0
  66. package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
  67. package/src/integrations/ruvector/self-learning.ts +2376 -0
  68. package/src/integrations/ruvector/streaming.ts +1737 -0
  69. package/src/integrations/ruvector/types.ts +1945 -0
  70. package/src/providers/index.ts +643 -0
  71. package/src/registry/dependency-graph.ts +568 -0
  72. package/src/registry/enhanced-plugin-registry.ts +994 -0
  73. package/src/registry/plugin-registry.ts +604 -0
  74. package/src/sdk/index.ts +563 -0
  75. package/src/security/index.ts +594 -0
  76. package/src/types/index.ts +446 -0
  77. package/src/workers/index.ts +700 -0
  78. package/tmp.json +0 -0
  79. package/tsconfig.json +25 -0
  80. package/vitest.config.ts +23 -0
@@ -0,0 +1,685 @@
1
+ /**
2
+ * Hooks Integration Module
3
+ *
4
+ * Provides comprehensive hook capabilities for plugin development.
5
+ * Enables lifecycle event interception, transformation, and monitoring.
6
+ */
7
+
8
+ import { EventEmitter } from 'events';
9
+ import type {
10
+ HookDefinition,
11
+ HookEvent,
12
+ HookPriority,
13
+ HookHandler,
14
+ HookContext,
15
+ HookResult,
16
+ ILogger,
17
+ IEventBus,
18
+ } from '../types/index.js';
19
+ import { HookEvent as HookEventEnum, HookPriority as HookPriorityEnum } from '../types/index.js';
20
+
21
+ // ============================================================================
22
+ // Hook Registry
23
+ // ============================================================================
24
+
25
+ export interface HookRegistryConfig {
26
+ logger?: ILogger;
27
+ eventBus?: IEventBus;
28
+ maxHooksPerEvent?: number;
29
+ defaultTimeout?: number;
30
+ parallelExecution?: boolean;
31
+ }
32
+
33
+ export interface HookEntry {
34
+ readonly hook: HookDefinition;
35
+ readonly pluginName?: string;
36
+ readonly registeredAt: Date;
37
+ executionCount: number;
38
+ lastExecuted?: Date;
39
+ lastError?: string;
40
+ avgExecutionTime: number;
41
+ }
42
+
43
+ export interface HookRegistryStats {
44
+ totalHooks: number;
45
+ hooksByEvent: Record<string, number>;
46
+ executionCount: number;
47
+ errorCount: number;
48
+ avgExecutionTime: number;
49
+ }
50
+
51
+ /**
52
+ * Central registry for hook management.
53
+ */
54
+ export class HookRegistry extends EventEmitter {
55
+ private readonly hooks = new Map<HookEvent, HookEntry[]>();
56
+ private readonly config: HookRegistryConfig;
57
+ private stats = { executionCount: 0, errorCount: 0, totalExecutionTime: 0 };
58
+
59
+ constructor(config?: HookRegistryConfig) {
60
+ super();
61
+ this.config = {
62
+ maxHooksPerEvent: 50,
63
+ defaultTimeout: 30000,
64
+ parallelExecution: false,
65
+ ...config,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Register a hook.
71
+ */
72
+ register(hook: HookDefinition, pluginName?: string): () => void {
73
+ const event = hook.event;
74
+
75
+ if (!this.hooks.has(event)) {
76
+ this.hooks.set(event, []);
77
+ }
78
+
79
+ const entries = this.hooks.get(event)!;
80
+
81
+ if (entries.length >= (this.config.maxHooksPerEvent ?? 50)) {
82
+ throw new Error(`Maximum hooks limit reached for event ${event}`);
83
+ }
84
+
85
+ const entry: HookEntry = {
86
+ hook,
87
+ pluginName,
88
+ registeredAt: new Date(),
89
+ executionCount: 0,
90
+ avgExecutionTime: 0,
91
+ };
92
+
93
+ // Insert in priority order (higher priority first)
94
+ const priority = hook.priority ?? HookPriorityEnum.Normal;
95
+ const insertIndex = entries.findIndex(e => (e.hook.priority ?? HookPriorityEnum.Normal) < priority);
96
+
97
+ if (insertIndex === -1) {
98
+ entries.push(entry);
99
+ } else {
100
+ entries.splice(insertIndex, 0, entry);
101
+ }
102
+
103
+ // Return unregister function
104
+ return () => this.unregister(event, hook.handler);
105
+ }
106
+
107
+ /**
108
+ * Unregister a hook.
109
+ */
110
+ unregister(event: HookEvent, handler: HookHandler): boolean {
111
+ const entries = this.hooks.get(event);
112
+ if (!entries) return false;
113
+
114
+ const index = entries.findIndex(e => e.hook.handler === handler);
115
+ if (index === -1) return false;
116
+
117
+ entries.splice(index, 1);
118
+ return true;
119
+ }
120
+
121
+ /**
122
+ * Execute hooks for an event.
123
+ */
124
+ async execute(event: HookEvent, data: unknown, source?: string): Promise<HookResult[]> {
125
+ const entries = this.hooks.get(event);
126
+ if (!entries || entries.length === 0) {
127
+ return [];
128
+ }
129
+
130
+ const context: HookContext = {
131
+ event,
132
+ data,
133
+ timestamp: new Date(),
134
+ source,
135
+ };
136
+
137
+ const results: HookResult[] = [];
138
+
139
+ if (this.config.parallelExecution) {
140
+ // Execute hooks in parallel (respect priority groups)
141
+ const priorityGroups = this.groupByPriority(entries);
142
+
143
+ for (const group of priorityGroups) {
144
+ const groupResults = await Promise.all(
145
+ group.map(entry => this.executeHook(entry, context))
146
+ );
147
+ results.push(...groupResults);
148
+
149
+ // Check for abort
150
+ if (groupResults.some(r => r.abort)) {
151
+ break;
152
+ }
153
+ }
154
+ } else {
155
+ // Execute hooks sequentially
156
+ for (const entry of entries) {
157
+ const result = await this.executeHook(entry, context);
158
+ results.push(result);
159
+
160
+ // Check for abort
161
+ if (result.abort) {
162
+ break;
163
+ }
164
+
165
+ // Pass modified data to next hook
166
+ if (result.modified && result.data !== undefined) {
167
+ (context as { data: unknown }).data = result.data;
168
+ }
169
+ }
170
+ }
171
+
172
+ return results;
173
+ }
174
+
175
+ private groupByPriority(entries: HookEntry[]): HookEntry[][] {
176
+ const groups: HookEntry[][] = [];
177
+ let currentPriority: number | null = null;
178
+ let currentGroup: HookEntry[] = [];
179
+
180
+ for (const entry of entries) {
181
+ const priority = entry.hook.priority ?? HookPriorityEnum.Normal;
182
+
183
+ if (currentPriority === null || currentPriority === priority) {
184
+ currentGroup.push(entry);
185
+ currentPriority = priority;
186
+ } else {
187
+ if (currentGroup.length > 0) {
188
+ groups.push(currentGroup);
189
+ }
190
+ currentGroup = [entry];
191
+ currentPriority = priority;
192
+ }
193
+ }
194
+
195
+ if (currentGroup.length > 0) {
196
+ groups.push(currentGroup);
197
+ }
198
+
199
+ return groups;
200
+ }
201
+
202
+ private async executeHook(entry: HookEntry, context: HookContext): Promise<HookResult> {
203
+ const startTime = Date.now();
204
+ this.stats.executionCount++;
205
+ entry.executionCount++;
206
+ entry.lastExecuted = new Date();
207
+
208
+ try {
209
+ const timeout = this.config.defaultTimeout ?? 30000;
210
+
211
+ const result = await Promise.race([
212
+ entry.hook.handler(context),
213
+ new Promise<never>((_, reject) =>
214
+ setTimeout(() => reject(new Error('Hook execution timeout')), timeout)
215
+ ),
216
+ ]);
217
+
218
+ const duration = Date.now() - startTime;
219
+ this.stats.totalExecutionTime += duration;
220
+
221
+ // Update average execution time
222
+ const totalTime = entry.avgExecutionTime * (entry.executionCount - 1) + duration;
223
+ entry.avgExecutionTime = totalTime / entry.executionCount;
224
+
225
+ return result;
226
+ } catch (error) {
227
+ this.stats.errorCount++;
228
+ entry.lastError = error instanceof Error ? error.message : String(error);
229
+
230
+ return {
231
+ success: false,
232
+ error: entry.lastError,
233
+ };
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Get hooks for a specific event.
239
+ */
240
+ getHooks(event: HookEvent): HookEntry[] {
241
+ return [...(this.hooks.get(event) ?? [])];
242
+ }
243
+
244
+ /**
245
+ * Get all registered hooks.
246
+ */
247
+ getAllHooks(): Map<HookEvent, HookEntry[]> {
248
+ return new Map(this.hooks);
249
+ }
250
+
251
+ /**
252
+ * Get registry statistics.
253
+ */
254
+ getStats(): HookRegistryStats {
255
+ const hooksByEvent: Record<string, number> = {};
256
+ let totalHooks = 0;
257
+
258
+ for (const [event, entries] of this.hooks) {
259
+ hooksByEvent[event] = entries.length;
260
+ totalHooks += entries.length;
261
+ }
262
+
263
+ return {
264
+ totalHooks,
265
+ hooksByEvent,
266
+ executionCount: this.stats.executionCount,
267
+ errorCount: this.stats.errorCount,
268
+ avgExecutionTime: this.stats.executionCount > 0
269
+ ? this.stats.totalExecutionTime / this.stats.executionCount
270
+ : 0,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Clear all hooks.
276
+ */
277
+ clear(): void {
278
+ this.hooks.clear();
279
+ this.stats = { executionCount: 0, errorCount: 0, totalExecutionTime: 0 };
280
+ }
281
+ }
282
+
283
+ // ============================================================================
284
+ // Hook Builder
285
+ // ============================================================================
286
+
287
+ /**
288
+ * Fluent builder for creating hooks.
289
+ */
290
+ export class HookBuilder {
291
+ private event: HookEvent;
292
+ private name?: string;
293
+ private description?: string;
294
+ private priority: HookPriority = HookPriorityEnum.Normal;
295
+ private isAsync: boolean = true;
296
+ private handler?: HookHandler;
297
+ private conditions: Array<(context: HookContext) => boolean> = [];
298
+ private transformers: Array<(data: unknown) => unknown> = [];
299
+
300
+ constructor(event: HookEvent) {
301
+ this.event = event;
302
+ }
303
+
304
+ withName(name: string): this {
305
+ this.name = name;
306
+ return this;
307
+ }
308
+
309
+ withDescription(description: string): this {
310
+ this.description = description;
311
+ return this;
312
+ }
313
+
314
+ withPriority(priority: HookPriority): this {
315
+ this.priority = priority;
316
+ return this;
317
+ }
318
+
319
+ synchronous(): this {
320
+ this.isAsync = false;
321
+ return this;
322
+ }
323
+
324
+ /**
325
+ * Add a condition that must be met for the hook to execute.
326
+ */
327
+ when(condition: (context: HookContext) => boolean): this {
328
+ this.conditions.push(condition);
329
+ return this;
330
+ }
331
+
332
+ /**
333
+ * Add a data transformer that runs before the handler.
334
+ */
335
+ transform(transformer: (data: unknown) => unknown): this {
336
+ this.transformers.push(transformer);
337
+ return this;
338
+ }
339
+
340
+ /**
341
+ * Set the handler function.
342
+ */
343
+ handle(handler: HookHandler): this {
344
+ this.handler = handler;
345
+ return this;
346
+ }
347
+
348
+ /**
349
+ * Build the hook definition.
350
+ */
351
+ build(): HookDefinition {
352
+ if (!this.handler) {
353
+ throw new Error(`Hook for event ${this.event} requires a handler`);
354
+ }
355
+
356
+ const originalHandler = this.handler;
357
+ const conditions = this.conditions;
358
+ const transformers = this.transformers;
359
+
360
+ // Wrap handler with conditions and transformers
361
+ const wrappedHandler: HookHandler = async (context: HookContext) => {
362
+ // Check conditions
363
+ for (const condition of conditions) {
364
+ if (!condition(context)) {
365
+ return { success: true, data: context.data };
366
+ }
367
+ }
368
+
369
+ // Apply transformers
370
+ let data = context.data;
371
+ for (const transformer of transformers) {
372
+ data = transformer(data);
373
+ }
374
+
375
+ // Create modified context
376
+ const modifiedContext: HookContext = { ...context, data };
377
+
378
+ // Execute handler
379
+ return originalHandler(modifiedContext);
380
+ };
381
+
382
+ return {
383
+ event: this.event,
384
+ handler: wrappedHandler,
385
+ priority: this.priority,
386
+ name: this.name,
387
+ description: this.description,
388
+ async: this.isAsync,
389
+ };
390
+ }
391
+ }
392
+
393
+ // ============================================================================
394
+ // Pre-built Hook Factories
395
+ // ============================================================================
396
+
397
+ /**
398
+ * Factory for creating common hooks.
399
+ */
400
+ export class HookFactory {
401
+ /**
402
+ * Create a logging hook for any event.
403
+ */
404
+ static createLogger(
405
+ event: HookEvent,
406
+ logger: ILogger,
407
+ options?: { name?: string; logLevel?: 'debug' | 'info' | 'warn' }
408
+ ): HookDefinition {
409
+ const logLevel = options?.logLevel ?? 'debug';
410
+
411
+ return new HookBuilder(event)
412
+ .withName(options?.name ?? `${event}-logger`)
413
+ .withDescription(`Logs ${event} events`)
414
+ .withPriority(HookPriorityEnum.Deferred)
415
+ .handle(async (context) => {
416
+ logger[logLevel](`Hook event: ${event}`, { data: context.data, source: context.source });
417
+ return { success: true };
418
+ })
419
+ .build();
420
+ }
421
+
422
+ /**
423
+ * Create a timing hook that measures execution time.
424
+ */
425
+ static createTimer(
426
+ event: HookEvent,
427
+ _onComplete: (duration: number, context: HookContext) => void
428
+ ): HookDefinition {
429
+ return new HookBuilder(event)
430
+ .withName(`${event}-timer`)
431
+ .withDescription(`Times ${event} events`)
432
+ .withPriority(HookPriorityEnum.Critical)
433
+ .handle(async (context) => {
434
+ const startTime = Date.now();
435
+
436
+ // Store start time in metadata
437
+ const metadata = { ...context.metadata, _startTime: startTime };
438
+
439
+ return {
440
+ success: true,
441
+ data: context.data,
442
+ metadata,
443
+ };
444
+ })
445
+ .build();
446
+ }
447
+
448
+ /**
449
+ * Create a validation hook.
450
+ */
451
+ static createValidator<T>(
452
+ event: HookEvent,
453
+ validator: (data: T) => boolean | string,
454
+ options?: { name?: string; abortOnFail?: boolean }
455
+ ): HookDefinition {
456
+ return new HookBuilder(event)
457
+ .withName(options?.name ?? `${event}-validator`)
458
+ .withDescription(`Validates ${event} data`)
459
+ .withPriority(HookPriorityEnum.High)
460
+ .handle(async (context) => {
461
+ const result = validator(context.data as T);
462
+
463
+ if (result === true) {
464
+ return { success: true };
465
+ }
466
+
467
+ const error = typeof result === 'string' ? result : 'Validation failed';
468
+
469
+ return {
470
+ success: false,
471
+ error,
472
+ abort: options?.abortOnFail ?? false,
473
+ };
474
+ })
475
+ .build();
476
+ }
477
+
478
+ /**
479
+ * Create a rate limiting hook.
480
+ */
481
+ static createRateLimiter(
482
+ event: HookEvent,
483
+ options: { maxPerMinute: number; name?: string }
484
+ ): HookDefinition {
485
+ const windowMs = 60000;
486
+ const timestamps: number[] = [];
487
+
488
+ return new HookBuilder(event)
489
+ .withName(options.name ?? `${event}-rate-limiter`)
490
+ .withDescription(`Rate limits ${event} to ${options.maxPerMinute}/min`)
491
+ .withPriority(HookPriorityEnum.Critical)
492
+ .handle(async () => {
493
+ const now = Date.now();
494
+
495
+ // Clean old timestamps
496
+ while (timestamps.length > 0 && timestamps[0] < now - windowMs) {
497
+ timestamps.shift();
498
+ }
499
+
500
+ if (timestamps.length >= options.maxPerMinute) {
501
+ return {
502
+ success: false,
503
+ error: `Rate limit exceeded: ${options.maxPerMinute}/min`,
504
+ abort: true,
505
+ };
506
+ }
507
+
508
+ timestamps.push(now);
509
+ return { success: true };
510
+ })
511
+ .build();
512
+ }
513
+
514
+ /**
515
+ * Create a caching hook.
516
+ */
517
+ static createCache<T>(
518
+ event: HookEvent,
519
+ options: {
520
+ keyExtractor: (data: T) => string;
521
+ ttlMs?: number;
522
+ maxSize?: number;
523
+ name?: string;
524
+ }
525
+ ): HookDefinition {
526
+ const cache = new Map<string, { value: unknown; expires: number }>();
527
+ const ttlMs = options.ttlMs ?? 60000;
528
+ const maxSize = options.maxSize ?? 100;
529
+
530
+ return new HookBuilder(event)
531
+ .withName(options.name ?? `${event}-cache`)
532
+ .withDescription(`Caches ${event} results`)
533
+ .withPriority(HookPriorityEnum.High)
534
+ .handle(async (context) => {
535
+ const key = options.keyExtractor(context.data as T);
536
+ const now = Date.now();
537
+
538
+ // Check cache
539
+ const cached = cache.get(key);
540
+ if (cached && cached.expires > now) {
541
+ return {
542
+ success: true,
543
+ data: cached.value,
544
+ modified: true,
545
+ };
546
+ }
547
+
548
+ // Clean expired entries if at max size
549
+ if (cache.size >= maxSize) {
550
+ for (const [k, v] of cache) {
551
+ if (v.expires < now) {
552
+ cache.delete(k);
553
+ }
554
+ }
555
+ // Store result with TTL
556
+ cache.set(key, { value: context.data, expires: now + ttlMs });
557
+ }
558
+
559
+ return { success: true };
560
+ })
561
+ .build();
562
+ }
563
+
564
+ /**
565
+ * Create a retry hook.
566
+ */
567
+ static createRetry(
568
+ event: HookEvent,
569
+ options: {
570
+ maxRetries: number;
571
+ delayMs?: number;
572
+ backoffMultiplier?: number;
573
+ name?: string;
574
+ }
575
+ ): HookDefinition {
576
+ const retryState = new Map<string, number>();
577
+
578
+ return new HookBuilder(event)
579
+ .withName(options.name ?? `${event}-retry`)
580
+ .withDescription(`Adds retry logic to ${event}`)
581
+ .withPriority(HookPriorityEnum.Normal)
582
+ .handle(async (context) => {
583
+ const key = context.source ?? 'default';
584
+ const retryCount = retryState.get(key) ?? 0;
585
+
586
+ if (retryCount >= options.maxRetries) {
587
+ retryState.delete(key);
588
+ return {
589
+ success: false,
590
+ error: `Max retries (${options.maxRetries}) exceeded`,
591
+ };
592
+ }
593
+
594
+ return {
595
+ success: true,
596
+ data: {
597
+ ...context.data as object,
598
+ _retryCount: retryCount,
599
+ },
600
+ modified: true,
601
+ };
602
+ })
603
+ .build();
604
+ }
605
+ }
606
+
607
+ // ============================================================================
608
+ // Hook Executor
609
+ // ============================================================================
610
+
611
+ /**
612
+ * Utility for executing hooks in different patterns.
613
+ */
614
+ export class HookExecutor {
615
+ private readonly registry: HookRegistry;
616
+
617
+ constructor(registry: HookRegistry) {
618
+ this.registry = registry;
619
+ }
620
+
621
+ /**
622
+ * Execute hooks and collect all results.
623
+ */
624
+ async executeAll(event: HookEvent, data: unknown, source?: string): Promise<HookResult[]> {
625
+ return this.registry.execute(event, data, source);
626
+ }
627
+
628
+ /**
629
+ * Execute hooks and return the first successful result.
630
+ */
631
+ async executeFirst(event: HookEvent, data: unknown, source?: string): Promise<HookResult | null> {
632
+ const results = await this.registry.execute(event, data, source);
633
+ return results.find(r => r.success) ?? null;
634
+ }
635
+
636
+ /**
637
+ * Execute hooks and return true if all succeeded.
638
+ */
639
+ async executeValidate(event: HookEvent, data: unknown, source?: string): Promise<boolean> {
640
+ const results = await this.registry.execute(event, data, source);
641
+ return results.every(r => r.success);
642
+ }
643
+
644
+ /**
645
+ * Execute hooks and return the final transformed data.
646
+ */
647
+ async executeTransform<T>(event: HookEvent, data: T, source?: string): Promise<T> {
648
+ const results = await this.registry.execute(event, data, source);
649
+
650
+ let result = data;
651
+ for (const r of results) {
652
+ if (r.success && r.modified && r.data !== undefined) {
653
+ result = r.data as T;
654
+ }
655
+ }
656
+
657
+ return result;
658
+ }
659
+
660
+ /**
661
+ * Execute hooks until one aborts.
662
+ */
663
+ async executeUntilAbort(
664
+ event: HookEvent,
665
+ data: unknown,
666
+ source?: string
667
+ ): Promise<{ results: HookResult[]; aborted: boolean }> {
668
+ const results = await this.registry.execute(event, data, source);
669
+ const aborted = results.some(r => r.abort);
670
+ return { results, aborted };
671
+ }
672
+ }
673
+
674
+ // ============================================================================
675
+ // Exports
676
+ // ============================================================================
677
+
678
+ export {
679
+ HookEventEnum as HookEvent,
680
+ HookPriorityEnum as HookPriority,
681
+ type HookDefinition,
682
+ type HookHandler,
683
+ type HookContext,
684
+ type HookResult,
685
+ };