Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.
Transactions
Learn how to use transactions to ensure data consistency and atomicity when performing multiple graph operations.
Quick Example
- TypeScript
- Java
- Go
- .NET
// All operations succeed or all fail together
await ductape.graph.executeTransaction(async (tx) => {
// Create user node
const user = await ductape.graph.createNode({
labels: ['User'],
properties: { name: 'Alice', email: 'alice@example.com' },
}, tx);
// Create profile node
const profile = await ductape.graph.createNode({
labels: ['Profile'],
properties: { bio: 'Software Engineer' },
}, tx);
// Link them with a relationship
await ductape.graph.createRelationship({
type: 'HAS_PROFILE',
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// If any operation fails, all changes are rolled back
});
// All operations succeed or all fail together
ductape.graph.executeTransaction(async (tx) => Map.of(
// Create user node
Map<String, Object> user = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of( "name", "Alice", "email", "alice@example.com" )
), tx);
// Create profile node
Map<String, Object> profile = ductape.graph.createNode(Map.of(
labels: ['Profile'],
properties: Map.of( "bio", "Software Engineer" )
), tx);
// Link them with a relationship
ductape.graph.createRelationship(Map.of(
"type", "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id
), tx);
// If any operation fails, all changes are rolled back
));
// All operations succeed or all fail together
client.graph.executeTransaction(async (tx) => {
// Create user node
user := client.graph.createNode({
labels: ['User'],
properties: { "name": "Alice", "email": "alice@example.com" },
}, tx);
// Create profile node
profile := client.graph.createNode({
labels: ['Profile'],
properties: { "bio": "Software Engineer" },
}, tx);
// Link them with a relationship
client.graph.createRelationship({
"type": "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// If any operation fails, all changes are rolled back
});
// All operations succeed or all fail together
await ductape.graph.executeTransaction(async (tx) => {
// Create user node
var user = await ductape.graph.createNode({
labels: ['User'],
properties: { ["name"] = "Alice", ["email"] = "alice@example.com" },
}, tx);
// Create profile node
var profile = await ductape.graph.createNode({
labels: ['Profile'],
properties: { ["bio"] = "Software Engineer" },
}, tx);
// Link them with a relationship
await ductape.graph.createRelationship({
["type"] = "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// If any operation fails, all changes are rolled back
});
Why Use Transactions?
Transactions ensure ACID properties:
- Atomicity: All operations succeed or all fail - no partial updates
- Consistency: Database moves from one valid state to another
- Isolation: Concurrent transactions don't interfere
- Durability: Committed changes are permanent
Without Transactions (Dangerous)
- TypeScript
- Java
- Go
- .NET
// Create user
const user = await ductape.graph.createNode({
labels: ['User'],
properties: { name: 'Bob', balance: 1000 },
});
// Transfer money - WHAT IF THIS FAILS?
const recipient = await ductape.graph.findNodeById('recipient-id');
// Update user balance
await ductape.graph.updateNode({
id: user.node.id,
properties: { balance: 500 },
});
// ❌ System crashes here - money is lost!
// Update recipient balance (never executed)
await ductape.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
});
// Create user
Map<String, Object> user = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of( "name", "Bob", "balance", 1000 )
));
// Transfer money - WHAT IF THIS FAILS?
Map<String, Object> recipient = ductape.graph.findNodeById('recipient-id');
// Update user balance
ductape.graph.updateNode(Map.of(
id: user.node.id,
properties: Map.of( "balance", 500 )
));
// ❌ System crashes here - money is lost!
// Update recipient balance (never executed)
ductape.graph.updateNode(Map.of(
id: recipient.id,
properties: Map.of( balance: recipient.properties.balance + 500 )
));
// Create user
user := client.graph.createNode({
labels: ['User'],
properties: { "name": "Bob", "balance": 1000 },
});
// Transfer money - WHAT IF THIS FAILS?
recipient := client.graph.findNodeById('recipient-id');
// Update user balance
client.graph.updateNode({
id: user.node.id,
properties: { "balance": 500 },
});
// ❌ System crashes here - money is lost!
// Update recipient balance (never executed)
client.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
});
// Create user
var user = await ductape.graph.createNode({
labels: ['User'],
properties: { ["name"] = "Bob", ["balance"] = 1000 },
});
// Transfer money - WHAT IF THIS FAILS?
var recipient = await ductape.graph.findNodeById('recipient-id');
// Update user balance
await ductape.graph.updateNode({
id: user.node.id,
properties: { ["balance"] = 500 },
});
// ❌ System crashes here - money is lost!
// Update recipient balance (never executed)
await ductape.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
});
With Transactions (Safe)
- TypeScript
- Java
- Go
- .NET
await ductape.graph.executeTransaction(async (tx) => {
// Get user
const user = await ductape.graph.findNodeById('user-id', tx);
// Get recipient
const recipient = await ductape.graph.findNodeById('recipient-id', tx);
// Deduct from user
await ductape.graph.updateNode({
id: user.id,
properties: { balance: user.properties.balance - 500 },
}, tx);
// Add to recipient
await ductape.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
}, tx);
// Both updates happen or neither happens
});
ductape.graph.executeTransaction(async (tx) => Map.of(
// Get user
Map<String, Object> user = ductape.graph.findNodeById('user-id', tx);
// Get recipient
Map<String, Object> recipient = ductape.graph.findNodeById('recipient-id', tx);
// Deduct from user
ductape.graph.updateNode(Map.of(
id: user.id,
properties: Map.of( balance: user.properties.balance - 500 )
), tx);
// Add to recipient
ductape.graph.updateNode(Map.of(
id: recipient.id,
properties: Map.of( balance: recipient.properties.balance + 500 )
), tx);
// Both updates happen or neither happens
));
client.graph.executeTransaction(async (tx) => {
// Get user
user := client.graph.findNodeById('user-id', tx);
// Get recipient
recipient := client.graph.findNodeById('recipient-id', tx);
// Deduct from user
client.graph.updateNode({
id: user.id,
properties: { balance: user.properties.balance - 500 },
}, tx);
// Add to recipient
client.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
}, tx);
// Both updates happen or neither happens
});
await ductape.graph.executeTransaction(async (tx) => {
// Get user
var user = await ductape.graph.findNodeById('user-id', tx);
// Get recipient
var recipient = await ductape.graph.findNodeById('recipient-id', tx);
// Deduct from user
await ductape.graph.updateNode({
id: user.id,
properties: { balance: user.properties.balance - 500 },
}, tx);
// Add to recipient
await ductape.graph.updateNode({
id: recipient.id,
properties: { balance: recipient.properties.balance + 500 },
}, tx);
// Both updates happen or neither happens
});
Execute Transaction (Recommended)
The easiest way to use transactions - automatically handles commit/rollback:
- TypeScript
- Java
- Go
- .NET
const result = await ductape.graph.executeTransaction(async (tx) => {
// All operations use the same transaction
const node1 = await ductape.graph.createNode({
labels: ['Product'],
properties: { name: 'Laptop', price: 999 },
}, tx);
const node2 = await ductape.graph.createNode({
labels: ['Category'],
properties: { name: 'Electronics' },
}, tx);
await ductape.graph.createRelationship({
type: 'IN_CATEGORY',
startNodeId: node1.node.id,
endNodeId: node2.node.id,
}, tx);
// Return any value you want
return { productId: node1.node.id, categoryId: node2.node.id };
});
console.log('Created:', result);
// Automatically committed if no errors
// Automatically rolled back if any error occurs
Map<String, Object> result = ductape.graph.executeTransaction(async (tx) => Map.of(
// All operations use the same transaction
Map<String, Object> node1 = ductape.graph.createNode(Map.of(
labels: ['Product'],
properties: Map.of( "name", "Laptop", "price", 999 )
), tx);
Map<String, Object> node2 = ductape.graph.createNode(Map.of(
labels: ['Category'],
properties: Map.of( "name", "Electronics" )
), tx);
ductape.graph.createRelationship(Map.of(
"type", "IN_CATEGORY",
startNodeId: node1.node.id,
endNodeId: node2.node.id
), tx);
// Return any value you want
return Map.of( productId: node1.node.id, categoryId: node2.node.id );
));
System.out.println('Created:', result);
// Automatically committed if no errors
// Automatically rolled back if any error occurs
result := client.graph.executeTransaction(async (tx) => {
// All operations use the same transaction
node1 := client.graph.createNode({
labels: ['Product'],
properties: { "name": "Laptop", "price": 999 },
}, tx);
node2 := client.graph.createNode({
labels: ['Category'],
properties: { "name": "Electronics" },
}, tx);
client.graph.createRelationship({
"type": "IN_CATEGORY",
startNodeId: node1.node.id,
endNodeId: node2.node.id,
}, tx);
// Return any value you want
return { productId: node1.node.id, categoryId: node2.node.id };
});
fmt.Println('Created:', result);
// Automatically committed if no errors
// Automatically rolled back if any error occurs
var result = await ductape.graph.executeTransaction(async (tx) => {
// All operations use the same transaction
var node1 = await ductape.graph.createNode({
labels: ['Product'],
properties: { ["name"] = "Laptop", ["price"] = 999 },
}, tx);
var node2 = await ductape.graph.createNode({
labels: ['Category'],
properties: { ["name"] = "Electronics" },
}, tx);
await ductape.graph.createRelationship({
["type"] = "IN_CATEGORY",
startNodeId: node1.node.id,
endNodeId: node2.node.id,
}, tx);
// Return any value you want
return { productId: node1.node.id, categoryId: node2.node.id };
});
Console.WriteLine('Created:', result);
// Automatically committed if no errors
// Automatically rolled back if any error occurs
Error Handling
- TypeScript
- Java
- Go
- .NET
try {
await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({
labels: ['User'],
properties: { email: 'alice@example.com' },
}, tx);
// This might fail (e.g., constraint violation)
await ductape.graph.createNode({
labels: ['User'],
properties: { email: 'alice@example.com' }, // Duplicate!
}, tx);
// Transaction automatically rolls back on error
});
} catch (error) {
console.log('Transaction failed, all changes rolled back');
console.error(error.message);
}
try Map.of(
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of( "email", "alice@example.com" )
), tx);
// This might fail (e.g., constraint violation)
ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of( "email", "alice@example.com" ), // Duplicate!
), tx);
// Transaction automatically rolls back on error
));
) catch (error) Map.of(
System.out.println('Transaction failed, all changes rolled back');
console.error(error.message);
)
try {
client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({
labels: ['User'],
properties: { "email": "alice@example.com" },
}, tx);
// This might fail (e.g., constraint violation)
client.graph.createNode({
labels: ['User'],
properties: { "email": "alice@example.com" }, // Duplicate!
}, tx);
// Transaction automatically rolls back on error
});
} catch (error) {
fmt.Println('Transaction failed, all changes rolled back');
console.error(error.message);
}
try {
await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({
labels: ['User'],
properties: { ["email"] = "alice@example.com" },
}, tx);
// This might fail (e.g., constraint violation)
await ductape.graph.createNode({
labels: ['User'],
properties: { ["email"] = "alice@example.com" }, // Duplicate!
}, tx);
// Transaction automatically rolls back on error
});
} catch (error) {
Console.WriteLine('Transaction failed, all changes rolled back');
console.error(error.message);
}
Manual Transaction Control
For more control, manage transactions manually:
Basic Pattern
- TypeScript
- Java
- Go
- .NET
// Begin transaction
const tx = await ductape.graph.beginTransaction();
try {
// Perform operations
const node = await ductape.graph.createNode({
labels: ['User'],
properties: { name: 'Charlie' },
}, tx);
const relationship = await ductape.graph.createRelationship({
type: 'FOLLOWS',
startNodeId: node.node.id,
endNodeId: 'another-node-id',
}, tx);
// Commit transaction
await ductape.graph.commitTransaction(tx);
console.log('Transaction committed successfully');
} catch (error) {
// Rollback on error
await ductape.graph.rollbackTransaction(tx);
console.error('Transaction rolled back:', error.message);
throw error;
}
// Begin transaction
Map<String, Object> tx = ductape.graph.beginTransaction();
try Map.of(
// Perform operations
Map<String, Object> node = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of( "name", "Charlie" )
), tx);
Map<String, Object> relationship = ductape.graph.createRelationship(Map.of(
"type", "FOLLOWS",
startNodeId: node.node.id,
"endNodeId", "another-node-id"
), tx);
// Commit transaction
ductape.graph.commitTransaction(tx);
System.out.println('Transaction committed successfully');
) catch (error) Map.of(
// Rollback on error
ductape.graph.rollbackTransaction(tx);
console.error('Transaction rolled back:', error.message);
throw error;
)
// Begin transaction
tx := client.graph.beginTransaction();
try {
// Perform operations
node := client.graph.createNode({
labels: ['User'],
properties: { "name": "Charlie" },
}, tx);
relationship := client.graph.createRelationship({
"type": "FOLLOWS",
startNodeId: node.node.id,
"endNodeId": "another-node-id",
}, tx);
// Commit transaction
client.graph.commitTransaction(tx);
fmt.Println('Transaction committed successfully');
} catch (error) {
// Rollback on error
client.graph.rollbackTransaction(tx);
console.error('Transaction rolled back:', error.message);
throw error;
}
// Begin transaction
var tx = await ductape.graph.beginTransaction();
try {
// Perform operations
var node = await ductape.graph.createNode({
labels: ['User'],
properties: { ["name"] = "Charlie" },
}, tx);
var relationship = await ductape.graph.createRelationship({
["type"] = "FOLLOWS",
startNodeId: node.node.id,
["endNodeId"] = "another-node-id",
}, tx);
// Commit transaction
await ductape.graph.commitTransaction(tx);
Console.WriteLine('Transaction committed successfully');
} catch (error) {
// Rollback on error
await ductape.graph.rollbackTransaction(tx);
console.error('Transaction rolled back:', error.message);
throw error;
}
With Finally Block
- TypeScript
- Java
- Go
- .NET
const tx = await ductape.graph.beginTransaction();
try {
// Your operations here
await ductape.graph.createNode({
labels: ['Article'],
properties: { title: 'Graph Databases' },
}, tx);
await ductape.graph.commitTransaction(tx);
} catch (error) {
await ductape.graph.rollbackTransaction(tx);
throw error;
} finally {
// Clean up resources if needed
console.log('Transaction completed');
}
Map<String, Object> tx = ductape.graph.beginTransaction();
try Map.of(
// Your operations here
ductape.graph.createNode(Map.of(
labels: ['Article'],
properties: Map.of( "title", "Graph Databases" )
), tx);
ductape.graph.commitTransaction(tx);
) catch (error) Map.of(
ductape.graph.rollbackTransaction(tx);
throw error;
) finally Map.of(
// Clean up resources if needed
System.out.println('Transaction completed');
)
tx := client.graph.beginTransaction();
try {
// Your operations here
client.graph.createNode({
labels: ['Article'],
properties: { "title": "Graph Databases" },
}, tx);
client.graph.commitTransaction(tx);
} catch (error) {
client.graph.rollbackTransaction(tx);
throw error;
} finally {
// Clean up resources if needed
fmt.Println('Transaction completed');
}
var tx = await ductape.graph.beginTransaction();
try {
// Your operations here
await ductape.graph.createNode({
labels: ['Article'],
properties: { ["title"] = "Graph Databases" },
}, tx);
await ductape.graph.commitTransaction(tx);
} catch (error) {
await ductape.graph.rollbackTransaction(tx);
throw error;
} finally {
// Clean up resources if needed
Console.WriteLine('Transaction completed');
}
Transaction Options
Isolation Levels
- TypeScript
- Java
- Go
- .NET
await ductape.graph.executeTransaction(
async (tx) => {
// Your operations
},
{
isolation: 'READ_COMMITTED', // or 'SERIALIZABLE'
}
);
ductape.graph.executeTransaction(
async (tx) => Map.of(
// Your operations
),
Map.of(
"isolation", "READ_COMMITTED", // or 'SERIALIZABLE'
)
);
client.graph.executeTransaction(
async (tx) => {
// Your operations
},
{
"isolation": "READ_COMMITTED", // or 'SERIALIZABLE'
}
);
await ductape.graph.executeTransaction(
async (tx) => {
// Your operations
},
{
["isolation"] = "READ_COMMITTED", // or 'SERIALIZABLE'
}
);
Isolation Levels:
READ_COMMITTED: Prevents dirty reads (default)SERIALIZABLE: Highest isolation, prevents all anomalies
Timeout
- TypeScript
- Java
- Go
- .NET
await ductape.graph.executeTransaction(
async (tx) => {
// Long-running operations
},
{
timeout: 30000, // 30 seconds
}
);
ductape.graph.executeTransaction(
async (tx) => Map.of(
// Long-running operations
),
Map.of(
"timeout", 30000, // 30 seconds
)
);
client.graph.executeTransaction(
async (tx) => {
// Long-running operations
},
{
"timeout": 30000, // 30 seconds
}
);
await ductape.graph.executeTransaction(
async (tx) => {
// Long-running operations
},
{
["timeout"] = 30000, // 30 seconds
}
);
Read-Only Transactions
- TypeScript
- Java
- Go
- .NET
await ductape.graph.executeTransaction(
async (tx) => {
// Only read operations
const users = await ductape.graph.findNodes({
labels: ['User'],
}, tx);
const stats = await ductape.graph.getStatistics(tx);
return { users, stats };
},
{
readOnly: true, // Optimizes for read performance
}
);
ductape.graph.executeTransaction(
async (tx) => Map.of(
// Only read operations
Map<String, Object> users = ductape.graph.findNodes(Map.of(
labels: ['User']
), tx);
Map<String, Object> stats = ductape.graph.getStatistics(tx);
return Map.of( users, stats );
),
Map.of(
"readOnly", true, // Optimizes for read performance
)
);
client.graph.executeTransaction(
async (tx) => {
// Only read operations
users := client.graph.findNodes({
labels: ['User'],
}, tx);
stats := client.graph.getStatistics(tx);
return { users, stats };
},
{
"readOnly": true, // Optimizes for read performance
}
);
await ductape.graph.executeTransaction(
async (tx) => {
// Only read operations
var users = await ductape.graph.findNodes({
labels: ['User'],
}, tx);
var stats = await ductape.graph.getStatistics(tx);
return { users, stats };
},
{
["readOnly"] = true, // Optimizes for read performance
}
);
Common Patterns
User Registration
- TypeScript
- Java
- Go
- .NET
async function registerUser(email: string, name: string, password: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Check if user exists
const existing = await ductape.graph.findNodes({
labels: ['User'],
where: { email },
limit: 1,
}, tx);
if (existing.nodes.length > 0) {
throw new Error('Email already registered');
}
// Create user node
const user = await ductape.graph.createNode({
labels: ['User'],
properties: {
email,
name,
passwordHash: hashPassword(password),
createdAt: new Date(),
},
}, tx);
// Create profile node
const profile = await ductape.graph.createNode({
labels: ['Profile'],
properties: {
userId: user.node.id,
avatar: null,
bio: '',
},
}, tx);
// Link user to profile
await ductape.graph.createRelationship({
type: 'HAS_PROFILE',
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// Create default settings
const settings = await ductape.graph.createNode({
labels: ['Settings'],
properties: {
theme: 'light',
notifications: true,
},
}, tx);
// Link user to settings
await ductape.graph.createRelationship({
type: 'HAS_SETTINGS',
startNodeId: user.node.id,
endNodeId: settings.node.id,
}, tx);
return user.node;
});
}
async function registerUser(email: string, name: string, password: string) Map.of(
return ductape.graph.executeTransaction(async (tx) => Map.of(
// Check if user exists
Map<String, Object> existing = ductape.graph.findNodes(Map.of(
labels: ['User'],
where: Map.of( email ),
"limit", 1
), tx);
if (existing.nodes.length > 0) Map.of(
throw new Error('Email already registered');
)
// Create user node
Map<String, Object> user = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of(
email,
name,
passwordHash: hashPassword(password),
createdAt: Instant.now()
)
), tx);
// Create profile node
Map<String, Object> profile = ductape.graph.createNode(Map.of(
labels: ['Profile'],
properties: Map.of(
userId: user.node.id,
avatar: null,
"bio", ""
)
), tx);
// Link user to profile
ductape.graph.createRelationship(Map.of(
"type", "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id
), tx);
// Create default settings
Map<String, Object> settings = ductape.graph.createNode(Map.of(
labels: ['Settings'],
properties: Map.of(
"theme", "light",
"notifications", true
)
), tx);
// Link user to settings
ductape.graph.createRelationship(Map.of(
"type", "HAS_SETTINGS",
startNodeId: user.node.id,
endNodeId: settings.node.id
), tx);
return user.node;
));
)
async function registerUser(email: string, name: string, password: string) {
return client.graph.executeTransaction(async (tx) => {
// Check if user exists
existing := client.graph.findNodes({
labels: ['User'],
where: { email },
"limit": 1,
}, tx);
if (existing.nodes.length > 0) {
throw new Error('Email already registered');
}
// Create user node
user := client.graph.createNode({
labels: ['User'],
properties: {
email,
name,
passwordHash: hashPassword(password),
createdAt: new Date(),
},
}, tx);
// Create profile node
profile := client.graph.createNode({
labels: ['Profile'],
properties: {
userId: user.node.id,
avatar: null,
"bio": "",
},
}, tx);
// Link user to profile
client.graph.createRelationship({
"type": "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// Create default settings
settings := client.graph.createNode({
labels: ['Settings'],
properties: {
"theme": "light",
"notifications": true,
},
}, tx);
// Link user to settings
client.graph.createRelationship({
"type": "HAS_SETTINGS",
startNodeId: user.node.id,
endNodeId: settings.node.id,
}, tx);
return user.node;
});
}
async function registerUser(email: string, name: string, password: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Check if user exists
var existing = await ductape.graph.findNodes({
labels: ['User'],
where: { email },
["limit"] = 1,
}, tx);
if (existing.nodes.length > 0) {
throw new Error('Email already registered');
}
// Create user node
var user = await ductape.graph.createNode({
labels: ['User'],
properties: {
email,
name,
passwordHash: hashPassword(password),
createdAt: DateTime.UtcNow,
},
}, tx);
// Create profile node
var profile = await ductape.graph.createNode({
labels: ['Profile'],
properties: {
userId: user.node.id,
avatar: null,
["bio"] = "",
},
}, tx);
// Link user to profile
await ductape.graph.createRelationship({
["type"] = "HAS_PROFILE",
startNodeId: user.node.id,
endNodeId: profile.node.id,
}, tx);
// Create default settings
var settings = await ductape.graph.createNode({
labels: ['Settings'],
properties: {
["theme"] = "light",
["notifications"] = true,
},
}, tx);
// Link user to settings
await ductape.graph.createRelationship({
["type"] = "HAS_SETTINGS",
startNodeId: user.node.id,
endNodeId: settings.node.id,
}, tx);
return user.node;
});
}
Money Transfer
- TypeScript
- Java
- Go
- .NET
async function transferMoney(
fromUserId: string,
toUserId: string,
amount: number
) {
return ductape.graph.executeTransaction(async (tx) => {
// Lock and fetch both accounts
const fromUser = await ductape.graph.findNodeById(fromUserId, tx);
const toUser = await ductape.graph.findNodeById(toUserId, tx);
if (!fromUser || !toUser) {
throw new Error('User not found');
}
// Check sufficient balance
if (fromUser.properties.balance < amount) {
throw new Error('Insufficient funds');
}
// Deduct from sender
await ductape.graph.updateNode({
id: fromUserId,
properties: {
balance: fromUser.properties.balance - amount,
},
}, tx);
// Add to recipient
await ductape.graph.updateNode({
id: toUserId,
properties: {
balance: toUser.properties.balance + amount,
},
}, tx);
// Create transaction record
const transaction = await ductape.graph.createNode({
labels: ['Transaction'],
properties: {
amount,
timestamp: new Date(),
type: 'transfer',
},
}, tx);
// Link transaction to users
await ductape.graph.createRelationship({
type: 'SENT',
startNodeId: fromUserId,
endNodeId: transaction.node.id,
}, tx);
await ductape.graph.createRelationship({
type: 'RECEIVED',
startNodeId: toUserId,
endNodeId: transaction.node.id,
}, tx);
return transaction.node;
});
}
async function transferMoney(
fromUserId: string,
toUserId: string,
amount: number
) Map.of(
return ductape.graph.executeTransaction(async (tx) => Map.of(
// Lock and fetch both accounts
Map<String, Object> fromUser = ductape.graph.findNodeById(fromUserId, tx);
Map<String, Object> toUser = ductape.graph.findNodeById(toUserId, tx);
if (!fromUser || !toUser) Map.of(
throw new Error('User not found');
)
// Check sufficient balance
if (fromUser.properties.balance < amount) Map.of(
throw new Error('Insufficient funds');
)
// Deduct from sender
ductape.graph.updateNode(Map.of(
id: fromUserId,
properties: Map.of(
balance: fromUser.properties.balance - amount
)
), tx);
// Add to recipient
ductape.graph.updateNode(Map.of(
id: toUserId,
properties: Map.of(
balance: toUser.properties.balance + amount
)
), tx);
// Create transaction record
Map<String, Object> transaction = ductape.graph.createNode(Map.of(
labels: ['Transaction'],
properties: Map.of(
amount,
timestamp: Instant.now(),
"type", "transfer"
)
), tx);
// Link transaction to users
ductape.graph.createRelationship(Map.of(
"type", "SENT",
startNodeId: fromUserId,
endNodeId: transaction.node.id
), tx);
ductape.graph.createRelationship(Map.of(
"type", "RECEIVED",
startNodeId: toUserId,
endNodeId: transaction.node.id
), tx);
return transaction.node;
));
)
async function transferMoney(
fromUserId: string,
toUserId: string,
amount: number
) {
return client.graph.executeTransaction(async (tx) => {
// Lock and fetch both accounts
fromUser := client.graph.findNodeById(fromUserId, tx);
toUser := client.graph.findNodeById(toUserId, tx);
if (!fromUser || !toUser) {
throw new Error('User not found');
}
// Check sufficient balance
if (fromUser.properties.balance < amount) {
throw new Error('Insufficient funds');
}
// Deduct from sender
client.graph.updateNode({
id: fromUserId,
properties: {
balance: fromUser.properties.balance - amount,
},
}, tx);
// Add to recipient
client.graph.updateNode({
id: toUserId,
properties: {
balance: toUser.properties.balance + amount,
},
}, tx);
// Create transaction record
transaction := client.graph.createNode({
labels: ['Transaction'],
properties: {
amount,
timestamp: new Date(),
"type": "transfer",
},
}, tx);
// Link transaction to users
client.graph.createRelationship({
"type": "SENT",
startNodeId: fromUserId,
endNodeId: transaction.node.id,
}, tx);
client.graph.createRelationship({
"type": "RECEIVED",
startNodeId: toUserId,
endNodeId: transaction.node.id,
}, tx);
return transaction.node;
});
}
async function transferMoney(
fromUserId: string,
toUserId: string,
amount: number
) {
return ductape.graph.executeTransaction(async (tx) => {
// Lock and fetch both accounts
var fromUser = await ductape.graph.findNodeById(fromUserId, tx);
var toUser = await ductape.graph.findNodeById(toUserId, tx);
if (!fromUser || !toUser) {
throw new Error('User not found');
}
// Check sufficient balance
if (fromUser.properties.balance < amount) {
throw new Error('Insufficient funds');
}
// Deduct from sender
await ductape.graph.updateNode({
id: fromUserId,
properties: {
balance: fromUser.properties.balance - amount,
},
}, tx);
// Add to recipient
await ductape.graph.updateNode({
id: toUserId,
properties: {
balance: toUser.properties.balance + amount,
},
}, tx);
// Create transaction record
var transaction = await ductape.graph.createNode({
labels: ['Transaction'],
properties: {
amount,
timestamp: DateTime.UtcNow,
["type"] = "transfer",
},
}, tx);
// Link transaction to users
await ductape.graph.createRelationship({
["type"] = "SENT",
startNodeId: fromUserId,
endNodeId: transaction.node.id,
}, tx);
await ductape.graph.createRelationship({
["type"] = "RECEIVED",
startNodeId: toUserId,
endNodeId: transaction.node.id,
}, tx);
return transaction.node;
});
}
Batch Operations
- TypeScript
- Java
- Go
- .NET
async function createMultipleUsersWithRelationships(
users: Array<{ name: string; email: string }>
) {
return ductape.graph.executeTransaction(async (tx) => {
const createdNodes = [];
// Create all user nodes
for (const userData of users) {
const user = await ductape.graph.createNode({
labels: ['User'],
properties: {
...userData,
createdAt: new Date(),
},
}, tx);
createdNodes.push(user.node);
}
// Create relationships between consecutive users
for (let i = 0; i < createdNodes.length - 1; i++) {
await ductape.graph.createRelationship({
type: 'INVITED_BY',
startNodeId: createdNodes[i + 1].id,
endNodeId: createdNodes[i].id,
properties: {
invitedAt: new Date(),
},
}, tx);
}
return createdNodes;
});
}
async function createMultipleUsersWithRelationships(
users: Array<Map.of( name: string; email: string )>
) Map.of(
return ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> createdNodes = [];
// Create all user nodes
for (Map<String, Object> userData of users) Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of(
labels: ['User'],
properties: Map.of(
...userData,
createdAt: Instant.now()
)
), tx);
createdNodes.push(user.node);
)
// Create relationships between consecutive users
for (Map<String, Object> i = 0; i < createdNodes.length - 1; i++) Map.of(
ductape.graph.createRelationship(Map.of(
"type", "INVITED_BY",
startNodeId: createdNodes[i + 1].id,
endNodeId: createdNodes[i].id,
properties: Map.of(
invitedAt: Instant.now()
)
), tx);
)
return createdNodes;
));
)
async function createMultipleUsersWithRelationships(
users: Array<{ name: string; email: string }>
) {
return client.graph.executeTransaction(async (tx) => {
createdNodes := [];
// Create all user nodes
for (const userData of users) {
user := client.graph.createNode({
labels: ['User'],
properties: {
...userData,
createdAt: new Date(),
},
}, tx);
createdNodes.push(user.node);
}
// Create relationships between consecutive users
for (i := 0; i < createdNodes.length - 1; i++) {
client.graph.createRelationship({
"type": "INVITED_BY",
startNodeId: createdNodes[i + 1].id,
endNodeId: createdNodes[i].id,
properties: {
invitedAt: new Date(),
},
}, tx);
}
return createdNodes;
});
}
async function createMultipleUsersWithRelationships(
users: Array<{ name: string; email: string }>
) {
return ductape.graph.executeTransaction(async (tx) => {
var createdNodes = [];
// Create all user nodes
for (var userData of users) {
var user = await ductape.graph.createNode({
labels: ['User'],
properties: {
...userData,
createdAt: DateTime.UtcNow,
},
}, tx);
createdNodes.push(user.node);
}
// Create relationships between consecutive users
for (var i = 0; i < createdNodes.length - 1; i++) {
await ductape.graph.createRelationship({
["type"] = "INVITED_BY",
startNodeId: createdNodes[i + 1].id,
endNodeId: createdNodes[i].id,
properties: {
invitedAt: DateTime.UtcNow,
},
}, tx);
}
return createdNodes;
});
}
Cascade Delete
- TypeScript
- Java
- Go
- .NET
async function deleteUserAndRelatedData(userId: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Find user
const user = await ductape.graph.findNodeById(userId, tx);
if (!user) {
throw new Error('User not found');
}
// Find all posts by user
const posts = await ductape.graph.findRelationships({
startNodeId: userId,
type: 'POSTED',
direction: 'OUTGOING',
}, tx);
// Delete all posts
for (const post of posts.relationships) {
await ductape.graph.deleteNode({
id: post.endNodeId,
detach: true,
}, tx);
}
// Find and delete profile
const profile = await ductape.graph.findRelationships({
startNodeId: userId,
type: 'HAS_PROFILE',
direction: 'OUTGOING',
}, tx);
if (profile.relationships.length > 0) {
await ductape.graph.deleteNode({
id: profile.relationships[0].endNodeId,
detach: true,
}, tx);
}
// Delete user node
await ductape.graph.deleteNode({
id: userId,
detach: true, // Also deletes all relationships
}, tx);
return { deleted: true, userId };
});
}
async function deleteUserAndRelatedData(userId: string) Map.of(
return ductape.graph.executeTransaction(async (tx) => Map.of(
// Find user
Map<String, Object> user = ductape.graph.findNodeById(userId, tx);
if (!user) Map.of(
throw new Error('User not found');
)
// Find all posts by user
Map<String, Object> posts = ductape.graph.findRelationships(Map.of(
startNodeId: userId,
"type", "POSTED",
"direction", "OUTGOING"
), tx);
// Delete all posts
for (Map<String, Object> post of posts.relationships) Map.of(
ductape.graph.deleteNode(Map.of(
id: post.endNodeId,
"detach", true
), tx);
)
// Find and delete profile
Map<String, Object> profile = ductape.graph.findRelationships(Map.of(
startNodeId: userId,
"type", "HAS_PROFILE",
"direction", "OUTGOING"
), tx);
if (profile.relationships.length > 0) Map.of(
ductape.graph.deleteNode(Map.of(
id: profile.relationships[0].endNodeId,
"detach", true
), tx);
)
// Delete user node
ductape.graph.deleteNode(Map.of(
id: userId,
"detach", true, // Also deletes all relationships
), tx);
return Map.of( "deleted", true, userId );
));
)
async function deleteUserAndRelatedData(userId: string) {
return client.graph.executeTransaction(async (tx) => {
// Find user
user := client.graph.findNodeById(userId, tx);
if (!user) {
throw new Error('User not found');
}
// Find all posts by user
posts := client.graph.findRelationships({
startNodeId: userId,
"type": "POSTED",
"direction": "OUTGOING",
}, tx);
// Delete all posts
for (const post of posts.relationships) {
client.graph.deleteNode({
id: post.endNodeId,
"detach": true,
}, tx);
}
// Find and delete profile
profile := client.graph.findRelationships({
startNodeId: userId,
"type": "HAS_PROFILE",
"direction": "OUTGOING",
}, tx);
if (profile.relationships.length > 0) {
client.graph.deleteNode({
id: profile.relationships[0].endNodeId,
"detach": true,
}, tx);
}
// Delete user node
client.graph.deleteNode({
id: userId,
"detach": true, // Also deletes all relationships
}, tx);
return { "deleted": true, userId };
});
}
async function deleteUserAndRelatedData(userId: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Find user
var user = await ductape.graph.findNodeById(userId, tx);
if (!user) {
throw new Error('User not found');
}
// Find all posts by user
var posts = await ductape.graph.findRelationships({
startNodeId: userId,
["type"] = "POSTED",
["direction"] = "OUTGOING",
}, tx);
// Delete all posts
for (var post of posts.relationships) {
await ductape.graph.deleteNode({
id: post.endNodeId,
["detach"] = true,
}, tx);
}
// Find and delete profile
var profile = await ductape.graph.findRelationships({
startNodeId: userId,
["type"] = "HAS_PROFILE",
["direction"] = "OUTGOING",
}, tx);
if (profile.relationships.length > 0) {
await ductape.graph.deleteNode({
id: profile.relationships[0].endNodeId,
["detach"] = true,
}, tx);
}
// Delete user node
await ductape.graph.deleteNode({
id: userId,
["detach"] = true, // Also deletes all relationships
}, tx);
return { ["deleted"] = true, userId };
});
}
Conditional Updates
- TypeScript
- Java
- Go
- .NET
async function incrementPostLikes(postId: string, userId: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Check if user already liked the post
const existingLike = await ductape.graph.findRelationships({
startNodeId: userId,
endNodeId: postId,
type: 'LIKED',
}, tx);
if (existingLike.relationships.length > 0) {
throw new Error('Already liked this post');
}
// Create like relationship
await ductape.graph.createRelationship({
type: 'LIKED',
startNodeId: userId,
endNodeId: postId,
properties: {
likedAt: new Date(),
},
}, tx);
// Get current post
const post = await ductape.graph.findNodeById(postId, tx);
// Increment likes count
await ductape.graph.updateNode({
id: postId,
properties: {
likes: (post.properties.likes || 0) + 1,
},
}, tx);
return post;
});
}
async function incrementPostLikes(postId: string, userId: string) Map.of(
return ductape.graph.executeTransaction(async (tx) => Map.of(
// Check if user already liked the post
Map<String, Object> existingLike = ductape.graph.findRelationships(Map.of(
startNodeId: userId,
endNodeId: postId,
"type", "LIKED"
), tx);
if (existingLike.relationships.length > 0) Map.of(
throw new Error('Already liked this post');
)
// Create like relationship
ductape.graph.createRelationship(Map.of(
"type", "LIKED",
startNodeId: userId,
endNodeId: postId,
properties: Map.of(
likedAt: Instant.now()
)
), tx);
// Get current post
Map<String, Object> post = ductape.graph.findNodeById(postId, tx);
// Increment likes count
ductape.graph.updateNode(Map.of(
id: postId,
properties: Map.of(
likes: (post.properties.likes || 0) + 1
)
), tx);
return post;
));
)
async function incrementPostLikes(postId: string, userId: string) {
return client.graph.executeTransaction(async (tx) => {
// Check if user already liked the post
existingLike := client.graph.findRelationships({
startNodeId: userId,
endNodeId: postId,
"type": "LIKED",
}, tx);
if (existingLike.relationships.length > 0) {
throw new Error('Already liked this post');
}
// Create like relationship
client.graph.createRelationship({
"type": "LIKED",
startNodeId: userId,
endNodeId: postId,
properties: {
likedAt: new Date(),
},
}, tx);
// Get current post
post := client.graph.findNodeById(postId, tx);
// Increment likes count
client.graph.updateNode({
id: postId,
properties: {
likes: (post.properties.likes || 0) + 1,
},
}, tx);
return post;
});
}
async function incrementPostLikes(postId: string, userId: string) {
return ductape.graph.executeTransaction(async (tx) => {
// Check if user already liked the post
var existingLike = await ductape.graph.findRelationships({
startNodeId: userId,
endNodeId: postId,
["type"] = "LIKED",
}, tx);
if (existingLike.relationships.length > 0) {
throw new Error('Already liked this post');
}
// Create like relationship
await ductape.graph.createRelationship({
["type"] = "LIKED",
startNodeId: userId,
endNodeId: postId,
properties: {
likedAt: DateTime.UtcNow,
},
}, tx);
// Get current post
var post = await ductape.graph.findNodeById(postId, tx);
// Increment likes count
await ductape.graph.updateNode({
id: postId,
properties: {
likes: (post.properties.likes || 0) + 1,
},
}, tx);
return post;
});
}
Transaction Performance
Keep Transactions Short
- TypeScript
- Java
- Go
- .NET
// ❌ Bad - transaction is open too long
await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({ /* ... */ }, tx);
// External API call - blocks transaction
await fetch('https://api.example.com/notify', {
method: 'POST',
body: JSON.stringify(user),
});
await ductape.graph.updateNode({ /* ... */ }, tx);
});
// Good - keep transaction short
const user = await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.updateNode({ /* ... */ }, tx);
return user;
});
// External API call after transaction commits
await fetch('https://api.example.com/notify', {
method: 'POST',
body: JSON.stringify(user),
});
// ❌ Bad - transaction is open too long
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of( /* ... */ ), tx);
// External API call - blocks transaction
fetch('https://api.example.com/notify', Map.of(
"method", "POST",
body: JSON.stringify(user)
));
ductape.graph.updateNode(Map.of( /* ... */ ), tx);
));
// Good - keep transaction short
Map<String, Object> user = ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of( /* ... */ ), tx);
ductape.graph.updateNode(Map.of( /* ... */ ), tx);
return user;
));
// External API call after transaction commits
fetch('https://api.example.com/notify', Map.of(
"method", "POST",
body: JSON.stringify(user)
));
// ❌ Bad - transaction is open too long
client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({ /* ... */ }, tx);
// External API call - blocks transaction
fetch('https://api.example.com/notify', {
"method": "POST",
body: JSON.stringify(user),
});
client.graph.updateNode({ /* ... */ }, tx);
});
// Good - keep transaction short
user := client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({ /* ... */ }, tx);
client.graph.updateNode({ /* ... */ }, tx);
return user;
});
// External API call after transaction commits
fetch('https://api.example.com/notify', {
"method": "POST",
body: JSON.stringify(user),
});
// ❌ Bad - transaction is open too long
await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({ /* ... */ }, tx);
// External API call - blocks transaction
await fetch('https://api.example.com/notify', {
["method"] = "POST",
body: JSON.stringify(user),
});
await ductape.graph.updateNode({ /* ... */ }, tx);
});
// Good - keep transaction short
var user = await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.updateNode({ /* ... */ }, tx);
return user;
});
// External API call after transaction commits
await fetch('https://api.example.com/notify', {
["method"] = "POST",
body: JSON.stringify(user),
});
Batch Operations
- TypeScript
- Java
- Go
- .NET
// Batch operations in single transaction
await ductape.graph.executeTransaction(async (tx) => {
const nodes = [];
for (const data of largeDataset) {
const node = await ductape.graph.createNode({
labels: ['Product'],
properties: data,
}, tx);
nodes.push(node);
}
return nodes;
});
// Batch operations in single transaction
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> nodes = [];
for (Map<String, Object> data of largeDataset) Map.of(
Map<String, Object> node = ductape.graph.createNode(Map.of(
labels: ['Product'],
properties: data
), tx);
nodes.push(node);
)
return nodes;
));
// Batch operations in single transaction
client.graph.executeTransaction(async (tx) => {
nodes := [];
for (const data of largeDataset) {
node := client.graph.createNode({
labels: ['Product'],
properties: data,
}, tx);
nodes.push(node);
}
return nodes;
});
// Batch operations in single transaction
await ductape.graph.executeTransaction(async (tx) => {
var nodes = [];
for (var data of largeDataset) {
var node = await ductape.graph.createNode({
labels: ['Product'],
properties: data,
}, tx);
nodes.push(node);
}
return nodes;
});
Read-Only Optimization
- TypeScript
- Java
- Go
- .NET
// Use read-only transactions for analytics queries
await ductape.graph.executeTransaction(
async (tx) => {
const stats = await ductape.graph.getStatistics(tx);
const popularPosts = await ductape.graph.findNodes({
labels: ['Post'],
where: { likes: { $GT: 1000 } },
}, tx);
return { stats, popularPosts };
},
{ readOnly: true } // Allows database to optimize
);
// Use read-only transactions for analytics queries
ductape.graph.executeTransaction(
async (tx) => Map.of(
Map<String, Object> stats = ductape.graph.getStatistics(tx);
Map<String, Object> popularPosts = ductape.graph.findNodes(Map.of(
labels: ['Post'],
where: Map.of( likes: Map.of( $"GT", 1000 ) )
), tx);
return Map.of( stats, popularPosts );
),
Map.of( "readOnly", true ) // Allows database to optimize
);
// Use read-only transactions for analytics queries
client.graph.executeTransaction(
async (tx) => {
stats := client.graph.getStatistics(tx);
popularPosts := client.graph.findNodes({
labels: ['Post'],
where: { likes: { $"GT": 1000 } },
}, tx);
return { stats, popularPosts };
},
{ "readOnly": true } // Allows database to optimize
);
// Use read-only transactions for analytics queries
await ductape.graph.executeTransaction(
async (tx) => {
var stats = await ductape.graph.getStatistics(tx);
var popularPosts = await ductape.graph.findNodes({
labels: ['Post'],
where: { likes: { $["GT"] = 1000 } },
}, tx);
return { stats, popularPosts };
},
{ ["readOnly"] = true } // Allows database to optimize
);
Best Practices
1. Always Use Transactions for Multiple Operations
- TypeScript
- Java
- Go
- .NET
// Good - ensures atomicity
await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createNode({ /* profile */ }, tx);
await ductape.graph.createRelationship({ /* link */ }, tx);
});
// Good - ensures atomicity
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of( /* ... */ ), tx);
ductape.graph.createNode(Map.of( /* profile */ ), tx);
ductape.graph.createRelationship(Map.of( /* link */ ), tx);
));
// Good - ensures atomicity
client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({ /* ... */ }, tx);
client.graph.createNode({ /* profile */ }, tx);
client.graph.createRelationship({ /* link */ }, tx);
});
// Good - ensures atomicity
await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createNode({ /* profile */ }, tx);
await ductape.graph.createRelationship({ /* link */ }, tx);
});
2. Use executeTransaction Instead of Manual Control
- TypeScript
- Java
- Go
- .NET
// Recommended - automatic commit/rollback
await ductape.graph.executeTransaction(async (tx) => {
// operations
});
// ❌ Avoid unless you need fine control
const tx = await ductape.graph.beginTransaction();
try {
// operations
await ductape.graph.commitTransaction(tx);
} catch (error) {
await ductape.graph.rollbackTransaction(tx);
}
// Recommended - automatic commit/rollback
ductape.graph.executeTransaction(async (tx) => Map.of(
// operations
));
// ❌ Avoid unless you need fine control
Map<String, Object> tx = ductape.graph.beginTransaction();
try Map.of(
// operations
ductape.graph.commitTransaction(tx);
) catch (error) Map.of(
ductape.graph.rollbackTransaction(tx);
)
// Recommended - automatic commit/rollback
client.graph.executeTransaction(async (tx) => {
// operations
});
// ❌ Avoid unless you need fine control
tx := client.graph.beginTransaction();
try {
// operations
client.graph.commitTransaction(tx);
} catch (error) {
client.graph.rollbackTransaction(tx);
}
// Recommended - automatic commit/rollback
await ductape.graph.executeTransaction(async (tx) => {
// operations
});
// ❌ Avoid unless you need fine control
var tx = await ductape.graph.beginTransaction();
try {
// operations
await ductape.graph.commitTransaction(tx);
} catch (error) {
await ductape.graph.rollbackTransaction(tx);
}
3. Handle Errors Appropriately
- TypeScript
- Java
- Go
- .NET
try {
await ductape.graph.executeTransaction(async (tx) => {
// operations that might fail
});
} catch (error) {
if (error.message.includes('constraint')) {
console.log('Data validation error');
} else if (error.message.includes('deadlock')) {
console.log('Retry transaction');
} else {
console.log('Unexpected error');
}
throw error;
}
try Map.of(
ductape.graph.executeTransaction(async (tx) => Map.of(
// operations that might fail
));
) catch (error) Map.of(
if (error.message.includes('constraint')) Map.of(
System.out.println('Data validation error');
) else if (error.message.includes('deadlock')) Map.of(
System.out.println('Retry transaction');
) else Map.of(
System.out.println('Unexpected error');
)
throw error;
)
try {
client.graph.executeTransaction(async (tx) => {
// operations that might fail
});
} catch (error) {
if (error.message.includes('constraint')) {
fmt.Println('Data validation error');
} else if (error.message.includes('deadlock')) {
fmt.Println('Retry transaction');
} else {
fmt.Println('Unexpected error');
}
throw error;
}
try {
await ductape.graph.executeTransaction(async (tx) => {
// operations that might fail
});
} catch (error) {
if (error.message.includes('constraint')) {
Console.WriteLine('Data validation error');
} else if (error.message.includes('deadlock')) {
Console.WriteLine('Retry transaction');
} else {
Console.WriteLine('Unexpected error');
}
throw error;
}
4. Don't Nest Transactions
- TypeScript
- Java
- Go
- .NET
// ❌ Bad - nested transactions not supported
await ductape.graph.executeTransaction(async (tx) => {
await ductape.graph.createNode({ /* ... */ }, tx);
// Don't start another transaction here
await ductape.graph.executeTransaction(async (tx2) => {
// This won't work as expected
});
});
// Good - single transaction for all operations
await ductape.graph.executeTransaction(async (tx) => {
await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
// ❌ Bad - nested transactions not supported
ductape.graph.executeTransaction(async (tx) => Map.of(
ductape.graph.createNode(Map.of( /* ... */ ), tx);
// Don't start another transaction here
ductape.graph.executeTransaction(async (tx2) => Map.of(
// This won't work as expected
));
));
// Good - single transaction for all operations
ductape.graph.executeTransaction(async (tx) => Map.of(
ductape.graph.createNode(Map.of( /* ... */ ), tx);
ductape.graph.createNode(Map.of( /* ... */ ), tx);
ductape.graph.createRelationship(Map.of( /* ... */ ), tx);
));
// ❌ Bad - nested transactions not supported
client.graph.executeTransaction(async (tx) => {
client.graph.createNode({ /* ... */ }, tx);
// Don't start another transaction here
client.graph.executeTransaction(async (tx2) => {
// This won't work as expected
});
});
// Good - single transaction for all operations
client.graph.executeTransaction(async (tx) => {
client.graph.createNode({ /* ... */ }, tx);
client.graph.createNode({ /* ... */ }, tx);
client.graph.createRelationship({ /* ... */ }, tx);
});
// ❌ Bad - nested transactions not supported
await ductape.graph.executeTransaction(async (tx) => {
await ductape.graph.createNode({ /* ... */ }, tx);
// Don't start another transaction here
await ductape.graph.executeTransaction(async (tx2) => {
// This won't work as expected
});
});
// Good - single transaction for all operations
await ductape.graph.executeTransaction(async (tx) => {
await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
5. Pass Transaction to All Operations
- TypeScript
- Java
- Go
- .NET
// Correct - all operations in same transaction
await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({ /* ... */ }, tx);
const profile = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
// ❌ Wrong - missing tx parameter means not in transaction
await ductape.graph.executeTransaction(async (tx) => {
const user = await ductape.graph.createNode({ /* ... */ }, tx);
const profile = await ductape.graph.createNode({ /* ... */ }); // Missing tx!
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
// Correct - all operations in same transaction
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of( /* ... */ ), tx);
Map<String, Object> profile = ductape.graph.createNode(Map.of( /* ... */ ), tx);
ductape.graph.createRelationship(Map.of( /* ... */ ), tx);
));
// ❌ Wrong - missing tx parameter means not in transaction
ductape.graph.executeTransaction(async (tx) => Map.of(
Map<String, Object> user = ductape.graph.createNode(Map.of( /* ... */ ), tx);
Map<String, Object> profile = ductape.graph.createNode(Map.of( /* ... */ )); // Missing tx!
ductape.graph.createRelationship(Map.of( /* ... */ ), tx);
));
// Correct - all operations in same transaction
client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({ /* ... */ }, tx);
profile := client.graph.createNode({ /* ... */ }, tx);
client.graph.createRelationship({ /* ... */ }, tx);
});
// ❌ Wrong - missing tx parameter means not in transaction
client.graph.executeTransaction(async (tx) => {
user := client.graph.createNode({ /* ... */ }, tx);
profile := client.graph.createNode({ /* ... */ }); // Missing tx!
client.graph.createRelationship({ /* ... */ }, tx);
});
// Correct - all operations in same transaction
await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({ /* ... */ }, tx);
var profile = await ductape.graph.createNode({ /* ... */ }, tx);
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
// ❌ Wrong - missing tx parameter means not in transaction
await ductape.graph.executeTransaction(async (tx) => {
var user = await ductape.graph.createNode({ /* ... */ }, tx);
var profile = await ductape.graph.createNode({ /* ... */ }); // Missing tx!
await ductape.graph.createRelationship({ /* ... */ }, tx);
});
6. Retry on Deadlocks
- TypeScript
- Java
- Go
- .NET
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (error.message.includes('deadlock') && i < maxRetries - 1) {
console.log(`Deadlock detected, retry ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, 100 * (i + 1)));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Usage
await withRetry(() =>
ductape.graph.executeTransaction(async (tx) => {
// operations that might deadlock
})
);
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> Map.of(
for (Map<String, Object> i = 0; i < maxRetries; i++) Map.of(
try Map.of(
return operation();
) catch (error) Map.of(
if (error.message.includes('deadlock') && i < maxRetries - 1) Map.of(
System.out.println(`Deadlock detected, retry $Map.of(i + 1)/$Map.of(maxRetries)`);
new Promise(resolve => setTimeout(resolve, 100 * (i + 1)));
continue;
)
throw error;
)
)
throw new Error('Max retries exceeded');
)
// Usage
withRetry(() =>
ductape.graph.executeTransaction(async (tx) => Map.of(
// operations that might deadlock
))
);
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (i := 0; i < maxRetries; i++) {
try {
return operation();
} catch (error) {
if (error.message.includes('deadlock') && i < maxRetries - 1) {
fmt.Println(`Deadlock detected, retry ${i + 1}/${maxRetries}`);
new Promise(resolve => setTimeout(resolve, 100 * (i + 1)));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Usage
withRetry(() =>
client.graph.executeTransaction(async (tx) => {
// operations that might deadlock
})
);
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (var i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (error.message.includes('deadlock') && i < maxRetries - 1) {
Console.WriteLine(`Deadlock detected, retry ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, 100 * (i + 1)));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Usage
await withRetry(() =>
ductape.graph.executeTransaction(async (tx) => {
// operations that might deadlock
})
);
Database-Specific Behavior
Neo4j
- Supports full ACID transactions
- Deadlock detection and prevention
- Optimistic locking
AWS Neptune
- Supports transactions via Gremlin and openCypher
- Eventual consistency for read replicas
ArangoDB
- Multi-document transactions supported
- ACID across collections
Memgraph
- Full transaction support
- ACID compliance
Next Steps
- Indexes & Constraints - Optimize performance and data integrity
- Best Practices - Graph database optimization
- Nodes - Working with graph nodes
- Relationships - Managing connections
See Also
- Graph Overview - Full API reference
- Error Handling - Managing errors effectively