@platform-x-shp/shp-adapter
v0.0.3
Published
TypeScript adapter SDK that bridges a React ecommerce frontend with headless microservices.
Maintainers
Readme
Ecommerce Adapter SDK
TypeScript npm package for React ecommerce applications to integrate with headless microservices.
What this package does
- Fetches and caches bearer tokens from auth service
- Provides typed clients for customer, product, cart, and order domains
- Supports customer signup/login flows
- Handles token injection, timeout, and base header configuration
Supported services
- Auth service:
https://localhost:3001 - Customer service:
https://localhost:3002 - Product service:
https://localhost:3003 - Cart service:
https://localhost:3004 - Order service:
https://localhost:3005
Installation
npm install @platform-x-shp/shp-adapterQuick start
import { createEcommerceAdapter } from "@platform-x-shp/shp-adapter";
const adapter = createEcommerceAdapter({
// Required — all five backend services are multi-tenant and reject any
// request with no sitename/sitehost header. Sent as `sitename` on every
// request this adapter makes.
siteName: "KIWI",
auth: {
baseUrl: "https://localhost:3001",
tokenPath: "/api/auth/token", // optional (default)
username: "admin",
password: "admin123"
},
customerService: {
baseUrl: "https://localhost:3002",
apiPrefix: "/api" // required for current customer service
},
productService: {
baseUrl: "https://localhost:3003",
apiPrefix: "/api" // required for current product service
},
cartService: {
baseUrl: "https://localhost:3004",
apiPrefix: "/api"
},
orderService: {
baseUrl: "https://localhost:3005",
apiPrefix: "/api"
},
request: {
timeoutMs: 10000,
headers: {
"X-Client": "web-store"
}
},
tokenRefreshSkewMs: 10000
});
async function bootstrap() {
const [customers, products] = await Promise.all([
adapter.listCustomers({ page: 1, limit: 20 }),
adapter.listProducts({ page: 1, limit: 20 })
]);
return { customers, products };
}Configuration
interface AdapterConfig {
siteName: string; // required — sent as the `sitename` header to all backend services
auth: {
baseUrl: string;
username: string;
password: string;
tokenPath?: string; // default: /api/auth/token
};
customerService: {
baseUrl: string;
apiPrefix?: string;
};
productService: {
baseUrl: string;
apiPrefix?: string;
};
cartService?: {
baseUrl: string;
apiPrefix?: string;
};
orderService?: {
baseUrl: string;
apiPrefix?: string;
};
request?: {
timeoutMs?: number;
headers?: Record<string, string>;
};
tokenRefreshSkewMs?: number; // default: 10000
}API surface
Auth token controls
setAccessToken(accessToken)URL: local token override in SDK (no HTTP call)refreshAccessToken()URL:POST https://localhost:3001/api/auth/token
Auth service endpoints (backend reference)
POST /api/auth/tokenURL:POST https://localhost:3001/api/auth/tokenPurpose: authenticate admin credentials and issue a JWT SDK support:refreshAccessToken()POST /api/auth/verifyURL:POST https://localhost:3001/api/auth/verifyPurpose: verify a JWT and return its payload SDK support:verifyAuthToken(payload)GET /api/auth/meURL:GET https://localhost:3001/api/auth/mePurpose: return authenticated principal from bearer token SDK support:getAuthPrincipal(accessToken)
Customer
getCustomer(customerId)URL:GET https://localhost:3002/api/customers/{customerId}listCustomers(query)URL:GET https://localhost:3002/api/customerscreateCustomer(payload)URL:POST https://localhost:3002/api/customersupdateCustomer(customerId, payload)URL:PUT https://localhost:3002/api/customers/{customerId}searchCustomers(searchTerm, limit?)URL:GET https://localhost:3002/api/customers/search?q={searchTerm}&limit={limit}getMyCustomerProfile()URL:GET https://localhost:3002/api/customers/megetCustomerOrders(customerId)URL:GET https://localhost:3002/api/customers/{customerId}/ordersgetCustomerAddresses(customerId)URL:GET https://localhost:3002/api/customers/{customerId}/addressesgetCustomerAddressById(customerId, addressId)URL:GET https://localhost:3002/api/customers/{customerId}/addresses/{addressId}createCustomerAddress(customerId, payload)URL:POST https://localhost:3002/api/customers/{customerId}/addressesupdateCustomerAddress(customerId, addressId, payload)URL:PUT https://localhost:3002/api/customers/{customerId}/addresses/{addressId}deleteCustomerAddress(customerId, addressId)URL:DELETE https://localhost:3002/api/customers/{customerId}/addresses/{addressId}setDefaultCustomerAddress(customerId, addressId)URL:PUT https://localhost:3002/api/customers/{customerId}/addresses/{addressId}/default
Customer auth
customerSignup(payload)URL:POST https://localhost:3001/api/customers/signupcustomerLogin(payload)URL:POST https://localhost:3001/api/customers/loginverifyAuthToken(payload)URL:POST https://localhost:3001/api/auth/verifygetAuthPrincipal(accessToken)URL:GET https://localhost:3001/api/auth/mechangeCustomerPassword(accessToken, payload)URL:POST https://localhost:3001/api/customers/password/changerequestCustomerPasswordReset(payload)URL:POST https://localhost:3001/api/customers/password/reset/requestconfirmCustomerPasswordReset(payload)URL:POST https://localhost:3001/api/customers/password/reset/confirm
Cart
createCart(payload)URL:POST https://localhost:3004/api/cartsgetCart(cartId)URL:GET https://localhost:3004/api/carts/{cartId}addCartLines(cartId, payload)URL:POST https://localhost:3004/api/carts/{cartId}/linesupdateCartLines(cartId, payload)URL:PUT https://localhost:3004/api/carts/{cartId}/linesremoveCartLines(cartId, payload)URL:DELETE https://localhost:3004/api/carts/{cartId}/linesupdateCartBuyerIdentity(cartId, payload)URL:PUT https://localhost:3004/api/carts/{cartId}/buyer-identityupdateCartAttributes(cartId, payload)URL:PUT https://localhost:3004/api/carts/{cartId}/attributesupdateCartDiscountCodes(cartId, payload)URL:PUT https://localhost:3004/api/carts/{cartId}/discount-codesgetCartCheckoutUrl(cartId)URL:GET https://localhost:3004/api/carts/{cartId}/checkout-urldeleteCart(cartId)URL:DELETE https://localhost:3004/api/carts/{cartId}
Order
Requires a bearer token (same auth flow as Customer/Product). Configure orderService to enable.
getOrder(orderId)URL:GET https://localhost:3005/api/orders/{orderId}listOrders(query)URL:GET https://localhost:3005/api/orders
Product
getProduct(productId)URL:GET https://localhost:3003/api/products/{productId}listProducts(query)URL:GET https://localhost:3003/api/productssearchProducts(searchTerm, query)URL:GET https://localhost:3003/api/products/search?q={searchTerm}getProductByHandle(handle)URL:GET https://localhost:3003/api/products/handle/{handle}getProductVariants(productId)URL:GET https://localhost:3003/api/products/{productId}/variantsgetProductInventory(productId)URL:GET https://localhost:3003/api/products/{productId}/inventorygetProductMedia(productId)URL:GET https://localhost:3003/api/products/{productId}/mediagetProductRecommendations(productId)URL:GET https://localhost:3003/api/products/{productId}/recommendationsgetVariantById(variantId)URL:GET https://localhost:3003/api/variants/{variantId}getVariantInventory(variantId)URL:GET https://localhost:3003/api/variants/{variantId}/inventorygetProductFilters()URL:GET https://localhost:3003/api/products/filtersgetProductTags()URL:GET https://localhost:3003/api/products/tagsgetProductTypes()URL:GET https://localhost:3003/api/products/typesgetProductVendors()URL:GET https://localhost:3003/api/products/vendorsfilterProducts(query)URL:GET https://localhost:3003/api/products/filtergetCollections()URL:GET https://localhost:3003/api/collectionsgetCollectionById(collectionId)URL:GET https://localhost:3003/api/collections/{collectionId}getCollectionProducts(collectionId)URL:GET https://localhost:3003/api/collections/{collectionId}/products
All product methods support an optional second argument:
{ requiresAuth?: boolean }Use requiresAuth: false to skip automatic bearer token injection for that call.
Example calls
// Customer auth
await adapter.customerSignup({
firstName: "Alex",
lastName: "Doe",
email: "[email protected]",
phone: "+1-555-123-4567",
password: "StrongPassword123"
});
const login = await adapter.customerLogin({
email: "[email protected]",
password: "StrongPassword123"
});
adapter.setAccessToken(login.token);
// Product lookup by handle
const product = await adapter.getProductByHandle("classic-white-tee");
// Product lookup without attaching bearer auth
const publicCatalog = await adapter.listProducts(
{ page: 1, limit: 12 },
{ requiresAuth: false }
);
// Order lookup (requires orderService to be configured)
const order = await adapter.getOrder("gid://shopify/Order/123456789");Error handling
The adapter throws EcommerceAdapterError for HTTP failures and token parsing issues.
import { EcommerceAdapterError } from "@platform-x-shp/shp-adapter";
try {
await adapter.listProducts({ page: 1, limit: 10 });
} catch (error) {
if (error instanceof EcommerceAdapterError) {
console.error(error.message, error.statusCode, error.service, error.details);
}
}Development
npm install
npm run buildAvailable scripts:
npm run build- compile TypeScript todistnpm run clean- removedistnpm run prepare- build before publish/install from git
Notes
- Auth token path defaults to
/api/auth/token. apiPrefixis optional per service and is prepended to all request paths. Customer, product, cart, and order services currently all mount their routes under/api, so setapiPrefix: "/api"for each.- Customer-service currently returns wrapped payloads such as
{ success, data, count }; this SDK normalizes those responses to adapter-friendly return types. - This SDK wraps admin-token fetch plus customer-auth flows including verify, me, and password lifecycle endpoints.
- Cart-service and order-service return wrapped payloads such as
{ success, data, count }; configurecartService/orderServiceto use those APIs. publishNotificationis implemented in the adapter's mapping layer but always rejects withProviderUnavailableError:shp-notificationis queue-only (RabbitMQ) with no HTTP transport, so there is no endpoint for the adapter to call yet.
