Skip to main content

Entity Spec Schema

This page defines the complete JSON Schema for Entity specifications in Fascia. An Entity is the fundamental business object abstraction -- it encapsulates fields, relationships, a status machine, and invariants. The database storage model (tables, columns, indexes) is automatically derived from the Entity spec.

JSON Schema

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["name", "version", "description", "fields", "statusMachine"],
"properties": {
"name": {
"type": "string",
"pattern": "^[A-Z][a-zA-Z0-9]*$",
"description": "PascalCase entity name"
},
"version": {
"type": "integer",
"minimum": 1
},
"description": {
"type": "string"
},
"fields": {
"type": "object",
"minProperties": 1,
"additionalProperties": { "$ref": "#/$defs/Field" }
},
"relationships": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/Relationship" }
},
"statusMachine": { "$ref": "#/$defs/StatusMachine" },
"invariants": {
"type": "array",
"items": { "$ref": "#/$defs/Invariant" }
},
"rowLevelAccess": {
"type": "boolean",
"default": false
},
"ownerField": {
"type": "string"
}
}
}

Top-Level Properties

PropertyTypeRequiredDescription
namestringYesPascalCase entity name (e.g., Reservation, PaymentRecord). Must match ^[A-Z][a-zA-Z0-9]*$.
versionintegerYesImmutable version number, starting at 1. Incremented on each change.
descriptionstringYesHuman-readable description of the entity's purpose.
fieldsobjectYesCustom fields defined for this entity. At least one field is required. See Field.
relationshipsobjectNoConnections to other entities. See Relationship.
statusMachineobjectYesState machine defining allowed states and transitions. See StatusMachine.
invariantsarrayNoBusiness rules that must always hold true. See Invariant.
rowLevelAccessbooleanNoWhen true, non-admin users can only access rows they own. Defaults to false.
ownerFieldstringNoField name that references the owning User entity. Required when rowLevelAccess is true.

Definitions

Field

Each entry in the fields object defines a typed data attribute on the entity.

{
"type": "object",
"required": ["type"],
"properties": {
"type": {
"enum": ["string", "number", "boolean", "date", "datetime", "enum", "uuid", "json", "reference"]
},
"required": { "type": "boolean", "default": false },
"unique": { "type": "boolean", "default": false },
"indexed": { "type": "boolean", "default": false },
"default": {},
"enumValues": {
"type": "array",
"items": { "type": "string" },
"description": "Required when type is 'enum'"
},
"referenceTo": {
"type": "string",
"description": "Target entity name. Required when type is 'reference'."
},
"description": { "type": "string" }
}
}
PropertyTypeRequiredDescription
typeenumYesOne of: string, number, boolean, date, datetime, enum, uuid, json, reference.
requiredbooleanNoWhether the field must have a value. Defaults to false.
uniquebooleanNoWhether a unique constraint is applied. Defaults to false.
indexedbooleanNoWhether the field is indexed for query performance. Defaults to false.
defaultanyNoDefault value applied when the field is not provided on creation.
enumValuesstring[]ConditionalList of allowed values. Required when type is "enum".
referenceTostringConditionalPascalCase name of the target entity. Required when type is "reference".
descriptionstringNoHuman-readable description of the field.

Allowed Field Types

TypeDescriptionDatabase Mapping
stringText dataTEXT or VARCHAR
numberNumeric data (integer or decimal)NUMERIC
booleanTrue/falseBOOLEAN
dateCalendar date without timeDATE
datetimeDate with time and timezoneTIMESTAMPTZ
enumOne of a fixed set of string valuesTEXT with CHECK constraint
uuidUUID identifierUUID
jsonArbitrary JSON dataJSONB
referenceForeign key to another entityUUID (FK constraint)

Relationship

Defines connections between entities.

{
"type": "object",
"required": ["type", "target"],
"properties": {
"type": { "enum": ["hasOne", "hasMany", "belongsTo", "manyToMany"] },
"target": { "type": "string", "description": "Target entity name" },
"foreignKey": { "type": "string" },
"through": { "type": "string", "description": "Join table name for manyToMany" }
}
}
PropertyTypeRequiredDescription
typeenumYesOne of: hasOne, hasMany, belongsTo, manyToMany.
targetstringYesPascalCase name of the related entity.
foreignKeystringNoName of the foreign key column. Auto-derived if omitted.
throughstringNoJoin table name. Required for manyToMany relationships.

Relationship Types

TypeCardinalityForeign Key Location
hasOne1:1On the target entity
hasMany1:NOn the target entity
belongsToN:1On the current entity
manyToManyN:MIn a separate join table (specified by through)

