Advanced Features
Context Reference - Advanced Features Advanced context features including file uploads, API information, shared context, and package access. Transactions Use await @TRANSACTION.run(async () => { ... }) when repository mutations must either all commit or all roll back. It is equiv
Context Reference - Advanced Features
Advanced context features including file uploads, API information, shared context, and package access.
Transactions
Use await @TRANSACTION.run(async () => { ... }) when repository mutations must either all commit or all roll back. It is equivalent to $ctx.$transaction.run(...) and works across the configured database backend.
await @TRANSACTION.run(async () => {
await @REPOS.orders.create({ data: @BODY.order });
await @REPOS.inventory.update({
id: @BODY.inventoryId,
data: { reserved: true }
});
});
Only repository operations participate in the transaction. Do not assume @FETCH, @STORAGE, @CACHE, @SOCKET, or @TRIGGER can be rolled back; defer those effects until after a successful transaction when that distinction matters.
File Uploads
Access information about uploaded files.
$ctx.$uploadedFile
Information about the uploaded file (if any).
if ($ctx.$uploadedFile) {
const filename = $ctx.$uploadedFile.originalname;
const mimetype = $ctx.$uploadedFile.mimetype;
const size = $ctx.$uploadedFile.size;
const fieldname = $ctx.$uploadedFile.fieldname;
}
$ctx.$uploadedFile describes a multipart request file. It does not expose the file body as a buffer. Pass it directly to $ctx.$storage.$upload({ file: $ctx.$uploadedFile }) or $ctx.$storage.$update(id, { file: $ctx.$uploadedFile }) so Enfyra streams from the temp file path.
Processing Uploaded Files
if ($ctx.$uploadedFile) {
$ctx.$logs(`File uploaded: ${$ctx.$uploadedFile.originalname}`);
$ctx.$logs(`File size: ${$ctx.$uploadedFile.size} bytes`);
$ctx.$logs(`MIME type: ${$ctx.$uploadedFile.mimetype}`);
// Save request file using the streaming helper.
const fileResult = await $ctx.$storage.$upload({
file: $ctx.$uploadedFile,
description: $ctx.$body.description
});
}
Streaming an HTTP response
For a proxy endpoint, use an installed server package that returns a readable stream and pass it directly to @RES.stream:
const upstream = await @PKGS.undici.request(@QUERY.url, { method: 'GET' });
await @RES.stream(upstream.body, {
mimetype: 'text/event-stream',
transform: (text, kind) => {
if (kind === 'end') return 'data: [DONE]\n\n';
return text.replace(/^data:/gm, 'event: converted\ndata:');
}
});
transform(text, kind) runs before each chunk and the final end relay. Return a string to replace output, null to suppress it, or undefined to keep it. It is for adapting the client-visible stream; a thrown error fails the stream. Enfyra uses one incremental UTF-8 decoder for the full stream before it calls transform, so a character split across network bytes remains intact in text. text can still end in the middle of an SSE line, JSON object, or event. Keep a request-local SSE buffer and parse only records terminated by a blank line (\n\n, or its CRLF form); do not use a fixed chunk size or spaces as a boundary. @HELPERS.$fetch buffers a response and is not suitable for SSE or chat streaming. Keep upstream authorization headers on the server, forward only safe response headers, and do not return another payload after @RES.stream starts.
API Information
Access detailed information about the current API request and response.
Request Information
const method = $ctx.$api.request.method; // HTTP method
const url = $ctx.$api.request.url; // Request URL
const timestamp = $ctx.$api.request.timestamp; // Request timestamp
const correlationId = $ctx.$api.request.correlationId; // Unique request ID
const userAgent = $ctx.$api.request.userAgent; // User agent
const ip = $ctx.$api.request.ip; // Client IP
Response Information
const statusCode = $ctx.$api.response.statusCode; // HTTP status code
const responseTime = $ctx.$api.response.responseTime; // Response time in ms
const timestamp = $ctx.$api.response.timestamp; // Response timestamp
Error Information (postHook Only)
if ($ctx.$api.error) {
const message = $ctx.$api.error.message; // Error message
const stack = $ctx.$api.error.stack; // Stack trace
const name = $ctx.$api.error.name; // Error class name
const statusCode = $ctx.$api.error.statusCode; // HTTP error status
const timestamp = $ctx.$api.error.timestamp; // Error timestamp
const details = $ctx.$api.error.details; // Additional details
}
Shared Context
Store data that persists across hooks and handlers.
$ctx.$share
Shared data container for passing data between hooks.
// In preHook - store data
$ctx.$share.validationPassed = true;
$ctx.$share.processStartTime = Date.now();
$ctx.$share.userId = $ctx.$user.id;
// In postHook - access shared data
if ($ctx.$share.validationPassed) {
const processingTime = Date.now() - $ctx.$share.processStartTime;
$ctx.$data.processingTime = processingTime;
}
Accessing Logs
// All logs are stored in $ctx.$share.$logs
const allLogs = $ctx.$share.$logs; // Array of all logged messages
Environment Access
Read non-secret process environment values from the sanitized environment snapshot.
$ctx.$env
$ctx.$env exposes a sanitized copy of process.env for dynamic scripts. It is useful for non-secret runtime switches such as node names, deployment labels, public URLs, or feature flags.
const nodeName = $ctx.$env.NODE_NAME || 'default';
const appMode = $ctx.$env.ENFYRA_MODE || 'all';
Sensitive infrastructure keys are not exposed. Current exact deny-list keys are:
DB_URIDB_REPLICA_URISREDIS_URISECRET_KEYADMIN_PASSWORD
Do not use $ctx.$env as an application secret store. Store application secrets in unpublished table fields marked isEncrypted=true, then read and write plaintext through normal repository or REST operations.
Encrypted table fields are tied to the server SECRET_KEY. In self-hosted deployments, keep SECRET_KEY stable, backed up, and identical across every server instance for the same Enfyra app. Changing or losing it prevents existing encrypted field values from decrypting.
Package Access
Access NPM packages installed in the system.
$ctx.$pkgs
Access installed packages by name.
// Use installed packages
const axios = $ctx.$pkgs.axios;
const lodash = $ctx.$pkgs.lodash;
const moment = $ctx.$pkgs.moment;
// Example: Using axios
if ($ctx.$pkgs.axios) {
const response = await $ctx.$pkgs.axios.get('https://api.example.com/data');
}
Note: Packages must be installed through Package Management in the UI to be accessible.
Common Patterns
Pattern 1: Validate and Transform Request Data
// Validate required fields
if (!$ctx.$body.email) {
$ctx.$throw['400']('Email is required');
return;
}
// Transform data
$ctx.$body.email = $ctx.$body.email.toLowerCase().trim();
Pattern 2: Check User Permissions
if (!$ctx.$user) {
$ctx.$throw['401']('Authentication required');
return;
}
if ($ctx.$user.role !== 'admin') {
$ctx.$throw['403']('Admin access required');
return;
}
Pattern 3: Use Shared Context Between Hooks
// In preHook
$ctx.$share.userId = $ctx.$user.id;
$ctx.$share.validationData = { passed: true };
// In postHook
const userId = $ctx.$share.userId;
if ($ctx.$share.validationData.passed) {
// Use validation result
}
Pattern 4: Cache Frequently Accessed Data
// Check cache first
const cacheKey = `user:${$ctx.$params.id}`;
let user = await $ctx.$cache.get(cacheKey);
if (!user) {
// Cache miss - fetch from database
const result = await $ctx.$repos.enfyra_user.find({
filter: { id: { _eq: $ctx.$params.id } }
});
if (result.data.length > 0) {
user = result.data[0];
// Cache for 5 minutes
await $ctx.$cache.set(cacheKey, user, 300000);
}
}
Pattern 5: Handle Errors in postHook
// In postHook
if ($ctx.$api.error) {
// Log error
$ctx.$logs(`Error: ${$ctx.$api.error.message}`);
// Log to audit system
await $ctx.$repos.audit_logs.create({
data: {
action: 'error_occurred',
userId: $ctx.$user?.id,
errorMessage: $ctx.$api.error.message,
statusCode: $ctx.$api.error.statusCode,
timestamp: new Date()
}
});
} else {
// Success - add metadata
$ctx.$data.processedAt = new Date();
$ctx.$data.processedBy = $ctx.$user?.id;
}
Next Steps
- See File Handling for complete file upload guide
- Check Cache Operations for detailed cache patterns
- Learn about Error Handling for error handling patterns