import {
clampInt,
nodesFromConnection,
pageInfoFromConnection,
shopifyGraphql,
trimString,
} from './lib/client.ts'
import { PAGE_INFO_FIELDS } from './lib/fragments.ts'
import { toGid } from './lib/shop.ts'
import type { ShopifyClientOptions, ShopifyPageInfo } from './types.ts'
export type CollectionSummary = {
id: string
title: string
handle: string | null
updatedAt: string | null
sortOrder: string | null
productsCount: number | null
}
export type ListCollectionsParams = ShopifyClientOptions & {
query?: string
first?: number
after?: string
}
function mapCollection(node: Record<string, unknown>): CollectionSummary {
const count =
node.productsCount && typeof node.productsCount === 'object'
? (node.productsCount as { count?: unknown }).count
: node.productsCount
return {
id: String(node.id || ''),
title: String(node.title || ''),
handle: typeof node.handle === 'string' ? node.handle : null,
updatedAt: typeof node.updatedAt === 'string' ? node.updatedAt : null,
sortOrder: typeof node.sortOrder === 'string' ? node.sortOrder : null,
productsCount: typeof count === 'number' ? count : null,
}
}
/**
* List collections (custom and smart).
*
* @example
* import { listCollections } from 'kody:@kody/shopify/collections'
* const { items } = await listCollections({ shop: 'acme', first: 20 })
*/
export async function listCollections(params: ListCollectionsParams = {}) {
const first = clampInt(params.first, 1, 50, 20)
const data = await shopifyGraphql<{ collections: unknown }>({
shop: params.shop,
apiVersion: params.apiVersion,
query: `query ListCollections($first: Int!, $after: String, $query: String) {
collections(first: $first, after: $after, query: $query) {
${PAGE_INFO_FIELDS}
nodes {
id
title
handle
updatedAt
sortOrder
productsCount { count }
}
}
}`,
variables: {
first,
after: trimString(params.after) || null,
query: trimString(params.query) || null,
},
})
return {
items: nodesFromConnection<Record<string, unknown>>(data.collections).map(
mapCollection,
),
pageInfo: pageInfoFromConnection(data.collections) as ShopifyPageInfo,
}
}
/**
* Get one collection and a page of its products.
*/
export async function getCollection(
params: ShopifyClientOptions & {
id: string
productFirst?: number
},
) {
const id = toGid('Collection', params.id)
const productFirst = clampInt(params.productFirst, 1, 50, 10)
const data = await shopifyGraphql<{
collection: (Record<string, unknown> & { products?: unknown }) | null
}>({
shop: params.shop,
apiVersion: params.apiVersion,
query: `query GetCollection($id: ID!, $productFirst: Int!) {
collection(id: $id) {
id
title
handle
updatedAt
sortOrder
description
productsCount { count }
products(first: $productFirst) {
nodes { id title handle status }
}
}
}`,
variables: { id, productFirst },
})
if (!data.collection) throw new Error(`Collection not found: ${id}`)
const collection = mapCollection(data.collection)
return {
...collection,
description:
typeof data.collection.description === 'string'
? data.collection.description
: null,
products: nodesFromConnection<Record<string, unknown>>(
data.collection.products,
).map((node) => ({
id: String(node.id || ''),
title: String(node.title || ''),
handle: typeof node.handle === 'string' ? node.handle : null,
status: typeof node.status === 'string' ? node.status : null,
})),
}
}
/** Default export: list collections. */
export default listCollections