Install
openclaw skills install fosmvvm-fluent-datamodel-generatorGenerate Fluent DataModels with migrations and tests for FOSMVVM server-side persistence, including relationships and system-assigned fields, based on existi...
openclaw skills install fosmvvm-fluent-datamodel-generatorGenerate 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 | OpenClaw reference 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
Invocation: /fosmvvm-fluent-datamodel-generator
Prerequisites:
Workflow integration: This skill is used for server-side persistence with Fluent. For form-backed models, run fosmvvm-fields-generator first to create the Fields protocol. The skill references conversation context automatically—no file paths or Q&A needed.
This skill references conversation context to determine DataModel structure:
From conversation context, the skill identifies:
From requirements already in context:
Based on data source:
If form-backed model (Fields protocol exists):
If system-only model (no Fields):
Before generating, the skill validates:
Skill references information from:
See 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 |
| 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 |
| 2.1 | 2026-01-24 | Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths. |