@nativesquare/refurbed
v0.1.0
Published
A refurbed component for Convex.
Readme
@nativesquare/refurbed
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.
Installation
npm install @nativesquare/refurbedQuick Start
1. Register the component
Create (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;2. Instantiate the client
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);3. Create a connection and start syncing
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.
Connection Management
Connections link a Refurbed merchant account to your Convex app. Each connection is scoped by organizationId for multi-tenant isolation.
Create a connection
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.
List connections
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.
Update a connection (credential rotation)
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.
Delete a connection
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.
Querying Synced Data
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.
Orders
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",
});Order Items
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",
});Offers
const offers = await refurbed.listOffers(ctx, {
organizationId: "org-acme",
status: "ACTIVE",
limit: 20,
});
const offer = await refurbed.getOffer(ctx, {
organizationId: "org-acme",
refurbedId: "42",
});Market Offers
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"
});Pass-Through Reference Data
These methods call the live Refurbed API in real time (no local caching). They require an ActionCtx — use them inside Convex action handlers.
Markets & Currencies
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",
});Instances, Shipping Profiles & Catalog Templates
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",
});Sync Control
Control which entity types are synced without redeployment.
Update individual toggles
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,
});Bulk enable / disable
await refurbed.enableAllSync(ctx); // Enable all sync toggles on all connections
await refurbed.disableAllSync(ctx); // Disable all sync toggles on all connectionsManual sync trigger
import { 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.
API Reference
| 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 |
Configuration Reference
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.
Direct Component Access
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.
