--- type: brief summary: What "interchangeable with @activitypods/refine-providers" has to mean for this binding — what the adapter owes, what waits on the polyfill, and what has no analogue at all --- # Interchangeability with `@activitypods/refine-providers` ## The bar This package is held to being **at least as capable as `@activitypods/refine-providers`**, because the two sets of providers must be interchangeable: an application should move from one to the other without rewriting its pages. That reference (v0.1.5, same author, a structural twin of `@ng-org/refine-providers`) is therefore the specification of what must work — not the `DataProvider` interface in the abstract, and not the NextGraph sibling, which is thinner than both. Interchangeability is claimed on **what an application can call and render**. Each binding gets its authentication from what it sits on — the reference from a Pod provider, this one from the engagement it consumes — and how far either goes is that provider's business, not this package's. Nothing about either mechanism belongs in this repository: a binding that reasoned about its upstream's internals would hold knowledge it cannot verify, that drifts when the upstream moves, and that its own consumers would have to unlearn. ## What the adapter owes, and can deliver alone The decisive finding of the comparison: the reference asks its server for almost none of this. Containers support no server-side filtering, sorting or pagination, so it fetches the whole container and computes in memory. That is the same posture available here, which moves most of the gap off the polyfill and onto this package. | | the reference | here | |---|---|---| | Filters | every Refine operator, plus nested `and`/`or`, in memory, case-insensitive by default with sensitive variants | the same, over the read result — the NextGraph sibling honours `eq` only and warns on the rest | | Sorting, pagination, `total` | in memory, over the whole container | the same, over the whole shape read | | `getMany` | one read per id, `allSettled`, keeps what answered | one read per document — which also sidesteps the all-or-nothing of naming several documents at once | | Live | one subscription per record (`Update`/`Delete`), one on the container (`Add`/`Remove`); **list views never see a member's own fields change**, a limit it documents | one subscription per document, over the documents a read hands back — `subscribeDoc` is the guaranteed channel. A diff of shape-query snapshots would be the elegant route and must NOT be assumed: the engagement promises a re-publication when a **capability arrives**, and nowhere when a held document's **content** changes | ## What waits on the polyfill `update` and `deleteOne`. The reference serves both as ordinary awaited calls — a read-then-merge `PUT`, and a `DELETE`. Here no published call is both shape-typed and awaitable: removal goes through the live set a shape query hands back, reachable only from a React hook, and a mutation of that set is batched into a call nothing awaits. Refine requires a promise that resolves with the record or rejects. This is the one gap with no caller-side answer, and it is declared in `polyfill-surface/usage_refine-providers-eventually.md`, with the ownership predicate an edit control would need to know whether to show itself. **And deletion is deeper than a missing call.** The engagement publishes no removal of any kind, and describes what a removal through the live set does take away: the predicates the shape declares, while the subject, the document and every key already handed out survive. So on this surface `deleteOne` cannot make a record absent the way the reference's `DELETE` does — a bookmarked show page renders an empty record instead of a missing one. Calling this "a missing call" understates it; what it needs is a published meaning for deletion, which is question 5 below. ## What has no analogue The reference's auth provider is not only authentication. It carries **delegated access consent**: an application registers itself against the person's Pod, declares the access it needs, and is sent to an authorization agent for approval — `registerApp()`, `getAppStatus()`, `upgradeNeeded`, a periodic re-check, and two antd components (`AntdAuthPage`, `AntdBackgroundChecks`). The engagement publishes nothing of the kind, and places user management outside its `## Scope` — so there is no surface here to bind such a flow to, and none to be asked for: what the eventual SDK will do about application consent is not something this leaf can know, and guessing it is how a binding ends up teaching a model that never arrives. The question is therefore what THIS package answers when an application calls those two methods, not what the platform ought to provide. **`logout` DOES have an analogue — what is missing is the call.** This is worth stating carefully, because getting it wrong is how a binding teaches a model that will not exist. In the target each person opens their own wallet, so an open wallet IS the authenticated state, and closing it is the sign-out; `ensureIdentity()` is the emulation of the first act, and this binding must be wired to it as such — `check()` asks whether an identity is settled, `getIdentity()` asks who, `logout()` closes. That shape survives migration unchanged. What does not exist yet is any published way to perform the closing: the engagement names "changing identity or clearing it" as an event that occurs, and offers no call that causes it. So an application that switches bindings keeps its Logout button and it stops protecting anything — the barrier comes back prefilled, one click re-enters the identity that just left, and `ensureIdentity()` shows no barrier at all. That is a NEED (declared), not an absence of analogue, and until it is served this binding's `logout` can only clear what it holds itself and say so. ## Open questions ~~1. What does "interchangeable" cover?~~ **SETTLED (2026-10-01): the pages and the providers' surface — not the antd components.** The three providers expose the same methods as the reference, `registerApp`, `getAppStatus` and `handleCallback` included, in a degenerate behaviour: there is nothing to register, so registering resolves at once and the status reads installed and up to date. Each binding keeps its own bootstrap — this one needs `configure` and an awaited `ensureIdentity()` before the interface renders — and no antd component is published here: the polyfill's own barrier leaves a login page almost nothing to do, and a gate component nothing to gate. **What decided it was the reverse direction.** "Interchangeable" is symmetric, and an application written here and later moved to ActivityPods must have registered itself to read anything there. Without the method it never called it, and the failure lands in the other binding, silently, caused by a surface missing from this one. Exposing it means an application calls it by construction. **How the degenerate answer must be worded.** "Nothing to register" describes THIS package: the engagement publishes no application registration, so there is nothing here to register. It says nothing about the platform, and nothing about what the eventual SDK will do with application consent. The engagement this package publishes will present these three as **compatibility, never capability** — a published member that describes no mechanism is honest only if it says so. ~~2. Where does a display name come from?~~ **SETTLED (2026-10-01): raised upstream; the identifier stands in meanwhile.** The sibling binding offered no lead — its `getIdentity` returns a hard-coded name and a stock photo, so the question was never faced there. Inventing a profile shape here would be a convention of this package's own, which an application would have to unlearn the day the SDK publishes one, so it is declared as a need instead (an ask to widen a scope that excludes display names, justified by a caller having no conforming option: the identifier may not be rendered as a name, and no name is offered). Until it is answered, `getIdentity` returns the identifier in the `name` field — the same stand-in the reference falls back to when a profile cannot be read. ~~3. How is the authentication difference stated?~~ **SETTLED (2026-10-01): nowhere — the question was wrongly posed.** It assumed this package has something to say about how strongly its upstream authenticates. It does not: it receives an authentication and wires Refine to it. How that authentication is established, and what it is worth today, belongs to the engagement and to its own `## Non-guarantees`, where a consumer of this package reads it directly. Restating it here would duplicate a claim this package cannot verify, would drift the moment the upstream moves, and would teach an application a distinction that disappears at migration. The point stands for the whole repository, not just this leaf: **what is internal to the emulation must not be known here.** ~~4. Does `check()` honour `allowAnonymous`?~~ **SETTLED (2026-10-01): the option is not published; `check()` fails closed.** It follows from question 1 rather than needing its own arbitration: `allowAnonymous` is a field of the reference's `AuthProviderConfig`, and the bootstrap is the part each binding keeps for itself — `clientId`, `redirectUri` and `appStatusCheckInterval` have no object here either. What belongs to the interchangeable surface is the behaviour of `check()`, and there is no anonymous state for it to allow: an identity is settled before the interface renders, or the call that settles it rejected. So `check()` answers unauthenticated and redirects to the login route when no identity is settled — the reference's own default when the option is absent. ~~5. What should a listing contain?~~ **SETTLED (2026-10-01): everything accessible of that shape, and this package filters nothing.** A listing is `getObjects(shapeType, { graphs: "" })` and that is the whole of it. The surface already returns only what the identity holds the key for, so the filtering a Refine resource needs is the one already performed on the way out — there is nothing for this package to add, decide or restrict, and it must not invent a narrower notion of "mine" on top of a surface that already answers the question. Two consequences follow, both accepted rather than worked around: - **A listing mixes records that can be written with records that can only be read.** The default path is therefore to let the person act and surface the refusal, exactly as any failed mutation surfaces — which needs the awaitable write, not a predicate. The ownership predicate stays requested, demoted from "required" to a comfort: it lets an interface hide a control instead of letting it fail, and it blocks nothing. - ~~6. Does browsing grow a resource?~~ **Yes, and that is the intended behaviour.** Opening a public document by its `reference` puts its key in what the identity holds, so it joins later listings of that shape. A listing reflects what is accessible; it is not an inventory this package curates. ## To graduate **Every question is settled.** This brief is ready to graduate: the inventory becomes the engagement's `## Scope` and `## Non-guarantees`, the six settled answers become entries there (question 1's three compatibility members, question 5's definition of a listing, question 4's `check()`), and the brief is deleted once they are written. The engagement itself still waits on what the declaration asks of the polyfill — an engagement promises only what it can hold. Then this brief splits — the inventory above becomes the engagement's `## Scope` and `## Non-guarantees`, the settled question 1 and the answer to 3 become entries there, and what remains of 2 and 4 becomes either a `decision_` or an engagement entry.