import { assertNoUserErrors, 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 WebhookSummary = { id: string topic: string | null uri: string | null format: string | null createdAt: string | null } function mapWebhook(node: Record): WebhookSummary { return { id: String(node.id || ''), topic: typeof node.topic === 'string' ? node.topic : null, uri: typeof node.uri === 'string' ? node.uri : null, format: typeof node.format === 'string' ? node.format : null, createdAt: typeof node.createdAt === 'string' ? node.createdAt : null, } } /** * List webhook subscriptions for the current app. * * @example * import { listWebhooks } from 'kody:@kody/shopify/webhooks' * const { items } = await listWebhooks({ shop: 'acme' }) */ export async function listWebhooks( params: ShopifyClientOptions & { first?: number; after?: string } = {}, ) { const first = clampInt(params.first, 1, 50, 20) const data = await shopifyGraphql<{ webhookSubscriptions: unknown }>({ shop: params.shop, apiVersion: params.apiVersion, query: `query ListWebhooks($first: Int!, $after: String) { webhookSubscriptions(first: $first, after: $after) { ${PAGE_INFO_FIELDS} nodes { id topic uri format createdAt } } }`, variables: { first, after: trimString(params.after) || null, }, }) return { items: nodesFromConnection>( data.webhookSubscriptions, ).map(mapWebhook), pageInfo: pageInfoFromConnection( data.webhookSubscriptions, ) as ShopifyPageInfo, } } export type CreateWebhookParams = ShopifyClientOptions & { /** GraphQL enum, e.g. `ORDERS_CREATE` or `PRODUCTS_UPDATE`. */ topic: string uri: string format?: 'JSON' | 'XML' dryRun?: boolean } /** * Create a webhook subscription. Defaults to dry-run. * * Shopify must be able to POST to `uri`. After you fork, pair this with a * Kody package webhook if you want inbound events in your account. */ export async function createWebhook(params: CreateWebhookParams) { const topic = trimString(params.topic).toUpperCase() const uri = trimString(params.uri) if (!topic) throw new Error('topic is required (e.g. ORDERS_CREATE).') if (!uri) throw new Error('uri is required.') const webhookSubscription = { callbackUrl: uri, format: params.format || 'JSON', } if (params.dryRun !== false) { return { dryRun: true as const, wouldCall: { mutation: 'webhookSubscriptionCreate', topic, webhookSubscription, }, } } const data = await shopifyGraphql<{ webhookSubscriptionCreate: unknown }>({ shop: params.shop, apiVersion: params.apiVersion, query: `mutation CreateWebhook($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) { webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) { webhookSubscription { id topic uri format createdAt } userErrors { field message } } }`, variables: { topic, webhookSubscription }, }) assertNoUserErrors(data.webhookSubscriptionCreate, 'webhookSubscriptionCreate') const created = ( data.webhookSubscriptionCreate as { webhookSubscription?: Record } ).webhookSubscription if (!created) { throw new Error('webhookSubscriptionCreate returned no subscription.') } return mapWebhook(created) } /** * Delete a webhook subscription. Defaults to dry-run. */ export async function deleteWebhook( params: ShopifyClientOptions & { id: string; dryRun?: boolean }, ) { const id = toGid('WebhookSubscription', params.id) if (params.dryRun !== false) { return { dryRun: true as const, wouldCall: { mutation: 'webhookSubscriptionDelete', id }, } } const data = await shopifyGraphql<{ webhookSubscriptionDelete: unknown }>({ shop: params.shop, apiVersion: params.apiVersion, query: `mutation DeleteWebhook($id: ID!) { webhookSubscriptionDelete(id: $id) { deletedWebhookSubscriptionId userErrors { field message } } }`, variables: { id }, }) assertNoUserErrors(data.webhookSubscriptionDelete, 'webhookSubscriptionDelete') return data.webhookSubscriptionDelete } /** Default export: list webhook subscriptions. */ export default listWebhooks