Files
ng-eventually-refine-providers/.project/concepts/refine-binding/polyfill-surface/usage_refine-providers-eventually.md
T
Sylvain Duchesne dcde2859fe Consume polyfill-surface, and declare what this package relies on
Pulls @ng-eventually/polyfill's engagement, version-pinned, and declares
back what this package actually consumes — the contract pair that lets the
two repositories coordinate without either reading the other's source.

The declaration is the channel: its Frictions and Needs are standing
requests the provider drains. The one that decides this package's shape:
modifying and removing a record have no published path that is both
shape-typed and awaitable, so Refine's update and deleteOne have nowhere
to land. Reading, creating and both live channels do have one.

The engagement describes 1.0.0-dev.14 and no tag carries it, so the pin is
the branch it is authored on and `against:` is the commit it resolved to.
2026-10-01 15:55:02 +02:00

8.8 KiB

type, summary, against
type summary against
usage Binds a Refine application's resources to NextGraph documents — one shape per resource, one document per entity — through this engagement's shape reads, its shape-typed insert and its inbox 4c1801f6ee1dba0ce193e693960a6788686d602e

usage_refine-providers-eventually — @ng-eventually/refine-providers

This package is a set of Refine providers — dataProvider, authProvider, liveProvider — that an application declares once and then drives through Refine's own hooks (useTable, useForm, useShow). It is a library, not an application: it declares this engagement a peer, so the application remains the one place the package is named.

It carries no compensation for the current state of NextGraph. Capability isolation, identity, inboxes, sharing, broker state: all of that is this engagement's. What this package does is translate — a Refine resource is one SHEX shape, a Refine id is the NURI of one document, and one document holds one business entity.

The pinned version is the engagement's own 1.0.0-dev.14, reached through the branch it is authored on rather than a tag — see ## Frictions.

Consumed surface

Bootstrap (the application's, before it renders; this package only reads what it settled) — configure, init, ensureIdentity.

Reading. getObjects(shapeType, config) is the whole of the read path, on two scopes and no other:

  • graphs: "" for a resource listing. There is no call that lists the documents of a store, so the per-document capability filter IS the listing: what comes back is what this identity holds of that shape, which is exactly what a listing should contain — the documents it created and the documents whose key it was given, with no gesture of its own to tell them apart.
  • graphs: [<nuri>] for one record, or several.

where and orderBy carry Refine's filters and sorters. docs.sparqlQuery, anchored, is called before a named read when the document may be a public one this identity has never read — see ## Frictions.

Writing. storeRegistry.createEntityDoc(store?) with privateStore() / protectedStore() / publicStore() to place it, then insertObject(shapeType, object) with the object carrying its @graph and an empty @id. docs.sparqlUpdate for modification and removal, for want of anything else — see ## Needs.

Reacting. useShapeQuery(shapeType, scope) for a resource channel, through getSnapshot / subscribe / refetch — it is an observable rather than a hook, which is what makes it reachable from a provider. subscribeDocs + docChangeType for a channel over named ids.

Sharing (outside Refine's CRUD, reached through the provider's custom or through hooks this package exports) — inbox.share, inbox.postToDocument, inbox.readForDocument, inbox.readSyncedForDocument, storeRegistry.openDocumentInbox.

Types — Nuri, NuriLike, Scope, RdfOrmConfig, ShapeType, BaseType, ShapeQuery, ShapeObservable, PrincipalId.

useShape is NOT consumed: it is a React hook, and a DataProvider is not a component.

Constraints

A read under graphs: "" must keep answering what this identity holds, and nothing else. This is the one mechanism that makes a resource listing both correct and complete, since nothing lists a store. If it ever widened to what the wallet physically carries, every application built on this package would show every identity's records with nothing signalling it.

useShapeQuery must stay an observable, and its data must keep carrying the shape's own objects (@id, @graph, the shape's predicates). Both are load-bearing: the first is what a non-React provider can subscribe to, the second is what lets an application move between this package and @ng-org/refine-providers without rewriting a single page — the records must have the same shape on both sides.

