Перейти к содержанию

Спецификация FAPL 1.0

Эта страница задаёт каркас FAPL 1.0 (Frontend Architecture & Process Language): какие поля есть у модуля, как устроены три слоя и какой документ считается согласованным. Зачем язык собран именно так и как его читать агенту, написано в обзоре.

Статус

Версия 1.0 — каркас. Он фиксирует форму документа, три слоя и пример модуля аутентификации. Парсер, рантайм и привязка к React, Vue или Angular в эту версию не входят.

Файл

Правило Значение
Кодировка UTF-8
Расширение .fapl.yaml
Синтаксис YAML
Версия YAML 1.2. Ключ карты событий пишется как "on", чтобы парсер YAML 1.1 не превратил его в булево значение
Комментарии Строки, начинающиеся с #, смысла для языка не несут

Корень файла — одно отображение. Порядок ключей в тексте: version, module, description, components, workflows, services. Порядок для машины не значим; для человека он совпадает с порядком чтения технического задания.

Идентификатор — непустая строка из латиницы, цифр и _, первый символ не цифра: ^[A-Za-z_][A-Za-z0-9_]*$.

Документ модуля

Поле Обязательность Форма
version обязан присутствовать Строка "1.0"
module обязан присутствовать Непустая строка, имя модуля
description может отсутствовать Строка для человека
components обязан присутствовать Список компонентов, возможно пустой
workflows обязан присутствовать Отображение «имя процесса → процесс», хотя бы один процесс
services обязан присутствовать Отображение «имя сервиса → сервис», список может быть пустым

Имена процессов — ключи workflows. Имена сервисов — ключи services. Оба набора подчиняются правилу идентификатора.

Компоненты

Элемент списка components описывает страницу или компонент интерфейса.

Поле Обязательность Форма
name обязан присутствовать Идентификатор, уникальный среди компонентов модуля
type обязан присутствовать Page или Component
route обязан у Page, у Component отсутствует Строка маршрута, начинается с /
children может отсутствовать Непустой список имён компонентов того же модуля
inputs может отсутствовать Непустой список входов
outputs может отсутствовать Непустой список выходов

Вход:

Поле Обязательность Форма
name обязан присутствовать Идентификатор, уникальный среди входов этого компонента
type обязан присутствовать Выражение типа

Выход:

Поле Обязательность Форма
name обязан присутствовать Идентификатор, уникальный среди выходов этого компонента
action обязан присутствовать Действие

Page — экран с маршрутом. Component — вложенная часть экрана. Имя из children указывает на другой элемент того же списка components.

В примере LoginView — страница /login, её ребёнок — LoginForm. Выход формы onSubmit связан с процессом строкой AuthWorkflow.startLogin.

Процессы

Процесс — конечный автомат.

Поле Обязательность Форма
description может отсутствовать Строка
initial_state обязан присутствовать Имя состояния из states этого процесса
states обязан присутствовать Отображение «имя состояния → состояние», хотя бы одно

Состояние:

Поле Обязательность Форма
type обязан у конечного состояния Единственное допустимое значение — Final
entry может быть у неконечного состояния Одна ссылка. Выполняется при входе в состояние
on обязан у неконечного состояния Отображение «имя события → переход»

Конечное состояние содержит только type: Final. Ключей on и entry у него нет.

У состояния есть либо type: Final, либо непустая карта on.

Переход:

Поле Обязательность Форма
target обязан присутствовать Имя состояния того же процесса
payload может отсутствовать Выражение типа полезной нагрузки события
action может отсутствовать Действие, выполняется вместе с переходом

Имя события уникально в карте on своего состояния. Одинаковое имя события в разных состояниях — разные переходы.

Порядок для одного события такой: при входе в исходное состояние выполняется его entry, если он есть; когда приходит событие, выполняется action перехода и процесс оказывается в target.

В примере AuthWorkflow стартует в Idle. Событие SUBMIT_FORM несёт email и pass и ведёт в Validating. На входе в Validating вызывается локальное действие executeValidation. На входе в RequestingApi вызывается AuthService.login.

stateDiagram-v2
    [*] --> Idle
    Idle --> Validating: SUBMIT_FORM
    Validating --> RequestingApi: VALIDATION_SUCCESS
    Validating --> Idle: VALIDATION_FAIL
    RequestingApi --> Authenticated: API_SUCCESS
    RequestingApi --> Idle: API_ERROR
    Authenticated --> [*]

Сервисы

Сервис — набор методов, к которым обращается фронтенд.

Поле Обязательность Форма
methods обязан присутствовать Непустой список методов

Метод:

Поле Обязательность Форма
name обязан присутствовать Идентификатор, уникальный среди методов этого сервиса
async может отсутствовать Булево. Отсутствие поля означает синхронный метод. true — вызов асинхронный
params обязан присутствовать Выражение типа аргументов
returns обязан присутствовать Выражение типа успешного результата
error может отсутствовать Выражение типа ошибки

AuthService.login в примере асинхронный. Аргументы — email и pass. Успех — token и вложенный user. Ошибка — code и message.

