Generate Fluent DataModels for FOSMVVM server-side persistence. Use when creating new database-backed entities, adding tables, or when the user mentions adding Models like Users, Ideas, Documents, etc. Uses fosmvvm-fields-generator for the Fields layer, then generates Fluent DataModel, migrations, and tests.
/plugin marketplace add foscomputerservices/FOSUtilities/plugin install fosmvvm-generators@fosmvvm-toolsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Generate Fluent DataModels for server-side persistence following FOSMVVM architecture.
Dependency: This skill uses fosmvvm-fields-generator for the Fields layer (protocol, messages, YAML). Run that skill first for form-backed models.
This skill is specifically for Fluent persistence layer (typically in Vapor server apps).
STOP and ask the user if:
Check for Fluent indicators:
Package.swift imports fluent, fluent-postgres-driver, fluent-sqlite-driver, etc.@ID, @Field, @Parent, @Children, @Siblings property wrappersMigrations/ directory exists with Fluent migration patternsFluentKit or FluentIf Fluent isn't present, inform the user: "This skill generates Fluent DataModels for server-side persistence. Your project doesn't appear to use Fluent. How would you like to proceed?"
In FOSMVVM, the Model is the center - the source of truth that reads and writes flow through.
See FOSMVVMArchitecture.md for full context.
┌─────────────────────────────────────┐
│ Fluent DataModel │
│ (implements Model + Fields) │
│ │
│ • All fields (system + user) │
│ • Relationships (@Parent, etc.) │
│ • Timestamps, audit fields │
│ • Persistence logic │
└──────────────┬──────────────────────┘
│
┌────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ViewModelFactory│ │ CreateRequest │ │ UpdateRequest │
│ (projector) │ │ RequestBody │ │ RequestBody │
│ │ │ │ │ │
│ → ViewModel │ │ → persists to │ │ → updates │
│ (projection) │ │ DataModel │ │ DataModel │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Fields protocol = Form input (user-editable subset)
DataModel = Complete entity (Fluent implementation)
Not all entities need Fields:
Each form-backed model requires files across multiple targets:
── fosmvvm-fields-generator ──────────────────────────────────
{ViewModelsTarget}/ (shared protocol layer)
FieldModels/
{Model}Fields.swift ← Protocol + Enum + Validation
{Model}FieldsMessages.swift ← Localization message struct
{ResourcesPath}/ (localization resources)
FieldModels/
{Model}FieldsMessages.yml ← YAML localization strings
── fosmvvm-fluent-datamodel-generator (this skill) ───────────
{WebServerTarget}/ (server implementation)
DataModels/
{Model}.swift ← Fluent model (implements protocol)
Migrations/
{Model}+Schema.swift ← Table creation migration
{Model}+Seed.swift ← Seed data migration
Tests/
{ViewModelsTarget}Tests/
FieldModels/
{Model}FieldsTests.swift ← Unit tests
database.swift ← Register migrations
Before generating, ask/confirm:
User, Idea, DocumentBefore generating files, explicitly confirm:
Is this a form? If no user input, skip Fields entirely.
Relationships?
@Parent in DataModel only, not in Fields@Siblings, NEVER UUID arraysSystem-assigned fields? (createdBy, timestamps, status history)
Clear naming? Relationship names must be self-documenting.
sourceNodes (what sources? what nodes?)originatingConversations (clear relationship meaning)Get explicit approval before generating.
Generate files in this order (dependencies flow down):
If form-backed model, run fosmvvm-fields-generator first:
{Model}Fields.swift - Protocol defines the contract (via fosmvvm-fields-generator){Model}FieldsMessages.swift - Validation message struct (via fosmvvm-fields-generator){Model}FieldsMessages.yml - Localization strings (via fosmvvm-fields-generator)Then generate DataModel layer (this skill):
4. {Model}.swift - Fluent model implementation
5. {Model}+Schema.swift - Database migration
6. {Model}+Seed.swift - Seed data
7. {Model}FieldsTests.swift - Tests
8. Update database.swift - Register migrations
After generation:
swiftformat . to add file headers and format codeswiftlint to verify lint rules are upheldswift build to verify compilationswift test to verify tests passSee reference.md for complete file templates with all patterns.
import FluentKit
import FOSFoundation
import FOSMVVM
import FOSMVVMVapor
import Foundation
final class {Model}: DataModel, {Model}Fields, Hashable, @unchecked Sendable {
static let schema = "{models}" // snake_case plural
@ID(key: .id) var id: ModelIdType?
// Fields from protocol
@Field(key: "field_name") var fieldName: FieldType
// Validation messages
let {model}ValidationMessages: {Model}FieldsMessages
// Timestamps
@Timestamp(key: "created_at", on: .create) var createdAt: Date?
@Timestamp(key: "updated_at", on: .update) var updatedAt: Date?
// CRITICAL: Initialize validationMessages FIRST
init() {
self.{model}ValidationMessages = .init()
}
init(id: ModelIdType? = nil, fieldName: FieldType) {
self.{model}ValidationMessages = .init() // FIRST!
self.id = id
self.fieldName = fieldName
}
}
PRINCIPLE: Existential types (any Protocol) are a code smell. Always ask "Is there any other way?" before using them.
For required relationships, use associated types in the protocol:
public protocol IdeaFields: ValidatableModel, Codable, Sendable {
associatedtype User: UserFields
var createdBy: User { get set }
}
In the Fluent model, @Parent directly satisfies the protocol:
final class Idea: DataModel, IdeaFields, Hashable, @unchecked Sendable {
@Parent(key: "created_by") var createdBy: User
// No computed property needed - @Parent satisfies the associated type directly
}
In schema: .field("created_by", .uuid, .required, .references(User.schema, "id", onDelete: .cascade))
When to use each pattern:
associatedtype User: UserFields): Required relationshipsModelIdType? for optional FKsModelIdType: Optional FKs, external system references"{Model.schema}-initial""{Model.schema}-seed"guard count() == 0For PostgreSQL-specific features (tsvector, LTREE, etc.), use SQLKit:
import Fluent
import SQLKit // Required for raw SQL
// In prepare():
guard let sql = database as? any SQLDatabase else { return }
let schema = Model.schema
try await sql.raw(SQLQueryString("ALTER TABLE \(unsafeRaw: schema) ADD COLUMN search_vector tsvector")).run()
Key points:
SQLKit (not just Fluent)database as? any SQLDatabaseSQLQueryString with \(unsafeRaw:) for identifiers@Suite annotation with descriptive nameLocalizableTestCase@Test(arguments:)Test structs with associated types:
private struct TestIdea: IdeaFields {
typealias User = TestUser // Satisfy the associated type
var id: ModelIdType?
var createdBy: TestUser // Concrete type, not existential
}
private struct TestUser: UserFields {
var id: ModelIdType? = .init()
var firstName: String = "Test"
// ... other required fields with defaults
}
| Concept | Convention | Example |
|---|---|---|
| Model class | PascalCase singular | User, Idea |
| Table name | snake_case plural | users, ideas |
| Field keys | snake_case | created_at, user_id |
| Enum cases | camelCase | searchLanguage, inProgress |
| Enum raw values | snake_case | "search_language", "in_progress" |
| Protocol | {Model}Fields | UserFields, IdeaFields |
| Messages struct | {Model}FieldsMessages | UserFieldsMessages |
| Swift Type | Fluent Type | Database |
|---|---|---|
String | .string | VARCHAR/TEXT |
Int | .int | INTEGER |
Bool | .bool | BOOLEAN |
Date | .datetime | TIMESTAMPTZ |
UUID | .uuid | UUID |
[UUID] | .array(of: .uuid) | UUID[] |
| Custom Enum | .string | VARCHAR (stored as raw value) |
JSONB | .json | JSONB |
IMPORTANT: Work WITH the user, not ahead of them:
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-12-23 | Initial skill based on SystemConfig pattern |
| 1.1 | 2025-12-23 | Added relationship patterns (@Parent), initialization order, imports list |
| 1.2 | 2025-12-23 | Associated types for relationships (not existentials), raw SQL patterns, test struct patterns |
| 1.3 | 2025-12-24 | Factored out Fields layer to fields-generator skill |
| 2.0 | 2025-12-26 | Renamed to fosmvvm-fluent-datamodel-generator, added Scope Guard, generalized from Kairos-specific to FOSMVVM patterns, added architecture context |
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.