> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Products API

> Manage e-commerce product catalog

## Overview

The Products API provides complete CRUD operations for managing an e-commerce product catalog. Returns **ProductInstance** objects with direct property access.

```typescript theme={null}
import { Products } from 'lua-cli';

// Search products - returns ProductSearchInstance
const results = await Products.search('laptop');

// Direct property access on results
const names = results.map(p => p.name);
const prices = results.map(p => p.price);

// Create product - returns ProductInstance
const product = await Products.create({
  name: 'MacBook Pro',
  price: 1999.99
});

// Direct property access
console.log(product.name);   // "MacBook Pro"
console.log(product.price);  // 1999.99

// Instance methods
await product.update({ price: 1799.99 });
await product.save();
await product.delete();
```

<CardGroup cols={2}>
  <Card title="Direct Access" icon="bolt">
    Access properties with `product.name` not `product.data.name`
  </Card>

  <Card title="Array Methods" icon="list">
    Use `.map()`, `.filter()` on search results
  </Card>

  <Card title="Instance Methods" icon="wrench">
    Built-in `update()`, `save()`, and `delete()`
  </Card>

  <Card title="Iteration" icon="arrows-rotate">
    Use `for...of` loops
  </Card>
</CardGroup>

## Return Shape Reference

<Warning>
  **`Products.search()` and `Products.get()` do NOT return arrays with a `.data` property.** They return instance wrappers that are directly iterable.
</Warning>

| Method                                                                 | Returns                                                                           | How to use                                                                        |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `Products.search(q, limit?)`                                           | `ProductSearchInstance` — iterable, has `.products`, `.length`                    | `results.map(p => p.name)`, `results.products`, `results.length` — **no `.data`** |
| `Products.get(page?, limit?)` or `Products.get({page, limit, filter})` | `ProductPaginationInstance` — iterable, has `.products`, `.pagination`, `.length` | `results.map(p => p.name)`, `results.pagination.totalPages` — **no `.data`**      |
| `Products.create(product)`                                             | `ProductInstance`                                                                 | `product.name`, `product.price`, `await product.update({...})`                    |
| `Products.getById(id)`                                                 | `ProductInstance`                                                                 | Direct property access                                                            |
| `Products.update(data, id)`                                            | `UpdateProductResponse`                                                           | `{ product }`                                                                     |
| `Products.delete(id)`                                                  | `DeleteProductResponse`                                                           | `{ success }`                                                                     |

**Quick examples:**

```typescript theme={null}
// ✅ Products.search → ProductSearchInstance (iterable)
const results = await Products.search('laptop');
results.forEach(p => console.log(p.name, p.price));  // iterate directly
const names = results.map(p => p.name);              // array methods work
console.log(results.length);                          // ✅ count
// ❌ results.data   → does not exist

// ✅ Products.get → ProductPaginationInstance
const page = await Products.get({ page: 1, limit: 20, filter: { inStock: true } });
page.map(p => p.name);                               // iterate directly
console.log(page.pagination.totalPages);             // ✅ pagination
// ❌ page.data      → does not exist
```

## Methods

### search()

Search products by name or description using semantic search.

```typescript theme={null}
Products.search(query: string): Promise<ProductSearchInstance>
```

<ParamField path="query" type="string" required>
  Search query to match against product names and descriptions
</ParamField>

**Returns:** `ProductSearchInstance` - Array-like collection with direct access to products via `.products` property

**Example:**

```typescript theme={null}
// Search with default limit (5)
const results = await Products.search('laptop');

// Search with custom limit
const moreResults = await Products.search('laptop', 10);

// Direct array methods
results.forEach(product => {
  console.log(`${product.name} - $${product.price}`);
});

// Or use .map()
const names = results.map(p => p.name);
const prices = results.map(p => p.price);

// Filter
const affordable = results.filter(p => p.price < 1000);

// Find
const specific = results.find(p => p.sku === 'LAP-001');

// Check length
console.log(`Found ${results.length} products`);

// For...of iteration
for (const product of results) {
  console.log(product.name);
}
```

### get()

Retrieve products with pagination and optional filtering.