getObjects's collection kind must stay predictable from the config — an array with orderBy, a Set otherwise. Refine needs a stable order and a count.

insertObject must keep returning the object with its @id filled in. Refine's create resolves with the created record; there is nothing else to build it from.

One document per entity must remain the recommended placement, and access must stay per document. A Refine id is a document's NURI; if access were granted at any other granularity, id would stop being the unit that can be read, shared and listed.

Relayed to the application, which owns its URL: ?ng-id= must survive its router. Refine's syncWithLocation rewrites the query string, and a dropped parameter is a silent mis-identification rather than an error.

Frictions

The version this engagement tells us to anchor on is not referenceable. ## Change policy names the regime — polyfill/vX.Y.Z, package-prefixed — and names 1.0.0-dev.14 as what exists today, to be pinned exactly and anchored here as against: @ng-eventually/polyfill@1.0.0-dev.14. The only tags that exist are polyfill/v1.0.0-dev.2 and polyfill/v1.0.0-dev.3, so that string names nothing: the pin is the branch the engagement is authored on and against: is the commit it resolved to. Nothing about the resolution is irregular — a canonical identity mapped onto a local checkout is what a shared machine is for, and the stamp records the canonical value either way. What is missing is only the tag, and until one carries this version the policy's own instruction cannot be followed, nor can "between two pre-releases nothing is promised" be acted on: there is no pre-release to compare against.

Modifying and removing a record have no published path that is both shape-typed and awaitable. ## Non-guarantees routes removal through the LIVE set a shape query handed back, and that set is reachable only through useShape — a React hook, which a DataProvider cannot call. Worse for this shape of caller: a mutation of a live set is batched into a call NextGraph does not await, so a refusal surfaces as an unhandled rejection in the console rather than at the line that made the change. Refine's update and deleteOne must resolve with the record or reject; neither is possible on that channel. What is left is projecting the shape into SPARQL and calling docs.sparqlUpdate — re-implementing in this package what NextGraph's ORM already does, which is exactly the kind of compensation this package exists not to carry.

A named read needs a priming call that has no other purpose. getObjects spends no round-trip asking a public store for its key, so a reference received from elsewhere reads empty until the document has been read by name through docs.sparqlQuery. Reading one record is therefore two calls, the first of which exists only to make the second work. useShapeQuery has not had this since 1.0.0-dev.9 — it asks once per document named in graphs — so the asymmetry between the two reads is what costs the call.

Naming several documents fails as a whole. One unreachable document in graphs means nothing is answered, so a getMany over a heterogeneous list — some created here, some received, some public — cannot return the records that ARE readable. A Refine table that resolves relations in one call cannot use it.

A listing has no count and no reachable pagination. getObjects's config is Omit<RdfOrmConfig, "pageSize" | "maxActivePages">, so the only way to a total is to read everything this identity holds of the shape and measure it. Refine paginates from a total, so every page costs the whole collection. useShape carries nextPage / previousPage; the one-shot read does not.

ShapeObservable and refetch() are both open migration points, by this engagement's own account — the first on the deletion list, coupled to the shared wallet, the second with no recorded counterpart and the only published way out of a read stuck pending. The live provider is written on both.

Needs

A shape-typed write and a shape-typed removal, awaitable, callable outside React. The analogue of insertObject for modifying and removing — same posture: the object carries its @graph, the call rejects when refused, and the caller awaits it. This is the one need that decides the shape of this package's dataProvider; everything else here is a cost, this is a wall.

A count, or a reachable page, on the one-shot read — so a listing does not have to rapatriate everything an identity holds to know how many there are.

A key asked for the documents named in getObjects's graphs, as useShapeQuery has done since 1.0.0-dev.9. That would retire the priming docs.sparqlQuery and make one record cost one call.

Per-document outcomes when several are named, rather than an all-or-nothing answer — so a listing of mixed provenance can show what is readable.

A tag for the version ## Change policy says to pin.