Overview
The BaseDocument class extends CoreDocument and provides a complete data model layer for Loopar Framework applications. It handles CRUD operations, field validation, list views, search functionality, and parent-child document relationships.
Import
import { BaseDocument } from 'loopar';
Constructor
class MyDocument extends BaseDocument {
constructor(props) {
super(props);
}
}
Properties
Entity metadata including name, module, doc_structure
Raw document data from database
Indicates if document is not yet saved to database
Unique identifier of the document
Application name that owns this document
Collection of DynamicField instances for each field
CRUD Operations
save(options)
Saves the document to the database (insert if new, update if existing).
Whether to run validation before saving
const doc = await loopar.newDocument('User', {
name: 'user@example.com',
email: 'user@example.com',
first_name: 'John'
});
await doc.save();
// Save without validation
await doc.save({ validate: false });
Process Flow:
- Sets unique name if required
- Runs validation (unless disabled)
- Inserts or updates database record
- Handles child table records
- Updates document history
- Processes file uploads
delete(options)
Deletes the document from the database.
Mark as deleted vs permanently remove
Force delete even if connected to other documents
Create deletion history entry
const user = await loopar.getDocument('User', 'old-user@example.com');
// Soft delete
await user.delete({ sofDelete: true });
// Hard delete (if no connections)
await user.delete({ sofDelete: false });
// Force delete (ignores connections)
await user.delete({ force: true });
List Views
getList(options)
Retrieves a paginated list of documents with optional filtering and search.
Array of field names to retrieve (defaults to fields marked for list view)
Key-value pairs for exact match filtering
Search query object with field names and values
Return only rows without metadata and pagination
const userDoc = await loopar.newDocument('User');
const result = await userDoc.getList({
fields: ['name', 'email', 'first_name', 'last_name'],
filters: { disabled: 0 },
q: { first_name: 'John' }
});
console.log(result.rows); // Array of user records
console.log(result.pagination); // { page, pageSize, totalPages, totalRecords }
console.log(result.fields); // Field names
console.log(result.labels); // Field labels
Response Structure:
{
rows: [
{ id: 1, name: 'user1', email: 'user1@example.com', ... },
{ id: 2, name: 'user2', email: 'user2@example.com', ... }
],
pagination: {
page: 1,
pageSize: 10,
totalPages: 5,
totalRecords: 47,
sortBy: 'id',
sortOrder: 'asc'
},
fields: ['name', 'email', 'first_name'],
labels: ['Name', 'Email', 'First Name'],
q: { first_name: 'John' }
}
Retrieves document list for form selection (child tables, dropdowns).
Same options as getList()
const items = await doc.getListToForm({
filters: { parent_id: 123 },
fields: ['name', 'quantity', 'price']
});
// Returns array of rows directly
console.log(items); // [{ name: 'Item 1', quantity: 5, ... }]
getListToSelectElement(q)
Retrieves documents for select/dropdown elements with search.
const userDoc = await loopar.newDocument('User');
const options = await userDoc.getListToSelectElement('john');
console.log(options.rows); // Matching users
console.log(options.title_fields); // Fields to display
Response:
{
title_fields: ['name', 'email'],
rows: [
{ name: 'john@example.com', email: 'john@example.com' },
{ name: 'johnny@example.com', email: 'johnny@example.com' }
]
}
Field Operations
getFieldListNames()
Returns array of field names configured for list view.
const fields = doc.getFieldListNames();
// ['name', 'email', 'created_at']
getFieldListLabels()
Returns array of field labels for list view.
const labels = doc.getFieldListLabels();
// ['Name', 'Email', 'Created At']
getFieldSelectNames()
Returns fields used for searching in select elements.
const searchFields = doc.getFieldSelectNames();
// ['name', 'email', 'first_name']
getFieldSelectLabels()
Returns fields displayed as title in select elements.
const titleFields = doc.getFieldSelectLabels();
// ['first_name', 'last_name']
getValueDescriptive(name)
Retrieves formatted display value for linked document.
Document name to retrieve
const description = await doc.getValueDescriptive('john@example.com');
// 'John - Doe - john@example.com'
Validation
validate()
Validates all document fields and linked documents. Throws error if validation fails.
try {
await doc.validate();
} catch (error) {
console.error('Validation failed:', error.message);
}
Validation Rules:
- Required fields must have values
- Field data types must match
- Linked documents must exist
- Select field values must be in allowed options
validateLinkDocuments()
Validates that all linked documents (SELECT fields) exist and are valid.
const errors = await doc.validateLinkDocuments();
if (errors.length > 0) {
console.error('Link validation errors:', errors);
}
Search & Filtering
buildCondition(q)
Builds Sequelize WHERE conditions from search query.
Search query with field names as keys
const condition = doc.buildCondition({
first_name: 'John',
email: 'john@example.com',
disabled: 0
});
// Returns Sequelize condition object
// { [Op.and]: [{ first_name: { [Op.like]: '%John%' }}, ...] }
Field Handling:
- Text fields: Uses LIKE with wildcards (
%value%)
- Select/Switch/Checkbox: Exact match
- Empty values: Ignored
buildConditionToSelect(q)
Builds conditions specifically for select element searches.
const condition = doc.buildConditionToSelect('john');
records(condition)
Returns count of records matching condition.
Sequelize WHERE condition
const totalUsers = await userDoc.records({ disabled: 0 });
console.log(`Active users: ${totalUsers}`);
Relationships
getConnectedDocuments()
Returns all documents that reference this document.
const connections = await doc.getConnectedDocuments();
// [
// { entity: 'Order', name: 'ORD-001' },
// { entity: 'Task', name: 'TASK-123' }
// ]
Useful for:
- Preventing deletion of referenced documents
- Displaying related records
- Cascade operations
getChildValues(field)
Retrieves child table records for a form table field.
Field name of the form table
const items = await doc.getChildValues('order_items');
console.log(items); // Array of child records
getChildRawValues(field)
Retrieves raw child table data without formatting.
const rawItems = await doc.getChildRawValues('order_items');
deleteChildRecords(force)
Deletes all child table records for this document.
Force deletion even without child values
await doc.deleteChildRecords(true);
Data Access
values(raw)
Returns formatted document values.
Return raw values without formatting
const values = await doc.values();
// {
// name: 'user@example.com',
// password: '********', // Protected
// created_at: '2024-01-15T10:30:00'
// }
const rawValues = await doc.values(true);
// { password: 'actual_hash_value', ... }
rawValues()
Returns raw document data.
const data = await doc.rawValues();
stringifyValues
Gets string-formatted values for database insertion.
const dbValues = doc.stringifyValues;
Gets display-formatted values.
const formatted = doc.formattedValues;
History
updateHistory(action)
Creates a history entry for the document.
Action type: ‘Created’, ‘Updated’, ‘Deleted’
await doc.updateHistory('Updated');
History tracks:
- User who made the change
- Timestamp
- Action type
- Document state
Lifecycle Hooks
Override these methods in your subclass:
class CustomDocument extends BaseDocument {
async onLoad() {
// Called after document is loaded
}
async beforeSave() {
// Called before save
}
async afterSave() {
// Called after save
}
async beforeDelete() {
// Called before delete
}
async afterDelete() {
// Called after delete
}
}
Example: Custom Document
import { BaseDocument } from 'loopar';
class Order extends BaseDocument {
constructor(props) {
super(props);
}
async validate() {
await super.validate();
// Custom validation
if (this.total < 0) {
loopar.throw('Order total cannot be negative');
}
}
async save() {
// Calculate total before saving
const items = await this.getChildValues('order_items');
this.total = items.reduce((sum, item) =>
sum + (item.quantity * item.price), 0
);
await super.save();
}
}
export default Order;
Complete Example
import { loopar } from 'loopar';
// Create new document
const order = await loopar.newDocument('Order', {
customer: 'john@example.com',
order_date: new Date(),
status: 'Draft'
});
// Add child items
order.order_items = [
{ item: 'Product A', quantity: 2, price: 50.00 },
{ item: 'Product B', quantity: 1, price: 75.00 }
];
// Save with validation
await order.save();
// Retrieve and update
const existingOrder = await loopar.getDocument('Order', order.name);
existingOrder.status = 'Submitted';
await existingOrder.save();
// List orders
const orders = await loopar.getList('Order', {
filters: { status: 'Submitted' },
q: { customer: 'john' }
});
console.log(`Found ${orders.pagination.totalRecords} orders`);
// Delete with connection check
const checkConnections = await order.getConnectedDocuments();
if (checkConnections.length === 0) {
await order.delete();
} else {
console.log('Cannot delete: order has connections');
}