Skip to content

Codex/sdk production baseline#1

Closed
labobankcom wants to merge 2 commits into
mainfrom
codex/sdk-production-baseline
Closed

Codex/sdk production baseline#1
labobankcom wants to merge 2 commits into
mainfrom
codex/sdk-production-baseline

Conversation

@labobankcom

@labobankcom labobankcom commented Mar 11, 2026

Copy link
Copy Markdown
Owner

Note

Medium Risk
Refactors core client construction and response-shape handling (normalizing [] vs {data: []}), which could subtly change runtime outputs for consumers. Also introduces new CI/release automation and packaging metadata that affects publishing behavior.

Overview
SDK API update (v1.4.0): Adds createEcommapsClient(config) (custom apiUrl, apiKey, fetch, and default headers) while keeping the default ecommapsClient singleton.

API surface + runtime behavior: Introduces typed store.pages and store.blogs endpoints and tightens typings for products/variants/media, promotions/coupons, and customer addresses. Client methods now normalize common response shapes (e.g., [] vs { data: [] }, and record payloads under data/result) and broaden error message extraction (detail/message).

Project/release baseline: Adds GitHub Actions CI (install/typecheck/build/pack smoke test) and a tag-driven Release workflow (npm publish --provenance), updates package.json metadata/exports/files/engines, adds pnpm-lock.yaml, and removes committed dist/ and node_modules/ artifacts (now ignored).

Written by Cursor Bugbot for commit 400b38c. This will update automatically on new commits. Configure here.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a production baseline for the Codex/SDK, focusing on enhancing the Ecommaps client with new features, improved type safety, and consistent data handling while ensuring backward compatibility. It provides developers with more flexibility in configuring the client and accessing store data.

Highlights

  • SDK Initialization: Introduces a factory function createEcommapsClient(config) for creating client instances with custom configurations, alongside the existing default singleton client.
  • Typed Store Endpoints: Adds strongly typed Store endpoints for pages, blogs, and coupon validation, enhancing type safety and developer experience.
  • Improved Type Coverage: Enhances type coverage for product variants, media payloads, promotions, coupons, and customer addresses, providing more robust type checking.
  • Normalized List Payload Handling: Standardizes list payload handling to support both [] and { data: [] } formats, ensuring consistent data retrieval.
  • Backward Compatibility: Maintains backward compatibility by preserving the default ecommapsClient export behavior, preventing breaking changes for existing applications.
Changelog
  • CHANGELOG.md
    • Added createEcommapsClient(config) factory.
    • Added typed Store endpoints: store.pages.list/retrieve, store.blogs.list/retrieve, store.coupons.validate (typed payload/response).
    • Improved type coverage for product variants, option values, product/media payloads, promotions/coupons, and customer addresses.
    • Normalized list payload handling ([] and { data: [] }) inside client methods.
    • Kept ecommapsClient default export behavior (non-breaking for existing apps).
  • MIGRATION.md
    • Added migration notes for v1.4.x, emphasizing backward compatibility and new typed capabilities.
    • Highlighted new features: createEcommapsClient(config), store.pages.list/retrieve, store.blogs.list/retrieve, typed store.coupons.validate, and stronger types for various entities.
    • Confirmed no breaking changes, ensuring existing ecommapsClient imports and cart/auth/products/orders calls remain functional.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/ci.yml
    • .github/workflows/release.yml
Activity
  • New files added: .gitignore, CHANGELOG.md, MIGRATION.md.
  • README.md was modified to reflect the new SDK structure and features.
  • Several files in dist/ and node_modules/ were removed, likely due to a cleanup or restructuring of the build output.
  • package.json was updated to version 1.4.0 with new scripts, dependencies, and configuration options.
  • pnpm-lock.yaml was added to manage dependencies.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Free Tier Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Search query parameter silently overridden by params spread
    • I changed the merge order in products.search to { ...(params || {}), q } so the explicit q argument always takes precedence over any params.q value.

Create PR

Or push these changes by commenting:

