Recording Model
The Recording model stores metadata about automation session recordings, linking video captures to flows and devices for debugging and analysis.
Schema Definition
model Recording {
id String @id @default(uuid())
recordingId String @unique
fileName String
filePath String
deviceId String
status String
startTime DateTime
endTime DateTime?
duration Int?
fileSize BigInt?
flowId String?
flowName String?
actionTimestamps Json[] @default([])
bitRate String?
resolution String?
orientation String?
error String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
flow Flow? @relation(fields: [flowId], references: [id])
@@map("recordings")
}
Field Descriptions
Primary Identification
| Field | Type | Description |
|---|---|---|
id | String | UUID primary key |
recordingId | String | Unique recording identifier |
File Information
| Field | Type | Description |
|---|---|---|
fileName | String | Recording filename |
filePath | String | Full path to recording file |
fileSize | BigInt? | File size in bytes |
Recording Metadata
| Field | Type | Description |
|---|---|---|
deviceId | String | Device identifier where recording was made |
status | String | Recording status (recording, completed, failed, processing) |
startTime | DateTime | When recording started |
endTime | DateTime? | When recording ended |
duration | Int? | Recording duration in milliseconds |
Flow Association
| Field | Type | Description |
|---|---|---|
flowId | String? | Associated flow identifier |
flowName | String? | Cached flow name for display |
Technical Details
| Field | Type | Description |
|---|---|---|
actionTimestamps | Json[] | Array of action timestamps during recording |
bitRate | String? | Video bitrate (e.g., "8M", "4M") |
resolution | String? | Video resolution (e.g., "1080x2400", "720p") |
orientation | String? | Screen orientation (portrait, landscape) |
Error Handling
| Field | Type | Description |
|---|---|---|
error | String? | Error message if recording failed |
Metadata
| Field | Type | Description |
|---|---|---|
createdAt | DateTime | Recording creation timestamp |
updatedAt | DateTime | Last update timestamp |
Relationships
Belongs To
- flow:
Flow?- The flow this recording is associated with
Usage Examples
Create Recording Entry
const recording = await prisma.recording.create({
data: {
recordingId: "rec-2024-01-01-12-00-00",
fileName: "flow-login-2024-01-01-12-00-00.mp4",
filePath: "/recordings/flow-login-2024-01-01-12-00-00.mp4",
deviceId: "emulator-5554",
status: "recording",
startTime: new Date(),
flowId: "flow-123",
flowName: "Login Flow",
bitRate: "8M",
resolution: "1080x2400"
}
});
Update Recording on Completion
await prisma.recording.update({
where: { recordingId: "rec-2024-01-01-12-00-00" },
data: {
status: "completed",
endTime: new Date(),
duration: 45000, // 45 seconds
fileSize: 12500000 // 12.5 MB
}
});
Record Action Timestamps
await prisma.recording.update({
where: { recordingId: "rec-2024-01-01-12-00-00" },
data: {
actionTimestamps: {
push: {
action: "tap",
element: "login_button",
timestamp: Date.now(),
coordinates: { x: 540, y: 1200 }
}
}
}
});
Get Recordings for Flow
const flowRecordings = await prisma.recording.findMany({
where: {
flowId: "flow-123",
status: "completed"
},
orderBy: { startTime: "desc" },
take: 10
});
Get Failed Recordings
const failedRecordings = await prisma.recording.findMany({
where: {
status: "failed",
endTime: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
}
},
select: {
recordingId: true,
flowName: true,
deviceId: true,
error: true,
startTime: true
}
});
Action Timestamps Structure
Action Event Format
{
"action": "tap",
"element": "login_button",
"timestamp": 1704120600000,
"coordinates": {
"x": 540,
"y": 1200
},
"confidence": 0.95
}
Complex Action Events
[
{
"action": "type",
"element": "username_field",
"timestamp": 1704120600000,
"text": "testuser",
"coordinates": { "x": 300, "y": 800 }
},
{
"action": "wait",
"duration": 2000,
"timestamp": 1704120602000
},
{
"action": "swipe",
"startCoordinates": { "x": 540, "y": 2000 },
"endCoordinates": { "x": 540, "y": 1000 },
"duration": 300,
"timestamp": 1704120605000
}
]
Recording Lifecycle
1. Initialization
const recording = await createRecording({
flowId: "flow-123",
deviceId: "emulator-5554",
options: {
bitRate: "8M",
resolution: "1080p"
}
});
2. Active Recording
// Record actions as they happen
await recordAction(recording.id, {
action: "tap",
element: "button",
coordinates: { x: 100, y: 200 }
});
3. Completion
await completeRecording(recording.id, {
duration: calculateDuration(),
fileSize: await getFileSize()
});
4. Post-Processing
// Generate thumbnails, compress, etc.
await postProcessRecording(recording.id);
Storage Management
File Organization
recordings/
├── flow-{flowId}-{timestamp}.mp4
├── thumbnails/
│ └── recording-{id}-{timestamp}.jpg
└── temp/
└── recording-active-{sessionId}.mp4
Storage Cleanup
async function cleanupOldRecordings(retentionDays: number = 30) {
const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
const oldRecordings = await prisma.recording.findMany({
where: {
createdAt: { lt: cutoffDate },
status: "completed"
},
select: { id: true, filePath: true }
});
// Delete files
for (const recording of oldRecordings) {
try {
await fs.unlink(recording.filePath);
// Delete thumbnails, etc.
} catch (error) {
console.error(`Failed to delete ${recording.filePath}:`, error);
}
}
// Delete database records
await prisma.recording.deleteMany({
where: {
id: { in: oldRecordings.map(r => r.id) }
}
});
}
Performance Considerations
Indexing
- Unique index on
recordingId - Index on
flowIdfor flow filtering - Index on
deviceIdfor device filtering - Index on
statusfor status queries - Index on
startTimefor time-based queries - Index on
createdAtfor cleanup operations
Query Optimization
- Use
selectto limit returned fields - Implement pagination for recording lists
- Use streaming for large action timestamp arrays
- Cache frequently accessed recording metadata
File Storage
- Use appropriate storage backend (local, S3, etc.)
- Implement file compression for large recordings
- Generate thumbnails for quick browsing
- Use CDN for distribution if needed
Analytics and Reporting
Recording Statistics
async function getRecordingStats(timeRange?: { start: Date; end: Date }) {
const whereClause = timeRange ? {
startTime: {
gte: timeRange.start,
lte: timeRange.end
}
} : {};
const stats = await prisma.recording.aggregate({
where: whereClause,
_count: true,
_sum: {
duration: true,
fileSize: true
}
});
const statusBreakdown = await prisma.recording.groupBy({
by: ['status'],
where: whereClause,
_count: true
});
return {
totalRecordings: stats._count,
totalDuration: stats._sum.duration,
totalSize: stats._sum.fileSize,
averageDuration: stats._sum.duration / stats._count,
statusBreakdown: statusBreakdown.reduce((acc, stat) => {
acc[stat.status] = stat._count;
return acc;
}, {})
};
}
Flow Performance Analysis
async function analyzeFlowPerformance(flowId: string) {
const recordings = await prisma.recording.findMany({
where: {
flowId,
status: "completed"
},
select: {
duration: true,
actionTimestamps: true,
startTime: true
}
});
// Analyze performance patterns
const avgDuration = recordings.reduce((sum, r) => sum + (r.duration || 0), 0) / recordings.length;
const actionCounts = recordings.flatMap(r => r.actionTimestamps).length / recordings.length;
return {
averageDuration: avgDuration,
averageActions: actionCounts,
recordingCount: recordings.length
};
}
Best Practices
File Management
- Implement automatic cleanup policies
- Use descriptive filenames with timestamps
- Compress recordings for storage efficiency
- Generate metadata for quick access
Error Handling
- Log recording failures with details
- Implement retry logic for transient failures
- Provide fallback recording options
- Monitor recording success rates
Security
- Secure access to recording files
- Implement user-based access controls
- Encrypt sensitive recording data
- Regular security audits of stored files
Monitoring
- Track recording success/failure rates
- Monitor storage usage and costs
- Alert on recording failures
- Analyze performance trends
Integration
- Sync recordings with external systems
- Provide APIs for recording access
- Implement search and filtering capabilities
- Support multiple output formats