@sparkleideas/plugins 3.0.0-alpha.8
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.
- package/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuVector PostgreSQL Bridge - Transactions Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Multi-vector atomic updates using transactions
|
|
6
|
+
* - Savepoint usage for partial rollbacks
|
|
7
|
+
* - Error recovery strategies
|
|
8
|
+
* - Batch operations within transactions
|
|
9
|
+
*
|
|
10
|
+
* Run with: npx ts-node examples/ruvector/transactions.ts
|
|
11
|
+
*
|
|
12
|
+
* @module @sparkleideas/plugins/examples/ruvector/transactions
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createRuVectorBridge,
|
|
17
|
+
type RuVectorBridge,
|
|
18
|
+
type VectorRecord,
|
|
19
|
+
} from '../../src/integrations/ruvector/index.js';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Configuration
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const config = {
|
|
26
|
+
connection: {
|
|
27
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
28
|
+
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
|
29
|
+
database: process.env.POSTGRES_DB || 'vectors',
|
|
30
|
+
user: process.env.POSTGRES_USER || 'postgres',
|
|
31
|
+
password: process.env.POSTGRES_PASSWORD || 'postgres',
|
|
32
|
+
},
|
|
33
|
+
dimensions: 128,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Helper Functions
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate a random normalized embedding.
|
|
42
|
+
*/
|
|
43
|
+
function generateEmbedding(dim: number): number[] {
|
|
44
|
+
const vec = Array.from({ length: dim }, () => Math.random() * 2 - 1);
|
|
45
|
+
const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0));
|
|
46
|
+
return vec.map(v => v / mag);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sleep for a specified duration.
|
|
51
|
+
*/
|
|
52
|
+
function sleep(ms: number): Promise<void> {
|
|
53
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Transaction Wrapper Types
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
interface TransactionContext {
|
|
61
|
+
id: string;
|
|
62
|
+
startTime: number;
|
|
63
|
+
operations: Array<{
|
|
64
|
+
type: string;
|
|
65
|
+
target: string;
|
|
66
|
+
success: boolean;
|
|
67
|
+
duration: number;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface TransactionResult<T> {
|
|
72
|
+
success: boolean;
|
|
73
|
+
result?: T;
|
|
74
|
+
error?: Error;
|
|
75
|
+
context: TransactionContext;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Transaction Manager
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Transaction manager for atomic vector operations.
|
|
84
|
+
* Note: This is a simulation - in production, use PostgreSQL's native transactions.
|
|
85
|
+
*/
|
|
86
|
+
class VectorTransactionManager {
|
|
87
|
+
private bridge: RuVectorBridge;
|
|
88
|
+
private activeTransactions: Map<string, TransactionContext> = new Map();
|
|
89
|
+
private savepointStack: Map<string, Array<{ name: string; snapshot: Map<string, VectorRecord> }>> = new Map();
|
|
90
|
+
|
|
91
|
+
constructor(bridge: RuVectorBridge) {
|
|
92
|
+
this.bridge = bridge;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Begin a new transaction.
|
|
97
|
+
*/
|
|
98
|
+
async begin(): Promise<TransactionContext> {
|
|
99
|
+
const txId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
100
|
+
const context: TransactionContext = {
|
|
101
|
+
id: txId,
|
|
102
|
+
startTime: Date.now(),
|
|
103
|
+
operations: [],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
this.activeTransactions.set(txId, context);
|
|
107
|
+
this.savepointStack.set(txId, []);
|
|
108
|
+
|
|
109
|
+
console.log(` [TX ${txId.slice(0, 12)}] Transaction started`);
|
|
110
|
+
return context;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create a savepoint within the transaction.
|
|
115
|
+
*/
|
|
116
|
+
async savepoint(context: TransactionContext, name: string, collection: string): Promise<void> {
|
|
117
|
+
const stack = this.savepointStack.get(context.id);
|
|
118
|
+
if (!stack) throw new Error('Transaction not found');
|
|
119
|
+
|
|
120
|
+
// Capture current state (simplified - in production, PostgreSQL handles this)
|
|
121
|
+
const snapshot = new Map<string, VectorRecord>();
|
|
122
|
+
|
|
123
|
+
// This is a simulation - in production, use SAVEPOINT SQL command
|
|
124
|
+
stack.push({ name, snapshot });
|
|
125
|
+
|
|
126
|
+
console.log(` [TX ${context.id.slice(0, 12)}] Savepoint '${name}' created`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rollback to a savepoint.
|
|
131
|
+
*/
|
|
132
|
+
async rollbackToSavepoint(context: TransactionContext, name: string): Promise<void> {
|
|
133
|
+
const stack = this.savepointStack.get(context.id);
|
|
134
|
+
if (!stack) throw new Error('Transaction not found');
|
|
135
|
+
|
|
136
|
+
// Find and remove savepoints up to and including the named one
|
|
137
|
+
let found = false;
|
|
138
|
+
while (stack.length > 0) {
|
|
139
|
+
const sp = stack.pop()!;
|
|
140
|
+
if (sp.name === name) {
|
|
141
|
+
found = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!found) {
|
|
147
|
+
throw new Error(`Savepoint '${name}' not found`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(` [TX ${context.id.slice(0, 12)}] Rolled back to savepoint '${name}'`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Commit the transaction.
|
|
155
|
+
*/
|
|
156
|
+
async commit(context: TransactionContext): Promise<void> {
|
|
157
|
+
const duration = Date.now() - context.startTime;
|
|
158
|
+
const successOps = context.operations.filter(op => op.success).length;
|
|
159
|
+
|
|
160
|
+
this.activeTransactions.delete(context.id);
|
|
161
|
+
this.savepointStack.delete(context.id);
|
|
162
|
+
|
|
163
|
+
console.log(
|
|
164
|
+
` [TX ${context.id.slice(0, 12)}] Committed ` +
|
|
165
|
+
`(${successOps}/${context.operations.length} ops, ${duration}ms)`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Rollback the transaction.
|
|
171
|
+
*/
|
|
172
|
+
async rollback(context: TransactionContext, reason?: string): Promise<void> {
|
|
173
|
+
const duration = Date.now() - context.startTime;
|
|
174
|
+
|
|
175
|
+
this.activeTransactions.delete(context.id);
|
|
176
|
+
this.savepointStack.delete(context.id);
|
|
177
|
+
|
|
178
|
+
console.log(
|
|
179
|
+
` [TX ${context.id.slice(0, 12)}] Rolled back ` +
|
|
180
|
+
`(${reason || 'explicit rollback'}, ${duration}ms)`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Execute an operation within the transaction.
|
|
186
|
+
*/
|
|
187
|
+
async execute<T>(
|
|
188
|
+
context: TransactionContext,
|
|
189
|
+
operationType: string,
|
|
190
|
+
target: string,
|
|
191
|
+
operation: () => Promise<T>
|
|
192
|
+
): Promise<T> {
|
|
193
|
+
const startTime = performance.now();
|
|
194
|
+
let success = false;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const result = await operation();
|
|
198
|
+
success = true;
|
|
199
|
+
return result;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw error;
|
|
202
|
+
} finally {
|
|
203
|
+
const duration = performance.now() - startTime;
|
|
204
|
+
context.operations.push({
|
|
205
|
+
type: operationType,
|
|
206
|
+
target,
|
|
207
|
+
success,
|
|
208
|
+
duration,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Run a transaction with automatic commit/rollback.
|
|
215
|
+
*/
|
|
216
|
+
async run<T>(
|
|
217
|
+
fn: (context: TransactionContext) => Promise<T>
|
|
218
|
+
): Promise<TransactionResult<T>> {
|
|
219
|
+
const context = await this.begin();
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const result = await fn(context);
|
|
223
|
+
await this.commit(context);
|
|
224
|
+
return { success: true, result, context };
|
|
225
|
+
} catch (error) {
|
|
226
|
+
await this.rollback(context, (error as Error).message);
|
|
227
|
+
return { success: false, error: error as Error, context };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Main Example
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
async function main(): Promise<void> {
|
|
237
|
+
console.log('RuVector PostgreSQL Bridge - Transactions Example');
|
|
238
|
+
console.log('===================================================\n');
|
|
239
|
+
|
|
240
|
+
const bridge: RuVectorBridge = createRuVectorBridge({
|
|
241
|
+
connectionString: `postgresql://${config.connection.user}:${config.connection.password}@${config.connection.host}:${config.connection.port}/${config.connection.database}`,
|
|
242
|
+
poolSize: 5,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const txManager = new VectorTransactionManager(bridge);
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await bridge.connect();
|
|
249
|
+
console.log('Connected to PostgreSQL\n');
|
|
250
|
+
|
|
251
|
+
// ========================================================================
|
|
252
|
+
// 1. Create Test Collection
|
|
253
|
+
// ========================================================================
|
|
254
|
+
console.log('1. Creating test collection...');
|
|
255
|
+
console.log(' ' + '-'.repeat(50));
|
|
256
|
+
|
|
257
|
+
await bridge.createCollection('transactions_demo', {
|
|
258
|
+
dimensions: config.dimensions,
|
|
259
|
+
distanceMetric: 'cosine',
|
|
260
|
+
indexType: 'hnsw',
|
|
261
|
+
});
|
|
262
|
+
console.log(' Collection created\n');
|
|
263
|
+
|
|
264
|
+
// ========================================================================
|
|
265
|
+
// 2. Simple Transaction - All or Nothing
|
|
266
|
+
// ========================================================================
|
|
267
|
+
console.log('2. Simple Transaction (All or Nothing)');
|
|
268
|
+
console.log(' ' + '-'.repeat(50));
|
|
269
|
+
|
|
270
|
+
const simpleResult = await txManager.run(async (ctx) => {
|
|
271
|
+
// Insert multiple vectors atomically
|
|
272
|
+
for (let i = 0; i < 5; i++) {
|
|
273
|
+
await txManager.execute(ctx, 'INSERT', `doc_${i}`, async () => {
|
|
274
|
+
await bridge.insert('transactions_demo', {
|
|
275
|
+
id: `doc_${i}`,
|
|
276
|
+
embedding: generateEmbedding(config.dimensions),
|
|
277
|
+
metadata: { batch: 1, index: i },
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { inserted: 5 };
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
console.log(` Result: ${simpleResult.success ? 'Committed' : 'Rolled back'}`);
|
|
286
|
+
console.log(` Inserted: ${simpleResult.result?.inserted || 0} vectors\n`);
|
|
287
|
+
|
|
288
|
+
// ========================================================================
|
|
289
|
+
// 3. Transaction with Error - Automatic Rollback
|
|
290
|
+
// ========================================================================
|
|
291
|
+
console.log('3. Transaction with Error (Automatic Rollback)');
|
|
292
|
+
console.log(' ' + '-'.repeat(50));
|
|
293
|
+
|
|
294
|
+
const errorResult = await txManager.run(async (ctx) => {
|
|
295
|
+
// Insert some vectors
|
|
296
|
+
for (let i = 5; i < 8; i++) {
|
|
297
|
+
await txManager.execute(ctx, 'INSERT', `doc_${i}`, async () => {
|
|
298
|
+
await bridge.insert('transactions_demo', {
|
|
299
|
+
id: `doc_${i}`,
|
|
300
|
+
embedding: generateEmbedding(config.dimensions),
|
|
301
|
+
metadata: { batch: 2, index: i },
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Simulate an error
|
|
307
|
+
throw new Error('Simulated processing error');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
console.log(` Result: ${errorResult.success ? 'Committed' : 'Rolled back'}`);
|
|
311
|
+
console.log(` Error: ${errorResult.error?.message}`);
|
|
312
|
+
console.log(` Operations attempted: ${errorResult.context.operations.length}\n`);
|
|
313
|
+
|
|
314
|
+
// ========================================================================
|
|
315
|
+
// 4. Transaction with Savepoints
|
|
316
|
+
// ========================================================================
|
|
317
|
+
console.log('4. Transaction with Savepoints');
|
|
318
|
+
console.log(' ' + '-'.repeat(50));
|
|
319
|
+
|
|
320
|
+
const savepointResult = await txManager.run(async (ctx) => {
|
|
321
|
+
const results: string[] = [];
|
|
322
|
+
|
|
323
|
+
// First batch
|
|
324
|
+
for (let i = 10; i < 13; i++) {
|
|
325
|
+
await txManager.execute(ctx, 'INSERT', `doc_${i}`, async () => {
|
|
326
|
+
await bridge.insert('transactions_demo', {
|
|
327
|
+
id: `doc_${i}`,
|
|
328
|
+
embedding: generateEmbedding(config.dimensions),
|
|
329
|
+
metadata: { batch: 3, index: i },
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
results.push(`doc_${i}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Create savepoint after first batch
|
|
336
|
+
await txManager.savepoint(ctx, 'after_batch_1', 'transactions_demo');
|
|
337
|
+
|
|
338
|
+
// Second batch (will be rolled back)
|
|
339
|
+
try {
|
|
340
|
+
for (let i = 13; i < 16; i++) {
|
|
341
|
+
await txManager.execute(ctx, 'INSERT', `doc_${i}`, async () => {
|
|
342
|
+
await bridge.insert('transactions_demo', {
|
|
343
|
+
id: `doc_${i}`,
|
|
344
|
+
embedding: generateEmbedding(config.dimensions),
|
|
345
|
+
metadata: { batch: 3, index: i },
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Simulate error on specific index
|
|
349
|
+
if (i === 14) {
|
|
350
|
+
throw new Error('Error in second batch');
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
results.push(`doc_${i}`);
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.log(` Error in second batch: ${(error as Error).message}`);
|
|
357
|
+
await txManager.rollbackToSavepoint(ctx, 'after_batch_1');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Third batch (after savepoint rollback)
|
|
361
|
+
for (let i = 20; i < 22; i++) {
|
|
362
|
+
await txManager.execute(ctx, 'INSERT', `doc_${i}`, async () => {
|
|
363
|
+
await bridge.insert('transactions_demo', {
|
|
364
|
+
id: `doc_${i}`,
|
|
365
|
+
embedding: generateEmbedding(config.dimensions),
|
|
366
|
+
metadata: { batch: 3, index: i, afterRollback: true },
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
results.push(`doc_${i}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return { inserted: results };
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
console.log(` Result: ${savepointResult.success ? 'Committed' : 'Rolled back'}`);
|
|
376
|
+
console.log(` Final inserted: ${savepointResult.result?.inserted?.join(', ')}\n`);
|
|
377
|
+
|
|
378
|
+
// ========================================================================
|
|
379
|
+
// 5. Batch Update Transaction
|
|
380
|
+
// ========================================================================
|
|
381
|
+
console.log('5. Batch Update Transaction');
|
|
382
|
+
console.log(' ' + '-'.repeat(50));
|
|
383
|
+
|
|
384
|
+
const updateResult = await txManager.run(async (ctx) => {
|
|
385
|
+
let updated = 0;
|
|
386
|
+
|
|
387
|
+
// Get existing documents
|
|
388
|
+
const searchResults = await bridge.search(
|
|
389
|
+
'transactions_demo',
|
|
390
|
+
generateEmbedding(config.dimensions),
|
|
391
|
+
{ k: 5, includeMetadata: true }
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
// Update each document with new embedding
|
|
395
|
+
for (const result of searchResults) {
|
|
396
|
+
await txManager.execute(ctx, 'UPDATE', result.id, async () => {
|
|
397
|
+
await bridge.update('transactions_demo', result.id, {
|
|
398
|
+
embedding: generateEmbedding(config.dimensions),
|
|
399
|
+
metadata: {
|
|
400
|
+
...result.metadata,
|
|
401
|
+
updatedAt: new Date().toISOString(),
|
|
402
|
+
version: ((result.metadata?.version as number) || 0) + 1,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
updated++;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { updated };
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
console.log(` Result: ${updateResult.success ? 'Committed' : 'Rolled back'}`);
|
|
413
|
+
console.log(` Updated: ${updateResult.result?.updated || 0} vectors\n`);
|
|
414
|
+
|
|
415
|
+
// ========================================================================
|
|
416
|
+
// 6. Conditional Transaction
|
|
417
|
+
// ========================================================================
|
|
418
|
+
console.log('6. Conditional Transaction (with validation)');
|
|
419
|
+
console.log(' ' + '-'.repeat(50));
|
|
420
|
+
|
|
421
|
+
const conditionalResult = await txManager.run(async (ctx) => {
|
|
422
|
+
// Check precondition
|
|
423
|
+
const stats = await bridge.getCollectionStats('transactions_demo');
|
|
424
|
+
console.log(` Current vector count: ${stats.vectorCount}`);
|
|
425
|
+
|
|
426
|
+
if (stats.vectorCount > 100) {
|
|
427
|
+
throw new Error('Collection already has too many vectors');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Proceed with inserts if validation passes
|
|
431
|
+
for (let i = 30; i < 35; i++) {
|
|
432
|
+
await txManager.execute(ctx, 'INSERT', `cond_${i}`, async () => {
|
|
433
|
+
await bridge.insert('transactions_demo', {
|
|
434
|
+
id: `cond_${i}`,
|
|
435
|
+
embedding: generateEmbedding(config.dimensions),
|
|
436
|
+
metadata: { batch: 'conditional', index: i },
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return { inserted: 5 };
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
console.log(` Result: ${conditionalResult.success ? 'Committed' : 'Rolled back'}`);
|
|
445
|
+
if (conditionalResult.error) {
|
|
446
|
+
console.log(` Reason: ${conditionalResult.error.message}`);
|
|
447
|
+
} else {
|
|
448
|
+
console.log(` Inserted: ${conditionalResult.result?.inserted || 0} vectors`);
|
|
449
|
+
}
|
|
450
|
+
console.log();
|
|
451
|
+
|
|
452
|
+
// ========================================================================
|
|
453
|
+
// 7. Retry with Exponential Backoff
|
|
454
|
+
// ========================================================================
|
|
455
|
+
console.log('7. Transaction with Retry (Exponential Backoff)');
|
|
456
|
+
console.log(' ' + '-'.repeat(50));
|
|
457
|
+
|
|
458
|
+
const maxRetries = 3;
|
|
459
|
+
let retryCount = 0;
|
|
460
|
+
let finalSuccess = false;
|
|
461
|
+
|
|
462
|
+
while (retryCount < maxRetries && !finalSuccess) {
|
|
463
|
+
const attemptResult = await txManager.run(async (ctx) => {
|
|
464
|
+
// Simulate occasional failures
|
|
465
|
+
const shouldFail = retryCount < 2 && Math.random() > 0.5;
|
|
466
|
+
|
|
467
|
+
for (let i = 40; i < 42; i++) {
|
|
468
|
+
await txManager.execute(ctx, 'INSERT', `retry_${i}`, async () => {
|
|
469
|
+
if (shouldFail && i === 41) {
|
|
470
|
+
throw new Error('Transient error');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
await bridge.insert('transactions_demo', {
|
|
474
|
+
id: `retry_${retryCount}_${i}`,
|
|
475
|
+
embedding: generateEmbedding(config.dimensions),
|
|
476
|
+
metadata: { batch: 'retry', attempt: retryCount },
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return { inserted: 2 };
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (attemptResult.success) {
|
|
485
|
+
finalSuccess = true;
|
|
486
|
+
console.log(` Success on attempt ${retryCount + 1}`);
|
|
487
|
+
} else {
|
|
488
|
+
retryCount++;
|
|
489
|
+
if (retryCount < maxRetries) {
|
|
490
|
+
const backoffMs = Math.pow(2, retryCount) * 100;
|
|
491
|
+
console.log(` Attempt ${retryCount} failed, retrying in ${backoffMs}ms...`);
|
|
492
|
+
await sleep(backoffMs);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log(` Final result: ${finalSuccess ? 'Success' : 'Failed after ' + maxRetries + ' attempts'}\n`);
|
|
498
|
+
|
|
499
|
+
// ========================================================================
|
|
500
|
+
// 8. Optimistic Locking Pattern
|
|
501
|
+
// ========================================================================
|
|
502
|
+
console.log('8. Optimistic Locking Pattern');
|
|
503
|
+
console.log(' ' + '-'.repeat(50));
|
|
504
|
+
|
|
505
|
+
// Insert a document with version
|
|
506
|
+
await bridge.insert('transactions_demo', {
|
|
507
|
+
id: 'optimistic_lock_test',
|
|
508
|
+
embedding: generateEmbedding(config.dimensions),
|
|
509
|
+
metadata: { version: 1, data: 'initial' },
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const optimisticResult = await txManager.run(async (ctx) => {
|
|
513
|
+
// Read current version
|
|
514
|
+
const current = await bridge.get('transactions_demo', 'optimistic_lock_test');
|
|
515
|
+
if (!current) throw new Error('Document not found');
|
|
516
|
+
|
|
517
|
+
const currentVersion = (current.metadata?.version as number) || 0;
|
|
518
|
+
console.log(` Current version: ${currentVersion}`);
|
|
519
|
+
|
|
520
|
+
// Simulate concurrent modification check
|
|
521
|
+
// In production, use a WHERE clause with version check
|
|
522
|
+
const expectedVersion = currentVersion;
|
|
523
|
+
|
|
524
|
+
// Update with version increment
|
|
525
|
+
await txManager.execute(ctx, 'UPDATE', 'optimistic_lock_test', async () => {
|
|
526
|
+
// Verify version hasn't changed (optimistic lock check)
|
|
527
|
+
const recheck = await bridge.get('transactions_demo', 'optimistic_lock_test');
|
|
528
|
+
if ((recheck?.metadata?.version as number) !== expectedVersion) {
|
|
529
|
+
throw new Error('Optimistic lock failed: version mismatch');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
await bridge.update('transactions_demo', 'optimistic_lock_test', {
|
|
533
|
+
embedding: generateEmbedding(config.dimensions),
|
|
534
|
+
metadata: {
|
|
535
|
+
version: expectedVersion + 1,
|
|
536
|
+
data: 'updated',
|
|
537
|
+
updatedAt: new Date().toISOString(),
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
return { newVersion: expectedVersion + 1 };
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
console.log(` Result: ${optimisticResult.success ? 'Committed' : 'Rolled back'}`);
|
|
546
|
+
if (optimisticResult.success) {
|
|
547
|
+
console.log(` New version: ${optimisticResult.result?.newVersion}`);
|
|
548
|
+
}
|
|
549
|
+
console.log();
|
|
550
|
+
|
|
551
|
+
// ========================================================================
|
|
552
|
+
// 9. Transaction Metrics
|
|
553
|
+
// ========================================================================
|
|
554
|
+
console.log('9. Transaction Summary Metrics');
|
|
555
|
+
console.log(' ' + '-'.repeat(50));
|
|
556
|
+
|
|
557
|
+
// Collect metrics from all transactions
|
|
558
|
+
const stats = await bridge.getCollectionStats('transactions_demo');
|
|
559
|
+
|
|
560
|
+
console.log(` Final collection state:`);
|
|
561
|
+
console.log(` Total vectors: ${stats.vectorCount}`);
|
|
562
|
+
console.log(` Index type: ${stats.indexType}`);
|
|
563
|
+
console.log(` Index size: ${(stats.indexSizeBytes / 1024).toFixed(2)} KB`);
|
|
564
|
+
console.log();
|
|
565
|
+
|
|
566
|
+
// ========================================================================
|
|
567
|
+
// 10. Cleanup
|
|
568
|
+
// ========================================================================
|
|
569
|
+
console.log('10. Cleanup');
|
|
570
|
+
console.log(' ' + '-'.repeat(50));
|
|
571
|
+
|
|
572
|
+
// Uncomment to drop the collection
|
|
573
|
+
// await bridge.dropCollection('transactions_demo');
|
|
574
|
+
// console.log(' Collection dropped');
|
|
575
|
+
|
|
576
|
+
console.log(' Skipping collection drop (uncomment to enable)');
|
|
577
|
+
|
|
578
|
+
// ========================================================================
|
|
579
|
+
// Done
|
|
580
|
+
// ========================================================================
|
|
581
|
+
console.log('\n' + '='.repeat(55));
|
|
582
|
+
console.log('Transactions example completed!');
|
|
583
|
+
console.log('='.repeat(55));
|
|
584
|
+
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.error('Error:', error);
|
|
587
|
+
throw error;
|
|
588
|
+
} finally {
|
|
589
|
+
await bridge.disconnect();
|
|
590
|
+
console.log('\nDisconnected from PostgreSQL.');
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
main().catch(console.error);
|