Working with Resources
Resources are the runtime data instances of your Object Types. When you define an Object Type called Application, each individual application record is a Resource. Resources are stored as typed JSONB in PostgreSQL, scoped by tenant, and accessed through the Platform SDK, React hooks, or CLI commands.
Resource Structure
Every resource has a standard envelope with metadata plus your custom data:
interface Resource<T> {
id: string; // UUID assigned by ResourceAPI
type: string; // Object Type name (e.g., "Application")
tenant: string; // Tenant slug
data: T; // Your typed domain data
version: number; // Incrementing version for optimistic locking
created_at: string; // ISO 8601 timestamp
updated_at: string; // ISO 8601 timestamp
created_by: string; // User ID who created the resource
updated_by: string; // User ID who last updated the resource
}
CRUD Operations
Using the Platform SDK
The Platform SDK (@enterpriseaigroup/platform-sdk) provides typed wrappers for all ResourceAPI endpoints:
import { EAIPlatformClient } from '@enterpriseaigroup/platform-sdk';
const client = new EAIPlatformClient({ tenantId: 'my-tenant' });
Create a Resource
const application = await client.resources.create('Application', {
applicantName: 'Jane Smith',
applicantEmail: 'jane@example.com',
projectAddress: '123 Main St',
estimatedCost: 50000,
status: 'draft',
});
console.log(application.id); // UUID
console.log(application.version); // 1
Get a Resource by ID
const app = await client.resources.get('Application', applicationId);
console.log(app.data.applicantName); // "Jane Smith"
List Resources with Pagination
const { docs, totalDocs, page, totalPages } = await client.resources.list(
'Application',
{
page: 1,
limit: 20,
sort: '-created_at', // Descending by creation date
}
);
docs.forEach((app) => {
console.log(app.data.applicantName, app.data.status);
});
Update a Resource
Updates require the current version for optimistic locking. If another user has modified the resource since you read it, the update will fail with a version conflict error.
const app = await client.resources.get('Application', applicationId);
await client.resources.update(
'Application',
app.id,
{ status: 'under-review' },
app.version // REQUIRED -- prevents concurrent write conflicts
);
Delete a Resource
await client.resources.delete('Application', applicationId);
Using the useResources React Hook
For React components, the useResources<T> hook provides the same CRUD operations with proper TypeScript generics:
'use client';
import { useResources } from '@/hooks/useResources';
interface ApplicationData {
applicantName: string;
applicantEmail: string;
projectAddress: string;
estimatedCost: number;
status: string;
}
function ApplicationList() {
const {
list,
create,
update,
delete: remove,
} = useResources<ApplicationData>('Application');
const [applications, setApplications] = useState([]);
useEffect(() => {
async function load() {
const result = await list({ page: 1, limit: 20 });
setApplications(result.docs);
}
load();
}, []);
async function handleCreate() {
const newApp = await create({
applicantName: 'John Doe',
applicantEmail: 'john@example.com',
projectAddress: '456 Oak Ave',
estimatedCost: 75000,
status: 'draft',
});
setApplications((prev) => [newApp, ...prev]);
}
async function handleUpdate(app: Resource<ApplicationData>) {
await update(app.id, { status: 'submitted' }, app.version);
}
return (
<div>
{applications.map((app) => (
<div key={app.id}>
{app.data.applicantName} - {app.data.status}
</div>
))}
</div>
);
}
Using CLI Commands
The EAI CLI provides commands for working with resources from the terminal:
# List resources of a given type
eai resources list Application --tenant my-tenant
# Get a specific resource by ID
eai resources get Application <resource-id> --tenant my-tenant
# Create a resource from JSON
eai resources create Application --tenant my-tenant \
--data '{"applicantName": "Jane Smith", "status": "draft"}'
# Update a resource (version is required)
eai resources update Application <resource-id> --tenant my-tenant \
--data '{"status": "submitted"}' --version 1
# Delete a resource
eai resources delete Application <resource-id> --tenant my-tenant
Query and Filtering
ResourceAPI supports cross-type queries with filtering, joining, and pagination through the query endpoint:
// Query with filtering
const results = await client.resources.query({
object_types: ['Application'],
where: {
'data.status': { equals: 'submitted' },
'data.estimatedCost': { gte: 10000 },
},
limit: 50,
sort: '-created_at',
});
List Endpoint Query Parameters
The list endpoint also supports filtering via query parameters:
const results = await client.resources.list('Application', {
page: 1,
limit: 20,
sort: '-created_at', // Sort descending by field
where: {
'data.status': 'submitted', // Exact match filter
},
});
Executing Actions
Actions are custom operations defined on Object Types that modify resource state. They validate preconditions, apply side effects, and enforce role-based access.
// Execute an action on a resource
await client.resources.executeAction(
'Application',
applicationId,
'submit',
{} // Optional params
);
When an action is executed:
- ResourceAPI validates the action's
requiredRoleagainst the user's role - ResourceAPI checks
validationRules(required fields, required status) - If validation passes, ResourceAPI applies all
sideEffects(set_field, set_timestamp, set_user) - The resource version is incremented
Working with Links
Links represent relationships between resources, as defined by Link Types on the Object Type.
// Get linked resources
const documents = await client.resources.getLinks(
'Application',
applicationId,
'documents' // Link type name
);
// Create a link between resources
await client.resources.createLink(
'Application',
applicationId,
'documents',
{
target_id: documentId,
target_type: 'Document',
}
);
// Delete a link
await client.resources.deleteLink(
'Application',
applicationId,
'documents',
documentId
);
Version History
Every mutation to a resource is recorded. You can retrieve the full history:
const history = await client.resources.getHistory(
'Application',
applicationId
);
history.forEach((entry) => {
console.log(entry.version, entry.updated_at, entry.updated_by);
});
API Reference
| Operation | Method | Path | Body |
|---|---|---|---|
| Create | POST | /v3/resources/{tenant}/{type} | { data } |
| Get | GET | /v3/resources/{tenant}/{type}/{id} | -- |
| List | GET | /v3/resources/{tenant}/{type}?page=&limit=&sort=&where= | -- |
| Update | PUT | /v3/resources/{tenant}/{type}/{id} | { data, version } |
| Delete | DELETE | /v3/resources/{tenant}/{type}/{id} | -- |
| Execute Action | POST | /v3/resources/{tenant}/{type}/{id}/actions/{action} | { params: {} } |
| Get Links | GET | /v3/resources/{tenant}/{type}/{id}/links/{linkType} | -- |
| Create Link | POST | /v3/resources/{tenant}/{type}/{id}/links/{linkType} | { target_id, target_type } |
| Delete Link | DELETE | /v3/resources/{tenant}/{type}/{id}/links/{linkType}/{targetId} | -- |
| Query | POST | /v3/resources/{tenant}/query | { object_types, where?, join?, limit? } |
| Get Schema | GET | /v3/resources/schema/{tenant} | -- |
| Get History | GET | /v3/resources/{tenant}/{type}/{id}/history | -- |
Always include the version field when updating a resource. Omitting it will result in a 400 Bad Request error. This optimistic locking mechanism prevents concurrent write conflicts.