A Convex component that syncs your Refurbed merchant data into local Convex tables — giving you reactive queries, multi-tenant isolation, and zero Refurbed API code in your app. Orders, offers, market offers, and reference data are all available through a simple class-based client or direct component function calls.
npm install @nativesquare/refurbedCreate (or update) your convex/convex.config.ts:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import refurbed from "@nativesquare/refurbed/convex.config.js";
const app = defineApp();
app.use(refurbed);
export default app;In any Convex function file, create a Refurbed instance:
import { Refurbed } from "@nativesquare/refurbed";
import { components } from "./_generated/api";
const refurbed = new Refurbed(components.refurbed);import { Refurbed } from "@nativesquare/refurbed";
import { components } from "./_generated/api";
import { mutation } from "./_generated/server";
import { v } from "convex/values";
const refurbed = new Refurbed(components.refurbed);
export const setupRefurbed = mutation({
args: { organizationId: v.string(), apiKey: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
// Creates the connection and enables sync (all entity types on by default)
return await refurbed.createConnection(ctx, {
organizationId: args.organizationId,
apiKey: args.apiKey,
});
},
});Three independent cron jobs run every 5 minutes, syncing orders (with order items), offers, and market offers automatically. Your data stays at most 5 minutes stale under normal operation.
Connections link a Refurbed merchant account to your Convex app. Each connection is scoped by organizationId for multi-tenant isolation.
const connectionId = await refurbed.createConnection(ctx, {
organizationId: "org-acme",
apiKey: "rfbd_live_...",
});Throws if a connection already exists for the given organizationId. All sync toggles are enabled by default.
import { query } from "./_generated/server";
export const listConnections = query({
args: {},
returns: v.any(),
handler: async (ctx) => {
return await refurbed.listConnections(ctx);
},
});Returns all connections with organizationId, sync toggles, last-sync timestamps, sync cursors, and error info. API keys are never returned.
await refurbed.updateConnection(ctx, {
connectionId: "...",
apiKey: "rfbd_live_new_...",
});Replaces the stored API key and clears any syncError state, re-enabling sync on the next cron cycle.
await refurbed.deleteConnection(ctx, {
connectionId: "...",
});Removes the connection and cascade-deletes all associated synced data (orders, order items, offers, market offers) for that organization. No other organization's data is affected.
Synced data queries read from local Convex tables — they are reactive Convex queries, so subscribed clients auto-update when data changes. No API calls are made.
import { query } from "./_generated/server";
import { v } from "convex/values";
export const listOrders = query({
args: { organizationId: v.string() },
returns: v.any(),
handler: async (ctx, args) => {
return await refurbed.listOrders(ctx, {
organizationId: args.organizationId,
});
},
});Filter by status and limit results:
const shipped = await refurbed.listOrders(ctx, {
organizationId: "org-acme",
status: "shipped",
limit: 10,
});Get a single order:
const order = await refurbed.getOrder(ctx, {
organizationId: "org-acme",
refurbedId: "12345",
});const items = await refurbed.listOrderItems(ctx, {
organizationId: "org-acme",
orderId: "12345",
limit: 50,
});Get a single order item:
const item = await refurbed.getOrderItem(ctx, {
organizationId: "org-acme",
refurbedId: "67890",
});const offers = await refurbed.listOffers(ctx, {
organizationId: "org-acme",
status: "ACTIVE",
limit: 20,
});
const offer = await refurbed.getOffer(ctx, {
organizationId: "org-acme",
refurbedId: "42",
});const marketOffers = await refurbed.listMarketOffers(ctx, {
organizationId: "org-acme",
offerId: "42",
limit: 20,
});
const marketOffer = await refurbed.getMarketOffer(ctx, {
organizationId: "org-acme",
refurbedId: "42:de", // compound key: "offerId:marketCode"
});These methods call the live Refurbed API in real time (no local caching). They require an ActionCtx — use them inside Convex action handlers.
import { action } from "./_generated/server";
import { v } from "convex/values";
export const getMarkets = action({
args: { organizationId: v.string() },
returns: v.any(),
handler: async (ctx, args) => {
return await refurbed.listMarkets(ctx, {
organizationId: args.organizationId,
});
},
});const market = await refurbed.getMarket(ctx, {
organizationId: "org-acme",
marketId: "de",
});
const currencies = await refurbed.listCurrencies(ctx, {
organizationId: "org-acme",
});
const currency = await refurbed.getCurrency(ctx, {
organizationId: "org-acme",
currencyId: "EUR",
});const instance = await refurbed.getInstance(ctx, {
organizationId: "org-acme",
instanceId: "inst-1",
});
const profiles = await refurbed.listShippingProfiles(ctx, {
organizationId: "org-acme",
});
const templates = await refurbed.getCatalogTemplates(ctx, {
organizationId: "org-acme",
});Control which entity types are synced without redeployment.
Updates the specified toggles on all connections — omitted toggles remain unchanged.
// Disable order sync on all connections, leave offers and market offers unchanged:
await refurbed.updateSettings(ctx, { syncOrdersEnabled: false });
// Enable all three toggles explicitly:
await refurbed.updateSettings(ctx, {
syncOrdersEnabled: true,
syncOffersEnabled: true,
syncMarketOffersEnabled: true,
});await refurbed.enableAllSync(ctx); // Enable all sync toggles on all connections
await refurbed.disableAllSync(ctx); // Disable all sync toggles on all connectionsimport { action } from "./_generated/server";
import { v } from "convex/values";
export const triggerSync = action({
args: { connectionId: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
return await refurbed.triggerSync(ctx, {
connectionId: args.connectionId,
});
},
});Triggers an immediate sync for all enabled entity types on the given connection. Requires ActionCtx because it makes HTTP calls to the Refurbed API.
| Method | Context | Data Source | Description |
|---|---|---|---|
createConnection(ctx, { organizationId, apiKey }) |
Mutation | Local write | Create a new merchant connection |
listConnections(ctx) |
Query | Local read | List all connections (no API keys) |
updateConnection(ctx, { connectionId, apiKey }) |
Mutation | Local write | Rotate API key, clear sync errors |
deleteConnection(ctx, { connectionId }) |
Mutation | Local write | Delete connection + cascade data |
listOrders(ctx, { organizationId, status?, limit? }) |
Query | Synced local | List synced orders with filters |
getOrder(ctx, { organizationId, refurbedId }) |
Query | Synced local | Get single order by Refurbed ID |
listOrderItems(ctx, { organizationId, orderId, limit? }) |
Query | Synced local | List order items for an order |
getOrderItem(ctx, { organizationId, refurbedId }) |
Query | Synced local | Get single order item |
listOffers(ctx, { organizationId, status?, limit? }) |
Query | Synced local | List synced offers with filters |
getOffer(ctx, { organizationId, refurbedId }) |
Query | Synced local | Get single offer |
listMarketOffers(ctx, { organizationId, offerId?, limit? }) |
Query | Synced local | List synced market offers |
getMarketOffer(ctx, { organizationId, refurbedId }) |
Query | Synced local | Get single market offer |
triggerSync(ctx, { connectionId }) |
Action | Live API | Manually trigger data sync |
updateSettings(ctx, { syncOrdersEnabled?, syncOffersEnabled?, syncMarketOffersEnabled? }) |
Mutation | Local write | Update sync toggles |
enableAllSync(ctx) |
Mutation | Local write | Enable all sync toggles |
disableAllSync(ctx) |
Mutation | Local write | Disable all sync toggles |
listMarkets(ctx, { organizationId }) |
Action | Live API | List all markets |
getMarket(ctx, { organizationId, marketId }) |
Action | Live API | Get market by code |
listCurrencies(ctx, { organizationId }) |
Action | Live API | List all currencies |
getCurrency(ctx, { organizationId, currencyId }) |
Action | Live API | Get currency by code |
getInstance(ctx, { organizationId, instanceId }) |
Action | Live API | Get instance by ID |
listShippingProfiles(ctx, { organizationId }) |
Action | Live API | List shipping profiles |
getCatalogTemplates(ctx, { organizationId }) |
Action | Live API | Get catalog templates |
Sync toggles are per-connection boolean flags that control which entity types are synced by the cron jobs:
| Toggle | Default | Cron Interval | Entities Synced |
|---|---|---|---|
syncOrdersEnabled |
true |
5 minutes | Orders + Order Items (coupled) |
syncOffersEnabled |
true |
5 minutes | Offers |
syncMarketOffersEnabled |
true |
5 minutes | Market Offers |
- Toggles can be changed at runtime via
updateSettings,enableAllSync, ordisableAllSync— no redeployment needed. - Each entity type syncs independently — a failure in one does not prevent the others from completing.
- On auth failure (401/403), the connection is flagged with
syncErrorand sync stops for that entity. UseupdateConnectionwith a new API key to clear the error and resume.
For advanced use cases, you can call component functions directly instead of using the Refurbed class:
import { query, mutation, action } from "./_generated/server";
import { components } from "./_generated/api";
import { v } from "convex/values";
// Query — use ctx.runQuery
export const listOrders = query({
args: { organizationId: v.string() },
returns: v.any(),
handler: async (ctx, args) => {
return await ctx.runQuery(
components.refurbed.public.listOrders,
args,
);
},
});
// Mutation — use ctx.runMutation
export const createConnection = mutation({
args: { organizationId: v.string(), apiKey: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
return await ctx.runMutation(
components.refurbed.public.createConnection,
args,
);
},
});
// Action — use ctx.runAction
export const triggerSync = action({
args: { connectionId: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
return await ctx.runAction(
components.refurbed.public.triggerSync,
args,
);
},
});All public functions are available under components.refurbed.public.*. Use ctx.runQuery for queries, ctx.runMutation for mutations, and ctx.runAction for actions.