@cursor push 78596dfa8c
Preview (78596dfa8c)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -1,0 +1,36 @@
+name: CI
+
+on:
+  pull_request:
+  push:
+    branches: [main]
+
+jobs:
+  quality:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: 10.28.0
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: 20
+          cache: pnpm
+
+      - name: Install
+        run: pnpm install --frozen-lockfile
+
+      - name: Typecheck
+        run: pnpm typecheck
+
+      - name: Build
+        run: pnpm build
+
+      - name: Package smoke test
+        run: npm pack --dry-run

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -1,0 +1,40 @@
+name: Release
+
+on:
+  workflow_dispatch:
+  push:
+    tags:
+      - "v*"
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      id-token: write
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: 10.28.0
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: 20
+          registry-url: https://registry.npmjs.org
+          cache: pnpm
+
+      - name: Install
+        run: pnpm install --frozen-lockfile
+
+      - name: Build
+        run: pnpm build
+
+      - name: Publish to npm
+        run: npm publish --access public --provenance
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

diff --git a/.gitignore b/.gitignore
new file mode 100644
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,8 @@
+node_modules/
+dist/
+.env
+.env.*
+*.log
+.DS_Store
+coverage/
+.npmrc

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -1,0 +1,16 @@
+# Changelog:
+
+## 1.4.0
+
+- Added `createEcommapsClient(config)` factory.
+- Added typed Store endpoints:
+  - `store.pages.list/retrieve`
+  - `store.blogs.list/retrieve`
+  - `store.coupons.validate` (typed payload/response)
+- Improved type coverage for:
+  - product variants and option values
+  - product/media payloads
+  - promotions/coupons
+  - customer addresses
+- Normalized list payload handling (`[]` and `{ data: [] }`) inside client methods.
+- Kept `ecommapsClient` default export behavior (non-breaking for existing apps).

diff --git a/MIGRATION.md b/MIGRATION.md
new file mode 100644
--- /dev/null
+++ b/MIGRATION.md
@@ -1,0 +1,16 @@
+# Migration Notes (v1.4.x)
+
+`@ecommaps/client` remains backward compatible while adding new typed capabilities.
+
+## New in v1.4.x
+
+- `createEcommapsClient(config)`
+- `store.pages.list/retrieve`
+- `store.blogs.list/retrieve`
+- typed `store.coupons.validate`
+- stronger product/variant/promotion/page/blog types
+
+## No breaking changes
+
+- Existing `ecommapsClient` import still works.
+- Existing cart/auth/products/orders calls keep the same usage.

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,144 +1,114 @@
 # @ecommaps/client
 