```typescript theme={null}
// Simple pagination (backward compatible)
Products.get(page?: number, limit?: number): Promise<ProductPaginationInstance>

// With filter options (recommended)
Products.get(options?: ProductFilterOptions): Promise<ProductPaginationInstance>
```

**ProductFilterOptions:**

```typescript theme={null}
interface ProductFilterOptions {
  page?: number;      // Page number (1-indexed), default: 1
  limit?: number;     // Items per page, default: 10
  filter?: object;    // MongoDB-style filter for product data
}
```

<ParamField path="page" type="number" default={1}>
  Page number (1-indexed)
</ParamField>

<ParamField path="limit" type="number" default={10}>
  Number of products per page
</ParamField>

<ParamField path="filter" type="object">
  MongoDB-style filter object to query product data fields
</ParamField>

**Returns:** `ProductPaginationInstance` - Array-like collection with pagination support

**Examples:**

```typescript theme={null}
// Simple pagination (backward compatible)
const page = await Products.get(1, 20);

// Using options object (recommended)
const page = await Products.get({ page: 1, limit: 20 });

// With filters - exact match
const electronics = await Products.get({
  page: 1,
  limit: 20,
  filter: { category: 'Electronics' }
});

// With filters - price range
const affordable = await Products.get({
  filter: {
    price: { $lte: 100 }
  }
});

// With filters - multiple conditions
const inStockLaptops = await Products.get({
  filter: {
    category: 'Laptops',
    inStock: true,
    price: { $gte: 500, $lte: 1500 }
  }
});

// Direct array methods
page.forEach(product => {
  console.log(`${product.name} - $${product.price}`);
});

// Access pagination info
console.log(`Page ${page.pagination.currentPage} of ${page.pagination.totalPages}`);
console.log(`Showing ${page.length} of ${page.pagination.totalCount} products`);

// Navigate pages
if (page.pagination.hasNextPage) {
  const nextPage = await page.nextPage();
}

if (page.pagination.hasPrevPage) {
  const prevPage = await page.prevPage();
}

// Use array methods
const names = page.map(p => p.name);
const inStock = page.filter(p => p.inStock);
```

**Filter Operators:**

The filter supports MongoDB-style operators:

```typescript theme={null}
// Comparison
{ price: { $eq: 99.99 } }      // Equal
{ price: { $ne: 99.99 } }      // Not equal
{ price: { $gt: 50 } }         // Greater than
{ price: { $gte: 50 } }        // Greater than or equal
{ price: { $lt: 100 } }        // Less than
{ price: { $lte: 100 } }       // Less than or equal

// Array matching
{ category: { $in: ['Electronics', 'Computers'] } }

// Nested fields (dot notation)
{ 'specs.color': 'black' }
{ 'metadata.brand': 'Apple' }
```

### getById()

Get a specific product by ID.

```typescript theme={null}
Products.getById(id: string): Promise<ProductInstance>
```

**Returns:** `ProductInstance` with direct property access and methods

**Example:**

```typescript theme={null}
const product = await Products.getById('product_abc123');

// Direct property access
console.log(product.name);   // "MacBook Pro"
console.log(product.price);  // 1999.99
console.log(product.sku);    // "MBP-14"

// Instance methods
await product.update({ price: 1799.99 });
await product.delete();
```

### create()

Create a new product.

```typescript theme={null}
Products.create(product: Product): Promise<ProductInstance>
```

<ParamField path="product.name" type="string" required>
  Product name
</ParamField>

<ParamField path="product.price" type="number" required>
  Product price
</ParamField>

<ParamField path="product.category" type="string">
  Product category
</ParamField>

<ParamField path="product.sku" type="string">
  Stock keeping unit
</ParamField>

<ParamField path="product.inStock" type="boolean" default={true}>
  Whether product is in stock
</ParamField>

<ParamField path="product.description" type="string">
  Product description
</ParamField>

**Returns:** `ProductInstance` with direct property access and methods

**Example:**

```typescript theme={null}
const product = await Products.create({
  name: 'Wireless Mouse',
  price: 29.99,
  category: 'Electronics',
  sku: 'MOUSE-001',
  inStock: true,
  description: 'Ergonomic wireless mouse'
});

// Direct property access
console.log(product.id);      // "product_xyz789"
console.log(product.name);    // "Wireless Mouse"
console.log(product.price);   // 29.99

// Instance methods available
await product.update({ price: 24.99 });
```

