TypeScript SDK
Complete TypeScript examples for integrating with Lorn AI.
Installation
npm install node-fetch # or use built-in fetch in Node 18+Setup
// lorn-client.ts
const LORN_BASE_URL = process.env.LORN_BASE_URL || "https://{{YOUR_STORE_URL}}";
const LORN_API_KEY = process.env.LORN_API_KEY || "{{YOUR_API_KEY}}";
// Types
interface Product {
id: string;
title: string;
description: string;
vendor?: string;
category?: string;
price: number;
currency: string;
availability?: {
status: string;
quantity: number;
};
tags: string[];
images: Array<{ url: string; alt_text?: string }>;
variants: Variant[];
similarity_score?: number;
}
interface Variant {
sku: string;
title?: string;
price: number;
currency: string;
attributes: Record<string, string>;
inventory?: {
status: string;
quantity: number;
};
}
interface SearchResponse {
items: Product[];
page: number;
page_size: number;
total: number;
}
interface Address {
name?: string;
line1?: string;
line2?: string;
city?: string;
state?: string;
postal_code?: string;
country?: string;
}
interface Customer {
email?: string;
phone?: string;
name?: string;
}
interface LineItem {
product_id: string;
variant_sku?: string;
quantity: number;
title?: string;
unit_price: number;
currency: string;
subtotal: number;
}
interface CheckoutSession {
id: string;
status: string;
currency: string;
line_items: LineItem[];
shipping_address?: Address;
customer?: Customer;
shipping: { method: string; amount: number; currency: string };
tax: { rate: number; amount: number; currency: string };
amounts: {
subtotal: number;
tax: number;
shipping: number;
total: number;
currency: string;
};
client_secret: string;
created_at: string;
}
interface CheckoutResponse {
checkout_session: CheckoutSession;
}
// Client class
class LornClient {
private baseUrl: string;
private apiKey: string;
constructor(baseUrl?: string, apiKey?: string) {
this.baseUrl = baseUrl || LORN_BASE_URL;
this.apiKey = apiKey || LORN_API_KEY;
}
private async request<T>(
method: string,
endpoint: string,
options: { params?: Record<string, any>; body?: any } = {}
): Promise<T> {
let url = `${this.baseUrl}${endpoint}`;
if (options.params) {
const searchParams = new URLSearchParams();
Object.entries(options.params).forEach(([key, value]) => {
if (value !== undefined) {
searchParams.append(key, String(value));
}
});
url += `?${searchParams}`;
}
const response = await fetch(url, {
method,
headers: {
"X-ACP-API-Key": this.apiKey,
"Content-Type": "application/json",
Accept: "application/json",
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
// Health
async health(): Promise<{ status: string; supabase: string }> {
return this.request("GET", "/health");
}
// Products
async searchProducts(options: {
q?: string;
category?: string;
tag?: string;
min_price?: number;
max_price?: number;
page?: number;
page_size?: number;
} = {}): Promise<SearchResponse> {
return this.request("GET", "/acp/products", { params: options });
}
async getProduct(productId: string): Promise<Product> {
return this.request("GET", `/acp/products/${productId}`);
}
// Checkout
async createCheckout(options: {
items: Array<{ product_id: string; variant_sku?: string; quantity?: number }>;
shipping_address?: Address;
customer?: Customer;
}): Promise<CheckoutResponse> {
return this.request("POST", "/checkout_sessions", { body: options });
}
async getCheckout(sessionId: string): Promise<CheckoutResponse> {
return this.request("GET", `/checkout_sessions/${sessionId}`);
}
async updateCheckout(
sessionId: string,
updates: {
items?: Array<{ product_id: string; variant_sku?: string; quantity?: number }>;
shipping_address?: Address;
customer?: Customer;
}
): Promise<CheckoutResponse> {
return this.request("PATCH", `/checkout_sessions/${sessionId}`, { body: updates });
}
async completeCheckout(sessionId: string): Promise<CheckoutResponse> {
return this.request("POST", `/checkout_sessions/${sessionId}/complete`);
}
async cancelCheckout(sessionId: string): Promise<CheckoutResponse> {
return this.request("POST", `/checkout_sessions/${sessionId}/cancel`);
}
// Webhooks
async emitWebhook(options: {
event_type: string;
checkout_session_id?: string;
target_url?: string;
payload?: Record<string, any>;
}): Promise<{ delivered: number | null; signature: string; payload: any }> {
return this.request("POST", "/webhooks/emit", { body: options });
}
}
export const client = new LornClient();
export { LornClient, Product, Variant, CheckoutSession, CheckoutResponse };Usage Examples
Search Products
import { client } from "./lorn-client";
// Basic search
const results = await client.searchProducts({ q: "comfortable running shoes" });
for (const product of results.items) {
console.log(`${product.title} - $${product.price}`);
if (product.similarity_score) {
console.log(` Relevance: ${product.similarity_score.toFixed(2)}`);
}
}
// With filters
const filtered = await client.searchProducts({
q: "wireless headphones",
min_price: 50,
max_price: 200,
page_size: 5,
});
// Category browse
const electronics = await client.searchProducts({
category: "Electronics",
page_size: 20,
});Get Product Details
const product = await client.getProduct("prod_abc123");
console.log(`Title: ${product.title}`);
console.log(`Price: $${product.price}`);
console.log(`Description: ${product.description}`);
// Show variants
for (const variant of product.variants) {
const attrs = Object.entries(variant.attributes)
.map(([k, v]) => `${k}: ${v}`)
.join(", ");
console.log(` - ${variant.sku}: ${attrs} - $${variant.price}`);
}Create Checkout
// Simple checkout
const session = await client.createCheckout({
items: [{ product_id: "prod_abc123", quantity: 1 }],
});
console.log(`Session ID: ${session.checkout_session.id}`);
console.log(`Total: $${session.checkout_session.amounts.total}`);
// With full details
const fullSession = await client.createCheckout({
items: [
{
product_id: "prod_abc123",
variant_sku: "sku_black_m",
quantity: 2,
},
],
shipping_address: {
name: "Jane Doe",
line1: "123 Main Street",
city: "San Francisco",
state: "CA",
postal_code: "94102",
country: "US",
},
customer: {
email: "jane@example.com",
name: "Jane Doe",
},
});Update Checkout
// Update shipping address
const updated = await client.updateCheckout("cs_demo_abc123", {
shipping_address: {
name: "Jane Doe",
line1: "456 Oak Avenue",
city: "Los Angeles",
state: "CA",
postal_code: "90001",
country: "US",
},
});
// Update cart items
const cartUpdated = await client.updateCheckout("cs_demo_abc123", {
items: [{ product_id: "prod_abc123", quantity: 3 }],
});Complete Checkout
const order = await client.completeCheckout("cs_demo_abc123");
console.log(`Status: ${order.checkout_session.status}`);
console.log(`Total: $${order.checkout_session.amounts.total}`);Complete Purchase Flow
async function completePurchaseFlow(): Promise<void> {
// 1. Search for products
console.log("Searching for running shoes...");
const results = await client.searchProducts({
q: "running shoes",
max_price: 150,
});
if (results.items.length === 0) {
console.log("No products found");
return;
}
const product = results.items[0];
console.log(`Found: ${product.title} - $${product.price}`);
// 2. Get product details
const productDetails = await client.getProduct(product.id);
const variant = productDetails.variants[0];
// 3. Create checkout
console.log("\nCreating checkout...");
const session = await client.createCheckout({
items: [
{
product_id: product.id,
variant_sku: variant?.sku,
quantity: 1,
},
],
shipping_address: {
name: "Jane Doe",
line1: "123 Main St",
city: "San Francisco",
state: "CA",
postal_code: "94102",
country: "US",
},
customer: { email: "jane@example.com" },
});
const { amounts } = session.checkout_session;
console.log(`Session: ${session.checkout_session.id}`);
console.log(`Subtotal: $${amounts.subtotal}`);
console.log(`Tax: $${amounts.tax}`);
console.log(`Shipping: $${amounts.shipping}`);
console.log(`Total: $${amounts.total}`);
// 4. Complete checkout
console.log("\nCompleting purchase...");
const order = await client.completeCheckout(session.checkout_session.id);
console.log("Order completed!");
console.log(`Status: ${order.checkout_session.status}`);
}
completePurchaseFlow().catch(console.error);Error Handling
async function safeApiCall(): Promise<void> {
try {
const results = await client.searchProducts({ q: "shoes" });
console.log(`Found ${results.total} products`);
} catch (error) {
if (error instanceof Error) {
if (error.message.includes("401")) {
console.error("Invalid API key");
} else if (error.message.includes("404")) {
console.error("Not found");
} else if (error.message.includes("429")) {
console.error("Rate limited - retry later");
} else {
console.error(`Error: ${error.message}`);
}
}
}
}Webhook Signature Verification
import crypto from "crypto";
function verifyWebhookSignature(
body: string,
signatureHeader: string,
secret: string,
toleranceSeconds: number = 300
): boolean {
// Parse signature header: "t=<timestamp>,v1=<signature>"
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("="))
);
const timestamp = parseInt(parts.t || "0");
const receivedSig = parts.v1 || "";
// Check timestamp freshness
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) {
return false;
}
// Compute expected signature
const expectedSig = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
// Timing-safe comparison
try {
return crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(receivedSig)
);
} catch {
return false;
}
}
// Express webhook handler example
import express from "express";
const app = express();
const WEBHOOK_SECRET = process.env.LORN_WEBHOOK_SECRET!;
app.post(
"/webhooks/lorn",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-webhook-signature"] as string;
if (!verifyWebhookSignature(req.body.toString(), signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
switch (event.type) {
case "order.created":
console.log(`New order: ${event.data.object.id}`);
break;
case "order.fulfilled":
console.log(`Order fulfilled: ${event.data.object.id}`);
break;
}
res.json({ received: true });
}
);
app.listen(3000, () => console.log("Webhook server running on port 3000"));React Hook Example
// useLornProducts.ts
import { useState, useEffect } from "react";
import { client, Product } from "./lorn-client";
interface UseProductsResult {
products: Product[];
loading: boolean;
error: Error | null;
search: (query: string) => Promise<void>;
}
export function useLornProducts(): UseProductsResult {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const search = async (query: string) => {
setLoading(true);
setError(null);
try {
const results = await client.searchProducts({ q: query });
setProducts(results.items);
} catch (e) {
setError(e instanceof Error ? e : new Error("Unknown error"));
} finally {
setLoading(false);
}
};
return { products, loading, error, search };
}
// Usage in component
function ProductSearch() {
const { products, loading, error, search } = useLornProducts();
const [query, setQuery] = useState("");
const handleSearch = () => search(query);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button onClick={handleSearch} disabled={loading}>
Search
</button>
{error && <p>Error: {error.message}</p>}
{products.map((product) => (
<div key={product.id}>
<h3>{product.title}</h3>
<p>${product.price}</p>
</div>
))}
</div>
);
}