Manage Data

Internal API

Internal API documentation for dnax Framework.

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 },
});
ParameterTypeDescription
collectionstringCollection name
paramsobjectPipeline parameters
optionsobjectAdditional options

Returns: Document[]


findOne

Retrieve a single document by ID.

const user = await rest.findOne('users', '64f1a2b3c4d5e6f7a8b9c0d1');
ParameterTypeDescription
collectionstringCollection name
_idstringDocument ID
paramsobjectOptional 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' } } },
]);
ParameterTypeDescription
collectionstringCollection name
pipelinearrayAggregation pipeline

Returns: Document[]


countDocuments

Count documents in a collection.

const count = await rest.countDocuments('users', { status: 'active' });
ParameterTypeDescription
collectionstringCollection name
queryobjectFilter 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 }
);
ParameterTypeDescription
collectionstringCollection name
filterobjectFilter document
updateobjectUpdate operators
optionsobjectOptions (upsert, etc.)

Returns: Document | null



Insert Methods

insertOne

Insert a single document.

const user = await rest.insertOne('users', {
  name: 'John',
  email: '[email protected]',
});
ParameterTypeDescription
collectionstringCollection name
dataobjectDocument data

Returns: T & { _id: string }


insertMany

Insert multiple documents.

const users = await rest.insertMany('users', [
  { name: 'Alice' },
  { name: 'Bob' },
]);
ParameterTypeDescription
collectionstringCollection name
dataarrayArray 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' } }
);
ParameterTypeDescription
collectionstringCollection name
_idstringDocument ID
updateobjectUpdate operators

Returns: Document


updateMany

Update multiple documents by IDs.

const result = await rest.updateMany(
  'users',
  ['64f1...', '64f2...'],
  { $set: { status: 'inactive' } }
);
ParameterTypeDescription
collectionstringCollection name
_idsstring[]Document IDs
updateobjectUpdate operators

Returns: UpdateResult


Delete Methods

deleteOne

Delete a single document by ID.

const result = await rest.deleteOne('users', '64f1a2b3c4d5e6f7a8b9c0d1');
ParameterTypeDescription
collectionstringCollection name
_idstringDocument ID

Returns: Document


deleteMany

Delete multiple documents by IDs.

const result = await rest.deleteMany('users', ['64f1...', '64f2...']);
ParameterTypeDescription
collectionstringCollection name
_idsstring[]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',
});
ParameterTypeDescription
collectionstringCollection name
actionstringCustom action name
dataanyData 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
}
Avoid infinite loops. When using 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);
  },
}
Always target a different action or extract shared logic into a separate function.

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.

Avoid infinite loops. When using 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
  },
}
Always target a different service or extract shared logic into a separate function.

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;
  },
}
ParameterTypeDescription
servicestringService name
actionstringAction name
dataanyData 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

PropertyTypeDescription
clientMongoClientMongoDB client
dbDbDatabase instance
tenant_idstringCurrent tenant ID
sessionClientSessionCurrent session
Copyright © 2026