Skip to content

Model Modules ​

Language: JavaScript

The model layer is where every entity defines its shape, validation rules, errors, and business processes. It is split into three peer packages - base-model, server-model, client-model - that compose into a single namespace at runtime via key-by-key merge in the loader. This document covers all three.

On This Page ​


Base Model Modules ​

base-model defines the shared domain model for an entity. It provides canonical data structures, validation, domain rules, and DTO transformations. Pure and IO-free, it is safe to share between server and client.

Base Model - Purpose ​

  • Define the shared domain model for an entity or domain
  • Provide canonical data structures, validations, domain rules, and DTO transformations
  • Be pure and IO-free so it can be safely shared between server and client when both run JavaScript

Base Model - Design Principles ​

PrincipleDetail
Domain-focused and deterministicSame input always produces the same output
Pure functions preferredNo hidden state
No database calls, no network callsBelongs in service layer
No secrets, no environment configBelongs in the loader
Explicit inputs and outputsFunction signatures are the contract
Stable contractsData and entity shapes evolve deliberately

Base Model - Naming Convention ​

ElementConvention
Module directory name[entity-name] (singular, e.g., user, survey)
Location[project]/src/model/[entity-name]/

Base Model - Standard Files ​

FilePurpose
index.jsPackage entry - returns { Contact: fn, User: fn, Survey: fn, Shared: fn }. Each property is a constructor function. The loader calls Models.Contact(Lib, {}) to execute it and get { data, errors, process, validation, _config }
[entity].config.jsDomain constants and rules (min/max lengths, regex patterns, enums, limits). Domain policy, not environment configuration
[entity].errors.jsDomain error catalog (error codes + default messages + optional HTTP status)
[entity].data.jsConsolidated entity constructors and DTO transformations. All DTOs are derived from the canonical entity shape. Each entity has ONE canonical structure
[entity].process.jsPure business logic - calculations, transformations, collections management. Receives Lib (with Lib.Utils) via the loader pattern
[entity].validation.jsPure validation functions based on [entity].config.js

index.js File Comments and Pattern ​

CommentFormat
Header// Info: Public export surface for [Entity] base model module
Dependencies note// Dependencies: Contact, User (uses Contact.validation, User.process)
Pattern note// Standard pattern: Loader receives Lib and config override, returns { data, errors, process, validation, _config }

The full index.js template lives in module-structure.md.

[entity].data.js Function Set ​

Each entity's data module typically exports:

FunctionPurpose
create(...)Build a complete internal shape with defaults
createUpdate(...)Partial update shape - only provided fields
toPublic(...)Strip server-only fields for API output
toSummary(...)Minimal version for list views
toInternal(...)Map external input to canonical internal shape

Keys not provided (undefined) are simply not added to the resulting object. See DTO Philosophy (JavaScript) for the rationale.

[entity].validation.js Cross-Module Validation ​

If validation delegates to another model (e.g., User validating an email via Contact), the validation module must use the loader pattern to receive Lib and access Lib.OtherModel.

javascript
// User validation that uses Contact's email validator
// Inside user.validation.js
const result = Lib.Contact.validation.validateEmail(email);

Never directly import another entity's model from inside a validation file - it bypasses the loader and breaks dependency injection.

DTO Terminology ​

TermWhere it appears
DTO (Data Transfer Object)Documentation - describes the conceptual pattern of transforming data shapes
DataCode - the actual API uses data for the module name (e.g., user.data.create(), user.data.toPublic())

This is intentional. Docs describe the what (DTO pattern); code implements the how (data module).

Base Model - Boundary Rules ​

base-model may be used by ​

  • server-service
  • server-controller
  • Client applications

base-model must NOT ​

  • Access database or repositories
  • Call external services (SMS, email, payments, ...)
  • Depend on server-only runtime assumptions
  • Contain authorization or policy decisions

Server Model Modules ​

server-model adds server-only properties and methods to base models - audit trails, internal IDs, admin-only DTOs, policy rules. They are peer packages to the base model, not subclasses. Both packages produce the same shape independently; the loader merges them at runtime.

Peer Package Pattern ​

RuleDetail
No imports between base and serverServer models do not import or reference the base model internally
Same return shapeBoth packages produce { data, errors, process, validation, _config }
Loader merges key-by-keyObject spread combines the two
Composition, not inheritanceThe merge happens in the loader, not in the modules

Merge Mechanics ​

The loader is responsible for the merge:

javascript
// Base loads first, assigned to Lib
const SurveyModel = Models.Survey(Lib, {});
Lib.Survey = {
  data: SurveyModel.data,
  errors: SurveyModel.errors,
  process: SurveyModel.process,
  validation: SurveyModel.validation
};

// Server extension loads second (can reference Lib.Survey)
const SurveyModelExtended = ModelsExtended.Survey(Lib, {});

// Loader merges key-by-key (extended adds to or overrides base)
Lib.Survey = {
  data: { ...Lib.Survey.data, ...SurveyModelExtended.data },
  errors: { ...Lib.Survey.errors, ...SurveyModelExtended.errors },
  process: { ...Lib.Survey.process, ...SurveyModelExtended.process },
  validation: { ...Lib.Survey.validation, ...SurveyModelExtended.validation }
};

// Config merged privately, never exposed on Lib
const SurveyConfig = { ...SurveyModel._config, ...SurveyModelExtended._config };

After the merge, callers access Lib.Survey.data.* transparently - both base and server methods are available on the same namespace.

Config Privacy ​

_config is private. The loader merges base._config + extended._config into a local variable and passes it to service and controller. Never exposed on Lib.Entity.

Structure ​

server-model follows the same file naming and layout as base-model:

  • index.js, [entity].config.js, [entity].data.js, [entity].errors.js, [entity].process.js, [entity].validation.js
  • Each entity constructor returns { data, errors, process, validation, _config }

What Belongs in Server Model ​

  • Server-only fields: created_by, organization_id, internal_notes, audit_trail
  • Admin-only DTOs and output shapes
  • Server-side policy logic

What Does NOT Belong Here ​

  • Base entity shapes (those live in model/)
  • Universal validations (those live in model/)
  • Client-relevant logic (that lives in model-client/)

Server Model - Naming Convention ​

ElementConvention
Module directory name[entity-name] (singular)
Location[project]/src/model-server/[entity-name]/

Client Model Modules ​

client-model adds client-relevant properties and methods to base models - client-side metadata, state tracking, lightweight presentation validations. Same peer-package pattern as server-model. Independently loaded; merged by the client-side loader.

What Belongs in Client Model ​

  • Client-relevant metadata: last_fetched_date, cache_expiry, sync_status
  • Client-side state tracking helpers
  • Lightweight client-only validations (e.g., real-time form checks)
  • Client-specific formatting and presentation helpers

What Does NOT Belong Here ​

  • Server logic (lives in model-server/)
  • Security-critical validations (must be in model/ or model-server/)
  • Browser-specific or platform-specific APIs (localStorage, window, document)

Merge Pattern ​

Same as server model - the client-side loader merges base + client extension key-by-key.

Client Model - Naming Convention ​

ElementConvention
Module directory name[entity-name] (singular)
Location[project]/src/model-client/[entity-name]/

Further Reading ​

Released under the MIT License.