### update() (Instance Method)

Update an existing product via an instance. Use `product.update()` on a retrieved product — there is no static `Products.update()` method.

```typescript theme={null}
product.update(data: Record<string, any>): Promise<Product>
```

<ParamField path="data" type="object" required>
  Partial product data to update
</ParamField>

**Example:**

```typescript theme={null}
const product = await Products.getById('product_xyz789');

// Update via instance method
await product.update({
  price: 24.99,
  inStock: false
});

// Access updated properties
console.log(product.price);   // 24.99
console.log(product.inStock); // false
```

### save() (Instance Method)

Save the current state of the product to the server. This is a convenience method that persists all changes made to the product instance.

```typescript theme={null}
product.save(): Promise<boolean>
```

**Returns:** Promise resolving to `true` if successful

**Example:**

```typescript theme={null}
const product = await Products.getById('product_xyz789');

// Modify product properties directly
product.price = 24.99;
product.inStock = false;
product.description = "Updated description";

// Save all changes at once
await product.save();

// Much cleaner workflow!
```

<Note>
  **New in Latest Version:** The `save()` method provides a simpler workflow - modify properties then save, rather than calling `Products.update()` with the product ID.
</Note>

### delete()

Delete a product.

```typescript theme={null}
Products.delete(id: string): Promise<DeleteProductResponse>
```

**Example:**

```typescript theme={null}
await Products.delete('product_xyz789');
```

## ProductInstance

All product methods return `ProductInstance` objects with:

**Direct Property Access:**

```typescript theme={null}
product.name
product.price
product.sku
product.inStock
product.category
product.description
// Any custom fields
```

**Instance Methods:**

```typescript theme={null}
await product.update({ price: 999.99 });
await product.save();
await product.delete();
```

**Backward Compatible:**

```typescript theme={null}
product.name;        // ✅ New way
product.data.name;   // ✅ Old way still works
```

## Complete Examples

### Search Tool

```typescript theme={null}
import { LuaTool, Products } from 'lua-cli';
import { z } from 'zod';

export class SearchProductsTool implements LuaTool {
  name = "search_products";
  description = "Search for products by name or description";
  
  inputSchema = z.object({
    query: z.string().describe("Search query"),
    maxPrice: z.number().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const results = await Products.search(input.query);
    
    // Filter by price if specified
    let products = results.products;
    if (input.maxPrice) {
      products = products.filter(p => p.price <= input.maxPrice);
    }
    
    return {
      products: products.map(p => ({
        id: p.id,
        name: p.name,
        price: `$${p.price.toFixed(2)}`,
        inStock: p.inStock ? '✅ In Stock' : '❌ Out of Stock'
      })),
      total: products.length
    };
  }
}
```

### Create Product Tool

```typescript theme={null}
export class CreateProductTool implements LuaTool {
  name = "create_product";
  description = "Add a new product to the catalog";
  
  inputSchema = z.object({
    name: z.string(),
    price: z.number().positive(),
    category: z.string().optional(),
    sku: z.string().optional(),
    description: z.string().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const result = await Products.create({
      ...input,
      inStock: true
    });
    
    return {
      success: true,
      productId: result.id,
      message: `Product "${input.name}" created successfully`
    };
  }
}
```

### Update Stock Tool

```typescript theme={null}
export class UpdateStockTool implements LuaTool {
  name = "update_stock";
  description = "Update product stock status";
  
  inputSchema = z.object({
    productId: z.string(),
    inStock: z.boolean()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    await Products.update(
      { inStock: input.inStock },
      input.productId
    );
    
    return {
      success: true,
      message: `Stock status updated to ${input.inStock ? 'in stock' : 'out of stock'}`
    };
  }
}
```

## Use Cases

### E-commerce Catalog

```typescript theme={null}
// Browse products with pagination
const products = await Products.get({ page: 1, limit: 20 });

// Search specific items (semantic search)
const laptops = await Products.search('laptop');

// Filter by category
const electronics = await Products.get({
  filter: { category: 'Electronics' }
});

// Get product details
const product = await Products.getById(laptops.products[0].id);
```

