> For the complete documentation index, see [llms.txt](https://he-said.tdreyno.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://he-said.tdreyno.com/readme.md).

# Introduction

Type-safe authorization rule algebra for TypeScript.

he-said lets you model authorization as composable, typed rules instead of scattered conditionals.

Use it to express RBAC, ABAC, ACL, and graph-style access checks with one consistent API.

## Why he-said

* Type-safe terms and relations with strong TypeScript inference
* Composable logic operators for readable, testable policies
* In-memory evaluation for local development and unit tests
* Postgres planning and execution support for production data
* Proof-oriented evaluation APIs for debugging policy behavior

## Install

```bash
npm install @tdreyno/he-said
```

## Quick Start

```ts
import {
  and,
  createInMemoryAdapter,
  evaluator,
  relation,
  term,
} from "@tdreyno/he-said"

type User = { id: string; suspended: boolean }
type Document = { id: string }

const actor = term<User>()
const document = term<Document>()

const userOwnsDocument = relation<User, Document>()
const activeActor = actor.is(value => !value.suspended)

const canReadDocument = and(userOwnsDocument(activeActor, document))

const u1 = { id: "u1", suspended: false }
const u2 = { id: "u2", suspended: true }
const d1 = { id: "d1" }

const instance = evaluator(
  createInMemoryAdapter({
    relations: [
      {
        relation: userOwnsDocument,
        pairs: [
          [u1, d1],
          [u2, d1],
        ],
      },
    ],
    domain: [u1, u2, d1],
  }),
  { evaluatorContext: null },
)

const allowed = await instance.evaluate(canReadDocument, {
  [actor]: u1,
  [document]: d1,
})

const blocked = await instance.evaluate(canReadDocument, {
  [actor]: u2,
  [document]: d1,
})
```

## What You Can Model

* RBAC: role and scope checks
* ABAC: attribute and context predicates
* ACL: explicit grant relations
* ReBAC-style checks: graph and relationship constraints

## Core API

Core constructors:

* `term<T>()`
* `term<T>().is(predicate)`
* `relation<Left, Right>()`
* `eq(leftTerm, rightTermOrValue)`

Composition operators:

* `and(...constraints)`
* `or(...constraints)`
* `not(constraint)`
* `implies(premise, consequence)`
* `oneOf(term, values)`
* `atLeast(count, ...constraints)`
* `atMost(count, ...constraints)`
* `exactly(count, ...constraints)`
* `through(term).to(relation, term)`
* `forAll(term, constraint)`
* `select(...terms)(constraint)`
* `distinct(constraint)`
* `letRule(name, constraint)`
* `ref(name)`

Evaluator:

* `evaluator(adapter, { evaluatorContext })`
* `instance.evaluate(rule, environment)`
* `instance.evaluateWithProof(rule, environment)`

## Common Building Blocks

* Deny rule: `not(...)`
* Action families: `oneOf(action, ["read", "download"])`
* Cardinality constraints: `atLeast`, `atMost`, `exactly`
* Reusable subrules: `letRule(name, rule)` and `ref(name)`
* Quantified constraints: `forAll(term, constraint)`
* Fluent relation chains: `through(term).to(relation, term)`

## Adapters

### In-Memory Adapter

Use for local evaluation, unit tests, and rule debugging.

* `createInMemoryAdapter({ relations, domain? })`
* `validateStratifiedNegation(rule)`

### Postgres Adapter

Use when relation facts are persisted in SQL tables.

* `createPostgresAdapter({ relationMappings, queryExecutor, ... })`
* `planPostgresRule(rule, { relationMappings, environment, ... })`

`planPostgresRule` can produce diagnostics for join-table index hints, domain coverage for `forAll`, and other planner guidance.

## Documentation

* [Getting Started](/getting-started.md)
* [Core Concepts](/core-concepts.md)
* [API Documentation](/api/api.md)
* [In-Memory Evaluation](/in-memory-evaluation.md)
* [Type Safety and Terms](/type-safety-and-terms.md)
* [RBAC Example](/rbac-implementation.md)
* [ACL Example](/acl-implementation.md)
* [ABAC Example](/abac-implementation.md)
* [FAQ](/etc/faq.md)

## When To Use

Use he-said when you want policy logic to be explicit, typed, and reusable across services, route handlers, and tests.

If your authorization model is small today but expected to grow, starting with composable rules helps avoid hard-to-maintain permission conditionals later.

## Development

```bash
npm run lint
npm run typecheck
npm run build
npm run test:ci
npm run test:types
```

## License

MIT


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://he-said.tdreyno.com/readme.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
