alepha@docs:~/docs/framework/reference/providers$
cat ResourceGateMemoProvider.md | pretty
2 min read
Last commit:

#ResourceGateMemoProvider

#Import

typescript
1import { ResourceGateMemoProvider } from "alepha/security";

#Overview

Request-scoped memo for the reads $owns performs to make its decision.

A page that loads seven things at once sends one POST /api/_batch, and every entry gates independently. Without this, one (user, project) pair is resolved seven times over: seven authority reads and seven membership reads, inside a single HTTP request.

#What it caches, and what it deliberately does not

The gate's own reads: the authority row it decides against, and the membership row that decides it. Not the resource row a through gate loads on the way - that is the row the handler is about to work on, and a handler must always see what its own gate just read rather than a copy a sibling action took before writing to it.

#Why the promise, not the value

Batch entries start concurrently. Storing the resolved value would have all seven miss, since none has finished when the others look. Storing the in-flight promise is what makes six of them await the first one's query.

#Why it is seeded from a hook and never lazily

AlsProvider.set writes into als.getStore() - the innermost layer - and $action.run() forks a fresh layer per action. A memo created on demand inside the guard therefore lands in that one action's fork and is invisible to its six siblings.

That failure is the quiet kind: every gate still allows, every behavioural test still passes, and the batch still issues seven lookups. Seeding on server:onRequest puts the Map on the request layer, which reads fall through to from every child fork, so each action finds the same object and mutates it by reference.

#Why a request is a safe boundary

The memo never outlives one request, and a request is already the atomic unit of authorization: nothing re-checks membership mid-request expecting a different answer. So this preserves revocation semantics exactly - unlike a cross-request TTL, which would trade them for a window.

With no seeded memo (a job, a CLI command, a direct run() in a test) every read simply happens, which is the behaviour these had before it existed.