import {
clampInt,
moneyFromSet,
nodesFromConnection,
pageInfoFromConnection,
shopifyGraphql,
trimString,
} from './lib/client.ts'
import { ORDER_FIELDS, PAGE_INFO_FIELDS } from './lib/fragments.ts'
import { toGid } from './lib/shop.ts'
import type { Money, ShopifyClientOptions, ShopifyPageInfo } from './types.ts'
export type OrderLineItem = {
id: string
title: string | null
sku: string | null
quantity: number | null
originalUnitPrice: Money | null
}
export type OrderSummary = {
id: string
name: string | null
email: string | null
createdAt: string | null
updatedAt: string | null
displayFinancialStatus: string | null
displayFulfillmentStatus: string | null
note: string | null
tags: Array<string>
currentTotalPrice: Money | null
customer: {
id: string
displayName: string | null
email: string | null
} | null
lineItems: Array<OrderLineItem>
}
export type ListOrdersParams = ShopifyClientOptions & {
/** Shopify search query, e.g. `financial_status:paid fulfillment_status:unfulfilled`. */
query?: string
first?: number
after?: string
}
function mapLineItem(node: Record<string, unknown>): OrderLineItem {
return {
id: String(node.id || ''),
title: typeof node.title === 'string' ? node.title : null,
sku: typeof node.sku === 'string' ? node.sku : null,
quantity: typeof node.quantity === 'number' ? node.quantity : null,
originalUnitPrice: moneyFromSet(node.originalUnitPriceSet),
}
}
function mapOrder(node: Record<string, unknown>): OrderSummary {
const customer =
node.customer && typeof node.customer === 'object'
? (node.customer as Record<string, unknown>)
: null
return {
id: String(node.id || ''),
name: typeof node.name === 'string' ? node.name : null,
email: typeof node.email === 'string' ? node.email : null,
createdAt: typeof node.createdAt === 'string' ? node.createdAt : null,
updatedAt: typeof node.updatedAt === 'string' ? node.updatedAt : null,
displayFinancialStatus:
typeof node.displayFinancialStatus === 'string'
? node.displayFinancialStatus
: null,
displayFulfillmentStatus:
typeof node.displayFulfillmentStatus === 'string'
? node.displayFulfillmentStatus
: null,
note: typeof node.note === 'string' ? node.note : null,
tags: Array.isArray(node.tags) ? node.tags.map(String) : [],
currentTotalPrice: moneyFromSet(node.currentTotalPriceSet),
customer: customer
? {
id: String(customer.id || ''),
displayName:
typeof customer.displayName === 'string'
? customer.displayName
: null,
email: typeof customer.email === 'string' ? customer.email : null,
}
: null,
lineItems: nodesFromConnection<Record<string, unknown>>(node.lineItems).map(
mapLineItem,
),
}
}
/**
* List orders with optional Shopify search syntax.
*
* @example
* import { listOrders } from 'kody:@kody/shopify/orders'
* const { items } = await listOrders({
* shop: 'acme',
* query: 'fulfillment_status:unfulfilled',
* first: 10,
* })
*/
export async function listOrders(params: ListOrdersParams = {}) {
const first = clampInt(params.first, 1, 50, 20)
const data = await shopifyGraphql<{ orders: unknown }>({
shop: params.shop,
apiVersion: params.apiVersion,
query: `query ListOrders($first: Int!, $after: String, $query: String) {
orders(first: $first, after: $after, query: $query, sortKey: CREATED_AT, reverse: true) {
${PAGE_INFO_FIELDS}
nodes { ${ORDER_FIELDS} }
}
}`,
variables: {
first,
after: trimString(params.after) || null,
query: trimString(params.query) || null,
},
})
return {
items: nodesFromConnection<Record<string, unknown>>(data.orders).map(
mapOrder,
),
pageInfo: pageInfoFromConnection(data.orders) as ShopifyPageInfo,
}
}
/**
* Get one order by GID or numeric id.
*/
export async function getOrder(
params: ShopifyClientOptions & { id: string },
): Promise<OrderSummary> {
const id = toGid('Order', params.id)
const data = await shopifyGraphql<{ order: Record<string, unknown> | null }>({
shop: params.shop,
apiVersion: params.apiVersion,
query: `query GetOrder($id: ID!) {
order(id: $id) { ${ORDER_FIELDS} }
}`,
variables: { id },
})
if (!data.order) throw new Error(`Order not found: ${id}`)
return mapOrder(data.order)
}
/** Default export: list orders. */
export default listOrders