A Typed Repository Layer for SharePoint CRUD in SPFx
PnPjs is a good low-level client for SharePoint. The problem is what application code starts to look like when every feature repeats list names, internal field names, lookup expansion, ETag handling, response mapping, and error translation.
I built spfx-data-kit as a small repository layer above @pnp/sp v4.
The contract
A feature works with a domain-oriented resource:
const result = await requests.update(
id,
{ title: "Updated", status: "approved" },
{ etag },
);
The feature does not need to know that title maps to SharePoint's Title field, or that an update may return no response body.
The public contract currently supports:
listgetcreateupdateremove
Single-item results carry the current ETag:
{
data: request,
etag: '"7"'
}
Lists keep entities ergonomic and expose ETags separately:
{
data: requests,
etags: {
"42": '"7"'
}
}
What it adds above PnPjs
spfx-data-kit is not a replacement for PnPjs and does not attempt to cover all SharePoint APIs. It adds application-level conventions:
- explicit domain-to-SharePoint field mapping;
- typed create and update inputs;
- stable error kinds for validation, not-found, conflict, permission, transient, and unknown failures;
- first-class optimistic-concurrency inputs through ETags;
- update re-reads after SharePoint's possible
204 No Contentresponse; - bounded async paging with
pageSize,maxPages, and an overalltopcap; - a local in-memory adapter implementing the same CRUD contract;
- cloning at local adapter boundaries to prevent accidental shared-state mutation.
This keeps TanStack Query in its proper place: TanStack can manage cache, refetch, invalidation, and optimistic UI while the repository owns SharePoint semantics.
React feature
↓
TanStack Query
↓
spfx-data-kit
↓
PnPjs v4
↓
SharePoint
Why a local adapter?
A mocked PnPjs fluent chain tests whether a particular call sequence happened. It does not provide much confidence that the feature's CRUD behavior is coherent.
The local adapter lets contract tests exercise validation, ETag conflicts, cloning, IDs, and CRUD behavior without a tenant. Tenant validation remains a separate responsibility: permissions, required fields, content types, and tenant configuration cannot be proven locally.
Current status
This is an early MVP, released as source on GitHub rather than presented as a finished enterprise framework.
Verified locally:
- 9 deterministic tests passing;
- TypeScript build passing;
- no-emission type check passing;
- package dry-run passing;
- npm dependency audit reporting zero vulnerabilities;
- pre-publish secret scrub passing.
Not yet validated against a live SharePoint tenant. That is the next honest test: initialize it after super.onInit(), connect it to a real list, and verify permissions, field requirements, live CRUD, and stale-ETag conflicts.
The repository is open for review:
github.com/vystartasv/spfx-data-kit
Feedback on the contract matters more than adding another layer of abstractions. If this is only a shorter spelling of PnPjs, it is not useful enough. The goal is a small, explicit boundary that makes SharePoint application code easier to test and harder to get subtly wrong.
Top comments (0)