Internal API
The Rest class provides direct database access within hooks, actions, and custom logic. It's available as useRest from @dnax/[email protected].
Initialize Rest
import { useRest } from '@dnax/core';
const rest = new useRest({
tenant_id: 'v1',
useHook: true,
});
Constructor Options
new useRest({
tenant_id?: string;
database?: { uri: string; options?: MongoClientOptions };
session?: ClientSession;
useHook?: boolean;
useCustomApi?: boolean;
})
Query Methods
find
Retrieve multiple documents with pipeline support.
const users = await rest.find('users', {
$match: { status: 'active' },
$limit: 20,
$skip: 0,
$sort: { createdAt: -1 },
$project: { name: 1, email: 1 },
});
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
params | object | Pipeline parameters |
options | object | Additional options |
Returns: Document[]
findOne
Retrieve a single document by ID.
const user = await rest.findOne('users', '64f1a2b3c4d5e6f7a8b9c0d1');
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
_id | string | Document ID |
params | object | Optional pipeline params |
Returns: Document | null
aggregate
Run an aggregation pipeline.
const result = await rest.aggregate('orders', [
{ $match: { status: 'completed' } },
{ $group: { _id: '$category', total: { $sum: '$amount' } } },
]);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
pipeline | array | Aggregation pipeline |
Returns: Document[]
countDocuments
Count documents in a collection.
const count = await rest.countDocuments('users', { status: 'active' });
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
query | object | Filter query |
Returns: number
findOneAndUpdate
Find and update a single document.
const updated = await rest.findOneAndUpdate(
'users',
{ email: '[email protected]' },
{ $set: { name: 'John Doe' } },
{ upsert: true }
);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
filter | object | Filter document |
update | object | Update operators |
options | object | Options (upsert, etc.) |
Returns: Document | null
Insert Methods
insertOne
Insert a single document.
const user = await rest.insertOne('users', {
name: 'John',
email: '[email protected]',
});
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
data | object | Document data |
Returns: T & { _id: string }
insertMany
Insert multiple documents.
const users = await rest.insertMany('users', [
{ name: 'Alice' },
{ name: 'Bob' },
]);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
data | array | Array of documents |
Returns: (T & { _id: string })[]
Update Methods
updateOne
Update a single document by ID.
const result = await rest.updateOne(
'users',
'64f1a2b3c4d5e6f7a8b9c0d1',
{ $set: { name: 'Jane Doe' } }
);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
_id | string | Document ID |
update | object | Update operators |
Returns: Document
updateMany
Update multiple documents by IDs.
const result = await rest.updateMany(
'users',
['64f1...', '64f2...'],
{ $set: { status: 'inactive' } }
);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
_ids | string[] | Document IDs |
update | object | Update operators |
Returns: UpdateResult
Delete Methods
deleteOne
Delete a single document by ID.
const result = await rest.deleteOne('users', '64f1a2b3c4d5e6f7a8b9c0d1');
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
_id | string | Document ID |
Returns: Document
deleteMany
Delete multiple documents by IDs.
const result = await rest.deleteMany('users', ['64f1...', '64f2...']);
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
_ids | string[] | Document IDs |
Returns: DeleteResult
Bulk Operations
bulkWrite
Execute multiple operations in one batch.
const result = await rest.bulkWrite('users', [
{ insertOne: { document: { name: 'Alice' } } },
{ updateOne: { filter: { _id: '64f1...' }, update: { $set: { name: 'Bob' } } } },
{ deleteOne: { filter: { _id: '64f2...' } } },
]);
bulkUpdate
Execute multiple update operations.
const result = await rest.bulkUpdate('users', [
{ updateOne: { filter: { _id: '64f1...' }, update: { $set: { name: 'Alice' } } },
{ updateMany: { filter: { status: 'old' }, update: { $set: { status: 'archived' } } },
]);
Custom Action & Service Methods
runAction
Execute a custom action on a collection programmatically (same API as POST /api/:tenant/:collection/:action).
const result = await rest.runAction('users', 'getPublicProfile', {
userId: '64f1a2b3c4d5e6f7a8b9c0d1',
});
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection name |
action | string | Custom action name |
data | any | Data passed to the action |
Returns: The return value of the custom action
The action receives the same context as when called via HTTP:
{
rest: this, // Rest instance
data: any, // The data argument
error: fn.error, // Error helper
jwt: func.jwt, // JWT utilities
token, // Current token from request context
}
runAction, never call the same action recursively — directly or indirectly — as this will cause an infinite loop.// ❌ BAD — calling itself creates an infinite loop
// File: invoices.model.ts (collection)
actions: {
generateInvoice: async ({ rest, data }) => {
return await rest.runAction('invoices', 'generateInvoice', data);
},
}
// ✅ OK — calling a different action
// File: invoices.model.ts (collection)
actions: {
generateInvoice: async ({ rest, data }) => {
return await rest.runAction('invoices', 'sendInvoice', data);
},
}
Example — defining and calling a custom action:
// File: invoices.model.ts (collection)
import { define } from '@dnax/core';
export default define.Collection({
slug: 'invoices',
fields: [
{ name: 'total', type: 'number' },
{ name: 'status', type: 'string' },
],
actions: {
markAsPaid: async ({ rest, data, error }) => {
const invoice = await rest.findOne('invoices', data.id);
if (!invoice) throw error('Invoice not found', { status: 404 });
return await rest.updateOne('invoices', data.id, {
$set: { status: 'paid', paidAt: new Date() },
});
},
processPayment: async ({ rest, data }) => {
// Reuse markAsPaid action from the same collection
return await rest.runAction('invoices', 'markAsPaid', {
id: data.invoiceId,
});
},
},
});
// File: orders.model.ts (collection)
actions: {
checkout: async ({ rest, data, error }) => {
const cart = await rest.findOne('carts', data.cartId);
if (!cart || !cart.items?.length) {
throw error('Cart is empty', { status: 400 });
}
const order = await rest.insertOne('orders', {
userId: cart.userId,
items: cart.items,
total: cart.items.reduce((s, i) => s + i.price * i.qty, 0),
status: 'pending',
});
const payment = await rest.runAction('payments', 'process', {
orderId: order._id,
method: data.paymentMethod,
});
if (payment.success) {
await rest.runAction('orders', 'confirm', { id: order._id });
}
await rest.deleteOne('carts', data.cartId);
return { order, payment };
},
}
runService
Execute a service action programmatically.
runService, never call back into the same service action — directly or indirectly — as this will cause an infinite loop.// ❌ BAD — service calling itself
// File: billing.service.ts (service)
actions: {
generate: async ({ rest, data }) => {
return await rest.runService('billing', 'generate', data);
},
}
// ❌ BAD — circular: collection action → service → same collection action
// File: invoices.model.ts (collection)
actions: {
process: async ({ rest, data }) => {
return await rest.runService('billing', 'charge', data);
},
}
// If billing.charge (billing.service.ts) calls rest.runAction('invoices', 'process', ...) → loop
// ✅ OK — calling a different service
// File: orders.model.ts (collection)
actions: {
checkout: async ({ rest, data }) => {
return await rest.runService('billing', 'generate', data);
// billing.generate does NOT call back into orders model
},
}
Example — defining a service and calling it from a collection action:
// File: billing.service.ts (service)
import { define } from '@dnax/core';
export default define.Service({
name: 'billing',
enabled: true,
actions: {
generate: async ({ rest, data }) => {
return await rest.insertOne('invoices', {
client: data.clientId,
total: data.total,
status: 'draft',
});
},
},
});
Usage from a collection action:
// File: orders.model.ts (collection)
actions: {
checkout: async ({ rest, data, error }) => {
const cart = await rest.findOne('carts', data.cartId);
if (!cart) throw error('Cart not found', { status: 404 });
// Call the billing service to generate an invoice
const invoice = await rest.runService('billing', 'generate', {
clientId: cart.userId,
total: cart.items.reduce((s, i) => s + i.price * i.qty, 0),
});
// Call the notification service to send confirmation
await rest.runService('notifications', 'send', {
userId: cart.userId,
type: 'order_confirmation',
invoiceId: invoice._id,
});
return { invoice };
},
}
// File: users.model.ts (collection)
actions: {
onboardUser: async ({ rest, data, error }) => {
// Create user
const user = await rest.insertOne('users', data);
try {
// Provision resources via external service
await rest.runService('provisioning', 'createWorkspace', {
userId: user._id,
plan: data.plan,
});
// Send welcome email
await rest.runService('notifications', 'sendEmail', {
to: data.email,
template: 'welcome',
});
// Log activity to analytics
await rest.runService('analytics', 'track', {
event: 'user.onboarded',
userId: user._id,
});
} catch (err) {
// Rollback: notify support of the partial failure
await rest.runService('support', 'createTicket', {
subject: 'Onboarding partial failure',
message: `User ${user._id} created but post-setup failed: ${err.message}`,
});
throw error('Onboarding failed, support has been notified', { status: 500 });
}
return user;
},
}
| Parameter | Type | Description |
|---|---|---|
service | string | Service name |
action | string | Action name |
data | any | Data passed to the action |
Returns: The return value of the service action
The action receives the same context as when called via HTTP:
{
data: any, // The data argument
error: fn.error, // Error helper
io, // Socket.IO server instance
jwt: func.jwt, // JWT utilities
token, // Current token from request context
rest: this, // Rest instance
}
Index Operations
dropIndex
Drop a specific index.
await rest.dropIndex('users', 'email_index');
dropIndexes
Drop all indexes.
await rest.dropIndexes('users');
dropCollection
Drop the entire collection.
await rest.dropCollection('users');
Transaction Support
startTransaction
Start a transaction.
rest.startTransaction();
commitTransaction
Commit the transaction.
await rest.commitTransaction();
abortTransaction
Abort the transaction.
await rest.abortTransaction();
startSession
Start a new session.
const session = rest.startSession();
Change Streams
watch
Watch for changes in a collection.
const stream = await rest.watch('users', [
{ $match: { operationType: 'insert' } }
], { fullDocument: 'updateLookup' });
Properties
| Property | Type | Description |
|---|---|---|
client | MongoClient | MongoDB client |
db | Db | Database instance |
tenant_id | string | Current tenant ID |
session | ClientSession | Current session |