Vol. VI · Functory Quarterly№ 001 · Edition 2026
I
For engineers

You declare. The platform delivers.

A library of primitives for backend applications on AWS.

You describe each entity as configuration, compose entities into an application, and deploy it into an AWS account you own. The configuration assistant is a convenience — the DSL stands on its own.

II
The diff

Before and after.

What a primitive costs by hand. What it costs as config.

On the left: what a single primitive typically costs in a hand-rolled backend. On the right: the equivalent EntityConfig that produces the same surface — API, persistence, lifecycle, events — at runtime.

Hand-rolled
EntityConfig
handlers/reservation.ts
export async function createReservation(
  req: CreateReservationRequest,
  ctx: RequestContext,
): Promise<Reservation> {
  const id = ulid();
  const now = new Date().toISOString();
  await assertResourceAvailable(req.resourceId, req.start, req.end);
  const record = {
    id, tenantId: ctx.tenantId, status: "PENDING",
    resourceId: req.resourceId, start: req.start, end: req.end,
    createdAt: now, updatedAt: now, version: 0,
  };
  await ddb.put({
    TableName: RESERVATIONS_TABLE,
    Item: record,
    ConditionExpression: "attribute_not_exists(id)",
  });
  await publishEvent("reservation.created", record);
  await scheduleExpiryTask(id, req.expiresAt);
  return toReservationDto(record);
}
ReservationConfig
val reservationConfig = EntityConfig(
  name          = "Reservation",
  accessMode    = EntityAccessMode.OWNER,
  fragments     = Seq("resourceId", "status"),
  externalKeys  = Seq("confirmationCode"),
  sortKeys      = Seq("start"),
).withLifecycle(
  LifecycleConfig(
    initial     = "Pending",
    transitions = Seq(
      Transition("Confirm", from = "Pending", to = "Confirmed"),
      Transition("Cancel",  from = Set("Pending", "Confirmed"), to = "Cancelled"),
    ),
    expiry      = Some(ExpiryRule(after = 30.minutes, transitionTo = "Cancelled")),
  ),
)

+ ~1,100 lines off-screen in service / repo / DTO / migration / Lambda wiring.

Derives: DynamoDB writes with optimistic locking · Pending→Confirmed→Cancelled state machine · REST + GraphQL endpoints · Search index projection · Expiry task scheduling
III
The flow

How it works.

Three stages, one runtime.

Each is a thin layer over AWS primitives — no hidden runtime that owns your data plane.

  1. 01

    Declare

    Each entity type is a single EntityConfig. Access mode, fragments, keys, lifecycle and validation are all data — no class hierarchy to subclass, no service to wire.

    fig. iii·a SupportTicketConfig
    val supportTicket = EntityConfig(
      name         = "SupportTicket",
      accessMode   = EntityAccessMode.TENANT,
      fragments    = Seq("priority", "status"),
      externalKeys = Seq("ticketNumber"),
    ).withLifecycle(
      LifecycleConfig(
        initial     = "Open",
        transitions = Seq(
          Transition("Assign",  from = "Open",     to = "InProgress"),
          Transition("Resolve", from = "InProgress", to = "Resolved"),
          Transition("Reopen",  from = "Resolved", to = "Open"),
        ),
      ),
    )
  2. 02

    Compose

    Entities compose via relations, shared events, and shared index projections. An application is a collection of EntityConfigs plus the wiring between them.

    fig. iii·b HelpDeskApp
    object HelpDeskApp extends App(
      tenants      = tenantConfig,
      entities     = Seq(supportTicket, customer, agent),
      relations    = Seq(
        Relation.oneToMany("SupportTicket", via = "customerId"),
        Relation.oneToMany("SupportTicket", via = "assignedAgentId"),
      ),
      notifications = Seq(
        OnTransition("SupportTicket", "Assign",  template = "ticket.assigned"),
        OnTransition("SupportTicket", "Resolve", template = "ticket.resolved"),
      ),
    )
  3. 03

    Deploy

    The CDK app provisions DynamoDB, the CDC pipelines, MSK, EventBridge, EKS services, OpenSearch — into an AWS account you own. No shared infrastructure.

    fig. iii·c deploy
    # deploy to your own AWS account
    $ aws configure --profile acme-prod
    $ functory deploy --app HelpDeskApp --profile acme-prod
    
     provisioned DynamoDB (entities, commands, configs)
     provisioned OpenSearch domain
     provisioned MSK cluster
     wired CDC handlers
     rolled out EKS services
    deployed in 4m 12s
IV
Included primitives

Gamification out of the box.

The scoring engine ships with the platform. Define score types with aggregation and decay, write event-driven rules that grant or deduct points, and issue achievement badges — all through configuration, without touching an event handler.

console.functory.io/scoring/badges

Scoring Engine

Badges

Achievement badges with triggers, categories, and rarity

BadgeCategoryRarityPointsTriggersStatus
🏆First PurchaseMILESTONECOMMON501ACTIVE
🔥7-Day StreakSTREAKUNCOMMON1001ACTIVE
Top ReviewerACHIEVEMENTRARE2502ACTIVE
💎Power UserACHIEVEMENTEPIC5003ACTIVE
👑Platform LegendMILESTONELEGENDARY10004ACTIVE
V
Operational console

The console ships with it.

Every EntityConfig generates an API. The console gives your team the operational UI on top — auth configuration, entity management, lifecycle design, validation rules, commerce, engagement, and more. Nothing to build.

console.functory.io/system

Console

System

Entity definitions, validation rules, and auth configuration.

Auth

Identity Providers

OIDC providers and OAuth social login integrations.

JWT & Token Settings

Token lifetimes, key rotation, and JWKS.

Rate Limiting

Rate limits for auth and API requests.

WebAuthn

Passkey settings, relying party, and allowed origins.

Entity Management

Entity Configs

Entity type configurations, access control, and versioning.

Entity Configurator

Visual editor for entity configurations with lifecycle support.

Lifecycle Designer

State machine configurations for entity lifecycles.

Validation & Rules

Validators

MVEL validation rules applied to entity operations.

Transformers

Data transformation rules applied to entity operations.

Expression Tester

Test MVEL, CEL, and GraalVM expressions against sample data.

Events & Compliance

Event Hooks

Webhook subscriptions triggered by entity lifecycle events.

GDPR / Privacy

Data subject access, erasure, and portability requests.

VI
Configuration console

Configure without code.

The entity configurator walks you through four steps: name the kind and let the platform infer an archetype, set access control and index keys, define the lifecycle state machine, then review and save. The result is the same EntityConfig you would have written by hand — generated from a form.

console.functory.io/configurator/entities/new

Configurator

Entities
Flows

New Entity

Create Entity Config

Step 1 of 4 — Kind

Entity Kind Name

Enter the name for your entity kind. We will try to infer sensible defaults.

Kind

SupportTicket
Matched archetype: TICKET

Tracks support requests with assignee, priority, and status. Typically tenant-scoped with a lifecycle.

Hints

  • Access mode: OWNER (submitter owns the ticket)
  • Fragments: status, priority, assignedAgentId
  • External keys: ticketNumber
  • Suggested lifecycle: OPEN → IN_PROGRESS → RESOLVED → CLOSED

Description (optional)

Tracks customer support requests from submission to resolution