Типы

Выражение типа — либо скаляр, либо отображение «поле → выражение типа».

Скаляры версии 1.0:

Имя Смысл
String Строка
ID Идентификатор сущности

Других скаляров в 1.0 нет. Вложенность объектов не ограничена. Имена полей — идентификаторы.

returns: { token: String, user: { id: ID, name: String } }

Здесь token и name — строки, user — объект, id — идентификатор.

Ссылки и действия

Ссылка — идентификатор либо два идентификатора через точку.

Запись Куда указывает
executeValidation Локальное действие модуля. Отдельного реестра таких имён в 1.0 нет: имя само является объявлением
AuthService.login Метод login сервиса AuthService. Оба имени обязаны существовать
AuthWorkflow.startLogin Символ процесса AuthWorkflow. Процесс обязан существовать. Символ после точки может не совпадать с именем события

Действие — одна ссылка или непустой список ссылок. Список выполняется в порядке записи. В примере переход API_SUCCESS выполняет saveToken, затем redirectToDashboard.

Привязка выхода и имя события

В каноническом примере выход onSubmit ссылается на AuthWorkflow.startLogin, а переход из Idle слушает событие SUBMIT_FORM. Каркас считает это допустимым: символ после точки — имя привязки, а не обязанное совпасть событие. В задании генератору нужно прямо сказать, какое событие шлёт выход. Для этого примера onSubmit шлёт SUBMIT_FORM.

Согласованность

Помимо JSON Schema, документ обязан выполнять правила ниже. Схема их не выражает: они связывают разные части файла.

  1. Имена компонентов модуля попарно различны.
  2. Каждое имя в children есть среди name компонентов.
  3. initial_state есть среди ключей states того же процесса.
  4. target каждого перехода есть среди ключей states того же процесса.
  5. Если ссылка начинается с имени сервиса и точки, после точки стоит имя метода этого сервиса.
  6. Если ссылка начинается с имени процесса и точки, такой процесс в модуле есть.
  7. Имена методов одного сервиса попарно различны.
  8. Имена входов одного компонента попарно различны. То же для выходов.
flowchart LR
    LoginView["LoginView /login"] --> LoginForm
    LoginForm -->|onSubmit| AuthWorkflow
    AuthWorkflow -->|AuthService.login| AuthService

Пример

Канонический файл: auth-module.fapl.yaml.

Описание модуля упоминает регистрацию и вход. Автомат примера раскладывает вход: состояния Idle, Validating, RequestingApi, Authenticated.

auth-module.fapl.yaml
version: "1.0"
module: Authentication
description: "Модуль регистрации и входа пользователей"

# --- 1. АРХИТЕКТУРА И ИНТЕРФЕЙС (Глазами человека и разметки ИИ) ---
components:
  - name: LoginView
    type: Page
    route: "/login"
    children: [LoginForm]

  - name: LoginForm
    type: Component
    inputs:
      - name: defaultEmail
        type: String
    outputs:
      - name: onSubmit
        action: AuthWorkflow.startLogin

# --- 2. БИЗНЕС-ПРОЦЕССЫ И СЦЕНАРИИ (Четкие инструкции для ИИ) ---
workflows:
  AuthWorkflow:
    description: "Процесс аутентификации пользователя"
    initial_state: Idle

    states:
      Idle:
        "on":
          SUBMIT_FORM:
            target: Validating
            payload: { email: String, pass: String }

      Validating:
        entry: executeValidation
        "on":
          VALIDATION_SUCCESS:
            target: RequestingApi
          VALIDATION_FAIL:
            target: Idle
            action: showValidationError

      RequestingApi:
        entry: AuthService.login
        "on":
          API_SUCCESS:
            target: Authenticated
            action: [saveToken, redirectToDashboard]
          API_ERROR:
            target: Idle
            action: showApiError

      Authenticated:
        type: Final

# --- 3. ИНТЕГРАЦИИ И ДАННЫЕ (Контракты для генерации кода) ---
services:
  AuthService:
    methods:
      - name: login
        async: true
        params: { email: String, pass: String }
        returns: { token: String, user: { id: ID, name: String } }
        error: { code: String, message: String }

JSON Schema

Синтаксис документа проверяет схема fapl-1.0.schema.json (JSON Schema, draft 2020-12). Её идентификатор — https://bnd.by/fapl/examples/fapl-1.0.schema.json.

Схема проверяет набор полей, перечисления Page, Component, Final, String, ID, форму ссылок и запрет route у Component. Правила из раздела «Согласованность» применяются отдельным шагом после схемы.

