@stsdti/approvable-core
v0.1.12
Published
Framework-agnostic client, domain models and view-model derivations for laravel-approvable.
Readme
@stsdti/approvable-core
The framework-agnostic half of laravel-approvable: the API client, the domain models, and the derivations a UI needs in order to render an approval.
You do not normally install this directly. @stsdti/approvable-vue depends on
it. Reach for it when you are building a binding for another framework, or
driving the API without a UI at all.
npm install @stsdti/approvable-coreIt has no runtime dependencies, and imports no UI framework — a test in this package enforces both.
This package is ESM only: it publishes ES modules and no CommonJS build. Use
import, or a dynamic import() from CommonJS.
Configuring the client
The default works in a browser with no setup, using fetch and sending the
XSRF-TOKEN cookie back as X-XSRF-TOKEN so Laravel's CSRF check passes.
To reuse an application's own axios instance, with its interceptors and auth:
import axios from 'axios'
import { configureApprovableClient } from '@stsdti/approvable-core'
configureApprovableClient({ axios })configureApprovableClient also accepts fetch, adapter and baseURL. It is
sugar over setApprovableClient(new ApprovableClient({ … })), which stays
available for whatever it does not cover, and returns the client it installed.
Any object with a request({ method, url, data, headers, signal }) method
returning { data, status, headers } is a valid adapter, which is also the
seam to stub in tests.
Ports
import { setApprovableNotifier, setApprovableDateFormatter } from '@stsdti/approvable-core'
setApprovableNotifier({ success: toast.success, error: toast.error })
setApprovableDateFormatter((value) => dayjs(value).format('LLL'))Without them, notifications go to the console and dates render as
dd.MM.yyyy HH:mm.
View models
The derivations that decide what an approval looks like — which buttons show, whether a step is visible, how a comment is truncated — are plain functions over plain data:
import { createApprovableViewModel, createStepViewModel } from '@stsdti/approvable-core'
const approval = createApprovableViewModel(document)
// { hasApprovable, approvable, steps, hasInactiveSteps, status, statusName, updatedAt }
const step = createStepViewModel(approval.steps[0])
// { isVisible, isActive, opacity, status, approverName, performedAt, buttons, comment }A Vue binding wraps these in computed, a React one in useMemo, a
server-rendered template calls them directly. Keeping the logic here is what
stops two bindings from quietly disagreeing about behaviour.
Actions
import { performStepAction } from '@stsdti/approvable-core'
const result = await performStepAction({
stepId: step.id,
button: 'approve',
buttons: step.buttons
})
// { ok: true } | { ok: false, skipped: 'no-status-code' } | { ok: false, error }Returns a result rather than throwing, and reports through the notifier port, so a binding only has to toggle a loading flag and refresh.
Custom action buttons
A button carrying an action_ui value renders through a registered renderer:
import { registerActionRenderer } from '@stsdti/approvable-core'
registerActionRenderer('pdf-signature', MySignatureComponent)The stored value is opaque to this package, so any framework's component is valid. The Vue binding wraps registration to mark components raw.
License
MIT
