@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,860 @@
1
+ /**
2
+ * RuVector Test Utilities
3
+ *
4
+ * Shared utilities for RuVector integration tests including:
5
+ * - Random vector generation
6
+ * - Mock data factories
7
+ * - Test database helpers
8
+ * - Performance measurement utilities
9
+ *
10
+ * @module @sparkleideas/plugins/__tests__/utils/ruvector-test-utils
11
+ */
12
+
13
+ import { vi, type Mock } from 'vitest';
14
+ import type {
15
+ RuVectorConfig,
16
+ RuVectorClientOptions,
17
+ VectorSearchOptions,
18
+ VectorSearchResult,
19
+ VectorInsertOptions,
20
+ VectorUpdateOptions,
21
+ VectorIndexOptions,
22
+ BatchVectorOptions,
23
+ GraphData,
24
+ GNNLayer,
25
+ AttentionConfig,
26
+ AttentionInput,
27
+ HyperbolicEmbedding,
28
+ HyperbolicInput,
29
+ DistanceMetric,
30
+ VectorIndexType,
31
+ IndexStats,
32
+ QueryResult,
33
+ BatchResult,
34
+ ConnectionResult,
35
+ HealthStatus,
36
+ RuVectorStats,
37
+ AnalysisResult,
38
+ MigrationResult,
39
+ } from '../../src/integrations/ruvector/types.js';
40
+
41
+ // ============================================================================
42
+ // Environment Detection
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Check if real database tests should be run
47
+ */
48
+ export function useRealDatabase(): boolean {
49
+ return process.env.RUVECTOR_TEST_DB === 'true';
50
+ }
51
+
52
+ /**
53
+ * Get test database configuration from environment
54
+ */
55
+ export function getTestDatabaseConfig(): RuVectorConfig {
56
+ return {
57
+ host: process.env.RUVECTOR_TEST_HOST ?? 'localhost',
58
+ port: parseInt(process.env.RUVECTOR_TEST_PORT ?? '5432', 10),
59
+ database: process.env.RUVECTOR_TEST_DATABASE ?? 'ruvector_test',
60
+ user: process.env.RUVECTOR_TEST_USER ?? 'postgres',
61
+ password: process.env.RUVECTOR_TEST_PASSWORD ?? 'postgres',
62
+ poolSize: 5,
63
+ connectionTimeoutMs: 5000,
64
+ queryTimeoutMs: 30000,
65
+ };
66
+ }
67
+
68
+ // ============================================================================
69
+ // Vector Generation Utilities
70
+ // ============================================================================
71
+
72
+ /**
73
+ * Generate a random vector with specified dimensions
74
+ */
75
+ export function randomVector(dimensions: number = 384): number[] {
76
+ return Array.from({ length: dimensions }, () => Math.random() * 2 - 1);
77
+ }
78
+
79
+ /**
80
+ * Generate a normalized random vector (unit length)
81
+ */
82
+ export function normalizedVector(dimensions: number = 384): number[] {
83
+ const vec = randomVector(dimensions);
84
+ const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
85
+ return vec.map(v => v / magnitude);
86
+ }
87
+
88
+ /**
89
+ * Generate a vector within Poincare ball (norm < 1)
90
+ */
91
+ export function poincareVector(dimensions: number = 32): number[] {
92
+ const vec = randomVector(dimensions);
93
+ const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
94
+ const scale = Math.random() * 0.95 / Math.max(magnitude, 0.001);
95
+ return vec.map(v => v * scale);
96
+ }
97
+
98
+ /**
99
+ * Generate multiple random vectors
100
+ */
101
+ export function randomVectors(count: number, dimensions: number = 384): number[][] {
102
+ return Array.from({ length: count }, () => randomVector(dimensions));
103
+ }
104
+
105
+ /**
106
+ * Generate vectors with known similarities for testing search accuracy
107
+ */
108
+ export function generateSimilarVectors(
109
+ base: number[],
110
+ count: number,
111
+ noise: number = 0.1
112
+ ): number[][] {
113
+ return Array.from({ length: count }, () =>
114
+ base.map(v => v + (Math.random() - 0.5) * noise * 2)
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Generate orthogonal vectors for testing
120
+ */
121
+ export function orthogonalVectors(dimensions: number, count: number): number[][] {
122
+ // Simple Gram-Schmidt orthogonalization
123
+ const vectors: number[][] = [];
124
+
125
+ for (let i = 0; i < count; i++) {
126
+ let v = randomVector(dimensions);
127
+
128
+ // Subtract projections onto previous vectors
129
+ for (const u of vectors) {
130
+ const dot = v.reduce((sum, val, idx) => sum + val * u[idx], 0);
131
+ v = v.map((val, idx) => val - dot * u[idx]);
132
+ }
133
+
134
+ // Normalize
135
+ const mag = Math.sqrt(v.reduce((sum, val) => sum + val * val, 0));
136
+ if (mag > 0.001) {
137
+ vectors.push(v.map(val => val / mag));
138
+ }
139
+ }
140
+
141
+ return vectors;
142
+ }
143
+
144
+ /**
145
+ * Calculate cosine similarity between two vectors
146
+ */
147
+ export function cosineSimilarity(a: number[], b: number[]): number {
148
+ const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
149
+ const magA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
150
+ const magB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
151
+ return dot / (magA * magB);
152
+ }
153
+
154
+ /**
155
+ * Calculate Euclidean distance between two vectors
156
+ */
157
+ export function euclideanDistance(a: number[], b: number[]): number {
158
+ return Math.sqrt(a.reduce((sum, v, i) => sum + (v - b[i]) ** 2, 0));
159
+ }
160
+
161
+ /**
162
+ * Calculate Poincare distance in hyperbolic space
163
+ */
164
+ export function poincareDistance(a: number[], b: number[]): number {
165
+ const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
166
+ const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
167
+ const diffNorm = Math.sqrt(a.reduce((sum, v, i) => sum + (v - b[i]) ** 2, 0));
168
+
169
+ const numerator = 2 * diffNorm ** 2;
170
+ const denominator = (1 - normA ** 2) * (1 - normB ** 2);
171
+
172
+ return Math.acosh(1 + numerator / Math.max(denominator, 1e-10));
173
+ }
174
+
175
+ // ============================================================================
176
+ // Mock Data Factories
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Create a test configuration with optional overrides
181
+ */
182
+ export function createTestConfig(overrides: Partial<RuVectorConfig> = {}): RuVectorConfig {
183
+ return {
184
+ host: 'localhost',
185
+ port: 5432,
186
+ database: 'test_db',
187
+ user: 'test_user',
188
+ password: 'test_password',
189
+ poolSize: 10,
190
+ connectionTimeoutMs: 5000,
191
+ queryTimeoutMs: 30000,
192
+ schema: 'public',
193
+ ...overrides,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Create test client options
199
+ */
200
+ export function createTestClientOptions(
201
+ overrides: Partial<RuVectorClientOptions> = {}
202
+ ): RuVectorClientOptions {
203
+ return {
204
+ ...createTestConfig(),
205
+ autoReconnect: true,
206
+ maxReconnectAttempts: 3,
207
+ ...overrides,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Create mock search results
213
+ */
214
+ export function createMockSearchResults(
215
+ count: number,
216
+ options: { includeVector?: boolean; includeMetadata?: boolean; dimensions?: number } = {}
217
+ ): VectorSearchResult[] {
218
+ return Array.from({ length: count }, (_, i) => ({
219
+ id: `result-${i}`,
220
+ score: 1 - i * (1 / count),
221
+ distance: i * (1 / count),
222
+ rank: i + 1,
223
+ retrievedAt: new Date(),
224
+ ...(options.includeVector && { vector: randomVector(options.dimensions ?? 384) }),
225
+ ...(options.includeMetadata && { metadata: { index: i, label: `item-${i}` } }),
226
+ }));
227
+ }
228
+
229
+ /**
230
+ * Create mock connection result
231
+ */
232
+ export function createMockConnectionResult(): ConnectionResult {
233
+ return {
234
+ connectionId: `conn-${Date.now()}`,
235
+ ready: true,
236
+ serverVersion: 'PostgreSQL 15.0',
237
+ ruVectorVersion: '1.0.0',
238
+ parameters: {
239
+ server_encoding: 'UTF8',
240
+ client_encoding: 'UTF8',
241
+ server_version: '15.0',
242
+ },
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Create mock index stats
248
+ */
249
+ export function createMockIndexStats(
250
+ indexName: string,
251
+ indexType: VectorIndexType = 'hnsw'
252
+ ): IndexStats {
253
+ return {
254
+ indexName,
255
+ indexType,
256
+ numVectors: 10000 + Math.floor(Math.random() * 90000),
257
+ sizeBytes: 1024 * 1024 * (50 + Math.floor(Math.random() * 200)),
258
+ buildTimeMs: 5000 + Math.floor(Math.random() * 10000),
259
+ lastRebuild: new Date(),
260
+ params: {
261
+ m: 16,
262
+ efConstruction: 200,
263
+ ef_search: 100,
264
+ },
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Create mock health status
270
+ */
271
+ export function createMockHealthStatus(healthy: boolean = true): HealthStatus {
272
+ return {
273
+ status: healthy ? 'healthy' : 'unhealthy',
274
+ components: {
275
+ database: {
276
+ name: 'PostgreSQL',
277
+ healthy,
278
+ latencyMs: healthy ? 5 : undefined,
279
+ error: healthy ? undefined : 'Connection failed',
280
+ },
281
+ ruvector: {
282
+ name: 'RuVector Extension',
283
+ healthy,
284
+ latencyMs: healthy ? 1 : undefined,
285
+ },
286
+ pool: {
287
+ name: 'Connection Pool',
288
+ healthy: true,
289
+ latencyMs: 0,
290
+ },
291
+ },
292
+ lastCheck: new Date(),
293
+ issues: healthy ? [] : ['Database connection failed'],
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Create mock stats
299
+ */
300
+ export function createMockStats(): RuVectorStats {
301
+ return {
302
+ version: '1.0.0',
303
+ totalVectors: 100000,
304
+ totalSizeBytes: 1024 * 1024 * 500,
305
+ numIndices: 3,
306
+ numTables: 5,
307
+ queryStats: {
308
+ totalQueries: 50000,
309
+ avgQueryTimeMs: 15,
310
+ p95QueryTimeMs: 50,
311
+ p99QueryTimeMs: 100,
312
+ cacheHitRate: 0.85,
313
+ },
314
+ memoryStats: {
315
+ usedBytes: 1024 * 1024 * 256,
316
+ peakBytes: 1024 * 1024 * 512,
317
+ indexBytes: 1024 * 1024 * 150,
318
+ cacheBytes: 1024 * 1024 * 50,
319
+ },
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Create mock analysis result
325
+ */
326
+ export function createMockAnalysisResult(tableName: string = 'vectors'): AnalysisResult {
327
+ return {
328
+ tableName,
329
+ numRows: 10000,
330
+ columnStats: [
331
+ {
332
+ columnName: 'id',
333
+ dataType: 'uuid',
334
+ nullPercent: 0,
335
+ distinctCount: 10000,
336
+ avgSizeBytes: 16,
337
+ },
338
+ {
339
+ columnName: 'embedding',
340
+ dataType: 'vector(384)',
341
+ nullPercent: 0,
342
+ distinctCount: 10000,
343
+ avgSizeBytes: 1536,
344
+ },
345
+ {
346
+ columnName: 'metadata',
347
+ dataType: 'jsonb',
348
+ nullPercent: 5,
349
+ distinctCount: 9500,
350
+ avgSizeBytes: 256,
351
+ },
352
+ ],
353
+ recommendations: [
354
+ 'Consider adding an HNSW index for faster similarity search',
355
+ 'Metadata column could benefit from a GIN index',
356
+ ],
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Create mock migration result
362
+ */
363
+ export function createMockMigrationResult(
364
+ name: string,
365
+ direction: 'up' | 'down' = 'up',
366
+ success: boolean = true
367
+ ): MigrationResult {
368
+ return {
369
+ name,
370
+ success,
371
+ direction,
372
+ durationMs: 500 + Math.floor(Math.random() * 2000),
373
+ affectedTables: ['vectors', 'vector_indices'],
374
+ error: success ? undefined : 'Migration failed: table already exists',
375
+ };
376
+ }
377
+
378
+ // ============================================================================
379
+ // Graph Data Factories
380
+ // ============================================================================
381
+
382
+ /**
383
+ * Create a random graph for GNN testing
384
+ */
385
+ export function createRandomGraph(
386
+ numNodes: number,
387
+ numEdges: number,
388
+ featureDim: number
389
+ ): GraphData {
390
+ const nodeFeatures = randomVectors(numNodes, featureDim);
391
+ const edges: [number[], number[]] = [[], []];
392
+
393
+ for (let i = 0; i < numEdges; i++) {
394
+ const source = Math.floor(Math.random() * numNodes);
395
+ const target = Math.floor(Math.random() * numNodes);
396
+ edges[0].push(source);
397
+ edges[1].push(target);
398
+ }
399
+
400
+ return {
401
+ nodeFeatures,
402
+ edgeIndex: edges,
403
+ };
404
+ }
405
+
406
+ /**
407
+ * Create a complete graph (all nodes connected)
408
+ */
409
+ export function createCompleteGraph(numNodes: number, featureDim: number): GraphData {
410
+ const nodeFeatures = randomVectors(numNodes, featureDim);
411
+ const edges: [number[], number[]] = [[], []];
412
+
413
+ for (let i = 0; i < numNodes; i++) {
414
+ for (let j = 0; j < numNodes; j++) {
415
+ if (i !== j) {
416
+ edges[0].push(i);
417
+ edges[1].push(j);
418
+ }
419
+ }
420
+ }
421
+
422
+ return {
423
+ nodeFeatures,
424
+ edgeIndex: edges,
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Create a chain graph (linear sequence)
430
+ */
431
+ export function createChainGraph(numNodes: number, featureDim: number): GraphData {
432
+ const nodeFeatures = randomVectors(numNodes, featureDim);
433
+ const edges: [number[], number[]] = [[], []];
434
+
435
+ for (let i = 0; i < numNodes - 1; i++) {
436
+ edges[0].push(i);
437
+ edges[1].push(i + 1);
438
+ // Bidirectional
439
+ edges[0].push(i + 1);
440
+ edges[1].push(i);
441
+ }
442
+
443
+ return {
444
+ nodeFeatures,
445
+ edgeIndex: edges,
446
+ };
447
+ }
448
+
449
+ // ============================================================================
450
+ // Mock Database Interfaces
451
+ // ============================================================================
452
+
453
+ /**
454
+ * Mock PostgreSQL client interface
455
+ */
456
+ export interface MockPgClient {
457
+ connect: Mock;
458
+ query: Mock;
459
+ release: Mock;
460
+ end: Mock;
461
+ on: Mock;
462
+ off: Mock;
463
+ }
464
+
465
+ /**
466
+ * Mock PostgreSQL pool interface
467
+ */
468
+ export interface MockPgPool {
469
+ connect: Mock;
470
+ query: Mock;
471
+ end: Mock;
472
+ on: Mock;
473
+ totalCount: number;
474
+ idleCount: number;
475
+ waitingCount: number;
476
+ }
477
+
478
+ /**
479
+ * Create a mock PostgreSQL client
480
+ */
481
+ export function createMockPgClient(): MockPgClient {
482
+ return {
483
+ connect: vi.fn().mockResolvedValue(undefined),
484
+ query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
485
+ release: vi.fn(),
486
+ end: vi.fn().mockResolvedValue(undefined),
487
+ on: vi.fn(),
488
+ off: vi.fn(),
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Create a mock PostgreSQL pool
494
+ */
495
+ export function createMockPgPool(): MockPgPool {
496
+ const mockClient = createMockPgClient();
497
+ return {
498
+ connect: vi.fn().mockResolvedValue(mockClient),
499
+ query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
500
+ end: vi.fn().mockResolvedValue(undefined),
501
+ on: vi.fn(),
502
+ totalCount: 10,
503
+ idleCount: 5,
504
+ waitingCount: 0,
505
+ };
506
+ }
507
+
508
+ // ============================================================================
509
+ // Performance Testing Utilities
510
+ // ============================================================================
511
+
512
+ /**
513
+ * Measure execution time of an async function
514
+ */
515
+ export async function measureAsync<T>(
516
+ fn: () => Promise<T>
517
+ ): Promise<{ result: T; durationMs: number }> {
518
+ const start = performance.now();
519
+ const result = await fn();
520
+ const durationMs = performance.now() - start;
521
+ return { result, durationMs };
522
+ }
523
+
524
+ /**
525
+ * Run a function multiple times and return statistics
526
+ */
527
+ export async function benchmark<T>(
528
+ fn: () => Promise<T>,
529
+ iterations: number = 100
530
+ ): Promise<{
531
+ iterations: number;
532
+ totalMs: number;
533
+ avgMs: number;
534
+ minMs: number;
535
+ maxMs: number;
536
+ p95Ms: number;
537
+ p99Ms: number;
538
+ }> {
539
+ const times: number[] = [];
540
+
541
+ for (let i = 0; i < iterations; i++) {
542
+ const { durationMs } = await measureAsync(fn);
543
+ times.push(durationMs);
544
+ }
545
+
546
+ times.sort((a, b) => a - b);
547
+
548
+ return {
549
+ iterations,
550
+ totalMs: times.reduce((sum, t) => sum + t, 0),
551
+ avgMs: times.reduce((sum, t) => sum + t, 0) / iterations,
552
+ minMs: times[0],
553
+ maxMs: times[times.length - 1],
554
+ p95Ms: times[Math.floor(iterations * 0.95)],
555
+ p99Ms: times[Math.floor(iterations * 0.99)],
556
+ };
557
+ }
558
+
559
+ /**
560
+ * Generate test data for throughput testing
561
+ */
562
+ export function generateBulkInsertData(
563
+ count: number,
564
+ dimensions: number = 384
565
+ ): VectorInsertOptions['vectors'] {
566
+ return Array.from({ length: count }, (_, i) => ({
567
+ id: `bulk-${Date.now()}-${i}`,
568
+ vector: randomVector(dimensions),
569
+ metadata: {
570
+ index: i,
571
+ timestamp: Date.now(),
572
+ batch: Math.floor(i / 100),
573
+ },
574
+ }));
575
+ }
576
+
577
+ // ============================================================================
578
+ // Test Data Builders
579
+ // ============================================================================
580
+
581
+ /**
582
+ * Builder for VectorSearchOptions
583
+ */
584
+ export class SearchOptionsBuilder {
585
+ private options: VectorSearchOptions;
586
+
587
+ constructor(dimensions: number = 384) {
588
+ this.options = {
589
+ query: randomVector(dimensions),
590
+ k: 10,
591
+ metric: 'cosine',
592
+ };
593
+ }
594
+
595
+ withQuery(query: number[]): this {
596
+ this.options = { ...this.options, query };
597
+ return this;
598
+ }
599
+
600
+ withK(k: number): this {
601
+ this.options = { ...this.options, k };
602
+ return this;
603
+ }
604
+
605
+ withMetric(metric: DistanceMetric): this {
606
+ this.options = { ...this.options, metric };
607
+ return this;
608
+ }
609
+
610
+ withFilter(filter: Record<string, unknown>): this {
611
+ this.options = { ...this.options, filter };
612
+ return this;
613
+ }
614
+
615
+ withThreshold(threshold: number): this {
616
+ this.options = { ...this.options, threshold };
617
+ return this;
618
+ }
619
+
620
+ withTable(tableName: string): this {
621
+ this.options = { ...this.options, tableName };
622
+ return this;
623
+ }
624
+
625
+ includeVector(include: boolean = true): this {
626
+ this.options = { ...this.options, includeVector: include };
627
+ return this;
628
+ }
629
+
630
+ includeMetadata(include: boolean = true): this {
631
+ this.options = { ...this.options, includeMetadata: include };
632
+ return this;
633
+ }
634
+
635
+ build(): VectorSearchOptions {
636
+ return { ...this.options };
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Builder for VectorInsertOptions
642
+ */
643
+ export class InsertOptionsBuilder {
644
+ private options: VectorInsertOptions;
645
+
646
+ constructor(tableName: string = 'vectors') {
647
+ this.options = {
648
+ tableName,
649
+ vectors: [],
650
+ };
651
+ }
652
+
653
+ addVector(
654
+ vector: number[],
655
+ id?: string,
656
+ metadata?: Record<string, unknown>
657
+ ): this {
658
+ this.options = {
659
+ ...this.options,
660
+ vectors: [...this.options.vectors, { id, vector, metadata }],
661
+ };
662
+ return this;
663
+ }
664
+
665
+ addRandomVectors(count: number, dimensions: number = 384): this {
666
+ const vectors = Array.from({ length: count }, (_, i) => ({
667
+ id: `gen-${Date.now()}-${i}`,
668
+ vector: randomVector(dimensions),
669
+ metadata: { generated: true, index: i },
670
+ }));
671
+ this.options = {
672
+ ...this.options,
673
+ vectors: [...this.options.vectors, ...vectors],
674
+ };
675
+ return this;
676
+ }
677
+
678
+ withUpsert(upsert: boolean = true): this {
679
+ this.options = { ...this.options, upsert };
680
+ return this;
681
+ }
682
+
683
+ withBatchSize(batchSize: number): this {
684
+ this.options = { ...this.options, batchSize };
685
+ return this;
686
+ }
687
+
688
+ withReturning(returning: boolean = true): this {
689
+ this.options = { ...this.options, returning };
690
+ return this;
691
+ }
692
+
693
+ build(): VectorInsertOptions {
694
+ return { ...this.options };
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Builder for VectorIndexOptions
700
+ */
701
+ export class IndexOptionsBuilder {
702
+ private options: VectorIndexOptions;
703
+
704
+ constructor(tableName: string, columnName: string = 'embedding') {
705
+ this.options = {
706
+ tableName,
707
+ columnName,
708
+ indexType: 'hnsw',
709
+ };
710
+ }
711
+
712
+ withType(indexType: VectorIndexType): this {
713
+ this.options = { ...this.options, indexType };
714
+ return this;
715
+ }
716
+
717
+ withName(indexName: string): this {
718
+ this.options = { ...this.options, indexName };
719
+ return this;
720
+ }
721
+
722
+ withMetric(metric: DistanceMetric): this {
723
+ this.options = { ...this.options, metric };
724
+ return this;
725
+ }
726
+
727
+ withHNSWParams(m: number, efConstruction: number): this {
728
+ this.options = { ...this.options, m, efConstruction };
729
+ return this;
730
+ }
731
+
732
+ withIVFParams(lists: number): this {
733
+ this.options = { ...this.options, lists };
734
+ return this;
735
+ }
736
+
737
+ concurrent(concurrent: boolean = true): this {
738
+ this.options = { ...this.options, concurrent };
739
+ return this;
740
+ }
741
+
742
+ replace(replace: boolean = true): this {
743
+ this.options = { ...this.options, replace };
744
+ return this;
745
+ }
746
+
747
+ build(): VectorIndexOptions {
748
+ return { ...this.options };
749
+ }
750
+ }
751
+
752
+ // ============================================================================
753
+ // Assertion Helpers
754
+ // ============================================================================
755
+
756
+ /**
757
+ * Assert that results are sorted by score descending
758
+ */
759
+ export function assertSortedByScore(results: VectorSearchResult[]): void {
760
+ for (let i = 1; i < results.length; i++) {
761
+ if (results[i].score > results[i - 1].score) {
762
+ throw new Error(
763
+ `Results not sorted by score: ${results[i - 1].score} > ${results[i].score}`
764
+ );
765
+ }
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Assert that results are sorted by distance ascending
771
+ */
772
+ export function assertSortedByDistance(results: VectorSearchResult[]): void {
773
+ for (let i = 1; i < results.length; i++) {
774
+ if (
775
+ results[i].distance !== undefined &&
776
+ results[i - 1].distance !== undefined &&
777
+ results[i].distance! < results[i - 1].distance!
778
+ ) {
779
+ throw new Error(
780
+ `Results not sorted by distance: ${results[i - 1].distance} < ${results[i].distance}`
781
+ );
782
+ }
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Assert that all vectors are normalized (unit length)
788
+ */
789
+ export function assertNormalized(vectors: number[][], tolerance: number = 0.001): void {
790
+ for (const vec of vectors) {
791
+ const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
792
+ if (Math.abs(magnitude - 1) > tolerance) {
793
+ throw new Error(`Vector not normalized: magnitude = ${magnitude}`);
794
+ }
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Assert that all vectors are inside Poincare ball
800
+ */
801
+ export function assertInPoincareBall(
802
+ vectors: number[][],
803
+ maxNorm: number = 0.99
804
+ ): void {
805
+ for (const vec of vectors) {
806
+ const norm = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
807
+ if (norm >= maxNorm) {
808
+ throw new Error(`Vector outside Poincare ball: norm = ${norm}`);
809
+ }
810
+ }
811
+ }
812
+
813
+ // ============================================================================
814
+ // Cleanup Utilities
815
+ // ============================================================================
816
+
817
+ /**
818
+ * Generate unique table name for tests
819
+ */
820
+ export function uniqueTableName(prefix: string = 'test'): string {
821
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
822
+ }
823
+
824
+ /**
825
+ * Generate unique index name for tests
826
+ */
827
+ export function uniqueIndexName(tableName: string, columnName: string = 'embedding'): string {
828
+ return `idx_${tableName}_${columnName}_${Math.random().toString(36).slice(2, 8)}`;
829
+ }
830
+
831
+ // ============================================================================
832
+ // Type Exports
833
+ // ============================================================================
834
+
835
+ export type {
836
+ RuVectorConfig,
837
+ RuVectorClientOptions,
838
+ VectorSearchOptions,
839
+ VectorSearchResult,
840
+ VectorInsertOptions,
841
+ VectorUpdateOptions,
842
+ VectorIndexOptions,
843
+ BatchVectorOptions,
844
+ GraphData,
845
+ GNNLayer,
846
+ AttentionConfig,
847
+ AttentionInput,
848
+ HyperbolicEmbedding,
849
+ HyperbolicInput,
850
+ DistanceMetric,
851
+ VectorIndexType,
852
+ IndexStats,
853
+ QueryResult,
854
+ BatchResult,
855
+ ConnectionResult,
856
+ HealthStatus,
857
+ RuVectorStats,
858
+ AnalysisResult,
859
+ MigrationResult,
860
+ };