Relate

Key Concepts

As a Relate author, you define the business objects your application works with, where their data comes from, who can access it, and what actions callers can take. You then connect that model to real systems and run it.

Consider a customer account: a CRM owns the Customer, a billing database owns its Invoices, and Relate owns Account Reviews created by your team. These records form one graph while retaining their separate owners.

This page describes the currently implemented authoring model. Relate is still at an early stage and is not ready for application use.

Sources and Connections

A source describes a kind of record in an external system. defineSource declares its schema and the field containing its record key. For example, a customer source might describe CRM records with id, name, and region.

A connection supplies the runtime binding for that source. connect pairs the definition with a connector that fetches records. Provider-verified connectors also identify the account; application-owned connectors explicitly rely on the host’s stable connectionId. The connector contains the API or database integration code and keeps credentials outside the graph definition.

// Excerpt: crmCustomers is a source definition; crmConnector reads the CRM.
const crmConnection = connect(crmCustomers, {
  providerAccountId: 'crm-prod',
  connectionId: 'crm-main',
  connector: crmConnector,
});

The binding above uses provider verification. SQLite defaults to application-owned identity: omit providerAccountId and give a different logical database a new connectionId. Relate cannot detect a substituted database under the same ID in that mode. Switching modes does not reuse retained identities.

The source describes what a record looks like; the connection determines how to read it in a particular environment. See Graph Modeling for source definitions and Getting Started for a working connector.

Objects, Membership, and Properties

An object definition describes a business entity such as Customer or AccountReview. An individual customer is a record of that object type. defineObject brings together its membership, properties, and display labels.

Membership says where records of that type come from:

MembershipOwnerExample
source(crmCustomers)An external systemCustomers read from the CRM
nativeMembership()RelateAccount reviews created by an action

Properties are the fields callers see. Each has a stable definition ID and a typed value:

HelperMeaningExample
objectId({ id })The record's Relate identity; exactly one per objectCustomer object ID
from(sourceField, { id })A field mapped from the membership sourceCustomer name
native(schema, { id })A value stored by RelateAccount review note
reference(Target, { id, from? })A typed pointer to another objectAn invoice's customer

A source-backed object currently has one membership source, and its mapped properties must come from that source. Native objects use native properties and references for values created by actions. See Graph Modeling for complete definitions.

Identity and Adoption

There are several names and IDs in a model, each with a different purpose:

Name or IDExamplePurpose
Definition ID'business.customer'Stable identity of an object type; properties also have definition IDs
Registry keyCustomer in objects: { Customer }Public API name, such as objects.Customer
Display label'Customer account'Human-facing wording
Source record key'cust_101'Identifies a record within its source
Object IDA Relate-generated ObjectId<'business.customer'>Identifies an individual record in the graph

Adoption gives an existing source record its Relate object ID and stores the mapping to the source key:

const customerId = await relate.host.adopt(Customer, 'cust_101');

Adoption is a host operation. It establishes graph membership without granting callers access to the record. Native records receive an object ID when an action creates them.

Use object IDs for graph reads and action inputs. Keep definition IDs stable as the model evolves; changing a registry key changes the caller-facing API. See Reading Data for adoption and typed IDs.

References and Relationships

A reference is a property on one object pointing to another. For example, Invoice.properties.customer points to a Customer. A source-backed reference maps a source key to an existing adopted target; a native reference stores the target's Relate object ID.

A relationship gives that reference named traversal paths in both directions:

const CustomerInvoices = defineRelationship({
  id: 'customer.invoices',
  forward: 'invoices',
  reverse: 'customer',
  via: Invoice.properties.customer,
});

After registration in the graph, callers can use objects.Customer.traverse.invoices(customerId) and objects.Invoice.traverse.customer(invoiceId). Traversal applies access rules to the records it returns. See Graph Modeling for registration and Reading Data for pagination and traversal coverage.

Access Definitions, Policies, and Principals

An access definition declares the roles, claims, and field groups available to your model. Policies use that vocabulary to decide who can read records, see particular fields, or create native records.

A principal is the authenticated caller supplied by your host application:

const caller = relate.as({
  id: 'ana',
  roles: ['sales'],
  claims: { region: 'emea' },
});

The host authenticates Ana and supplies her roles and claims. Relate evaluates the policies when she operates on the graph. For example, a customer policy can require the sales role and match the customer's region to Ana's claim.

Every object needs an explicit read policy. A hidden record returns not-found; a withheld field is omitted from data and marked forbidden in the result's evidence. See Access Control.

Actions and Receipts

An action is a named operation a caller can invoke, such as addAccountReview. defineAction declares its input, output, execution policy, which native objects it may create, and any expected business failures. implementAction supplies the server-side handler.

Actions currently create Relate-owned records in a transaction. They do not write to the CRM or billing system. A handler can read as the caller, create permitted native records, and use fail(...) to report a declared business failure while rolling back its writes.

A receipt records a successful result or a declared business failure. An invocation includes an idempotency key: retrying the same action with the same caller, input, and key returns its stored receipt after current-access checks. Unexpected errors and authorization rejections are thrown rather than recorded as business-failure receipts. See Actions.

Graphs, Apps, and the Runtime

A graph assembles your definitions into one model with defineGraph:

// Excerpt: definitions and policies are authored in their own modules.
const graph = defineGraph({
  id: 'business',
  objects: { Customer, Invoice, AccountReview },
  relationships: { CustomerInvoices },
  actions: { addAccountReview: AddAccountReview },
  access,
  policies,
});

Compilation validates the graph and produces a manifest, a serializable description of the model, plus a definition revision identifying that exact definition. The runtime compiles the graph for you. See Graph Compilation for revision pinning and its current migration limits.

An app packages the graph with deferred environment setup:

const app = defineApp({
  graph,
  setup() {
    return {
      graphId: 'business-prod',
      connections: [crmConnection, billingConnection],
      actionImplementations: [addAccountReview],
    };
  },
});

const relate = await startApp(app); // startApp comes from @relate/node.

defineApp starts nothing. startApp runs setup and creates the runtime, which executes reads, traversal, and actions. You can also call createRuntime directly with the graph and its bindings. The runtime graphId identifies an installation of the graph; the graph definition's id identifies the model. Close the running instance with await relate.close() when finished.

The Inspector can load the graph or app to display the model without calling app setup.

Observations, Evidence, and Storage

An observation is what Relate learned about a source record at a particular time. Reads return selected data alongside evidence describing field availability, origin, and freshness. A successful read can still have missing fields, so callers should inspect the evidence or use assertFields for values they require.

The runtime's store retains adopted identities, observations, native records, and action receipts. It defaults to memory; Postgres provides durable storage. This store is separate from a billing database connected as a source, even if both use PostgreSQL: one owns Relate's runtime state, the other owns invoices.

See Reading Data for evidence and freshness, and Persistence for store setup.

On this page