Skip to main content

Models

Loopar uses a document-based model system built on top of Sequelize ORM. All models extend from BaseDocument, which provides a rich set of methods for data manipulation and validation.

BaseDocument Class

The BaseDocument class is the foundation for all data models in Loopar:
packages/loopar/core/document/base-document.js
import CoreDocument from './core-document.js';
import { loopar } from '../loopar.js';
import { Op } from '@sequelize/core';

export default class BaseDocument extends CoreDocument {
  constructor(props) {
    super(props);
  }

  // Model methods...
}

Field Types

Loopar provides a comprehensive set of field types defined in the TYPES object:
packages/loopar/core/global/element-definition.js
export const TYPES = Object.freeze({
  increments: 'increments',      // Auto-incrementing integer
  integer: 'INTEGER',             // Integer
  bigInteger: 'BIGINT',          // Big integer
  float: 'FLOAT',                // Floating point
  decimal: 'DECIMAL',            // Decimal with precision
  double: 'DOUBLE',              // Double precision
  smallint: 'SMALLINT',          // Small integer
  tinyint: 'TINYINT',            // Tiny integer
  string: 'STRING',              // VARCHAR(255)
  text: 'TEXT',                  // Text field
  mediumtext: 'TEXT.medium',     // Medium text
  longtext: 'TEXT.long',         // Long text
  uuid: 'UUID',                  // UUID
  enum: 'ENUM',                  // Enumeration
  boolean: 'BOOLEAN',            // Boolean
  date: 'DATEONLY',              // Date only
  dateTime: 'DATE',              // Date and time
  time: 'TIME',                  // Time only
  timestamp: 'DATE',             // Timestamp
  timestamps: 'timestamps',       // Created/updated timestamps
  binary: 'BLOB',                // Binary data
  json: 'JSON',                  // JSON data
  jsonb: 'JSONB',                // JSON binary (PostgreSQL)
  geometry: 'GEOMETRY',          // Geometry type
  point: 'GEOMETRY.POINT',       // Point geometry
  multiPoint: 'GEOMETRY.MULTIPOINT' // Multi-point geometry
});

Form Elements

Form elements define how fields are rendered and stored:
packages/loopar/core/global/element-definition.js
export const elementsDefinition = {
  [FORM_ELEMENT]: [
    { element: "input", icon: "FormInput", type: TYPES.string },
    { element: "password", icon: "Asterisk", type: TYPES.text },
    { element: "date", icon: "Calendar", type: TYPES.date, format: 'YYYY-MM-DD' },
    { element: "date_time", icon: "CalendarClock", type: TYPES.dateTime, format: 'YYYY-MM-DD HH:mm:ss' },
    { element: "time", icon: "Clock10", type: TYPES.time, format: 'HH:mm:ss' },
    { element: "currency", icon: "Currency", type: TYPES.decimal },
    { element: "integer", icon: "fa-duotone fa-input-numeric", type: TYPES.integer },
    { element: "decimal", icon: "fa fa-00", type: TYPES.decimal },
    { element: "select", icon: "ChevronDown", type: TYPES.text },
    { element: "textarea", icon: "FileText", type: TYPES.longtext },
    { element: "text_editor", icon: "TextCursorInput", type: TYPES.longtext, clientOnly: true },
    { element: "checkbox", icon: "CheckSquare", type: TYPES.integer },
    { element: "switch", icon: "ToggleLeft", type: TYPES.integer },
    { element: "id", icon: "BookKey", type: TYPES.increments },
    { element: "file_input", icon: "FileInput", type: TYPES.longtext },
    { element: "image_input", icon: "FileImage", type: TYPES.longtext },
    { element: "color_picker", icon: "Palette", type: TYPES.text },
    { element: "radio_group", icon: "Circle", type: TYPES.text }
  ]
}

Creating a Model

Models are created by defining an Entity with a document structure:
// Example: User model structure
const userDocStructure = [
  {
    element: "row",
    elements: [
      {
        element: "col",
        elements: [
          {
            element: "input",
            data: {
              name: "name",
              label: "Full Name",
              required: true
            }
          },
          {
            element: "input",
            data: {
              name: "email",
              label: "Email",
              format: "email",
              required: true,
              unique: true
            }
          },
          {
            element: "password",
            data: {
              name: "password",
              label: "Password",
              required: true
            }
          },
          {
            element: "switch",
            data: {
              name: "disabled",
              label: "Disabled",
              default_value: 0
            }
          },
          {
            element: "date_time",
            data: {
              name: "created_at",
              label: "Created At",
              set_only_time: true
            }
          }
        ]
      }
    ]
  }
];

Field Properties

name
string
required
Field name (must be unique within the model)
label
string
required
Human-readable field label
element
string
required
Field element type (input, select, textarea, etc.)
required
boolean
Whether the field is required (default: false)
unique
boolean
Whether the field value must be unique (default: false)
default_value
any
Default value for the field
format
string
Input format (email, url, phone, etc.)
length
number
Maximum length for string fields
precision
number
Precision for decimal fields (default: 10)
scale
number
Scale for decimal fields (default: 2)
index
boolean
Create an index on this field (default: false)

Working with Models

Creating a New Document

// Create a new User document
const user = await loopar.newDocument('User', {
  name: 'John Doe',
  email: 'john@example.com',
  password: 'securePassword123'
});

// Save to database
await user.save();

Getting a Document

// Get a User by name
const user = await loopar.getDocument('User', 'john@example.com');

// Access field values
console.log(user.name);     // "John Doe"
console.log(user.email);    // "john@example.com"
console.log(user.disabled); // 0

Updating a Document

// Get the document
const user = await loopar.getDocument('User', 'john@example.com');

// Update fields
user.name = 'John Smith';
user.disabled = 1;

// Save changes
await user.save();

Deleting a Document

// Soft delete (default)
await loopar.deleteDocument('User', 'john@example.com');

// Hard delete with force option
await loopar.deleteDocument('User', 'john@example.com', { 
  sofDelete: false,
  force: true 
});

Field Validation

Loopar automatically validates fields based on their type and properties:
packages/loopar/core/global/element-definition.js
class DataInterface {
  isEmail() {
    var regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return {
      valid: regex.test(this.value),
      message: 'Invalid email address'
    }
  }

  validatorRequired() {
    const required = [true, 'true', 1, '1'].includes(this.data.required);
    return {
      valid: !required || !(typeof this.value == "undefined" || 
             (["null", "undefined"].includes(this.value) || 
             (this.value || "").toString().length === 0)),
      message: `${this.__label()} is required`
    }
  }
}

Reserved Field Names

The following field names are reserved and automatically added to all models:
  • id - Auto-incrementing primary key
  • name - Document name (unique identifier)
  • __document_status__ - Document status (Active/Deleted)

Custom Model Classes

You can extend BaseDocument to add custom methods:
import BaseDocument from '@loopar/core/document/base-document.js';

export default class User extends BaseDocument {
  constructor(props) {
    super(props);
  }

  // Custom method
  async sendWelcomeEmail() {
    // Send welcome email logic
    console.log(`Sending welcome email to ${this.email}`);
  }

  // Override onLoad hook
  async onLoad() {
    // Custom logic when document is loaded
    console.log(`User ${this.name} loaded`);
  }

  // Validation hook
  async validate() {
    await super.validate();
    
    // Custom validation
    if (this.email && !this.email.includes('@')) {
      loopar.throw('Invalid email format');
    }
  }
}

Next Steps

Queries

Learn how to query data

Migrations

Manage schema changes

Build docs developers (and LLMs) love