Skip to main content

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:

  1. ResourceAPI validates the action's requiredRole against the user's role
  2. ResourceAPI checks validationRules (required fields, required status)
  3. If validation passes, ResourceAPI applies all sideEffects (set_field, set_timestamp, set_user)
  4. The resource version is incremented

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

OperationMethodPathBody
CreatePOST/v3/resources/{tenant}/{type}{ data }
GetGET/v3/resources/{tenant}/{type}/{id}--
ListGET/v3/resources/{tenant}/{type}?page=&limit=&sort=&where=--
UpdatePUT/v3/resources/{tenant}/{type}/{id}{ data, version }
DeleteDELETE/v3/resources/{tenant}/{type}/{id}--
Execute ActionPOST/v3/resources/{tenant}/{type}/{id}/actions/{action}{ params: {} }
Get LinksGET/v3/resources/{tenant}/{type}/{id}/links/{linkType}--
Create LinkPOST/v3/resources/{tenant}/{type}/{id}/links/{linkType}{ target_id, target_type }
Delete LinkDELETE/v3/resources/{tenant}/{type}/{id}/links/{linkType}/{targetId}--
QueryPOST/v3/resources/{tenant}/query{ object_types, where?, join?, limit? }
Get SchemaGET/v3/resources/schema/{tenant}--
Get HistoryGET/v3/resources/{tenant}/{type}/{id}/history--
tip

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.