-[![NPM Version](https://img.shields.io/npm/v/@ecommaps/client)](https://www.npmjs.com/package/@ecommaps/client)
-[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
+Official TypeScript SDK for Ecommaps storefront APIs.
 
-The official, high-performance JavaScript/TypeScript SDK for building custom, headless storefronts on the **Ecommaps** ecosystem. Specifically tailored for the **Algerian (DZ)**, **North African (MENA)**, and **Middle Eastern** e-commerce markets. Designed for developers and AI agents to build, scale, and manage modern commerce experiences with sub-second performance and local feature support (DZD currency, local addresses, and region-specific logic).
+## 1. Positioning
 
----
+`@ecommaps/client` is the Core API SDK layer.
 
-## 🚀 Quick Start
+Use this package when you need:
+- direct access to Storefront endpoints
+- strongly typed contracts for products, cart, auth, pages, blogs, and coupons
+- environment-agnostic API consumption in Next.js, Node.js, Edge, or workers
 
-### Installation
+If you need commerce logic (variant resolver, promotion classifier), use `@ecommaps/storefront-kit`.
 
+## 2. Installation
+
 ```bash
-npm install @ecommaps/client
-# or
 pnpm add @ecommaps/client

-### Configuration
+## 3. Configuration

-The client automatically reads from environment variables but can also be configured per request.
+### Environment variables

-```typescript
-import { ecommapsClient } from "@ecommaps/client";

-// Set these in your .env
-// NEXT_PUBLIC_ECOMMAPS_API_URL=https://api.ecommaps.com/api/v1/storefront
-// ECOMMAPS_API_KEY=your_store_key
+```env
+NEXT_PUBLIC_ECOMMAPS_API_URL=https://api.ecommaps.com/api/v1/storefront
+ECOMMAPS_API_KEY=your_storefront_api_key


----
+### Default singleton client

-## 🛠 Features & Examples
+```ts
+import { ecommapsClient } from "@ecommaps/client";
+```

-### 🔐 Authentication & Customer Profile
-Handle customer sessions using industry-standard JWT.
+### Factory client

-```typescript
-// Login
-const { token, user } = await ecommapsClient.auth.login({ 
-  email: "customer@example.com", 
-  password: "secure_password" 
-});
+```ts
+import { createEcommapsClient } from "@ecommaps/client";

-// Get Current User Profile (Authenticated)
-const { customer } = await ecommapsClient.auth.me({
-  headers: { Authorization: `Bearer ${token}` }
+const client = createEcommapsClient({
+  apiUrl: "https://api.ecommaps.com/api/v1/storefront",
+  apiKey: process.env.ECOMMAPS_API_KEY!,
});
-
-// Add Address to Customer Profile
-await ecommapsClient.auth.addAddress({
-  line1: "123 Main St",
-  city: "Algiers",
-  state: "16",
-  country: "DZ",
-  postal_code: "16000",
-  phone: "0555000000",
-  label: "Home"
-}, {
-  headers: { Authorization: `Bearer ${token}` }
-});

-### 📦 Products & Collections
-Retrieve catalog data with built-in pagination.
+## 4. API Surface

-```typescript
-// List Products
-const products = await ecommapsClient.products.list({ limit: 10, offset: 0 });
+### Store

-// Retrieve Single Product by Slug
-const product = await ecommapsClient.products.retrieve("summer-t-shirt");

-// List Collections
-const { data: collections } = await ecommapsClient.collections.list();
+```ts
+await ecommapsClient.store.retrieve();
+await ecommapsClient.store.menus.list();
+await ecommapsClient.store.menus.retrieve("main-menu");
+await ecommapsClient.store.pages.list();
+await ecommapsClient.store.pages.retrieve("refund");
+await ecommapsClient.store.blogs.list();
+await ecommapsClient.store.blogs.retrieve("new-arrivals");
+await ecommapsClient.store.coupons.validate({ code: "DISCOUNT20", cart_total: 10000, items: [] });


-### 🛒 Cart Management
-Highly flexible cart APIs optimized for sub-second performance.
+### Products / Collections

-```typescript
-// Create a new Cart
-const cart = await ecommapsClient.cart.create();
-
-// Add Item
-await ecommapsClient.cart.addItem(cart.id, {
-  product_id: "prod_123",
-  quantity: 2
-});
-
-// Retrieve Cart
-const currentCart = await ecommapsClient.cart.retrieve(cart.id);
+```ts
+await ecommapsClient.products.list({ limit: 12, offset: 0 });
+await ecommapsClient.products.retrieve("product-slug");
+await ecommapsClient.products.search("shirt", { limit: 10 });
+await ecommapsClient.collections.list();
+await ecommapsClient.collections.retrieve("menswear", 20, 0);

-### 🧾 Orders
-Seamless order management from creation to tracking.
+### Cart / Orders / Auth

-```typescript
-// List Customer Orders (Authenticated)
-const { data: orders } = await ecommapsClient.orders.list({

  • headers: { Authorization: Bearer ${token} },
  • params: { limit: 5 }
    -});

-// Retrieve Order Details
-const order = await ecommapsClient.orders.retrieve("ECORD-2024-XXXX");
+```ts
+const cart = await ecommapsClient.cart.create();
+await ecommapsClient.cart.addItem(cart.id, { product_id: "product_uuid", variant_id: "variant_uuid", quantity: 1 });
+await ecommapsClient.cart.retrieve(cart.id);
+await ecommapsClient.orders.create({ cart_id: cart.id, customer_name: "Name", customer_phone: "0555000000" });
+await ecommapsClient.auth.login({ email: "user@mail.com", password: "secret" });


----
+## 5. Error handling

-## 🤖 AI Agent Integration (Skills)
+All non-2xx responses throw `EcommapsAPIError`:

-This SDK is **Agent-Ready**. If you are an AI agent building for a user, follow these conventions:
+```ts
+import { EcommapsAPIError } from "@ecommaps/client";

-1. **Mapping**: Always map the backend JSON structure to the UI components (e.g., Use `line1` for `address_1`, `name` for `title`).
-2. **Context**: Use `ecommapsClient.auth.me` to verify user status before attempting protected operations.
-3. **Optimistic UI**: Use the returned data from cart/order operations to update local state immediately.
+try {
+  await ecommapsClient.products.retrieve("missing");
+} catch (error) {
+  if (error instanceof EcommapsAPIError) {
+    console.error(error.status, error.message);
+  }
+}
+```

----
+## 6. Compatibility

-## 🏗 Developing the Storefront
+- Runtime: Node.js 20+
+- Module formats: ESM + CJS
+- TypeScript: full declaration files included
+- Backward compatibility: `ecommapsClient` default singleton remains supported

-To build a complete store:
-1. Initialize a Next.js 14+ project.
-2. Install `@ecommaps/client`.
-3. Use Next.js **Server Actions** to wrap SDK calls (see `auth` and `cart`).
-4. Implement **Dynamic Routing** (`/[slug]`) using `products.retrieve`.
+## 7. Migration

----
+See [MIGRATION.md](./MIGRATION.md).

-## 🌍 Universal Compatibility
+## 8. Release policy

-The Ecommaps SDK is built using standard Fetch API and is environment-agnostic. It works seamlessly across:
-- **Web Frameworks**: React, Next.js, Vue, Nuxt, Svelte, Angular.
-- **Mobile**: React Native, Expo, Ionic.
-- **Server-side**: Node.js, Bun, Deno, Edge Functions (Vercel, Cloudflare).
-- **AI Agents**: Highly optimized for AI-driven development and autonomous agents.
-
----
-
-## 📄 License
-
-Distributed under the ISC License. © 2026 ecommaps.com
+- Semver is enforced.
+- Breaking changes are released only in major versions.
+- Every published change must include changelog notes.

diff --git a/dist/index.d.mts b/dist/index.d.mts
deleted file mode 100644
--- a/dist/index.d.mts
+++ /dev/null
@@ -1,228 +1,0 @@
-interface EcommapsMenuItem {
-    title: string;
-    url: string | null;
-    type?: string | null;
-    position?: number;
-    children?: EcommapsMenuItem[];
-}
-interface EcommapsMenu {
-    id: string;
-    title: string;
-    handle: string;
-    items: EcommapsMenuItem[];
-}
-interface EcommapsSite {
-    id: string;
-    name: string;
-    slug: string;
-    description: string | null;
-    logo_url: string | null;
-    custom_domain: string | null;
-    theme: any | null;
-    theme_settings: any | null;
-    settings: any | null;
-    contact_info: any | null;
-    social_links: any | null;
-}
-interface EcommapsProductVariant {
-    title: string;
-    prices: any[];
-    options: any[];
-    inventory_quantity: number;
-    sku: string | null;
-    id?: string;
-}
-interface EcommapsProduct {
-    id: string;
-    site_id: string;
-    name: string;
-    slug: string;
-    description: string | null;
-    price: number;
-    compare_at_price: number | null;
-    currency: string | null;
-    images: any | null;
-    category: string | null;
-    tags: string[] | null;
-    inventory_quantity: number | null;
-    is_active: boolean | null;
-    created_at: string | null;
-    updated_at: string | null;
-    sku: string | null;
-    barcode: string | null;
-    cost_per_item: number | null;
-    track_quantity: boolean | null;
-    continue_selling_when_out_of_stock: boolean | null;
-    weight: number | null;
-    weight_unit: string | null;
-    vendor: string | null;
-    product_type: string | null;
-    options: any | null;
-    variants: any | null;
-    charge_tax: boolean | null;
-    is_physical: boolean | null;
-    discounted_price?: number;
-    discount_text?: string;
-}
-interface EcommapsCollection {
-    id: string;
-    site_id: string;
-    title: string;
-    description: string | null;
-    slug: string;
-    image_url: string | null;
-    is_active: boolean | null;
-    sort_order: string | null;
-    created_at: string;
-    updated_at: string;
-}
-interface PaginationMeta {
-    total: number;
-    limit: number;
-    offset: number;
-    has_more: boolean;
-}
-interface PaginatedResponse<T> {
-    data: T[];
-    pagination: PaginationMeta;
-}
-interface EcommapsCartItem {
-    id: string;
-    product_id: string;
-    variant_id: string | null;
-    product_name: string;
-    product_price: number;
-    product_image: string | null;
-    quantity: number;
-    subtotal: number;
-}
-interface EcommapsCart {
-    id: string;
-    site_id: string;
-    items: EcommapsCartItem[];
-    items_count: number;
-    subtotal: number;
-    created_at: string | null;
-}
-interface EcommapsCustomer {
-    id: string;
-    store_id: string;
-    email: string;
-    full_name: string;
-    phone: string | null;
-    addresses: any[];
-    created_at: string;
-}
-interface EcommapsAuthResponse {
-    token: string;
-    user: EcommapsCustomer;
-}
-interface EcommapsSearchResponse {
-    data: EcommapsProduct[];
-    query: string;
-    pagination: PaginationMeta;
-}
-interface EcommapsAppliedDiscount {
-    id?: string;
-    code?: string;
-    discount_type?: "percentage" | "fixed_amount" | "free_shipping";
-    discount_value?: number;
-    discount_amount?: number;
-    promotion_type?: string;
-    target_type?: string;
-    message?: string;
-}
-interface EcommapsCouponValidateResponse {
-    valid: boolean;
-    applied_discounts?: EcommapsAppliedDiscount[];
-    code?: string;
-    discount_type?: "percentage" | "fixed_amount" | "free_shipping";
-    discount_value?: number;
-    discount_amount?: number;
-    target_type?: string;
-    promotion_type?: string;
-    message?: string;
-}
-
-declare class EcommapsAPIError extends Error {
-    status: number;
-    constructor(message: string, status: number);
-}
-declare const ecommapsClient: {
-    store: {
-        retrieve: (options?: RequestInit) => Promise<EcommapsSite>;
-        menus: {
-            list: (options?: RequestInit) => Promise<EcommapsMenu[]>;
-            retrieve: (handle: string, options?: RequestInit) => Promise<EcommapsMenu>;
-        };
-        coupons: {
-            validate: (body: {
-                code: string;
-                cart_total?: number;
-                items?: any[];
-            }, options?: RequestInit) => Promise<EcommapsCouponValidateResponse>;
-        };
-    };
-    products: {
-        list: (params?: Record<string, string | number>, options?: RequestInit) => Promise<PaginatedResponse<EcommapsProduct>>;
-        retrieve: (slug: string, options?: RequestInit) => Promise<EcommapsProduct>;
-        search: (q: string, params?: Record<string, string | number>, options?: RequestInit) => Promise<EcommapsSearchResponse>;
-    };
-    collections: {
-        list: (options?: RequestInit) => Promise<{
-            data: EcommapsCollection[];
-        }>;
-        retrieve: (slug: string, limit?: number, offset?: number, options?: RequestInit) => Promise<{
-            collection: EcommapsCollection;
-            products: EcommapsProduct[];
-            pagination: any;
-        }>;
-    };
-    cart: {
-        create: (options?: RequestInit) => Promise<EcommapsCart>;
-        retrieve: (cartId: string, options?: RequestInit) => Promise<EcommapsCart>;
-        addItem: (cartId: string, body: {
-            product_id: string;
-            variant_id?: string;
-            quantity: number;
-        }, options?: RequestInit) => Promise<EcommapsCart>;
-        updateItem: (cartId: string, itemId: string, body: {
-            quantity: number;
-        }, options?: RequestInit) => Promise<EcommapsCart>;
-        removeItem: (cartId: string, itemId: string, options?: RequestInit) => Promise<EcommapsCart>;
-    };
-    orders: {
-        create: (body: any, options?: RequestInit) => Promise<any>;
-        retrieve: (orderNumber: string, options?: RequestInit) => Promise<any>;
-        list: (options?: RequestInit & {
-            params?: {
-                limit?: number;
-                offset?: number;
-            };
-        }) => Promise<{
-            data: any[];
-            pagination: any;
-        }>;
-    };
-    auth: {
-        login: (body: any, options?: RequestInit) => Promise<EcommapsAuthResponse>;
-        signup: (body: any, options?: RequestInit) => Promise<EcommapsAuthResponse>;
-        me: (options?: RequestInit) => Promise<{
-            customer: EcommapsCustomer;
-        }>;
-        addAddress: (body: any, options?: RequestInit) => Promise<{
-            success: boolean;
-            address: any;
-        }>;
-        setDefaultAddress: (addressId: string, options?: RequestInit) => Promise<{
-            success: boolean;
-            addresses: any[];
-        }>;
-        deleteAddress: (addressId: string, options?: RequestInit) => Promise<{
-            success: boolean;
-            addresses: any[];
-        }>;
-    };
-};
-
-export { EcommapsAPIError, type EcommapsAppliedDiscount, type EcommapsAuthResponse, type EcommapsCart, type EcommapsCartItem, type EcommapsCollection, type EcommapsCouponValidateResponse, type EcommapsCustomer, type EcommapsMenu, type EcommapsMenuItem, type EcommapsProduct, type EcommapsProductVariant, type EcommapsSearchResponse, type EcommapsSite, type PaginatedResponse, type PaginationMeta, ecommapsClient };
\ No newline at end of file

diff --git a/dist/index.d.ts b/dist/index.d.ts
deleted file mode 100644
--- a/dist/index.d.ts
+++ /dev/null
@@ -1,228 +1,0 @@
-interface EcommapsMenuItem {
-    title: string;
-    url: string | null;
-    type?: string | null;
-    position?: number;
-    children?: EcommapsMenuItem[];
-}
-interface EcommapsMenu {
-    id: string;
-    title: string;
-    handle: string;
-    items: EcommapsMenuItem[];
-}
-interface EcommapsSite {
-    id: string;
-    name: string;
-    slug: string;
-    description: string | null;
-    logo_url: string | null;
-    custom_domain: string | null;
-    theme: any | null;
-    theme_settings: any | null;
-    settings: any | null;
-    contact_info: any | null;
-    social_links: any | null;
-}
-interface EcommapsProductVariant {
-    title: string;
-    prices: any[];
-    options: any[];
-    inventory_quantity: number;
-    sku: string | null;
-    id?: string;
-}
-interface EcommapsProduct {
-    id: string;
-    site_id: string;
-    name: string;
-    slug: string;
-    description: string | null;
-    price: number;
-    compare_at_price: number | null;
-    currency: string | null;
-    images: any | null;
-    category: string | null;
-    tags: string[] | null;
-    inventory_quantity: number | null;
-    is_active: boolean | null;
-    created_at: string | null;
-    updated_at: string | null;
-    sku: string | null;
-    barcode: string | null;
-    cost_per_item: number | null;
-    track_quantity: boolean | null;
-    continue_selling_when_out_of_stock: boolean | null;
-    weight: number | null;
-    weight_unit: string | null;
-    vendor: string | null;
-    product_type: string | null;
-    options: any | null;
-    variants: any | null;
-    charge_tax: boolean | null;
-    is_physical: boolean | null;
-    discounted_price?: number;
-    discount_text?: string;
-}
-interface EcommapsCollection {
-    id: string;
-    site_id: string;
-    title: string;
-    description: string | null;
-    slug: string;
-    image_url: string | null;
-    is_active: boolean | null;
-    sort_order: string | null;
-    created_at: string;
-    updated_at: string;
-}
-interface PaginationMeta {
-    total: number;
-    limit: number;
-    offset: number;
-    has_more: boolean;
-}
-interface PaginatedResponse<T> {
-    data: T[];
-    pagination: PaginationMeta;
-}
-interface EcommapsCartItem {
-    id: string;
-    product_id: string;
-    variant_id: string | null;
-    product_name: string;
-    product_price: number;
-    product_image: string | null;
-    quantity: number;
-    subtotal: number;
-}
-interface EcommapsCart {
-    id: string;
-    site_id: string;
-    items: EcommapsCartItem[];
-    items_count: number;
-    subtotal: number;
-    created_at: string | null;
-}
-interface EcommapsCustomer {
-    id: string;
-    store_id: string;
-    email: string;
-    full_name: string;
-    phone: string | null;
-    addresses: any[];
-    created_at: string;
-}
-interface EcommapsAuthResponse {
-    token: string;
-    user: EcommapsCustomer;
-}
-interface EcommapsSearchResponse {
-    data: EcommapsProduct[];
-    query: string;
-    pagination: PaginationMeta;
-}
-interface EcommapsAppliedDiscount {
-    id?: string;
-    code?: string;
-    discount_type?: "percentage" | "fixed_amount" | "free_shipping";
-    discount_value?: number;
-    discount_amount?: number;
-    promotion_type?: string;
-    target_type?: string;
-    message?: string;
-}
-interface EcommapsCouponValidateResponse {
-    valid: boolean;
-    applied_discounts?: EcommapsAppliedDiscount[];
-    code?: string;
-    discount_type?: "percentage" | "fixed_amount" | "free_shipping";
-    discount_value?: number;
-    discount_amount?: number;
-    target_type?: string;
-    promotion_type?: string;
-    message?: string;
-}
-
-declare class EcommapsAPIError extends Error {
-    status: number;
-    constructor(message: string, status: number);
-}
-declare const ecommapsClient: {
-    store: {
-        retrieve: (options?: RequestInit) => Promise<EcommapsSite>;
-        menus: {
-            list: (options?: RequestInit) => Promise<EcommapsMenu[]>;
-            retrieve: (handle: string, options?: RequestInit) => Promise<EcommapsMenu>;
-        };
-        coupons: {
-            validate: (body: {
-                code: string;
-                cart_total?: number;
-                items?: any[];
-            }, options?: RequestInit) => Promise<EcommapsCouponValidateResponse>;
-        };
-    };
-    products: {
-        list: (params?: Record<string, string | number>, options?: RequestInit) => Promise<PaginatedResponse<EcommapsProduct>>;
-        retrieve: (slug: string, options?: RequestInit) => Promise<EcommapsProduct>;
-        search: (q: string, params?: Record<string, string | number>, options?: RequestInit) => Promise<EcommapsSearchResponse>;
-    };
-    collections: {
-        list: (options?: RequestInit) => Promise<{
-            data: EcommapsCollection[];
-        }>;
-        retrieve: (slug: string, limit?: number, offset?: number, options?: RequestInit) => Promise<{
-            collection: EcommapsCollection;
-            products: EcommapsProduct[];
-            pagination: any;
-        }>;
-    };
-    cart: {
-        create: (options?: RequestInit) => Promise<EcommapsCart>;
-        retrieve: (cartId: string, options?: RequestInit) => Promise<EcommapsCart>;
-        addItem: (cartId: string, body: {
-            product_id: string;
-            variant_id?: string;
-            quantity: number;
-        }, options?: RequestInit) => Promise<EcommapsCart>;
-        updateItem: (cartId: string, itemId: string, body: {
-            quantity: number;
-        }, options?: RequestInit) => Promise<EcommapsCart>;
-        removeItem: (cartId: string, itemId: string, options?: RequestInit) => Promise<EcommapsCart>;
... diff truncated: showing 800 of 3276 lines

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread src/client.ts
},
retrieve: (slug: string, options?: RequestInit) => ecommapsFetch<EcommapsProduct>(`/products/${slug}`, options),
search: async (q: string, params?: QueryParams, options?: RequestInit) => {
const payload = await ecommapsFetch<unknown>(buildEndpoint("/search", { q, ...(params || {}) }), options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search query parameter silently overridden by params spread

Medium Severity

In products.search, the call buildEndpoint("/search", { q, ...(params || {}) }) allows params to silently override the explicit q argument because the object spread of params comes after q. If a caller passes { q: "other" } inside params, the dedicated q function argument is discarded. The previous implementation used URLSearchParams.append which preserved the first q and added params separately, so this is a behavior regression.

Fix in Cursor Fix in Web

@labobankcom labobankcom force-pushed the codex/sdk-production-baseline branch from 6983ad5 to 400b38c Compare March 11, 2026 00:22

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant and well-executed refactoring that establishes a solid production baseline for the SDK, introducing a configurable client factory, improved type safety, response normalization, and modern packaging practices. However, a recurring security issue was identified where user-supplied parameters are directly interpolated into URL paths without sanitization, which could lead to path traversal vulnerabilities if the SDK is used with untrusted input. It is recommended to apply encodeURIComponent() to all path parameters. Additionally, a couple of suggestions have been provided to further enhance maintainability and correctness.

Comment thread src/client.ts
options
)
retrieve: async (handle: string, options?: RequestInit) => {
const payload = await ecommapsFetch<unknown>(`/store/menus/${handle}`, options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The handle parameter is interpolated directly into the URL path without sanitization. If an attacker can control this value, they could perform a path traversal attack (e.g., by passing ../../another/endpoint) to access unintended API resources. Use encodeURIComponent() to sanitize the parameter.

Suggested change
const payload = await ecommapsFetch<unknown>(`/store/menus/${handle}`, options);
const payload = await ecommapsFetch<unknown>(`/store/menus/${encodeURIComponent(handle)}`, options);

Comment thread src/client.ts
return normalizeArrayPayload<EcommapsPage>(payload);
},
retrieve: async (slug: string, options?: RequestInit) => {
const payload = await ecommapsFetch<unknown>(`/pages/${slug}`, options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The slug parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

Suggested change
const payload = await ecommapsFetch<unknown>(`/pages/${slug}`, options);
const payload = await ecommapsFetch<unknown>(`/pages/${encodeURIComponent(slug)}`, options);

Comment thread src/client.ts
return normalizeArrayPayload<EcommapsBlog>(payload);
},
retrieve: async (slug: string, options?: RequestInit) => {
const payload = await ecommapsFetch<unknown>(`/blogs/${slug}`, options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The slug parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

Suggested change
const payload = await ecommapsFetch<unknown>(`/blogs/${slug}`, options);
const payload = await ecommapsFetch<unknown>(`/blogs/${encodeURIComponent(slug)}`, options);

Comment thread src/client.ts
const payload = await ecommapsFetch<unknown>(buildEndpoint("/products", params), options);
return normalizePaginatedPayload<EcommapsProduct>(payload);
},
retrieve: (slug: string, options?: RequestInit) => ecommapsFetch<EcommapsProduct>(`/products/${slug}`, options),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The slug parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

Suggested change
retrieve: (slug: string, options?: RequestInit) => ecommapsFetch<EcommapsProduct>(`/products/${slug}`, options),
retrieve: (slug: string, options?: RequestInit) => ecommapsFetch<EcommapsProduct>(`/products/${encodeURIComponent(slug)}`, options),

Comment thread src/client.ts
collection: EcommapsCollection;
products: EcommapsProduct[];
pagination: unknown;
}>(`/collections/${slug}?limit=${limit}&offset=${offset}`, options),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The slug parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

Suggested change
}>(`/collections/${slug}?limit=${limit}&offset=${offset}`, options),
}>(`/collections/${encodeURIComponent(slug)}?limit=${limit}&offset=${offset}`, options),

Comment thread src/client.ts
method: "POST",
body: JSON.stringify(body),
}),
retrieve: (orderNumber: string, options?: RequestInit) => ecommapsFetch<unknown>(`/orders/${orderNumber}`, options),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The orderNumber parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

Suggested change
retrieve: (orderNumber: string, options?: RequestInit) => ecommapsFetch<unknown>(`/orders/${orderNumber}`, options),
retrieve: (orderNumber: string, options?: RequestInit) => ecommapsFetch<unknown>(`/orders/${encodeURIComponent(orderNumber)}`, options),

Comment thread src/client.ts
body: JSON.stringify(body),
}),
setDefaultAddress: (addressId: string, options?: RequestInit) =>
ecommapsFetch<{ success: boolean; addresses: unknown[] }>(`/auth/me/addresses/${addressId}/default`, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The addressId parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

        ecommapsFetch<<{ success: boolean; addresses: unknown[] }>(`/auth/me/addresses/${encodeURIComponent(addressId)}/default`, {

Comment thread src/client.ts
method: "PATCH",
}),
deleteAddress: (addressId: string, options?: RequestInit) =>
ecommapsFetch<{ success: boolean; addresses: unknown[] }>(`/auth/me/addresses/${addressId}`, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The addressId parameter is interpolated directly into the URL path without sanitization. This could allow path traversal attacks if the input is not trusted. Use encodeURIComponent() to sanitize the parameter.

        ecommapsFetch<{ success: boolean; addresses: unknown[] }>(`/auth/me/addresses/${encodeURIComponent(addressId)}`, {

Comment thread package.json
"clean": "rm -rf dist"
},
"devDependencies": {
"@types/node": "^25.3.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The version specified for @types/node is ^25.3.0. This appears to be a typo, as Node.js v25 does not exist. The engines field in this package.json specifies node: ">=20", so the types should align with a supported Node.js version. Using an invalid version for types can lead to incorrect type definitions and compatibility issues. I suggest correcting this to a stable version range compatible with Node 20.

Suggested change
"@types/node": "^25.3.0",
"@types/node": "^20.0.0",

Comment thread src/types.ts
Comment on lines +64 to +65
option_values?: Record<string, string>;
options?: string[] | Record<string, string> | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The EcommapsProductVariant interface includes both option_values and options. This could be confusing for consumers of the SDK as they seem to serve a similar purpose. The name option_values is more descriptive than options.

To improve clarity, I recommend adding TSDoc comments to explain the purpose of each field and mark options as deprecated if option_values is the preferred property going forward.

Here is a suggested implementation:

  /**
   * The specific option values for this variant, e.g., `{ "Size": "Large", "Color": "Red" }`.
   * This is the preferred way to access variant options.
   */
  option_values?: Record<string, string>;
  /**
   * @deprecated Use `option_values` instead. This field is kept for backward compatibility.
   */
  options?: string[] | Record<string, string> | null;

@labobankcom labobankcom deleted the codex/sdk-production-baseline branch March 11, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant