Flowfull Elixir Client

View Source

Hex.pm Elixir Version License

A zero-dependency Elixir client library for Flowfull backends with built-in authentication, session management, and query building. Includes Phoenix integration for WebSockets and LiveView.

Professional, type-safe, and production-ready Elixir client for Flowfull API backends.

Features

  • Minimal Dependencies - Only Req and Jason
  • Type-Safe - Full typespec coverage
  • Session Management - Auto-detection with pluggable storage
  • Query Builder - Chainable API with 14+ filter operators
  • Authentication - Complete auth system (25+ endpoints)
  • Retry Logic - Configurable retries with exponential backoff
  • Interceptors - Request/response middleware
  • Phoenix Integration - WebSocket and LiveView support
  • Multi-Tenant - Built-in multi-tenant support
  • File Upload - Multipart form data support

Installation

Add flowfull to your list of dependencies in mix.exs:

def deps do
  [
    {:flowfull, "~> 0.1.0"}
  ]
end

Quick Start

# Create a client
client = Flowfull.new("https://api.example.com")

# Make a simple GET request
{:ok, response} = Flowfull.get(client, "/users")

# Use query builder
{:ok, users} = 
  Flowfull.query(client, "/users")
  |> Flowfull.Query.where("age", Flowfull.Operators.gte(18))
  |> Flowfull.Query.where("status", Flowfull.Operators.eq("active"))
  |> Flowfull.Query.sort("created_at", :desc)
  |> Flowfull.Query.page(1)
  |> Flowfull.Query.limit(10)
  |> Flowfull.Query.execute()

# Authentication
{:ok, result} = Flowfull.Auth.login(client, %{
  email: "user@example.com",
  password: "password123"
})

# Get current user
{:ok, user} = Flowfull.Auth.me(client)

Configuration

# With options
client = Flowfull.new("https://api.example.com",
  session_id: "abc123",
  timeout: 60_000,
  headers: %{"X-Custom" => "value"},
  retry_attempts: 5,
  retry_exponential: true,
  storage: Flowfull.Storage.File.new(".sessions")
)

# With session function
client = Flowfull.new("https://api.example.com",
  get_session_id: fn -> 
    {:ok, System.get_env("SESSION_ID")}
  end
)

# Include session in all requests
client = Flowfull.new("https://api.example.com",
  include_session: true,
  session_header: "X-Session-Id"
)

HTTP Methods

# GET
{:ok, response} = Flowfull.get(client, "/users")

# POST
{:ok, response} = Flowfull.post(client, "/users", %{
  name: "John Doe",
  email: "john@example.com"
})

# PUT
{:ok, response} = Flowfull.put(client, "/users/123", %{
  name: "Jane Doe"
})

# PATCH
{:ok, response} = Flowfull.patch(client, "/users/123", %{
  email: "jane@example.com"
})

# DELETE
{:ok, response} = Flowfull.delete(client, "/users/123")

Query Builder

# Complex query with multiple filters
{:ok, products} = 
  Flowfull.query(client, "/products")
  |> Flowfull.Query.where("price", Flowfull.Operators.between(10, 100))
  |> Flowfull.Query.where("category", Flowfull.Operators.in_list(["electronics", "books"]))
  |> Flowfull.Query.where("name", Flowfull.Operators.ilike("%laptop%"))
  |> Flowfull.Query.where("stock", Flowfull.Operators.gt(0))
  |> Flowfull.Query.sort("price", :asc)
  |> Flowfull.Query.sort("created_at", :desc)
  |> Flowfull.Query.page(1)
  |> Flowfull.Query.limit(20)
  |> Flowfull.Query.select(["id", "name", "price"])
  |> Flowfull.Query.execute()

Filter Operators

OperatorFunctionExampleSQL Equivalent
Equalityeq(value)where("age", eq(25))age = 25
Not Equalne(value)where("status", ne("inactive"))status != 'inactive'
Greater Thangt(value)where("price", gt(100))price > 100
Greater or Equalgte(value)where("age", gte(18))age >= 18
Less Thanlt(value)where("stock", lt(10))stock < 10
Less or Equallte(value)where("price", lte(50))price <= 50
LIKElike(pattern)where("name", like("%John%"))name LIKE '%John%'
ILIKEilike(pattern)where("email", ilike("%@gmail.com"))email ILIKE '%@gmail.com'
Containscontains(value)where("description", contains("laptop"))description LIKE '%laptop%'
Starts Withstarts_with(prefix)where("name", starts_with("A"))name LIKE 'A%'
Ends Withends_with(suffix)where("email", ends_with("@example.com"))email LIKE '%@example.com'
INin_list(values)where("status", in_list(["active", "pending"]))status IN ('active', 'pending')
NOT INnot_in(values)where("role", not_in(["admin", "super"]))role NOT IN ('admin', 'super')
IS NULLis_null()where("deleted_at", is_null())deleted_at IS NULL
NOT NULLnot_null()where("email", not_null())email IS NOT NULL
BETWEENbetween(min, max)where("age", between(18, 65))age BETWEEN 18 AND 65
NOT BETWEENnot_between(min, max)where("price", not_between(0, 10))price NOT BETWEEN 0 AND 10

Authentication

Login & Registration

# Login with email
{:ok, result} = Flowfull.Auth.login(client, %{
  email: "user@example.com",
  password: "password123"
})

# Login with username
{:ok, result} = Flowfull.Auth.login(client, %{
  user_name: "johndoe",
  password: "password123"
})

# Register new user
{:ok, result} = Flowfull.Auth.register(client, %{
  email: "user@example.com",
  password: "password123",
  name: "John",
  last_name: "Doe"
})

# Logout
{:ok, _} = Flowfull.Auth.logout(client)

# Get current user
{:ok, user} = Flowfull.Auth.me(client)

# Update profile
{:ok, user} = Flowfull.Auth.update_profile(client, %{
  name: "Jane",
  phone: "+1234567890"
})

Password Reset

# Request password reset
{:ok, _} = Flowfull.Auth.Password.request_reset(client, %{
  email: "user@example.com",
  reset_url: "https://app.example.com/reset-password"
})

# Validate reset token
{:ok, result} = Flowfull.Auth.Password.validate_token(client, token)

# Complete password reset
{:ok, _} = Flowfull.Auth.Password.complete_reset(client, %{
  token: token,
  password: "newpassword123"
})

# Change password (authenticated)
{:ok, _} = Flowfull.Auth.Password.change_password(client, %{
  old_password: "oldpassword",
  new_password: "newpassword"
})

Token Authentication

# Create login token
{:ok, result} = Flowfull.Auth.Token.create(client, %{
  email: "user@example.com",
  token_url: "https://app.example.com/login"
})

# Validate token
{:ok, result} = Flowfull.Auth.Token.validate(client, token)

# Login with token
{:ok, result} = Flowfull.Auth.Token.login(client, token)

Social OAuth

# Get available providers
{:ok, providers} = Flowfull.Auth.Social.get_providers(client)

# Start OAuth flow
{:ok, auth_url} = Flowfull.Auth.Social.start_oauth(
  client,
  :google,
  "https://app.example.com/callback"
)

# Login with OAuth (after callback)
{:ok, result} = Flowfull.Auth.Social.login_api(client, %{
  provider: "google",
  code: oauth_code
})

# Get linked accounts
{:ok, accounts} = Flowfull.Auth.Social.get_accounts(client)

# Unlink account
{:ok, _} = Flowfull.Auth.Social.unlink(client, :google)

Phoenix Integration

WebSocket Authentication

# In your UserSocket module
defmodule MyAppWeb.UserSocket do
  use Phoenix.Socket

  def connect(%{"session_id" => session_id}, socket, _connect_info) do
    client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))

    case Flowfull.Phoenix.validate_socket_session(client, session_id) do
      {:ok, user} ->
        {:ok, assign(socket, :user, user)}

      {:error, _reason} ->
        :error
    end
  end

  def connect(_params, _socket, _connect_info), do: :error
end

LiveView Authentication

# In your LiveView module
defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  def mount(_params, %{"session_id" => session_id}, socket) do
    client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))

    case Flowfull.Phoenix.validate_liveview_session(client, session_id) do
      {:ok, user} ->
        {:ok, assign(socket, :current_user, user)}

      {:error, _reason} ->
        {:ok, redirect(socket, to: "/login")}
    end
  end
end

Controller Authentication Plug

# In your router
pipeline :authenticated do
  plug Flowfull.Phoenix.SessionPlug,
    client: fn -> Flowfull.new(Application.get_env(:my_app, :flowfull_url)) end
end

scope "/dashboard", MyAppWeb do
  pipe_through [:browser, :authenticated]

  get "/", DashboardController, :index
end

Storage Adapters

Memory Storage (Agent-based)

# Start the memory storage
{:ok, _} = Flowfull.Storage.Memory.start_link()

# Use with client
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.Memory
)

File Storage

# Use file storage
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.File.new(".sessions")
)

ETS Storage (High Performance)

# Initialize ETS storage
Flowfull.Storage.ETS.init()

# Use with client
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.ETS
)

Interceptors

# Add request interceptor
client = Flowfull.add_request_interceptor(client, fn request ->
  IO.inspect(request, label: "Request")
  {:ok, request}
end)

# Add response interceptor
client = Flowfull.add_response_interceptor(client, fn response ->
  IO.inspect(response, label: "Response")
  {:ok, response}
end)

Examples

See the examples directory for complete examples:

Documentation

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

Flowfull Elixir Client is licensed under the AGPL-3.0-only license.

What does this mean?

  • Free to use - Use commercially without paying anything
  • Modify freely - Change the code as needed
  • Distribute - Share with others
  • ⚠️ Share modifications - If you modify and offer as a web service, you must release your changes
  • ⚠️ Same license - Derivative works must use AGPL-3.0

Commercial License Available

For organizations that cannot comply with AGPL-3.0 or need:

  • 💼 Keep modifications private
  • 🛡️ Legal indemnification
  • 🎯 Enterprise support and SLA
  • 🚀 Custom features

Contact: enterprise@pubflow.com Learn more: https://pubflow.com/dual-licensing

See LICENSE for full details.

Support


Copyright © 2024-present Pubflow, Inc. SPDX-License-Identifier: AGPL-3.0-only

Made with ❤️ by the Pubflow team