import "server-only"; import { createCartSnapshot, inventoryStateSchema, orderSnapshotSchema, resolveInventoryAvailability, type CanonicalCartLine, type OrderItemSnapshot, } from "@salixcommerce/core"; import { PostgrestClient } from "@supabase/postgrest-js"; import { commerceFailure, type CommerceRepository, type CommerceResult } from "./repository"; interface SupabaseCommerceCredentials { readonly currency: string; readonly serviceRoleKey: string; readonly url: string; } type DatabaseRow = Record; export function createSupabaseCommerceRepository( credentials: SupabaseCommerceCredentials, ): CommerceRepository { const client = new PostgrestClient(`${credentials.url.replace(/\/$/u, "")}/rest/v1`, { headers: { apikey: credentials.serviceRoleKey, authorization: `Bearer ${credentials.serviceRoleKey}`, }, timeout: 10_000, }); async function loadCart(capabilityHash: string) { const cartResult = await client .from("carts") .select("id,currency") .eq("capability_hash", capabilityHash) .eq("status", "active") .limit(1); const cartRows = readRows(cartResult.data, cartResult.error, "cart"); const cartRow = cartRows[0]; if (cartRow === undefined) return null; const cartId = readIdentifier(cartRow, "id"); const currency = readString(cartRow, "currency"); if (currency !== credentials.currency) throw new Error("Cart currency is misconfigured."); const itemResult = await client .from("cart_items") .select("variant_id,quantity,created_at") .eq("cart_id", cartId) .order("created_at") .order("variant_id"); const itemRows = readRows(itemResult.data, itemResult.error, "cart items"); if (itemRows.length === 0) return createCartSnapshot(cartId, currency, []); const variantIds = itemRows.map((row) => readIdentifier(row, "variant_id")); const variantResult = await client .from("product_variants") .select("id,product_id,sku,name,price_minor,currency,active,allow_backorder") .in("id", variantIds); const variantRows = readRows(variantResult.data, variantResult.error, "cart variants"); const productIds = [...new Set(variantRows.map((row) => readIdentifier(row, "product_id")))]; const [productResult, inventoryResult] = await Promise.all([ client.from("products").select("id,slug,name,status,published_at").in("id", productIds), client .from("inventory_items") .select("variant_id,mode,on_hand,reserved") .in("variant_id", variantIds), ]); const productRows = readRows(productResult.data, productResult.error, "cart products"); const inventoryRows = readRows(inventoryResult.data, inventoryResult.error, "cart inventory"); const variants = indexRows(variantRows, "id"); const products = indexRows(productRows, "id"); const inventory = indexRows(inventoryRows, "variant_id"); const now = Date.now(); const lines: CanonicalCartLine[] = itemRows.map((itemRow) => { const variantId = readIdentifier(itemRow, "variant_id"); const variant = requiredIndexedRow(variants, variantId, "variant"); const product = requiredIndexedRow( products, readIdentifier(variant, "product_id"), "product", ); const inventoryRow = requiredIndexedRow(inventory, variantId, "inventory"); const inventoryState = inventoryStateSchema.parse({ mode: readString(inventoryRow, "mode"), onHand: readSafeNumber(inventoryRow, "on_hand"), reserved: readSafeNumber(inventoryRow, "reserved"), }); const availability = resolveInventoryAvailability( inventoryState, readBoolean(variant, "allow_backorder"), ); const publishedAt = readNullableString(product, "published_at"); const lifecyclePublic = readBoolean(variant, "active") && readString(product, "status") === "published" && publishedAt !== null && new Date(publishedAt).getTime() <= now; return { availability, productName: readString(product, "name"), productSlug: readString(product, "slug"), purchasable: lifecyclePublic && availability !== "out_of_stock", quantity: readSafeNumber(itemRow, "quantity"), sku: readNullableString(variant, "sku"), unitPrice: { amountMinor: readSafeNumber(variant, "price_minor"), currency: readExpectedCurrency(variant, credentials.currency), }, variantId, variantName: readString(variant, "name"), }; }); return createCartSnapshot(cartId, currency, lines); } async function loadOrder(capabilityHash: string, reference: string) { const orderResult = await client .from("orders") .select("id,reference,contact_email,status,currency,merchandise_subtotal_minor,created_at") .eq("capability_hash", capabilityHash) .eq("reference", reference) .limit(1); const orderRows = readRows(orderResult.data, orderResult.error, "order"); const orderRow = orderRows[0]; if (orderRow === undefined) return null; const orderId = readIdentifier(orderRow, "id"); const itemResult = await client .from("order_items") .select( "source_variant_id,product_name,product_slug,variant_name,sku,quantity,unit_price_minor,line_total_minor,currency,inventory_mode,reserved_quantity,backordered_quantity,position", ) .eq("order_id", orderId) .order("position"); const itemRows = readRows(itemResult.data, itemResult.error, "order items"); const items: OrderItemSnapshot[] = itemRows.map((row) => ({ backorderedQuantity: readSafeNumber(row, "backordered_quantity"), inventoryMode: readInventoryMode(row), lineTotal: { amountMinor: readSafeNumber(row, "line_total_minor"), currency: readExpectedCurrency(row, credentials.currency), }, productName: readString(row, "product_name"), productSlug: readString(row, "product_slug"), quantity: readSafeNumber(row, "quantity"), reservedQuantity: readSafeNumber(row, "reserved_quantity"), sku: readNullableString(row, "sku"), sourceVariantId: readIdentifier(row, "source_variant_id"), unitPrice: { amountMinor: readSafeNumber(row, "unit_price_minor"), currency: readExpectedCurrency(row, credentials.currency), }, variantName: readString(row, "variant_name"), })); return orderSnapshotSchema.parse({ contactEmail: readString(orderRow, "contact_email"), createdAt: readString(orderRow, "created_at"), currency: readExpectedCurrency(orderRow, credentials.currency), id: orderId, items, merchandiseSubtotal: { amountMinor: readSafeNumber(orderRow, "merchandise_subtotal_minor"), currency: credentials.currency, }, reference: readString(orderRow, "reference"), status: readString(orderRow, "status"), }); } const repository = { async cancelOrder(input) { const result = await client.rpc("salix_cancel_order", { p_capability_hash: input.capabilityHash, p_idempotency_key: input.idempotencyKey, p_order_reference: input.reference, p_reason: input.reason, p_request_hash: input.requestHash, }); const rpc = readRpcResult(result.data, result.error); if (!rpc.ok) return { error: rpc.error, ok: false }; const order = await loadOrder(input.capabilityHash, readString(rpc.data, "order_reference")); if (order === null) return providerFailure(); return { data: { order, replayed: readBoolean(rpc.data, "replayed") }, ok: true }; }, async createOrder(input) { const result = await client.rpc("salix_create_order", { p_capability_hash: input.capabilityHash, p_contact_email: input.contactEmail, p_idempotency_key: input.idempotencyKey, p_request_hash: input.requestHash, }); const rpc = readRpcResult(result.data, result.error); if (!rpc.ok) return { error: rpc.error, ok: false }; const order = await loadOrder(input.capabilityHash, readString(rpc.data, "order_reference")); if (order === null) return providerFailure(); return { data: { order, replayed: readBoolean(rpc.data, "replayed") }, ok: true }; }, loadCart, loadOrder, async mutateCart(input) { const result = await client.rpc("salix_mutate_cart", { p_capability_hash: input.capabilityHash, p_currency: credentials.currency, p_idempotency_key: input.command.idempotencyKey, p_operation: input.command.operation, p_quantity: input.command.operation === "remove" ? null : input.command.quantity, p_request_hash: input.requestHash, p_variant_id: input.command.variantId, }); const rpc = readRpcResult(result.data, result.error); if (!rpc.ok) return { error: rpc.error, ok: false }; const cart = await loadCart(input.capabilityHash); if (cart === null) return providerFailure(); return { data: { cart, replayed: readBoolean(rpc.data, "replayed") }, ok: true }; }, } satisfies CommerceRepository; return Object.freeze(repository); } function readRpcResult(data: unknown, error: unknown): CommerceResult { if (error !== null) return { error: mapProviderError(error), ok: false }; if (!Array.isArray(data) || data.length !== 1 || !isDatabaseRow(data[0])) { return providerFailure(); } return { data: data[0], ok: true }; } function mapProviderError(error: unknown) { const message = isDatabaseRow(error) && typeof error.message === "string" ? error.message : ""; const known = { SALIX_CART_CLOSED: commerceFailure("CART_CLOSED", "This cart is no longer active.", 409), SALIX_EMPTY_CART: commerceFailure("EMPTY_CART", "Add an item before creating an order.", 409), SALIX_IDEMPOTENCY_CONFLICT: commerceFailure( "IDEMPOTENCY_CONFLICT", "That request key was already used for different input.", 409, ), SALIX_ILLEGAL_ORDER_TRANSITION: commerceFailure( "ILLEGAL_ORDER_TRANSITION", "This order can no longer be cancelled.", 409, ), SALIX_INSUFFICIENT_INVENTORY: commerceFailure( "INSUFFICIENT_INVENTORY", "The requested quantity is no longer available.", 409, ), SALIX_ORDER_NOT_FOUND: commerceFailure("ORDER_NOT_FOUND", "Order not found.", 404), SALIX_PAYMENT_ALREADY_PENDING: commerceFailure( "PAYMENT_ALREADY_PENDING", "Finish or cancel the payment attempt before cancelling this order.", 409, ), SALIX_VARIANT_UNAVAILABLE: commerceFailure( "VARIANT_UNAVAILABLE", "This product option is no longer available.", 409, ), } as const; if (message in known) return known[message as keyof typeof known]; if (message.startsWith("SALIX_INVALID_")) { return commerceFailure("INVALID_REQUEST", "The commerce request is invalid.", 400); } return commerceFailure( "PROVIDER_FAILURE", "The commerce service could not complete the request.", 500, ); } function providerFailure(): CommerceResult { return { error: commerceFailure( "PROVIDER_FAILURE", "The commerce service returned an invalid response.", 500, ), ok: false, }; } function readRows(data: unknown, error: unknown, label: string): DatabaseRow[] { if (error !== null) throw new Error(`Unable to load ${label} from the commerce provider.`); if (!Array.isArray(data) || !data.every(isDatabaseRow)) { throw new TypeError(`Commerce provider returned invalid ${label}.`); } return data; } function isDatabaseRow(value: unknown): value is DatabaseRow { return typeof value === "object" && value !== null && !Array.isArray(value); } function indexRows(rows: readonly DatabaseRow[], key: string): Map { return new Map(rows.map((row) => [readIdentifier(row, key), row])); } function requiredIndexedRow( rows: ReadonlyMap, identifier: string, label: string, ): DatabaseRow { const row = rows.get(identifier); if (row === undefined) throw new Error(`Commerce data is missing ${label} ${identifier}.`); return row; } function readString(row: DatabaseRow, key: string): string { const value = row[key]; if (typeof value !== "string") throw new TypeError(`Commerce field ${key} must be a string.`); return value; } function readNullableString(row: DatabaseRow, key: string): string | null { const value = row[key]; if (value === null) return null; if (typeof value !== "string") { throw new TypeError(`Commerce field ${key} must be a string or null.`); } return value; } function readBoolean(row: DatabaseRow, key: string): boolean { const value = row[key]; if (typeof value !== "boolean") throw new TypeError(`Commerce field ${key} must be boolean.`); return value; } function readIdentifier(row: DatabaseRow, key: string): string { const value = row[key]; if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return String(value); if (typeof value === "string" && /^[1-9][0-9]*$/u.test(value)) return value; throw new TypeError(`Commerce field ${key} must be a positive safe identifier.`); } function readSafeNumber(row: DatabaseRow, key: string): number { const value = row[key]; const parsed = typeof value === "string" && /^-?[0-9]+$/u.test(value) ? Number(value) : value; if (typeof parsed !== "number" || !Number.isSafeInteger(parsed)) { throw new TypeError(`Commerce field ${key} must be a safe integer.`); } return parsed; } function readExpectedCurrency(row: DatabaseRow, currency: string): string { const actual = readString(row, "currency"); if (actual !== currency) throw new Error(`Commerce currency must be ${currency}.`); return actual; } function readInventoryMode(row: DatabaseRow): "tracked" | "untracked" { const mode = readString(row, "inventory_mode"); if (mode === "tracked" || mode === "untracked") return mode; throw new TypeError("Commerce inventory mode is invalid."); }