AbsoluteJS

Fine-Grained Authorization

Relationship-based access control — the WorkOS FGA / Google Zanzibar model. Permissions are relationship tuples ("warrants"), and a schema lets relations inherit, so you don't write a warrant for every permission. Self-hosted, no per-check pricing.

#Schema & inheritance

The schema declares each resource type's relations and how they derive:

selfDirect warrants.
computedUsersetAn editor is also a viewer.
tupleToUsersetA doc inherits its parent folder's viewers.
unionAny of the combined derivations grants the relation.

Usersets like group#member expand recursively.

TS
import { createFgaEngine, createNeonWarrantStore } from '@absolutejs/auth';
import type { FgaSchema } from '@absolutejs/auth';

// Relationship-based access control (the WorkOS FGA / Google Zanzibar model).
// Relations inherit: an editor is also a viewer; a doc inherits its folder's
// viewers (tupleToUserset); usersets (group#member) expand recursively.
const schema: FgaSchema = {
  document: {
    owner: { kind: 'self' },
    editor: {
      kind: 'union',
      rules: [{ kind: 'self' }, { kind: 'computedUserset', relation: 'owner' }]
    },
    viewer: {
      kind: 'union',
      rules: [
        { kind: 'self' },
        { kind: 'computedUserset', relation: 'editor' },
        { kind: 'tupleToUserset', viaRelation: 'parent', relation: 'viewer' }
      ]
    }
  },
  folder: { viewer: { kind: 'self' } }
};

export const fga = createFgaEngine({
  schema,
  warrantStore: createNeonWarrantStore(process.env.DATABASE_URL)
});

#Warrants, check & query

Write warrants, then check (does X have a relation on Y?) or listSubjects (who has it?). The engine follows the schema's inheritance, with cycle protection and a depth limit.

v1 is a correct ReBAC engine — not yet Zanzibar-scale throughput.
TS
// Write relationships (warrants), then check or query — the schema's inheritance
// does the rest, so you don't write a warrant per permission.
await fga.writeWarrant({
  resourceType: 'document',
  resourceId: 'doc1',
  relation: 'owner',
  subjectType: 'user',
  subjectId: 'alice'
});
// doc1 lives in folder f1 (so it inherits f1's viewers):
await fga.writeWarrant({
  resourceType: 'document',
  resourceId: 'doc1',
  relation: 'parent',
  subjectType: 'folder',
  subjectId: 'f1'
});

// Check API — does alice have <relation> on doc1?
await fga.check({
  resourceType: 'document',
  resourceId: 'doc1',
  relation: 'viewer',
  subjectType: 'user',
  subjectId: 'alice'
}); // true: owner -> editor -> viewer

// Query API — who can view doc1? (expands every path, incl. group#member)
const viewers = await fga.listSubjects({
  resourceType: 'document',
  resourceId: 'doc1',
  relation: 'viewer'
});

#Reverse queries & DSL

listObjects answers the inverse of check — "which resources of this type can the subject access?" — by enumerating candidates and checking each (add a reverse index for very high object counts). parseSchema lets you author the schema in the OpenFGA-style DSL instead of the object form.

TS
// Reverse query — which documents can alice view? (the inverse of check)
const docs = await fga.listObjects({
  resourceType: 'document',
  relation: 'viewer',
  subjectType: 'user',
  subjectId: 'alice'
}); // -> ['doc1', ...]

// Author the schema in the OpenFGA-style DSL and parse it, instead of the
// object form. Grammar (one statement per line):
//   type document
//     relations
//       define owner: [user]
//       define editor: [user] or owner
//       define viewer: [user] or editor or viewer from parent
import { parseSchema } from '@absolutejs/auth';
const schema = parseSchema(dsl);

#Caching

Pass a cache to memoize check (and the checks listObjects runs).

A write clears the cache on that instance; other instances see staleness up to ttlMs — supply a shared FgaCache for a multi-instance deployment.
TS
import { createFgaEngine, createInMemoryCheckCache } from '@absolutejs/auth';

// Memoize check() with a TTL cache. Writes (writeWarrant/deleteWarrant) clear it
// on this instance; other instances see staleness up to ttlMs — supply a shared
// (e.g. Redis-backed) FgaCache for those.
const fga = createFgaEngine({
  schema,
  warrantStore,
  cache: createInMemoryCheckCache({ ttlMs: 5000, maxEntries: 10000 })
});