Skip to main content

API integration overview

This section explains how the package communicates with the Propeller GraphQL backend — the layers a request passes through, who owns what, and where to look when something goes wrong. The per-domain pages (cart, auth, catalog, orders, B2B) then go composable-by-composable.

For the exact signature of any symbol named here, see the generated API reference.

The request path​

Every backend call made by the package travels the same path:

component / your code
│ calls
▼
composable (use* function) ← reactive state, loading, error handling
│ calls
▼
Services bundle (services.x) ← built once by createServices()
│ delegates to
▼
propeller-sdk-v2 Service class ← builds the GraphQL document
│ uses
▼
GraphQLClient ← YOUR endpoint, headers, auth
│ HTTP
▼
Propeller GraphQL API

The package owns the middle three layers. It does not own the GraphQLClient — you construct that — and it does not own the network transport. See The SDK seam for why.

GraphQLClient — you build it​

The package ships no client and no endpoint. You construct a GraphQLClient from propeller-sdk-v2 with the endpoint, headers, timeout and auth resolver that fit your app, and you keep ownership of it.

import { GraphQLClient } from 'propeller-sdk-v2';

const graphqlClient = new GraphQLClient({
endpoint: '/api/graphql', // your route / proxy / upstream URL
apiKey: '',
timeout: 30_000,
});

The client is stateful: SDK code mutates its config in place (graphqlClient.updateConfig({ headers })) when auth changes — see Authentication. Everything downstream reads the current headers at request time, so a login that updates the client is immediately visible to every composable.

createServices — the Services bundle​

createServices(client) builds one Services object — a typed bundle of 15 SDK service instances (product, cart, user, category, order, payMethod, login, address, company, crossupsell, bundle, favoriteList, purchaseAuthConfig, cluster, orderlist), each wired to the client you passed.

import { createServices } from 'propeller-v2-vue-ui';

const services = createServices(graphqlClient);
// services.cart, services.product, services.order, …

It is memoised per client with a WeakMap — calling it again with the same client returns the same bundle, so there is no cost to calling it freely. You pass services (and graphqlClient) into providePropeller once; from there useServices() returns the bundle anywhere in the tree.

See createServices and Services in the reference.

How composables reach the API​

There are two patterns, and the package uses both:

  1. Via the provider — a composable calls useServices() to read the bundle from providePropeller. Used by useServices itself and the pure context composables.
  2. Via an explicit graphqlClient option — most data composables (useCart, useAuth, useOrders, …) accept graphqlClient in their options object and derive a local Services bundle from it with createServices. Because createServices is memoised, this resolves to the same bundle the provider holds.

In practice you pass the same graphqlClient instance everywhere — into the provider and into each composable's options — so both patterns observe one client, one bundle, one auth state.

Reactive options​

Unlike the React hooks they mirror, these composables take reactive options. Fields that change over the page's lifetime — user, companyId, language, cartId — are passed as Vue Refs (or computeds), not plain values:

const cart = useCart({
graphqlClient,
user, // Ref<Contact | Customer | null>
companyId, // Ref<number | undefined>
language: ref('NL'),
configuration,
});

The composable unrefs them at call time, so when your auth state or the active company changes, the next action sees the new value with no re-instantiation. There is no Rules-of-Hooks constraint — call a composable conditionally or inside a branch if it makes sense.

toPlain — normalising SDK results​

SDK service methods return class instances whose getters forward to underscore-prefixed backing fields (_items, _firstName). Anything that serialises them — JSON, Vue's reactivity diffing, structuredClone — sees the underscored shape, not the clean public type.

toPlain(value) recursively strips that prefix. Apply it once at the service boundary when you need a plain object (for reactive state, for passing to a Nuxt server context, for logging). The package's own components handle this internally; you only need toPlain for data you fetch yourself.

Error handling convention​

API-talking composables do not throw to the caller. Each async action returns a result object — typically { success: boolean; error?: string }, sometimes with a payload (cart, orderId, user). Loading and the last error are also surfaced as loading / error refs on the composable return.

const { addItem, loading, error } = useCart({ graphqlClient, user, configuration });

const result = await addItem({ product, quantity: 1 });
if (!result.success) {
// result.error is a human-readable message
}

The exception is useServices(), which throws if called outside providePropeller — that is an integration mistake, not a runtime error, so it fails loudly and early.

Authentication, in one paragraph​

useAuth().login() registers the session token on the GraphQLClient you own (setAccessToken) for the rest of the page session, and hands it back to you through onAuthHeaderUpdate and the result object. Cross-reload auth is your app's responsibility — typically an httpOnly cookie set by your backend, with your GraphQL proxy injecting the Bearer header server-side. Full detail on the auth page.