Текст схемы
fapl-1.0.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://bnd.by/fapl/examples/fapl-1.0.schema.json",
  "title": "FAPL 1.0",
  "description": "Синтаксическая схема модуля Frontend Architecture & Process Language версии 1.0. Семантические правила согласованности имён описаны в спецификации.",
  "type": "object",
  "additionalProperties": false,
  "required": ["version", "module", "components", "workflows", "services"],
  "properties": {
    "version": {
      "const": "1.0",
      "description": "Версия языка."
    },
    "module": {
      "type": "string",
      "minLength": 1,
      "description": "Имя модуля."
    },
    "description": {
      "type": "string",
      "description": "Назначение модуля человеческим языком."
    },
    "components": {
      "type": "array",
      "items": { "$ref": "#/$defs/component" }
    },
    "workflows": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": { "$ref": "#/$defs/ident" },
      "additionalProperties": { "$ref": "#/$defs/workflow" }
    },
    "services": {
      "type": "object",
      "propertyNames": { "$ref": "#/$defs/ident" },
      "additionalProperties": { "$ref": "#/$defs/service" }
    }
  },
  "$defs": {
    "ident": {
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
    },
    "scalarType": {
      "type": "string",
      "enum": ["String", "ID"]
    },
    "typeExpr": {
      "description": "Скалярный тип либо объект «поле → тип».",
      "oneOf": [
        { "$ref": "#/$defs/scalarType" },
        {
          "type": "object",
          "minProperties": 1,
          "propertyNames": { "$ref": "#/$defs/ident" },
          "additionalProperties": { "$ref": "#/$defs/typeExpr" }
        }
      ]
    },
    "refString": {
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)?$",
      "description": "Локальное действие (имя) или ссылка Service.method / Workflow.symbol."
    },
    "action": {
      "oneOf": [
        { "$ref": "#/$defs/refString" },
        {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/refString" }
        }
      ]
    },
    "component": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "type"],
      "properties": {
        "name": { "$ref": "#/$defs/ident" },
        "type": { "enum": ["Page", "Component"] },
        "route": {
          "type": "string",
          "pattern": "^/\\S*$"
        },
        "children": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/ident" }
        },
        "inputs": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/input" }
        },
        "outputs": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/output" }
        }
      },
      "allOf": [
        {
          "if": {
            "properties": { "type": { "const": "Page" } },
            "required": ["type"]
          },
          "then": { "required": ["route"] }
        },
        {
          "if": {
            "properties": { "type": { "const": "Component" } },
            "required": ["type"]
          },
          "then": {
            "properties": { "route": false }
          }
        }
      ]
    },
    "input": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "type"],
      "properties": {
        "name": { "$ref": "#/$defs/ident" },
        "type": { "$ref": "#/$defs/typeExpr" }
      }
    },
    "output": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "action"],
      "properties": {
        "name": { "$ref": "#/$defs/ident" },
        "action": { "$ref": "#/$defs/action" }
      }
    },
    "workflow": {
      "type": "object",
      "additionalProperties": false,
      "required": ["initial_state", "states"],
      "properties": {
        "description": { "type": "string" },
        "initial_state": { "$ref": "#/$defs/ident" },
        "states": {
          "type": "object",
          "minProperties": 1,
          "propertyNames": { "$ref": "#/$defs/ident" },
          "additionalProperties": { "$ref": "#/$defs/state" }
        }
      }
    },
    "state": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "type": { "const": "Final" },
        "entry": { "$ref": "#/$defs/refString" },
        "on": {
          "type": "object",
          "minProperties": 1,
          "propertyNames": { "$ref": "#/$defs/ident" },
          "additionalProperties": { "$ref": "#/$defs/transition" }
        }
      },
      "anyOf": [
        { "required": ["type"] },
        { "required": ["on"] }
      ],
      "allOf": [
        {
          "if": {
            "properties": { "type": { "const": "Final" } },
            "required": ["type"]
          },
          "then": {
            "allOf": [
              { "not": { "required": ["on"] } },
              { "not": { "required": ["entry"] } }
            ]
          }
        }
      ]
    },
    "transition": {
      "type": "object",
      "additionalProperties": false,
      "required": ["target"],
      "properties": {
        "target": { "$ref": "#/$defs/ident" },
        "payload": { "$ref": "#/$defs/typeExpr" },
        "action": { "$ref": "#/$defs/action" }
      }
    },
    "service": {
      "type": "object",
      "additionalProperties": false,
      "required": ["methods"],
      "properties": {
        "methods": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/method" }
        }
      }
    },
    "method": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "params", "returns"],
      "properties": {
        "name": { "$ref": "#/$defs/ident" },
        "async": { "type": "boolean" },
        "params": { "$ref": "#/$defs/typeExpr" },
        "returns": { "$ref": "#/$defs/typeExpr" },
        "error": { "$ref": "#/$defs/typeExpr" }
      }
    }
  }
}

За пределами 1.0

Следующие вещи каркас сознательно оставляет снаружи, чтобы документ модуля оставался описанием поведения:

  • исполняемый парсер и рантайм автомата;
  • поля хуков React, стора Vue и любого другого фреймворка;
  • TypeScript DSL с теми же сущностями;
  • системная инструкция агента — её черновик лежит в обзоре, к грамматике языка она не относится;
  • скалярные типы кроме String и ID;
  • сценарий регистрации: строка description примера его называет, отдельного автомата у него нет.