Skip to main content

useEndpointSource

ONE coherent endpoint data binding shared by the abstract widgets (Wave 2, #91). Resolves a manifest-authored endpointSource block — an arbitrary app REST endpoint OpenRegister's aggregations can't express (a bespoke analytics controller, a summary payload) — into reactive { data, loading, error, refetch }.

Consumed by CnStatWidget, CnDeltaWidget, CnChartWidget, and CnWidgetObjectTable; usable directly by consumer apps.

Signature

import { useEndpointSource } from '@conduction/nextcloud-vue'

const { data, loading, error, refetch } = useEndpointSource(endpointSource, {
ctx: () => ({ objectId, object, workspace, config }), // token context (object | ref | getter)
widgetId: 'kpi-revenue', // matches cn:widget:refresh payloads
refreshKey: myReactiveToken, // app-local refresh escape hatch
})

endpointSource may be an object, a ref, or a getter (() => props.endpointSource). When null / without a url, the composable never queries and data.value stays null.

The endpointSource block

{
url: '/apps/pipelinq/api/analytics/overview', // app-relative (generateUrl) or absolute
method: 'GET', // 'GET' (default) | 'POST' (params → JSON body)
params: { period: '@workspace.datePreset?' }, // SAME token grammar as widget filters
responsePath: 'summary', // dot-path pluck (default: whole body)
}
  • Token resolutionparams values pass through resolveFilterTokens, exactly like widget filters: @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object.<field> (detail context), @workspace.<key>/@config.<key>. A trailing ? marks a value OPTIONAL (dropped when unresolved); a REQUIRED token that stays unresolved blocks the fetch (no request, no error) until the page context provides it. The url itself may interpolate @page.*/@workspace.*/@config.*/@objectId/@object.* inline. An unresolved URL token collapses to an empty string, and when that empties a path segment the fetch is blocked too, on the same terms as an unresolved params token. /cases/x/progress/@object.caseType would otherwise be sent as /cases/x/progress/ on the first render, before the record arrives — a 404 that renders identically to a real zero.
  • Shared cache + dedup — one in-flight promise + short-TTL (5 min) cache entry per (method, url, resolved params), module-wide. Four KPI tiles reading the same overview endpoint issue ONE request per render pass — the same per-period semantics pipelinq's dashboardData.js used. responsePath plucking happens per subscriber, after the shared response resolves.
  • Reactivity — the resolved request signature is watched: when the page-level workspace context changes (the dashboard date-range pills publish dateFrom/dateTo/datePreset; page filters publish their keys), params re-resolve and the widget refetches automatically.

Refresh wiring

Grounded in the library's own event-bus channels (what CnActionsMenu broadcasts):

SignalBehaviour
cn:page:refreshForce-refetch past the shared cache (page-level Refresh action).
cn:widget:refresh with matching widgetIdForce-refetch (per-widget Refresh action). Requires a non-empty widgetId option.
refreshKey bumpForce-refetch. Escape hatch for app-local refresh signals the library cannot observe (e.g. a Vue.observable token like pipelinq's refreshDashboardData()).
refetch()Force-refetch. refetch(false) allows a warm cache hit.

Helpers

Validation

In manifests, endpointSource is exactly-one-of with the widget's OpenRegister binding (source on stat/delta/object-table, dataSource on chart) — enforced by validateManifestV2() as a post-schema check (schema v2.14.0, $defs/endpointSource / $defs/chartEndpointSource).