StatusMachine

Every entity must define a status machine, even if simple (e.g., active to deleted). The status machine governs which state transitions are allowed.

{
"type": "object",
"required": ["states", "initialState", "transitions"],
"properties": {
"states": {
"type": "array",
"items": { "type": "string" },
"minItems": 2
},
"initialState": { "type": "string" },
"transitions": {
"type": "array",
"items": { "$ref": "#/$defs/Transition" },
"minItems": 1
}
}
}
PropertyTypeRequiredDescription
statesstring[]YesAll possible states. Minimum of 2 states. Use lowercase_snake naming (e.g., pending, in_progress).
initialStatestringYesThe state assigned to newly created records. Must be in the states array.
transitionsTransition[]YesList of allowed state transitions. At least one transition is required.

Rules:

  • Every entity must have a status machine.
  • Transitions must be explicit -- no wildcard transitions.
  • initialState must be one of the defined states.

Transition

Defines an allowed state change within a status machine.

{
"type": "object",
"required": ["from", "to"],
"properties": {
"from": { "type": "string" },
"to": { "type": "string" },
"guard": {
"type": "string",
"description": "Value DSL boolean expression. Transition only allowed if true."
}
}
}
PropertyTypeRequiredDescription
fromstringYesSource state. Must be in the states array.
tostringYesTarget state. Must be in the states array.
guardstringNoA Value DSL boolean expression. The transition is only allowed when this expression evaluates to true.

Invariant

A business rule that must always hold true for an entity. Invariants are checked after flow execution but before transaction commit (step 6 of the Execution Contract).

{
"type": "object",
"required": ["name", "expression"],
"properties": {
"name": { "type": "string", "description": "camelCase assertion-style name" },
"expression": { "type": "string", "description": "Value DSL boolean expression" },
"message": { "type": "string", "description": "Error message when violated" }
}
}
PropertyTypeRequiredDescription
namestringYescamelCase, assertion-style name (e.g., orderTotalPositive, endDateAfterStart).
expressionstringYesA Value DSL boolean expression. Must evaluate to true for the entity to be valid.
messagestringNoHuman-readable error message returned when the invariant is violated.

System Fields

Every entity automatically includes the following system fields. Do not define these manually in the fields object -- they are injected by the platform.

FieldTypeDescription
iduuidPrimary key. Auto-generated on creation.
createdAtdatetimeTimestamp set automatically on creation.
updatedAtdatetimeTimestamp set automatically on every update.
deletedAtdatetime (nullable)Set when the record is soft-deleted. null for active records.
versionintegerOptimistic locking counter. Starts at 1, incremented on each update.

Example

A complete Reservation entity spec:

{
"name": "Reservation",
"version": 1,
"description": "A vehicle reservation made by a customer",
"fields": {
"customerId": {
"type": "reference",
"referenceTo": "Customer",
"required": true,
"indexed": true,
"description": "The customer who made the reservation"
},
"vehicleId": {
"type": "reference",
"referenceTo": "Vehicle",
"required": true,
"description": "The reserved vehicle"
},
"startDate": {
"type": "datetime",
"required": true,
"description": "Reservation start date and time"
},
"endDate": {
"type": "datetime",
"required": true,
"description": "Reservation end date and time"
},
"totalPrice": {
"type": "number",
"required": true,
"description": "Total price for the reservation period"
},
"notes": {
"type": "string",
"description": "Optional notes from the customer"
}
},
"relationships": {
"customer": {
"type": "belongsTo",
"target": "Customer",
"foreignKey": "customerId"
},
"vehicle": {
"type": "belongsTo",
"target": "Vehicle",
"foreignKey": "vehicleId"
},
"payments": {
"type": "hasMany",
"target": "Payment"
}
},
"statusMachine": {
"states": ["pending", "confirmed", "in_progress", "completed", "cancelled"],
"initialState": "pending",
"transitions": [
{ "from": "pending", "to": "confirmed" },
{ "from": "pending", "to": "cancelled" },
{ "from": "confirmed", "to": "in_progress", "guard": "now() >= startDate" },
{ "from": "confirmed", "to": "cancelled" },
{ "from": "in_progress", "to": "completed", "guard": "now() >= endDate" }
]
},
"invariants": [
{
"name": "endDateAfterStart",
"expression": "endDate > startDate",
"message": "End date must be after start date"
},
{
"name": "totalPricePositive",
"expression": "totalPrice > 0",
"message": "Total price must be positive"
}
],
"rowLevelAccess": true,
"ownerField": "customerId"
}

See Also