### Inventory Management

```typescript theme={null}
// Update product price
await Products.update({ price: 899.99 }, productId);

// Mark as out of stock
await Products.update({ inStock: false }, productId);

// Update multiple fields
await Products.update({
  price: 799.99,
  inStock: true,
  category: 'Electronics - Sale'
}, productId);

// Get all out-of-stock products
const outOfStock = await Products.get({
  filter: { inStock: false }
});
```

### Filtering Products

```typescript theme={null}
// By price range
const affordable = await Products.get({
  filter: {
    price: { $gte: 50, $lte: 200 }
  }
});

// By category and availability
const availablePhones = await Products.get({
  filter: {
    category: 'Phones',
    inStock: true
  }
});

// By nested properties
const appleProducts = await Products.get({
  filter: {
    'metadata.brand': 'Apple'
  }
});

// Multiple categories
const gadgets = await Products.get({
  filter: {
    category: { $in: ['Phones', 'Tablets', 'Watches'] }
  }
});
```

### Product Recommendations

```typescript theme={null}
// Search similar products
const similar = await Products.search(product.category);

// Or filter by same category
const sameCategory = await Products.get({
  filter: {
    category: product.category,
    price: {
      $gte: product.price * 0.8,
      $lte: product.price * 1.2
    }
  }
});
```

### Search vs Filter

<Tabs>
  <Tab title="When to Use Search">
    **Use `Products.search()`** for semantic/fuzzy matching:

    * "laptop for students"
    * "wireless headphones"
    * Natural language queries

    ```typescript theme={null}
    // Finds products by meaning, not exact match
    const results = await Products.search('affordable gaming laptop');
    ```
  </Tab>

  <Tab title="When to Use Filter">
    **Use `Products.get()` with filter** for structured queries:

    * Exact category matches
    * Price ranges
    * Stock status
    * Attribute combinations

    ```typescript theme={null}
    // Finds products matching exact criteria
    const results = await Products.get({
      filter: {
        category: 'Laptops',
        price: { $lte: 1000 },
        inStock: true
      }
    });
    ```
  </Tab>
</Tabs>

## Best Practices

<AccordionGroup>
  <Accordion title="Validate Products Exist">
    Always check if product exists:

    ```typescript theme={null}
    const product = await Products.getById(input.productId);

    if (!product) {
      throw new Error(`Product not found: ${input.productId}`);
    }
    ```
  </Accordion>

  <Accordion title="Check Stock Before Adding to Cart">
    ```typescript theme={null}
    const product = await Products.getById(productId);

    if (!product.inStock) {
      return {
        success: false,
        message: `${product.name} is currently out of stock`
      };
    }
    ```
  </Accordion>

  <Accordion title="Format Prices for Display">
    ```typescript theme={null}
    return {
      products: products.map(p => ({
        ...p,
        price: `$${p.price.toFixed(2)}`,
        formattedPrice: new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD'
        }).format(p.price)
      }))
    };
    ```
  </Accordion>

  <Accordion title="Handle Search with No Results">
    ```typescript theme={null}
    const results = await Products.search(query);

    if (results.products.length === 0) {
      return {
        products: [],
        message: `No products found for "${query}". Try different search terms.`
      };
    }
    ```
  </Accordion>
</AccordionGroup>

<Note title="Debugging tip">
  If product results don't look right, log the raw return to see the actual shape:

  ```typescript theme={null}
  const results = await Products.search(input.query);
  console.log('Products.search result:', JSON.stringify(results, null, 2));
  ```

  Then run `lua logs --type skill --limit 5` after a test message to inspect the output. See the [Debugging Skills guide](/cli/debugging) for the full workflow.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Baskets API" icon="cart-shopping" href="/api/baskets">
    Add products to shopping carts
  </Card>

  <Card title="Product Examples" icon="layer-group" href="/examples/products">
    See complete tool examples
  </Card>

  <Card title="Debugging Skills" icon="bug" href="/cli/debugging">
    Inspect runtime return values
  </Card>
</CardGroup>
