@devx-retailos/products
v0.0.2
Published
Product catalog mirror for retailOS POS. Pluggable ProductSourceAdapter pulls from Shopify (built-in), BigCommerce, Medusa storefront, or custom WMS into a local mirror so the POS works offline. Webhook-driven freshness.
Keywords
Readme
@devx-retailos/products
Local product catalog mirror for POS backends. A pluggable ProductSourceAdapter pulls products, variants, images, and collections from an upstream platform (Shopify built-in) into local tables, so the POS can browse, scan, and sell even when the source platform is slow or unreachable. Webhooks keep the mirror fresh between syncs.
Part of retailOS, a Medusa v2 SDK for offline-store POS systems. Packages are installed independently and composed in a brand's Medusa backend.
Installation
npm install @devx-retailos/productsRequires a Medusa v2 backend — @medusajs/framework and @medusajs/medusa ^2.15.0 are peer dependencies.
Setup
Register the plugin in medusa-config.ts:
plugins: [
{
resolve: "@devx-retailos/products",
options: {},
},
],The module registers under the key products (exported as PRODUCTS_MODULE) and ships its own migrations.
Usage
Configure a source and sync
A ProductSource row holds the adapter type and its config. The built-in shopify adapter is validated against ShopifyAdapterConfig:
import { PRODUCTS_MODULE, type ProductsModuleService } from "@devx-retailos/products"
const products = container.resolve<ProductsModuleService>(PRODUCTS_MODULE)
const [source] = await products.createProductSources([
{
organization_id: "org_01...",
name: "Main catalog",
adapter_type: "shopify",
config: {
shop_domain: "your-shop.myshopify.com",
admin_access_token: process.env.SOURCE_ADMIN_TOKEN,
webhook_secret: process.env.SOURCE_WEBHOOK_SECRET, // optional, enables webhook verification
// api_version: "2025-01" (default), default_currency: "INR" (default)
},
},
])
const outcome = await products.syncSource({ source_id: source.id, mode: "full" })
// { job_id, status: "succeeded" | "failed", pages_fetched, products_upserted, products_deleted, variants_upserted }mode defaults to "incremental" once a full sync has run (using the source's last_full_sync_at / last_incremental_sync_at watermarks). Every run is recorded as a ProductSyncJob. A full sync also removes mirrored products the source no longer returns.
Webhook-driven freshness
The package ships no HTTP routes; expose a route in your backend and hand the raw request to the service. The adapter verifies the signature (HMAC-SHA256 for Shopify) and applies product.upsert / product.delete events:
const result = await products.applyWebhookEvent({
source_id,
http: { raw_body: rawBody, headers },
})
// { verified: boolean, event: ParsedWebhookEvent, applied: boolean }POS lookups
await products.lookupByBarcode({ organization_id, barcode: "8901234567890" })
await products.lookupBySku({ organization_id, sku: "TSHIRT-M" })
// -> { product, variant } | nullStandard Medusa CRUD methods are generated for ProductSource, Product, ProductVariant, and ProductSyncJob (e.g. listProducts, listProductVariants, retrieveProductSource).
Extension points
ProductSourceAdapter
Implement the interface from @devx-retailos/products/types to mirror any platform (BigCommerce, a WMS, a custom API):
import type { types } from "@devx-retailos/products"
const myAdapter: types.ProductSourceAdapter<MyConfig> = {
type: "my-source",
description: "Custom catalog source",
capabilities: {
supports_webhooks: false,
supports_incremental_sync: true,
supports_search: false,
},
validateConfig(config) {
return MyConfigSchema.parse(config)
},
async fetchProducts(config, opts) {
// opts: { since?, cursor?, page_size? }
return { products: [/* NormalizedProduct[] */], has_more: false }
},
async fetchProduct(config, source_id) {
return null
},
// optional: searchProducts, verifyWebhook, parseWebhook
}
products.registerAdapter(myAdapter)Adapters return NormalizedProduct objects (title, status, tags, collections, images, variants with price/sku/barcode) — the service handles all persistence. shopifyProductSource, BUILTIN_PRODUCT_SOURCES, and createDefaultSourceRegistry are exported from @devx-retailos/products/adapters.
Permissions
Registered via PRODUCTS_PERMISSIONS (also exported from @devx-retailos/products/permissions):
product.source.read— view configured product sourcesproduct.source.write— create or update product sourcesproduct.source.delete— deactivate product sourcesproduct.sync— trigger a manual catalog syncproduct.read— read mirrored products and variants (POS browse/scan)product.webhook— server-only permission for accepting source webhooks
Related packages
@devx-retailos/core— shared types, errors,Logger, permission registry@devx-retailos/rbac— organizations, stores, roles, permissions@devx-retailos/order— POS orders that sell the mirrored catalog@devx-retailos/admin-extensions— admin API routes and Medusa Admin UI@devx-retailos/sdk-client— typed frontend client and React hooks
License
MIT
