SDK seam
createServices(client) is the one place where the SDK enters the UI
packages. Everything else — components, composables, contexts — talks to
services, not to GraphQLClient. That indirection is what lets the
React and Vue packages share most of their composable logic and what
keeps tests fast (you stub services, not the network).
createServices
import { createServices, type Services } from 'propeller-v2-core-ui';
import { GraphQLClient } from 'propeller-sdk-v2';
const client = new GraphQLClient(API_URL, { headers: { ... } });
const services: Services = createServices(client);
The returned Services object groups SDK calls by domain:
services.auth // login, register, logout, refreshToken
services.cart // create, addItem, updateItem, removeItem, setPaymethod, …
services.orders // list, get, setStatus (accept quote), submit
services.products // getById, getByCategoryId, getBySearch, getByCluster
services.companies // get, listContacts, addContact, …
services.favorites // listLists, addToList, removeFromList, createList
services.cms // optional — undefined when no CMS adapter wired
(Exact method list lives in the SDK — these are the canonical façades the UI packages build composables on top of.)
Memoisation
createServices(client) is memoised on the client reference. Calling
it twice with the same GraphQLClient returns the same services
object — no per-call allocation, no per-request churn under SSR.
const a = createServices(client);
const b = createServices(client);
a === b; // true
In practice this means the consumer can call createServices at the
root of every SSR request handler without worrying about allocation
cost — as long as it reuses the same GraphQLClient, the work is done
once per client lifetime.
Why "seam"?
The framework-specific UI packages used to import the SDK directly. That made:
- Component tests slow — every test booted a real GraphQL client.
- The package surface unstable — any SDK refactor rippled into components.
- Cross-framework code duplication impossible — React composables hand-rolled the same SDK calls as Vue composables.
Wrapping the SDK behind createServices gives the UI packages a single,
mockable boundary. Component tests stub services.cart.addItem, not the
network. React and Vue composables share semantic intent; only the state
shape differs.
toPlain
toPlain<T>(value: T): T
Normalises class-instance returns from the SDK (Cart, Order, …) into
plain JSON objects. Use at SSR-to-client serialisation boundaries
(Next.js Server Component → Client Component, Nuxt useFetch, etc) so
the hydration layer doesn't try to serialise getters or method bindings.
const cart = await services.cart.get(cartId);
return { cart: toPlain(cart) }; // safe to JSON.stringify
Custom services
createServices does not currently accept a custom service map override
— if you need that, fork the call locally and post a PR. The "seam"
metaphor is deliberate: there is one place to extend, and it lives in
this package.