Fulcro Rapid Application Development Tools

repository·main·Indexed 42 days ago

https://github.com/fulcrologic/fulcro-rad

A framework for rapid application development using Fulcro. It provides tools for defining data models via the defattr macro, managing attributes, schemas, and relationships, and building forms and reports. Version 1.0.0 includes support for various data types, identity attributes, and polymorphic references.

Tokens
66.2K
Snippets
160
Records
222
Agent score
52%

What's inside Fulcro RAD

  1. Overview of AI-optimized documentation structure

    main

    The AI documentation is organized into phases, prioritizing the Data Model Foundation before moving to Core Features. The structure is as follows:

    Phase 1: Data Model Foundation (Complete)

    • 01-attributes-data-model.md: Covers defattr macro, identity vs regular attributes, schema/clustering, and type systems.
    • 02-relationships-cardinality.md: Covers to-one/to-many relationships, owned vs referenced patterns, polymorphism, and subform/picker configuration.
    • 03-attribute-options.md: A complete reference of all attribute options (core, display, reference, Pathom 2/3 resolver, and validation).

    Phase 2: Core Features (In Progress)

    • 04-forms-basics.md: Covers defsc-form macro, form operations (create!, edit!, save!, cancel!), routing, and lifecycle.
    • Upcoming: 05-form-relationships.md, 08-reports-basics.md, 10-server-setup.md, and 11-database-adapters.md.
  2. What is Fulcro RAD?

    main

    Fulcro RAD (Rapid Application Development) is a library built on top of Fulcro and Pathom. It is designed to help developers quickly create full-stack single-page web, mobile, and desktop applications by providing a rich, potentially federated data model.

    At its core, RAD uses a central data model based on attributes. These attributes are fully-open maps that describe your data model, similar to Datomic attributes, and include a fully-qualified name (e.g., :account/email), a type, and cardinality. This approach allows for an open data model where attributes can represent identities, non-identity values, or even virtual things like graph edges or aggregates.

  3. Overview of RAD Server Setup

    main

    A RAD server provides an EQL API endpoint (typically at /api) using Pathom for query resolution. The setup consists of four core layers:

    1. Configuration: EDN files containing application settings, managed via mount.
    2. Form Middleware: Ring-like handlers for save and delete operations that interface with database adapters.
    3. Pathom Parser: The engine for EQL query resolution, utilizing RAD plugins and auto-generated resolvers.
    4. Ring Middleware: The HTTP server layer that exposes the API endpoint.

    RAD integrates with database adapters and can auto-generate resolvers directly from your attribute definitions.

  4. How form validation works in RAD

    main

    RAD forms use Fulcro's form-state validation system to validate user input. Validation occurs at three distinct levels:

    1. Type-level enforcement: Attribute types (e.g., :int, :decimal, :instant) and rendering styles prevent invalid input types (like text in a number field) at the UI level.
    2. Attribute-level validation: General constraints defined on specific attributes that apply wherever they are used (e.g., "age must be 0-130").
    3. Form-level validation: Contextual constraints that involve multiple fields (e.g., "start-date must be before end-date").

    RAD uses Fulcro form-state validators which return one of three states:

    • :valid: The field passes validation.
    • :invalid: The field fails validation.
    • :unknown: The field is not yet ready for validation (e.g., it hasn't been touched).
  5. Compare Computed vs. Derived fields

    main

    Fulcro RAD provides two ways to handle dynamic data, depending on whether the value needs to be persisted.

    FeatureComputed Fields (ao/computed-value)Derived Fields (:derive-fields)
    StorageNever in stateStored in Fulcro state
    LifecycleRecomputed on renderUpdated on any change
    PersistenceRead-only display; cannot be savedCan be saved to the database
    RolePart of the UI logicPart of the data model
  6. Configure database selection in Pathom environment

    main

    The Datomic plugin requires the Pathom environment (env) to be augmented so that generated resolvers and mutations know which database to use for a specific schema.

    You must provide a map keyed by schema containing:

    • ::datomic/connections: A map of database connections for mutations.
    • ::datomic/databases: A map of the most recent database values for consistent reads across multiple resolvers.
  7. Core Concepts: Attributes, Forms, and Reports

    main

    Fulcro RAD is organized around several key abstractions:

    Attributes

    Attributes are the foundation of the RAD data model. They are fully-qualified names (e.g., :account/id) that define the structure of your data. They can be extended with arbitrary user or plugin-defined information to provide features or define how data is calculated. This allows RAD to support federated data systems where data lives in multiple locations but is seamlessly combined in the UI.

    Forms

    A Form is defined as any screen where a tree of data is loaded and saved "together," typically involving validation and free-form inputs.

    Reports

    A Report is any screen containing a mix of read-only, derived, and aggregate data. Reports can be organized in various ways (columns, rows, kanban boards, charts) and support interactions like linking (navigation), filtering, pagination, and abstract actions (e.g., deleting an item or moving a card).

  8. Understand the execution order of form triggers

    main

    When a user interacts with a form, triggers fire in a specific sequence. Understanding this order is critical for managing state updates and re-renders:

    1. User changes field $\rightarrow$ form/input-changed! is called.
    2. :on-change trigger fires on the specific form where the field resides.
    3. :derive-fields triggers fire:
      • First on the child form (if an attribute changed there).
      • Then on the master form (always).
    4. UI re-renders with the updated values.

    Important: :on-change triggers do NOT cascade. If you modify a field inside an :on-change handler, it will not trigger another :on-change. To achieve cascading updates, use :derive-fields.

  9. Create Virtual Relationships

    main

    Virtual relationships are edges that are not physically stored in the database but are computed dynamically via custom resolvers. They behave like standard relationships in EQL queries and can be used in forms and reports.

    To create a virtual relationship, define an attribute with the :ref option but omit the ao/schema (or equivalent database schema mapping). You must provide ao/pc-input, ao/pc-output, and ao/pc-resolve to handle the computation.

    ;; Virtual to-one: Customer's most-shipped-to address
    (defattr likely-address :customer/most-likely-address :ref
      {ao/target :address/id
       ao/pc-input #{:customer/id}
       ao/pc-output [{:customer/most-likely-address [:address/id]}]
       ao/pc-resolve (fn [env {:customer/keys [id]}]
                       #?(:clj
                          (let [addr-id (calc-most-likely-address env id)]
                            {:customer/most-likely-address [:address/id addr-id]})))})
  10. Understand RAD Form System Architecture

    main

    RAD forms are built on a layered architecture designed for extensibility:

    1. Server: Uses Pathom resolvers for reads and a single form/save-form mutation for writes. Save logic is handled by middleware processing a minimal diff.
    2. UI State Machine (UISM): A top-level machine that controls the state of the entire form set, including nested subforms.
    3. UI Components: defsc-form is a Fulcro component that uses component options to drive plugin behavior. Forms can be nested, where each nested form knows its immediate parent and the master form.
  11. How derived, stored fields work

    main

    Derived fields are attributes that are stored in Fulcro state and can be saved to the server. They are used to manage interdependencies of calculated data (e.g., a total sum based on line items).

    Use the :derive-fields trigger in a form definition. This trigger is a referentially-transparent function that receives the entire denormalized form tree and must return an updated version of that tree.

    Execution Order:

    1. An attribute change triggers :derive-fields on the form where the attribute lives.
    2. The master form's :derive-fields is guaranteed to run after the nested child form's trigger.
    3. Row additions/deletions always trigger the master form's :derive-fields.
    (defn sum-subtotals* [{:invoice/keys [line-items] :as invoice}]
      (assoc invoice :invoice/total
             (reduce (fn [t {:line-item/keys [subtotal]}] (math/+ t subtotal)) (math/zero) line-items)))
    
    (form/defsc-form InvoiceForm [this props]
      {fo/id            invoice/id
       fo/attributes    [invoice/customer invoice/date invoice/line-items invoice/total]
       fo/subforms      {:invoice/line-items {fo/ui LineItemForm}}
       fo/triggers      {:derive-fields (fn [new-form-tree] (sum-subtotals* new-form-tree))}})
  12. Handle Minimum Form Deltas and Concurrency

    main

    To optimize concurrency and minimize data transfer, RAD transmits only a minimum diff of actually-changed attributes.

    Example delta format:

    {[:person/id 1]
      {:person/name {:before "Alice"
                     :after  "Bob"}}}

    Important Concurrency Note

    :before values are hints, not guarantees. Because clients may have cached different versions of the truth, the server must verify that the value being retracted (the :before value) actually exists in the database at the time of the mutation to ensure state validity.